Linux/Linux Structure 2021. 9. 26. 03:04

Chapter 5. 메모리 관리

메모리 통계 정보

free

free 명령어로 메모리에 대한 정보를 알 수 있다. (단위 : KiB)

$ free
              total        used        free      shared  buff/cache   available
Mem:        7729028     1552460     4177240      536648     1999328     5375580
스왑:       2097148           0     2097148
  • total : 시스템의 전체 메모리 용량

  • free : 표기 상 이용하지 않는 메모리 (available - buff)

  • buff/cache : 버퍼 캐시, 페이지 캐시가 이용하는 메모리. free 필드의 메모리가 부족하면 커널이 buff/cache 메모리를 해제하고 할당해준다.

  • available : 실질적으로 사용할 수 있는 메모리. free + buff/cache + 다른 커널 내의 해제할 수 있는 메모리 를 의미한다.

sar -r

sar -r 명령어를 통해 메모리에 관련된 통계 정보를 얻을 수 있다.

$ sar -r 1 1 # 1초 간격(2번째 파라미터)으로 1번만(3번째 파라미터)
Linux 5.13.13-surface (ubun2-Surface-Pro-7)     2021년 09월 16일     _x86_64_(8 CPU)

00시 44분 33초 kbmemfree   kbavail kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
00시 44분 34초   4225432   5460160   1397816     18.09     67080   1869944   6410488     65.24    722640   2019192       152
...

free 결과와의 비교

  • free 명령어 결과의 free 필드 : kbmemfree

  • buff/cache 필드 : kbbuffers + kbcached


메모리 부족

메모리 사용량이 증가하면 비어 있는 메모리(free 필드)가 줄어들고, 커널은 해제 가능한 메모리 영역(buff/cache + 다른 커널에서 해제 가능한 영역)을 해제한다.

메모리 사용량이 감당할 수 없을 정도로 계속해서 증가하면 시스템은 메모리 부족 상태OOM, Out Of Memory가 된다.

  • OOM Killer : 메모리 관리 시스템이 적절한 프로세스를 선택하여 강제 종료(kill) 하여 메모리를 확보하는 기능

  • OOM Killer는 어떤 프로세스가 강제 종료될 지 알 수 없다. 내부적으로는 우선순위나 사용한지 오래되었다거나 하는 알고리즘을 통해 선정할 것이다.

  • 서버 컴퓨터에서 프로세스가 강제 종료되어 문제가 발생할 경우 조치가 더 힘들기 때문에, sysctl의 vm.panic_on_oom 파라미터의 기본 값을 1로 변경하여 메모리 부족 시 시스템 자체를 강제 종료하는 경우도 있다고 한다.


단순 메모리 할당

메모리 할당이 필요한 상황은 주로 프로세스를 생성하거나, 동적 메모리를 할당하는 경우이다. 프로세스를 생성하는 경우는 3장에서와 같이 fork(), execve() 등의 함수를 이용하는 경우이다.

메모리 할당은 단순히 물리 메모리에 할당하는 방식과 가상 메모리를 사용하는 방식이 있는데, 단순 메모리 할당 방식은 문제점이 있다.

동적 메모리 할당 과정

프로세스에서 동적으로 메모리가 필요한 경우, 프로세스는 커널에 메모리 확보용 시스템 콜을 호출한다. 커널은 필요한 사이즈를 메모리 영역에서 확보하고, 해당 영역의 시작 주소값을 반환한다. 실제 메모리 할당 방식은 가상 메모리를 통해 이루어진다. 단순 메모리 할당 방식을 사용하면 다음의 문제점들이 존재한다.

  • 메모리 단편화

  • 다른 프로세스의 메모리에 접근 가능

  • 여러 프로세스 실행 시 문제

메모리 단편화memory fragmentation

메모리의 획득, 해제를 반복할 경우 메모리에 빈 공간이 여러 군데에 쪼개져있는 상태로 지속되고, 이 때문에 메모리가 충분함에도 큰 용량에 대한 메모리 할당을 할 수 없게된다.

물리적으로 떨어진 빈 공간 여러 개를 프로세스가 하나의 영역으로 다루기에는 다음과 같은 문제점이 존재한다.

  • 프로세스가 메모리를 획득할 때마다 메모리의 분할 개수를 확인해야 하므로 불편하다.

  • 하나의 영역처럼 다루더라도 배열같이 연속된 데이터를 다루기가 쉽지 않다.

다른 프로세스의 메모리에 접근 가능

단순 메모리 할당 방식에서는 프로세스가 커널이나 다른 프로세스가 사용하고 있는 주소를 직접 지정할 경우 해당 영역에 접근할 수 있기 때문에 위험하다.

여러 프로세스 실행 시 문제

3장에서 readelf 명령어로 프로그램의 구조를 봤을 때, 코드와 데이터 영역의 파일 상 오프셋 등의 정보가 존재했다.

만약 동일한 프로그램으로 여러 프로세스를 생성하는 경우, 해당 메모리 영역들은 어쩔 수 없이 메모리 상에서 서로 다른 공간에 위치하게 된다. 이는 최대 1개의 프로세스만 프로그램의 정보에 있는 메모리 주소와 일치할 수 있으므로 다른 프로세스들은 실행할 수 없게된다.

만약 단순 메모리 할당 방식을 사용하면, 각 프로그램마다 동작할 주소가 겹치지 않도록 프로그래머가 신경써야 하는데, 현실적으로 불가능하다.


가상 메모리

위의 문제점들을 해결하기 위해 요즘 나오는 CPU에는 메모리 관리 장치MMU, Memory Management Unit가 존재하며, MMU에서 가상 메모리 주소를 실제 메모리 주소로 변환한다.

