Memory · Linux v6.6 · source + diagram note

page fault

ARM64 예외 주소·접근 유형을 VMA 권한 검사와 PTE 처리에 연결합니다. 빠른 VMA 잠금, 재시도, 사용자 시그널과 커널 예외 복구를 분리해 읽습니다.

전체 흐름

CPU의 translation/access/permission fault가 모두 새 페이지 할당을 뜻하지는 않습니다. ARM64 예외 핸들러는 FAR·ESR·pt_regs에서 주소와 접근 종류를 정하고, 보호된 VMA에서 권한을 확인한 뒤 handle_mm_fault()로 넘깁니다. 결과는 기존 PTE의 접근 비트 갱신, anonymous/file 처리, 재시도, OOM 또는 시그널일 수 있습니다.

그림 1. Linux v6.6 ARM64 사용자 page fault 분기
해결 새 PTE 설치가 필수는 아님.RETRY 잠금과 VMA를 다시 확보.오류 EL0은 시그널/OOM, EL1은 커널 fault 경로.

일반 사용자 fault 중심의 개념도입니다. huge page와 kernel translation fault는 단순화했습니다.

고정 소스: arch/arm64/mm/fault.c:530–700, mm/memory.c:4655–4727, 4947–5023.

소스 코드 위치

먼저 확인할 Linux v6.6 소스 파일

원본 코드:
Linux v6.6: arch/arm64/mm/fault.c
Linux v6.6: mm/memory.c
Linux v6.6: mm/mmap.c

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

대표 코드

do_page_fault

원본 코드: arch/arm64/mm/fault.c, 495행 부근
}

#define VM_FAULT_BADMAP		((__force vm_fault_t)0x010000)
#define VM_FAULT_BADACCESS	((__force vm_fault_t)0x020000)

static vm_fault_t __do_page_fault(struct mm_struct *mm,
				  struct vm_area_struct *vma, unsigned long addr,
				  unsigned int mm_flags, unsigned long vm_flags,
				  struct pt_regs *regs)
{
	/*
	 * Ok, we have a good vm_area for this memory access, so we can handle
	 * it.
	 * Check that the permissions on the VMA allow for the fault which
	 * occurred.
	 */
	if (!(vma->vm_flags & vm_flags))
		return VM_FAULT_BADACCESS;
	return handle_mm_fault(vma, addr, mm_flags, regs);
}

static bool is_el0_instruction_abort(unsigned long esr)
{
	return ESR_ELx_EC(esr) == ESR_ELx_EC_IABT_LOW;
}

/*
 * Note: not valid for EL1 DC IVAC, but we never use that such that it
 * should fault. EL0 cannot issue DC IVAC (undef).
 */
static bool is_write_abort(unsigned long esr)
{
	return (esr & ESR_ELx_WNR) && !(esr & ESR_ELx_CM);
}

static int __kprobes do_page_fault(unsigned long far, unsigned long esr,
				   struct pt_regs *regs)
{
	const struct fault_info *inf;
	struct mm_struct *mm = current->mm;

확인 사항: 이 발췌는 __do_page_fault()의 VMA 권한 검사와 do_page_fault() 시작을 함께 보여 줍니다. handle_mm_fault() 정의는 mm/memory.c에 있습니다. 첫 닫는 중괄호는 발췌 경계입니다.

do_page_fault 진입부의 다음 발췌

원본 코드: arch/arm64/mm/fault.c, 508행 부근 (handle_mm_fault 정의 아님)
	 * Check that the permissions on the VMA allow for the fault which
	 * occurred.
	 */
	if (!(vma->vm_flags & vm_flags))
		return VM_FAULT_BADACCESS;
	return handle_mm_fault(vma, addr, mm_flags, regs);
}

static bool is_el0_instruction_abort(unsigned long esr)
{
	return ESR_ELx_EC(esr) == ESR_ELx_EC_IABT_LOW;
}

/*
 * Note: not valid for EL1 DC IVAC, but we never use that such that it
 * should fault. EL0 cannot issue DC IVAC (undef).
 */
static bool is_write_abort(unsigned long esr)
{
	return (esr & ESR_ELx_WNR) && !(esr & ESR_ELx_CM);
}

static int __kprobes do_page_fault(unsigned long far, unsigned long esr,
				   struct pt_regs *regs)
{
	const struct fault_info *inf;
	struct mm_struct *mm = current->mm;
	vm_fault_t fault;
	unsigned long vm_flags;
	unsigned int mm_flags = FAULT_FLAG_DEFAULT;
	unsigned long addr = untagged_addr(far);
	struct vm_area_struct *vma;

	if (kprobe_page_fault(regs, esr))
		return 0;

