From 47023cdb333627add7fee8612b1c2205d8561b9f Mon Sep 17 00:00:00 2001 From: ky0422 Date: Fri, 25 Oct 2024 12:05:48 +0900 Subject: [PATCH] updated --- _posts/fast/2024-10-25-os.md | 114 ++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 3 deletions(-) diff --git a/_posts/fast/2024-10-25-os.md b/_posts/fast/2024-10-25-os.md index 2abf2a6..22cabff 100644 --- a/_posts/fast/2024-10-25-os.md +++ b/_posts/fast/2024-10-25-os.md @@ -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) @@ -88,7 +92,7 @@ tags: ["컴퓨터", "하드웨어", "CPU"] 이 글에서 설명한 방법은 후자를 택하여 계산해볼 예정이다. 만약 `1011`이라는 2진법을 10진수로 변환해보자. -```m +``` 1 0 1 1 b 8 4 2 1 d @@ -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 @@ -361,4 +465,8 @@ opcode엔 다양한 역할의 opcode가 존재하는데, 크게 아래와 같은 [^todo]: [작성자용] 아직 완료되지 않은 차례는 `%`로 표기한다. 하위 차례 전체를 포함할 시 `%%`로 표기한다. [^architecture]: 아키텍쳐란 컴퓨터 시스템의 하드웨어 구조를 의미한다. x86-64, ARM 등의 다양한 아키텍쳐가 존재한다. - +[^plt0]: 소스 코드(Source Code)는 소프트웨어 제작에 사용되는 코드로, 다른 말로 원시 코드라고도 한다. +[^plt1]: 니모닉(mnemonic) 기호라고 부른다. +[^plt2]: 이해하지 않아도 좋다. 어셈블리어가 저렇게 생겼다는 것만 알면 된다. +[^pp1]: 자세히 들어가면 "그대로" 복사하는건 아니다. 최적화 등의 과정으로 내용이 바뀔 수 있다. +[^ip1]: 다만 인터프리트 과정에서 추가적인 메모리 소비(오버헤드)가 발생하기 때문에 이 점을 고려해야 한다.