본문 바로가기
CS/OS

운영체제(OS)-WEEK5 (2) [페이징(Paging)]+WEEK6 (1)

by assidu 2026. 4. 3.

세그먼테이션(Segmentation)'이 가진 치명적인 단점(외부 단편화)을 해결하기 위해 등장한 페이징(Paging) 기법과, 그로 인해 새롭게 발생한 문제들을 하드웨어(TLB)로 극복하는 흥미진진한 과정이 담겨 있습니다.

 

 

세그먼테이션은 조각의 크기가 제각각(variable size)이라서 빈 공간이 지저분하게 남는 '외부 단편화'가 문제였습니다. 이를 해결하기 위한 아이디어가 바로 페이징입니다.

 

 

Phase 1: 페이징의 등장 배경과 기본 개념 (Pages 2-6)

 

세그먼테이션(Segmentation)은 조각의 크기가 제각각(variable size)이라서 물리 메모리 중간중간에 쓸 수 없는 작은 빈 공간들이 생기는 **'외부 단편화(External Fragmentation)'**라는 치명적인 문제가 있었습니다.

 

  • 페이징의 아이디어 (Pages 2-4): 이 문제를 해결하기 위해, 가상 주소 공간을 동일하고 고정된 크기인 **'페이지(Page)'**로 쪼개고, 물리 메모리 역시 똑같은 크기의 **'페이지 프레임(Page Frame)'**으로 쪼갭니다. 조각의 크기가 모두 같으므로 외부 단편화가 아예 사라집니다. 이를 위해 가상 페이지를 물리 프레임으로 연결해 주는 '페이지 테이블(Page Table)'이 필요해진다.페이징(Paging)은 현대 운영체제 메모리 관리의 핵심입니다. 이전 장에서 배운 세그먼테이션의 한계를 어떻게 극복하고, 또 어떤 새로운 과제를 던져주었는지 6개의 핵심 국면으로 나눠서 설명하겠습니다.

프로세스마다 페이지 테이블을 가짐, 페이지 수는 테이블에서도 동일, 가상 페이지를 물리 프레임으로 바꿔

  • 페이지 테이블 (Pages 3-4): 가상 메모리의 N번 페이지가 물리 메모리의 몇 번 프레임에 들어있는지 기록해 두는 장부가 필요한데, 이를 **페이지 테이블(Page Table)**이라고 부르며 각 프로세스마다 하나씩 가지고 있습니다. * 장점 (Page 6): 빈 공간 관리가 매우 단순해집니다. 힙(Heap)과 스택(Stack)이 어느 방향으로 자라든 신경 쓸 필요 없이, 빈 프레임이 있으면 그냥 쏙 집어넣으면 되기 때문입니다.

 

 

페이징 예시 (숫자 유심히 보기****)  물리 메모리 128바이트고 16바이트 간격으로 페이지 프레임 나눠짐.

