Memory · Linux 6.18.37 LTS · source + diagram note

page fault

최신 LTS 기준: kernel.org longterm 6.18.37 기준으로 파일 위치와 링크를 맞춘 원본 코드 분석 노트입니다. do_page_fault, handle_mm_fault, fault flags.

전체 흐름

메모리 관리에서는 주소 변환, 물리 페이지의 소유권, 회수 정책, 할당 지연 시간이 서로 영향을 준다. 이 층에서는 '어느 page가 지금 누구 소유인가'와 '그 소유권을 어떤 lock/refcount가 증명하는가'를 끝까지 추적해야 한다.

핵심 확인 사항

함수의 입력과 처리 결과를 먼저 확인한다. 구조체를 생성하고 연결하는 시점, 다른 코드에서 사용할 수 있게 하는 시점, 더 이상 사용하지 않아 회수하는 시점을 구분한다.

오류 처리 확인

정상 처리와 오류 처리를 함께 확인한다. 오류 복구, 장치의 실행 중 연결·분리(hotplug), 자원 해제 과정에서도 상태가 올바르게 정리되는지 점검한다.

그림 1. page fault 이 kernel 안에서 놓이는 위치
virtual addressVMA, fault, kernel mapping
→
page tablePTE/PMD/PUD, TLB maintenance
→
struct page/foliorefcount, mapcount, flags
→
allocator/reclaimzone, LRU, compaction, writeback

왼쪽부터 처리 단계를 나열했다. 각 코드 조각에서 바뀌는 상태를 해당 단계와 대조한다.

소스 분기 지도: 주소에서 복구 결과까지

do_page_fault()는 fault 주소와 접근 종류를 해석하고, 해당 VMA를 찾은 뒤 접근 권한을 검사합니다. 통과한 경우에만 handle_mm_fault()가 페이지 테이블 처리를 맡습니다. 빈 PTE에서 anonymous/file 경로가 갈리고, 이미 존재하는 PTE도 non-present·NUMA 보호·쓰기 보호·접근 비트 상태에 따라 동작이 다릅니다. 따라서 page fault가 항상 새 PTE 하나를 설치하는 것은 아닙니다.

가로로 이동해 읽을 수 있습니다. 도해 · fault 분기 크게 열기 ↗

FAR와 ESR에서 주소와 접근 종류를 정한 뒤 VMA 검사로 가고, 일반 PTE의 빈 항목·non-present 항목·NUMA 보호 항목·쓰기 보호 항목이 각각 다른 처리로 갈라진다. 결과는 복구, 재시도 또는 오류다.
도해 · fault 분기 지도. 일반 PTE 경로를 이해하기 위한 자체 제작 개념도입니다. 실제 주소 배치나 계측 trace가 아니며, hugetlb 및 PUD/PMD huge-page 분기는 간략화했습니다. EL0에서 VMA 조회·잠금에 실패하면 mmap_lock 경로로 넘어갑니다.
단계마다 누가 VMA·PTE를 보호하는가
판정 위치읽는 객체와 보호갈림길
do_page_fault() 진입far, esr, pt_regs, current->mm. 아직 VMA 잠금 전.untagged_addr(far)와 접근 종류를 정함. interrupt 또는 mm 부재 시 no_context.
EL0 VMA 빠른 경로lock_vma_under_rcu()로 얻은 VMA의 read lock. FAULT_FLAG_VMA_LOCK을 전달.VMA가 없거나 잠금 시도가 실패하면 lock_mmap으로 이동. 권한/pkey 거부는 bad_area.
VMA 느린 경로lock_mm_and_find_vma() 성공 시 mmap_lock read. 그 보호 아래 vma->vm_flags 검사.VMA 없음 → SEGV_MAPERR, 권한 불일치 → SEGV_ACCERR. VM_FAULT_RETRY면 VMA를 다시 찾음.
일반 PTE 경로vm_fault, pmd, orig_pte. 진입 시 VMA 또는 mmap read lock. present PTE 갱신에서는 ptl을 획득.없음 → anonymous/file fault, non-present → do_swap_page(), NUMA 보호 PTE → do_numa_page(), 쓰기 보호 → do_wp_page(), 일반 present → 접근/dirty 갱신.
호출자에게 복귀반환값 vm_fault_t와 현재 잠금 상태를 같이 확인. RETRY/COMPLETED 후에는 VMA 재참조 금지.정상 복구 또는 이미 해결됨 → 명령 재실행. RETRY → 잠금·VMA 재획득. 오류 → OOM 또는 해당 signal 처리.

잠금 주의: handle_mm_fault()에 들어갈 때는 VMA read lock 또는 mmap_lock을 갖지만, fault 처리 중 잠금이 풀릴 수 있습니다. 특히 VM_FAULT_RETRY와 VM_FAULT_COMPLETED를 단순 성공/실패 값으로만 읽지 말고 호출자의 unlock·재탐색 분기와 함께 보세요.

