운영체제 기본/Linux/메모리 매핑/페이지 폴트 처리

관리자 (토론 | 기여)님의 2026년 11월 1일 (일) 00:28 판 (공통 mmap·페이지 폴트와 대상별 처리 경로 분리)

mmap()으로 만든 VMA는 주소 구간의 의미와 권한을 정한다. 그 주소의 PTE가 아직 없거나 접근에 필요한 권한이 부족하면 CPU가 페이지 폴트(Page Fault)를 발생시킨다. 정상적인 지연 할당과 쓰기 시 복사(Copy-on-Write, COW)도 이 예외를 이용하므로, 페이지 폴트가 곧 프로그램 오류라는 뜻은 아니다.

Linux v6.12의 기본 페이지 경로를 기준으로, CPU 예외 진입과 공통 메모리 관리 코드의 연결을 살펴본다. 실제 페이지를 마련하는 방법은 익명 메모리와 파일 매핑 장에서 이어진다.

CPU 예외와 VMA 검사

두 CPU 모두 필요한 주소 변환이 없으면 동기 예외를 일으킨다. 커널은 폴트 주소와 접근 종류를 알아내고, VMA가 그 접근을 허용하는지 검사한 뒤 공통 함수 handle_mm_fault()에 복구를 요청한다. 예외 진입 경로와 정보의 표현은 아키텍처별이고, VMA와 페이지 테이블을 다루는 핵심 경로는 공통이다.

load bytes[i]
  |
  +-- x86-64: #PF -> IDT -> exc_page_fault
  |             -> handle_page_fault -> do_user_addr_fault
  |
  +-- RV64: page fault -> handle_exception
                 -> do_page_fault -> handle_page_fault
  |
  v
VMA lookup + access check
  |
  v
handle_mm_fault -> 대상별 처리 -> PTE 설정 -> 명령 재시도

이 예외는 장치가 비동기적으로 보낸 완료 인터럽트가 아니다. 지금 실행한 load 때문에 발생한다. 두 소스 트리에 handle_page_fault라는 같은 이름이 등장하지만 서로 다른 아키텍처별 함수다.

x86-64: #PF, CR2, 오류 코드

사용 가능한 변환이 없으면 x86-64 CPU는 페이지 폴트 예외 #PF를 발생시킨다. 예외 벡터는 14이고 폴트 주소는 CR2에 남는다. CPU가 전달한 오류 코드는 페이지가 존재하지 않는 경우와 보호 위반, 읽기와 쓰기, 사용자 모드 접근 등의 정보를 구별한다. 아직 연결되지 않은 주소의 읽기와, 읽기 전용 PTE에 대한 쓰기는 오류 코드에서 구별된다.

load bytes[i]
  → #PF, vector 14, CR2 = 폴트 주소, 오류 코드
  → IDT → asm_exc_page_fault
  → exc_page_fault
  → handle_page_fault
  → do_user_addr_fault
  → handle_mm_fault

v6.12의 IDT는 해당 예외를 asm_exc_page_fault로 연결한다. C 쪽 exc_page_fault는 이 진입 경로에서 CR2를 읽고 handle_page_fault()를 호출한다. 사용자 주소 처리는 do_user_addr_fault()로 이어진다.[1][2]

RISC-V RV64: scause, stval, sepc

RISC-V에서는 stvec가 지정하는 트랩 진입점으로 제어가 넘어간다. S-mode 커널의 트랩 진입에서 scause는 원인을, stval은 페이지 폴트 주소를, sepc는 중단된 명령의 주소를 제공한다. 페이지 폴트 원인 번호는 명령어 가져오기 12, load 13, store/AMO 15로 나뉜다. 따라서 읽기 접근은 원인 13인 load page fault다. x86의 페이지 폴트 오류 코드 비트를 그대로 적용하는 설명은 맞지 않는다.[3]

load bytes[i]
  → load page fault: scause = 13, stval = 폴트 주소, sepc = load 주소
  → handle_exception
  → excp_vect_table의 load page fault 항목
  → do_page_fault                [arch/riscv/kernel/traps.c]
  → handle_page_fault            [arch/riscv/mm/fault.c]
  → handle_mm_fault              [mm/memory.c]

handle_exception은 트랩 레지스터 값을 pt_regs의 cause, badaddr, epc 등에 저장한다. 예외 표가 선택하는 do_page_fault()는 handle_page_fault()를 부른다. 이 함수는 저장된 원인과 주소를 사용하고, access_error()에서 VMA의 읽기·쓰기·실행 조건을 확인한다.[4][5][6]

VMA 검사와 공통 폴트 처리

두 아키텍처의 처리 함수 모두 폴트 주소를 포함하는 VMA를 찾아 권한을 검사한다. 매핑이 없거나 허용되지 않은 접근이면 정상적인 폴트 복구 대신 신호 처리로 이어질 수 있다. VMA가 허용한 접근이라면 handle_mm_fault()로 진행한다.[2][6]