가상 주소에서도 16바이트 간격으로 페이지가 나눠져있다. (가상공간에서 2^6이므로 6비트 표현, 페이지 크기 16바이트, offset 위해 하위 4bit 필요, 상위 2비트는 VPN(Virtual Page Number)이 됩니다.(2비트이므로 4개의 가상 페이지 (Page 0~3)를 가질 수 있다. 물리 메모리에서 위치 알려면 128=2^7 이므로 7bit 필요 (이 때 핵심은 "주소 변환 시 오프셋(Offset)은 절대 변하지 않는다" 라는 것) 따라서 7비트 중 하위 4비트가 그대로 오프셋을 나타내게 됩니다. 나머지 상위 3비트는 PFN(Page Frame Number)이 됩니다. 3비트이므로 8개의 페이지 프레임(Frame 0~7)을 가질 수 있습니다.

 

위 사진으로 설명하면 CPU가 6비트짜리 가상 주소 [VPN 2비트 | 오프셋 4비트] 를 만들어내면, 하드웨어(MMU)는 앞의 2비트(VPN)만 떼어내서 페이지 테이블을 뒤집니다. 그리고 찾아낸 3비트짜리 실제 프레임 번호(PFN)를 가져와서, 원래 있던 4비트짜리 오프셋을 그 뒤에 그대로 갖다 붙여서 최종적으로 7비트짜리 물리 주소 [PFN 3비트 | 오프셋 4비트] 를 완성하는 것입니다.

 

페이징 장점: 주소 공간 추상화를 효율적으로 지원, 빈 공간 관리 쉽다(주소 공간 페이지 크기 = 페이지 프레임 크기)

 

 

Phase 2: 페이징의 주소 변환 과정 (Pages 7-10)

그렇다면 가상 주소를 물리 주소로 어떻게 변환할까요? 세그먼테이션 때 '오프셋'을 계산했던 것처럼, 페이징도 주소를 두 부분으로 나눕니다.

가상 메모리 <VPN, Offset> 물리 메모리 <PFN, Offset> / 페이지마다 PTE 한 개 보유

페이징

 

 

  • 가상 주소의 구조 (Pages 7, 9): CPU가 발생시키는 가상 주소는 앞부분의 **VPN (Virtual Page Number, 가상 페이지 번호)**과 뒷부분의 **Offset (오프셋)**으로 나뉩니다. (사실 당연한거긴하네..이진수를 생각해보면 맨 앞 비트 떼내서 보면 페이지 번호인게)  위 사진에서 p(page)는 페이지 테이블을 거쳐 f(frame)으로, d는 offeset으로 그대로 전달된다.

 

 

 

 

가상 주소에 A,F,E를 보고 16진수 표현인걸 알아채기, 한 칸이 4bit짜리임(4bit * 8칸 = 32bits)

  • 변환 원리 (Pages 8-10): 하드웨어(MMU)는 VPN을 '인덱스'로 삼아 페이지 테이블을 뒤집니다. "아하, 가상 1번 페이지(VPN)는 물리 7번 프레임(PFN)에 있구나!" 하고 찾아낸 뒤, VPN을 PFN(Page Frame Number)으로 싹 바꿔치기합니다. 이때 페이지 안에서의 정확한 위치를 나타내는 Offset은 변하지 않고 그대로 뒤에 붙습니다. * 계산 예시 (Page 10): 32비트 가상 주소 체계에서 페이지 크기가 4KB라면, 4KB(2^{12})를 표현하기 위해 하위 12비트는 Offset이 되고, 나머지 상위 20비트가 VPN이 됩니다. (1KB = 2^10바이트->10비트, 1MB = 2^20바이트->20비트 1GB= 2^30바이트->30비트) (페이지 크기만큼 Offset 공간있어야함)
  • Offset이 의미하는 비트수는 바이트를 비트로 환산한게 아니라, "서로 다른 위치를 가리키기 위해" 필요한 자릿수입니다.

몇 비트 필요한지, 어디부터 어디까지 offset인지 바로 알아내기 ************


Phase 3: 페이지 테이블의 구조와 치명적 한계 (Pages 11-21)

페이징은 완벽해 보였지만, 곧바로 두 가지 거대한 벽에 부딪힙니다.

  • 첫 번째 벽: 엄청난 메모리 차지 (Pages 11-15): 페이지 크기가 작을수록 페이지의 개수는 기하급수적으로 늘어납니다.(잘게 쪼갤수록 개수는 많아짐, 비트로 보면 offset이 작아지고 VPN비트가 커지는 것..) 앞선 예시(20비트 VPN)의 경우 한 프로세스당 2^{20}개(약 100만 개)의 페이지 테이블 엔트리(PTE)가 필요, 당연하게 페이지 수도 2^(20)개 필요, PTE 하나가 4바이트라면 프로세스 하나당 무려 4MB의 페이지 테이블을 물리 메모리에 둬야 합니다.

  • PTE의 구조 (Pages 14-15): 이 PTE 안에는 단순히 PFN(페이지 프레임 넘버)만 있는 게 아니라, Valid(유효성), Protection(읽기/쓰기 권한), Present(현재 메모리에 있는지 여부), Dirty(수정되었는지) 등의 중요한 상태 비트(Flags)들이 꽉꽉 채워져 있습니다.

 

 

유효성 비트 : 가상 공간 사용되고 있는지 아닌지 알 수 있게 해주는 bit

Protection bit : 페이지가 읽어질 수 있는지, 작성될 수 있는지 또는 실행될 수 있는지 나타냄

Present bit : 해당 페이지가 물리 메모리나 디스크(스토리지)에 존재하는 건지 나타내는 비트

Dirty bit: 페이지가 변경(업데이트)되면 표시해주는 비트

Reference bit(accessed bit): 얼마나 접근되었는지 나타낸다.

 

 

 

 

 

  • 두 번째 벽: 너무 느린 속도 (Pages 16-21): 이게 제일 심각한 문제입니다. 페이지 테이블이 메모리에 있다 보니, CPU가 변수 하나를 읽으려 할 때마다 1) 페이지 테이블을 읽으러 메모리에 한 번 가고, 2) 실제 데이터를 읽으러 메모리에 또 가는 현상이 발생합니다. 교안 19페이지의 그림을 보면, 단순한 배열 읽기 작업조차 메모리 접근 횟수가 2배로 폭증하는 것을 볼 수 있습니다.
  • 단순히 "다른 주소를 찾아가서" 느린 것이 아니라, **"속도가 느린 물리 메모리(RAM)를 두 번이나 들러야 하기 때문"**에 느린 것입니다.

 

메모리 참조가 2번 일어나는 진짜 과정 (교안 17페이지)

CPU 손안에 '가상 주소 100번지'라는 숫자가 쥐어진 상태에서 페이징 변환이 어떻게 일어나는지 흐름을 보겠습니다.

페이징으로 메모리 접근하는 방식
더보기

