페이징에서는 가상 주소 공간을 page로, 물리 메모리를 frame으로 나누고 page table로 둘을 연결합니다. 프로세스가 접근한 page가 현재 물리 메모리에 없으면 page fault가 발생하고, OS는 디스크에서 page를 가져오거나 잘못된 접근이면 예외를 처리합니다. 정상적인 demand paging에서도 page fault는 발생할 수 있습니다.
주소 변환의 실제 주체는 CPU 안의 MMU(Memory Management Unit)라는 하드웨어입니다. 프로그램이 만드는 모든 메모리 접근은 MMU를 거쳐 가상 주소가 물리 주소로 변환되며, 이 변환 규칙이 page table에 담겨 있습니다. page 크기는 보통 4KB이므로, 가상 주소의 상위 비트는 page 번호로 page table을 찾는 데 쓰고 하위 12비트는 page 안의 offset으로 그대로 사용합니다.
매 접근마다 메모리에 있는 page table을 읽으면 접근 한 번이 두 번 이상이 되어 버립니다. 그래서 MMU 안에 최근 변환 결과를 담아 두는 TLB(Translation Lookaside Buffer)라는 전용 캐시를 두고, 대부분의 접근은 TLB에서 바로 변환합니다. "주소 변환에도 캐시가 있다"는 점은 CPU 캐시의 지역성 논리가 주소 변환에도 그대로 적용된다는 뜻입니다.
꼬리질문
조금 더 깊게 물어본다면
답변 뒤에 이어질 수 있는 질문들을 하나씩 열어볼 수 있어요.
page와 frame의 차이는 무엇인가요?
page는 가상 주소 공간의 고정 크기 블록이고, frame은 물리 메모리의 고정 크기 블록입니다. page table이 page를 frame에 매핑합니다.
둘을 분리해서 얻는 것은 매핑의 자유입니다. 프로세스마다 page table이 따로 있으므로 서로 다른 프로세스의 같은 가상 주소가 다른 frame을 가리킬 수 있고, 반대로 서로 다른 page가 같은 frame을 가리키게 할 수도 있습니다. 공유 라이브러리가 물리 메모리에 한 벌만 올라가는 것, fork 직후 부모와 자식이 메모리를 복사 없이 공유하는 copy-on-write가 모두 후자의 응용입니다.
또한 프로그램은 자기 page들이 연속된 주소 공간이라고 보지만, 실제 frame은 물리 메모리 어디에 있어도 됩니다. "연속으로 보이는 것"과 "실제 배치"를 분리한 덕에, 물리 메모리에 연속된 큰 빈 공간이 없어도 큰 할당이 가능합니다.
page fault가 항상 오류는 아닌 이유는 무엇인가요?
demand paging에서는 필요한 page를 처음 접근할 때 의도적으로 page fault가 발생할 수 있습니다. OS가 page를 적재하면 정상적으로 계속 실행됩니다.
demand paging은 "실제로 접근하기 전까지는 올리지 않는다"는 게으른 적재 전략입니다. 실행 파일을 통째로 메모리에 올리는 대신 page table에 매핑만 만들어 두면, 프로그램 시작이 빨라지고 실제로 쓰이지 않는 코드(예외 처리 경로, 안 쓰는 기능)는 영영 메모리를 차지하지 않습니다. page fault는 이 전략에서 "이제 진짜 필요해졌다"는 신호로 쓰이는 정상 메커니즘입니다.
fault의 비용 차이로 minor fault와 major fault를 구분합니다. minor fault는 디스크 I/O 없이 처리되는 경우로, 데이터가 이미 메모리(page cache)에 있어 매핑만 만들면 됩니다. major fault는 디스크에서 실제로 읽어야 하는 경우로 수 ms가 걸릴 수 있습니다. 성능 분석에서 문제가 되는 것은 대부분 major fault의 빈도입니다.
segmentation fault와 page fault는 같은가요?
같지 않습니다. page fault는 메모리 접근 예외의 넓은 메커니즘이고, 잘못된 주소나 권한 접근이 복구 불가능하면 segmentation fault 같은 오류로 이어질 수 있습니다.
관계를 정리하면 page fault는 입구이고 segmentation fault는 여러 출구 중 하나입니다. MMU가 변환에 실패하면 일단 page fault가 발생하고, OS가 원인을 판별합니다. 유효한 주소인데 아직 안 올라온 것이면 적재 후 재개하고, copy-on-write page에 쓰기가 들어온 것이면 복사본을 만들어 재개합니다. 둘 다 프로그램은 fault가 있었는지도 모릅니다.
반면 그 주소가 프로세스에 할당된 적이 없거나(해제된 pointer, NULL) 권한이 없는 접근(읽기 전용 영역에 쓰기)이면, OS는 복구할 방법이 없으므로 프로세스에 SIGSEGV 신호를 보냅니다. 이것이 개발자가 보는 segmentation fault입니다. 즉 같은 하드웨어 메커니즘이 정상 동작과 오류 통보에 함께 쓰이고, 갈림길은 OS의 판단입니다.
page replacement는 언제 필요한가요?
새 page를 올려야 하는데 빈 frame이 부족할 때 필요합니다. OS는 LRU 계열 같은 정책으로 내보낼 page를 고릅니다.
정확한 LRU는 모든 메모리 접근마다 순서 기록을 갱신해야 해서 OS 수준에서는 비용이 너무 큽니다. 그래서 실제로는 근사 알고리즘을 씁니다. 대표적인 clock 알고리즘은 page마다 참조 비트를 두고, 하드웨어가 접근 시 비트를 켜면 OS가 원형으로 돌며 비트가 켜진 page는 끄고 넘어가고 꺼진 page를 교체 대상으로 고릅니다. "최근에 한 번이라도 쓰였는가"만 보는 값싼 LRU 근사입니다.
내보낼 page의 상태도 비용을 좌우합니다. 적재 후 수정되지 않은 page는 디스크의 원본과 같으므로 그냥 버리면 되지만, 수정된 dirty page는 버리기 전에 디스크에 써야 해서 두 배로 비쌉니다. 그래서 OS는 dirty 비트를 추적하고, 여유 있을 때 미리 디스크에 써 두어(writeback) 교체 시점의 비용을 줄입니다.
내부 단편화와 외부 단편화의 차이는?
내부 단편화는 할당된 블록 안에 쓰지 않는 공간이 남는 것, 외부 단편화는 빈 공간의 총량은 충분하지만 흩어져 있어 연속 할당이 불가능한 것입니다.
페이징은 내부 단편화가 발생합니다. 1바이트짜리 데이터도 페이지(4KB) 하나를 통째로 차지해 나머지 4095바이트가 낭비됩니다.
세그멘테이션은 가변 크기로 할당하므로 내부 낭비는 없지만, 할당과 해제가 반복되면 빈 공간이 조각나는 외부 단편화가 생깁니다. 빈 공간이 2+2바이트로 나뉜 상태에서 4바이트 연속 할당 요청이 오면 실패합니다.
현대 OS는 페이징을 기본으로 쓰기 때문에 외부 단편화는 거의 발생하지 않습니다. 페이지 크기가 고정되어 있어 빈 frame을 어느 page에도 재사용할 수 있기 때문입니다.
thrashing은 무엇인가요?
실행에 필요한 working set이 메모리에 못 올라와 page 교체가 과도하게 반복되는 상태입니다. CPU보다 디스크 I/O가 병목이 됩니다.
thrashing의 악순환 구조를 보면, 어떤 프로세스가 방금 내보낸 page를 곧 다시 필요로 하면 다시 fault가 나고, 그 page를 올리려고 또 다른 필요한 page를 내보냅니다. 모든 프로세스가 이 상태에 빠지면 CPU는 대부분의 시간을 디스크 I/O 대기에 쓰므로, 겉보기에는 CPU 사용률이 낮은데 시스템은 멈춘 것처럼 느려집니다.
working set은 "프로세스가 최근 일정 구간 동안 실제로 사용한 page 집합"으로, thrashing의 판별 기준이 됩니다. 실행 중인 모든 프로세스의 working set 합이 물리 메모리를 넘으면 thrashing이 시작됩니다. 고전적인 해법은 동시에 실행하는 프로세스 수를 줄여 남은 프로세스의 working set이라도 온전히 담는 것이고, 현대 시스템에서는 메모리 압박이 한계에 달하면 Linux의 OOM killer처럼 프로세스를 강제 종료해 시스템 전체가 멈추는 것을 막습니다.
부가 설명
메모리를 연속된 큰 덩어리로만 배치하려고 하면 빈 공간 관리가 어려워집니다. 페이징은 메모리를 일정한 크기로 나누어 어디에 놓였는지 page table로 찾게 합니다. 프로그램은 연속된 주소처럼 보지만 실제 frame은 물리 메모리 곳곳에 흩어져 있을 수 있습니다.
page fault는 이름 때문에 항상 버그처럼 들리지만 그렇지는 않습니다. 아직 메모리에 올리지 않은 page를 처음 접근할 때도 발생하고, OS가 필요한 page를 가져오면 실행이 이어집니다. 다만 page fault가 너무 많아지면 디스크 I/O가 늘고, thrashing처럼 프로그램이 계산보다 page 교체에 시간을 쓰는 상태가 될 수 있습니다.
한 줄 정리
페이징은 메모리를 page 단위로 매핑하는 방식이고, page fault는 필요한 page나 권한을 확인하는 순간 발생하는 예외입니다.