Virtual Memory: Demand Paging, Page Replacement, and Thrashing

1️⃣ Virtual Memory and Demand Paging
2️⃣ Page Replacement Algorithms and Thrashing
3️⃣ Virtual Memory Operation Observation Practice
1️⃣ Virtual Memory and Demand Paging
가상메모리(Virtual Memory)
이번 주차에서는 메모리 관리(Memory Management) 중 가상메모리(Virtual Memory)를 학습한다. 프로세스(Process)가 실행되려면 메모리가 필요하지만, 실제 물리 메모리(Physical Memory)는 크기가 제한되어 있다. 따라서 모든 프로그램을 한 번에 메모리에 올려 실행하기는 어렵다.
이를 해결하기 위해 운영체제(Operating System)는 가상메모리를 사용한다. 가상메모리는 프로그램 전체가 아니라, 실행에 필요한 부분만 메모리에 올려 사용하는 방식이다. 대표적인 방법이 요구 페이징(Demand Paging)이다. 필요한 페이지(Page)가 메모리에 없을 때는 페이지 부재(Page Fault)가 발생하고, 운영체제가 해당 페이지를 메모리로 가져온다.
또한 메모리가 부족하면 기존 페이지 중 하나를 교체해야 한다. 이를 위해 페이지 대치 알고리즘(Page Replacement Algorithm)이 사용된다. 대표적인 알고리즘으로는 먼저 들어온 페이지를 교체하는 FIFO(First-In, First-Out)와, 가장 오래 사용되지 않은 페이지를 교체하는 LRU(Least Recently Used)가 있다.
페이지 교체가 너무 자주 발생하면 실제 프로그램 실행보다 페이지를 넣고 빼는 데 시간이 더 많이 걸릴 수 있다. 이런 현상을 스래싱(Thrashing)이라고 하며, 시스템 성능을 크게 떨어뜨린다.
이번 주차의 핵심은 가상메모리의 필요성, 요구 페이징, 페이지 부재, 페이지 대치 알고리즘, 스래싱의 흐름을 연결해서 이해하는 것이다.
가상메모리(Virtual Memory)의 필요성 예제
가상메모리(Virtual Memory)의 필요성은 책상과 책장의 예시로 쉽게 이해할 수 있다. 집에서 공부할 때 책상 위에는 지금 당장 사용하는 책이나 노트만 올려두고, 나머지 책들은 책장에 보관해두는 경우가 많다.
만약 모든 책을 한꺼번에 책상 위에 올려둔다면 책상이 금방 복잡해지고, 오히려 필요한 책을 찾거나 사용하는 데 불편함이 생길 수 있다. 책상 공간은 제한되어 있기 때문에, 현재 필요한 자료만 올려두는 것이 더 효율적이다.
컴퓨터의 메모리(Memory)도 이와 비슷하다. 물리 메모리(Physical Memory)의 크기는 제한되어 있기 때문에, 프로그램 전체를 한 번에 메모리에 올리는 것은 비효율적일 수 있다. 그래서 운영체제(Operating System)는 실행에 필요한 부분만 메모리에 올리고, 나머지는 보조기억장치(Secondary Storage)에 두었다가 필요할 때 가져온다.
이처럼 필요한 것만 선택적으로 가져와 사용하는 방식이 가상메모리의 핵심 개념이다. 가상메모리는 제한된 물리 메모리를 더 효율적으로 사용하게 해주며, 여러 프로그램이 동시에 실행될 수 있도록 돕는다.
필요한 데이터 가져오기(Demand Paging)
프로그램은 실행되기 위해 메모리(Memory)에 적재되어야 한다. 하지만 실제 물리 메모리(Physical Memory)보다 더 큰 프로그램을 실행해야 하는 경우도 있다. 이때 프로그램 전체를 한 번에 메모리에 올리는 대신, 실행에 필요한 부분만 선택적으로 올려 사용할 수 있다.
이 방식이 가능하려면, 실행 중 필요한 데이터가 메모리에 있는지 확인해야 한다. 만약 필요한 데이터가 메모리에 올라와 있지 않다면 페이지 부재(Page Fault)가 발생한다. 그러면 운영체제(Operating System)는 해당 데이터를 보조기억장치(Secondary Storage)에서 찾아 메모리로 가져온 뒤, 프로그램 실행을 계속 진행한다.
따라서 이번 내용의 핵심 질문은 두 가지로 정리할 수 있다. 첫 번째는 왜 모든 내용을 한 번에 메모리에 올리지 않는가이고, 두 번째는 필요한 데이터를 어떻게 가져와 사용할 것인가이다. 이 두 질문을 중심으로 가상메모리(Virtual Memory)의 필요성과 동작 방식을 이해해야 한다.
가상메모리(Virtual Memory)의 필요성
이번 차시에서는 가상메모리(Virtual Memory)의 동작을 이해하기 위해, 프로그램을 필요한 부분만 메모리에 올려 실행하는 방식과 실행 중 필요한 데이터를 가져오는 과정을 살펴본다.
프로그램(Program)이 실행되기 위해서는 먼저 메모리(Memory)에 적재되어야 한다. 하지만 여기서 문제가 발생한다. 프로그램의 크기가 점점 커지면서 실제 물리 메모리(Physical Memory)보다 더 큰 프로그램도 실행해야 하는 상황이 생긴다.
또한 컴퓨터에서는 하나의 프로그램만 실행되는 것이 아니라, 여러 프로그램이 동시에 실행되는 경우가 많다. 즉, 메모리의 크기는 제한되어 있는데 메모리를 필요로 하는 프로그램의 수와 크기는 계속 늘어나는 상황이 된다.
이러한 문제를 해결하기 위해 운영체제(Operating System)는 프로그램 전체를 한 번에 메모리에 올리지 않고, 실행에 필요한 부분만 선택적으로 메모리에 올리는 가상메모리 방식을 사용한다. 가상메모리는 제한된 물리 메모리를 효율적으로 사용하고, 여러 프로그램이 동시에 실행될 수 있도록 돕는 중요한 메모리 관리 기법이다.
기존 메모리 적재 방식의 한계(Limitations of Traditional Memory Loading)
기존 방식처럼 프로그램(Program) 전체를 메모리(Memory)에 한 번에 올려 실행한다고 생각해보자. 이 경우 실제로 지금 당장 사용하지 않는 부분까지 함께 메모리에 적재되기 때문에, 불필요하게 메모리 공간을 차지하게 된다.
더 큰 문제는 프로그램의 크기가 물리 메모리(Physical Memory)보다 큰 경우이다. 프로그램 중 일부만 사용하면 실행할 수 있는 상황이라도, 전체를 한 번에 메모리에 올려야 한다면 실행 자체가 불가능해질 수 있다.
결국 문제의 핵심은 필요한 만큼만 사용하면 되는데, 전체를 모두 메모리에 올려야 하는 구조에 있다. 이러한 방식은 메모리 낭비(Memory Waste)를 발생시키고, 큰 프로그램의 실행을 제한한다.
따라서 운영체제(Operating System)는 프로그램 전체가 아니라 실행에 필요한 부분만 선택적으로 메모리에 올리는 방법이 필요하다. 이 문제를 해결하기 위해 등장한 개념이 가상메모리(Virtual Memory)이다.
해결 방안: 가상메모리(Virtual Memory)
기존 메모리 적재 방식의 한계를 해결하기 위한 방법이 가상메모리(Virtual Memory)이다. 가상메모리의 핵심은 프로그램(Program) 전체를 한 번에 메모리(Memory)에 올리는 것이 아니라, 실행에 필요한 부분만 선택적으로 메모리에 올려 사용하는 것이다.
이렇게 하면 지금 당장 사용하지 않는 부분은 굳이 물리 메모리(Physical Memory)를 차지하지 않는다. 그 결과 제한된 메모리 공간을 더 효율적으로 사용할 수 있다.
또한 프로그램의 전체 크기가 실제 메모리보다 크더라도, 필요한 부분을 나누어 메모리에 올리면서 실행할 수 있다. 즉, 가상메모리는 메모리 낭비를 줄이고, 더 큰 프로그램이나 여러 프로그램을 동시에 실행할 수 있도록 도와주는 메모리 관리(Memory Management) 방식이다.
가상메모리(Virtual Memory)의 동작 구조
그림을 보면 각 프로세스(Process)가 요구하는 메모리 크기가 상당히 크다는 것을 알 수 있다. 예를 들어 프로세스 A(Process A)는 4GB의 메모리를 요구하고, 다른 프로세스들도 각각 일정한 크기의 메모리를 필요로 한다.
하지만 실제 물리 메모리(Physical Memory)는 이 모든 프로세스를 동시에 그대로 담기에는 부족한 경우가 많다. 따라서 운영체제(Operating System)는 메모리 관리자(Memory Manager) 역할을 하면서, 각 프로세스를 전체 그대로 메모리에 올리지 않는다.
대신 실행에 필요한 부분만 선택적으로 물리 메모리에 올리고, 당장 사용하지 않는 나머지 부분은 저장장치(Storage), 즉 보조기억장치(Secondary Storage)에 두고 관리한다.
즉, 프로세스가 요구하는 전체 메모리 크기와 실제 물리 메모리에 올라가는 크기는 다를 수 있다. 이 차이를 운영체제가 조정해주는 구조가 바로 **가상메모리(Virtual Memory)**이다.
가상메모리(Virtual Memory)의 개념
가상메모리(Virtual Memory)의 가장 중요한 특징은 프로그램(Program) 전체가 아니라 일부만 메모리(Memory)에 올라간 상태에서도 실행될 수 있다는 점이다. 즉, 지금 당장 필요한 부분만 물리 메모리(Physical Memory)에 올라가고, 나머지 부분은 메모리에 없는 상태로 존재한다.
여기서 메모리에 없다는 것은 데이터가 사라졌다는 의미가 아니다. 당장 사용하지 않는 부분은 저장장치(Storage), 즉 보조기억장치(Secondary Storage)에 유지되어 있다가 필요한 시점에 다시 메모리로 올라온다.
중요한 것은 프로그램이 바라보는 관점과 실제 시스템 내부의 구조가 다르다는 점이다. 프로그램 입장에서는 전체 메모리 공간이 하나의 연속된 공간처럼 보인다. 즉, 프로그램은 자신이 필요한 메모리 전체를 끊김 없이 사용할 수 있다고 인식한다.
하지만 실제 시스템 내부에서는 물리 메모리에 일부만 올라와 있고, 나머지는 저장장치에 나뉘어 저장되어 있다. 운영체제(Operating System)는 이 과정을 관리하면서 필요한 부분을 메모리에 올리고, 필요하지 않은 부분은 저장장치에 두는 방식으로 동작한다.
결국 가상메모리는 프로그램에게는 연속적인 메모리 공간을 제공하는 것처럼 보이게 하면서, 실제 내부에서는 필요한 부분만 나누어 관리하는 메모리 관리(Memory Management) 방식이다.
요구 페이징(Demand Paging)의 개념
앞에서 살펴본 것처럼 가상메모리(Virtual Memory)에서는 프로그램(Program) 전체가 아니라 일부만 메모리(Memory)에 올라간 상태에서도 실행될 수 있다.
그렇다면 메모리에 올라와 있지 않은 나머지 부분은 언제, 어떻게 사용될까?
프로그램은 처음부터 필요한 모든 내용을 메모리에 올리지 않고 실행을 시작한다. 하지만 실행이 진행되다 보면 현재 메모리에 없는 코드(Code)나 데이터(Data)가 필요해질 수 있다. 이때 운영체제(Operating System)는 필요한 부분을 그 시점에 메모리로 가져온다.
이처럼 처음부터 모든 페이지(Page)를 메모리에 올리는 것이 아니라, 실제로 필요해졌을 때 해당 페이지를 메모리에 적재하는 방식을 요구 페이징(Demand Paging)이라고 한다.
즉, 요구 페이징은 “필요할 때 가져온다”는 개념을 바탕으로 한다. 이를 통해 메모리 사용량을 줄이고, 프로그램 실행에 필요한 부분만 효율적으로 관리할 수 있게 된다.
요구 페이징(Demand Paging)의 핵심 특징
요구 페이징(Demand Paging)은 프로그램(Program)을 시작할 때 모든 내용을 미리 메모리(Memory)에 올려두는 방식이 아니다. 실행 중에 실제로 필요한 순간이 되었을 때, 해당 부분을 메모리로 가져와 사용하는 방식이다.
즉, 어떤 페이지(Page)가 필요해지기 전까지는 물리 메모리(Physical Memory)에 올리지 않는다. 그러다가 프로그램이 해당 페이지에 접근하려는 시점에 운영체제(Operating System)가 그 페이지를 메모리로 가져오고, 실행을 계속 이어가게 한다.
이 방식에서는 실행 과정에 따라 필요한 데이터(Data)나 코드(Code)만 선택적으로 메모리에 올라간다. 따라서 프로그램 전체가 항상 메모리에 존재할 필요는 없다.
하지만 프로그램 입장에서는 자신이 사용하는 전체 메모리 공간이 하나의 연속된 공간처럼 보인다. 실제 시스템 내부에서는 일부만 메모리에 있고 나머지는 저장장치(Storage)에 있는 구조이지만, 프로그램은 이 차이를 직접 인식하지 않고 실행을 계속한다.
결국 요구 페이징은 필요한 부분만 필요할 때 메모리에 올려 메모리를 효율적으로 사용하는 가상메모리(Virtual Memory)의 핵심 동작 방식이라고 볼 수 있다.
요구 페이징(Demand Paging)의 동작 과정
요구 페이징(Demand Paging)은 프로그램(Program)이 실행되는 중 특정 주소(Address)에 접근할 때 동작한다. 먼저 운영체제(Operating System)는 해당 주소에 필요한 데이터(Data)가 현재 물리 메모리(Physical Memory)에 올라와 있는지 확인한다.
만약 해당 데이터가 메모리에 존재한다면, 프로그램은 바로 그 데이터를 사용하고 실행을 계속 이어간다. 하지만 필요한 데이터가 메모리에 없다면, 즉시 사용할 수 없기 때문에 운영체제가 저장장치(Storage)에서 해당 데이터를 찾아 물리 메모리로 가져온다.
그 후 운영체제는 원래 실행하려던 명령(Instruction)을 다시 수행하게 하고, 프로그램은 중단되었던 흐름을 이어서 진행한다.
정리하면, 요구 페이징은 프로그램이 특정 주소에 접근했을 때 필요한 데이터가 메모리에 있는지 확인하고, 없을 경우 저장장치에서 가져와 메모리에 적재한 뒤 다시 실행을 이어가는 방식이다.
요구 페이징(Demand Paging)의 동작 관점
요구 페이징(Demand Paging)은 메인 메모리(Main Memory)와 보조기억장치(Secondary Storage) 사이에서 데이터를 주고받으며 동작한다. 그림에서 왼쪽은 메인 메모리, 오른쪽은 보조기억장치를 나타낸다.
프로그램 A(Program A)와 프로그램 B(Program B)가 실행 중이지만, 두 프로그램의 전체 내용이 모두 메모리에 올라와 있는 것은 아니다. 실제로는 실행에 필요한 일부만 메인 메모리에 올라와 있고, 나머지 부분은 보조기억장치에 저장되어 있다.
프로그램 실행이 진행되다가 현재 메모리에 없는 데이터(Data)가 필요해지면, 운영체제(Operating System)는 보조기억장치에 있는 해당 데이터를 메모리로 가져온다. 이때 메모리에 빈 공간이 부족하면 기존에 메모리에 있던 일부 데이터가 보조기억장치로 나가고, 새로 필요한 데이터가 그 자리를 대신 차지한다.
즉, 요구 페이징은 메모리와 보조기억장치 사이에서 필요한 데이터가 들어오고 나가는 교체 과정을 통해 프로그램 실행을 이어가는 방식이다. 이 그림은 요구 페이징이 실제로 이러한 데이터 교체(Page Replacement) 과정을 통해 동작한다는 점을 보여준다.
적재 방식 비교(Loading Method Comparison)
페이지(Page)는 프로그램(Program)을 일정한 크기로 나눈 단위이다. 즉, 프로그램 전체를 하나의 덩어리로 관리하는 것이 아니라 여러 개의 작은 블록(Block)으로 나누어 각각 메모리(Memory)에 올리고 관리하는 구조라고 볼 수 있다.
페이지를 메모리에 올리는 방식은 크게 선적재(Preloading)와 요구 페이징(Demand Paging)으로 비교할 수 있다.
선적재(Preloading)는 프로그램을 실행하기 전에 필요한 페이지를 미리 메모리에 올리는 방식이다. 이 방식은 초기에 많은 페이지를 한꺼번에 올려야 하므로 실행 준비 시간이 길어질 수 있다. 또한 실제로 사용하지 않는 페이지까지 메모리에 포함될 수 있어 메모리 낭비가 발생할 수 있다.
반면 요구 페이징(Demand Paging)은 프로그램 실행 중 필요한 페이지가 생겼을 때 그때그때 메모리에 올리는 방식이다. 필요한 페이지만 선택적으로 가져오기 때문에 초기 적재 부담이 줄어들고, 메모리를 더 효율적으로 사용할 수 있다.
따라서 두 방식의 핵심 차이는 페이지를 언제 메모리에 올리느냐에 있다. 선적재는 실행 전에 미리 올리는 방식이고, 요구 페이징은 실행 중 실제로 필요해진 시점에 올리는 방식이다.
페이지 부재(Page Fault)의 개념
요구 페이징(Demand Paging)에서 중요한 개념 중 하나는 페이지 부재(Page Fault)이다. 프로그램(Program)이 실행되는 중 특정 주소(Address)의 페이지(Page)에 접근했을 때, 해당 페이지가 현재 메모리(Memory)에 없는 경우가 있다. 이때 페이지 부재가 발생한다.
운영체제(Operating System)는 먼저 페이지 테이블(Page Table)을 통해 해당 페이지가 메모리에 있는지 확인한다. 페이지 테이블에는 각 페이지가 현재 메모리에 올라와 있는지를 나타내는 값이 저장되어 있다. 이 값이 1이면 페이지가 메모리에 존재한다는 의미이고, 0이면 아직 메모리에 없다는 의미이다.
페이지가 메모리에 있으면 프로그램은 바로 해당 페이지에 접근할 수 있다. 반대로 페이지가 메모리에 없다면 페이지 부재가 발생하고, 운영체제는 필요한 페이지를 저장장치(Storage)에서 가져와 메모리에 적재한다.
여기서 중요한 점은 페이지 부재를 단순한 오류(Error)로 보면 안 된다는 것이다. 요구 페이징에서는 처음부터 프로그램 전체가 아니라 일부 페이지만 메모리에 올려두기 때문에, 실행 중 메모리에 없는 페이지가 필요해지는 상황은 자연스럽게 발생한다.
따라서 페이지 부재는 문제가 생겼다는 의미라기보다, 이제 해당 페이지가 필요하다는 신호라고 볼 수 있다. 즉, 페이지 부재는 운영체제가 필요한 페이지를 메모리에 가져오도록 만드는 정상적인 동작 과정의 일부이다.
페이지 부재(Page Fault)와 타당 비트(Valid Bit)
페이지 부재(Page Fault)는 프로그램(Program)이 특정 페이지(Page)에 접근하려고 할 때, 해당 페이지가 현재 메모리(Memory)에 없는 경우 발생한다. 이를 확인하기 위해 운영체제(Operating System)는 페이지 테이블(Page Table)을 사용한다.
그림을 보면 논리 주소(Logical Address)에서 시작해 페이지 테이블을 거쳐 물리 메모리(Physical Memory)로 연결되는 구조를 확인할 수 있다. 이때 페이지 테이블에는 각 페이지가 현재 메모리에 올라와 있는지를 표시하는 값이 포함되어 있다. 이 값을 타당 비트(Valid Bit) 또는 비타당 비트(Invalid Bit)라고 한다.
타당 비트가 1이면 해당 페이지가 현재 메모리에 존재한다는 의미이다. 따라서 프로그램은 바로 물리 메모리에 접근하여 실행을 이어갈 수 있다. 반대로 비타당 비트가 0이면 해당 페이지가 아직 메모리에 올라와 있지 않다는 의미이다.
이 경우에는 해당 페이지를 바로 사용할 수 없기 때문에 페이지 부재가 발생한다. 그러면 운영체제는 필요한 페이지를 저장장치(Storage)에서 찾아 메모리로 가져오는 과정을 수행한다.
즉, 타당 비트와 비타당 비트는 각 페이지가 현재 메모리에 존재하는지 판단하고, 프로그램이 해당 페이지에 접근할 수 있는지를 결정하는 기준이라고 볼 수 있다.
페이지 부재(Page Fault) 발생 시점
페이지 부재(Page Fault)는 프로그램(Program)이 실행되는 과정에서 자연스럽게 여러 번 발생할 수 있다. 요구 페이징(Demand Paging)에서는 처음부터 모든 페이지(Page)를 메모리(Memory)에 올려두지 않기 때문이다.
먼저 프로그램을 처음 실행했을 때 페이지 부재가 발생할 수 있다. 아직 필요한 페이지들이 모두 메모리에 올라와 있지 않은 상태이기 때문에, 처음 접근하는 페이지에서 페이지 부재가 발생한다.
또한 실행이 진행되면서 이전에 사용하지 않았던 새로운 코드(Code)나 데이터(Data) 영역에 접근할 때도 페이지 부재가 발생할 수 있다. 프로그램의 실행 흐름이 바뀌거나 다른 기능을 사용하게 되면, 그 순간 새로운 페이지가 필요해질 수 있기 때문이다.
결국 페이지 부재는 한 번만 발생하는 현상이 아니다. 프로그램이 실행되는 동안 필요한 페이지가 메모리에 없을 때마다 반복적으로 발생할 수 있으며, 이는 요구 페이징 구조에서 나타나는 정상적인 동작 과정이라고 볼 수 있다.
페이지 부재(Page Fault) 처리 흐름
페이지 부재(Page Fault)가 발생하면 운영체제(Operating System)가 개입하여 처리를 시작한다. 먼저 프로그램(Program)이 특정 주소(Address)에 접근하면, 운영체제는 페이지 테이블(Page Table)을 통해 해당 페이지(Page)가 메모리(Memory)에 있는지 확인한다.
이때 해당 페이지가 메모리에 없다면 페이지 부재 처리가 시작된다. 운영체제는 저장장치(Storage)에 있는 해당 페이지를 찾아 읽어오고, 그 페이지를 물리 메모리(Physical Memory)에 적재한다.
페이지가 메모리에 올라오면 운영체제는 중단되었던 명령(Instruction)을 다시 실행한다. 실제 내부 동작에서는 프로그램 실행이 잠시 멈췄다가 다시 이어지지만, 프로그램 입장에서는 중단 없이 연속적으로 실행되는 것처럼 보인다.
정리하면, 페이지 부재 처리 흐름은 페이지 테이블 확인, 운영체제 개입, 저장장치에서 페이지 읽기, 메모리 적재, 중단된 명령 재실행의 과정으로 이루어진다.
페이지 부재(Page Fault) 처리 흐름 그림
그림은 페이지 부재(Page Fault)가 발생했을 때 처리되는 과정을 단계별로 보여준다.
먼저 프로세스(Process)가 실행되는 중 1️⃣논리 주소(Logical Address)를 통해 특정 페이지(Page)에 접근을 시도한다. 이때 운영체제(Operating System)는 2️⃣페이지 테이블(Page Table)을 확인하여 해당 페이지가 현재 메모리(Memory)에 있는지 판단한다.
만약 해당 페이지가 메모리에 없다면 3️⃣페이지 부재가 발생한다. 그러면 운영체제가 개입하여 메모리 안에서 4️⃣비어 있는 공간을 찾고, 5️⃣저장장치(Storage)에 있는 해당 페이지를 가져와 그 공간에 적재한다.
6️⃣페이지가 메모리에 올라오면 7️⃣페이지 테이블의 정보도 새롭게 수정된다. 이후 운영체제는 중단되었던 명령(Instruction)을 8️⃣다시 실행하여 프로세스가 계속 진행되도록 한다.
정리하면 페이지 부재 처리 흐름은 논리 주소 접근, 페이지 테이블 확인, 페이지 부재 발생, 운영체제 개입, 페이지 적재, 페이지 테이블 수정, 명령 재실행의 순서로 이해할 수 있다.
요구 페이징(Demand Paging)의 동작과 핵심 결과
요구 페이징(Demand Paging)에서는 프로그램(Program)이 특정 페이지(Page)에 접근할 때마다 해당 페이지가 메모리(Memory)에 있는지 확인한다. 페이지가 이미 메모리에 있다면 프로그램 실행은 바로 이어진다.
반대로 해당 페이지가 메모리에 없다면 페이지 부재(Page Fault)가 발생한다. 이 경우 운영체제(Operating System)는 저장장치(Storage)에서 필요한 페이지를 가져와 메모리에 적재하고, 중단되었던 명령(Instruction)을 다시 실행한다.
이 과정이 반복되면서 실제로 필요한 페이지들만 메모리에 유지된다. 따라서 프로그램 전체를 한 번에 메모리에 올리지 않아도 실행할 수 있고, 메모리 낭비를 줄일 수 있다.
즉, 요구 페이징은 필요한 부분만 메모리에 유지하면서도 프로그램 실행이 계속 이어지도록 해주는 가상메모리(Virtual Memory)의 핵심 동작 방식이다.
지금까지는 가상메모리의 개념, 요구 페이징의 동작 방식, 그리고 페이지 부재가 발생하고 처리되는 과정을 살펴보았다.
2️⃣ Page Replacement Algorithms and Thrashing
페이지 대치(Page Replacement)의 필요성
이번에는 메모리에 올라온 페이지(Page)를 어떤 기준으로 교체할지 결정하는 페이지 대치 알고리즘(Page Replacement Algorithm)과 스레싱(Thrashing) 현상에 대해 살펴본다.
요구 페이징(Demand Paging)에서는 프로그램 실행 중 필요한 페이지를 계속 메모리(Memory)로 가져온다. 하지만 여기서 문제가 발생한다. 메모리에 올릴 수 있는 페이지의 개수, 즉 프레임(Frame)의 수는 물리 메모리(Physical Memory)의 크기에 의해 결정되기 때문에 무한히 늘릴 수 없다.
따라서 메모리에 항상 여유 공간이 있는 것은 아니다. 이미 대부분의 공간이 사용 중인 상태에서 새로운 페이지를 메모리에 올려야 하는 상황이 자주 발생할 수 있다.
이때 새로운 페이지를 적재하려면, 기존에 메모리에 올라와 있던 페이지 중 하나를 제거해야 한다. 즉, 어떤 페이지를 내보내고 어떤 페이지를 계속 유지할 것인지 선택해야 하는 문제가 생긴다.
이처럼 제한된 메모리 공간에서 새로운 페이지를 적재하기 위해 기존 페이지를 교체해야 하며, 이때 필요한 기준이 바로 페이지 대치 알고리즘이다.
페이지 대치(Page Replacement)의 필요성 그림
이 그림은 새로운 페이지(Page)를 메모리(Memory)에 올려야 할 때, 왜 페이지 대치(Page Replacement)가 필요한지를 보여준다.
현재 물리 메모리(Physical Memory)에는 여러 페이지가 이미 적재되어 있다. 각 사용자 또는 프로세스(Process)는 자신만의 논리 주소 공간(Logical Address Space)과 페이지 테이블(Page Table)을 가지고 있으며, 페이지 테이블을 통해 어떤 페이지가 메모리에 올라와 있는지 확인할 수 있다.
예를 들어 특정 프로세스가 load M 명령을 실행한다고 하면, 이는 M 페이지에 있는 데이터를 사용하려는 동작이다. 그런데 페이지 테이블을 확인했을 때 M 페이지에 프레임 번호(Frame Number)가 없고 비타당 상태(Invalid State)로 표시되어 있다면, 현재 메모리에 없는 페이지라는 의미가 된다.
이 경우 M 페이지가 필요해졌기 때문에 페이지 부재(Page Fault)가 발생한다. 운영체제(Operating System)는 M 페이지를 보조기억장치(Secondary Storage)에서 가져와 물리 메모리에 적재해야 한다.
하지만 물리 메모리에 빈 프레임(Free Frame)이 없다면, 이미 메모리에 올라와 있는 페이지 중 하나를 제거해야 한다. 그리고 그 자리에 새로 필요한 M 페이지를 올리게 된다.
즉, 이 그림은 각 프로세스가 논리 주소 공간과 페이지 테이블을 따로 가지고 있어도, 실제 물리 메모리는 공유되기 때문에 새로운 페이지를 올리기 위해 기존 페이지를 교체해야 하는 상황이 발생한다는 것을 보여준다. 이때 어떤 페이지를 내보낼지 결정하는 기준이 페이지 대치 알고리즘(Page Replacement Algorithm)이다.
페이지 대치(Page Replacement)의 개념
페이지 대치(Page Replacement)는 메모리에 빈 공간이 없을 때, 새로운 페이지(Page)를 올리기 위해 기존 페이지 중 하나를 제거하고 그 자리에 필요한 페이지를 적재하는 과정이다.
새로운 페이지를 메모리(Memory)에 올려야 하는데 빈 프레임(Free Frame)이 없다면, 운영체제(Operating System)는 현재 메모리에 있는 페이지 중 하나를 선택해야 한다. 이때 선택되어 메모리에서 제거되는 페이지를 희생 페이지(Victim Page)라고 한다.
희생 페이지는 메모리에서 제거되고, 필요한 경우 저장장치(Storage)로 이동한다. 그 다음 운영체제는 보조기억장치(Secondary Storage)에서 새로 필요한 페이지를 읽어와 메모리에 적재한다.
마지막으로 페이지 테이블(Page Table)을 갱신하여 새 페이지가 어느 프레임(Frame)에 들어갔는지 반영한다. 즉, 페이지 대치는 기존 페이지를 하나 내보내고, 새로운 페이지를 그 자리에 올린 뒤, 변경된 상태를 페이지 테이블에 기록하는 과정이라고 이해할 수 있다.
페이지 대치(Page Replacement) 개념 그림
이 그림은 페이지 대치(Page Replacement)가 실제로 어떤 흐름으로 이루어지는지를 보여준다. 메모리(Memory)에 빈 공간이 없는 상태에서 새로운 페이지(Page)를 올려야 한다면, 먼저 현재 메모리에 있는 페이지 중 하나를 희생 페이지(Victim Page)로 선택해야 한다.
1번 과정에서는 기존 페이지 중 하나가 희생 페이지로 선택되어 메모리 밖으로 나간다. 이때 어떤 페이지가 선택되는지는 고정되어 있지 않고, 페이지 대치 알고리즘(Page Replacement Algorithm)에 따라 달라진다. 예를 들어 M 페이지가 희생 페이지로 선택되었다면, M 페이지는 메모리에서 제거되어 저장장치(Storage)로 이동한다.
2번 과정에서는 M 페이지가 더 이상 메모리에 없다는 사실을 반영하기 위해 페이지 테이블(Page Table)의 상태가 변경된다. 즉, 해당 페이지의 정보가 비타당 상태(Invalid State)로 수정된다.
3번 과정에서는 현재 필요한 페이지를 저장장치에서 읽어와 메모리로 가져온다. 예를 들어 A 페이지가 필요하다면, 앞에서 비워진 프레임(Frame)에 A 페이지가 적재된다.
마지막 4번 과정에서는 새로 올라온 A 페이지의 위치가 반영되도록 페이지 테이블이 갱신된다. 즉, A 페이지가 어느 프레임에 들어갔는지 기록하고, 해당 페이지를 타당 상태(Valid State)로 변경한다.
결국 페이지 대치는 기존 페이지를 하나 내보내고, 그 자리에 새로운 페이지를 올린 뒤, 변경된 내용을 페이지 테이블에 반영하는 과정이라고 이해할 수 있겠다.
페이지 대치 알고리즘(Page Replacement Algorithm)의 목표
페이지 대치(Page Replacement)가 필요한 상황을 이해했다면, 다음으로 중요한 질문은 어떤 페이지(Page)를 선택해서 내보낼 것인가이다. 어떤 페이지를 희생 페이지(Victim Page)로 선택하느냐에 따라 전체 시스템 성능이 크게 달라질 수 있다.
페이지 대치 알고리즘(Page Replacement Algorithm)은 기본적으로 페이지 부재(Page Fault)가 발생하는 횟수를 줄이는 것을 목표로 한다. 페이지 부재가 자주 발생하면 운영체제(Operating System)가 저장장치(Storage)에서 페이지를 다시 가져와야 하므로 실행 속도가 느려질 수 있다.
특히 이미 제거했던 페이지가 곧바로 다시 필요해지는 상황이 반복되면 불필요한 페이지 교체가 계속 발생한다. 이런 경우 메모리(Memory)는 계속 페이지를 넣고 빼는 데 사용되고, 실제 프로그램 실행 효율은 떨어지게 된다.
따라서 페이지 대치 알고리즘의 핵심은 가능하면 당장 필요하지 않을 페이지를 선택해 내보내는 것이다. 결국 어떤 기준으로 페이지를 선택하느냐에 따라 알고리즘의 성능 차이가 발생하게 된다.
페이지 대치 알고리즘(Page Replacement Algorithm): 프레임 수(Frame Count)와 페이지 부재(Page Fault)
페이지 대치 알고리즘(Page Replacement Algorithm)을 이해하려면 먼저 프레임(Frame)의 개념을 살펴볼 필요가 있다. 메모리(Memory)의 프레임 수는 동시에 메모리에 올려둘 수 있는 페이지(Page)의 개수를 의미한다. 즉, 프레임이 많을수록 프로그램(Program)이 사용하는 데이터를 더 많이 메모리에 유지할 수 있다.
프로그램이 실행될 때 페이지들은 일정한 순서로 계속 참조된다. 이러한 페이지 접근 순서를 참조 문자열(Reference String)이라고 한다. 프로그램은 이 참조 문자열을 따라가며 필요한 페이지를 하나씩 요청하게 된다.
프레임 수가 많아지면 한 번 메모리에 가져온 페이지를 더 오래 유지할 수 있다. 따라서 같은 페이지를 다시 요청했을 때, 그 페이지가 아직 메모리에 남아 있을 가능성이 높아진다. 그 결과 페이지 부재(Page Fault)가 줄어들 수 있다.
반대로 프레임 수가 적으면 메모리에 유지할 수 있는 페이지 수가 제한된다. 이 경우 새로운 페이지를 가져오기 위해 기존 페이지를 더 자주 교체해야 하고, 이미 내보낸 페이지가 다시 필요해지는 상황도 발생할 수 있다.
결국 프레임 수는 페이지 부재의 발생 빈도와 직접적으로 관련이 있다. 프레임이 많을수록 페이지를 더 많이 유지할 수 있어 페이지 부재가 줄어들 가능성이 높고, 프레임이 적을수록 페이지 교체가 자주 발생하게 된다.
프레임 수(Frame Count)와 페이지 부재(Page Fault)
프레임(Frame)은 메모리(Memory)에 페이지(Page)를 올려둘 수 있는 공간을 의미한다. 따라서 프레임 수가 많다는 것은 동시에 더 많은 페이지를 메모리에 유지할 수 있다는 뜻이다.
그래서 일반적인 경우엔 프레임수가 증가할수록 '페이지부재' 횟수는 감소하는 경향을 보이게 된다. 참조 문자열에따라 실행하는 과정에서 이미 사용했던 페이지가 메모리에 계속 남아있게되면서 다시 가져와야하는 상황이 줄어들기 때문이다.
결국 메모리에 더 많은 페이지를 유지할 수 있을수록 저장장치(Storage)에서 데이터를 다시 읽어오는 횟수가 줄어들고, 그 결과 페이지 부재도 감소한다. 따라서 대부분의 경우 프레임 수가 늘어나면 프로그램 실행 성능이 좋아진다고 볼 수 있다고 이해하자.
프레임 수(Frame Count)와 페이지 부재(Page Fault)의 예
프레임 수(Frame Count)와 페이지 부재(Page Fault)의 관계를 볼 때, 항상 같은 결과가 나타나는 것은 아니라는 점을 알아둘 필요가 있다.
그림의 왼쪽에는 참조 문자열(Reference String)이 제시되어 있다. 참조 문자열은 프로그램(Program)이 실행되는 동안 페이지(Page)에 접근하는 순서를 의미한다. 이 문자열을 보면 같은 페이지가 반복해서 등장하는 것을 확인할 수 있다. 즉, 프로그램은 새로운 페이지만 계속 사용하는 것이 아니라, 이미 사용했던 페이지를 다시 참조하는 패턴을 가진다.
따라서 어떤 페이지를 메모리(Memory)에 계속 유지하느냐가 페이지 부재 횟수에 큰 영향을 준다. 이미 사용했던 페이지가 메모리에 남아 있다면 다시 접근할 때 바로 사용할 수 있지만, 메모리에서 제거된 상태라면 다시 페이지 부재가 발생하게 된다.
오른쪽 그래프는 같은 참조 문자열을 기준으로 프레임 수를 바꿔가며 실행했을 때의 결과를 보여준다. 일반적으로 프레임 수가 많아질수록 페이지 부재 횟수는 감소하는 경향이 있다. 메모리에 더 많은 페이지를 유지할 수 있기 때문에, 이미 사용했던 페이지를 다시 참조할 때 페이지 부재가 발생할 가능성이 줄어들기 때문이다.
다만 모든 경우에 프레임 수가 증가한다고 항상 페이지 부재가 줄어드는 것은 아니다. 특정 페이지 대치 알고리즘(Page Replacement Algorithm)에서는 같은 참조 문자열을 사용하더라도 예상과 다른 결과가 나타날 수 있다. 이 점은 이후 페이지 대치 알고리즘을 이해하는 데 중요한 부분이다.
선입선출 페이지 대치 알고리즘(FIFO Page Replacement Algorithm)
가장 기본적인 페이지 대치 알고리즘(Page Replacement Algorithm) 중 하나는 선입선출 방식인 FIFO(First-In, First-Out)이다.
FIFO는 이름 그대로 먼저 메모리(Memory)에 들어온 페이지(Page)를 먼저 제거하는 방식이다. 즉, 페이지가 메모리에 적재된 순서를 기준으로 가장 오래전에 들어온 페이지가 교체 대상이 된다.
이 구조는 자료구조(Data Structure)에서 배우는 큐(Queue)와 비슷하게 이해할 수 있다. 먼저 들어온 데이터가 앞쪽에 위치하고, 새로운 페이지가 들어와야 할 때는 가장 앞에 있는 페이지가 제거된다.
FIFO는 적재된 순서만 관리하면 되기 때문에 구현이 매우 단순하다. 따라서 이해하기 쉽고 구현하기 쉬운 페이지 대치 알고리즘이라고 볼 수 있다.
선입선출 페이지 대치 알고리즘(FIFO Page Replacement Algorithm)의 한계
선입선출 페이지 대치 알고리즘(FIFO Page Replacement Algorithm)은 구조가 단순하다는 장점이 있지만, 한계도 분명하다.
가장 큰 한계는 페이지(Page)가 실제로 사용되고 있는지와 관계없이, 단순히 메모리(Memory)에 들어온 순서만을 기준으로 제거 대상이 결정된다는 점이다. 즉, 가장 오래전에 들어온 페이지가 현재도 계속 사용되고 있는 중요한 페이지일 수 있는데도 교체 대상이 될 수 있다.
또한 FIFO는 페이지가 과거에 언제 들어왔는지만 고려할 뿐, 앞으로 다시 사용될 가능성은 반영하지 못한다. 따라서 실제 프로그램(Program)의 사용 패턴을 충분히 고려하지 못하고, 비효율적인 페이지 교체(Page Replacement)가 발생할 수 있다.
결국 FIFO는 구현이 쉽고 이해하기 쉬운 방식이지만, 페이지의 실제 사용 빈도나 중요도를 반영하지 못한다는 한계를 가진다.
선입선출 페이지 대치 알고리즘(FIFO Page Replacement Algorithm)의 동작 과정
선입선출 페이지 대치 알고리즘(FIFO Page Replacement Algorithm)은 메모리에 먼저 들어온 페이지(Page)를 먼저 제거하는 방식이다. 이 과정은 큐(Queue)의 앞쪽에 있는 페이지를 제거하고, 새로 들어온 페이지를 뒤쪽에 추가하는 흐름으로 동작한다.
1️⃣ 먼저 FIFO 큐(Queue)의 헤드(Head)에 있는 페이지가 제거 대상, 즉 희생 페이지(Victim Page)로 선택된다. 이 페이지는 가장 먼저 메모리(Memory)에 들어온 페이지이므로 교체 대상이 된다.
2️⃣ 그다음 선택된 희생 페이지는 메모리에서 제거되어 저장장치(Storage)로 이동한다. 이 과정을 스왑 아웃(Swap Out)이라고 한다. 페이지가 메모리에서 나갔기 때문에, 해당 페이지에 대한 페이지 테이블(Page Table)의 정보도 갱신된다.
3️⃣ 이때 타당 비트(Valid Bit)는 0으로 변경되어 비타당 상태(Invalid State)가 된다.
4️⃣ 이후 새로 필요한 페이지를 저장장치에서 가져와 메모리에 적재한다. 이 과정을 스왑 인(Swap In)이라고 한다.
5️⃣ 새 페이지가 메모리에 올라오면 페이지 테이블도 다시 갱신된다. 새 페이지가 어느 프레임(Frame)에 들어갔는지 기록하고, 타당 비트를 1로 변경하여 타당 상태(Valid State)로 표시한다.
6️⃣ 마지막으로 새로 적재된 페이지는 FIFO 큐의 뒤쪽에 추가된다. 이렇게 하면 메모리에 들어온 순서가 다시 정리되고, 다음에 페이지 교체가 필요할 때는 다시 큐의 앞쪽에 있는 페이지가 제거 대상이 된다.
즉, FIFO 방식은 기존 페이지를 스왑 아웃(Swap Out)하고, 새로운 페이지를 스왑 인(Swap In)한 뒤, 새 페이지를 큐의 뒤쪽에 추가하는 과정을 반복하는 페이지 대치 방식이다.
선입선출 페이지 대치 알고리즘(FIFO Page Replacement Algorithm) 예시
그림의 위쪽에는 하나의 참조 문자열(Reference String)이 주어져 있다. 참조 문자열은 프로그램(Program)이 실행되는 동안 페이지(Page)를 요청하는 순서를 의미한다. 예를 들어 페이지가 1, 2, 3, 2, 1, 5, 2, 1, 6과 같은 순서로 요청되면, 프로그램은 이 순서에 따라 필요한 페이지에 접근하게 된다.
이때 프로그램이 어떤 페이지를 사용하려고 했는데, 해당 페이지가 현재 메모리(Memory)에 없다면 페이지 부재(Page Fault)가 발생한다.
그림에서는 프레임(Frame) 수가 3개일 때 FIFO(First-In, First-Out) 방식으로 페이지가 어떻게 적재되고 교체되는지를 보여준다. 처음 1이 요청되면 메모리가 비어 있으므로 첫 번째 프레임에 적재되고 페이지 부재가 발생한다. 이후 2와 3도 아직 메모리에 없기 때문에 각각 페이지 부재가 발생하며 프레임이 채워진다.
그다음 2와 1처럼 이미 메모리에 있는 페이지가 다시 요청되면 페이지 부재 없이 그대로 사용된다. 하지만 5처럼 메모리에 없는 새로운 페이지가 요청되면 다시 페이지 부재가 발생한다. 이때 빈 프레임이 없으므로 FIFO 규칙에 따라 가장 먼저 들어온 페이지인 1이 제거되고, 그 자리에 5가 적재된다.
이러한 과정을 참조 문자열의 끝까지 반복하면, FIFO 방식에서는 먼저 들어온 페이지를 기준으로 계속 교체가 이루어진다. 따라서 페이지가 실제로 자주 사용되는지와 관계없이, 가장 오래전에 적재된 페이지가 제거될 수 있다.
단, 녹취록에 나온 예시 참조 문자열이 1, 2, 3, 2, 1, 5, 2, 1, 6만을 의미한다면 페이지 요청 횟수가 9번이므로 페이지 부재가 14번 발생할 수는 없다. 14번의 페이지 부재는 그림에 표시된 더 긴 전체 참조 문자열을 기준으로 한 결과로 보는 것이 자연스럽다.
선입선출 페이지 대치 알고리즘(FIFO Page Replacement Algorithm)의 문제점
선입선출 페이지 대치 알고리즘(FIFO Page Replacement Algorithm)은 단순히 먼저 들어온 페이지(Page)를 먼저 제거하는 방식이다. 하지만 이 방식은 메모리(Memory)를 더 많이 할당해도 항상 성능이 좋아지는 것은 아니라는 한계를 가진다.
그림의 왼쪽에는 하나의 참조 문자열(Reference String)이 주어져 있고, 이를 기준으로 프레임(Frame)을 3개 사용했을 때와 4개 사용했을 때의 결과가 비교되어 있다. 일반적으로는 프레임 수가 늘어나면 더 많은 페이지를 메모리에 유지할 수 있으므로 페이지 부재(Page Fault)가 줄어드는 것이 자연스럽다.
하지만 이 예시에서는 프레임이 3개일 때 페이지 부재가 9번 발생했고, 프레임을 4개로 늘렸을 때는 오히려 페이지 부재가 10번으로 증가하였다. 즉, 메모리를 더 많이 할당했음에도 페이지 부재가 더 많이 발생한 것이다.
이러한 현상을 벨라디의 이상 현상(Belady’s Anomaly)이라고 한다. 이는 FIFO와 같은 일부 페이지 대치 알고리즘에서 나타날 수 있는 문제로, 프레임 수를 늘렸는데도 페이지 부재 횟수가 오히려 증가하는 현상을 의미한다.
결국 FIFO는 구현이 쉽고 단순하지만, 실제 페이지 사용 패턴을 고려하지 않기 때문에 메모리를 더 많이 사용해도 성능이 나빠질 수 있다는 한계를 가진다.
최적 페이지 대치 알고리즘(Optimal Page Replacement Algorithm)
최적 페이지 대치 알고리즘(Optimal Page Replacement Algorithm)은 앞으로 가장 오랫동안 사용되지 않을 페이지(Page)를 선택해서 제거하는 방식이다. 즉, 과거에 언제 사용되었는지가 아니라 앞으로 언제 다시 사용될지를 기준으로 희생 페이지(Victim Page)를 결정한다.
이 방식에서는 가장 나중에 다시 사용될 페이지, 또는 앞으로 사용되지 않을 페이지를 제거한다. 이렇게 하면 불필요한 페이지 교체(Page Replacement)를 최소화할 수 있기 때문에 페이지 부재(Page Fault) 횟수를 가장 적게 만들 수 있다.
다만 이 알고리즘은 앞으로의 페이지 참조 순서, 즉 미래의 참조 정보(Future Reference Information)를 알고 있다는 가정이 필요하다. 실제 실행 중에는 미래에 어떤 페이지가 사용될지 정확히 알 수 없기 때문에 현실적으로 구현하기는 어렵다.
따라서 최적 페이지 대치 알고리즘은 실제 운영체제(Operating System)에서 그대로 사용되기보다는, 다른 페이지 대치 알고리즘의 성능을 비교하는 기준으로 사용된다. 즉, 가장 이상적인 성능을 보이는 기준 알고리즘(Benchmark Algorithm)이라고 이해할 수 있다.
최적 페이지 대치 알고리즘(Optimal Page Replacement Algorithm)의 한계
최적 페이지 대치 알고리즘(Optimal Page Replacement Algorithm)은 앞으로 어떤 페이지(Page)가 언제 다시 사용될지를 미리 알고 있어야 한다. 하지만 실제 시스템에서는 프로그램(Program)의 미래 참조(Future Reference)를 정확히 알 수 없다.
따라서 이 알고리즘을 실제 운영체제(Operating System)에서 그대로 구현하는 것은 불가능하다. 실행 중인 프로그램이 앞으로 어떤 순서로 페이지에 접근할지 미리 예측할 수 없기 때문이다.
그래서 최적 페이지 대치 알고리즘은 실제로 사용하는 방식이라기보다 이론적인 기준(Theoretical Standard)으로 활용된다. 다른 페이지 대치 알고리즘(Page Replacement Algorithm)이 얼마나 효율적으로 동작하는지 평가할 때, 이론적으로 가장 좋은 경우와 비교하는 기준으로 사용된다.
즉, 최적 페이지 대치 알고리즘은 현실적으로 구현하기는 어렵지만, 페이지 부재(Page Fault)를 최소화할 수 있는 이상적인 알고리즘으로서 성능 비교의 기준이 된다.
최적 페이지 대치 알고리즘(Optimal Page Replacement Algorithm)의 동작 과정
최적 페이지 대치 알고리즘(Optimal Page Replacement Algorithm)은 앞으로 가장 오랫동안 사용되지 않을 페이지(Page)를 선택해 교체하는 방식이다. 같은 참조 문자열(Reference String)이 주어지고 프레임(Frame)이 3개라고 할 때, 참조 문자열의 흐름을 보면서 어떤 페이지를 제거할지 결정한다.
처음에는 1, 2, 3 페이지가 차례대로 요청되어 프레임이 채워지고, 이 과정에서 페이지 부재(Page Fault)가 발생한다. 이후 새로운 페이지인 5가 필요해지면, 현재 메모리에 있는 1, 2, 3 중 어떤 페이지를 제거할지 선택해야 한다.
이때 최적 알고리즘은 현재 위치 이후의 참조 문자열을 확인한다. 5 다음에 2가 먼저 다시 사용되고, 그다음 1이 다시 사용되며, 3은 그보다 더 나중에 사용된다면 3이 교체 대상이 된다. 즉, 현재 메모리에 있는 페이지 중 앞으로 가장 늦게 다시 사용될 페이지를 제거하는 것이다.
이 방식은 단순히 먼저 들어온 페이지를 제거하는 것이 아니라, 앞으로의 참조 순서를 기준으로 가장 효율적인 선택을 한다. 따라서 불필요한 페이지 교체를 최소화할 수 있고, 전체 페이지 부재 횟수도 줄어든다.
이 예제에서는 최적 페이지 대치 알고리즘을 적용했을 때 총 페이지 부재 횟수가 9회로 나타난다. 이는 앞에서 살펴본 FIFO(First-In, First-Out) 방식보다 더 적은 결과이다. 결국 최적 페이지 대치 알고리즘은 미래의 참조 정보를 알고 있다는 가정하에서 페이지 부재를 최소화하는 이론적으로 가장 효율적인 알고리즘이다.
LRU 페이지 대치 알고리즘(LRU Page Replacement Algorithm)
LRU(Least Recently Used) 페이지 대치 알고리즘은 가장 오랫동안 사용되지 않은 페이지(Page)를 선택해서 제거하는 방식이다. 앞으로 어떤 페이지가 사용될지 정확히 알 수 없기 때문에, 대신 과거의 사용 이력(Usage History)을 기준으로 판단한다.
이 알고리즘은 최근에 사용된 페이지는 다시 사용될 가능성이 높고, 오랫동안 사용되지 않은 페이지는 앞으로도 사용되지 않을 가능성이 높다고 가정한다. 따라서 메모리(Memory)에 있는 페이지 중 가장 오래 사용되지 않은 페이지를 희생 페이지(Victim Page)로 선택한다.
LRU는 미래의 참조 정보를 알 수 없다는 현실적인 한계를 고려하여, 과거의 참조 기록을 바탕으로 최적 페이지 대치 알고리즘(Optimal Page Replacement Algorithm)에 가까운 선택을 하려는 방식이다.
즉, LRU는 실제 사용 패턴을 어느 정도 반영할 수 있기 때문에 FIFO(First-In, First-Out)보다 효율적인 결과를 기대할 수 있는 대표적인 페이지 대치 알고리즘이다.
LRU 페이지 대치 알고리즘(LRU Page Replacement Algorithm)의 특징
LRU(Least Recently Used) 페이지 대치 알고리즘은 최근에 사용된 페이지(Page)는 다시 사용될 가능성이 높다는 가정을 바탕으로 동작한다. 따라서 각 페이지가 마지막으로 사용된 시점을 기준으로, 가장 오랫동안 사용되지 않은 페이지를 교체 대상으로 선택한다.
이 방식은 과거의 사용 이력(Usage History)을 반영하기 때문에 FIFO(First-In, First-Out)보다 더 합리적인 선택이 가능하다. 단순히 먼저 들어온 페이지를 제거하는 것이 아니라, 실제로 최근에 사용되었는지를 고려하기 때문이다.
하지만 LRU가 동작하려면 각 페이지가 언제 사용되었는지를 기록해야 한다. 예를 들어 페이지 사용 시간(Use Time)을 저장하거나, 스택(Stack)과 같은 구조를 이용해 사용 순서(Use Order)를 관리할 수 있다.
즉, LRU는 페이지 사용 패턴을 반영할 수 있어 효율적인 페이지 대치가 가능하지만, 이를 구현하기 위해 추가적인 정보 관리 비용(Management Cost)이 필요하다는 특징이 있다.
LRU 페이지 대치 알고리즘(LRU Page Replacement Algorithm)의 동작 흐름
LRU(Least Recently Used) 페이지 대치 알고리즘은 가장 오랫동안 사용되지 않은 페이지(Page)를 교체 대상으로 선택하는 방식이다. 참조 문자열(Reference String)이 주어지고 프레임(Frame)이 3개일 때, 페이지 요청 순서에 따라 메모리 상태가 계속 갱신된다.
처음 1, 2, 3 페이지가 차례대로 들어올 때는 메모리가 비어 있는 상태이기 때문에 각각 페이지 부재(Page Fault)가 발생하고, 프레임이 하나씩 채워진다.
이후 2가 다시 요청되면 이미 메모리에 있기 때문에 페이지 교체는 발생하지 않는다. 대신 2는 가장 최근에 사용된 페이지로 갱신된다. 그다음 1이 요청될 때도 마찬가지로 교체 없이 사용되며, 이번에는 1이 가장 최근에 사용된 페이지가 된다.
이 상태에서 5가 요청되면 메모리가 이미 가득 차 있기 때문에 기존 페이지 중 하나를 제거해야 한다. LRU에서는 가장 오래 사용되지 않은 페이지를 선택한다. 이때 3은 처음 들어온 이후로 다시 사용되지 않았고, 1과 2는 최근에 사용되었기 때문에 3이 제거된다. 그 자리에 5가 새로 적재된다.
이후에도 페이지가 요청될 때마다 최근 사용 시점을 기준으로 가장 오래 사용되지 않은 페이지를 교체한다. 이 과정을 끝까지 반복하면 예제에서는 총 11회의 페이지 부재가 발생한다.
즉, LRU의 핵심은 단순히 메모리에 들어온 순서가 아니라 실제 사용 이력(Usage History)을 기준으로 교체 대상을 결정한다는 점이다.
전역 대치(Global Replacement), 지역 대치(Local Replacement)
페이지 대치(Page Replacement)에서 중요한 문제 중 하나는 교체 대상을 어느 범위까지 볼 것인가이다. 즉, 특정 프로세스(Process) 내부에서만 희생 페이지(Victim Page)를 선택할 것인지, 아니면 메모리(Memory)에 올라와 있는 모든 프로세스의 페이지를 대상으로 선택할 것인지에 따라 동작 방식이 달라진다.
이 기준에 따라 페이지 대치는 전역 대치(Global Replacement)와 지역 대치(Local Replacement)로 나눌 수 있다.
전역 대치(Global Replacement)는 희생 페이지를 선택할 때 메모리에 있는 모든 프로세스의 프레임(Frame)을 대상으로 하는 방식이다. 따라서 한 프로세스가 새로운 페이지(Page)를 필요로 할 때, 다른 프로세스가 사용 중인 프레임을 가져올 수도 있다. 이 방식은 시스템 전체 관점에서는 메모리를 효율적으로 사용할 수 있지만, 특정 프로세스 입장에서는 자신에게 할당된 프레임을 빼앗길 수 있기 때문에 성능이 불안정해질 수 있다.
반면 지역 대치(Local Replacement)는 각 프로세스가 자신에게 할당받은 프레임 안에서만 페이지 교체를 수행하는 방식이다. 다른 프로세스의 프레임에는 영향을 주지 않기 때문에 각 프로세스의 실행은 비교적 안정적으로 유지될 수 있다. 하지만 전체 메모리를 유연하게 활용하기는 어렵기 때문에, 시스템 전체 관점에서는 효율이 떨어질 수 있다.
결국 전역 대치와 지역 대치는 메모리 효율성과 프로세스 안정성 사이의 차이를 보여준다. 전역 대치는 전체 메모리 활용에는 유리하지만 개별 프로세스 성능이 흔들릴 수 있고, 지역 대치는 개별 프로세스의 안정성은 높지만 전체 메모리 활용 효율은 낮아질 수 있다.
페이지 버퍼링 알고리즘(Page Buffering Algorithm)
지금까지는 어떤 페이지(Page)를 선택해서 교체할지, 즉 페이지 대치 알고리즘(Page Replacement Algorithm)에 대해 살펴보았다. 하지만 페이지 부재(Page Fault)가 발생했을 때는 어떤 페이지를 교체할지도 중요하지만, 실제 성능에는 디스크 입출력(Disk I/O) 시간이 큰 영향을 준다.
페이지 부재가 발생하면 필요한 페이지를 보조기억장치(Secondary Storage)에서 메모리(Memory)로 가져와야 한다. 이 과정에서 가장 큰 지연은 디스크에서 데이터를 읽어오는 시간 때문에 발생한다. 따라서 무엇을 교체할 것인가뿐만 아니라, 페이지 부재를 얼마나 빠르게 처리할 것인가도 중요하다.
이러한 지연을 줄이기 위해 사용하는 기법이 페이지 버퍼링(Page Buffering)이다. 페이지 버퍼링은 페이지 부재가 발생했을 때 바로 사용할 수 있는 여유 프레임(Free Frame)을 미리 확보해두거나, 제거된 페이지를 버퍼(Buffer)에 잠시 유지해두는 방식이다.
이렇게 하면 새로운 페이지가 필요할 때 바로 적재할 수 있는 공간이 준비되어 있기 때문에 처리 지연을 줄일 수 있다. 또한 경우에 따라 제거된 페이지가 다시 필요해지면 디스크에서 다시 읽어오지 않고 버퍼에 남아 있는 페이지를 재사용할 수도 있다.
결국 페이지 버퍼링은 디스크 입출력 지연을 줄이기 위해 미리 공간을 준비하고, 제거된 페이지를 잠시 보관하여 페이지 부재 처리 시간을 줄이는 기법이라고 이해할 수 있겠다.
스래싱(Thrashing)
페이지 대치 알고리즘(Page Replacement Algorithm)과 페이지 버퍼링(Page Buffering)을 통해 가상메모리(Virtual Memory)의 성능을 어느 정도 개선할 수 있다. 하지만 메모리(Memory)가 부족하거나 실행 중인 프로세스(Process)가 지나치게 많아지면 이러한 기법만으로는 성능을 유지하기 어렵다.
이처럼 페이지 부재(Page Fault)가 과도하게 발생하는 현상을 스래싱(Thrashing)이라고 한다. 정상적인 경우에는 페이지 교체(Page Replacement)가 발생하더라도 프로그램 실행이 계속 이어진다. 하지만 스래싱 상태에서는 페이지를 메모리로 가져오고 다시 내보내는 작업이 반복되면서, 실제 프로그램 실행은 거의 이루어지지 않는다.
이 경우 CPU(Central Processing Unit)는 대부분의 시간을 작업이 끝나기를 기다리는 데 사용하고, 디스크(Disk)는 계속해서 페이지를 읽고 쓰는 작업만 수행하게 된다. 결국 시스템 전체가 실제 실행보다 페이지 교체에 대부분의 시간을 사용하는 비효율적인 상태에 빠지게 된다.
따라서 스래싱은 단순히 페이지 부재가 많이 발생하는 문제가 아니라, 메모리 부족으로 인해 시스템 성능 전체가 크게 저하되는 현상이라고 이해할 수 있다.
스레싱(Thrashing) 발생 원인
스레싱(Thrashing)의 발생 원인은 결국 메모리 부족(Memory Shortage)과 관련이 있다. 프로세스(Process)에 할당된 프레임(Frame) 수가 부족하면, 프로세스가 필요로 하는 페이지(Page)를 메모리에 충분히 유지할 수 없다. 이 경우 필요한 페이지가 메모리에 없어서 페이지 부재(Page Fault)가 계속 발생하게 된다.
또한 동시에 실행되는 프로세스의 수가 너무 많아져도 스레싱이 발생할 수 있다. 실행 중인 프로세스가 많아지면 각 프로세스에 할당될 수 있는 메모리의 양이 줄어들고, 그 결과 각 프로세스가 필요한 페이지를 충분히 유지하지 못하게 된다.
이때 중요한 개념이 워킹 셋(Working Set)이다. 워킹 셋은 프로세스가 일정 시간 동안 실제로 자주 사용하는 페이지들의 집합을 의미한다. 프로세스가 원활하게 실행되려면 이 워킹 셋을 메모리에 유지할 수 있어야 한다.
하지만 워킹 셋을 유지할 만큼의 메모리가 확보되지 않으면, 필요한 페이지를 계속 가져오고 다시 내보내는 상황이 반복된다. 이로 인해 페이지 부재가 과도하게 발생하고, 결국 스레싱 상태에 빠지게 된다.
즉, 스레싱의 핵심 원인은 프로세스가 자주 사용하는 페이지 집합인 워킹 셋을 메모리에 유지할 만큼 충분한 프레임이 확보되지 않는 데 있다.
스래싱(Thrashing) 동작 과정
스래싱(Thrashing)이 발생하면 페이지 교체(Page Replacement)가 반복되는 악순환 구조로 이어진다.
먼저 프로그램(Program) 실행 중 페이지 부재(Page Fault)가 계속 증가하기 시작한다. 필요한 페이지(Page)가 메모리(Memory)에 없기 때문에 운영체제(Operating System)는 새로운 페이지를 메모리에 올려야 한다.
그런데 메모리에 여유 공간이 부족하면, 새로운 페이지를 올리기 위해 기존에 있던 페이지 중 하나를 제거해야 한다. 문제는 이렇게 제거된 페이지가 얼마 지나지 않아 다시 필요해질 수 있다는 점이다.
그러면 운영체제는 다시 그 페이지를 가져오기 위해 또 다른 페이지를 제거하게 된다. 이 과정이 반복되면 메모리는 계속 페이지를 가져오고 내보내는 작업에 사용되고, 실제 프로그램 실행은 거의 이루어지지 않게 된다.
즉, 스래싱은 페이지 부재 증가, 기존 페이지 제거, 제거된 페이지의 재요청, 또 다른 페이지 제거가 반복되면서 실행보다 페이지 교체가 더 많이 발생하는 상태라고 이해할 수 있다.
스래싱(Thrashing)과 워킹 셋(Working Set)
스래싱(Thrashing)의 동작 과정을 이해하기 위해서는 워킹 셋(Working Set)의 개념을 함께 살펴볼 필요가 있다. 워킹 셋은 프로세스(Process)가 일정 시간 동안 실제로 자주 사용하는 페이지(Page)들의 집합을 의미한다.
그림에서는 프로그램이 실행되면서 페이지 참조열(Page Reference String)을 따라 여러 페이지를 순서대로 참조하는 모습을 보여준다. 이때 특정 시점을 기준으로 바로 이전의 일정 구간, 즉 델타(Delta)만큼의 범위 안에서 최근에 사용된 페이지들을 모아 워킹 셋을 구한다.
예를 들어 t1 시점에서 왼쪽으로 델타=10 범위를 보면, 이 구간 안에서 참조된 페이지는 2, 1, 5, 7이다. 따라서 이때의 워킹 셋은 [2, 1, 5, 7]이 된다.
t2 시점에서도 같은 방식으로 왼쪽의 델타=10 범위를 확인한다. 이 구간에서는 7, 5, 1, 3, 4가 참조되었으므로, t2 시점의 워킹 셋은 [7, 5, 1, 3, 4]가 된다.
t3 시점에서는 최근에 참조된 페이지가 3, 4로 줄어든다. 따라서 이때의 워킹 셋은 [3, 4]가 된다.
이 그림이 보여주는 핵심은 워킹 셋이 고정된 하나의 집합이 아니라는 점이다. 프로그램이 실행되는 시점에 따라 최근에 자주 참조되는 페이지의 묶음은 계속 달라진다. 따라서 운영체제(Operating System)는 각 시점의 워킹 셋을 메모리(Memory)에 유지할 수 있도록 관리해야 한다.
만약 워킹 셋을 충분히 메모리에 유지하지 못하면, 방금까지 필요했던 페이지를 계속 내보내고 다시 가져오는 일이 반복된다. 이런 상황이 심해지면 페이지 교체(Page Replacement)가 실제 실행보다 더 많이 발생하게 되고, 결국 스래싱으로 이어질 수 있다.
또한 델타의 크기도 중요하다. 델타가 너무 작으면 실제로 필요한 페이지들을 충분히 포함하지 못할 수 있다. 반대로 델타가 너무 크면 현재는 자주 사용하지 않는 페이지까지 포함하게 되어 메모리를 낭비할 수 있다.
워킹 셋(Working Set)과 프레임 수(Frame Count)
이 그림은 워킹 셋(Working Set)의 크기와 프레임 수(Frame Count)에 따라 페이지 부재 비율(Page Fault Rate)이 어떻게 달라지는지를 보여준다.
가로축은 메모리(Memory)에 할당된 프레임 수를 의미하고, 세로축은 페이지 부재 비율을 의미한다. 프레임 수가 매우 적은 구간에서는 페이지 부재 비율이 매우 높게 나타난다. 이 구간은 프로세스(Process)의 워킹 셋보다 할당된 프레임 수가 적은 상태라고 볼 수 있다.
즉, 프로세스가 자주 사용하는 페이지(Page)들을 메모리에 모두 유지하지 못하기 때문에, 필요한 페이지를 계속 가져오고 다시 내보내는 상황이 반복된다. 그 결과 페이지 부재가 급격히 증가하며, 이 상태가 바로 스래싱(Thrashing)이 발생하는 구간이다.
프레임 수를 점점 늘리면 페이지 부재 비율이 급격히 떨어지는 구간이 나타난다. 이 지점은 메모리가 워킹 셋을 수용할 수 있게 되는 구간이다. 자주 사용하는 페이지들이 메모리에 유지되기 때문에 페이지 부재가 크게 감소한다.
그 이후로 프레임 수를 더 늘리면 페이지 부재 비율은 계속 줄어들 수 있지만, 감소 폭은 점점 작아진다. 이미 대부분의 워킹 셋이 메모리에 유지되고 있기 때문에 추가적인 프레임을 더 제공해도 성능 개선 효과가 크지 않기 때문이다.
따라서 핵심은 각 프로세스의 워킹 셋을 충분히 담을 수 있을 만큼의 프레임이 확보되어야 한다는 점이다. 그래야 페이지 부재를 줄이고 스래싱을 방지할 수 있다.
스래싱(Thrashing)의 영향
스래싱(Thrashing)이 발생하면 시스템 전체에 여러 가지 영향이 연쇄적으로 나타난다. 가장 먼저 페이지 부재율(Page Fault Rate)이 급격하게 증가한다. 필요한 페이지(Page)가 계속 메모리(Memory)에 없기 때문에, 운영체제(Operating System)는 반복적으로 페이지를 가져와야 한다.
이로 인해 디스크 입출력(Disk I/O)이 크게 증가한다. 페이지를 계속 읽고 쓰는 작업이 반복되면서 디스크(Disk)가 과도하게 사용되는 상태가 된다.
반대로 CPU(Central Processing Unit)는 실제 작업을 제대로 수행하지 못하고, 대부분의 시간을 디스크 작업이 끝나기를 기다리는 데 사용하게 된다. 즉, CPU는 놀고 있고 디스크만 계속 동작하는 비정상적인 상태가 되는 것이다.
이러한 상황이 계속되면 프로그램 실행보다 페이지 교체(Page Replacement)에 더 많은 시간이 사용된다. 결과적으로 시스템 전체 성능(System Performance)이 크게 저하된다.
CPU 이용률(CPU Utilization)과 다중 프로그래밍 수준(Degree of Multiprogramming)
다중 프로그래밍 수준(Degree of Multiprogramming)이 증가한다는 것은 동시에 실행되는 프로세스(Process)의 수가 많아진다는 의미이다. 초기에는 프로세스 수가 늘어날수록 CPU(Central Processing Unit)가 처리할 작업도 많아지기 때문에 CPU 이용률 (CPU Utilization)이 증가한다.
예를 들어 한 프로세스가 입출력(I/O) 작업으로 기다리는 동안, 다른 프로세스가 CPU를 사용할 수 있다. 따라서 CPU가 쉬는 시간이 줄어들고, 시스템 전체의 처리 효율이 좋아질 수 있다.
하지만 프로세스 수가 일정 수준을 넘어서면 문제가 발생한다. 동시에 실행되는 프로세스가 너무 많아지면 각 프로세스에 할당되는 메모리(Memory)의 양이 줄어든다. 그 결과 각 프로세스가 자신의 워킹 셋(Working Set)을 충분히 메모리에 유지하지 못하게 되고, 페이지 부재(Page Fault)가 증가한다.
페이지 부재가 많아지면 운영체제(Operating System)는 필요한 페이지(Page)를 디스크(Disk)에서 계속 가져와야 한다. 이 과정에서 프로세스들은 실제 실행보다 대기 시간이 길어지고, CPU는 디스크 작업이 끝나기를 기다리게 된다.
결국 다중 프로그래밍 수준이 증가하면 처음에는 CPU 이용률이 높아지지만, 일정 수준을 넘어서면 스래싱(Thrashing)이 발생하면서 CPU 이용률이 급격히 감소한다. 즉, 프로세스 수를 무조건 늘리는 것이 항상 좋은 것은 아니며, 메모리가 감당할 수 있는 수준 안에서 적절하게 조절해야 한다.
CPU 이용률(CPU Utilization)과 다중 프로그래밍 수준(Degree of Multiprogramming) 그래프
이 그래프는 다중 프로그래밍 수준(Degree of Multiprogramming)이 CPU 이용률(CPU Utilization)에 어떤 영향을 주는지를 보여준다.
초기에는 동시에 실행되는 프로세스(Process)의 수가 증가할수록 CPU가 처리할 작업도 많아지기 때문에 CPU 이용률이 증가한다. 한 프로세스가 입출력(I/O)이나 페이지 처리로 기다리는 동안 다른 프로세스가 실행될 수 있기 때문이다.
하지만 일정 수준을 넘어서면 문제가 발생한다. 프로세스 수가 너무 많아지면 각 프로세스에 할당되는 프레임(Frame) 수가 부족해지고, 워킹 셋(Working Set)을 메모리(Memory)에 충분히 유지하지 못하게 된다. 그 결과 페이지 부재(Page Fault)가 증가하고, 스래싱(Thrashing)이 발생한다.
스래싱 구간에서는 프로세스들이 실제 실행보다 페이지를 가져오고 내보내는 작업에 더 많은 시간을 사용한다. 이로 인해 디스크 입출력(Disk I/O)은 증가하지만 CPU는 대기하는 시간이 많아져 CPU 이용률이 급격히 감소한다.
즉, 다중 프로그래밍 수준이 낮을 때는 프로세스 수를 늘리는 것이 CPU 이용률을 높이는 데 도움이 된다. 하지만 너무 많이 늘리면 메모리 부족으로 인해 스래싱이 발생하고, 오히려 CPU 이용률이 떨어진다. 따라서 운영체제(Operating System)는 메모리가 감당할 수 있는 적정 수준에서 프로세스 수를 조절해야 한다.
스래싱(Thrashing) 해결 방법
스래싱(Thrashing)이 발생하면 시스템은 실제 실행보다 페이지 교체(Page Replacement)에 더 많은 시간을 사용하게 된다. 따라서 해결 방법은 메모리(Memory)에 걸리는 부담을 줄이는 방향으로 접근해야 한다.
대표적인 방법은 동시에 실행되는 프로세스(Process)의 수를 줄이는 것이다. 즉, 다중 프로그래밍 수준(Degree of Multiprogramming)을 낮추어 각 프로세스가 사용할 수 있는 메모리 공간을 늘리는 방식이다.
또 다른 방법은 일부 프로세스를 일시적으로 중단하거나 제거하여, 남아 있는 프로세스들에게 더 많은 프레임(Frame)을 확보해주는 것이다. 이렇게 하면 각 프로세스가 자신의 워킹 셋(Working Set)을 메모리에 유지할 가능성이 높아지고, 페이지 부재(Page Fault)의 발생도 줄어들 수 있다.
결국 스래싱을 해결하려면 너무 많은 프로세스가 제한된 메모리를 나누어 쓰는 상황을 완화해야 한다. 각 프로세스가 필요한 페이지들을 충분히 유지할 수 있도록 프레임을 확보해주는 것이 핵심이다.
스래싱(Thrashing) 방지 기법
스래싱(Thrashing)을 근본적으로 방지하기 위해서는 각 프로세스(Process)가 실행에 필요한 만큼의 메모리(Memory)를 유지할 수 있도록 관리하는 것이 중요하다. 즉, 프로세스가 자주 사용하는 페이지(Page)를 충분히 메모리에 올려둘 수 있어야 한다.
대표적인 방법은 워킹 셋 모델(Working Set Model)이다. 워킹 셋 모델은 각 프로세스가 일정 시간 동안 자주 사용하는 페이지들의 집합, 즉 워킹 셋(Working Set)을 메모리에 유지하도록 관리하는 방식이다. 프로세스의 워킹 셋을 메모리에 유지할 수 있으면 페이지 부재(Page Fault)를 줄이고 스래싱을 예방할 수 있다.
또 다른 방법은 페이지 부재 빈도(Page Fault Frequency, PFF)를 기준으로 프레임(Frame) 수를 조절하는 방식이다. 페이지 부재율(Page Fault Rate)이 높으면 해당 프로세스에 할당된 프레임 수가 부족하다는 의미로 보고 프레임을 늘려준다. 반대로 페이지 부재율이 낮으면 프레임이 과도하게 할당된 상태일 수 있으므로 프레임 수를 줄인다.
즉, 스래싱 방지의 핵심은 프로세스가 필요로 하는 페이지를 충분히 유지할 수 있도록 프레임 수를 조절하는 것이다. 이를 통해 페이지 부재가 과도하게 발생하지 않도록 하고, 시스템 성능 저하를 막을 수 있다.
페이지 부재 빈도(Page Fault Frequency, PFF) 기반 스래싱(Thrashing) 방지
페이지 부재 빈도(Page Fault Frequency, PFF) 방식은 페이지 부재율(Page Fault Rate)을 기준으로 프레임 수(Frame Count)를 조절하여 스래싱(Thrashing)을 방지하는 방법이다.
이 그래프는 페이지 부재율이 일정한 범위 안에 유지되도록 프레임 수를 조절하는 흐름을 보여준다. 페이지 부재율이 상한(Upper Bound)을 넘으면, 해당 프로세스(Process)에 할당된 프레임 수가 부족하다는 의미로 볼 수 있다. 따라서 프레임 수를 증가시켜 더 많은 페이지(Page)를 메모리(Memory)에 유지하도록 하고, 페이지 부재율을 낮춘다.
반대로 페이지 부재율이 하한(Lower Bound)보다 낮으면, 해당 프로세스에 프레임이 과도하게 할당되어 있을 가능성이 있다. 이 경우에는 프레임 수를 줄여 다른 프로세스가 메모리를 사용할 수 있도록 한다.
즉, PFF 방식은 페이지 부재율이 너무 높지도, 너무 낮지도 않게 일정 범위 안에서 유지되도록 프레임 수를 조절하는 기법이다. 이를 통해 각 프로세스가 필요한 만큼의 메모리를 확보하게 하고, 페이지 부재가 과도하게 발생하는 스래싱 상태를 예방할 수 있다.
3️⃣ Virtual Memory Operation Observation Practice
가상메모리(Virtual Memory) 실습 확인
지금부터는 가상메모리(Virtual Memory) 환경에서 페이지(Page)가 실제로 어떻게 동작하는지 프로그램 실행을 통해 확인한다.
앞에서 요구 페이징(Demand Paging)과 페이지 부재(Page Fault)의 개념을 살펴보았다. 요구 페이징은 프로그램 실행 중 필요한 페이지를 그때그때 메모리(Memory)로 가져오는 방식이고, 페이지 부재는 필요한 페이지가 현재 메모리에 없을 때 발생하는 현상이다.
이제는 이러한 개념들이 실제 시스템(System)에서 어떻게 나타나는지 직접 확인해볼 차례이다. 프로그램을 실행하면서 메모리 접근 과정에서 페이지가 어떻게 적재되고, 페이지 부재가 어떤 방식으로 발생하는지 살펴볼 수 있다.
가상메모리(Virtual Memory) 실습 목표
이번 실습에서는 가상 주소 공간(Virtual Address Space)을 확보하는 시점과 실제 메모리(Memory)에 적재되는 시점의 차이를 확인한다. 또한 페이지(Page)에 처음 접근했을 때와 다시 접근했을 때 어떤 차이가 나타나는지도 비교한다.
추가로 fork 이후에 복사 후 쓰기(Copy-on-Write, COW)가 어떻게 동작하는지도 함께 살펴본다. 이를 위해 단순히 코드를 실행하는 것이 아니라, 특정 기준을 가지고 결과를 관찰하는 것이 중요하다.
이번 실습에서는 mmap, fork, getrusage() 호출을 사용해 메모리 동작을 확인한다. 특히 getrusage()는 프로그램 실행 중 발생한 페이지 부재(Page Fault) 횟수를 확인할 수 있게 해준다.
여기서 중요한 관찰 기준은 페이지 부재이다. 페이지 부재는 크게 마이너 페이지 부재(Minor Page Fault)와 메이저 페이지 부재(Major Page Fault)로 나눌 수 있다.
마이너 페이지 부재는 디스크 접근 없이 메모리 할당이나 페이지 연결이 이루어지는 경우를 의미한다. 반면 메이저 페이지 부재는 필요한 데이터를 가져오기 위해 실제 디스크 입출력(Disk I/O)이 발생하는 경우를 의미한다.
따라서 이번 실습에서는 페이지에 처음 접근하거나 쓰기 작업이 발생하는 순간, 마이너 페이지 부재 값이 어떻게 변화하는지를 중심으로 관찰한다. 이를 통해 가상메모리 환경에서 페이지가 실제로 언제 메모리에 적재되고, 요구 페이징(Demand Paging)이 어떻게 나타나는지 확인할 수 있다.
요구 페이징(Demand Paging) 관찰
이번 실습에서는 페이지(Page)에 처음 접근했을 때 페이지 부재(Page Fault)가 발생하는지, 그리고 실제 메모리(Memory)가 언제 할당되는지를 확인한다.
요구 페이징(Demand Paging)의 동작 방식에서 mmap() 함수를 사용하면 먼저 가상 주소 공간(Virtual Address Space)만 확보된다. 이 시점에서는 실제 물리 메모리(Physical Memory)가 아직 할당된 상태가 아니다.
이후 프로그램이 해당 메모리에 실제로 접근하는 순간, 비로소 물리 메모리에 적재가 이루어진다. 이 과정에서 각 페이지에 처음 접근할 때마다 페이지 부재가 발생할 수 있다.
즉, mmap()으로 주소 공간을 확보하는 것과 실제 메모리가 할당되는 시점은 다르다. 이번 실습에서는 이 차이를 확인하면서, 요구 페이징이 실제 시스템에서 어떻게 나타나는지 관찰한다.
핵심 코드(Core Code)
핵심 코드는 mmap()을 통해 가상 주소 공간(Virtual Address Space)을 확보하고, 이후 각 페이지(Page)에 실제로 접근하면서 페이지 부재(Page Fault)가 발생하는지를 확인하는 부분이다.
먼저 char *buf = mmap(...); 코드는 mmap() 함수를 사용해 가상 주소 공간을 확보하는 역할을 한다. 이 시점에서는 주소 공간만 예약된 상태이고, 실제 물리 메모리(Physical Memory)는 아직 할당되지 않는다.
그다음 buf[i * page_size] = 1; 코드는 각 페이지에 한 번씩 접근하도록 만든다. 이 접근이 일어나는 순간 해당 페이지가 처음으로 사용되고, 그때 실제 물리 메모리에 적재가 이루어진다. 이 과정에서 페이지 부재가 발생한다.
여기서 중요한 점은 각 페이지는 처음 접근할 때만 페이지 부재가 발생한다는 것이다. 같은 페이지에 다시 접근하면 이미 메모리에 올라와 있기 때문에 추가적인 페이지 부재는 발생하지 않는다.
즉, 이 실습에서 페이지 부재는 단순한 접근 횟수보다 서로 다른 페이지를 처음 접근한 횟수와 관련이 있다. mmap()으로 주소 공간을 확보하는 시점과 실제 메모리가 할당되는 시점이 다르다는 것을 이 코드를 통해 확인할 수 있다.
요구 페이징(Demand Paging) 관찰 코드
이 코드는 페이지 부재(Page Fault)가 언제 발생하는지를 확인하기 위해 세 가지 시점으로 나누어 측정한다.
먼저 long f1 = get_faults();를 통해 프로그램 시작 시점의 페이지 부재 횟수를 저장한다. 그 다음 char *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);를 호출하여 가상 주소 공간(Virtual Address Space)을 확보한다.
이후 long f2 = get_faults();를 통해 mmap() 호출 이후의 페이지 부재 횟수를 확인한다. 이 시점에서는 실제 메모리 접근이 이루어진 것이 아니기 때문에 페이지 부재 횟수는 거의 변하지 않는다. 즉, mmap()은 가상 주소 공간만 확보할 뿐, 실제 물리 메모리(Physical Memory)를 바로 할당하는 것은 아니다.
그 다음 반복문 for (int i = 0; i < pages; i++) buf[i * page_size] = 1;을 통해 각 페이지(Page)에 한 번씩 접근한다. 이 부분이 각 페이지에 처음 접근하는 순간이며, 이때 실제 물리 메모리 적재가 이루어지고 페이지 부재가 발생한다.
마지막으로 f3 = get_faults();를 통해 접근 이후의 페이지 부재 횟수를 확인하면, 접근한 페이지 수만큼 페이지 부재가 증가한 것을 확인할 수 있다.
즉, 이 코드는 mmap() 시점에는 페이지 부재가 거의 발생하지 않지만, 실제로 각 페이지에 접근하는 순간 페이지 부재가 발생한다는 것을 확인하기 위한 코드이다. 이를 통해 요구 페이징(Demand Paging)에서는 주소 공간을 확보하는 시점과 실제 메모리가 할당되는 시점이 다르다는 것을 알 수 있다.
요구 페이징(Demand Paging) 관찰 결과
실행 결과를 보면 세 시점에서의 마이너 폴트(Minor Fault) 값을 비교할 수 있다. 이번 실습에서는 특히 마이너 폴트의 변화에 집중한다.
먼저 프로그램 시작 시점에서는 마이너 폴트 값이 minor: 320처럼 일정한 값으로 나타난다. 그다음 mmap() 호출 이후를 보면 마이너 폴트 값이 minor: 322 정도로 거의 변하지 않은 것을 확인할 수 있다. 이는 mmap() 함수가 실제 물리 메모리(Physical Memory)를 바로 할당하는 것이 아니라, 가상 주소 공간(Virtual Address Space)만 확보한다는 것을 보여준다.
반면 실제로 페이지(Page)에 접근한 이후에는 마이너 폴트 값이 minor: 1340처럼 크게 증가한다. 이 증가는 각 페이지에 처음 접근하는 순간 페이지 부재(Page Fault)가 발생했기 때문이다. 즉, 확보된 가상 주소 공간에 실제로 접근할 때 물리 메모리가 연결되면서 마이너 폴트가 증가한 것이다.
따라서 이 실습 결과의 핵심은 mmap() 이후에는 페이지 부재가 거의 증가하지 않지만, 실제 페이지에 접근하는 순간 마이너 폴트가 크게 증가한다는 점이다. 이를 통해 요구 페이징(Demand Paging)에서는 가상 주소 공간을 확보하는 시점과 실제 메모리가 할당되는 시점이 다르다는 것을 확인할 수 있다.
단, 마이너 폴트의 숫자 자체는 실행 환경에 따라 달라질 수 있다. 운영체제(Operating System)의 상태나 기존 실행 이력에 따라 초기값이나 증가량은 다르게 나타날 수 있으므로, 중요한 것은 숫자 자체보다 mmap() 이후와 실제 접근 이후의 변화 흐름이다.
요구 페이징(Demand Paging) 관찰 결과 해석
실행 결과를 정리하면, mmap() 함수는 실제 물리 메모리(Physical Memory)를 바로 할당하는 것이 아니라 가상 주소 공간(Virtual Address Space)만 확보한다는 것을 확인할 수 있다.
따라서 mmap()을 호출한 직후에는 페이지 부재(Page Fault)가 거의 증가하지 않는다. 아직 해당 주소 공간에 실제로 접근하지 않았기 때문이다.
반면 확보된 주소 공간의 각 페이지(Page)에 실제로 접근하는 순간, 그때 물리 메모리에 적재가 이루어진다. 이 과정에서 마이너 폴트(Minor Fault)가 증가한다.
즉, 요구 페이징(Demand Paging)에서는 주소 공간을 확보하는 시점과 실제 메모리가 할당되는 시점이 다르다. 실제 메모리는 페이지에 처음 접근하는 순간 할당되며, 이때 페이지 부재가 발생한다고 이해할 수 있다.
실행 결과 해석(Result Interpretation)
Fault의 의미를 보면, 마이너 폴트(Minor Fault)는 디스크 접근(Disk Access) 없이 메모리 할당(Memory Allocation)이 이루어지는 경우를 의미한다. 반면 메이저 폴트(Major Fault)는 필요한 데이터를 가져오기 위해 디스크 입출력(Disk I/O)이 발생하는 경우를 의미한다.
이번 실습에서는 마이너 폴트가 증가하는 모습을 통해 요구 페이징(Demand Paging)의 동작을 확인하였다. mmap() 함수로 가상 주소 공간(Virtual Address Space)을 확보했을 때는 실제 물리 메모리(Physical Memory)가 바로 할당되지 않았고, 페이지(Page)에 실제로 접근하는 순간 메모리가 사용되면서 마이너 폴트가 증가하였다.
결국 가상메모리(Virtual Memory)의 핵심은 메모리가 할당되는 시점이 단순히 주소 공간을 확보한 시점이 아니라, 실제로 해당 페이지에 접근하는 시점이라는 점이다.
실습 결과
요구 페이징 재접근 특성 관찰 Observation of Re-access Characteristics in Demand Paging
이번 실습에서는 페이지(Page)에 처음 접근했을 때 페이지 부재(Page Fault)가 발생하는 것에서 한 단계 더 나아가, 이미 접근했던 페이지에 다시 접근했을 때 어떤 변화가 있는지 확인한다.
핵심은 동일한 페이지를 다시 접근할 때 페이지 부재가 다시 발생하는지 관찰하는 것이다. 요구 페이징(Demand Paging)에서는 페이지에 처음 접근하는 순간 물리 메모리(Physical Memory)에 적재가 이루어지고, 이때 페이지 부재가 발생한다.
하지만 한 번 메모리에 적재된 페이지는 이후 다시 접근할 때 그대로 재사용될 수 있다. 따라서 같은 페이지에 다시 접근하는 경우에는 일반적으로 추가적인 페이지 부재가 발생하지 않는다.
이번 실습에서는 처음 접근과 재접근을 비교하면서, 페이지가 한 번 물리 메모리에 올라온 이후에는 어떻게 재사용되는지 확인한다. 이를 통해 요구 페이징에서 페이지 부재는 단순한 접근 횟수가 아니라, 처음 접근하는 페이지 수와 관련이 있다는 점을 이해할 수 있다.
Observation of Re-access Characteristics in Demand Paging
핵심 코드는 같은 방식으로 페이지(Page)에 접근하는 반복문이 두 번 실행되는 구조이다. 이를 통해 처음 접근할 때와 다시 접근할 때 페이지 부재(Page Fault)가 어떻게 달라지는지 확인할 수 있다.
첫 번째 루프(First Loop)는 각 페이지에 처음 접근하는 과정이다. 이때 각 페이지는 아직 물리 메모리(Physical Memory)에 올라와 있지 않기 때문에, 페이지마다 페이지 부재가 발생한다. 즉, 처음 접근하는 순간 실제 메모리 적재가 이루어진다.
반면 두 번째 루프(Second Loop)는 이미 한 번 접근했던 동일한 페이지에 다시 접근하는 과정이다. 이 경우 해당 페이지들은 이미 메모리에 올라와 있기 때문에 추가적인 페이지 부재는 거의 발생하지 않는다.
즉, 이 코드는 첫 접근(First Access)과 재접근(Re-access)의 차이를 확인하기 위한 예제이다. 페이지는 한 번 메모리에 올라오면 이후 다시 접근할 때 그대로 재사용될 수 있으며, 같은 페이지에 반복해서 접근한다고 해서 매번 페이지 부재가 발생하는 것은 아니다라는 걸 알수있다.
Core Code for Observing Re-access Characteristics in Demand Paging
이 코드는 같은 페이지(Page)에 대한 접근을 두 번 수행하면서, 각각 페이지 폴트(Page Fault)의 변화를 비교하는 코드이다.
먼저 첫 번째 순회(First Pass)에서는 before 값을 통해 현재 마이너 폴트(Minor Fault) 수를 저장한 뒤, 반복문을 통해 각 페이지에 한 번씩 접근한다. 이때 모든 페이지는 처음 접근되는 상태이기 때문에, 각 페이지마다 페이지 폴트가 발생한다. 따라서 첫 번째 순회 결과에서는 페이지 수만큼 마이너 폴트가 증가하는 것을 확인할 수 있다.
두 번째 순회(Second Pass)에서는 이미 한 번 접근했던 동일한 페이지에 다시 접근한다. 이 경우 해당 페이지들은 이미 물리 메모리(Physical Memory)에 올라와 있는 상태이기 때문에, 추가적인 페이지 폴트는 거의 발생하지 않는다. 따라서 두 번째 순회의 결과는 첫 번째 순회와 비교했을 때 매우 작거나 0에 가까운 값으로 나타난다.
결국 이 코드는 첫 접근(First Access)에서는 페이지 폴트가 발생하지만, 재접근(Re-access) 시에는 같은 페이지가 이미 메모리에 적재되어 있기 때문에 페이지 폴트가 거의 발생하지 않는다는 점을 수치적으로 확인하기 위한 예제라고 할 수 있다.
Example Execution Result
실행 결과를 보면 첫 번째 순회(First Pass)에서 마이너 폴트(Minor Fault)가 1024만큼 증가한 것을 확인할 수 있다. 이는 페이지(Page) 수가 1024개이기 때문에, 각 페이지에 처음 접근하면서 모든 페이지에서 페이지 폴트(Page Fault)가 발생했기 때문이다.
반면 두 번째 순회(Second Pass)에서는 마이너 폴트 증가량이 0으로 나타난다. 이는 첫 번째 순회에서 이미 접근했던 페이지들이 모두 물리 메모리(Physical Memory)에 올라와 있기 때문이다. 따라서 같은 페이지에 다시 접근할 때는 추가적인 페이지 폴트가 발생하지 않는다.
이 결과를 통해 페이지는 처음 접근할 때 메모리에 적재되고, 한 번 적재된 이후에는 다시 접근해도 그대로 재사용된다는 점을 확인할 수 있다. 즉, 요구 페이징(Demand Paging)에서는 페이지 폴트가 매번 발생하는 것이 아니라, 해당 페이지에 처음 접근할 때 주로 발생한다고 이해할 수 있다.
Result Interpretation
지금까지의 결과를 정리하면, 페이지(Page)에 처음 접근할 때는 해당 페이지가 아직 메모리(Memory)에 없기 때문에 페이지 폴트(Page Fault)가 발생한다.
하지만 한 번 물리 메모리(Physical Memory)에 적재된 이후에는 같은 페이지에 다시 접근하더라도 추가적인 페이지 폴트는 발생하지 않는다. 이미 해당 페이지가 메모리에 올라와 있기 때문에 그대로 재사용될 수 있기 때문이다.
또한 페이지 폴트의 증가량은 단순한 접근 횟수로 결정되는 것이 아니라, 처음 접근한 서로 다른 페이지 수에 의해 결정된다. 이는 메모리가 페이지 단위(Page Unit)로 관리되기 때문이다.
따라서 이 실습의 핵심은 각 페이지는 처음 접근할 때만 페이지 폴트가 발생하고, 한 번 적재된 페이지는 메모리에 유지되는 동안 재사용된다는 점이다.
실습결과
Copy-on-Write 관찰(Observation of Copy-on-Write)
세 번째 실습에서는 fork() 이후 메모리(Memory)가 어떻게 복사되는지 확인한다. 특히 쓰기 작업(Write)이 발생하는 시점에 Copy-on-Write가 어떻게 동작하는지, 그리고 그 결과 부모 프로세스(Parent Process)와 자식 프로세스(Child Process)의 메모리가 어떻게 분리되는지를 살펴본다.
핵심 개념은 fork()가 호출되었을 때 부모 프로세스와 자식 프로세스가 처음부터 메모리를 완전히 따로 복사해서 가지는 것이 아니라는 점이다. 처음에는 두 프로세스가 같은 메모리 공간을 공유하는 상태로 시작한다.
하지만 이후 부모 프로세스나 자식 프로세스 중 하나가 해당 메모리에 쓰기 작업을 수행하면, 그 순간 실제 복사가 이루어진다. 이렇게 쓰기 시점에 복사가 발생하는 방식을 Copy-on-Write라고 한다.
즉, Copy-on-Write는 처음에는 메모리를 공유하다가, 실제로 내용을 변경하려는 순간에만 복사하여 부모와 자식의 메모리를 분리하는 방식이다. 이를 통해 불필요한 메모리 복사를 줄이고, fork() 이후의 메모리 사용을 더 효율적으로 관리할 수 있다.
Copy-on-Write Core Code
Copy-on-Write 핵심 코드는 크게 세 단계로 나누어 이해할 수 있다.
먼저 fork() 이전에는 buf[i * page_size] = 1 코드를 통해 부모 프로세스(Parent Process)가 각 페이지(Page)에 먼저 접근한다. 이 과정에서 해당 페이지들은 물리 메모리(Physical Memory)에 적재된다.
그 이후 pid_t pid = fork();가 호출되면 부모 프로세스와 자식 프로세스(Child Process)가 생성된다. 이 시점에서는 메모리 전체가 바로 복사되는 것이 아니라, 두 프로세스가 같은 페이지를 공유하는 상태가 된다.
이후 자식 프로세스가 buf[i * page_size] += 1과 같이 공유 중인 페이지에 쓰기 작업(Write)을 시도하면 Copy-on-Write가 발생한다. 공유된 페이지는 바로 수정될 수 없기 때문에, 쓰기 시점에 페이지 폴트(Page Fault)가 발생한다.
운영체제(Operating System)는 이 페이지 폴트를 계기로 해당 페이지를 복사하고, 자식 프로세스에게 새로운 페이지를 할당한다. 그 결과 자식 프로세스는 복사된 새 페이지를 사용하고, 부모 프로세스는 기존 페이지를 그대로 유지한다.
즉, Copy-on-Write는 fork() 직후에는 메모리를 공유하다가, 실제 쓰기 작업이 발생하는 순간에만 페이지를 복사하여 부모와 자식 프로세스의 메모리를 분리하는 방식이다.
Copy-on-Write Core Code
이 코드는 Copy-on-Write가 어떤 시점에서 발생하는지를 확인하기 위한 코드이다.
먼저 fork() 이전에는 부모 프로세스(Parent Process)가 반복문을 통해 각 페이지(Page)에 한 번씩 접근한다. 이 과정에서 페이지가 물리 메모리(Physical Memory)에 적재된다.
그다음 fork()가 호출되면 부모 프로세스와 자식 프로세스(Child Process)가 생성된다. 이 시점에서는 실제 메모리 복사가 일어나지 않고, 두 프로세스가 동일한 페이지를 공유한다.
이후 자식 프로세스에서 buf[i * page_size] += 1 코드가 실행되면 공유된 페이지에 쓰기 작업(Write)이 발생한다. 이때 공유된 페이지는 읽기 전용(Read-only) 상태이기 때문에 페이지 폴트(Page Fault)가 발생한다.
운영체제(Operating System)는 이 페이지 폴트를 계기로 해당 페이지를 복사하고, 자식 프로세스에게 새로운 페이지를 할당한다. 이 과정이 Copy-on-Write이다.
따라서 자식 프로세스에서는 쓰기 작업이 발생한 시점에 마이너 폴트(Minor Fault)가 증가하는 것을 확인할 수 있다. 반면 부모 프로세스는 기존 페이지를 그대로 유지하므로, 자식 프로세스의 쓰기 작업이 부모 프로세스의 메모리 값에는 영향을 주지 않는다.
Copy-on-Write Observation Result
실행 결과를 보면 자식 프로세스(Child Process)와 부모 프로세스(Parent Process)의 차이를 확인할 수 있다.
먼저 자식 프로세스에서는 마이너 폴트(Minor Fault)가 256만큼 증가한 것을 볼 수 있다. 이는 자식 프로세스가 각 페이지(Page)에 쓰기 작업(Write)을 수행하는 순간마다 페이지 폴트(Page Fault)가 발생했기 때문이다. 즉, 자식 프로세스가 접근한 페이지 수만큼 Copy-on-Write가 발생했다고 볼 수 있다.
또한 자식 프로세스의 첫 번째 페이지 값은 기존 값 1에서 2로 증가하였다. 이는 자식 프로세스가 해당 페이지에 쓰기 작업을 수행했기 때문이다.
반면 부모 프로세스의 첫 번째 페이지 값은 여전히 1로 유지된다. 이는 자식 프로세스가 공유 페이지를 직접 수정한 것이 아니라, 쓰기 시점에 별도의 페이지를 복사해서 사용했기 때문이다.
따라서 Copy-on-Write는 fork() 직후에는 부모와 자식이 같은 페이지를 공유하지만, 쓰기 작업이 발생하는 순간 페이지를 복사하여 두 프로세스의 메모리를 분리한다. 이로 인해 자식 프로세스의 변경은 부모 프로세스에 영향을 주지 않는다는 것을 알 수 있다.
Copy-on-Write Result Interpretation
지금까지의 결과를 정리하면, fork() 직후에는 부모 프로세스(Parent Process)와 자식 프로세스(Child Process)가 동일한 페이지(Page)를 공유한다. 이 상태에서는 실제 메모리 복사(Memory Copy)가 바로 이루어지지 않고, 같은 데이터를 함께 사용하는 구조가 된다.
이후 자식 프로세스가 해당 페이지에 쓰기 작업(Write)을 수행하면, 그 시점에서 Copy-on-Write가 발생한다. 즉, 복사는 fork()가 호출되는 순간이 아니라 실제로 write가 발생하는 순간에 이루어진다.
이때 자식 프로세스는 새롭게 복사된 페이지를 사용하고, 부모 프로세스는 기존 페이지를 그대로 유지한다. 따라서 자식 프로세스가 값을 변경하더라도 부모 프로세스의 메모리 값에는 영향을 주지 않는다.
또한 Copy-on-Write는 페이지 폴트(Page Fault)를 이용해 실제 복사 시점을 지연시키는 방식이라고 이해할 수 있다. 처음에는 메모리를 공유하다가, 쓰기 작업이 발생할 때만 필요한 페이지를 복사함으로써 불필요한 메모리 복사를 줄일 수 있다.
이번 실습은 복잡한 구현보다는 코드를 실행하고 결과를 관찰하는 것이 핵심이다. mmap(), fork(), getrusage()를 통해 가상 주소 공간(Virtual Address Space), 요구 페이징(Demand Paging), 페이지 폴트(Page Fault), Copy-on-Write의 동작을 실제 결과로 확인하는 데 목적이 있다.
실습 결과


