NAME

madvise - 메모리 사용에 대한 조언 주기

SYNOPSIS

#include <sys/mman.h>

int madvise(void *addr, size_t length, int advice);

glibc 기능 확인 매크로 요건 (feature_test_macros(7) 참고):

madvise():
glibc 2.19부터:
_DEFAULT_SOURCE
glibc 2.19까지:
_BSD_SOURCE

DESCRIPTION

madvise() 시스템 호출을 사용해 주소 addr에서 시작하고 크기가 length 바이트인 주소 범위에 대해 커널에게 조언 내지 지시를 한다. 대부분의 경우 그 조언의 목적은 시스템 내지 응용의 성능을 개선하는 것이다.

처음에 이 시스템 호출은 다른 여러 구현에서도 사용 가능한 "전통적인" advice 값들을 지원했다. (참고로 madvise()는 POSIX에 명세되어 있지 않다.) 이후에 리눅스 전용 advice 값들이 여러 가지 추가되었다.

전통적인 조언 값

응용에서 아래 나열된 advice 값들을 이용하여 어떤 맵 된 메모리 영역이나 공유 메모리 영역을 어떻게 사용할 예정인지 커널에게 알려 줄 수 있다. 그러면 커널에서 적절한 미리 읽기 및 캐시 기법을 선택할 수 있다. 이 advice 값들은 (MADV_DONTNEED를 제외하고) 응용의 동작 결과에 영향을 주지 않으며 성능에만 영향을 끼칠 수 있다. 여기 나열된 advice 값들은 모두 POSIX 명세 함수인 posix_madvise(3)에 유사한 항목이 있으며 MADV_DONTNEED를 제외하고 값의 의미가 같다.

조언을 나타내는 advice 인자는 다음 중 하나이다.

MADV_NORMAL
특별한 처리 없음. 기본이다.
MADV_RANDOM
임의 순서로 페이지 참조를 할 예정. (따라서 미리 읽기가 평상시보다 쓸모가 없을 수 있다.)
MADV_SEQUENTIAL
순차 순서로 페이지 참조를 할 예정. (따라서 지정한 범위의 페이지들을 적극적으로 미리 읽기 할 수 있으며 접근 후에 바로 해제할 수도 있다.)
MADV_WILLNEED
조만간 접근할 예정. (따라서 페이지들을 미리 읽어 두는 게 좋을 수도 있다.)
MADV_DONTNEED

당분간은 접근할 예정 없음. (응용에서 한동안 그 범위에 볼일이 없으므로 커널에서 거기 연계된 자원을 해제할 수 있다.)

MADV_DONTNEED 동작 성공 후에는 지정한 영역에서의 메모리 접근 동작 결과가 바뀐다. 그 범위 내 페이지에 대한 이후의 접근이 성공은 하지만 (공유 파일 매핑, 공유 익명 매핑, 시스템 V 공유 메모리 세그먼트 같은 shmem 기반 기법인 경우) 기반 맵 파일의 최신 내용으로 메모리 내용을 다시 채우게 되거나, 익명 비공유 매핑인 경우 zero-fill-on-demand 페이지가 된다.

참고로 공유 매핑에 MADV_DONTNEED 적용 시 범위 내 페이지들이 즉시 해제되지 않을 수도 있다. 커널에서 페이지 해제를 자유로이 적절한 시점으로 연기할 수 있다. 하지만 호출 프로세스의 상주 집합 크기(RSS)는 즉시 줄어든다.

고정된 페이지, 거대 TLB 페이지, VM_PFNMAP 페이지에 MADV_DONTNEED를 적용할 수 없다. (커널 내부용인 VM_PFNMAP 플래그로 표시된 페이지는 가상 메모리 서브시스템이 관리하지 않는 특수한 메모리 영역이다. 보통 장치 드라이버에서 그런 페이지를 만들어서 사용자 공간으로 맵 한다.)

리눅스 전용 조언 값

다음의 리눅스 전용 advice 값들은 POSIX에 명세된 posix_madvise(3)에 대응 항목이 없으며, 다른 구현에서 사용 가능한 madvise() 인터페이스에는 대응 항목이 있을 수도 있고 없을 수도 있다. 참고로 이 동작들 중 일부는 메모리 접근의 동작 결과를 바꾼다.

MADV_REMOVE (리눅스 2.6.16부터)

지정한 페이지 범위 및 연계된 기반 저장 공간을 해제한다. 기반 저장 공간의 대응하는 바이트 범위에 구멍을 뚫는 것과 동등하다. (fallocate(2) 참고.) 지정한 주소 범위를 이후에 접근하면 0이 담긴 바이트들을 보게 된다.