1. SHIFT 값은 어떻게 아나요?

  • 공식: SHIFT = 오프셋(Offset)을 표현하는 데 필요한 비트 수
  • 원리: 가상 주소는 [VPN | Offset] 구조로 되어 있습니다. VPN 부분만 온전히 정수로 빼내려면, 뒤에 붙어있는 Offset의 길이만큼 오른쪽으로 비트를 밀어서(Shift) 버려야 합니다.
  • 예시 (교안 10페이지 기준):
    • 페이지 크기가 4KB라고 가정해 보겠습니다. 4KB = 4096Bytes = 2^{12}Bytes입니다.
    • 4KB 공간 안에서 특정 바이트 위치를 가리키려면 12비트의 오프셋이 필요합니다.
    • 따라서 이 시스템에서 하드웨어에 설정된 SHIFT 값은 12가 됩니다. (가상 주소를 오른쪽으로 12칸 밀어냄)

① VPN 추출 (가상 주소 분해)

 
VPN = (VirtualAddress & VPN_MASK) >> SHIFT

 

가상 주소에서 순수한 VPN 인덱스만 추출합니다. (SHIFT는 OFFSET만큼)

 

② PTE 주소 계산 (지도에서 찾을 위치 계산)

 
PTEAddr = PTBR + (VPN * sizeof(PTE))
  • PTBR (Page Table Base Register): 메모리 상에 페이지 테이블이 시작하는 물리적 기준 주소입니다.
  • PTBR에 VPN 인덱스 * PTE 크기를 더해, 페이지 테이블 안에서 내가 원하는 PTE의 정확한 메모리 주소를 계산합니다. (배열 인덱스 계산과 동일)

③ PTE 가져오기 (첫 번째 메모리 접근!)

 
PTE = AccessMemory(PTEAddr)
  • 위에서 계산한 주소로 물리 메모리에 직접 가서 PTE를 읽어옵니다. (페이징이 느린 원인인 첫 번째 램(RAM) 접근)

④ 유효성 및 권한 검사 (Protection)

 
if (PTE.Valid == False)
    RaiseException(SEGMENTATION_FAULT)
else if (CanAccess(PTE.ProtectBits) == False)
    RaiseException(PROTECTION_FAULT)
  • 가져온 PTE의 제어 비트들을 확인합니다. 할당되지 않은 메모리인지(Valid), 혹은 읽기 전용인데 쓰기를 시도했는지(ProtectBits) 하드웨어가 검사하여 차단합니다.

⑤ 최종 물리 주소 완성 및 데이터 접근 (두 번째 메모리 접근!)

 
else
    offset = VirtualAddress & OFFSET_MASK
    PhysAddr = (PTE.PFN << PFN_SHIFT) | offset
    Register = AccessMemory(PhysAddr)
  • offset = ...: 가상 주소에서 뒤쪽의 오프셋 부분만 추출합니다.
  • PhysAddr = ...: PTE에서 알아낸 실제 프레임 번호(PFN)를 이번엔 반대로 왼쪽으로 밀어 올리고(<< PFN_SHIFT), 빈자리에 offset을 채워 넣어서(|) 최종 물리 주소를 완성합니다.
  • AccessMemory(PhysAddr): 완성된 주소를 가지고 두 번째 메모리 방문을 하여 진짜 원하는 데이터를 레지스터로 가져옵니다.

정확히 비트 연산의 원리를 파악하셨습니다! SHIFT = 오프셋 비트 수 = log_2{페이지 크기}

 

 

페이징이 느린 이유 추가 설명]

더보기

## 1. 근본적인 원인: CPU와 메모리(RAM)의 속도 차이

컴퓨터 시스템에서 CPU의 연산 속도는 빛처럼 빠른 반면, 물리 메모리(RAM)의 데이터를 읽고 쓰는 속도는 상대적으로 매우 느립니다.

  • 이전 방식(Base and Bounds): 주소 변환에 필요한 정보(Base, Bound 값)가 CPU 내부의 레지스터에 있었습니다. 레지스터는 CPU와 속도가 같기 때문에 주소 변환이 순식간에 끝났고, 메모리 방문은 실제 데이터를 가져올 때 딱 한 번만 하면 됐습니다.페이징 방식: 페이지 테이블은 덩치가 너무 커서 CPU 내부 레지스터에 다 담을 수 없습니다. 그래서 페이지 테이블 자체를 물리 메모리(RAM)에 저장하게 됩니다.

## 2. 왜 "두 번 접근"이 치명적인가? (Pages 16-17)

CPU가 변수 x의 값을 읽으려고 할 때, 다음과 같은 정직한 두 단계 과정을 거쳐야 합니다.

  1. 첫 번째 메모리 방문 (PTE 읽기): CPU는 먼저 메모리에 있는 페이지 테이블로 찾아갑니다. 거기서 내가 원하는 페이지가 실제 어디(PFN)에 있는지 적힌 PTE(Page Table Entry)를 읽어옵니다. (첫 번째 느린 메모리 접근)
  2. 두 번째 메모리 방문 (실제 데이터 읽기): 이제야 진짜 주소를 알게 된 CPU는 다시 메모리의 해당 위치로 찾아가서 진짜 x의 값을 읽어옵니다. (두 번째 느린 메모리 접근)

결과적으로, 모든 메모리 참조 명령어가 실행될 때마다 운영체제는 추가적인 메모리 참조를 한 번 더 수행해야만 합니다. 즉, 이론적으로 프로그램의 실행 속도가 2배나 느려지게 되는 셈이죠.

 

