글 목록으로 돌아가기

Jungle / WIL(Weekly I Learned)

[WIL] 12주

12주차는 Pintos Project 3의 기반이 되는 가상메모리, 페이징, 페이지 테이블, 페이지 폴트, 세그멘테이션, Copy-on-write를 정리하며 프로세스가 보는 주소 공간과 실제 메모리가 어떻게 연결되는지 학습한 한 주였다.

임재환
임재환 2026년 5월 21일 · 7분 읽기 · 수정 2026년 5월 21일
[WIL] 12주

12주차 주간 회고: 가상메모리와 주소 변환을 다시 보는 한 주

12주차에는 Pintos의 다음 단계로 넘어가기 위해 가상메모리와 페이징을 다시 정리했다. 이전까지는 스레드, 시스템 콜, 파일 디스크립터, 프로세스 생명주기처럼 커널이 제공하는 기능을 중심으로 봤다면, 이번 주에는 프로세스가 바라보는 주소 공간과 실제 RAM 사이에 운영체제가 어떤 층을 끼워 넣는지를 집중해서 봤다.

핵심 흐름은 가상메모리 → 페이징 → 페이지 테이블 → 페이지 폴트 → 세그멘테이션 → Copy-on-write로 이어졌다. 처음에는 각각의 개념이 따로 떨어져 보였지만, 결국 모두 “프로세스에게는 독립적이고 연속적인 메모리처럼 보이게 하면서, 실제 물리 메모리는 운영체제가 유연하게 관리한다”는 하나의 목표로 묶여 있었다.

Pintos 가상 메모리 맵
Pintos의 메모리 맵을 보며 유저 영역과 커널 영역, 가상 주소 공간의 경계를 다시 확인했다

1. 가상메모리의 큰 그림

가상메모리는 물리적 메모리, 즉 RAM이 부족할 때 보조기억장치의 일부를 메모리처럼 사용할 수 있게 해주는 기술이다. 더 넓게 보면, 물리 메모리 크기와 배치에 상관없이 각 프로세스에게 독립적인 메모리 공간을 제공하는 운영체제의 핵심 추상화다.

예를 들어 프로세스 A와 B는 모두 자신이 0x0000부터 0xFFFF까지의 주소 공간 전체를 쓰고 있다고 생각할 수 있다.

프로세스 A -> "나는 0x0000~0xFFFF 전체를 쓴다"
프로세스 B -> "나도 0x0000~0xFFFF 전체를 쓴다"
실제 RAM    -> OS가 필요한 부분만 쪼개서 각 프로세스에 매핑한다

프로세스 입장에서는 주소 공간이 자기 것처럼 보이지만, 실제로는 운영체제가 페이지 테이블을 통해 가상 주소를 물리 주소로 바꿔준다. 이 구조 덕분에 프로세스끼리 서로의 메모리를 함부로 침범하지 못하고, RAM이 부족할 때는 일부 데이터를 디스크로 내보내는 것도 가능해진다.

이번 주에 가장 먼저 잡아야 했던 감각은 “가상 주소는 진짜 물리 위치가 아니라, 운영체제가 해석해 주는 이름표”라는 점이었다.

2. 페이징과 외부 단편화

페이징은 메모리를 고정 크기의 작은 조각으로 나누어 관리하는 방식이다. 가상 메모리의 조각은 페이지, 물리 메모리의 조각은 프레임이라고 부른다. RAM이 부족하면 당장 쓰지 않는 페이지를 디스크의 스왑 영역으로 내보내고, 필요할 때 다시 불러올 수 있다.

페이징 구조
페이징은 가상 주소 공간의 페이지를 물리 메모리의 프레임에 매핑한다

페이징의 중요한 장점은 외부 단편화를 해결할 수 있다는 것이다. 세그멘테이션처럼 연속된 큰 공간을 요구하는 방식에서는 전체 여유 공간이 충분해도, 그 공간이 여러 조각으로 흩어져 있으면 새 프로세스를 올리지 못할 수 있다.