가상 메모리는 프로세스가 물리 메모리에 직접 접근(위에서 말한 단순 메모리 할당 방식)하지 않고, 가상 주소를 사용하여 간접적으로 접근하도록 하는 방식이다.

  • 프로세스가 인식하는 주소는 가상 주소이고, 이를 CPU에서 변환하여 물리 주소에 접근한다.

  • readelf, cat /proc/PID/maps 의 출력 결과에 나온 주소는 모두 가상 주소이다.

  • 프로세스에서 물리 주소에 직접 접근하는 방법은 없다.

페이지 테이블

가상 주소에서 물리 주소로의 변환은 커널 내부의 페이지 테이블Page Table을 참조한다. 변환은 페이지 단위로 이루어진다.

페이지 테이블에서 한 페이지에 대한 데이터를 페이지 테이블 엔트리PTE, Page Table Entry라고 한다.

  • PTE에는 가상 주소와 물리 주소의 대응 정보가 들어있다.

  • x86_64 아키텍처의 페이지 사이즈는 4KiB이다.

PT에 물리 주소가 매핑되지 않은 가상 주소에 접근하는 경우에는 CPU에 페이지 폴트page fault 인터럽트가 발생한다. 그러니까 PTE가 존재하지 않는 가상 주소에 접근한다는 것은 허용되지 않은 메모리 영역에 접근한다는 것이다.

  • 현재 실행 중인 명령이 중단되고, 커널 내의 페이지 폴트 핸들러page fault handler 인터럽트 핸들러가 동작한다.

  • 커널은 메모리 접근이 잘못되었다는 내용을 페이지 폴트 핸들러에 전달하고, SIGSEGV 시그널을 프로세스에 전달하여 프로세스를 강제 종료시킨다. C언어 프로그래밍할 때 많이 봤던 segmentation fault가 그 예시이다.

segmentation fault 발생 프로그램

잘못된 주소에 접근하는 프로그램을 작성한다.

$ gcc -o output/segv src/segv.c 
$ output/segv
Before invalid access
[1]    9866 segmentation fault (core dumped)  output/segv
  • *p = 0;을 처리하면서 segmentation fault가 발생한 것을 알 수 있다.

파이썬, javascript 등 메모리를 직접 다루지 않는 언어의 경우 직접 작성한 부분에서는 이런 에러가 발생하는 경우가 거의 없다.


메모리 할당 과정

프로세스 생성

프로세스를 생성할 때는 3장에서 본 것처럼, 프로그램의 실행 파일을 통해 여러 정보를 획득하여 다음 과정을 통해 실행된다.

  • 코드 영역 사이즈 + 데이터 영역 사이즈 만큼의 메모리를 물리 메모리에 할당하여 필요한 데이터를 복사한다.

    • 실제로는 디맨트 페이징 방식을 사용하여 할당한다.
  • 프로세스를 위한 페이지 테이블을 생성하고, 가상 주소를 물리 주소에 매핑한다.

  • 엔트리 포인트의 주소에서 실행을 시작한다.

추가 메모리 할당

프로세스가 동적 메모리를 요구하면 커널은 새로운 메모리를 할당하여 페이지 테이블에 저장하고, 할당한 물리 메모리 주소에 대응되는 가상 주소를 프로세스에 반환한다.

메모리 맵 출력 프로그램

과정

  • 프로세스의 메모리 맵 정보(/proc/PID/maps)를 출력한다.

  • 메모리를 100MiB 확보한다.

  • 다시 메모리 맵 정보를 출력한다.

$ gcc -o output/mmap src/mmap.c
$ output/mmap 
memory map before memory allocation
55cf6a754000-55cf6a755000 r--p 00000000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6a755000-55cf6a756000 r-xp 00001000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6a756000-55cf6a757000 r--p 00002000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6a757000-55cf6a758000 r--p 00002000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6a758000-55cf6a759000 rw-p 00003000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6af4d000-55cf6af6e000 rw-p 00000000 00:00 0                          [heap]
7f2b8f0a2000-7f2b8f0c7000 r--p 00000000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f0c7000-7f2b8f23f000 r-xp 00025000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f23f000-7f2b8f289000 r--p 0019d000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f289000-7f2b8f28a000 ---p 001e7000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f28a000-7f2b8f28d000 r--p 001e7000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f28d000-7f2b8f290000 rw-p 001ea000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f290000-7f2b8f296000 rw-p 00000000 00:00 0 
7f2b8f2a8000-7f2b8f2a9000 r--p 00000000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2a9000-7f2b8f2cc000 r-xp 00001000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2cc000-7f2b8f2d4000 r--p 00024000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2d5000-7f2b8f2d6000 r--p 0002c000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2d6000-7f2b8f2d7000 rw-p 0002d000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2d7000-7f2b8f2d8000 rw-p 00000000 00:00 0 
7fff2c0fc000-7fff2c11d000 rw-p 00000000 00:00 0                          [stack]
7fff2c164000-7fff2c168000 r--p 00000000 00:00 0                          [vvar]
7fff2c168000-7fff2c16a000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]

succeeded to allocate memory : address(0x7f2b88ca2000), size(0x6400000) # 아래에서 이 주소를 찾을 수 있다. 위에는 없음

