Study · 운영체제 · 페이지 폴트 · 메모리

페이지 폴트는 오류가 아니라 정상 동작이다

장면

잡아만 둔 메모리를 처음 건드리면 무엇이 일어날까? 그 일은 몇 번이나 일어날까?

$ /usr/bin/time -v ./a.out 2>&1 | grep -i "page faults"
  Minor (reclaiming a frame) page faults: 12345
# 이 수가 페이지를 붙인 횟수다. 잡아만 뒀을 때는 거의 0, 다 건드리면 확 는다

아래 재생기의 ▶를 누르면 한 스텝씩 진행된다. 금색 테두리가 지금 이야기하는 곳이고, 캡션이 무슨 일이 일어났는지 말해 준다. 굵게 밑줄 친 말은 누르면 뜻이 나온다. 빨간 딱지는 그때 벌어지는 일의 이름이고, 캡션 바로 위 회색 글자는 지금 스텝이 속한 묶음의 이름이다.

카드 아래 회색 줄은 그 일이 실제로 적혀 있는 소스의 파일 이름 : 줄 번호와 함수 이름이다. 글 안에 (:669)처럼 줄 번호만 적힌 것은 바로 앞에 적은 파일의 그 줄이라는 뜻이다. 캡션 한가운데 박힌 긴 영어 이름과 괄호 속 파일 · 줄 번호도 같다 — 건너뛰고 한글만 읽어도 뜻이 통하게 써 두었다.

장면 · 닿는 순간에 붙는다 0 / 12

▶ 를 누르면 앞 항목에서 잡아만 둔 32칸을 처음 건드릴 때 무슨 일이 일어나는지 본다. 페이지 폴트는 프로그램이 부르는 것이 아니라 CPU 가 올리는 신호다. minor 폴트는 디스크에 가지 않고 끝난 폴트를 말한다.

키보드 · ← → 한 스텝 · Space 재생/멈춤 · Home 처음

페이지 폴트는 오류가 아니라 「지금 붙여 달라」는 신호다. 닿는 순간 커널이 페이지를 붙이고, 그때 비로소 물리 메모리가 준다.

어떻게 확인했나

소스 어디를 보고 적었는지와 무엇을 돌려서 쟀는지를 남긴 자리다. 처음 읽을 때는 건너뛰어도 된다.

  • 폴트는 시스템 콜이 아니라 하드웨어 예외로 들어온다 — exc_page_fault(arch/x86/mm/fault.c:1492) → handle_page_fault(:1471) → do_user_addr_fault(:1216). 커널이 맨 먼저 하는 것은 lock_mm_and_find_vma(:1366)로 그 주소를 덮는 vma 를 찾는 일이고, 못 찾으면 거기서 SIGSEGV 다.
  • 붙이는 차례는 handle_mm_fault(mm/memory.c:6841) → handle_pte_fault(:6514) → do_pte_missing(:6557 에서 부르고, 정의는 :4695) → do_anonymous_page(:5427, 가르는 줄은 :4697) 다. 실제로 페이지를 받아 오는 줄이 alloc_anon_folio(:5482), 페이지 표에 적는 줄이 map_anon_folio_pte_pf(:5519).
  • VmRSS 가 느는 자리는 map_anon_folio_pte_pf 안의 add_mm_counter(mm, MM_ANONPAGES, ...)(mm/memory.c:5417) 하나다. mmap 경로에는 이 줄이 없다 — 12번 항목의 VmSize 와 갈리는 지점이 여기다.
  • 읽기로 처음 닿으면 물리 페이지를 아예 안 준다 — 0 으로 채워진 공용 페이지를 가리키게만 하고 끝낸다(mm/memory.c:5447-5450). 내 페이지가 붙는 것은 쓰기일 때뿐이다.
  • minor · major 를 가르는 줄은 mm_account_fault(mm/memory.c:6725)의 major = (ret & VM_FAULT_MAJOR) || (flags & FAULT_FLAG_TRIED)(:6757)다. VM_FAULT_MAJOR 가 서는 곳은 스왑에서 읽어 올 때(do_swap_page, :4988)이고, 그때 maj_flt(:6760), 아니면 min_flt(:6762) 가 오른다.
  • 같은 1GB 를 페이지마다 한 바이트씩 건드리니 VmRSS 가 1,049,856 kB 가 됐다. /usr/bin/time 이 찍는 minor 폴트는 잡기만 했을 때 76번, 전부 건드렸을 때 262,219번이었다 — 1GB ÷ 4KB = 262,144 와 거의 같다. major 폴트는 둘 다 0 이었다. 디스크에서 읽어 올 것이 없는 익명 메모리라 그렇다.