처음 상태
[  A(100KB)  ][  B(200KB)  ][  C(150KB)  ]

B 해제 후
[  A(100KB)  ][   빈공간    ][  C(150KB)  ]
               (200KB)

새로운 프로세스 D(250KB) 등장
-> 빈 공간이 200KB밖에 없으니 들어갈 수 없다
-> 전체 여유 공간은 충분해도 연속된 공간이 부족하다

반면 페이징은 연속된 물리 공간을 요구하지 않는다. 프로세스 A가 12KB를 필요로 한다면 4KB 프레임 3개만 있으면 된다. 그 프레임들이 물리적으로 떨어져 있어도 페이지 테이블이 연결해 주기 때문에, 프로세스 입장에서는 연속된 메모리처럼 보인다.

프로세스 A가 12KB 필요
-> 4KB 프레임 3개면 충분하다
-> 3개가 물리적으로 떨어져 있어도 괜찮다

물리 메모리:
[프레임0: A의 1번째][프레임1: 비어있음][프레임2: A의 2번째][프레임3: A의 3번째]

결국 페이지 테이블이 떨어진 조각들을 연결해 주기 때문에, 프로세스는 실제 배치를 몰라도 된다. 이 부분이 페이징의 가장 큰 힘이었다.

3. 페이징의 주소 변환

페이징에서 가상 주소는 보통 페이지 번호와 오프셋으로 나뉜다.

가상 주소 = [페이지 번호 | 오프셋]

주소 변환은 다음 순서로 일어난다.

  1. CPU가 가상 주소를 만든다.
  2. 페이지 번호로 페이지 테이블을 조회한다.
  3. 페이지 테이블에서 프레임 번호를 얻는다.
  4. 프레임 번호와 오프셋을 합쳐 물리 주소를 만든다.
가상 주소 = [페이지 번호 | 오프셋]
페이지 테이블 조회 = 페이지 번호 -> 프레임 번호
물리 주소 = 프레임 번호 + 오프셋

여기서 오프셋은 페이지 안에서의 위치다. 가상 페이지가 어떤 물리 프레임에 올라가든, 그 페이지 내부에서 몇 번째 바이트인지는 그대로 유지된다. 그래서 주소 변환은 “큰 조각의 번호는 바꾸고, 조각 내부 위치는 유지하는 작업”으로 이해할 수 있었다.

4. 페이지 테이블

페이지 테이블은 가상 메모리 주소와 물리 메모리 주소의 매핑 정보를 담는 테이블이다. 단순히 페이지 번호와 프레임 번호만 저장하는 것이 아니라, 그 페이지가 현재 메모리에 있는지, 접근된 적이 있는지, 수정되었는지, 어떤 권한을 갖는지도 함께 관리한다.

페이지 테이블 구조
페이지 테이블 엔트리는 프레임 번호뿐 아니라 접근, 변경, 유효, 권한 비트 같은 상태 정보도 함께 가진다

페이지 테이블에서 중요한 정보는 다음과 같다.

항목 의미
페이지 번호 가상 주소에서 어떤 페이지인지 나타내는 값
프레임 번호 실제 물리 메모리의 어느 프레임에 올라가 있는지 나타내는 값
접근 비트 페이지가 메모리에 올라온 뒤 사용된 적이 있는지 알려주는 비트
변경 비트 페이지 내용이 수정되었는지 알려주는 비트
유효 비트 페이지가 현재 물리 메모리에 있는지 나타내는 비트
읽기/쓰기/실행 비트 페이지에 대한 접근 권한을 나타내는 비트

이번 주에 정리하면서 페이지 테이블은 단순한 주소 변환표가 아니라, 운영체제가 메모리 정책을 수행하기 위한 판단 근거라는 점이 더 중요하게 느껴졌다. 어떤 페이지를 쫓아낼지, 디스크에 다시 써야 하는지, 접근을 허용할지 같은 판단이 이 비트들에 걸려 있다.