memory map after memory allocation
55cf6a754000-55cf6a755000 r--p 00000000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6a755000-55cf6a756000 r-xp 00001000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6a756000-55cf6a757000 r--p 00002000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6a757000-55cf6a758000 r--p 00002000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6a758000-55cf6a759000 rw-p 00003000 103:05 1443273                   /home/ubun2/linux-structure-practice/chapter05/output/mmap
55cf6af4d000-55cf6af6e000 rw-p 00000000 00:00 0                          [heap]
7f2b88ca2000-7f2b8f0a2000 rw-p 00000000 00:00 0  # 새로 할당된 부분
7f2b8f0a2000-7f2b8f0c7000 r--p 00000000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f0c7000-7f2b8f23f000 r-xp 00025000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f23f000-7f2b8f289000 r--p 0019d000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f289000-7f2b8f28a000 ---p 001e7000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f28a000-7f2b8f28d000 r--p 001e7000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f28d000-7f2b8f290000 rw-p 001ea000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f2b8f290000-7f2b8f296000 rw-p 00000000 00:00 0 
7f2b8f2a8000-7f2b8f2a9000 r--p 00000000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2a9000-7f2b8f2cc000 r-xp 00001000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2cc000-7f2b8f2d4000 r--p 00024000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2d5000-7f2b8f2d6000 r--p 0002c000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2d6000-7f2b8f2d7000 rw-p 0002d000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f2b8f2d7000-7f2b8f2d8000 rw-p 00000000 00:00 0 
7fff2c0fc000-7fff2c11d000 rw-p 00000000 00:00 0                          [stack]
7fff2c164000-7fff2c168000 r--p 00000000 00:00 0                          [vvar]
7fff2c168000-7fff2c16a000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
  • 계산을 통해 새로 할당된 메모리 영역의 크기가 100MiB임을 볼 수 있다.

    $ python -c "print((0x7f2b8f0a2000 - 0x7f2b88ca2000) / 1024 / 1024, 'MiB')"
    100.0 MiB

High level에서의 메모리 할당

libc에는 malloc() 함수로 메모리 확보를 하는데, 리눅스에서는 malloc() 함수 내부에서 mmap() 시스템 콜을 호출하여 메모리 할당을 구현한다.

  • mmap() 함수는 커널 모드에서 동작한다.

  • 메모리 확보 단위

    • mmap() : 페이지 단위

    • malloc() : 바이트 단위

먼저 mmap() 함수에서 커다란 메모리 영역을 확보하여 메모리 풀을 만들고, malloc()이 호출되면 바이트 단위로 할당한다. 메모리 풀에 비어 있는 영역이 없을 경우 다시 mmap() 함수로 새로운 메모리 영역을 확보한다.

위의 과정은 OS가 유저 모드로 동작할 때 OS에서 제공하는 기능(glibc의 malloc())이다.

여담으로, 사용 중인 메모리의 양을 측정하는 프로그램에서는 malloc() 함수 등으로 획득한 바이트 수의 총합을 나타내는데, 실제 리눅스에서는 해당 값보다 더 많은 메모리 풀 영역을 확보하고 있기 때문에 리눅스 자체에서 사용하는 메모리 크기가 더 크다.

또한 파이썬처럼 직접 메모리 관리를 하지 않는 스크립트 언어에서도 내부적으로는 libc의 malloc() 함수를 사용한다. 이는 2장에서처럼 strace 명령어로 추적해볼 수 있다.


가상 메모리를 통한 문제점 해결

단순 메모리 할당 방식에서의 문제점들을 가상 메모리로 해결할 수 있다.

메모리 단편화

단편화되어있는 물리 메모리 주소를 페이지 테이블에서 적절하게 가상 주소로 매핑하면 해결할 수 있다.

다른 프로세스의 메모리에 접근 가능

가상 주소 공간과 페이지 테이블은 프로세스 별로 만들어지기 때문에, 구조적으로 다른 프로세스의 메모리에 접근할 수 없다.

실제로는 커널의 메모리에 대해 모든 프로세스가 가상 주소 공간에 매핑되어 있는데(페이지 테이블에 커널 영역까지 기록되어있음), 커널 영역의 PTE(커널 자체가 사용하는 메모리에 대응하는 페이지)에는 CPU가 커널 모드로 실행할 때만 접근하도록 하는 커널 모드 전용 정보가 추가되어있다. 즉 유저 모드에서 커널 메모리에 접근하는 것은 불가능하다.

여러 프로세스 실행 시 문제

각 프로세스마다 가상 주소 공간이 존재하므로, 구조적으로 다른 프로그램과 물리 주소가 겹칠 일이 없다.


가상 메모리 응용

파일 맵file map

일반적으로 프로세스가 파일에 접근할 때는 파일을 열고 read(), write(), lseek() 등의 시스템 콜을 사용한다.

mmap() 함수를 특정한 방법으로 호출하면, 파일의 내용을 메모리에 읽어서 가상 주소 공간에 매핑할 수 있다. 매핑된 파일은 메모리에 접근하는 것과 같은 방식으로 접근할 수 있고, 접근하여 수정한 영역은 나중에(6장에서 설명) 실제 파일에 기록된다.