고정 소스 근거: arch/arm64/mm/fault.c:552–620 (주소·접근 종류), 622–779 (VMA 잠금과 결과), mm/memory.c:6166–6351 (PTE·상위 테이블, NUMA 분기 6215–6216행), 4371–4377 (빈 PTE의 anonymous/file 분기), 6478–6549 (잠금 계약과 반환). 검증에 사용한 로컬 체크아웃의 태그 v6.18.37 파일 SHA-256은 arch/arm64/mm/fault.c: F616F18E8279D914653D38A3A4745EAEA779A2044173605C1696CA1FEAE58E9D, mm/memory.c: B67175AAC45601E50972AB32F38854756CF8CE5D20C3E323B008E9BF40327045입니다.

소스 코드 위치

먼저 확인할 Linux 6.18.37 LTS 소스 파일

원본 코드:
Linux 6.18.37 LTS: arch/arm64/mm/fault.c
Linux 6.18.37 LTS: mm/memory.c
Linux 6.18.37 LTS: mm/mmap.c

설명: 진입 함수가 있는 파일과 구조체 정의, 아키텍처별 연결 코드, 콜백 구현을 함께 확인한다. 여러 파일을 대조해야 호출 관계와 각 코드의 역할을 알 수 있다.

대표 코드

do_page_fault

원본 코드: arch/arm64/mm/fault.c, 622–660행 (EL0 VMA 경로 발췌)
	if (!(mm_flags & FAULT_FLAG_USER))
		goto lock_mmap;

	vma = lock_vma_under_rcu(mm, addr);
	if (!vma)
		goto lock_mmap;

	if (is_invalid_gcs_access(vma, esr)) {
		vma_end_read(vma);
		fault = 0;
		si_code = SEGV_ACCERR;
		goto bad_area;
	}

	if (!(vma->vm_flags & vm_flags)) {
		vma_end_read(vma);
		fault = 0;
		si_code = SEGV_ACCERR;
		count_vm_vma_lock_event(VMA_LOCK_SUCCESS);
		goto bad_area;
	}

	if (fault_from_pkey(vma, mm_flags)) {
		pkey = vma_pkey(vma);
		vma_end_read(vma);
		fault = 0;
		si_code = SEGV_PKUERR;
		count_vm_vma_lock_event(VMA_LOCK_SUCCESS);
		goto bad_area;
	}

	fault = handle_mm_fault(vma, addr, mm_flags | FAULT_FLAG_VMA_LOCK, regs);
	if (!(fault & (VM_FAULT_RETRY | VM_FAULT_COMPLETED)))
		vma_end_read(vma);

	if (!(fault & VM_FAULT_RETRY)) {
		count_vm_vma_lock_event(VMA_LOCK_SUCCESS);
		goto done;
	}

확인 사항: do_page_fault() 정의는 552행입니다. 사용자 fault는 먼저 VMA read lock을 시도하고, 조회·잠금에 실패하면 mmap_lock 경로(671–713행)로 내려갑니다. 권한 또는 pkey 검사 뒤 handle_mm_fault()를 호출하며, 반환값의 RETRY/COMPLETED 비트에 따라 unlock과 재탐색 책임이 달라집니다.

handle_mm_fault

원본 코드: mm/memory.c, 6485–6549행 (함수 본문)
vm_fault_t handle_mm_fault(struct vm_area_struct *vma, unsigned long address,
			   unsigned int flags, struct pt_regs *regs)
{
	/* If the fault handler drops the mmap_lock, vma may be freed */
	struct mm_struct *mm = vma->vm_mm;
	vm_fault_t ret;
	bool is_droppable;

	__set_current_state(TASK_RUNNING);

	ret = sanitize_fault_flags(vma, &flags);
	if (ret)
		goto out;

	if (!arch_vma_access_permitted(vma, flags & FAULT_FLAG_WRITE,
					    flags & FAULT_FLAG_INSTRUCTION,
					    flags & FAULT_FLAG_REMOTE)) {
		ret = VM_FAULT_SIGSEGV;
		goto out;
	}

	is_droppable = !!(vma->vm_flags & VM_DROPPABLE);

	/*
	 * Enable the memcg OOM handling for faults triggered in user
	 * space.  Kernel faults are handled more gracefully.
	 */
	if (flags & FAULT_FLAG_USER)
		mem_cgroup_enter_user_fault();

	lru_gen_enter_fault(vma);

	if (unlikely(is_vm_hugetlb_page(vma)))
		ret = hugetlb_fault(vma->vm_mm, vma, address, flags);
	else
		ret = __handle_mm_fault(vma, address, flags);

	/*
	 * Warning: It is no longer safe to dereference vma-> after this point,
	 * because mmap_lock might have been dropped by __handle_mm_fault(), so
	 * vma might be destroyed from underneath us.
	 */

	lru_gen_exit_fault();

	/* If the mapping is droppable, then errors due to OOM aren't fatal. */
	if (is_droppable)
		ret &= ~VM_FAULT_OOM;

	if (flags & FAULT_FLAG_USER) {
		mem_cgroup_exit_user_fault();
		/*
		 * The task may have entered a memcg OOM situation but
		 * if the allocation error was handled gracefully (no
		 * VM_FAULT_OOM), there is no need to kill anything.
		 * Just clean up the OOM state peacefully.
		 */
		if (task_in_memcg_oom(current) && !(ret & VM_FAULT_OOM))
			mem_cgroup_oom_synchronize(false);
	}
out:
	mm_account_fault(mm, regs, address, flags, ret);

	return ret;
}

