각 태스크는 task_stuct
라는 자료구조를 통해 관리되고, 고유한 가상 메모리 역시 mm_struct
를 통해 관리되어 진다.
mm_struct
자료구조는 크게 세 부분으로 구분되어진다.
vm_area_struct
region
, 흔히 말하는 세그먼트)regin
을 vm_area_struct
로 관리한다.pgd
(Page Global Directory
)start_code
, start_data
, start_stack
등등.vm_area_struct
코드include/linux/mm_types.h
헤더 파일 내의 내용이다. 다음의 코드에서 다음의 필드를 찾을 수 있다:
vm_start
: 시작 주소vm_end
: 끝 주소vm_flags
: 접근 제어 플래그(Read-Only
, Writable
, etc.) vm_file
: 실제 파일 위치vm_offset
: 파일 내 세그먼트의 위치에 해당하는 오프셋와 같은 필드를 찾을 수 있다. 4번과 5번은 페이지 폴트 발생 시 사용한다.
리눅스의 가상 주소 메모리 할당의 단위를 페이지(Page
) 라 부르며, 크기는 보통 4 KiB
이다.
vm_area_struct
구조 및 동작 방식하나의 태스크는 여러 개의 vm_area_struct
를 가진다. 겹치지 않으며 새로 사용하려는 가상 주소 공간이 인접한 vm_area_struct
와 동일한 속성을 가진다면, 하나의 vm_area_struct
로 합쳐진다.
같은 태스크에 속한 vm_area_struct
는 연결 리스트 + 레드 블랙 트리로 연결되어 있다. 섹션 8 의 그림을 보면 vm_next
, vm_prev
, vm_rb
가 그것에 해당한다.
효율성 문제 때문인데 탐색에서는 Red black Tree
를 사용하고, 연결 관계 파악에는 Doubly linked List
를 사용한다.
각각의 멤버 변수는 가상 주소(Virtual Address)
를 저장한다.
가상 메모리의 각 주소는 elf_loader
가 결정한다. 커널은 전혀 개입하지 않는다.
data
, bss
는 컴파일 타임에 그 크기가 결정되지만 다음의 차이가 있다: bss
는 메모리가 초기화되어 있으나, data
는 그러하지 않음.
스택과 힙 사이 공간에는 라이브러리가 올라간다 (ex. glibc
)
mm_struct
의 관리 범위mm_struct
는 커널과 유저 영역 모두 관리할까?
1. 커널의 mm_struct
는 init_mm
하나만 존재
2. 커널 스택은 task_struct
의 void *stack
으로 관리됨.
프로그램의 실행은 아래의 순서로 이뤄진다:
실행 파일의 어느 부분을 물리 메모리에 적재하는지는 ELF Header
를 읽어서 결정한다. 가상주소와의 연결은 미리 정해진 규칙(~/include/linux/elf.h
) 을 따른다.
Page frame
) 페이지(Page
): 가상 메모리의 최소 할당 단위
페이지 프레임(Page frame
): 물리 메모리의 최소 할당 단위
sys_execve()
시스템 콜은 인자로 전달된 프로그램을 메모리에 적재한다. sys_execv()
는 아래의 순서로 동작한다:
free
한 페이지 프레임들을 할당 받는다.demand paging
)파일의 모든 내용을 실행 즉시 물리 메모리에 적재하는 것이 아닌, 페이지 테이블의 NP (Not Present)
상태일 때, 폴트를 발생시켜 그 때 로드한다. 이를 요구 페이징(demand paging
) 이라 한다.
요구 페이징을 확인하기 위해 간단한 예제 코드를 작성해보았다:
#include <stdio.h>
#include <stdbool.h>
char array[1024 * 1024 * 1024]; // 1 GiB
int main(void)
{
while (true)
/* do nothing */;
return 0;
}
보는 것처럼 가상 메모리(VIRT
)는 크지만 실제 사용하고 있는 메모리(RES
)는 그리 많지 않다.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
char array[1024 * 1024 * 1024]; // 1 GiB
int main(void)
{
srand((unsigned long) time(NULL));
for (int i = 0; i < 1024 * 1024 * 1024; i++)
array[i] = rand();
while (true)
/* do nothing */;
return 0;
}
코드를 바꿔서 배열의 각 원소에 임의의 값을 집어 넣도록 했더니 실제 물리 메모리 사용량이 1.0g
로 바뀐 것을 볼 수 있다.
PGD
= Page Global Directory
PDM
= Page Middle Directory
PTE
= Page Table Entry
~/mm/memory.c
=> follow_page_pte()
대부분의 CPU
는 가상주소를 물리주소로 변환해주는 별도의 하드웨어를 장착하고 있고 이를, MMU (Memory management Unit)
이라 부른다. 지원하는 페이지 레벨은 CPU
제조사 별로 다르다.
64 bit
CPU 인 경우 3 단계 페이징 기법으로 표현하기에 무리가 있으므로 4 단계 페이징을 사용한다 > 5.10
버전에선 5 단계까지 사용
버디나 슬랩 할당자는 연속적인 메모리 공간을 할당해주는데, 시스템은 연속적인 물리 메모리가 늘 넉넉하진 않다. 따라서 반드시 물리적으로 연속되지 않아도 되는, 가상적으로만 연속인 메모리를 할당해주는 vmalloc()
과 vfree()
를 통해 할당&해제 받을 수 있다.
demand paging
)HAT (Hardware Addresss Translation)
또는 MMU (Memory Management Unit
)) 를 필요로 한다.[이미지] https://www.programmersought.com/article/55702605527/
[사이트] https://medium.com/@mxatone/kernel-memory-randomization-and-trampoline-page-tables-9f73827270ab
[책] 리눅스 커널: 내부구조 (백승제, 최종무 저)
운영체제 과제를 하는 도중에 많이 도움을 받았습니다 감사합니다 ㅠㅠ