## 3. 주소의 거리보다는 '횟수'의 문제 (Pages 19-20)

승규님이 말씀하신 "계속 다른 주소를 찾아간다"는 점도 일리가 있습니다. 교안 19~20페이지의 메모리 트레이스(Trace) 그림을 보면, 하나의 명령어를 처리하기 위해 페이지 테이블 주소(Page Table PA)와 실제 데이터 주소(Array PA) 사이를 계속 왔다 갔다 하는 것을 볼 수 있습니다.

 

하지만 더 큰 문제는 주소 간의 거리가 멀어서가 아니라, CPU가 데이터를 얻기 위해 '기다려야 하는 시간'이 두 배로 늘어난다는 점입니다.

💡 요약하자면

페이징이 느린 이유는 **"지도는 메모리에 있고, 목적지도 메모리에 있기 때문"**입니다. 목적지에 가기 위해 매번 메모리에 있는 지도를 먼저 펼쳐봐야 하니 느려지는 것이죠.

이 끔찍한 성능 저하를 해결하기 위해 하드웨어가 내놓은 해결책이 바로 다음 섹션에 나오는 **TLB(Translation Lookaside Buffer)**입니다. TLB는 이 '지도(페이지 테이블)' 중 자주 보는 부분만 CPU 옆에 미리 복사해두는 아주 빠른 캐시 메모리입니다.

 

메모리 참조가 2번 일어나는 진짜 과정 (교안 17페이지)

CPU 손안에 '가상 주소 100번지'라는 숫자가 쥐어진 상태에서 페이징 변환이 어떻게 일어나는지 흐름을 보겠습니다.

 
  • [메모리 참조 0회] 가상 주소 발급: CPU가 "가상 주소 100번지"를 만들어냅니다. (이것은 CPU 내부에서 일어나는 일이므로 메모리 참조가 아닙니다.)
  • [메모리 참조 1회] 페이지 테이블 읽기: CPU 안의 MMU는 100번지가 진짜 물리 메모리 어디에 있는지 모릅니다. 그래서 물리 메모리(RAM)에 저장되어 있는 '페이지 테이블'을 읽으러 갑니다. 여기서 첫 번째 진짜 메모리 참조가 발생하여 가상 주소를 물리 주소로 번역해 옵니다.
  •  
  • [메모리 참조 2회] 실제 데이터 읽기: 번역 결과 "아하! 진짜 주소는 5000번지구나!"라는 것을 알아냈습니다. 이제 진짜 데이터를 가져오기 위해 물리 메모리 5000번지로 다시 달려갑니다. 여기서 두 번째 진짜 메모리 참조가 발생합니다.

 

 

1. 그래프 구조 이해하기

  • X축 (Memory Access): 시간의 흐름(메모리 접근 순서)을 나타냅니다.
  • Y축 (3개의 층): 컴퓨터가 접근하는 메모리의 구역을 세 층으로 나누어 보여줍니다.
    • 맨 위 (Page Table): 주소를 번역하기 위해 들르는 '페이지 테이블 (지도)'
    • 중간 (Array): 프로그램이 조작하려는 '배열 데이터'
    • 맨 아래 (Code): CPU가 실행할 '명령어 (어셈블리 코드)'
    • 참고: Y축 안쪽 숫자는 가상 주소(VA), 바깥쪽 숫자는 물리 주소(PA)입니다.

2. 빨간 박스 내부의 상세 동작 (두 번째 이미지 기준)

교안 18페이지에 있던 array[i] = 0; 루프문의 첫 번째 반복이 실행되는 과정입니다. 점이 찍히는 순서를 따라가 보세요.

① 명령어 가져오기 (mov 명령어)

  • CPU가 가상 주소 1024번지에 있는 mov 명령어를 실행하고 싶습니다.
  • [1차 방문 - 회색 점] 바로 가지 못하고 맨 위 **Page Table[1]**로 가서 진짜 물리 주소를 번역해 옵니다. (1024 -> 4096)
  • [2차 방문 - 검은 점] 번역된 물리 주소 4096번지(맨 아래 Code 영역)로 가서 진짜 명령어를 읽어옵니다.

② 데이터 처리하기 (Array 배열에 접근)

  • 방금 읽어온 mov 명령어가 "가상 주소 40000번지(배열)에 0을 넣어라"입니다.
  • [3차 방문 - 회색 점] 데이터를 쓰기 위해 또 맨 위 **Page Table[39]**로 가서 주소를 번역합니다. (40000 -> 7232)
  • [4차 방문 - 검은 점] 번역된 물리 주소 7232번지(중간 Array 영역)로 가서 데이터를 씁니다.

③ 나머지 명령어 실행 (inc, cmp, jne)

  • 루프를 돌기 위해 변수 i를 1 더하고(inc), 1000과 비교하고(cmp), 처음으로 돌아가는(jne) 나머지 명령어들을 차례대로 가져와야 합니다.
  • 이때도 가상 주소 1028, 102C, 1030을 읽기 위해 매번 맨 위 페이지 테이블(회색 점)을 먼저 들른 후 맨 아래 코드(검은 점)를 읽어옵니다.