5. 페이지 폴트

페이지 폴트는 프로세스가 접근하려는 페이지가 현재 RAM에 없을 때 발생한다. 프로세스의 가상 주소 공간에는 존재하지만, 실제 물리 메모리에는 아직 올라와 있지 않은 데이터를 읽거나 쓰려고 할 때 생기는 문제다.

페이지 폴트 처리 흐름
페이지 폴트는 실패가 아니라, 필요한 페이지를 뒤늦게 메모리에 올리는 계기가 될 수 있다

페이지 폴트가 발생하면 운영체제는 먼저 이것이 정상적으로 처리 가능한 접근인지 확인해야 한다. 예를 들어 스왑 영역에 내려가 있던 페이지를 다시 가져오면 되는 상황이라면, 빈 프레임을 확보하고 데이터를 읽어 온 뒤 페이지 테이블을 갱신하면 된다.

하지만 잘못된 주소에 접근했거나 권한이 없는 페이지에 접근했다면 이야기가 달라진다. 이 경우는 페이지를 가져오는 문제가 아니라 잘못된 메모리 접근이므로, 해당 프로세스를 종료해야 한다.

정리하면 페이지 폴트 처리는 다음 두 갈래로 나뉜다.

상황 처리
유효한 접근이지만 페이지가 RAM에 없음 디스크나 파일에서 페이지를 가져와 매핑
잘못된 주소 또는 권한 위반 프로세스 종료

Pintos Project 3에서는 이 구분이 중요해질 것 같다. 모든 page fault를 똑같이 보면 안 되고, “복구 가능한 fault인가”와 “프로세스를 죽여야 하는 fault인가”를 나눠야 한다.

6. 세그멘테이션

세그멘테이션은 프로그램을 논리적으로 의미 있는 덩어리로 나누는 방식이다. 하나의 프로세스는 보통 코드, 데이터, 힙, 스택 같은 영역으로 나눠 볼 수 있다.

하나의 프로세스
├── 코드 세그먼트    (실행할 명령어들)
├── 데이터 세그먼트  (전역 변수)
├── 힙 세그먼트      (동적 할당 메모리)
└── 스택 세그먼트    (함수 호출, 지역 변수)

세그멘테이션의 주소 변환은 세그먼트 번호와 오프셋을 기준으로 이뤄진다.

가상 주소 = [세그먼트 번호 | 오프셋]
세그먼트 테이블 조회 = 세그먼트 번호 -> 베이스 주소 + 한계값
물리 주소 = 베이스 주소 + 오프셋

이 방식은 논리적 구조를 표현하기 좋지만, 각 세그먼트가 서로 다른 크기를 갖기 때문에 외부 단편화가 생길 수 있다.

총 1000KB
[A 200KB][B 300KB][C 150KB][빈공간 350KB]

프로세스 B 종료
[A 200KB][빈공 300KB][C 150KB][빈공간 350KB]

프로세스 D(250KB) 할당
[A 200KB][D 250KB][빈공 50KB][C 150KB][빈공간 350KB]

프로세스 E(350KB) 요청
-> 전체 빈 공간은 400KB지만 연속된 350KB 공간이 없으면 배치가 어렵다

세그멘테이션과 페이징을 비교하면서, “논리적으로 나누기 좋은 단위”와 “물리 메모리를 효율적으로 관리하기 좋은 단위”가 다를 수 있다는 점을 배웠다.

7. Copy-on-write

Copy-on-write는 처음부터 모든 메모리를 복사하지 않고, 실제로 쓰기가 발생할 때 복사하는 전략이다. 특히 fork()처럼 부모 프로세스의 주소 공간을 자식에게 복제해야 하는 상황에서 중요하다.

일반적인 복사 방식이라면 fork() 시점에 부모의 모든 페이지를 자식에게 복사해야 한다. 하지만 실제로는 fork() 직후 exec()를 호출하는 경우도 많고, 부모와 자식이 모든 페이지를 전부 수정하지 않을 수도 있다. 이때 처음부터 전체를 복사하면 비용이 크다.