파일 맵 실험

  • 확인할 항목

    • 파일이 가상 주소 공간에 매핑되는지

    • 매핑된 영역을 읽으면 파일이 실제로 읽어지는지

    • 매핑된 영역에 쓰기를 하면 실제 파일에 써지는지

  • 사전 작업

    • hello 문자열을 파일에 저장한다.

      $ echo hello > testfile
      $ cat testfile 
      hello
  • 테스트 프로그램

    1. 프로세스의 메모리 맵 정보(/proc/PID/maps)를 출력한다.

    2. testfile을 열어둔다.

    3. 파일을 mmap()으로 메모리 공간에 매핑한다.

    4. 프로세스의 메모리 맵 정보를 다시 출력한다.

    5. 매핑 영역의 데이터를 읽어서 출력한다.

    6. 매핑 영역에 쓰기를 시도한다.

    7. 파일 확인

  • 프로그램 실행 결과

    $ gcc -o output/filemap src/filemap.c 
    $ output/filemap 
    memory map before memory allocation
    55aeb0a27000-55aeb0a28000 r--p 00000000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a28000-55aeb0a29000 r-xp 00001000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a29000-55aeb0a2a000 r--p 00002000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a2a000-55aeb0a2b000 r--p 00002000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a2b000-55aeb0a2c000 rw-p 00003000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a52000-55aeb0a73000 rw-p 00000000 00:00 0                          [heap]
    7f5e50cac000-7f5e50cd1000 r--p 00000000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50cd1000-7f5e50e49000 r-xp 00025000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e49000-7f5e50e93000 r--p 0019d000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e93000-7f5e50e94000 ---p 001e7000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e94000-7f5e50e97000 r--p 001e7000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e97000-7f5e50e9a000 rw-p 001ea000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e9a000-7f5e50ea0000 rw-p 00000000 00:00 0 
    7f5e50eb2000-7f5e50eb3000 r--p 00000000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50eb3000-7f5e50ed6000 r-xp 00001000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50ed6000-7f5e50ede000 r--p 00024000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50edf000-7f5e50ee0000 r--p 0002c000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50ee0000-7f5e50ee1000 rw-p 0002d000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50ee1000-7f5e50ee2000 rw-p 00000000 00:00 0 
    7ffc7424d000-7ffc7426e000 rw-p 00000000 00:00 0                          [stack]
    7ffc7435e000-7ffc74362000 r--p 00000000 00:00 0                          [vvar]
    7ffc74362000-7ffc74364000 r-xp 00000000 00:00 0                          [vdso]
    ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
    
    succeeded to allocate memory : address(0x7f5e4a8ac000), size(0x6400000) # 이 주소를 아래에서 찾으면 됨
    
    memory map after memory allocation
    55aeb0a27000-55aeb0a28000 r--p 00000000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a28000-55aeb0a29000 r-xp 00001000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a29000-55aeb0a2a000 r--p 00002000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a2a000-55aeb0a2b000 r--p 00002000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a2b000-55aeb0a2c000 rw-p 00003000 103:05 1448221                   /home/ubun2/linux-structure-practice/chapter05/output/filemap
    55aeb0a52000-55aeb0a73000 rw-p 00000000 00:00 0                          [heap]
    7f5e4a8ac000-7f5e50cac000 rw-s 00000000 103:05 1443568                   /home/ubun2/linux-structure-practice/chapter05/testfile # 여기가 testfile이 매핑된 영역
    7f5e50cac000-7f5e50cd1000 r--p 00000000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50cd1000-7f5e50e49000 r-xp 00025000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e49000-7f5e50e93000 r--p 0019d000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e93000-7f5e50e94000 ---p 001e7000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e94000-7f5e50e97000 r--p 001e7000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e97000-7f5e50e9a000 rw-p 001ea000 103:05 2099337                   /usr/lib/x86_64-linux-gnu/libc-2.31.so
    7f5e50e9a000-7f5e50ea0000 rw-p 00000000 00:00 0 
    7f5e50eb2000-7f5e50eb3000 r--p 00000000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50eb3000-7f5e50ed6000 r-xp 00001000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50ed6000-7f5e50ede000 r--p 00024000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50edf000-7f5e50ee0000 r--p 0002c000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50ee0000-7f5e50ee1000 rw-p 0002d000 103:05 2099333                   /usr/lib/x86_64-linux-gnu/ld-2.31.so
    7f5e50ee1000-7f5e50ee2000 rw-p 00000000 00:00 0 
    7ffc7424d000-7ffc7426e000 rw-p 00000000 00:00 0                          [stack]
    7ffc7435e000-7ffc74362000 r--p 00000000 00:00 0                          [vvar]
    7ffc74362000-7ffc74364000 r-xp 00000000 00:00 0                          [vdso]
    ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0                  [vsyscall]
    
    file contents before overwrite mapped region: hello # 기존 파일 내용
    
    overwritten mapped region: HELLO # 덮어쓰기한 내용을 출력한 결과
    
    $ cat testfile 
    HELLO # 실제 파일에도 적용됨
    • testfile에 write() 시스템 콜이나 fprintf() 등의 함수를 실행하지 않고, 메모리에 매핑된 영역에 memcpy() 함수로 복사하는 것만으로도 파일의 내용을 변경할 수 있다는 것을 알 수 있다.

디맨드 페이징

앞에서 설명한 메모리 확보 방식(커널이 메모리를 미리 확보해놓는 방식)은 메모리의 낭비가 존재한다.

  • 프로그램 중 실행에 사용하지 않는 기능에 대한 코드와 데이터 영역

  • glibc가 확보한 메모리 맵 중 malloc() 함수로 확보되지 않는 부분

이러한 문제를 해결하기 위해 리눅스에서는 디맨드 페이징demand paging 방식을 사용한다. 가상 주소 공간만 먼저 할당해두고, 물리 메모리는 말 그대로 요구(디맨드)할 떄 매핑하는(페이징) 방법이다.

  • PTE에서 "프로세스에는 할당되었지만 물리 메모리에는 할당되지 않음" 상태가 추가된다.

    • 가상 주소 공간은 존재하지만 물리 주소 공간에 매핑되지 않은 상태를 의미한다.
  • 가상 주소에 처음 접근할 때 CPU에 페이지 폴트가 발생하고, 해당 가상 주소 공간에 물리 주소가 매핑된다.

    • 커널의 페이지 폴트 핸들러가 물리 메모리를 매핑한 다음, 페이지 폴트를 지운다. (저 위의 segmentation fault와는 다름)
  • 유저 모드에서 프로세스 실행을 계속한다. 프로세스는 페이지 폴트가 발생한 사실을 알지 못한다.

프로세스에서 mmap() 함수를 통해 동적으로 메모리를 획득한 경우, "가상 메모리를 확보한 상태"라고 볼 수 있다. 이 가상 메모리에 접근할 때 물리 메모리를 확보하고 매핑하는 것을 "물리 메모리를 확보한 상태"라고 표현한다. 이 때 가상 메모리와는 상관없이 물리 메모리 부족이 발생할 수 있다.