	/*
	 * If we're in an interrupt or have no user context, we must not take
	 * the fault.
	 */

확인 사항: 이 두 번째 발췌도 arch/arm64/mm/fault.c의 do_page_fault() 시작부입니다. 아래 VMA 잠금·retry 분기는 원본 링크에서 이어서 확인합니다.

함수별 분석

do_mem_abort()의 fault_info가 page fault 경로를 택하면 do_page_fault()가 접근 유형을 해석합니다. EL0 빠른 VMA 경로의 권한 불일치는 이 버전에서 곧장 SIGSEGV로 가지 않고 lock_mmap에서 재검사합니다. 느린 경로의 __do_page_fault()가 권한 검사 후 handle_mm_fault()를 호출합니다.

위치입력·판정분기·나갈 때
ARM64 do_page_fault()far·esr·regs, current->mmfaulthandler_disabled/no mm이면 no_context. EL0만 per-VMA 잠금 먼저 시도.
VMA 빠른 경로lock_vma_under_rcu()의 VMA read lock권한 불일치·VMA 없음은 mmap_lock 경로. RETRY/COMPLETED는 잠금 해제를 반영.
VMA 느린 경로lock_mm_and_find_vma() 성공 시 mmap_lock__do_page_fault()에서 vm_flags 검사 후 handle_mm_fault(). RETRY면 VMA 재조회.
MM 공통 처리VMA·주소·FAULT_FLAG_*hugetlb 또는 __handle_mm_fault(); 일반 PTE는 없음/스왑/NUMA/쓰기 보호/접근 갱신.

잠금 계약: VM_FAULT_RETRY 또는 VM_FAULT_COMPLETED 뒤에는 이전 VMA를 계속 사용하지 않습니다. RETRY에서는 주소로 VMA를 다시 찾습니다.

v6.6 ARM64 fault.c, v6.6 mm/memory.c.

구조체와 필드

예외 입력, VMA 접근 정책, 페이지 테이블 상태, 반환값은 서로 다른 판단 근거입니다. 하나의 순차적 객체 열로 읽으면 VMA가 PTE를 직접 소유한다는 잘못된 인상을 줍니다.

객체·값역할보호·수명
far / esr / pt_regs주소, exception 원인, EL0/EL1·접근 유형arch fault 진입의 입력; 사용자/커널 처리 구별
mm_struct / vm_area_struct주소 공간과 해당 범위의 vm_flagsper-VMA read lock 또는 mmap_lock으로 보호; RETRY 뒤 옛 VMA 사용 금지
vm_fault / orig_pteMM 내부 주소·flags·원래 PTE 상태일반 present PTE 갱신은 ptl 아래 재검사
vm_fault_t해결, RETRY, COMPLETED, OOM, SIGBUS 등의 결과호출자의 unlock·재탐색·시그널 분기 결정

실행 단계와 상태 변화

fault 성공은 항상 “PTE 하나를 새로 설치했다”는 뜻이 아닙니다. 이미 present인 PTE는 접근/dirty 상태만 바꾸거나 경쟁자가 먼저 해결했을 수 있습니다.

PTE 상태일반 PTE의 대표 경로호출자 관찰
빈 PTEanonymous → do_anonymous_page(); file → do_fault()페이지 준비와 PTE 설치 또는 retry/error 가능
non-present PTEdo_swap_page()swap/marker 복구 또는 오류
NUMA 보호 PTEdo_numa_page()NUMA balancing
쓰기 보호 PTEdo_wp_page()COW 또는 쓰기 허용. 단순 할당과 다름
present PTEptl 아래 재확인 후 young/dirty/access 갱신새 folio 생성 없이 해결 가능

hugetlb 및 상위 단계의 huge PMD/PUD 경로는 별도입니다. 이 표는 일반 PTE 경로만 분리합니다.

v6.6 mm/memory.c.

불변 조건과 실패 사례

VMA 보호가 먼저: vm_flags 검사와 MM 처리에는 per-VMA read lock 또는 mmap_lock이 필요합니다. RETRY 뒤에는 주소로 다시 VMA를 찾습니다.
사용자와 커널 구분: EL0 BADMAP/BADACCESS는 MAPERR/ACCERR로 분리합니다. EL1의 복구 불가 fault는 no_context 경로를 봅니다.
COW를 분해: 쓰기 보호 present PTE는 do_wp_page()로 향합니다. 모든 COW를 단순 할당으로 요약하지 않습니다.

계측과 검증

/proc/<pid>/maps로 주소가 어느 VMA에 있는지 대조하고, perf stat -e page-faults,minor-faults,major-faults -- <command>로 fault 발생량을 볼 수 있습니다. 카운터만으로 특정 PTE 분기나 시그널 원인을 증명하지는 못합니다.

질문관찰 자료한계
주소가 VMA 밖인가?/proc/<pid>/maps, SIGSEGV si_code실행 중 VMA 변화에 유의해 동일 주소·시점 확인
fault가 반복되는가?minor/major counters, perf page-faultsCOW·swap·NUMA·권한 실패를 카운터만으로 구분 못함
잠금 재시도를 거쳤나?fault.c의 retry label, 필요 시 표적 ftrace잠금 해제와 VMA 재획득을 함께 확인

추가 확인 사항

  • EL0 빠른 VMA 경로의 VMA 없음과 권한 불일치는 어디로 이동하는가?
  • 빈 PTE, non-present, NUMA 보호, 쓰기 보호, present PTE는 각각 어디로 갈라지는가?
  • VM_FAULT_RETRY/COMPLETED 뒤 잠금과 VMA 수명은 누가 책임지는가?
  • 사용자 시그널과 커널 no_context를 어떤 조건으로 구분하는가?