확인 사항: 진입 전에 호출자가 VMA read lock 또는 mmap_lock을 보유합니다. 이 함수는 접근 허용 여부를 확인하고 hugetlb 또는 일반 __handle_mm_fault()로 분기한 뒤 결과를 계수합니다. 하위 함수가 잠금을 해제했을 수 있으므로, 호출 뒤에는 vma를 다시 역참조하지 않습니다.

함수별 분석

CPU exception을 VMA policy, page table update, file/anonymous backing 처리로 연결하는 demand paging 경로다.

원본 코드를 함수별로 나누어 살펴본다. 각 함수가 준비하는 값과 다음 함수가 그 값을 사용하는 방식을 확인한다.

그림 2. page fault 함수 체인과 관찰 지점
entrycaller가 넘기는 객체와 context를 확정
→
do_page_faultarch fault handler 진입
→
handle_mm_fault주소가 어떤 mapping인지 결정
→
observable statetracepoint, counter, sysfs/proc에서 확인되는 결과

각 단계의 입력과 처리 결과를 연결해 본다. 다음 단계로 넘어가기 전에 바뀌는 필드를 코드에서 확인한다.

1. do_page_fault

do_page_fault 주변에서는 pt_regs를 중심으로 본다. 이 필드는 fault context와 EL 정보 역할을 하므로, 함수가 끝날 때 exception entered 상태가 실제로 성립했는지 확인해야 한다.

원본 코드에서 입력 범위 검사, 주요 필드 변경, 다른 계층에서 사용할 수 있게 하는 시점, 실패 시 복구 순서를 확인한다. 이 조건을 만족해야 다음 함수가 arch fault handler 진입을 전제로 삼을 수 있다.

자주 틀리는 해석: kernel fault와 user fault를 같은 정책으로 봄

2. handle_mm_fault

handle_mm_fault 주변에서는 fault_address를 중심으로 본다. 이 필드는 문제 virtual address 역할을 하므로, 함수가 끝날 때 VMA resolved 상태가 실제로 성립했는지 확인해야 한다.

원본 코드에서 입력 범위 검사, 주요 필드 변경, 다른 계층에서 사용할 수 있게 하는 시점, 실패 시 복구 순서를 확인한다. 이 조건을 만족해야 다음 함수가 주소가 어떤 mapping인지 결정을 전제로 삼을 수 있다.

자주 틀리는 해석: mmap_lock retry 규칙을 무시함

함수입력상태 변경확인할 사항
do_page_faultpt_regs, 호출 시 실행 문맥, 하위 시스템의 준비 조건exception entered: arch fault handler 진입kernel fault와 user fault를 같은 정책으로 봄 문제를 코드상 어느 조건문 또는 error label에서 분리하는가
handle_mm_faultfault_address, 호출 시 실행 문맥, 하위 시스템의 준비 조건VMA resolved: 주소가 어떤 mapping인지 결정mmap_lock retry 규칙을 무시함 문제를 코드상 어느 조건문 또는 error label에서 분리하는가

구조체와 필드

구조체의 필드를 언제 바꿀 수 있고 다른 코드가 언제 읽을 수 있는지 확인한다. 필드의 의미와 함께 잠금 등 접근을 보호하는 규칙을 살펴본다.

그림 3. page fault 핵심 필드 연결
pt_regsfault context와 EL 정보
→
fault_address문제 virtual address
→
vm_area_struct허용된 memory policy
→
vm_fault하위 fault handler 전달 상태

각 단계에서 읽고 쓰는 필드를 구조체 정의와 대조한다.

어떤 코드가 필드를 변경하고 어떤 방식으로 접근을 보호하는지, 객체를 언제 해제하는지 원본 코드에서 확인한다.