디맨드 페이징 실험

  • 확인할 항목

    • 메모리 획득 시 가상 메모리 사용량만 증가한다. (물리 메모리 사용량은 그대로)

    • 획득한 메모리에 접근하면 페이지 폴트가 발생하고, 물리 메모리의 사용량이 증가한다.

  • 테스트 프로그램

    1. 이 프로그램이 실행되는 동안 sar -r 명령어를 통해 메모리 사용량을 분석한다. 이를 위해 출력 메시지에 현재 시간을 표시한다.

    2. 메모리 획득 전에 메시지를 출력한다. 그 후 사용자의 입력을 기다린다.

    3. 100MiB 메모리 획득

    4. 다시 한번 메시지를 출력하고 사용자의 입력을 기다린다.

    5. 획득한 메모리를 처음부터 끝까지 1페이지씩 접근하고, 10MiB씩 접근할 때마다 메시지 출력

    6. 사용자의 입력을 기다린다.

  • 테스트 결과 (터미널 3개로 확인)

    $ output/demand-paging 
    Thu Sep 16 22:31:49 2021: before allocation, press Enter key
    
    Thu Sep 16 22:31:51 2021: allocated 100MiB, press Enter key # 가상 메모리 할당
    
    Thu Sep 16 22:31:52 2021: touched 10MiB
    Thu Sep 16 22:31:53 2021: touched 20MiB
    Thu Sep 16 22:31:54 2021: touched 30MiB
    Thu Sep 16 22:31:55 2021: touched 40MiB
    Thu Sep 16 22:31:56 2021: touched 50MiB
    Thu Sep 16 22:31:57 2021: touched 60MiB
    Thu Sep 16 22:31:58 2021: touched 70MiB
    Thu Sep 16 22:31:59 2021: touched 80MiB
    Thu Sep 16 22:32:00 2021: touched 90MiB
    Thu Sep 16 22:32:01 2021: touched 100MiB, press Enter key
    $ sar -r 1
    Linux 5.13.13-surface (ubun2-Surface-Pro-7)     2021년 09월 16일     _x86_64_(8 CPU)
    
    22시 31분 48초 kbmemfree   kbavail kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty
    22시 31분 49초   2906364   4545340   1993144     25.79     83824   2546712   9809544     99.83    823128   2930380         8
    22시 31분 50초   2901536   4540512   1993376     25.79     83824   2551308   9814376     99.88    823136   2930532        12
    22시 31분 51초   2935556   4574532   1992912     25.78     83824   2517752   9883228    100.58    823136   2930640        24
    22시 31분 52초   2941856   4580832   1993412     25.79     83824   2510952   9876428    100.51    823136   2930748        84
    22시 31분 53초   2931532   4570508   2003740     25.92     83824   2510948   9876512    100.51    823136   2941008        84 # 여기부터 물리 메모리 사용량인 kbmemused 필드가 10MiB씩 증가한다. 
    22시 31분 54초   2921436   4560412   2013288     26.05     83832   2511488   9877092    100.52    823136   2951348       144
    22시 31분 55초   2911364   4550340   2024088     26.19     83832   2510760   9876244    100.51    823136   2961720       144
    22시 31분 56초   2901032   4540008   2034388     26.32     83832   2510792   9876280    100.51    823136   2972032       148
    22시 31분 57초   2890700   4529676   2044720     26.46     83832   2510792   9876256    100.51    823136   2982276       148
    22시 31분 58초   2889700   4528684   2045740     26.47     83832   2510776   9835556    100.10    823200   2983708       268
    22시 31분 59초   2879116   4518100   2056356     26.61     83832   2510744   9835524    100.10    823200   2993972       268
    22시 32분 00초   2869036   4508020   2066436     26.74     83832   2510744   9835540    100.10    823200   3004256         8
    22시 32분 01초   2858452   4497440   2076472     26.87     83840   2511284   9836044    100.10    823200   3014612        64
    22시 32분 02초   2848372   4487360   2086568     27.00     83840   2511268   9836028    100.10    823200   3024932        72 # 여기까지 100MiB 할당
    22시 32분 03초   2848372   4487364   2087056     27.00     83848   2510772   9835536    100.10    823208   3025080        96
    22시 32분 04초   2945400   4584400   1986660     25.70     83848   2514140   9736176     99.08    823212   2922648        96 # 프로세스가 종료되어 물리 메모리가 반환됨
    22시 32분 05초   2945140   4584140   1986936     25.71     83848   2514124   9736160     99.08    823212   2922648        96
    $  sar -B 1
    Linux 5.13.13-surface (ubun2-Surface-Pro-7)     2021년 09월 16일     _x86_64_(8 CPU)
    
    22시 31분 48초  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgscand/s pgsteal/s    %vmeff
    22시 31분 49초      0.00    392.00    554.00      0.00   1308.00      0.00      0.00      0.00      0.00
    22시 31분 50초      0.00      0.00    203.00      0.00    399.00      0.00      0.00      0.00      0.00
    22시 31분 51초      0.00      0.00     37.00      0.00   8816.00      0.00      0.00      0.00      0.00
    22시 31분 52초      0.00      0.00     51.00      0.00   1832.00      0.00      0.00      0.00      0.00
    22시 31분 53초      0.00      0.00   2600.00      0.00    359.00      0.00      0.00      0.00      0.00 # 여기부터 10초간 fault/s 필드값이 증가한 상태
    22시 31분 54초      0.00     56.00   2584.00      0.00    153.00      0.00      0.00      0.00      0.00
    22시 31분 55초      0.00      0.00   2588.00      0.00    344.00      0.00      0.00      0.00      0.00
    22시 31분 56초      0.00      0.00   2594.00      0.00    127.00      0.00      0.00      0.00      0.00
    22시 31분 57초      0.00      0.00   2590.00      0.00    194.00      0.00      0.00      0.00      0.00
    22시 31분 58초      0.00      0.00   2702.00      0.00   2432.00      0.00      0.00      0.00      0.00
    22시 31분 59초      0.00      0.00   2577.00      0.00    141.00      0.00      0.00      0.00      0.00
    22시 32분 00초      0.00    248.00   2584.00      0.00    126.00      0.00      0.00      0.00      0.00
    22시 32분 01초      0.00     44.00   2614.00      0.00    141.00      0.00      0.00      0.00      0.00
    22시 32분 02초      0.00      0.00   2582.00      0.00    141.00      0.00      0.00      0.00      0.00 # 여기까지
    22시 32분 03초      0.00    148.00     67.00      0.00    322.00      0.00      0.00      0.00      0.00
    22시 32분 04초      0.00      0.00   5309.00      0.00  29590.00      0.00      0.00      0.00      0.00
    22시 32분 05초      0.00      0.00     19.00      0.00    175.00      0.00      0.00      0.00      0.00
    22시 32분 06초      0.00      0.00   1283.00      0.00   3497.00      0.00      0.00      0.00      0.00

