Skip to content

Commit

Permalink
updated
Browse files Browse the repository at this point in the history
  • Loading branch information
yulmwu committed Oct 25, 2024
1 parent 0753f8f commit 47023cd
Showing 1 changed file with 111 additions and 3 deletions.
114 changes: 111 additions & 3 deletions _posts/fast/2024-10-25-os.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ tags: ["컴퓨터", "하드웨어", "CPU"]
2. [operand](#1-3-1-2-operand)
3. [메모리 주소 지정 방식](#1-3-1-3-메모리-주소-지정-방식)
2. [프로그래밍 언어론(PLT)](#1-3-2-프로그래밍-언어론)
1. [고급 언어와 저급 언어](#1-3-2-1-고급-언어와-저급-언어)
2. [컴파일](#1-3-2-2-컴파일)
3. [인터프리트](#1-3-2-3-인터프리트)
4. [하이브리드 컴파일러](#1-3-2-4-하이브리드-컴파일러)
2. [컴퓨터 구조 %%](#2-컴퓨터-구조)
1. [CPU](#2-1-cpu)
1. [ALU(산술논리연산장치)](#2-1-1-alu)
Expand Down Expand Up @@ -88,7 +92,7 @@ tags: ["컴퓨터", "하드웨어", "CPU"]

이 글에서 설명한 방법은 후자를 택하여 계산해볼 예정이다. 만약 `1011`이라는 2진법을 10진수로 변환해보자.

```m
```
1 0 1 1 b
8 4 2 1 d
Expand Down Expand Up @@ -315,12 +319,112 @@ opcode엔 다양한 역할의 opcode가 존재하는데, 크게 아래와 같은

추가적으로 주소 지정 방식의 속도를 빠른 순으로 정리하면 다음과 같다.

```r
```
묵시적 > 즉시 > 레지스터 > 직접 > 레지스터 간접 > 간접 > 변위
```

### 1-3-2. 프로그래밍 언어론

여기까지 열심히 읽었다면 의문이 들 수 있는 내용인데, 우리가 프로그래밍을 할 때 컴퓨터는 '이 소스 코드[^plt0]를 어떻게 해석할까?' 라는 내용이다. 컴퓨터는 0과 1만 이해할 수 있는데 프로그래밍 언어는 문자로 이루어져 있기 때문이다.

#### 1-3-2-1. 고급 언어와 저급 언어

먼저 아래 내용을 설명하기 전에 **고급 언어**(High Level)와 **저급 언어**(Low Level)로 나뉘게 된다.
우리가 일반적으로 쓰는 프로그래밍 언어는 대부분 고급 언어일것이다. 간단히 사람이 이해하기 쉽게 만들어진 언어이다.

저급 언어엔 두가지가 있는데, **어셈블리어(Assemble language)****기계어(Machine Code)**이다. **기계어**는 말 그대로 기계가 이해할 수 있는, 0과 1로만 이루어진 코드이다. 이는 컴퓨터가 직접 이해할 수 있는 반면, 0과 1로만 이루어져 있기 때문에 사람이 읽기가 매우 어렵다. 때문에 이러한 기계어의 명령어를 1:1 대응 문자[^plt1]로 대응시킨것이 **어셈블리어**이다.

어셈블리어는 아래와 같은 형식으로, 다음은 고급 언어를 어셈블리어로 번역시킨 것이다[^plt2].

```c
int a = 1;
int b = 2;
int c = a + b;
```

```s
section .data
a dd 1 ; a = 1
b dd 2 ; b = 2
c dd 0 ; c = 0 (초기화)
section .text
global _start
_start:
; a와 b를 더하여 c에 저장
mov eax, [a] ; a의 값을 레지스터 eax로 로드
add eax, [b] ; eax에 b의 값을 더함
mov [c], eax ; 결과를 c에 저장
; 프로그램 종료
mov rax, 60 ; 시스템 호출: exit
xor rdi, rdi ; 종료 코드 0
syscall
```

이 처럼 어셈블리어는 기계어와 1:1 대응으로 명령어와 피연산자로 이루어져있다. 하지만 지금도 비전공자가 읽기엔 어려움이 있고, 때문에 고급 언어가 등장하게 된다. 고급 언어는 일반적으로 어셈블리어로 번역되고, 그 어셈블리어가 1:1 대응되어 기계어로 번역된다.

이는 즉 소스 코드를 0과 1로 바꿔(번역되어) 실행이 된다는 얘기인데, 이 과정은 크게 **컴파일(Compile)** 또는 **인터프리트(Interpret)** 과정을 통해 이루어진다.

#### 1-3-2-2. 컴파일

컴파일 과정은 소스코드를 실행 전 전부 0과 1[^binary]로 이루어진 기계어(명령어의 집합)로 번역된다.

대표적으로 C 언어를 예를 들 수 있는데, C 언어의 컴파일 과정은 크게 다음과 같다:

```
전처리 -> 컴파일 -> 어셈블 -> 링크
```

1. **전처리(preprocess)**
* C에서 `#include`는 외부 라이브러리나 파일을 가져오는 전처리기다. 내부적으로 어떻게 작동할까? 정답은 가져온 파일의 내용을 그대로[^pp1] 복사하여 가져오는 방식이다. 이 과정은 보이지 않는데, 이를 **전처리** 과정을 통해 처리하게 된다. 즉 컴파일하기 전 처리하는 과정을 의미한다. 파일 확장자는 `.i`로 출력된다.
2. **컴파일(compile)**
* 전처리기에서 전처리된 코드를 어셈블리어로 번역하는 과정이다. 예시는 위 [고급 언어와 저급 언어](#1-3-2-1-고급-언어와-저급-언어)에서 서술하였다. 파일 확자자는 `.s`로 출력된다.
3. **어셈블(assemble)**
* 이제 어셈블리어로된 코드를 기계어로 번역하는 과정이다. 하지만 이 상태로는 바로 실행되기가 어려운데, 이유는 외부 파일이나 라이브러리들이 기계어에 완벽히 포함되지 않았기 때문이다. 그래서 다른 파일이나 라이브러리를 합치는 과정을 다음 과정인 **링킹**이다. 아직 실행파일(exe 등)이 아니고, **목적 코드(Object Code)**라고하며, 파일 확장자는 `.o`이다.
4. **링크(link)**
* 마지막으로 여러 목적 코드를 합치는 과정으로, 이 과정을 **링킹**(Linking)이라고 한다.
이 과정을 마지막으로 실행 파일이 생성되며, 윈도우에선 `.exe` 등의 확장자가 대표적이다.

미리 기계어로 번역(컴파일)이 되기때문에 실행 시 실행 속도가 추후 설명할 인터프리터보다 빠르다는 장점이 있다. 또한, 컴파일 과정 중에 오류가 검출되기 때문에 오류가 발생할 가능이 비교적 적다.

다만 컴파일하는 과정에서 메모리와 시간이 소비되고, 실행 파일의 크기가 클 수 있다는 단점이 존재한다.

컴파일 과정에 대해 자세한 내용은 [이 글](http://127.0.0.1:4000/posts/compiler-phases/)을 참고하면 좋을 듯 하다.

#### 1-3-2-3. 인터프리트

인터프리트 방식은 컴파일 방식과는 다르게, 소스 코드에서 한번에 한 문장(코드)씩 해석하고 실행된다.
예를 들어, 아래와 같은 코드가 있을 때 인터프리트 방식은 다음과 같다.

```py
a = 10
b = 20
print(a + b)
```

1. 메모리 공간 `a``10`을 저장한다.
2. 메모리 공간 `b``20`을 저장한다.
3. `a``b`를 더해서 출력한다.

인터프리트 과정은 이 과정을 프로그램을 실행하는 동안 번역하며 한줄한줄 실행한다. 만약 컴파일 방식의 언어였다면 위 실행 단계를 모두 컴파일하여 실행 파일을 만들고, 그 실행 파일엔 위 실행 단계들이 기계어로 번역되어 저장되었을 것이다.

이 처럼 인터프리터 언어는 한줄한줄 실행하며 번역되기 때문에 컴파일러 언어의 장단점과 반대된다.
컴파일 방식보다 실행 속도는 느리지만 컴파일 과정이 없기 때문에 실행 시 메모리 및 시간 소비가 없고[^ip1], 실행 파일이 없기 때문에 실행 파일의 크기를 고려할 필요는 없다.

단점으론 오류가 실행 중 발생할 가능성이 있다. 예를 들어, 정의되지 않은 변수가 있을 때 컴파일 방식에선 컴파일 전 오류를 띄우지만, 인터프리터 방식에선 대부분 실행 중 오류가 발생한다.

또한 이식성도 생각을 해야한다. 컴파일 방식에서 실행 파일은 CPU의 아키텍쳐마다, 운영체제 마다 구조가 다르기 때문에 각 환경에 맞춰 컴파일(빌드)를 해야한다. 하지만 인터프리트 방식은 소스 코드를 실행하는 인터프리터만 있으면 되기에 각 환경에 맞춰 컴파일하여 배포할 필요가 없다.

#### 1-3-2-4. 하이브리드 컴파일러

하이브리드 방식의 경우 컴파일과 인터프리트 방식을 혼합한것이며, 소스 코드를 미리 **바이트 코드(Bytecode)**라는 코드로 컴파일하며, 이 바이트 코드를 **가상 머신**에서 인터프리트하여 실행한다.

하이브리드 방식 언어인 자바로 예를 들자면, 자바는 실행 전 바이트 코드 파일인 `.class` 파일로 컴파일된다. 이 과정은 실행파일 컴파일 과정보단 메모리/시간 소비가 적으며, 이러한 바이트코드는 JVM이라는 자바 가상 머신에서 실행된다.

때문에 컴파일과 인터프리트 방식의 중간이며, 각 장점을 합친 방식이다. 자세한 내용은 _프로그래밍 언어론 (PLT)_라는 이론을 참고하길 바란다.

# 2. 컴퓨터 구조

## 2-1. CPU
Expand Down Expand Up @@ -361,4 +465,8 @@ opcode엔 다양한 역할의 opcode가 존재하는데, 크게 아래와 같은

[^todo]: [작성자용] 아직 완료되지 않은 차례는 `%`로 표기한다. 하위 차례 전체를 포함할 시 `%%`로 표기한다.
[^architecture]: 아키텍쳐란 컴퓨터 시스템의 하드웨어 구조를 의미한다. x86-64, ARM 등의 다양한 아키텍쳐가 존재한다.

[^plt0]: 소스 코드(Source Code)는 소프트웨어 제작에 사용되는 코드로, 다른 말로 원시 코드라고도 한다.
[^plt1]: 니모닉(mnemonic) 기호라고 부른다.
[^plt2]: 이해하지 않아도 좋다. 어셈블리어가 저렇게 생겼다는 것만 알면 된다.
[^pp1]: 자세히 들어가면 "그대로" 복사하는건 아니다. 최적화 등의 과정으로 내용이 바뀔 수 있다.
[^ip1]: 다만 인터프리트 과정에서 추가적인 메모리 소비(오버헤드)가 발생하기 때문에 이 점을 고려해야 한다.

0 comments on commit 47023cd

Please sign in to comment.