이번 주는 가상 메모리와 관련된 과제를 진행하였다. VM 파트는 크게 Memory Management, Anonymous Page, Stack Growth, Memory Mapped Files, Swap in/out으로 구분되며 extra로 Copy-on-Write까지 있다.
이번 포스팅에서는 위의 내용들 중에서 Memory Management 파트에 대해 다뤄볼 예정이다.
User Pool & Kernel Pool
사실 memory management 파트에서는 구현할 부분이 다른 파트에 비해 많지는 않은 편이다. 다만 초반에 코드의 흐름을 잡기가 힘들고, 가상 메모리 개념 자체가 잘 와닿지 않기때문에 핀토스의 메모리 구조와 프레임 할당 방식에 관해 중점적으로 다뤄볼 것이다.
우선 핀토스는 아래 그림과 같이 물리 메모리 영역을 유저 풀과 커널 풀의 두 가지 영역으로 나눈다.
테스트에서는 대부분 물리 메모리 영역을 20MB로 잡기 때문에 그림에서도 20MB로 표현했다. 기본적으로 커널 풀과 유저 풀은 각각 물리 메모리의 절반을 차지한다.
우리가 핀토스에서 사용하는 palloc 관련 함수들이 바로 이 물리 메모리 영역을 할당받는 함수이다. threads/palloc.c 파일을 보면 자세히 기술되어 있다.
핀토스에서 메모리를 할당할때에는 무조건 페이지 단위로 맞춰서 메모리 할당을 진행한다. 페이지의 크기는 4096바이트이며, 따라서 모든 페이지의 주소 값은 4096의 배수 형태로 나타나고, 이로 인해 주소값에서 하위 12비트는 페이지의 오프셋을 나타낸다. 물리 메모리 영역을 페이지 크기로 쪼개어 놓은 것을 앞으로 프레임이라고 부르자!
Pintos Virtual Address
우리가 핀토스에서 사용하는 모든 주소 값들은 Virtual Address이다. 즉, palloc을 통해 할당받는 주소 값들 또한 모두 가상 주소이고, 실제로 물리 메모리의 주소 값을 참조하는 일은 없다. 여기까지 왔으면 아마 스레드 구조체의 주소 값을 많이 찍어봤을 텐데, initial thread의 주소 값을 찍어보면 KERNEL_BASE와 같은 0x8004000000을 볼 수 있을 것이다. 핀토스는 init.c 에서 처음 시작할 때 KERNEL_BASE부터 시작하여 KERNEL_BASE + (물리 메모리의 크기)까지 실제 물리 메모리와 mapping 작업을 진행한다. 또한 위에서 유저 풀과 커널 풀에 대해서 이야기했는데, 사실 유저 풀과 커널 풀도 물리 메모리에서 나뉘는 것이 아니라 커널의 가상 주소 영역에서 나뉜다. 그림으로 나타내보면 아래와 같다.
그림에서는 물리 메모리 영역도 두 가지 풀로 나눠뒀는데, 실제로 물리 메모리가 나뉘어져 있지는 않다. 다만 커널의 가상 주소 영역을 유저 풀과 커널 풀로 나누어 매핑하기 때문에 물리 메모리에도 그러한 영역이 나타나는 것.
include/threads/vaddr.h 파일을 보면, is_kernel_addr 이라는 매크로 함수가 있는 것을 볼 수 있다. 인자로 들어온 주소가 커널 영역의 주소, 즉 물리 프레임과 직접 매핑이 되어 있는 주소인지 아니면 유저 영역의 주소인지를 확인해 주는 함수이다. 판단 방법은 인자로 들어온 주소가 KERNEL_BASE보다 큰지 작은지 판별하는 것이다. 그런데 지금 그림에서는 커널 영역의 주소만이 물리 프레임과 매핑이 되어있다. 여기에서 유저 영역의 가상 주소까지 표현하면 다음과 같아진다.
그림과 같이 가상 메모리가 실제 물리 메모리에 비해 아주 큰 것을 볼 수 있다. Kaist-Pintos에서는 64비트 운영체제를 사용하고, 64비트 중 총 48비트를 가상 주소를 표현하는데 사용한다. 따라서 가상 주소로 표현할 수 있는 주소 공간은 무려 256TB이다!
이렇게 방대한 가상 주소 공간에 비해 실제 물리 메모리는 아주 작다. 프로젝트 3에서는 유저가 이렇게 방대한 가상 메모리 공간을 혼자 모두 사용하고있다고 착각하게 만드는 것이 목표이다!
Page Table
위의 그림은 핀토스에서 사용하는 Page Table 구조를 나타낸 것이다. 가상 주소를 해석할때 비트의 자릿수를 나누어 해당 테이블에서의 offset으로 활용하는 것을 볼 수 있다. CPU는 CR3 레지스터에 세팅된 값을 읽어 PML4 Table의 위치를 얻어오고, 주소의 offset 값을 활용해 최종적으로 물리 메모리의 주소를 얻어내는 것을 볼 수 있다.
핀토스의 코드를 보면, process_activate 라는 함수가 있는 것을 알 수 있는데, 이 함수는 프로세스가 처음 실행되거나 context switching이 발생하는 상황에서 호출된다. 이 함수는 CR3 레지스터의 값을 context switching이 발생한 후 CPU를 점유하게 되는 스레드의 PML4 값으로 바꿔주는 역할을 한다.
우리가 프로젝트 3을 진행하기 전에, 이미 핀토스에는 이러한 페이지 테이블이 존재한다. 하지만 기존 페이지 테이블의 가장 큰 문제점이 있는데, 바로 가상 주소 공간이 실제 물리 메모리의 공간만큼 할당이 되면 더 이상 메모리를 할당하지 못한다는 것이다! 이번 과제는 여러 가지 기법들을 통해서 유저 프로그램이 실제 물리 메모리의 공간보다 더 큰 가상 메모리 공간을 모두 사용하는 것처럼 느끼게 하는 것이다.
Supplemental Page Table
우리가 첫번째로 구현해야 할 자료구조이다. 여러 가지 방법으로 구현할 수도 있겠지만 나는 Hash Table 형태로 구현했다. 그렇다면 Supplemental Page Table은 무엇이고, 이미 Page Table이 존재하는데 이 자료구조가 왜 필요할까?
Supplemental Page Table은 기존 페이지 테이블을 조금 더 보완하는 기능을 한다. 기존의 페이지 테이블과 가장 다른 점을 꼽자면, 페이지 테이블은 실제 물리 메모리를 할당받은 페이지들에 대한 정보만 가지고 있지만, Supplemental Page Table은 실제 물리 메모리를 할당받지 못한 페이지들 또한 가지고 있다는 점이다!
그림으로 살펴보자면, 위의 그림이 기존의 페이지 테이블이다.
그리고 위의 그림이 Supplemental Page Table이다. 실제 물리 메모리에 없는 페이지의 정보도 가지고 있고, 그러한 페이지의 값은 실제로 Memory 보다는 조금 더 멀리 있는 Disk에 저장되어 있다. 페이지의 종류에 따라 쫓겨나는 장소가 Swap Disk 또는 File로 달라지는데, 이 내용은 추후에 포스팅할 예정이다!
이렇게 핀토스의 가상 메모리 구조와 우리가 구현해야 할 Supplemental Page Table에 대해 알아보았다! 핀토스 코드에서 threads/mmu.c 파일에 기존 페이지 테이블의 코드가 아주 상세히 나와있으니, 궁금하면 참고해도 좋다.
'Operating System' 카테고리의 다른 글
[Pintos] User VA, Kernel VA (0) | 2023.05.23 |
---|---|
[Pintos] Project 2 - System Call(fork, wait, exec, exit) (0) | 2023.05.20 |
[Pintos] System Call - File I/O (1) | 2023.05.09 |
[Pintos] Multi-Level Feedback Queue Scheduler(mlfqs) (1) | 2023.05.08 |
[Pintos] Project 2 User Programs - Argument Passing (1) | 2023.05.08 |