지정한 주소 범위가 공유 맵이고 쓰기 가능해야 한다. 고정된 페이지, 거대 TLB 페이지, VM_PFNMAP 페이지에 이 플래그를 적용할 수 없다.

초기 구현에서는 tmpfs(5)MADV_REMOVE를 지원했다. 리눅스 3.5부터는 fallocate(2) FALLOC_FL_PUNCH_HOLE 모드를 지원하는 파일 시스템이면 MADV_REMOVE도 지원한다. hugetlbfs는 EINVAL로 실패하며 다른 파일 시스템들은 EOPNOTSUPP 오류로 실패한다.

MADV_DONTFORK (리눅스 2.6.16부터)
이 범위의 페이지들을 fork(2) 후에 자식에서 사용 가능하게 만들지 않는다. fork(2) 후에 부모가 페이지에 쓰기를 하는 경우 copy-on-write 동작 방식 때문에 페이지의 물리적 위치가 바뀌는 것을 막는 데 유용하다. (하드웨어에서 그 페이지로 DMA 하는 경우에 그런 페이지 재배치가 문제를 일으킨다.)
MADV_DOFORK (리눅스 2.6.16부터)
MADV_DONTFORK의 효과를 되돌린다. fork(2)를 거치며 매핑을 물려받는 기본 동작 방식을 복원한다.
MADV_HWPOISON (리눅스 2.6.32부터)

addrlength로 지정한 범위의 페이지들에 오염 표시를 해서 그 페이지들에 대한 이후 참조를 하드웨어 메모리 오염처럼 처리한다. 이 동작은 특권(CAP_SYS_ADMIN) 프로세스에만 사용 가능하다. 이 동작으로 인해 호출 프로세스가 SIGBUS를 받고 페이지 맵이 제거될 수도 있다.

이 기능은 메모리 오류 처리 코드 테스트를 위한 것이다. 커널을 CONFIG_MEMORY_FAILURE로 구성했을 때만 사용 가능하다.

MADV_MERGEABLE (리눅스 2.6.32부터)

addrlength로 지정한 범위의 페이지들에 커널 동일 페이지 병합(KSM: Kernel Samepage Merging) 기능을 켠다. 병합 가능하다고 표시된 사용자 메모리 영역들을 커널이 정기적으로 조사해서 내용이 동일한 페이지들을 찾는다. 그리고 그 페이지들을 쓰기 방지 페이지 한 개로 교체한다. (이후 프로세스에서 페이지 내용을 갱신하려 하면 페이지가 자동으로 복사된다.) KSM은 비공유 익명 페이지(mmap(2) 참고)만 병합한다.

KSM 기능은 같은 데이터의 인스턴스를 여러 개 생성하는 응용들(가령 KVM 같은 가상화 시스템)을 위한 것이다. 프로세싱 능력을 많이 소모할 수 있으므로 조심해서 써야 한다. 더 자세한 내용은 커널 소스 파일 Documentation/admin-guide/mm/ksm.rst를 보라.

MADV_MERGEABLEMADV_UNMERGEABLE 동작은 커널을 CONFIG_KSM으로 구성했을 때만 사용 가능하다.

MADV_UNMERGEABLE (리눅스 2.6.32부터)
지정한 주소 범위에서 앞선 MADV_MERGEABLE 동작의 효과를 되돌린다. addrlength로 지정한 주소 범위에서 KSM으로 병합했던 페이지가 있으면 다시 분리한다.
MADV_SOFT_OFFLINE (리눅스 2.6.33부터)

addrlength로 지정한 범위의 페이지들을 연성 오프라인 처리한다. 지정한 범위의 각 페이지의 메모리가 그대로 유지된다. (즉, 다음 접근 때 같은 내용이 보인다. 하지만 새 물리적 페이지 프레임에 있는 것이다.) 그리고 원래 페이지는 오프라인이 된다. (즉, 더는 안 쓰이고 통상적인 메모리 관리에서 빠진다.) MADV_SOFT_OFFLINE 동작의 효과는 호출 프로세스에게 보이지 않는다. (즉, 호출 프로세스의 동작이 바뀌지 않는다.)

이 기능은 메모리 오류 처리 코드 테스트를 위한 것이다. 커널을 CONFIG_MEMORY_FAILURE로 구성했을 때만 사용 가능하다.

MADV_HUGEPAGE (리눅스 2.6.38부터)