Copy-on-write는 다음 흐름으로 동작한다.

  1. 부모와 자식이 같은 물리 프레임을 공유한다.
  2. 공유 중인 페이지는 읽기 전용으로 표시한다.
  3. 둘 중 하나가 해당 페이지에 쓰려고 하면 page fault가 발생한다.
  4. 운영체제가 새 프레임을 할당하고 기존 내용을 복사한다.
  5. 쓰기를 시도한 프로세스의 페이지 테이블만 새 프레임을 가리키게 바꾼다.
Copy-on-write 1단계
처음에는 부모와 자식이 같은 프레임을 공유할 수 있다
Copy-on-write 2단계
공유 페이지는 쓰기 시점까지 실제 복사를 미룬다
Copy-on-write 3단계
쓰기 요청이 들어오면 page fault를 계기로 새 프레임을 준비한다
Copy-on-write 4단계
수정하는 프로세스만 새 프레임을 바라보게 하여 불필요한 복사를 줄인다

이 구조를 보면서 page fault가 항상 “에러”만은 아니라는 점을 다시 느꼈다. 어떤 page fault는 운영체제가 lazy하게 일을 처리하기 위한 신호가 된다. Copy-on-write에서는 쓰기 권한 위반처럼 보이는 fault를 이용해, 그 순간에 진짜 복사를 수행한다.

이번 주에 배운 점

이번 주의 가장 큰 배움은 가상메모리가 단순히 “RAM이 부족할 때 디스크를 쓰는 기술”만은 아니라는 점이다. 가상메모리는 프로세스에게 독립적인 주소 공간을 제공하고, 메모리 보호를 가능하게 하며, 실제 물리 메모리를 훨씬 유연하게 관리하게 해주는 운영체제의 핵심 추상화다.

두 번째 배움은 페이지 테이블을 주소 변환표로만 보면 부족하다는 점이다. 페이지 테이블 엔트리 안의 접근 비트, 변경 비트, 유효 비트, 권한 비트는 모두 운영체제가 메모리 정책을 수행하기 위한 정보다. 앞으로 Pintos에서 page fault handler나 frame table을 구현할 때 이 비트들이 실제 판단의 근거가 될 것이다.

세 번째는 page fault를 더 넓게 봐야 한다는 점이다. 잘못된 접근으로 프로세스를 죽여야 하는 fault도 있지만, 스왑된 페이지를 다시 가져오거나 Copy-on-write를 수행하기 위한 정상적인 fault도 있다. 결국 중요한 것은 fault가 발생했다는 사실 자체보다, 그 fault가 어떤 의미인지 해석하는 일이다.

아쉬웠던 점과 다음 액션

아쉬운 점은 아직 가상 주소, 페이지 테이블, 프레임, 스왑, 파일 매핑이 실제 코드에서 어떤 구조체로 연결되는지 완전히 손에 잡히지는 않는다는 것이다. 개념은 정리했지만, Pintos Project 3의 구현으로 들어가면 supplemental page table, frame table, swap table 같은 구체적인 자료구조와 연결해야 한다.

다음 액션은 세 가지다.

첫째, Pintos의 vm/ 디렉터리와 page fault handler 흐름을 읽으면서 가상 주소 하나가 어떤 자료구조들을 거쳐 물리 프레임에 도달하는지 추적한다. 둘째, 페이지 폴트가 발생했을 때 “유효한 lazy load인지, stack growth인지, 잘못된 접근인지”를 구분하는 기준을 표로 정리한다. 셋째, Copy-on-write가 실제로 구현된다면 어떤 비트와 reference count가 필요한지 손으로 그려본다.

12주차는 메모리를 “주소값”이 아니라 “운영체제가 관리하는 매핑과 정책”으로 다시 보기 시작한 주였다. 다음 주에는 이 개념들을 Pintos 코드 안에서 실제 자료구조와 함수 흐름으로 연결해 봐야겠다.