프로세스 별 메모리 통계 확인

  • 가상 메모리의 양, 확보된 물리 메모리의 양, 프로세스 생성 시부터 페이지 폴트의 횟수 확인

    • ps -eo 명령어의 vsz, rss, maj_flt, min_flt 필드로 확인할 수 있다.

    • 페이지 폴트의 횟수는 maj_flt(Major Fault) + min_flt(Minor Fault)이다.

  • 확인을 위한 셸 스크립트 실행 (터미널 2개)

    $ output/demand-paging
    Thu Sep 16 22:43:32 2021: before allocation, press Enter key
    
    Thu Sep 16 22:43:35 2021: allocated 100MiB, press Enter key
    
    Thu Sep 16 22:43:37 2021: touched 10MiB
    Thu Sep 16 22:43:38 2021: touched 20MiB
    Thu Sep 16 22:43:39 2021: touched 30MiB
    Thu Sep 16 22:43:40 2021: touched 40MiB
    Thu Sep 16 22:43:41 2021: touched 50MiB
    Thu Sep 16 22:43:42 2021: touched 60MiB
    Thu Sep 16 22:43:43 2021: touched 70MiB
    Thu Sep 16 22:43:44 2021: touched 80MiB
    Thu Sep 16 22:43:45 2021: touched 90MiB
    Thu Sep 16 22:43:46 2021: touched 100MiB, press Enter key
    $ script/vsz-rss.sh
    2021. 09. 16. (목) 22:43:34 KST:   10439 demand-paging     2496   648      0    113
    2021. 09. 16. (목) 22:43:35 KST:   10439 demand-paging     2496   648      0    113
    2021. 09. 16. (목) 22:43:36 KST:   10439 demand-paging   104900   648      0    114 # 가상 메모리(vsz) 100MiB 할당
    2021. 09. 16. (목) 22:43:37 KST:   10439 demand-paging   104900 11772      0   2675 # 여기부터 물리 메모리 10MiB씩 할당
    2021. 09. 16. (목) 22:43:38 KST:   10439 demand-paging   104900 21864      0   5235
    2021. 09. 16. (목) 22:43:39 KST:   10439 demand-paging   104900 32160      0   7795
    2021. 09. 16. (목) 22:43:40 KST:   10439 demand-paging   104900 42456      0  10355
    2021. 09. 16. (목) 22:43:41 KST:   10439 demand-paging   104900 52752      0  12915
    2021. 09. 16. (목) 22:43:42 KST:   10439 demand-paging   104900 63048      0  15475
    2021. 09. 16. (목) 22:43:43 KST:   10439 demand-paging   104900 73080      0  18035
    2021. 09. 16. (목) 22:43:44 KST:   10439 demand-paging   104900 83376      0  20595
    2021. 09. 16. (목) 22:43:45 KST:   10439 demand-paging   104900 93672      0  23155
    2021. 09. 16. (목) 22:43:46 KST:   10439 demand-paging   104900 103968     0  25714 # 물리 메모리 100MiB 매핑 완료
    2021. 09. 16. (목) 22:43:47 KST:   10439 demand-paging   104900 103968     0  25714
    2021. 09. 16. (목) 22:43:48 KST: target process seems to be finished

가상 메모리 부족과 물리 메모리 부족

프로세스가 가상 메모리를 전부 사용한 뒤에도 가상 메모리를 더 요청할 경우, 가상 메모리 부족이 발생한다. 이는 물리 메모리가 남아있어도 발생할 수 있다.

  • 32비트 프로세스에서의 가상 메모리 영역은 232Byte(=4GiB)만 사용할 수 있으며, 이 이상으로 메모리 요청을 하면 가상 메모리 부족이 발생한다.

물리 메모리 부족은 말 그대로 시스템의 물리 메모리가 전부 사용되어 더 이상 할당할 메모리가 없을 때 발생한다.

Copy on Write (CoW)

3장의 fork() 시스템 콜도 사실 가상 메모리 방식을 사용하여 고속화된다.

먼저 부모 프로세스의 페이지 테이블을 자식 프로세스에 복사한다. 메모리 영역 전체를 복사하는게 아니므로 속도는 훨씬 빠르고, 페이지 읽기의 경우 같은 영역(공유된 물리 페이지)을 읽게 되는 것이다.