addrlength로 지정한 범위의 페이지들에 자동 거대 페이지(THP: Transparent Huge Pages) 기능을 켠다. 현재 자동 거대 페이지 기능은 비공유 익명 페이지(mmap(2) 참고)로만 동작한다. 거대 페이지 후보로 표시된 영역들을 커널이 정기적으로 조사해서 거대 페이지로 교체한다. 또한 커널에서 할당하는 영역이 거대 페이지 크기에 자연스럽게 정렬되어 있으면 (posix_memalign(3) 참고) 바로 거대 페이지를 할당하게 된다.

이 기능이 주로 노리는 것은 큰 데이터 매핑을 사용하고 그 메모리의 큰 영역들에 일시에 접근하는 응용들(가령 QEMU 같은 가상화 시스템)이다. 이 기능은 메모리를 낭비하기가 아주 쉽다. (가령 2MB짜리 매핑에서 한 바이트만 접근하는 경우 4KB짜리 페이지 하나가 아니라 메모리 2MB가 연결된다.) 더 자세한 내용은 리눅스 커널 소스 파일 Documentation/admin-guide/mm/transhuge.rst를 보라.

흔히 만날 수 있는 커널 구성들에선 대부분 기본적으로 MADV_HUGEPAGE 같은 동작을 제공하며, 그래서 보통은 MADV_HUGEPAGE가 필요하지 않다. 주로 필요한 경우는 커널에서 MADV_HUGEPAGE 같은 동작이 기본적으로 켜져 있지 않을 수도 있는 임베디드 시스템에서다. 그런 시스템에서 이 플래그를 써서 선택적으로 THP를 켤 수 있다. MADV_HUGEPAGE를 사용한다면 반드시 자동 거대 페이지를 켰을 때의 접근 패턴이 응용의 메모리 사용 규모를 늘이지 않는다는 것을 개발자가 미리 알고 있는 메모리 영역에서여야 한다.

MADV_HUGEPAGEMADV_NOHUGEPAGE 동작은 커널을 CONFIG_TRANSPARENT_HUGEPAGE로 구성했을 때만 사용 가능하다.

MADV_NOHUGEPAGE (리눅스 2.6.38부터)
addrlength로 지정한 주소 범위의 메모리에 자동 거대 페이지를 쓰지 않게 한다.
MADV_DONTDUMP (리눅스 3.4부터)
addrlength로 지정한 범위의 페이지들을 코어 덤프에서 제외한다. 코어 덤프에서 쓸모가 없는 커다란 메모리 영역을 가진 응용에 유용하다. MADV_DONTDUMP의 효과가 /proc/[pid]/coredump_filter 파일(core(5) 참고)로 설정한 비트 마스크보다 우선한다.
MADV_DODUMP (리눅스 3.4부터)
앞선 MADV_DONTDUMP의 효과를 되돌린다.
MADV_FREE (리눅스 4.5부터)

addrlength로 지정한 범위의 페이지들을 응용에서 더는 필요로 하지 않는다. 따라서 커널에서 그 페이지들을 해제할 수 있는데, 메모리 압박이 있을 때까지 해제가 미뤄질 수도 있다. 그리고 해제하게 표시가 되었지만 아직 해제되지 않은 페이지에 호출자가 쓰기를 하면 해제 동작이 취소된다. MADV_FREE 동작 성공 후에 커널에서 페이지를 해제할 때 최신이 아닌 데이터(즉 더러워졌고 기록 안 된 페이지)가 있으면 그냥 사라지게 된다. 하지만 뒤이어 그 범위의 페이지에서 쓰기에 성공하고 나면 커널이 그 더러워진 페이지를 해제할 수 없고, 그래서 호출자는 언제나 방금 기록한 데이터를 볼 수 있다. 이어지는 쓰기가 없으면 커널에서 그 페이지를 어느 때든 해제할 수 있다. 그 범위의 페이지가 해제되고 나면 호출자는 이어지는 페이지 참조에서 zero-fill-on-demand 페이지를 보게 된다.

MADV_FREE 동작은 비공유 익명 페이지(mmap(2) 참고)에만 적용할 수 있다. 리눅스 4.12 전에선 스왑이 없는 시스템에서 페이지를 해제할 때는 메모리 압박과 상관없이 지정한 범위의 페이지를 즉시 해제한다.

MADV_WIPEONFORK (리눅스 4.14부터)

fork(2) 후 자식 프로세스에게 이 범위에 0으로 채운 메모리를 준다. 서버에서 분기하면서 민감한 프로세스별 데이터(가령 PRNG 시드, 암호학적 비밀값 등)가 자식 프로세스에게 전달되지 않도록 하는 데 유용하다.

MADV_WIPEONFORK 동작은 비공유 익명 페이지(mmap(2) 참고)에만 적용할 수 있다.

