운영체제 기본/Linux/메모리 매핑/mmap과 주소 공간: 두 판 사이의 차이
Linux v6.12 mmap 파일 매핑: 사용자 예제, VMA 설정과 ext4 vm_ops 등록 |
잔글 Linux 각론 경로 변경에 따른 본문·목차·틀 링크 갱신 |
||
| (같은 사용자의 중간 판 10개는 보이지 않습니다) | |||
| 1번째 줄: | 1번째 줄: | ||
[[운영체제 기본/ | {{이전 다음 문서 | ||
|이전=처음 문서 | |||
|목차=[[운영체제 기본/Linux/메모리 매핑|메모리 매핑 목차]] | |||
|다음=다음 문서 →<br>[[운영체제 기본/Linux/메모리 매핑/페이지 폴트 처리|페이지 폴트 처리]] | |||
}} | |||
<code>mmap()</code>은 호출한 프로세스에 가상 주소 구간을 설정한다. 파일을 읽기 위한 별도 명령이라기보다, <strong>이 주소를 읽거나 쓸 때 어떤 대상을 어떻게 접근할 것인지</strong>를 정하는 인터페이스다. Linux v6.12의 MMU를 사용하는 네이티브 x86-64와 RISC-V RV64를 기준으로 한다. | |||
== | == mmap 인자와 매핑 대상 == | ||
<pre> | |||
void *p = mmap(addr, length, prot, flags, fd, offset); | |||
</pre> | |||
<code>addr</code>가 NULL이면 커널이 주소를 선택한다. <code>length</code>는 매핑할 길이이고, <code>prot</code>은 읽기·쓰기·실행 등 허용할 접근을 지정한다. <code>flags</code>는 공유 여부와 익명 매핑 여부 등을 정한다. 반환값은 물리 주소가 아니라 이 프로세스가 접근할 <strong>가상 주소</strong>이며, 실패하면 <code>MAP_FAILED</code>다. | |||
{| class="wikitable" | |||
! 대상 !! 대표 인자 !! 주소가 가리킬 대상 | |||
|- | |||
| 익명 메모리 || MAP_PRIVATE | MAP_ANONYMOUS, fd = -1, offset = 0 || 처음에는 0으로 보이며, 쓰기에 필요한 RAM을 마련하는 영역 | |||
|- | |||
| 일반 파일 || MAP_PRIVATE 또는 MAP_SHARED, 열린 파일의 fd || 파일 오프셋에 대응하는 데이터 | |||
|- | |||
| MMIO 장치 || 드라이버가 허용한 flags와 장치 fd || 드라이버가 공개한 장치의 물리 주소 영역 | |||
|} | |||
<code>MAP_PRIVATE</code>와 <code>MAP_SHARED</code>는 읽기·쓰기 권한과 별개다. <code>PROT_WRITE</code>는 쓰기를 허용할지를 정하고, private/shared는 허용된 쓰기의 결과를 어떻게 취급할지 정한다. 일반 파일의 private 쓰기는 그 프로세스의 사본에 적용되며, shared 쓰기는 공유하는 파일 데이터에 적용된다. 익명·장치 매핑의 구체적인 동작은 뒤 장에서 구분한다. | |||
== VMA와 페이지 테이블 == | |||
<strong>VMA(Virtual Memory Area)</strong>는 연속된 가상 주소 범위의 정책을 담는 커널 자료구조다. 시작·끝 주소, 접근 권한, 공유 여부, 연결된 파일과 처리 함수 등을 기록한다. 반면 <strong>페이지 테이블 엔트리(PTE)</strong>는 CPU가 개별 가상 페이지를 실제 대상으로 변환하는 데 쓰는 항목이다. | |||
<pre> | <pre> | ||
프로세스의 가상 주소 구간 | |||
VMA: 이 구간의 의미, 권한, 대상, 처리 방법 | |||
| | |||
+-- PTE 없음: 접근하면 페이지 폴트로 준비 | |||
+-- PTE 있음: 지정된 RAM 또는 장치 주소로 변환 | |||
</pre> | </pre> | ||
<code>mmap()</code> | 익명 메모리나 일반 파일의 보통 매핑에서는 <code>mmap()</code>이 VMA를 설정하고, 실제 페이지 준비와 PTE 설치는 접근할 때로 미룬다. 따라서 <code>mmap()</code> 성공이 모든 RAM 할당이나 파일 읽기의 완료를 뜻하지 않는다. 장치 드라이버는 매핑을 만드는 중에 PTE를 직접 설치할 수도 있다. 미리 페이지를 채우는 옵션이나 큰 페이지의 세부 경로 대신, 이후 장에서는 기본 페이지 단위의 일반적인 경로를 따라간다. | ||
<code> | == mmap 시스템 콜과 주소 공간 == | ||
<code>mmap()</code>의 인자와 사용자 인터페이스는 두 아키텍처에서 같다. 그러나 사용자 공간에서 커널로 넘어가는 명령, 인자 레지스터, 커널 진입 코드는 다르다. 먼저 갈라지는 부분을 보고, 공통 메모리 관리 코드에서 다시 합친다. 여기서 설명하는 시스템 콜 ABI는 일반 C 함수 호출 규약과 구별한다. | |||
== | === x86-64: syscall과 x86 mmap 진입점 === | ||
네이티브 x86-64 ABI는 <code>syscall</code> 명령을 사용한다. 시스템 콜 번호는 <code>rax</code>에, 여섯 인자 <code>addr, len, prot, flags, fd, offset</code>은 순서대로 <code>rdi, rsi, rdx, r10, r8, r9</code>에 놓인다. <code>mmap</code> 번호는 9다. 이 진입은 [[운영체제 기본/Linux/메모리 매핑/페이지 폴트 처리|페이지 폴트]]처럼 IDT를 통하는 경로가 아니다.<ref name="mmap-x86-entry">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/entry_64.S?h=v6.12 Linux v6.12, arch/x86/entry/entry_64.S: entry_SYSCALL_64와 ABI 주석].</ref><ref name="mmap-x86-table">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/entry/syscalls/syscall_64.tbl?h=v6.12 Linux v6.12, arch/x86/entry/syscalls/syscall_64.tbl: mmap].</ref> | |||
<code> | 시스템 콜 디스패치를 거쳐 <code>arch/x86/kernel/sys_x86_64.c</code>의 <code>SYSCALL_DEFINE6(mmap, ...)</code> 본문에 도달한다. 이 코드는 바이트 단위 오프셋의 페이지 정렬을 검사하고, <code>off >> PAGE_SHIFT</code>로 페이지 단위 오프셋을 만들어 <code>ksys_mmap_pgoff()</code>를 호출한다.<ref name="mmap-x86">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/sys_x86_64.c?h=v6.12 Linux v6.12, arch/x86/kernel/sys_x86_64.c: SYSCALL_DEFINE6(mmap)].</ref> | ||
=== RISC-V RV64: ecall과 RISC-V mmap 진입점 === | |||
RV64의 네이티브 ABI는 <code>ecall</code> 명령을 사용한다. 시스템 콜 번호는 <code>a7</code>에, 같은 여섯 인자는 <code>a0</code>부터 <code>a5</code>에 놓인다. <code>ecall</code>은 사용자 모드의 환경 호출 예외를 일으킨다. CPU는 <code>stvec</code>에 등록된 트랩 진입점으로 이동하며, Linux의 <code>handle_exception</code>과 예외 분기 표를 거쳐 <code>do_trap_ecall_u()</code>로 전달된다. 이 함수는 <code>regs->a7</code>의 번호로 시스템 콜을 분기한다. <code>ecall</code> 다음 명령으로 돌아가도록 저장된 <code>epc</code>를 4 증가시키는 것도 여기서 한다.<ref name="mmap-rv-entry">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/riscv/kernel/entry.S?h=v6.12 Linux v6.12, arch/riscv/kernel/entry.S: handle_exception, excp_vect_table].</ref><ref name="mmap-rv-traps">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/riscv/kernel/traps.c?h=v6.12 Linux v6.12, arch/riscv/kernel/traps.c: do_trap_ecall_u].</ref><ref name="mmap-rv-abi">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/riscv/include/asm/syscall.h?h=v6.12 Linux v6.12, arch/riscv/include/asm/syscall.h: syscall_get_arguments].</ref> | |||
== | RV64의 <code>SYSCALL_DEFINE6(mmap, ...)</code>은 <code>riscv_sys_mmap(..., offset, 0)</code>을 호출한다. 이 도우미도 바이트 단위 오프셋의 정렬을 검사하고 페이지 단위로 바꾸어 <code>ksys_mmap_pgoff()</code>에 전달한다. 오프셋은 이 경계에서 한 번 페이지 단위로 바뀐다.<ref name="mmap-rv">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/riscv/kernel/sys_riscv.c?h=v6.12 Linux v6.12, arch/riscv/kernel/sys_riscv.c: mmap, riscv_sys_mmap, mmap2].</ref> | ||
=== 공통 경로와 대상별 연결 === | |||
<pre> | <pre> | ||
mmap | mmap | ||
→ mmap_region → __mmap_region | +-- x86-64: syscall → entry_SYSCALL_64 → x86 mmap | ||
+-- RV64: ecall → handle_exception → do_trap_ecall_u | |||
→ RV64 mmap → riscv_sys_mmap | |||
| | |||
v | |||
ksys_mmap_pgoff → vm_mmap_pgoff → do_mmap | |||
→ mmap_region → __mmap_region | |||
+-- private anonymous: 익명 VMA 설정 | |||
+-- 일반 파일: 파일시스템의 mmap 콜백 | |||
+-- 장치 파일: 장치 드라이버의 mmap 콜백 | |||
</pre> | </pre> | ||
<code> | <code>ksys_mmap_pgoff()</code>는 <code>MAP_ANONYMOUS</code>가 아니면 <code>fget()</code>으로 fd를 열린 <code>struct file</code> 객체에 연결한다. 익명 매핑은 사용자 파일을 찾지 않는다. <code>vm_mmap_pgoff()</code>가 주소 공간의 쓰기 잠금을 잡고, <code>do_mmap()</code>은 길이와 오프셋, 접근 조건을 검사하며 사용할 주소를 구한다.<ref name="mmap">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/mmap.c?h=v6.12 Linux v6.12, mm/mmap.c], ksys_mmap_pgoff, do_mmap, __mmap_region.</ref><ref name="util">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/util.c?h=v6.12 Linux v6.12, mm/util.c], vm_mmap_pgoff.</ref> | ||
<code>__mmap_region()</code>은 VMA를 구성하고 대상별 처리를 연결한다. private anonymous 매핑에는 <code>vma_set_anonymous()</code>를 사용한다. 파일이 있으면 <code>mmap_file()</code>과 <code>call_mmap()</code>을 통해 <code>file->f_op->mmap</code>을 호출한다. 같은 파일 연산 표라도 일반 파일에서는 파일시스템의 함수, 장치 파일에서는 그 장치 드라이버의 함수로 이어진다. 커널이 VMA를 관리하는 틀은 공통이지만, 데이터의 공급 방식까지 같은 것은 아니다.<ref name="mmap"/><ref name="internal">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/internal.h?h=v6.12 Linux v6.12, mm/internal.h], mmap_file.</ref><ref name="fs">[https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/fs.h?h=v6.12 Linux v6.12, include/linux/fs.h], call_mmap.</ref> | |||
파일 매핑의 <code>vm_pgoff</code>는 VMA 시작점에 대응하는 페이지 단위 파일 오프셋이다. 일반 파일에서는 구간 안 주소의 파일 위치가 다음처럼 정해진다. | |||
= | <pre> | ||
파일 위치 = (vm_pgoff × PAGE_SIZE) + (접근 주소 - vm_start) | |||
</pre> | |||
장치 파일의 오프셋 의미는 드라이버의 인터페이스에 달려 있다. 예를 들어 MMIO 장의 UIO는 오프셋을 장치의 매핑 영역 번호를 고르는 데 사용한다. 또한 인접한 호환 VMA와 합칠 수 있으므로 <code>mmap()</code> 호출 하나가 항상 VMA 하나로 남는 것은 아니다.<ref name="mmap"/> | |||
== 매핑의 수명 == | |||
파일 매핑은 파일 객체에 대한 참조를 보유한다. 매핑 성공 뒤 fd를 <code>close()</code>하더라도 매핑은 남는다. <code>munmap()</code>은 주소 구간의 매핑을 해제하는 별도 작업이다. 해제한 포인터를 다시 사용하면 안 되며, 같은 주소가 나중에 재사용될 수 있으므로 잘못된 접근이 항상 신호로 드러나지도 않는다.<ref name="mmap"/> | |||
주소 구간을 확보했다고 해서 이후 모든 접근의 성공이 보장되지는 않는다. 익명 페이지를 준비할 메모리가 부족할 수 있고, 매핑한 파일이 줄어들 수도 있으며, 장치에는 장치별 수명 조건이 있다. 다음 장에서는 유효한 접근을 CPU 예외와 커널이 어떻게 구별하고 완성하는지 살펴본다. | |||
== | == 참고 문헌 == | ||
<references /> | <references /> | ||
{{이전 다음 문서 | |||
|이전=처음 문서 | |||
|목차=[[운영체제 기본/Linux/메모리 매핑|메모리 매핑 목차]] | |||
|다음=다음 문서 →<br>[[운영체제 기본/Linux/메모리 매핑/페이지 폴트 처리|페이지 폴트 처리]] | |||
}} | |||
{{운영체제 기본 둘러보기}} | |||
2026년 11월 1일 (일) 02:22 기준 최신판
mmap()은 호출한 프로세스에 가상 주소 구간을 설정한다. 파일을 읽기 위한 별도 명령이라기보다, 이 주소를 읽거나 쓸 때 어떤 대상을 어떻게 접근할 것인지를 정하는 인터페이스다. Linux v6.12의 MMU를 사용하는 네이티브 x86-64와 RISC-V RV64를 기준으로 한다.
mmap 인자와 매핑 대상
void *p = mmap(addr, length, prot, flags, fd, offset);
addr가 NULL이면 커널이 주소를 선택한다. length는 매핑할 길이이고, prot은 읽기·쓰기·실행 등 허용할 접근을 지정한다. flags는 공유 여부와 익명 매핑 여부 등을 정한다. 반환값은 물리 주소가 아니라 이 프로세스가 접근할 가상 주소이며, 실패하면 MAP_FAILED다.
| 대상 | 대표 인자 | 주소가 가리킬 대상 |
|---|---|---|
| 익명 메모리 | MAP_PRIVATE | MAP_ANONYMOUS, fd = -1, offset = 0 | 처음에는 0으로 보이며, 쓰기에 필요한 RAM을 마련하는 영역 |
| 일반 파일 | MAP_PRIVATE 또는 MAP_SHARED, 열린 파일의 fd | 파일 오프셋에 대응하는 데이터 |
| MMIO 장치 | 드라이버가 허용한 flags와 장치 fd | 드라이버가 공개한 장치의 물리 주소 영역 |
MAP_PRIVATE와 MAP_SHARED는 읽기·쓰기 권한과 별개다. PROT_WRITE는 쓰기를 허용할지를 정하고, private/shared는 허용된 쓰기의 결과를 어떻게 취급할지 정한다. 일반 파일의 private 쓰기는 그 프로세스의 사본에 적용되며, shared 쓰기는 공유하는 파일 데이터에 적용된다. 익명·장치 매핑의 구체적인 동작은 뒤 장에서 구분한다.
VMA와 페이지 테이블
VMA(Virtual Memory Area)는 연속된 가상 주소 범위의 정책을 담는 커널 자료구조다. 시작·끝 주소, 접근 권한, 공유 여부, 연결된 파일과 처리 함수 등을 기록한다. 반면 페이지 테이블 엔트리(PTE)는 CPU가 개별 가상 페이지를 실제 대상으로 변환하는 데 쓰는 항목이다.
프로세스의 가상 주소 구간
VMA: 이 구간의 의미, 권한, 대상, 처리 방법
|
+-- PTE 없음: 접근하면 페이지 폴트로 준비
+-- PTE 있음: 지정된 RAM 또는 장치 주소로 변환
익명 메모리나 일반 파일의 보통 매핑에서는 mmap()이 VMA를 설정하고, 실제 페이지 준비와 PTE 설치는 접근할 때로 미룬다. 따라서 mmap() 성공이 모든 RAM 할당이나 파일 읽기의 완료를 뜻하지 않는다. 장치 드라이버는 매핑을 만드는 중에 PTE를 직접 설치할 수도 있다. 미리 페이지를 채우는 옵션이나 큰 페이지의 세부 경로 대신, 이후 장에서는 기본 페이지 단위의 일반적인 경로를 따라간다.
mmap 시스템 콜과 주소 공간
mmap()의 인자와 사용자 인터페이스는 두 아키텍처에서 같다. 그러나 사용자 공간에서 커널로 넘어가는 명령, 인자 레지스터, 커널 진입 코드는 다르다. 먼저 갈라지는 부분을 보고, 공통 메모리 관리 코드에서 다시 합친다. 여기서 설명하는 시스템 콜 ABI는 일반 C 함수 호출 규약과 구별한다.
x86-64: syscall과 x86 mmap 진입점
네이티브 x86-64 ABI는 syscall 명령을 사용한다. 시스템 콜 번호는 rax에, 여섯 인자 addr, len, prot, flags, fd, offset은 순서대로 rdi, rsi, rdx, r10, r8, r9에 놓인다. mmap 번호는 9다. 이 진입은 페이지 폴트처럼 IDT를 통하는 경로가 아니다.[1][2]
시스템 콜 디스패치를 거쳐 arch/x86/kernel/sys_x86_64.c의 SYSCALL_DEFINE6(mmap, ...) 본문에 도달한다. 이 코드는 바이트 단위 오프셋의 페이지 정렬을 검사하고, off >> PAGE_SHIFT로 페이지 단위 오프셋을 만들어 ksys_mmap_pgoff()를 호출한다.[3]
RISC-V RV64: ecall과 RISC-V mmap 진입점
RV64의 네이티브 ABI는 ecall 명령을 사용한다. 시스템 콜 번호는 a7에, 같은 여섯 인자는 a0부터 a5에 놓인다. ecall은 사용자 모드의 환경 호출 예외를 일으킨다. CPU는 stvec에 등록된 트랩 진입점으로 이동하며, Linux의 handle_exception과 예외 분기 표를 거쳐 do_trap_ecall_u()로 전달된다. 이 함수는 regs->a7의 번호로 시스템 콜을 분기한다. ecall 다음 명령으로 돌아가도록 저장된 epc를 4 증가시키는 것도 여기서 한다.[4][5][6]
RV64의 SYSCALL_DEFINE6(mmap, ...)은 riscv_sys_mmap(..., offset, 0)을 호출한다. 이 도우미도 바이트 단위 오프셋의 정렬을 검사하고 페이지 단위로 바꾸어 ksys_mmap_pgoff()에 전달한다. 오프셋은 이 경계에서 한 번 페이지 단위로 바뀐다.[7]
공통 경로와 대상별 연결
mmap
+-- x86-64: syscall → entry_SYSCALL_64 → x86 mmap
+-- RV64: ecall → handle_exception → do_trap_ecall_u
→ RV64 mmap → riscv_sys_mmap
|
v
ksys_mmap_pgoff → vm_mmap_pgoff → do_mmap
→ mmap_region → __mmap_region
+-- private anonymous: 익명 VMA 설정
+-- 일반 파일: 파일시스템의 mmap 콜백
+-- 장치 파일: 장치 드라이버의 mmap 콜백
ksys_mmap_pgoff()는 MAP_ANONYMOUS가 아니면 fget()으로 fd를 열린 struct file 객체에 연결한다. 익명 매핑은 사용자 파일을 찾지 않는다. vm_mmap_pgoff()가 주소 공간의 쓰기 잠금을 잡고, do_mmap()은 길이와 오프셋, 접근 조건을 검사하며 사용할 주소를 구한다.[8][9]
__mmap_region()은 VMA를 구성하고 대상별 처리를 연결한다. private anonymous 매핑에는 vma_set_anonymous()를 사용한다. 파일이 있으면 mmap_file()과 call_mmap()을 통해 file->f_op->mmap을 호출한다. 같은 파일 연산 표라도 일반 파일에서는 파일시스템의 함수, 장치 파일에서는 그 장치 드라이버의 함수로 이어진다. 커널이 VMA를 관리하는 틀은 공통이지만, 데이터의 공급 방식까지 같은 것은 아니다.[8][10][11]
파일 매핑의 vm_pgoff는 VMA 시작점에 대응하는 페이지 단위 파일 오프셋이다. 일반 파일에서는 구간 안 주소의 파일 위치가 다음처럼 정해진다.
파일 위치 = (vm_pgoff × PAGE_SIZE) + (접근 주소 - vm_start)
장치 파일의 오프셋 의미는 드라이버의 인터페이스에 달려 있다. 예를 들어 MMIO 장의 UIO는 오프셋을 장치의 매핑 영역 번호를 고르는 데 사용한다. 또한 인접한 호환 VMA와 합칠 수 있으므로 mmap() 호출 하나가 항상 VMA 하나로 남는 것은 아니다.[8]
매핑의 수명
파일 매핑은 파일 객체에 대한 참조를 보유한다. 매핑 성공 뒤 fd를 close()하더라도 매핑은 남는다. munmap()은 주소 구간의 매핑을 해제하는 별도 작업이다. 해제한 포인터를 다시 사용하면 안 되며, 같은 주소가 나중에 재사용될 수 있으므로 잘못된 접근이 항상 신호로 드러나지도 않는다.[8]
주소 구간을 확보했다고 해서 이후 모든 접근의 성공이 보장되지는 않는다. 익명 페이지를 준비할 메모리가 부족할 수 있고, 매핑한 파일이 줄어들 수도 있으며, 장치에는 장치별 수명 조건이 있다. 다음 장에서는 유효한 접근을 CPU 예외와 커널이 어떻게 구별하고 완성하는지 살펴본다.
참고 문헌
- ↑ Linux v6.12, arch/x86/entry/entry_64.S: entry_SYSCALL_64와 ABI 주석.
- ↑ Linux v6.12, arch/x86/entry/syscalls/syscall_64.tbl: mmap.
- ↑ Linux v6.12, arch/x86/kernel/sys_x86_64.c: SYSCALL_DEFINE6(mmap).
- ↑ Linux v6.12, arch/riscv/kernel/entry.S: handle_exception, excp_vect_table.
- ↑ Linux v6.12, arch/riscv/kernel/traps.c: do_trap_ecall_u.
- ↑ Linux v6.12, arch/riscv/include/asm/syscall.h: syscall_get_arguments.
- ↑ Linux v6.12, arch/riscv/kernel/sys_riscv.c: mmap, riscv_sys_mmap, mmap2.
- ↑ 8.0 8.1 8.2 8.3 Linux v6.12, mm/mmap.c, ksys_mmap_pgoff, do_mmap, __mmap_region.
- ↑ Linux v6.12, mm/util.c, vm_mmap_pgoff.
- ↑ Linux v6.12, mm/internal.h, mmap_file.
- ↑ Linux v6.12, include/linux/fs.h, call_mmap.