복사할 때 부모 프로세스와 자식 프로세스의 PTE에 있는 쓰기 권한을 둘 다 무효화(불가능) 처리한다. 이후 페이지의 내용을 변경하려고 하면 다음과 같은 흐름이 발생한다.

  1. 페이지 쓰기 권한이 없으므로 CPU에 페이지 폴트가 발생한다.

  2. CPU가 커널 모드로 변경되고, 커널의 페이지 폴트 핸들러가 동작한다. 접근한 페이지를 다른 메모리 영역에 복사하고, 해당 프로세스에 할당한 뒤 내용을 변경한다.

  3. 변경하려한 프로세스의 PTE에서 가상 주소 영역을 새로 복사한 물리 메모리 영역으로 매핑하고, 부모 프로세스와 자식 프로세스 PTE의 쓰기 권한을 둘 다 허용으로 변경하여 이후 자유로운 쓰기가 가능하도록 한다. (더 이상 해당 물리 메모리를 공유하지 않으므로)

즉, fork() 시스템 콜을 호출했을 때가 아닌 쓰기 작업이 발생할 때 물리 메모리를 복사하기 때문에, 이 방식을 CoWCopy on Write라고 부른다.

  • fork() 시스템 콜을 호출할 시점과 상관없이 쓰기 작업이 발생한 시점에 물리 메모리에 여유 공간이 부족하다면 물리 메모리 부족이 발생한다.

CoW 실험

  • 확인할 사항

    • fork() 시스템 콜을 호출했을 떄부터 쓰기 작업이 발생할 때까지는 부모 프로세스와 자식 프로세스가 물리 메모리 영역을 공유한다.

    • 쓰기 작업이 발생할 때 페이지 폴트가 발생한다.

  • 테스트 프로그램

    1. 100MiB 메모리를 확보하여 모든 페이지에 접근한다.

    2. 시스템 메모리 사용량 출력

    3. fork() 시스템 콜 호출

    4. 자식 프로세스에서 시스템 메모리 사용량 및 자식 프로세스의 메모리 사용량을 출력한다.

    5. 1에서 확보한 메모리 영역에 자식 프로세스가 전부 쓰기 작업을 수행한다.

    6. 자식 프로세스에서 시스템 메모리 사용량 및 자식 프로세스의 메모리 사용량을 출력한다.

  • 실험 결과

    $ output/cow                                       
    free memory info before fork():
                  total        used        free      shared  buff/cache   available
    Mem:        7729028     2316096     2569044      862244     2843888     4274080
    스왑:       2097148           0     2097148
    child ps info before memory access:
      14264 cow             104900 103320     0     24
    free memory info before memory access:
                  total        used        free      shared  buff/cache   available
    Mem:        7729028     2317056     2568084      862244     2843888     4273120 # 위의 결과와 별 차이가 없다. (아직 메모리를 복사하지 않았기 때문)
    스왑:       2097148           0     2097148
    child ps info after memory access:
      14264 cow             104900 103320     0  25626 # 페이지 폴트가 많이 증가함
    free memory info after memory access:
                  total        used        free      shared  buff/cache   available
    Mem:        7729028     2420332     2464808      862244     2843888     4169844 # used 필드가 100MiB정도 늘었다.
  • ps 명령어로 부모 프로세스와 자식 프로세스의 메모리를 확인할 때, 공유된 부분은 두 프로세스에서 모두 표시된다는 것에 주의해야 한다. 메모리의 합을 계산할 때 그냥 더해버리면 공유된 부분이 중복으로 더해지는 것이다.

스왑swap

물리 메모리가 부족할 경우 메모리 부족OOM 상태가 되는 것을 방지하기 위해, 저장 장치(HDD, SSD 등)의 일부를 메모리 대신 사용하는 방식이다.

  • 물리 메모리가 부족한 경우 기존에 사용하던 물리 메모리의 일부분을 저장 장치에 저장하여 빈 영역을 만들어서 할당해준다. 이 때 저장 장치에 임시로 저장한 영역을 스왑 영역이라고 부른다. (윈도우에서는 가상 메모리라고 부르고, 이 때문에 예전에 가상 메모리에 대한 개념이 헷갈렸다ㅠ)

  • 커널이 사용 중인 물리 메모리의 일부를 스왑 영역에 임시 보관하는 것을 스왑 아웃swap out, 혹은 페이지 아웃page out이라고 한다.

  • 스왑 영역에 임시 보관했던 데이터를 물리 메모리에 되돌리는 것을 스왑 인swap in, 혹은 페이지 인page in이라고 한다.

    • 스왑 아웃, 스왑 인을 통틀어서 스와핑swapping, 혹은 페이징paging이라고 한다.

스래싱thrashing

  • 시스템의 메모리가 계속 부족한 상태로 유지되면, 메모리에 접근할 때마다 스왑 아웃, 스왑 인이 반복되는 현상이 발생하는데 이를 스래싱이라고 한다.

  • 저장 장치에 접근하는 속도는 메모리에 접근하는 것보다 매우 느리기 때문에, 스래싱이 발생하면 시스템이 원활하게 작동되지 않는다.