fork(2)로 생성한 자식에서 지정한 주소 범위에 MADV_WIPEONFORK 설정이 그대로 유지된다. execve(2) 과정에서는 이 설정이 사라진다.

MADV_KEEPONFORK (리눅스 4.14부터)
앞선 MADV_WIPEONFORK의 효과를 되돌린다.
MADV_COLD (리눅스 5.4부터)
지정한 페이지 범위를 비활성화한다. 이렇게 하면 메모리 압박이 있을 때 그 페이지들이 회수 대상이 될 가능성이 더 높아지게 된다. 이는 비파괴적인 동작이다. 적용 가능하지 않은 경우 범위 내의 일부 페이지에 대해 조언이 무시될 수도 있다.
MADV_PAGEOUT (리눅스 5.4부터)
지정한 페이지 범위를 회수한다. 그렇게 해서 그 페이지들이 차지하는 메모리를 해제한다. 익명 페이지이면 스왑으로 내보낸다. 파일 기반 페이지이고 더러워져 있으면 기반 저장소로 기록한다. 적용 가능하지 않은 경우 범위 내의 일부 페이지에 대해 조언이 무시될 수도 있다.

RETURN VALUE

성공 시 madvise()는 0을 반환한다. 오류 시 -1을 반환하며 오류를 나타내도록 errno를 설정한다.

ERRORS

EACCES
adviceMADV_REMOVE인데 지정한 주소 범위가 쓰기 가능한 공유 매핑이 아니다.
EAGAIN
커널 자원이 일시적으로 부족하다.
EBADF
맵이 존재하지만 그 영역에 파일 아닌 뭔가가 맵 되어 있다.
EINVAL
addr이 페이지에 정렬되어 있지 않거나 length가 음수이다.
EINVAL
advice가 유효하지 않다.
EINVAL
adviceMADV_DONTNEEDMADV_REMOVE인데 지정한 주소 범위에 고정된 페이지나 거대 TLB 페이지, VM_PFNMAP 페이지가 포함돼 있다.
EINVAL
adviceMADV_MERGEABLE이나 MADV_UNMERGEABLE인데 커널을 CONFIG_KSM으로 구성하지 않았다.
EINVAL
adviceMADV_FREEMADV_WIPEONFORK인데 지정한 주소 범위에 파일이나 거대 TLB, MAP_SHARED, VM_PFNMAP 범위가 포함돼 있다.
EIO
(MADV_WILLNEED에서) 이 영역의 페이지들을 들이면 프로세스 최대 상주 집합 크기를 초과하게 된다.
ENOMEM
(MADV_WILLNEED에서) 메모리가 부족해서 페이지를 들이는 데 실패했다.
ENOMEM
지정한 범위의 주소가 현재 맵 되어 있지 않거나 프로세스의 주소 공간 밖에 있다.
EPERM
adviceMADV_HWPOISON인데 호출자가 CAP_SYS_ADMIN 역능을 가지고 있지 않다.

VERSIONS

리눅스 3.18부터 기반 시스템 호출 지원이 선택적이다. CONFIG_ADVISE_SYSCALLS 구성 옵션 설정에 따라 정해진다.

CONFORMING TO

madvise()는 어느 표준에도 명세되어 있지 않다. 다양한 advice 값들을 구현한 이 시스템 호출의 여러 버전이 다른 여러 구현들에 존재한다. 보통 다른 구현들에서는 적어도 전통적인 조언 값에 나열된 플래그들을 구현하고 있지만 의미에 약간의 차이가 있다.

POSIX.1-2001에 기술하는 posix_madvise(3)에는 상수 POSIX_MADV_NORMAL, POSIX_MADV_RANDOM, POSIX_MADV_SEQUENTIAL, POSIX_MADV_WILLNEED, POSIX_MADV_DONTNEED 등이 있는데 위에 나열된 비슷한 이름의 플래그들과 동작 방식이 비슷하다.

NOTES

리눅스 참고 사항

리눅스 구현에서는 주소 addr이 페이지에 정렬되어 있기를 요구하며 length가 0인 것을 허용한다. 지정한 주소 범위의 일부가 맵 되어 있지 않은 경우 리눅스 버전 madvise()는 그 부분을 무시하고 나머지에 호출 내용을 적용한다. (하지만 시스템 호출은 원래 그래야 하는 대로 ENOMEM을 반환한다.)

SEE ALSO

getrlimit(2), mincore(2), mmap(2), mprotect(2), msync(2), munmap(2), prctl(2), process_madvise(2), posix_madvise(3), core(5)


2021-03-22