diff --git a/week12/seoyeon/week12.md b/week12/seoyeon/week12.md new file mode 100644 index 0000000..c6d737d --- /dev/null +++ b/week12/seoyeon/week12.md @@ -0,0 +1,840 @@ +## 1.1 여러분이 프로그래밍 언어를 발명한다면? + +### **1.1.1 창세기: CPU는 바보** + +> 초기 프로그래머는 CPU가 이해하도록 0과 1로 구성된 명령어를 통해 컴퓨터 작업 제어 +> + +**CPU** + +간단한 스위치의 조합이 복잡한 불 논리(boolean logic) 표현 가능 ⇒ 이를 기반으로 CPU 생성 + +즉, CPU는 간단한 개폐(on-off)만 이해할 수 있으며, 이를 숫자로 표현하면 “0과 1” + +CPU는 데이터를 옮기거나 간단히 연산하는 작업만 가능. BUT 속도가 굉장히 빠름 + +**[Step1] CPU 초기의 프로그래머: 천공 카드** + +프로그래머는 천공 카드(punched card)를 이용하여 컴퓨터 작업 제어 + +- 천공 카드란, 종이에 구멍을 뚫어서 데이터를 표현한 카드 +- 구멍이 뚫려 있으면 1, 뚫려 있지 않으면 0 + +CPU 의지에 따라 직접 0과 1로 구성된 명령어 작성 + +- 해당 명령어를 컴퓨터에 입력하면, 컴퓨터가 작업 +- 종이가 낭비되지만, 눈에 보이고 만질 수 있음 +- BUT 무슨 뜻인지 알아차릴 수 없음 + + +### 1.1.2 어셈블리어 등장 + +> 기계어(0과 1) 대신 “어셈블리어” 사용 +> + +**[Step2] 어셈블리어** + +저수준 언어(Low-level Language) + +CPU가 몇 가지 명령어만 실행할 수 있다는 사실 발견 + +⇒ **기계어와 해당 특정 작업을 간단히 대응시켜 기계어를 “인간이 읽고 이해할 수 있는 단어와 대응”** + +(기존) 0과 1로 구성된 문자열 → (변화) 인간이 인식할 수 있는 기계 명령어 + +```nasm +sub $8, $rsp +mov $.LC0, %edi +call puts +mov $0, %eax +``` + +### 1.1.3 - 1.1.8 + +> 어셈블리어보다 더욱 인간 친화적인 언어인 “고급 프로그래밍 언어” 등장 +CPU는 기계 명령어만 이해 +고급 프로그래밍 언어를 기계어로 번역하기 위해 1)컴파일러 또는 2)인터프리터 사용 +> + +**[Step3] 고급 프로그래밍 언어** + +고수준 언어(High-level Language) + +인간의 추상적인 표현을 CPU가 이해할 수 있는 구체적인 구현으로 자동으로 변환할 수 있을까? ⇒ 고급 언어 + +매개변수와 함수 탄생 + +```nasm +//조건에 따른 이동 +if *** + blablabla +else *** + blablabla + +//순환 +while *** + blablabla + +//함수 +func abc: + blablabla +``` + +**고급 프로그래밍 언어를 컴퓨터가 인식할 수 있는 기계 명령어로 변환하는 방법** + +1. Compiler (컴파일러) + 1. 고수준 언어를 저수준 언어로 번역하는 프로그램 + 2. 프로그래밍 언어를 처리할 때 구문 정의에 따라 트리 형태로 코드 구성 + 3. 전체 트리를 구체적인 기계 명령어로 번역 + + +2. Interpreter (인터프리터) + 1. 등장 배경 + 1. 각 CPU는 자신만의 고유한 언어가 있기 때문에 서로 다른 CPU는 서로의 기계 명령어를 실행할 수 없음 + 2. 다양한 CPU마다 상응하는 시뮬레이션 프로그램 준비하여 코드를 서로 다른 플랫폼(CPU)에서 실행할 수 있도록 함 + 2. Compiler 원칙에 따른 CPU 시뮬레이션 프로그램 + 3. 표준 명령어 집합을 정의해서 CPU 기계 명령어 실행 과정을 모방하는 프로그램 + + + +이때 Compiler와 Interpreter는 어셈블리어 단계를 거치기도 하고 생략하여 즉시 기계어로 번역하기도 함 + +- 어셈블리어를 거치는 경우는 전통적인 C 컴파일러 (GCC)를 사용하는 경우 +- 그외의 경우는 보통 직접 기계어 생성 + +| | Compiler | Interpreter | +| --- | --- | --- | +| 동작 방식 | 고급 언어 전체를 기계어로 변환해 “실행 파일” 생성 → CPU가 이 기계어를 직접 실행 | 고급 언어를 실행할 때마다 한 줄씩 읽고 번역 → 내부적으로 번역된 기계어 명령을 CPU에 전달 | +| 초기 실행 속도 | 느림 | 빠름 | +| 실행 속도 | 빠름 | 느림 | +| 디버깅 | 불편함 (에러가 한 번에 나옴) | 편리함 (에러가 나는 줄에서 즉시 멈춤) | +| 결과물 | 실행 파일(.exe) | X | +| 이식성 | 낮음 (기계어는 OS/CPU 종속) | 높음 (인터프리터만 있으면 실행 가능) | +| 대표 언어 | C, C++ | Python, JavaScript | + +## 1.2 컴파일러는 어떻게 작동하는 것일까? + +### 1.2.1 컴파일러는 그저 일반적인 프로그램일 뿐, 대단하지 않다 + +> 컴파일러는 소스 코드를 실행 파일로 변환하는 역할 +> + +**컴파일러 프로그램** + +소스 파일(프로그래머가 작성하는 고급 언어.텍스트 파일 형태)는 컴파일러를 통해 실행 파일 형태가 됨 + +즉, “소스 파일(고급 언어) → (컴파일러) → 실행 파일(0과 1)” + + + +### 1.2.2-1.2.6 + +> 컴파일러가 소스 파일을 실행 파일로 변환하는 방법 +토큰 추출 → 토큰 처리(Syntax Error) → 트리 이상 확인 → 중간 코드 생성 → 어셈블리어 생성 → 기계 명령어 생성 +> + +**소스 코드 예시** + +```c +int a=1; +int b=2; + +while (a 링커 동작 방식 +1. 심벌 해석: 종속성 확인 +2. 실행 파일 생성 +3. 재배치: 다른 모듈에 정의되어 있는 함수의 실제 메모리 주소 할당 +> + +**링커(Linker)** + +컴파일러가 생성한 대상 파일 여러 개를 하나로 묶어 하나의 최종 실행 파일 생성 + +**링커가 일하는 방법** + +- 링커가 일하는 방법을 책을 집필하는 예시로 비유 +1. `심벌 해석(symbol resolution)`: 종속성이 올바르게 설정되어 있는지 확인 + 1. 인터페이스 구현이 종속된 모듈에서 사용 가능한지 확인 + 2. 참조하는 외부 심벌(external symbol)에 대한 실제 구현이 존재하는지 확인 + 3. 프로그램이 다른 모듈의 프로그래밍 인터페이스 또는 변수 참조하는 경우 + 1. Ex. list.c에서 linked list 구현하고 다른 모듈에서 이 linked list 사용 ⇒ 두 모듈 사이의 종속성(dependency) 존재 +2. `실행 파일 생성`: 링크 과정을 마친 후 최종적인 실행 파일이 됨 +3. `재배치(relocation)`: 다른 모듈에 정의되어 있는 함수를 참조할 때, 링커가 함수의 주소를 확인하고 실제 메모리 주소로 대체 + 1. 컴파일러가 소스 파일을 컴파일할 때 함수가 어느 메모리 주소(memory address)에 위치하는지 알 수 없음 ⇒ [예시] N으로 표시 후 넘어감 + 2. 링커가 이 표시를 확인하고 모아 실행 파일을 생성하는 과정에서 함수의 주소를 확인하고 실제 메모리 주소로 대체 ⇒ [예시] N을 각각의 페이지로 대체 + 1. 이때 이 메모리 주소는 실제 물리 주소가 아닌 **“실행 파일 안에서의 주소(가상 주소)”** + 2. 링커 단계 이후 실행 단계에서 OS의 loader가 1)실행 파일을 읽어 메모리에 올리고, 2)링커가 붙여둔 주소 정보 읽고, 3)실행 시점에 실제 물리 주소 할당 + + +### 1.3.2 심벌 해석: 수요와 공급 + +> Linker의 심벌 해석은 Compiler가 제공하는 코드 영역, 데이터 영역, 심벌 테이블을 통해 진행 +> + +**심벌** + +전역 변수(global variable)와 함수(function)의 이름을 포함하는 모든 변수명 + +- 지역 변수(local variable)는 모듈 내에서만 사용되어 외부 모듈에서 참조할 수 없기 때문에 링커가 신경쓰지않음 + +링커는 대상 파일에서 참조하고 있는 모든 외부 심벌마다 대상 정의가 반드시 존재하는지, 단 하나만 존재하는지 확인 + +**예시 코드** + +```c +//func.c +int g_a = 1; //전역변수 +extern int g_e; //외부변수 +int func_a(int x,int y); //함수참조 + +int func_b() +{ + int m = g_a+2; + return func_a(m+g_e); +} +``` + +지역변수: m + +전역변수 + +- g_a, g_e, func_b + - 자체적인 전역 변수 + - 두 심벌은 다른 모듈에서 참조 가능 +- g_e, func_a + - 다른 모듈에서 정의된 변수 + +위 코드에서 링커는 전역 변수의 정보(다른 모듈에서 참조할 수 있는 심벌 2개, 다른 모듈에서 정의한 심벌 2개)를 알아야 함 + +- 컴파일러는 “기계 명령어를 생성”할 뿐만 아니라 “명령어를 실행시키는 데이터”도 함께 생성하며 대상 파일에 반드시 포함 +- [Compiler] 대상 파일의 영역 + - `명령어 부분(code section)`: 소스 파일에 정의된 함수에서 변환된 기계 명령어가 저장되는 부분 + - `데이터 부분(data section)`: 소스 파일의 전역 변수가 저장되는 부분. 로컬 변수는 프로그램이 실행된 후 스택 영역에서 생성되고 사용하면 제거되기 때문에 대상 파일에 저장되지 않음 + + +- [Compiler] 심벌 테이블 작성 + - 컴파일 과정에서 외부에 정의된 전역 변수나 함수를 발견하는 경우, “선언이 존재하는지 확인”하고 실제 정의 여부는 신경쓰지 않음 → 링커가 담당 + - (대신 링커의 부담을 줄이고자) 심벌 테이블 작성: 외부 심벌 정보 기록 + - 소스 파일마다 외부에서 참조 가능한 심벌이 어떤 것인지 정보를 기록 + - 어떤 외부 심벌을 참조하는지 기록 + - 전체 심벌 테이블의 표현 내용 + 1. 내가 정의한 심벌, 즉 다른 모듈에서 사용할 수 있는 심벌 + 2. 내가 사용하는 외부 심벌 + - 대상 파일에 생성한 심벌 테이블 저장 + + +- [Linker] 심벌 해석 과정 + - 각 대상 파일에서 사용할 외부 심벌이 심벌 테이블에서 유일한 정의(unique definition)를 발견 가능한지 확인하는 작업 + - 실제 코드 작성 시 공급이 수요를 초과할 수 있음 + - 실제로 사용하지 않는 함수 정의 + - 하지만 수요가 공급을 초과하는 상황은 발생하면 안 됨 + - Ex. 아래 코드를 컴파일하는 경우 “undefined reference to ‘func’” 오류 발생 + + ⇒ 컴파일 오류 발생 X. 링커가 func 함수에 대한 정의를 찾지 못함 + + ```c + // main.c + void func(); + + void main() + { + func(); + } + ``` + + + +### 1.3.3 정적 라이브러리, 동적 라이브러리, 실행 파일 + +> 정적 라이브러리: 참조 코드 모두 실행 파일에 저장 +동적 라이브러리: 참조하는 중요 정보만 실행 파일에 저장 +1. Load-time Dynamic LInking +2. Run-time Dynamic Linking +> + +**정적 라이브러리(static library)** + +코드를 컴파일한 후 패키지로 묶고 구현된 모든 함수의 선언을 포함하는 header file 제공 + +윈도우에서는 .lib, 리눅스에서는 .a + +이때 여러 소스 파일을 각각 컴파일하고 링크해 정적 라이브러리 생성 + + +실행 파일을 생성할 때 자신의 코드만을 컴파일하여 미리 컴파일 완료된 정적 라이브러리는 다시 컴파일할 필요 없이 링크 과정에 복제 ⇒ 정적 링크(static linking) + +코드가 의존하는 외부 코드를 매번 컴파일하지 않아도 됨 ⇒ 컴파일 속도 향상 + + +정적 링크는 여러 대상 파일을 모아 각각의 대상 파일에서 “데이터 영역”과 “코드 영역”을 각각 결합 + +- 실행 파일에도 코드 영역과 데이터 영역이 있어 대상 파일과 유사 +- BUT 실행 파일에는 특수한 심벌인 **_start** 존재 + - CPU는 이 심벌 주소에서 프로그램을 실행하는 데 필요한 기계 명령어 탐색 + - 이 기계 명령어 실행한 후 main 함수 실행 + + +**정적 라이브러리에서 발생할 수 있는 문제** + +1. 디스크와 메모리 낭비 + 1. 정적 링크는 라이브러리를 실행 파일에 직접 복사 + 2. 거의 모든 프로그램에 적용되는 표준 라이브러리를 사용하면 정적 링크로 생성된 실행 파일은 모두 동일한 코드와 데이터 복사본 가짐 +2. 정적 라이브러리에 종속성 존재하고 라이브러리 코드 변경 시 라이브러리에 종속된 프로그램도 매번 컴파일해야함 + +⇒ “동적 라이브러리” 등장 + +**동적 라이브러리(dynamic library. =공유 라이브러리)** + +1. 윈도우: DLL 파일이 동적 라이브러리. 동적 라이브러리 많이 사용 +2. 리눅스: 동적 라이브러리로 .so 확장자 사용. 접두사로 lib 사용 + 1. 동적 라이브러리 생성: 두 소스 파일 a.c와 b.c를 동적 라이브러리 foo로 생성하고 싶은 경우 + + ```c + gcc -shared -fPIC -o libfoo.so a.c b.c + ``` + + +동적 라이브러리도 정적 라이브러리와 동일하게 코드 영역과 데이터 영역 존재 + +- 정적 라이브러리와 사용 방식과 사용 시간이 다름 + +**정적 라이브러리 vs 동적 라이브러리** + +1. [정적 라이브러리] 코드 영역과 데이터 영역을 모두 묶어 실행 파일에 복사(copy) + 1. 컴파일 단계에서 실행 파일에 복사되기 때문에 실행 파일에는 정적 라이브러리의 전체 내용 포함 + + +1. [동적 라이브러리] 참조된 동적 라이브러리 이름, 심벌 테이블, 재배치 정보 등 필수 정보만 실행 파일에 포함 + 1. 정적 라이브러리에 비해 실행 파일의 크기 줄어듬 + + + + 2. 참조된 동적 라이브러리의 필수 정보는 실행 파일 내에 저장 + 1. 아래는 실행 파일에 포함된 동적 라이브러리 필수 정보 + 2. 해당 정보는 동적 링크(dynamic linking)가 일어날 때 사용 + + + + 3. 동적 라이브러리에 의존하는 실행 파일에는 컴파일 단계에서 필수 정보만 저장되기 때문에 동적 링크는 실제 프로그램 실행 시점까지 미룸 + +**⇒ 즉, 정적 링크는 컴파일+링크 단계에서 실행 파일을 생성할 때 이루어지고, 동적 링크는 실제 프로그램 실행 시점에 이루어짐** + +**동적 링크 방식** + +**[방식1] 프로그램이 메모리에 적재(loading)될 때 동적 링크 진행** + +- `적재`란, 실행 파일을 실행하기 위해 디스크에서 읽어 메모리의 특정 영역으로 이동시키는 과정 + - 이때 적재 도구 loader라는 전용 프로세스 실행 +- 동적 링크 과정에 문제가 발생하는 경우, 아래 오류 메세지 출력하며 프로그램 시작 X + + +- 동적 링크 사용하기 위해 실행 파일이 어떤 동적 라이브러리를 참조하는지 컴파일러에 명시적으로 알려줘야 함 + - Ex. 동적 라이브러리 libfoo.so에 의존하는 소스 파일 main.c, 이를 컴파일하여 pro라는 실행 파일 생성 + - 컴파일 단계 + 링크 단계 + + ```c + gcc -o pro main.c /path/to/libfoo.so + ``` + + +동작 과정 + +1. 실행 파일 적재 +2. Loader가 실행 파일이 동적 라이브러리에 의존하는지 여부 확인 +3. 동적 라이브러리가 필요하다면, 동적 링커(dynamic linker)라는 별도의 프로세스 실행 + 1. 참조하는 동적 라이브러리 존재 여부와 위치, 심벌의 메모리 위치 확인 + +**[방식2] 실행 시간 동적 링크(runtime dynamic linking)** + +- 프로그램 먼저 실행 후 프로그램 실행 시간(runtime)동안 코드가 직접 동적 링크 실행 + - `실행 시간(runtime)`이란, CPU가 프로그램을 실행하기 시작한 시점부터 실행이 완료되어 프로그램이 종료된 시점까지의 시간 +- 어떤 동적 라이브러리에 의존하는지 알 필요 없기 때문에 좀 더 동적인 링크 방식 +- 링크 과정을 프로그램이 실행된 이후로 미룸 +- 실행 파일을 생성하는 과정에서 실행 파일 내부에 동적 라이브러리 정보가 저장되지 않음 + - 프로그래머가 코드에 특정 API를 사용하여 필요할 때마다 동적 라이브러리를 동적으로 적재 +- Ex. 리눅스의 dlopen, dlsym, dlclose + + + +### **1.3.4 동적 라이브러리의 장단점** + +> 동적 라이브러리 장점 +- 디스크와 메모리 절약 +- 동적 라이브러리 수정 시 해당 파일만 재컴파일하면 됨 +- 플러그인을 통한 기능 확장 가능 +- 여러 언어 사용 및 코드 재사용 효율 향상 +동적 라이브러리 단점 +- 성능 저하 +- 코드 복잡성 증가 +- 실행 파일만으로 실행 불가능 +그럼에도 장점이 훨씬 커 동적 라이브러리 많이 사용 +> + +**장점** + +1. 디스크 절약 가능 + 1. 정적 라이브러리의 경우, 같은 라이브러리를 사용하는 프로그램마다 중복된 코드가 각각의 실행 파일에 들어감 ⇒ 디스크 낭비 + 2. 동적 라이브러리는 .so 파일이 동적(공유) 라이브러리로 별도 보관되고, 실행 파일에 복사되지 않음 ⇒ 디스크 절약 +2. OS의 동적 링커와 가상 메모리 시스템이 .so 파일을 한 번만 메모리에 적재하고 필요한 여러 프로그램에 공유(매핑) + 1. 메모리에 적재되는 동적 라이브러리 코드 메모리 적재 절약 가능 + + + +3. 동적 라이브러리의 코드 수정 시 해당 동적 라이브러리만 다시 컴파일하면 됨 + 1. 실행 파일을 다시 컴파일할 필요 없음 + 2. 프로그램 업그레이드와 버그 수정에 유리 +4. 실행 시간 동적 링크(runtime dynamic linking)를 사용하는 경우 플러그인을 적재해 프로그램 기능 쉽게 확장 가능 + 1. 플러그인(plug-in) 구현해 기능 확장 가능 +5. 여러 언어를 혼합하여 개발할 때 유용&코드의 재사용 효율 향상 + 1. 파이썬은 프로젝트의 개발 속도가 빨라지고, C/C++은 성능이 좋음 + 2. 더 높은 성능이 요구되는 부분은 C/C++로 작성한 후 컴파일하여 동적 라이브러리 생성 + +**단점** + +1. 성능 저하 발생 + 1. 프로그램이 적재되는 시간 또는 실행 시간에 동적 링크를 사용하기 때문에 성능 저하 +2. 동적 라이브러리는 메모리 절대 주소로 참조 불가능 ⇒ 성능 저하 및 코드 복잡도 향상 + 1. 메모리에 단 하나의 복사본만 존재하고 해당 코드는 여러 프로세스가 공유하기 때문에 동적 라이브러리 코드는 임의의 메모리 절대 주소로 참조 불가능 + 2. 동적 라이브러리는 특정 메모리 주소와 독립적으로 동작 ⇒ 독립 코드(position-independent code.PIC)로 불림 + 3. 절대 주소는 foo 함수를 호출하는 명령어와 같이 고정적으로 기록된 값 + + ```c + call 0x4004dh //foo 함수 호출 + ``` + +3. 실행 파일만으로 실행 불가능 + 1. 종속된 동적 라이브러리를 제공하지 않거나 버전 호환이 불가능한 경우 프로그램 실행되지 않음 + + + +**동적 라이브러리 예시** + +```c +#clude + +int main() +{ + printf("hello, world\n"); + return 0; +} +``` + +printf 함수는 C 표준 라이브러리에 구현 + +- 동적 라이브러리 +- 링커가 실행 파일에 자동으로 링크 + +리눅스의 ldd 명령어를 사용해 실행 파일이 어떤 동적 라이브러리에 의존하는지 확인 가능 + +- 위 코드를 컴파일한 실행 파일 이름이 “helloworld”인 경우 `ldd helloworld` 로 의존하는 동적 라이브러리 확인 가능 + - libc.so가 C 표준 라이브러리 + + +### 1.3.5 재배치: 심벌의 실행 시 주소 결정하기 + +> 링커는 심벌에 대응하는 메모리 주소 결정 +> + +**재배치 배경** + +어셈블리어로 작성된 코드를 보면, 명령어에 변수 정보가 전혀 없고 메모리 주소 사용 + +foo 함수 호출 시 대응하는 기계 명령어는 `call 0x4004d6` + +- 이때 0x4004d6이 foo 함수의 첫 번째 기계 명령어가 위치하는 주소 + +링커는 실행 파일을 생성할 때 함수가 적재될 메모리 주소를 확정해야 함 + +**재배치** + +심벌의 메모리 주소를 수정하는 과정 + +Ex. 0x00 → 0x4004d6 + +**재배치 과정** + +1. [컴파일러] 컴파일을 통해 대상 파일을 생성할 때 foo 함수가 어느 메모리 주소에 적재될지 알 수 없음 + 1. 간단하게 0x00으로 지정하여 호출한다는 사실만 기록 + 2. 메모리 주소를 확정할 수 없는 변수를 발견할 때마다 + 1. `.relo.text` 에 해당 명령어 저장 + 2. `.relo.data` 에 해당 명령어와 관련된 데이터 저장 + 3. 따라서 대상 파일은 아래와 같은 형태 + + +2. [링커] 대상 파일에서 동일한 유형의 영역끼리 병합 + + +3. [링커] 모든 기계 명령어와 전역 변수가 프로그램 실행 시간에 위치할 메모리 주소 결정 가능 + 1. 이때 foo 함수의 실행 시간 메모리 주소가 0x4004d6이라는 것을 알 수 있음 + 2. 추후 다시 설명 + +4. [링커] 각 대상 파일의 .relo.text 영역을 하나씩 읽어 기계 명령어를 수정해야 하는 foo라는 심벌이 있으며, 이 심벌 코드 영역 시작 주소 기준 오프셋이 60 바이트임을 확인 + 1. 링커는 이렇게 확인한 정보를 이용해 실행 파일에서 해당 call 명령어를 정확히 찾고, 이동할 소스 주소를 0x00에서 0x4004d6으로 수정 + + + + +### 1.3.6 가상 메모리와 프로그램 메모리 구조 + +> 각 프로세스는 가상 메모리 공간을 가지며, 페이지 테이블을 통해 물리 메모리 공간과 매핑 +> + +**링커가 프로그램이 실행된 후의 변수나 기계 명령어의 메모리 주소를 확인할 수 있는 이유** + +변수나 명령어의 메모리 주소는 프로그램이 실행될 때마다 변경되기 때문에 실제로 그 시점이 되어야 알 수 있지 않는가? + +링커가 변수의 실행 시간 메모리 주소를 미리 알 수 있는 방법 ⇒ Virtual Memory + +**프로그램 실행 시 메모리 상태** + +`heap segment` , `stack segment` , `data segment` + +프로그램이 실행되면 해당 프로그램의 프로세스가 메모리에 적재 + +1. 메모리 상위 주소에 `stack segment` 존재 +2. 그 아래에 비어 있는 큰 공간 존재 +3. 그 아래에 `heap segment` 존재 + 1. malloc 함수가 heap segment에서 메모리 할당 +4. 그 아래에 `data segment` 와 `code segment` 존재 + 1. 두 영역은 실행 파일의 내용이 메모리에 적재되는 곳 + + +code segment가 시작되는 위치가 가장 중요! + +- 모든 프로그램은 실행된 후 code segment가 예외 없이 **메모리 주소 0x400000에서 시작** +- 두 프로그램이 동시에 실행 중일 때 CPU가 메모리 주소 0x400000에서 가져오는 기계 명령어는 프로그램 A의 코드일까, B의 코드일까? + - 정답은 프로그램 A를 실행할 때 메모리 주소 0x400000에서 가져온 명령어는 프로그램 A의 데이터, 프로그램 B를 실행할 때 메모리 주소 0x400000에서 가져온 명령어는 프로그램 B의 데이터 + + **⇒ 운영 체제의 가상 메모리 기술** + + +**가상 메모리** + +각각의 프로그램이 실행 중일 때 자기 자신이 모든 메모리를 모두 독점적으로 사용하고 있는 것처럼 착각하게 만듬 + +Ex. 32비트 시스템에서 실제로 시스템에 설치된 물리적 메모리가 얼마가 되었든 자신이 2^32바이트, 즉 4GB 메모리를 독점하고 있다고 생각 + +따라서 위 “프로그램 실행 시 메모리 상태” 그림은 실제 물리 메모리가 아닌, 논리로만 존재하는 허상 + +- 실제 데이터는 디스크 전체에 무작위로 흩어져 있음 + +링커가 실행 파일을 생성하자마자 실행 시 심벌의 메모리 주소를 결정할 수 있는 것은 프로그램 실행 여부와 관계없이 프로세스 메모리 구조를 알기 때문! + +- 64비트 시스템은 code segment는 언제나 메모리 주소 0x400000에서 시작하고 stack segment는 항상 메모리의 상위 주소에 위치 +- 이런 메모리 구조를 사용하면 메모리 주소가 가상이더라도 실행 시 심벌의 메모리 주소 확인 가능 +- 사실상 링커는 프로그램이 실행될 때 명령어나 데이터가 실제로 존재하는 물리 메모리의 위치를 신경쓰지 않음 + +**물리 메모리 주소 찾는 방법** + +> 결국 데이터와 명령어는 물리 메모리에 저장되어야 하고, CPU가 프로그램 A를 실행하여 메모리 주소 0x400000에 접근할 때 실제 명령어를 꺼내는 물리 메모리 주소 찾아야 함! +⇒ 실행 파일 실행 시 물리 메모리에 적재되어야 함 +> + +페이지 테이블(page table) + +각각의 프로세스는 자신만의 페이지 테이블 존재 + +이때 실행 파일의 code segment가 물리 메모리 주소 0x80ef0000에 적재된다고 가정하면, 시스템에는 다음과 같은 매핑 관계 추가 + +- CPU가 프로그램 A를 실행하고 메모리 주소 0x400000에 접근하면 특별히 설계된 하드웨어가 페이지 테이블을 참조하여 물리 메모리 주소 0x80ef0000로 변환 후 접근 + + + +프로그램이 실행 중일 때 메모리 상태 (아래 그림. 중요한 정보 존재) + +1. 모든 프로세스의 가상 메모리는 표준화되어 있고 크기가 동일. 프로세스마다 각 영역의 크기가 다를 수는 있지만 영역이 배치되는 순서는 동일 +2. 실제 물리 메모리의 크기는 가상 메모리의 크기와는 무관하며 물리 메모리에는 heap segment, stack segment 등 영역 구분조차 존재하지 않음. 단, 운영 체제마다 조금씩 다를 수 있음 +3. 모든 프로세스는 자신만의 페이지 테이블을 가지고 있으며, 같은 가상 메모리 주소라도 페이지 테이블을 확인하여 서로 다른 물리 메모리 주소 획득. 따라서 CPU는 동일한 가상 메모리 주소에서 서로 다른 내용을 가져올 수 있음 + + +## 1.4 컴퓨터 과학에서 추상화가 중요한 이유 + +### 1.4.1 프로그래밍과 추상화 + +소프트웨어는 복잡하지만, 프로그래머는 추상화를 통해 복잡도 제어 가능 + +- Ex. 모듈 기반의 설계 시 각 모듈이 간단한 API를 추상화하면 각 모듈을 사용할 때 복잡한 내부 구현 사항을 고민할 필요 없이 추상화된 API에 집중 가능 + +모든 프로그래밍 언어는 추상화를 지원하기 위해 자신만의 작동 방식 제공 + +- Ex. 객체 지향 언어의 주 장점은 다형성과 추상 클래스를 통한 추상화 가능 + - 구체적인 내부 구현이 아닌 추상화만 고려하여 프로그래밍할 수 있어 프로그램 확장성 향상 및 요구 사항 변화에 더 잘 대응 가능 + +### 1.4.2 시스템 설계와 추상화 + +컴퓨터 시스템은 기본적으로 추상화 위에 구축 + +- CPU + - 하드웨어는 트랜지스터 여러 개로 구성되어 있지만, 명령어 집합이라는 개념으로 내부 구현 세부 사항 보호 + - 따라서 프로그래머는 트랜지스터 세부 사항을 고려할 필요 없이 명령어 집합에 포함된 기계 명령어를 사용해 CPU 작업을 지시하기만 하면 됨 +- 기계 명령어에 대한 추상화 계층은 고급 프로그래밍 언어와 연관됨 +- 입출력 장치는 파일로 추상화 +- 실행 중인 프로그램은 프로세스로 추상화 + - 프로그래머는 프로그램을 작성할 때 자신의 프로그램이 CPU를 독점한다고 가정할 수 있어 단일 CPU 시스템에서도 수많은 프로세스 동시 실행 가능 +- 물리 메모리와 파일은 가상 메모리로 추상화 +- 네트워크 프로그래밍은 소켓으로 추상화 +- 프로세스와 프로세스에 종속적인 실행 환경은 컨테이너로 추상화 +- CPU, OS, application은 가상 머신으로 추상화 + +추상화는 프로그래머를 저수준 계층에서 점점 더 멀어지게 만들고, 저수준 계층의 세부 사항에 신경 쓸 필요 없이 만듬 + +하지만 저수준 계층의 이해 필요 + +## 1.5 요약 + +> `컴파일러`, `인터프리터`: 고급 프로그래밍 언어 → 기계 명령어. (소스 코드 → 대상 파일) +`링커`: 모든 코드와 데이터, 라이브러리를 한데 묶어 실행 파일 생성. (대상 파일 → 실행 파일) +> + + + +## Questions + +[](https://www.notion.so/212deb6219af8094a5e7d9ae2f895712?pvs=21) **장점의 “메모리 절약”과 단점의 “특정 메모리 주소와 독립적으로 동작”이 무슨 말인가** + +## More + +- 🌀 Virtual Memory & Paging System + + **가상 메모리 (Virtual Memory)** + + OS가 각 프로세스 간 공간 분리로 “가상 메모리 공간” 할당 + + 즉, 프로세스는 가상 주소(virtual address)를 사용하고 데이터를 읽고 쓸 때 물리 주소(physical address)로 변환 + + - `Virtual Address`: 프로세스가 참조하는 주소 + - `Physical Address`: 실제 메모리 주소 + + 주소 변환 시 활용하는 것이 MMU(Memory Management Unit) + + - CPU에 코드 실행 시 가상 주소 메모리 접근이 필요할 때 해당 주소를 물리 주소 값으로 변환해주는 하드웨어 장치 + + **CPU는 가상 메모리를 다루고, 가상 메모리의 가상 주소 접근 시 MMU를 통해 물리 주소로 변환되어 물리 메모리에 접근** + + + **페이징 시스템(Paging System)** + + 가상 메모리 시스템을 구현하는 가장 일반적인 방법 + + `paging` : 크기가 동일한 page. 가상 주소 공간과 이에 매핑되는 물리 주소 공간 관리 + + `page (=page frame)` : 고정된 동일한 크기의 block + + `paging table` : 프로세스의 PCB에 Page Table 구조체를 가리키는 주소가 있는데, 이 page table에 물리 메모리에 있는 page frame 번호와 해당 페이지의 첫 물리 주소 정보를 매핑한 표 \ No newline at end of file