공통 폴트 처리와 분기

CPU는 VMA를 읽지 않는다. CPU가 보는 것은 페이지 테이블이고, 커널은 예외를 받은 뒤 VMA를 확인한다. 따라서 VMA가 쓰기를 허용해도 PTE는 일시적으로 읽기 전용일 수 있다. 쓰기 시점에 COW를 수행하려고 의도적으로 남겨 둔 차이다.

handle_mm_fault → __handle_mm_fault → handle_pte_fault
  |
  +-- PTE 없음 → do_pte_missing
  |      +-- private anonymous → do_anonymous_page
  |      +-- 파일 등의 처리기 → do_fault
  |
  +-- 읽기 전용 PTE에 쓰기 → do_wp_page

do_pte_missing()은 private anonymous VMA와 파일 등의 처리기를 사용하는 VMA를 나눈다. do_fault()는 읽기, private 쓰기, shared 쓰기를 각각 do_read_fault(), do_cow_fault(), do_shared_fault()로 나눈다. 각 분기는 VMA가 허용한 접근에 필요한 페이지와 권한을 준비한다.[7]

앞으로 익명 매핑에서는 0으로 보이는 메모리를 마련하고, 파일 매핑에서는 파일 페이지를 가져오거나 사본을 만든다. MMIO 매핑은 드라이버가 매핑 시점에 장치 PTE를 설치할 수 있으므로, 모든 매핑이 이 지연 할당 순서를 거치는 것은 아니다.

필요한 입출력이나 메모리를 기다리는 동안 현재 태스크가 잠들 수 있다. 그동안 CPU는 다른 태스크를 실행할 수 있다. 커널이 필요한 페이지를 준비하고 페이지 테이블 잠금 아래 PTE를 설치하면, 예외에서 돌아온 CPU가 중단한 명령을 다시 실행한다. 파일 데이터가 이미 준비되어 있거나 주변 페이지를 함께 연결하는 최적화가 적용되면 더 짧은 경로로 끝날 수도 있다.

PTE 설정과 접근 재시도

공통 코드가 사용하는 PTE라는 이름이 하드웨어 비트 형식까지 같다는 뜻은 아니다. x86-64는 present 비트와 읽기/쓰기, 사용자 접근 등의 속성을 사용하고, RISC-V는 V(valid), R, W, X, U 등의 비트로 유효성과 접근 조건을 표현한다. 페이지 테이블 단계와 실제 엔트리 조작은 아키텍처별 구현에 맡긴다. 이 장에서 중요한 공통 결과는 해당 프로세스의 가상 주소가 준비된 페이지를 허용된 권한으로 가리키게 되는 것이다.[8][9]

RISC-V의 정상적인 페이지 폴트 복구는 시스템 콜의 ecall처럼 epc를 다음 명령으로 증가시키지 않는다. 저장된 fault 명령 위치로 복귀해 같은 load를 다시 수행한다. x86-64도 성공한 페이지 폴트 처리 뒤에는 중단된 load를 다시 실행한다. 레지스터 복원과 사용자 모드 복귀 방식은 두 아키텍처의 사용자 모드 진입에서 다룬다. 커널 안에서 잠금이나 입출력 때문에 폴트 처리를 재시도하는 일과, 사용자 모드로 돌아가 CPU 명령을 다시 실행하는 일은 구별한다.[5][4][6][2]


참고 문헌

  1. ↑ Linux v6.12 arch/x86/kernel/idt.c, 페이지 폴트 IDT 항목.
  2. ↑ 2.0 2.1 2.2 Linux v6.12 arch/x86/mm/fault.c, exc_page_fault, handle_page_fault, do_user_addr_fault와 오류 코드 검사.
  3. ↑ Linux v6.12 arch/riscv/include/asm/csr.h, EXC_*_PAGE_FAULT, CSR_SCAUSE, CSR_STVAL, CSR_SEPC와 S-mode 별칭.
  4. ↑ 4.0 4.1 Linux v6.12 arch/riscv/kernel/entry.S, handle_exception, excp_vect_table, ret_from_exception.
  5. ↑ 5.0 5.1 Linux v6.12 arch/riscv/kernel/traps.c, do_page_fault.
  6. ↑ 6.0 6.1 6.2 Linux v6.12 arch/riscv/mm/fault.c, handle_page_fault, access_error.
  7. ↑ Linux v6.12, mm/memory.c, do_pte_missing, do_anonymous_page, do_fault, do_wp_page, wp_page_copy.
  8. ↑ Linux v6.12 arch/x86/include/asm/pgtable_types.h, 페이지 테이블 속성 비트.
  9. ↑ Linux v6.12 arch/riscv/include/asm/pgtable-bits.h, _PAGE_PRESENT, _PAGE_READ, _PAGE_WRITE, _PAGE_EXEC, _PAGE_USER.