크래프톤 정글 Pintos 마지막인 VM 파트가 시작되었다. 물론 File System도 있지만 정글의 커리큘럼에서 더 이상 File System을 수행하지는 않는 것 같다.
새로운 팀원들과 함께 Pintos에서 VM을 어떻게 구현해야 할지 제공된 Gitbook 내용을 파악하고 가상 메모리에서 유저영역과 커널 영역까지 가상 메모리라는 것을 이해하기 위해 며칠을 할당했다.
가상 메모리 (Virtual Memory)
가상 메모리(Virtual Memory)는 운영체제가 제한된 물리적 메모리를 보다 효율적이고 안전하게 사용하기 위해 도입한 메모리 관리 기법으로 프로그램이 실제 메모리(RAM)의 용량이나 배치에 구애받지 않고 큰 메모리 공간을 사용하는 것처럼 동작할 수 있도록 한다. 이게 왜 중요한가? 컴퓨터의 물리 메모리는 한계가 있다. 물론 현재는 더 큰 메모리용량을 사용할 수 있게 되었는데 과거 컴퓨터의 RAM은 매우 작았다. 이런 메모리에 1대 1로 메모리 할당을 한다면 만약 컴퓨터의 메모리 용량보다 큰 프로그램이 있다면 실행할 수 없을 것이다.
가상 메모리란 실행하려는 프로그램을 페이지 단위로 나누고(4KB) 정도 물리메모리도 논리적으로 프레임이라고 불리는 4KB 단위로 나누어서 사용한다. 이후 프로그램이 실행되면 페이지에 프레임을 매핑하여 해당 가상주소에 접근하면 해당되는 물리 프레임에 있는 데이터를 읽어서 사용하는 것이다.
페이지 테이블 (Page Table)
그렇다면 가상 메모리에서 페이지라는 단위로 나누어진 프로그램은 물리메모리에 어떻게 위치하게 될까? 과연 연속적으로 페이지가 위치하게 될까? 그건 아니다. 우리는 하나의 프로그램만 실행하지 않는다. 또한 I/O 출력이나 파일 읽기 쓰기 등 다양한 작업이 일어나게 될 것이며 가상 메모리 시스템에서는 물리 메모리에 나누어진 페이지 데이터들이 비 연속적으로 그리고 여기저기 나누어져서 저장될 수 있다. 그런 상태라면 필요한 데이터를 찾기 위해 메모리를 순회하며 모두 찾아야 할지도 모르고 이는 매우 비 효율적이다. 그래서 페이지 테이블이라는 것이 필요하다.
페이지 테이블은 어떤 페이지 가 어느 프레임과 매핑되어있는지 관리하고 있는 테이블이다. 페이지 테이블에 대한 자세한 내용은 이 포스팅에서 다루지는 않을 것이다. 또한 CPU와 MMU를 이용한 가상 주소를 이용해 물리 주소를 찾아내는 과정은 생략하겠다. 자세한 내용은 과거 포스팅에서 확인할 수 있다.
2025.04.20 - [크래프톤 정글 (컴퓨터 시스템: CSAPP)/9장 가상 메모리] - 컴퓨터 시스템 : CSAPP 9장 정리 - 9.1 ~ 9.2
컴퓨터 시스템 : CSAPP 9장 정리 - 9.1 ~ 9.2
Chapter 9: Virtual Memory (가상 메모리)가상 메모리는 현대 시스템에서 매우 중요한 메모리 추상화 개념이다. 이 추상화는 각 프로세스에게 독립적이고 균일한 메모리 주소 공간을 제공하며, 다음 세
www.gowoong.com
운영체제와 가상메모리
우리는 Pintos라는 OS를 구현 해보고 있다.
이전 User Program에서도 이미 가상 메모리가 있었다. 하지만 이를 우리가 직접 구현하지 않고 이미 구성되어 있었던 가상 메모리를 이용해 User Program을 실행하는 것이었다. 하지만 이제 우리는 VM을 설계하고 구현해야 한다. 그래서 우리는 Pintos에서의 가상 메모리를 분석할 필요가 있다.
커널도 가상메모리이다.
우리가 주로 구현하고 있는 커널의 프로그램도 사실 가상 메모리 위에서 동작한다. 하지만 Pintos에서는 커널을 물리메모리와 1대 1로 매핑을 해 두었다.
- include/threads/loader.h
/* Physical address of kernel base. */
#define LOADER_KERN_BASE 0x8004000000
/* Kernel virtual address at which all physical memory is mapped. */
#define LOADER_PHYS_BASE 0x200000
해당 파일에 보면 Pintos의 커널의 시작 주소와 해당 주소와 매핑된 물리 주소의 값을 알 수 있다. 즉 Pintos의 가상 주소의 시작은 0x8004000000이고 이 주소는 실제 물리 메모리의 0x200000부터 시작된다는 것이다.
💡 이렇게 하면 나중에 커널의 가상 주소를 이용해 유저 가상메모리 영역의 물리 주소를 찾아갈 수 있다는 것이다.
나도 처음에 이 말이 제대로 이해되지 않았는데 코치님은 이 개념을 잡고 들어가야 나중에 문제가 발생해도 감각적으로 어느 영역에서 문제가 생겼다고 눈치챌 수 있다고 했기에 집중해서 딥다이브 해 보았다.
우리가 이전에 구현하던 쓰레드, UserProgram은 과연 Pintos에서 어디에 저장되고 있었을까? 이전에는 당장 눈앞에 있는 과제를 해결하기 위해 애써 무시하고 있던 내용이었다. 과연 유저 영역의 프로그램은 Pintos에서 어디에 저장되고 관리되고 있었나 하면 바로 커널이 관리하고 있었다.
더 자세히는 커널영역안에 Pool이라고 하는 영역이 있었고 이 영역을 커널전용, 유저전용으로 나누어서 palloc_get_page()를 호출했을 때 PAL_USER면 유저 영역에서 페이지와 물리 메모리를 할당해서 제공하고 이런 값이 없다면 커널 영역에서 할당을 하는 방식이었던 것이었다. 이렇게 하면 유저의 메모리에 접근하기 위해 커널의 가상 주소를 이용해 유저 메모리를 찾아낼 수 있던 것이었다!!
그럼 우리가 가상 메모리를 배우다 보면
이런 내용을 접했을 것인데 pintos에서는 이런 구조도 결국 커널이 관리하고 있는 영역에서 페이지로 구분된 영역에 저장되고 사용되고 있었다는 것이다. 반면 커널도 결국 C 프로그램이고 그렇다면 위의 구조와 유사하게 Code 영역 Data 영역 등 여러 영역을 가지게 되는데 커널의 경우 가상 메모리지만 물리 메모리와 1대 1 매핑이 되어 있기 때문에 유저 프로그램과는 다르게 항상 비슷한 주소에 위치하게 된다.
objdump -h build/kernel.o
build/kernel.o: file format elf64-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0002130b 0000008004200000 0000000000200000 00200000 2**12
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 000057e0 0000008004221320 0000000000221320 00221320 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .data 000008aa 0000008004227000 0000000000227000 00227000 2**5
CONTENTS, ALLOC, LOAD, DATA
3 .bss 000030c0 00000080042278c0 00000000002278c0 002278aa 2**5
ALLOC
4 .debug_info 0001f8e5 0000000000000000 0000000000000000 002278aa 2**0
CONTENTS, READONLY, DEBUGGING
5 .debug_abbrev 000060d7 0000000000000000 0000000000000000 0024718f 2**0
CONTENTS, READONLY, DEBUGGING
6 .debug_aranges 00000ba0 0000000000000000 0000000000000000 0024d266 2**0
CONTENTS, READONLY, DEBUGGING
7 .debug_line 00007478 0000000000000000 0000000000000000 0024de06 2**0
CONTENTS, READONLY, DEBUGGING
8 .debug_str 00003785 0000000000000000 0000000000000000 0025527e 2**0
CONTENTS, READONLY, DEBUGGING
9 .comment 00000029 0000000000000000 0000000000000000 00258a03 2**0
CONTENTS, READONLY
10 .debug_ranges 00000210 0000000000000000 0000000000000000 00258a2c 2**0
CONTENTS, READONLY, DEBUGGING
11 .stabstr 00000fb2 0000000000000000 0000000000000000 00258c3c 2**0
CONTENTS, READONLY, DEBUGGING
이건 내가 커널이 실행될 때 실제 가상 주소로 보면 어느 주소에 어느 데이터가 위치하는지 알아보기 위해 objdump를 실행했던 결과이다. 이를 통해 커널의 프로그램들이 커널 베이스 주소 위에 매핑이 된다는 것을 확인할 수 있었다.
그렇다면 커널의 스택과 힙은 무엇인가?
우리는 Userprogram 등 과거에 이미 malloc을 사용해왔는데 그렇다면 힙이 존재한다고 생각할 수 있었는데 이 역시 Pintos에서는 제공하지 않고 malloc에 대한 코드를 살펴보면
void *
malloc (size_t size) {
struct desc *d;
struct block *b;
struct arena *a;
/* A null pointer satisfies a request for 0 bytes. */
if (size == 0)
return NULL;
/* Find the smallest descriptor that satisfies a SIZE-byte
request. */
for (d = descs; d < descs + desc_cnt; d++)
if (d->block_size >= size)
break;
if (d == descs + desc_cnt) {
/* SIZE is too big for any descriptor.
Allocate enough pages to hold SIZE plus an arena. */
size_t page_cnt = DIV_ROUND_UP (size + sizeof *a, PGSIZE);
a = palloc_get_multiple (0, page_cnt);
if (a == NULL)
return NULL;
/* Initialize the arena to indicate a big block of PAGE_CNT
pages, and return it. */
a->magic = ARENA_MAGIC;
a->desc = NULL;
a->free_cnt = page_cnt;
return a + 1;
.
.
.
.
}
내부에서 palloc_get_multiple을 호출하고 커널 영역에 할당된 페이지를 이용해 malloc 요청을 처리했던 것이다.
그렇다면 프로세스 무엇이었나?
그럼 프로세스라고 해서 우리가 User Program 에서 구현했던 것은 무엇이었나?
결국 또 커널의 영역에 할당된 페이지에 thread 구조체를 담아놓고 예를 들어 1페이지 구조 0~4096 바이트 내에서 rsp를 조절해 가며 이를 표시하고 있었던 것이다.
우리는 그 동안 인지하지 못했지만 모든 작업을 가상 메모리에서 수행하고 있었고 Pintos라는 제한된 환경에서 이를 구현하기 위해 스레드를 이용해 프로세스로 정의했다.
보조 페이지 테이블 (Supplemental Page Table)
우리는 SPT(Supplemental Page Table)이라고 정의한 자료구조를 구현해야 한다. 원래 페이지 테이블이란 것은 CPU와 MMU가 수행하는 가상 주소와 물리 주소를 매핑하고 이를 찾아내는 작업을 하는 하드웨어 적인 페이지 테이블의 한계(추가적인 메타 데이터나 해당 페이지가 어디에 저장되어 있는지 등에 대한 정보)에 의해 추가적인 데이터를 기록하기 위한 소프트웨어적인 페이지 테이블이다.
이제 우리는 이 SPT를 어떻게 관리할 것인지 페이지 테이블을 어떻게 어디에 관리할 것이지 등과 같은 설계를 포함해 구현을 진행한다. 리스트나 배열, 해시 맵, 비트맵과 같은 자료 구조를 소개하는데 사실 해시 맵을 사용하는 것이 가장 보편적이다.
페이지 폴트 (Page Fault)
User Program에서는 페이지 폴트가 나면 별다른 처리를 하지 않고 exit(-1)로 그냥 문제가 있다고 처리했다. 하지만 이제 VM에서는 그렇지 않다. 다른 포스팅에서 다루지만 Lazy Loading이라고 해서 페이지정보만 초기화해서 등록하고 추 후 실제 프로그램 실행 시 가상 주소를 이용해 페이지를 조회했을 때 프레임 정보가 없을 때 페이지 폴트가 발생하고 이때 실제 필요한 데이터를 불러오거나 파일을 읽는 등의 작업을 수행하게 되니 우리는 페이지 폴트 핸들러를 구현해서 페이지 폴트가 발생했을 때 가상 주소에 대한 검증과 프레임 정보를 매핑하고 스택을 확장시키는 등 다양한 구현을 진행하게 될 예정이다.
그렇다면 자세한 내용은 다음 포스팅에서 설명하겠다.
'크래프톤 정글' 카테고리의 다른 글
[pintos] Week4~5: Virtual Memory - Part.3 Anonymous Page (0) | 2025.06.06 |
---|---|
[pintos] Week4~5: Virtual Memory - Part.2 Memory Management (1) | 2025.06.06 |
[pintos] Week2~3: User Program 외전 - Linked List를 이용한 FD관리 (0) | 2025.05.26 |
[pintos] Week2~3: User Program Part.8 - exec, wait (0) | 2025.05.26 |
[pintos] Week2~3: User Program Part.7 - fork (0) | 2025.05.26 |