가상메모리에 관하여(3)
안녕하세요~
저번 시간에 가상 메모리의 생성 과정을 코드로 확인해보는 시간을 가졌습니다.
하지만, 실제 프로세스 구동에 필요한 코드나 데이터는 물리 메모리(RAM) 에 존재하는데요.
따라서, 오늘 시간에는 CPU가 어떻게 물리메모리를 접근할 수 있는지에 대해 알아보겠습니다.
0. 메모리 액세스
1. 페이지 테이블
2. 페이지 Fault
0. 메모리 액세스
메모리 액세스란, CPU 가 가상 메모리 주소를 사용하여 물리 메모리에 저장된 데이터나 명령어를 R/W 하는 과정입니다.
이때, CPU는 물리 메모리 주소에 접근하기 위 프로세스의 페이지 테이블을 참조합니다.
메모리 액세스의 순서는 다음과 같습니다.
(1) Fetch
CPU가 프로그램 카운터(PC)로 부터 다음에 실행되어야 할 instruction 을 가져 옵니다.
(2) Decode
해당 명령어를 해석하여 물리 메모리 r/w 여부를 판단합니다.
(3) Execute
명령어를 실행하는 과정인데, 메모리 접근이 필요할 시 CPU는 MMU(Memory Managemnet Unit) 을 통해 페이지 테이블을 조회하고 가상주소를 물리 주소로 변환합니다.
이후, 변환된 물리 주소를 사용하여 물리 메모리에 데이터를 읽고 씁니다.
그러면, objdump 를 통해서 해당 과정을 빠르게 해석해봅시다.
[root@ip-10-0-142-220 ~]# whatis objdump
objdump (1) - display information from object files
root@ip-10-0-142-220 test]# objdump -d hello
000000000401136 <main>:
401136: 55 push %rbp
401137: 48 89 e5 mov %rsp,%rbp
40113a: 48 83 ec 10 sub $0x10,%rsp
40113e: c7 45 fc 01 00 00 00 movl $0x1,-0x4(%rbp)
401145: 8b 45 fc mov -0x4(%rbp),%eax
401148: 89 c6 mov %eax,%esi
40114a: bf 10 20 40 00 mov $0x402010,%edi
40114f: b8 00 00 00 00 mov $0x0,%eax
저희가, 참고해야 될 부분은 mov1 instruction 입니다.
movl $0x1, -0x4(%rbp) 명령어는 '1' 을 메모리 위치에 저장하는 명령어 입니다.
Execute 시, 메모리 액세스가 발생하는데 CPU는 %rbp 레지스터와 해당 코드의 offset 을 조합하여 가상 메모리 주소를 얻습니다.
이후, MMU 를 통해 페이지테이블 조회하여 변환된 물리주소에 '1' 을 저장합니다.
1. 페이지 테이블
CPU가 실제 메모리에 있는 데이터를 읽거나 쓰기 위해서 물리 메모리 접근(Memory Access) 과정이 필요합니다.
이때 OS 는 프로세스별 페이지 테이블을 생성하고, 이 페이지 테이블을 통해 가상 주소와 물리 주소간의 매핑이 이뤄집니다. CPU 는 페이지 테이블을 참조하여 물리 메모리에 접근합니다.
실제 코드를 통해 페이지 테이블을 이해해보겠습니다.
#include <stdio.h>
#include <unistd.h>
int main() {
int a = 1;
printf("Hello, World! %d\n", a);
printf("Address of a: %p\n", (void*)&a);
while (1){
sleep(1);
}
return 0;
}
gcc 컴파일러를 통해 hello.c -> hello (ELF) 파일로 변환합니다.
hello 코드 내, 변수 a를 선언하였고, 이는 가상 메모리 내 스택 주소공간에 할당됩니다.
실제 변수 a가 stack 공간에 할당 되었는지 확인해보겠습니다.
[root@ip-10-0-142-220 test]# ./hello
Hello, World! 1
Address of a: 0x7ffc4f649e9c
[root@ip-10-0-142-220 ~]# pidof hello
35202
[root@ip-10-0-142-220 ~]# cat /proc/35202/maps
00400000-00401000 r--p 00000000 ca:04 9098393 /root/test/hello
00401000-00402000 r-xp 00001000 ca:04 9098393 /root/test/hello
00402000-00403000 r--p 00002000 ca:04 9098393 /root/test/hello
00403000-00404000 r--p 00002000 ca:04 9098393 /root/test/hello
00404000-00405000 rw-p 00003000 ca:04 9098393 /root/test/hello
007ff000-00820000 rw-p 00000000 00:00 0 [heap]
7fed57c00000-7fed57c28000 r--p 00000000 ca:04 25540836 /usr/lib64/libc.so.6
7fed57c28000-7fed57d9d000 r-xp 00028000 ca:04 25540836 /usr/lib64/libc.so.6
7fed57d9d000-7fed57df5000 r--p 0019d000 ca:04 25540836 /usr/lib64/libc.so.6
7fed57df5000-7fed57df6000 ---p 001f5000 ca:04 25540836 /usr/lib64/libc.so.6
7fed57df6000-7fed57dfa000 r--p 001f5000 ca:04 25540836 /usr/lib64/libc.so.6
7fed57dfa000-7fed57dfc000 rw-p 001f9000 ca:04 25540836 /usr/lib64/libc.so.6
7fed57dfc000-7fed57e09000 rw-p 00000000 00:00 0
7fed57f56000-7fed57f5a000 rw-p 00000000 00:00 0
7fed57f5e000-7fed57f60000 r--p 00000000 ca:04 25540832 /usr/lib64/ld-linux-x86-64.so.2
7fed57f60000-7fed57f86000 r-xp 00002000 ca:04 25540832 /usr/lib64/ld-linux-x86-64.so.2
7fed57f86000-7fed57f91000 r--p 00028000 ca:04 25540832 /usr/lib64/ld-linux-x86-64.so.2
7fed57f92000-7fed57f94000 r--p 00033000 ca:04 25540832 /usr/lib64/ld-linux-x86-64.so.2
7fed57f94000-7fed57f96000 rw-p 00035000 ca:04 25540832 /usr/lib64/ld-linux-x86-64.so.2
7ffc4f62a000-7ffc4f64b000 rw-p 00000000 00:00 0 [stack]
7ffc4f6d2000-7ffc4f6d6000 r--p 00000000 00:00 0 [vvar]
7ffc4f6d6000-7ffc4f6d8000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
실제 수행되고 있는 hello 프로세스의 맵을 확인하기 위해, /proc/[pid]/maps 를 확인합니다.
변수 a 의 가상주소가 stack 섹션에 위치 함을 알 수 있습니다.
그러면, 해당 가상주소가 어떠한 물리 주소에 매핑되어 메모리 액세스를 진행하는지 알아보겠습니다.
/proc/[pid]/pagemap 파일 시스템을 통해 알 수 있는데, pagemap 의 경우 바이너리 파일이라 신규 프로그램을 작성해야 합니다.
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <inttypes.h>
#define PAGEMAP_LENGTH 8
uint64_t get_pagemap_entry(uint64_t virt_addr) {
FILE *f;
uint64_t file_offset, pagemap_entry;
ssize_t bytes_read;
// Open the pagemap file for the current process
f = fopen("/proc/self/pagemap", "rb");
// Calculate the offset in the pagemap file
file_offset = (virt_addr / getpagesize()) * PAGEMAP_LENGTH;
// Read the entry from the pagemap file
bytes_read = fread(&pagemap_entry, 1, PAGEMAP_LENGTH, f);
fclose(f);
return pagemap_entry;
}
int main() {
int a = 1;
uint64_t virt_addr = (uint64_t)&a;
uint64_t pagemap_entry = get_pagemap_entry(virt_addr);
// Extract the physical page number (PPN)
uint64_t phys_addr = (pagemap_entry & 0x7FFFFFFFFFFFFF) * getpagesize();
printf("Virtual address: 0x%" PRIx64 "\n", virt_addr);
printf("Physical address: 0x%" PRIx64 "\n", phys_addr);
return 0;
}
[root@ip-10-0-142-220 test]# ./hello_pagemap
Virtual address: 0x7ffc4fdc6254
Physical address: 0x1b02f000
실제 물리 메모리 접근 시, 컴파일된 프로그램의 offset 과 커널이 알고 있는 가상 메모리 주소를 결합하여 페이지 테이블을 생성합니다.
해당 코드를 통해, 대략적인 페이지 테이블을 그려보면 아래와 같습니다.
VA | PA |
0x7ffc4dc6254 | 0x1b02f000 |
2. 페이지 Fault
Page Fault, ?
1. CPU 가 페이지 테이블을 참조하는데, 가상 주소와 물리 메모리 주소가 매핑되지 않은 상태.
VA | PA |
0x7ffc4dc6254 | ? |
2. 페이지 테이블에 매핑 되어 있으나, SWAP OUT 되어 해당 페이지가 디스크에 있는 경우.
이러한 경우, OS 는 어떠한 역할을 수행하게 될까요?
Page Fault 처리 과정
1. 페이지 폴트 핸들러 실행
2. OS 가 페이지 테이블을 검사 합니다.
3. 페이지 로드
- 페이지 스왑 아웃의 경우, 스왑 영역에서 해당 페이지를 찾아 물리 메모리에 로드합니다.
- 신규 페이지의 경우, 물리 메모리에서 빈 페이지를 찾아 페이지 테이블을 업데이트 합니다.
4. 프로세스 재개
오늘도 방문해주셔셔 감사합니다.
피드백은 언제나 환영입니다.