💡 위 슬라이드의 핵심 결론

그래프 전체를 넓게 보시면, 맨 아래(명령어)나 중간(배열 데이터)에 검은 점이 찍힐 때마다 항상 그 직전에 맨 위(페이지 테이블)에 회색 점이 짝꿍처럼 찍혀 있는 것을 볼 수 있습니다.

배열에 단순하게 0을 채워 넣는 가벼운 코드를 실행할 뿐인데, 페이지 테이블을 뒤지느라 메모리 접근 횟수가 무식하게 2배로 폭증하는 페이징의 치명적인 단점을 눈으로 확인


Phase 4: 요구 페이징 (Demand Paging)과 페이지 폴트 (Pages 22-26)

속도 문제를 해결하기 전에, 운영체제가 이 수많은 페이지들을 어떻게 물리 메모리에 다 우겨넣는지 살펴봅니다.

  • 요구 페이징(Demand Paging) (Page 22): OS는 모든 가상 페이지를 물리 메모리에 다 올려두지 않습니다. 지금 당장 '요구(Demand)'되는 페이지만 물리 메모리에 올리고, 안 쓰는 페이지는 디스크(하드드라이브)로 쫓아냅니다(Eviction).

 

- 장점 :

I/O 덜 필요로 함.

메모리를 덜 필요로 함.

반응이 더 빨라짐.

프로세스들 더 많이 실행 가능?

>> Major, Minor, Invaild Page Faults explanation

더보기

1. 주요 페이지 폴트 (Major Page Fault / Hard Page Fault)

가장 정석적이고, 운영체제 입장에서 가장 뼈아프고 느린(비용이 큰) 페이지 폴트입니다.

  • 상황: 프로세스가 요청한 페이지가 물리 메모리(RAM)에 아예 없어서, 반드시 하드디스크(디스크 스왑 영역 등)까지 다녀와야 하는 상황입니다.

 

  • 동작 원리:

1. CPU가 PTE를 확인했더니 **Present bit = 0**입니다.3. 디스크의 물리적인 헤드가 움직여 데이터를 찾아 램으로 복사해 옵니다.

2. 물리 메모리에 페이지가 없으므로, OS는 디스크 I/O 작업을 지시합니다.

  • 특징: 디스크 접근은 메모리 접근보다 수백만 배 느리기 때문에, 이 폴트가 발생하면 프로그램은 데이터를 가져올 때까지 꼼짝없이 긴 시간 동안 멈춰서 대기(Block)해야 합니다. 앞서 설명해 드렸던 "하드디스크에서 자고 있는 페이지를 깨워오는 상황"이 바로 이 메이저 폴트입니다.

2. 사소한 페이지 폴트 (Minor Page Fault / Soft Page Fault)

이름 그대로 운영체제 입장에서 아주 다행스럽고 가벼운 페이지 폴트입니다.

  • 상황: 프로그램의 페이지 테이블(PTE)에는 **Present bit = 0**으로 되어 있어서 예외(Fault)가 발생하긴 했는데, 막상 찾아보니 그 데이터가 하드디스크가 아니라 이미 램(RAM) 어딘가에 올라와 있는 상황입니다.
  • 왜 이런 일이 발생할까?
    1. 공유 라이브러리: 승규님의 프로그램이 C언어의 printf 함수를 쓰려고 하는데, 이미 카카오톡이 printf 코드를 램에 올려놓고 쓰고 있었습니다. OS는 디스크에 갈 필요 없이 카카오톡이 올려둔 그 램 주소를 승규님의 PTE에 쓱 연결만 해줍니다.
    2. 페이지 캐시 보존: 램이 꽉 차서 승규님의 페이지를 방금 쫓아내려고(Eviction) 명단에서 지웠는데(Present bit = 0), 아직 디스크로 쫓겨나기 직전에 승규님이 다시 그 페이지를 찾은 경우입니다.
  • 특징: 디스크(하드드라이브)까지 갈 필요 없이 메모리 안에서 주소만 다시 연결해 주면 되기 때문에 속도가 매우 빠릅니다.

3. 유효하지 않은 페이지 폴트 (Invalid Page Fault)

이건 정상적인 데이터 요청이 아니라, 프로그램이 명백한 '불법 접근'을 저질렀을 때 발생하는 치명적인 에러입니다.

  • 상황: 프로그램이 자기가 할당받지도 않은 엉뚱한 메모리 공간(예: NULL 포인터, 또는 다른 프로그램의 공간)을 찌르거나, '읽기 전용' 공간에 '쓰기'를 시도한 상황입니다.
  • 동작 원리: 1. CPU가 PTE를 확인했더니 Valid bit = 0 (할당된 적 없는 공간)이거나, 권한 비트(Protection Bits)가 위반되었습니다.
  • 특징: 이 경우 운영체제는 디스크나 램을 뒤지는 수고를 하지 않습니다. "이 프로세스가 선을 넘었군!"이라고 판단하고, 가차 없이 해당 프로그램을 강제 종료시켜 버립니다. 이때 우리가 C/C++ 프로그래밍을 하면서 가장 끔찍하게 마주하는 Segmentation Fault (core dumped) 에러 메시지가 뜨게 됩니다.

