운영체제 기본/Linux/메모리 매핑/mmap과 주소 공간: 두 판 사이의 차이
잔글 관리자님이 운영체제 기본/Linux의 파일 메모리 매핑/mmap과 주소 공간 문서를 운영체제 기본/Linux의 메모리 매핑/mmap과 주소 공간 문서로 이동했습니다: 공통 mmap 구조와 매핑 유형별 설명으로 재구성 |
잔글 Linux 각론 경로 변경에 따른 본문·목차·틀 링크 갱신 |
||
| (같은 사용자의 중간 판 2개는 보이지 않습니다) | |||
| 1번째 줄: | 1번째 줄: | ||
{{이전 다음 문서 | {{이전 다음 문서 | ||
|목차=[[운영체제 기본/ | |이전=처음 문서 | ||
|다음=다음 문서 →<br | |목차=[[운영체제 기본/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를 직접 설치할 수도 있다. 미리 페이지를 채우는 옵션이나 큰 페이지의 세부 경로 대신, 이후 장에서는 기본 페이지 단위의 일반적인 경로를 따라간다. | ||
== mmap 시스템 콜과 주소 공간 == | == mmap 시스템 콜과 주소 공간 == | ||
<code>mmap( | <code>mmap()</code>의 인자와 사용자 인터페이스는 두 아키텍처에서 같다. 그러나 사용자 공간에서 커널로 넘어가는 명령, 인자 레지스터, 커널 진입 코드는 다르다. 먼저 갈라지는 부분을 보고, 공통 메모리 관리 코드에서 다시 합친다. 여기서 설명하는 시스템 콜 ABI는 일반 C 함수 호출 규약과 구별한다. | ||
=== x86-64: syscall과 x86 mmap 진입점 === | === 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다. 이 진입은 | 네이티브 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>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> | 시스템 콜 디스패치를 거쳐 <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> | ||
| 42번째 줄: | 52번째 줄: | ||
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의 네이티브 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>에 전달한다. 이 | 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 | ||
+-- 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 | |||
ksys_mmap_pgoff | +-- private anonymous: 익명 VMA 설정 | ||
+-- 일반 파일: 파일시스템의 mmap 콜백 | |||
+-- 장치 파일: 장치 드라이버의 mmap 콜백 | |||
</pre> | </pre> | ||
<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>ksys_mmap_pgoff()</code>는 | |||
<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> | <pre> | ||
파일 위치 = (vm_pgoff × PAGE_SIZE) + (접근 주소 - vm_start) | |||
</pre> | </pre> | ||
장치 파일의 오프셋 의미는 드라이버의 인터페이스에 달려 있다. 예를 들어 MMIO 장의 UIO는 오프셋을 장치의 매핑 영역 번호를 고르는 데 사용한다. 또한 인접한 호환 VMA와 합칠 수 있으므로 <code>mmap()</code> 호출 하나가 항상 VMA 하나로 남는 것은 아니다.<ref name="mmap"/> | |||
== 매핑의 수명 == | |||
파일 매핑은 파일 객체에 대한 참조를 보유한다. 매핑 성공 뒤 fd를 <code>close()</code>하더라도 매핑은 남는다. <code>munmap()</code>은 주소 구간의 매핑을 해제하는 별도 작업이다. 해제한 포인터를 다시 사용하면 안 되며, 같은 주소가 나중에 재사용될 수 있으므로 잘못된 접근이 항상 신호로 드러나지도 않는다.<ref name="mmap"/> | |||
주소 구간을 확보했다고 해서 이후 모든 접근의 성공이 보장되지는 않는다. 익명 페이지를 준비할 메모리가 부족할 수 있고, 매핑한 파일이 줄어들 수도 있으며, 장치에는 장치별 수명 조건이 있다. 다음 장에서는 유효한 접근을 CPU 예외와 커널이 어떻게 구별하고 완성하는지 살펴본다. | |||
== 참고 문헌 == | == 참고 문헌 == | ||
| 93번째 줄: | 90번째 줄: | ||
{{이전 다음 문서 | {{이전 다음 문서 | ||
|목차=[[운영체제 기본/ | |이전=처음 문서 | ||
|다음=다음 문서 →<br | |목차=[[운영체제 기본/Linux/메모리 매핑|메모리 매핑 목차]] | ||
|다음=다음 문서 →<br>[[운영체제 기본/Linux/메모리 매핑/페이지 폴트 처리|페이지 폴트 처리]] | |||
}} | }} | ||
{{운영체제 기본 둘러보기}} | {{운영체제 기본 둘러보기}} | ||