필드읽는 법
pt_regsfault context와 EL 정보
fault_address문제 virtual address
vm_area_struct허용된 memory policy
vm_fault하위 fault handler 전달 상태

실행 단계와 상태 변화

실행 단계를 따로 정리하면 정상 경로와 실패 경로를 나란히 비교할 수 있다. 커널 문제는 최종 결과보다 준비가 덜 된 중간 상태를 다른 코드에 공개한 뒤, 실패 시 제대로 정리하지 못할 때 자주 발생한다.

그림 4. page fault 실행 단계
exception enteredarch fault handler 진입
→
VMA resolved주소가 어떤 mapping인지 결정
→
PTE installedCOW/file/anon page 연결
→
retry or signalretry, SIGSEGV, OOM 중 결정

화살표는 정상 진행 방향을 나타낸다. 중간 단계에서 실패하면 각 단계의 오류 처리 또는 대체 경로로 이동한다.

상태의미진입 조건깨지는 지점
exception enteredarch fault handler 진입앞 단계의 준비와 오류 처리가 끝난 뒤다음 단계가 준비되지 않은 상태를 사용하거나 필요한 정리를 늦출 때
VMA resolved주소가 어떤 mapping인지 결정앞 단계의 준비와 오류 처리가 끝난 뒤다음 단계가 준비되지 않은 상태를 사용하거나 필요한 정리를 늦출 때
PTE installedCOW/file/anon page 연결앞 단계의 준비와 오류 처리가 끝난 뒤다음 단계가 준비되지 않은 상태를 사용하거나 필요한 정리를 늦출 때
retry or signalretry, SIGSEGV, OOM 중 결정앞 단계의 준비와 오류 처리가 끝난 뒤다음 단계가 준비되지 않은 상태를 사용하거나 필요한 정리를 늦출 때
page fault 검토 기준 = 상태 변화 + 소유권 변화 + 외부 공개 시점

불변 조건과 실패 사례

lifetime

page fault의 핵심 객체가 외부에 공개된 뒤에는 마지막 참조가 사라질 때까지 해제 경로가 callback, timer, IRQ, worker와 경합하지 않아야 한다.

ordering

다른 CPU나 하위 계층이 변경된 필드를 읽는다면 잠금과 메모리 배리어가 접근 순서를 어떻게 보장하는지 확인한다. 참조 횟수와 RCU로 관리하는 객체 수명도 별도로 확인한다.

오류 복구

중간 단계에서 실패하면 이미 확보한 자원을 의존 관계에 맞춰 해제해야 한다. goto 레이블이 많다면 각 분기에 도달했을 때 확보한 자원을 표로 정리한다.

bring-up symptom

임베디드 보드를 처음 구동할 때는 패닉 대신 로그 중단, 시간 초과, 장치 초기화 지연, 과도한 인터럽트 같은 증상으로 문제가 드러날 수 있다.

주의

kernel fault와 user fault를 같은 정책으로 봄

주의

mmap_lock retry 규칙을 무시함

주의

COW fault를 단순 allocation으로만 해석함

계측과 검증

로그를 무작정 추가하지 말고 확인할 가설을 먼저 세운다. 상태표에서 처리가 멈춘 단계를 찾은 뒤, 상태를 바꾸는 함수와 그 결과를 사용하는 함수를 함께 살펴본다.

로그가 출력되었다고 처리가 끝난 것으로 판단하지 않는다. 원본 코드에서 로그 앞뒤의 필드 변경을 확인한다.

도구보는 것
exceptions:page_fault_userpage fault 실행이 어느 단계에서 멈추는지 확인
perf mempage fault 실행이 어느 단계에서 멈추는지 확인
ftrace:handle_mm_faultpage fault 실행이 어느 단계에서 멈추는지 확인
cat /proc/<pid>/mapspage fault 실행이 어느 단계에서 멈추는지 확인
기본 추적 경로:
# 예시: tracefs가 켜진 보드에서 토픽별 event를 좁혀 본다.
mount -t tracefs nodev /sys/kernel/tracing
echo function_graph > /sys/kernel/tracing/current_tracer
echo ':mod:*' > /sys/kernel/tracing/set_ftrace_filter
cat /sys/kernel/tracing/trace_pipe

추가 확인 사항

  • page fault 의 state machine을 네 단계로 줄였을 때, 실제 코드에서 빠지는 intermediate state는 무엇인가?
  • page fault 의 fast path가 생략한 검사는 어느 init path 또는 slow path에서 보증되는가?
  • 실험으로 확인한다면 'kernel fault와 user fault를 같은 정책으로 봄' 문제를 어떤 tracepoint와 counter로 분리할 수 있는가?
  • 실험으로 확인한다면 'mmap_lock retry 규칙을 무시함' 문제를 어떤 tracepoint와 counter로 분리할 수 있는가?