스왑 확인

  • 시스템의 스왑 영역 확인

    $ swapon --show
    NAME      TYPE SIZE USED PRIO
    /swapfile file   2G   0B   -2
    $ free
                  total        used        free      shared  buff/cache   available
    Mem:        7729028     1357144     4296532      594456     2075352     5504204
    스왑:       2097148           0     2097148
  • 스와핑이 발생 중인지 확인 (sar -W 옵션)

    $ sar -W 1
    Linux 5.13.13-surface (ubun2-Surface-Pro-7)     2021년 09월 17일     _x86_64_(8 CPU)
    
    16시 58분 13초  pswpin/s pswpout/s # 초당 스와핑된 페이지의 수
    16시 58분 14초      0.00      0.00
  • 스왑 영역의 사용량 추이 (sar -S 옵션)

    $ sar -S 1
    Linux 5.13.13-surface (ubun2-Surface-Pro-7)     2021년 09월 17일     _x86_64_(8 CPU)
    
    17시 01분 06초 kbswpfree kbswpused  %swpused  kbswpcad   %swpcad
    17시 01분 07초   2097148         0      0.00         0      0.00
    17시 01분 08초   2097148         0      0.00         0      0.00
    17시 01분 09초   2097148         0      0.00         0      0.00
    17시 01분 10초   2097148         0      0.00         0      0.00
    17시 01분 11초   2097148         0      0.00         0      0.00
    • kbswpused 필드가 스왑 영역의 사용량을 나타내는데, 이 값이 점점 증가하면 주의해야 한다.

    • 만약 이 명령어를 입력했을 때 Cannot open /var/log/sysstat/sa17: No such file or directory 등의 에러가 발생하면, /etc/default/sysstat 파일을 열어서 ENABLED="true"로 바꾸고 service sysstat restart으로 서비스를 재시작한다.

  • Page Fault의 종류

    • Major Fault : 스와핑과 같이 저장 장치에 대한 접근이 발생하는 페이지 폴트

    • Minor Fault : 저장 장치에 대한 접근이 발생하지 않는 페이지 폴트

계층형 페이지 테이블

페이지 테이블에는 가상 주소 공간 페이지의 전부에 대응되는 데이터가 저장된다.

1차원적으로 페이지 테이블을 구성할 경우

  • x86_64 아키텍처에서 보통 가상 주소 공간의 크기는 128TiB이다.

  • 1페이지는 4KiB, PTE는 8Byte이다.

  • 프로세스 1개당 페이지 테이블의 크기는 이론상 8Byte * 128TiB / 4KiB = 256GiB이다.

위와 같이 1차원적으로 페이지 테이블을 구성하면 크기가 엄청나게 커지므로, x86_64의 페이지 테이블은 계층형 구조로 되어있어 메모리를 절약한다.

  • 예를 들어 1차원적으로 0부터 15까지(페이지 단위라고 생각하자)의 가상 주소에 대한 PTE 중 앞의 4개만 사용하는 페이지 테이블이라면, 4개씩 묶어서 하위 페이지 테이블의 주소를 가리키도록 한다. 03, 47, 811, 1215 이렇게 묶으면 4개의 PTE가 필요하고, 하위 페이지 테이블로 0부터 3까지 4개의 PTE, 즉 전체 16개의 PTE를 8개로 줄일 수 있다.

  • 물론 사용하는 가상 메모리의 양이 많아지면 필요한 페이지 테이블도 많아지고, 어느 순간부터는 1차원적인 페이지 테이블보다 용량이 더 커질 수도 있다. 하지만 일반적인 상황에서 그런 상황은 발생하지 않는다.

  • 실제 x86_64 아키텍처에서는 페이지 테이블의 구조가 4단 구조이다.

페이지 테이블 사용량 확인 (sar -r 옵션)

  • 물리 메모리 중에 페이지 테이블의 사용량을 확인해보자.

    $ sar -r ALL 1
    Linux 5.13.13-surface (ubun2-Surface-Pro-7)     2021년 09월 17일     _x86_64_(8 CPU)
    
    19시 17분 07초 kbmemfree   kbavail kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact   kbdirty  kbanonpg    kbslab  kbkstack   kbpgtbl  kbvmused
    19시 17분 08초   2971536   4750228   1817952     23.52     86856   2635636   9296076     94.61    903012   2847004        24   1692840    217048     13104     32928     35964
    • knpgtbl 필드가 현재 페이지 테이블이 사용 중인 물리 메모리 크기이다.

Huge Page

프로세스의 가상 메모리 사용량이 늘어나면, 페이지 테이블이 사용하는 물리 메모리양도 증가한다.

  • 페이지 테이블의 크기가 큰 경우, fork() 시스템 콜 호출 시 속도도 느려진다. 페이지 테이블은 CoW와 관계없이 부모의 프로세스와 같은 크기로 생성하기 때문이다.

이를 해결하기 위해 Huge Page를 사용한다.

  • 가상 주소 뭉탱이를 물리 주소 뭉탱이에 매핑한다고 생각하면 된다. 페이지의 크기를 4KiB에서 2MiB로 크게 잡는 등의 방식을 사용한다.

  • 이를 통해 페이지 테이블에 필요한 메모리의 양이 줄어들며, MMU의 캐시 역할을 하는 TLBtranslation lookaside buffer의 hit을 증가시켜 성능을 향상시킨다.

사용 방법

Transparent Huge Page

  • 리눅스 환경에서, 가상 주소 공간에 연속된 4KiB의 페이지들이 특정 조건을 만족하면 자동으로 Huge Page로 묶는 기능이다.

  • 문제점 : Huge Page로 묶거나, 다시 4KiB 페이지로 분리할 때 국소적으로 성능이 하락하는 경우가 존재한다.

  • 활성화 여부

    $ cat /sys/kernel/mm/transparent_hugepage/enabled 
    always [madvise] never
    • 대괄호 쳐진게 적용된 항목이다.

    • always : 항상 사용

    • madvise : madvise() 시스템 콜을 사용하여 특정 메모리 영역에만 적용할 수 있다.

    • never : 사용 안함

  • 무효화

    $ sudo su
    # echo never > /sys/kernel/mm/transparent_hugepage/enabled 

참고


소스 코드

'Linux > Linux Structure' 카테고리의 다른 글

Chapter 7. 파일시스템  (0) 2021.09.26
Chapter 6. 메모리 계층  (0) 2021.09.26
Chapter 4. 프로세스 스케줄러  (0) 2021.09.26
Gnuplot  (0) 2021.09.26
Chapter 3. 프로세스 관리  (1) 2021.09.26