LabHub

운영체제 · 메모리 관리 · 이론

요구 페이징과 스래싱 — 메모리가 모자랄 때 벌어지는 일

LabHub 에서 이어서 보기

한 줄 요약

페이지를 필요할 때만 올리면 물리 메모리보다 큰 프로그램도 돌릴 수 있지만, 폴트 한 번의 비용이 메모리 접근보다 수만 배 비싸므로 폴트율이 조금만 올라도 시스템이 무너진다.

왜 이게 필요했나

프로그램 전체를 메모리에 올릴 필요는 없다. 오류 처리 코드처럼 거의 실행되지 않는 부분도 많고, 큰 배열의 일부만 실제로 쓰이는 경우도 흔하다. 필요한 시점에만 올리면(요구 페이징) 더 많은 프로세스를 동시에 돌릴 수 있고, 프로그램 시작도 빨라진다.

어떻게 동작하나

페이지 테이블 항목에 유효 비트를 둔다. 유효하지 않은 페이지에 접근하면 페이지 폴트가 발생하고 다음 순서로 처리된다.

1. 트랩이 걸려 커널로 진입한다.
2. 잘못된 접근인지(주소 범위 밖) 아니면 아직 안 올린 것인지 판단한다.
3. 빈 프레임을 찾는다. 없으면 희생 페이지를 골라 내보낸다. 그 페이지가 수정된 상태(dirty)면 먼저 디스크에 기록한다.
4. 디스크에서 페이지를 읽어 프레임에 올린다.
5. 페이지 테이블을 갱신하고, 폴트를 일으킨 명령어부터 다시 실행한다.

비용을 계산해 보면 무섭다. 메모리 접근이 200ns 이고 페이지 폴트 처리가 8ms 라고 하자. 폴트 확률이 p 일 때 실효 접근 시간은 (1 빼기 p) 곱하기 200 더하기 p 곱하기 8,000,000 이다.

페이지 교체 알고리즘의 성격도 알아 둘 만하다. 참조열 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1 에 프레임 3개를 주면 FIFO 는 폴트 15회, LRU 는 12회, 이론적 최적인 OPT 는 9회다. FIFO 에는 프레임을 늘렸는데 폴트가 늘어나는 벨래디의 이상현상까지 있다. 실무 시스템은 순수 LRU 대신 참조 비트를 이용한 클럭(second-chance) 알고리즘을 쓴다. LRU 에 가까운 성능을 훨씬 싼 비용으로 얻기 때문이다.

스래싱은 프레임이 부족해 폴트가 폭증하고, 그 때문에 CPU 가 놀고, 운영체제가 "한가하니 프로세스를 더 올리자"고 판단해 상황이 더 나빠지는 악순환이다. 대응은 워킹 셋(최근 일정 구간에서 실제로 참조된 페이지 집합)을 추적해 그만큼의 프레임을 보장하거나, 폴트율을 직접 모니터링해 프레임을 조절하는 것이다.

현장에서 만나는 모습

컨테이너에 메모리 제한을 걸었을 때 두 가지 결말이 있다. 스왑이 꺼져 있으면 OOM Killer 가 프로세스를 죽인다. 갑작스럽지만 원인은 명확하다. 스왑이 켜져 있으면 대신 스래싱이 시작된다. 프로세스는 살아 있는데 응답이 수십 배 느려지고, CPU 사용률은 낮은데 iowait 만 치솟는다. 죽는 편이 차라리 진단하기 쉽다는 말이 나오는 이유다. 쿠버네티스가 오랫동안 스왑을 끄도록 요구했던 배경에도 이 예측 불가능성이 있다.

이어지는 퀴즈에서 확인할 것

페이지 폴트 비용 계산을 직접 할 수 있는지, 스래싱이 왜 스스로를 강화하는 악순환인지 설명할 수 있는지 확인한다.