💡 핵심 요약

  • 메이저 폴트: "데이터가 디스크에 있네. (한숨) 다녀올 테니까 한참 기다려!" ->  느림
  • 마이너 폴트: "어? 운 좋게 디스크 안 가도 램에 이미 있네! 주소만 연결해 줄게!" -> 빠름
  • 인밸리드 폴트: "허락 안 한 남의 메모리를 건드려? 넌 사형이다!" -> 강제 종료 (에러)

페이지 폴트 다루는 동작 방식

  • 페이지 폴트(Page Fault) (Pages 23-24): 만약 CPU가 찾고자 하는 페이지가 현재 물리 메모리에 없다면(PTE의 Present 비트가 0이라면), 하드웨어는 OS에게 *'페이지 폴트(Page Fault)라는 SOS 예외를 발생시킵니다. 그러면 OS가 멈춰서 디스크로 달려가 해당 페이지를 물리 메모리의 빈 프레임으로 가져온 뒤 프로그램을 재개합니다.

 

 

  • 정리 (Pages 25-26): 페이징은 외부 단편화가 없고 관리가 쉽지만(Pros), 내부 단편화가 발생할 수 있고, 페이지 테이블 덩치가 너무 크며, 무엇보다 메모리 접근을 2배로 만들어 시스템을 끔찍하게 느리게 만든다는 치명적인 단점(Cons)을 가집니다.

 


 

Phase 5: 속도 문제 해결의 구원자, TLB (Pages 27-38)

메모리를 두 번이나 읽어야 하는 속도 저하를 막기 위해, 주소 변환을 빠르게 하기 위해서 하드웨어 엔지니어들은 CPU(정확히는 MMU) 안에 **TLB (Translation Lookaside Buffer)**라는 특수한 캐시(Cache)를 집어넣습니다.

 

 

 

 

  • TLB의 역할 (Pages 29, 31): TLB는 가장 최근에 변환해 본 'VPN -> PFN' 매핑 정보를 기억해 두는 아주 빠르고 작은 하드웨어 캐시입니다. 

 

 

더보기
  • TLB 안에는 PTE를 통째로 캐시로 저장한다!
  • PTE 안에는 주소(PFN)뿐만 아니라, 이 페이지의 읽기/쓰기 권한(Protection 비트), 문맥 교환을 위한 명찰(ASID), 더러워졌는지 여부(Dirty 비트) 등 중요한 제어 정보가 다 들어있습니다. 만약 TLB에 주소(PFN)만 덜렁 있다면, 권한을 확인하기 위해 결국 램(RAM)에 있는 진짜 PTE를 또 읽으러 가야겠죠? TLB에 PTE 전체를 통째로 넣어두면, 주소 변환과 권한/보안 검사를 메모리 접근 없이 CPU 내부에서 한 방에 끝낼 수 있기 때문입니다.

"Fully Associative (완전 연관)"

이건 TLB라는 캐시 메모리에 데이터를 **'어떻게 배치할 것인가'**에 대한 하드웨어적인 규칙입니다.

 
  • 주차장 비유:
    • Direct Mapped (직접 사상): 차량 번호 끝자리가 3번인 차는 무조건 3번 구역에만 주차해야 하는 규칙입니다. 차를 찾기는 쉽지만, 3번 구역이 꽉 차 있으면 다른 구역이 텅텅 비어있어도 주차를 못 하고 쫓겨납니다.
    • Fully Associative (완전 연관): 주차장에 빈자리가 있으면 규칙 없이 아무 데나 마음대로 주차할 수 있는 방식입니다.
  • TLB에서의 의미: 특정 VPN 정보가 TLB 내부의 16~256개 빈 공간 중 '아무 곳에나' 들어갈 수 있다는 뜻입니다. 빈칸을 알뜰하게 100% 활용할 수 있어서 히트율이 아주 높습니다. 대신 규칙 없이 막 주차해 놨으니 어디 있는지 찾기가 힘들겠죠? 그래서 하드웨어가 TLB 내부의 모든 칸을 동시에(병렬로, in parallel) 쫙 스캔해서 원하는 VPN을 순식간에 찾아냅니다.

"Hash 매핑 방법" (교수님의 찰떡 비유)

교수님께서 'Hash(해시)'라는 단어를 쓰신 이유는, 위에서 말한 하드웨어의 '동시 스캔' 능력이 우리가 프로그래밍에서 쓰는 해시 테이블(Hash Table) 자료구조의 특징과 아주 똑같이 보이기 때문입니다.

  • 일반적인 배열(Array)에서 원하는 값을 찾으려면 0번 방부터 N번 방까지 하나하나 반복문을 돌며 뒤져야 합니다. (시간 복잡도 O(N)
  • 하지만 파이썬의 딕셔너리(dict)나 자바의 HashMap에 Map.get(Key)를 하면, 반복문 없이 원하는 값(Value)이 한 번에 즉시 툭 튀어나옵니다. (시간 복잡도 O(1))
  • 교수님은 TLB의 동작을 이에 빗대어 설명하신 겁니다. CPU가 TLB에 VPN(Key)을 던져주면, Fully Associative 방식의 칩셋이 해시맵처럼 작동해서 모든 방을 순서대로 뒤지는 게 아니라 PTE(Value)를 즉시 한 방에 찾아낸다는 뜻입니다.

결론적으로 교수님의 말씀은 **"TLB는 메모리에 갈 필요 없이 권한 검사까지 한 번에 끝내려고 PTE 전체를 통째로 들고 있으며, 빈 공간 아무 데나 쑤셔 넣어도 하드웨어가 마치 해시맵처럼 한 방에 데이터를 찾아주는 아주 똑똑한 부품이다!"**라는 멋진 요약

 

 

 

  • 동작 방식 (Pages 32-33): CPU가 가상 주소를 던지면, 메모리에 있는 페이지 테이블을 뒤지기 전에 **TLB부터 먼저 확인(Lookup)**합니다.

 

  • 만약 TLB에 정보가 있다면(TLB Hit), 메모리에 갈 필요 없이 곧바로 물리 주소를 만들어 데이터를 가져옵니다. (메모리 접근 1회로 단축!)
  • 만약 없다면(TLB Miss), 어쩔 수 없이 메모리의 페이지 테이블을 뒤져서 PFN을 알아낸 뒤, 그 정보를 TLB에 새로 적어두고 다시 명령을 실행합니다.

 

 

 

  • 왜 성능이 좋아질까? (Pages 34-35): 교안 34페이지의 배열(Array) 예시를 보면, 첫 번째 원소 a[0]를 읽을 때만 TLB Miss가 나고, 같은 페이지 안에 들어있는 a[1], a[2]를 읽을 때는 모두 TLB Hit가 납니다. 프로그램은 한 번 접근한 곳 근처를 계속 접근하려는 성질(공간적 지역성, Spatial Locality)이 있기 때문에 TLB의 적중률은 생각보다 매우 높습니다.

 

1. 시간적 지역성 (Temporal Locality)

"방금 썼던 거, 이따가 또 쓸 확률이 아주 높다!

  • 개념: 어떤 명령어(Instruction)나 데이터에 한 번 접근했다면, 가까운 미래에 그 동일한 위치에 다시 접근할 가능성이 매우 높다는 성질입니다.
     
  • 슬라이드 그림 해석: 1번째 접근했던 곳이 'Page 1'이었는데, 2번째 접근하는 곳도 똑같이 'Page 1'에 머물러 있는 것을 빨간 화살표 하나로 표현하고 있습니다.
  • 코딩 예시: 프로그래밍에서 for 문이나 while 문을 생각해보세요. 루프를 도는 동안 int i 같은 인덱스 변수나 덧셈 결과를 담는 sum 같은 변수들은 매 반복마다 계속해서 읽히고 쓰입니다. 또한, 그 루프를 실행하는 '명령어(Code)' 자체도 루프가 끝날 때까지 CPU가 똑같은 위치를 계속 반복해서 읽어옵니다.

2. 공간적 지역성 (Spatial Locality)

"방금 건드린 곳의 '바로 옆동네'를 곧 갈 확률이 아주 높다!"

  • 개념: 프로그램이 메모리의 특정 주소(x)에 접근했다면, 머지않아 그 주소의 근처(x 근처)에 있는 메모리에도 접근할 가능성이 높다는 성질입니다.
  • 슬라이드 그림 해석: 1번째 접근은 빨간 화살표인 'Page 1'이었는데, 2번째 접근은 파란 화살표인 'Page 2' (혹은 그 근방)로 바로 옆으로 이동하는 모습을 보여줍니다.
  • 코딩 예시: 가장 대표적인 예가 바로 **배열(Array)**입니다. 교안 34페이지의 sum += a[i] 예시를 보면, a[0]을 읽은 다음에는 십중팔구 a[1], a[2], a[3] 순서대로 나란히 붙어있는 데이터를 차례대로 읽게 됩니다.

 

- 교안 적용: TLB 미스(Miss)가 났을 때의 대처법

CPU가 TLB에서 주소를 찾지 못하는 'TLB Miss'가 발생했을 때, 이 두 철학은 완전히 다른 방식으로 문제를 해결합니다.

 

① CISC의 방식: 하드웨어 관리 TLB (Hardware-managed TLB)

 
  • "내가 알아서 할게, OS는 가만히 있어!"
  • CISC 기반의 CPU(하드웨어)는 똑똑하고 복잡한 일을 할 수 있습니다. 그래서 TLB 미스가 나면, CPU가 직접 물리 메모리로 걸어가서(Walk) 페이지 테이블을 뒤집니다.
  • 알맞은 번역 정보(PTE)를 찾아내면 CPU가 스스로 TLB에 채워 넣고 중단되었던 명령어를 다시 실행합니다. 운영체제(OS)는 이 과정에 전혀 개입하지 않습니다.

 

② RISC의 방식: 소프트웨어 관리 TLB (Software-managed TLB)

 
  • "나(CPU)는 단순해서 이런 거 못 찾아. OS야 도와줘!"
  • RISC 기반의 CPU는 스스로 페이지 테이블을 뒤질 능력을 빼버려서 아주 가볍습니다. 대신 TLB 미스가 나면 CPU는 하던 일을 멈추고 예외(Exception/Trap) 알람을 빵 울리며 운영체제를 호출합니다.
  • 알람을 들은 **운영체제(소프트웨어)**의 'Trap Handler' 코드가 실행되어, OS가 직접 메모리의 페이지 테이블을 뒤지고 TLB를 업데이트해 줍니다.


Phase 6: TLB의 문맥 교환 이슈와 관리 기법 (Pages 39-48)

TLB 덕분에 속도는 빨라졌지만, 여러 프로세스가 번갈아 실행되는 '문맥 교환(Context Switch)' 상황에서 새로운 문제가 터집니다.

  • TLB의 딜레마 (Pages 39-42): 프로세스 A와 프로세스 B는 각자의 독립적인 가상 주소 공간을 가지므로, 둘 다 '가상 10번 페이지(VPN 10)'를 가질 수 있습니다. 프로세스 A가 실행되다 B로 넘어갔는데, TLB에 A가 쓰던 매핑 정보가 그대로 남아있다면 B는 엉뚱한 물리 메모리에 접근하게 됩니다.

 

  • 해결책 1 - 비우기 (Page 42): 문맥 교환이 일어날 때마다 TLB를 싹 비워버립니다(Flush). 하지만 이러면 B가 실행될 때마다 무조건 TLB Miss부터 겪어야 하니 비용(Cost)이 너무 큽니다.

 

  • 해결책 2 - ASID 도입 (Page 43): 그래서 TLB 구조를 개선하여, 매핑 정보 옆에 '이 정보가 누구의 것인지' 알려주는 명찰(ASID, Address Space Identifier) 비트를 추가로 달아줍니다. 이렇게 하면 TLB를 비우지 않고도 A와 B의 매핑 정보를 안전하게 구별할 수 있습니다.

 

  • 현대의 TLB (Page 47): 그래서 실제 MIPS R4000 같은 프로세서의 TLB 엔트리를 보면, VPN과 PFN뿐만 아니라 공유 페이지를 위한 Global 비트(G), ASID, 그리고 캐시 일관성과 수정 여부를 나타내는 C, D, V 비트 등 복잡한 제어 정보들이 빼곡하게 들어있는 것을 볼 수 있습니다.

TLB (CPU 내부에 위치): 반면 TLB는 CPU 안에 있는 아주 비싸고 좁은 하드웨어 공간입니다. 내 프로그램이 10만 개의 페이지를 쓰든 100만 개를 쓰든 상관없이, 하드웨어 공장에서 CPU를 찍어낼 때 물리적인 방(엔트리)의 개수가 64칸, 128칸 등으로 아예 고정되어 있습니다. 

TLB 엔트리 1개는 가상 주소 1개를 물리 주소로 바꿔주는 **'번역 정보(PTE) 1세트'**가 들어가는 방 1칸을 의미합니다. (예: VPN 10, PFN 100, Valid 1 등이 적힌 한 줄)


즉, "TLB 엔트리 개수"란 TLB라는 캐시 메모리 안에 번역 정보(PTE)를 꽂아둘 수 있는 물리적인 '빈칸(Slot)'의 총 개수를 말합니다. 수십만 개의 PTE 중 가장 최근에 쓴 64개만 이 빈칸에 들어올 수 있는 것이죠.

TLB Reach: "메모리(RAM)에 있는 느린 페이지 테이블을 뒤지지 않고, CPU가 현재 TLB에 들어있는 정보만으로 한 번에 '초고속'으로 접근할 수 있는 메모리 데이터의 총량"
=> 지금 캐시로 저장된 걸로 바로 접근할 수 있는 메모리 데이터 총량 의미!!! 그러니 TLB 엔트리 개수(저장된 캐시 개수)와 페이지 크기를 곱하는 것!!!

  • 공식: TLB Reach = {TLB 엔트리 개수} * {페이지 크기}

💡 알기 쉬운 지갑(TLB) 비유

  • TLB 엔트리 개수: 내 지갑에 카드를 꽂을 수 있는 빈칸 개수 (딱 64칸으로 고정)
  • 페이지 크기: 카드 1장의 한도액 (예: 4KB 수표 vs 4MB 대형 수표)

결과적으로 5장 페이징 교안은 **"파편화 해결(페이징) ->속도 저하 발생(계속 페이지테이블 갔다가 메모리 접근?? 메모리 두 번 참조??) -> 하드웨어 캐시 도입(TLB) -> 문맥 교환 충돌 발생 -> ASID로 해결"**이라는 운영체제 발전의 아주 전형적이고 아름다운 문제 해결 논리를 보여주고 있습니다.