
저번 시간에 배운 메인 메모리 관리 방법들은 프로세스 전체가 실행되기 전에 메모리로 적재되어야 했다.
가상 메모리(Virtual Memory)는 프로세스 전체가 메모리에 적재되지 않아도 실행이 가능하도록 하는 기법이다.
가상 메모리 사용시, 프로그램의 크기가 물리 메모리 크기보다 커도 실행이 가능하다.
또한, 공유 메모리 구현이 가능하여 파일과 라이브러리의 공유가 쉽다.
대신 가상 메모리는 구현하기 어렵고 잘못 사용시 성능이 저하될 수 있다.
오늘은 가상 메모리 개념과 구현 방법 및 고려 사항들을 알아보자.
실제 프로그램들을 살펴보면, 프로그램 전체가 굳이 메모리에 적재되지 않아도 된다는 사실을 알 수 있다.
예를 들면,
만약, 프로그램의 필요한 부분만 메모리에 적재하고 실행한다면 다음과 같은 이점이 있다.
가상 메모리를 사용하면, 운영체제와 사용자 모두에게 이득이다.

가상 메모리는 실제 물리 메모리 개념과 개발자의 논리 메모리 개념을 분리한다.
프로그래머는 메모리 크기에 관련한 문제를 염려할 필요 없이, 프로그램 내의 문제만 집중해서 쉽게 프로그램을 작성할 수 있다.
"가상 주소 공간"은 프로세스가 메모리에 저장되는 논리적인 모습을 말하며 아래와 같다.

일반적으로 위의 그림처럼 0번지 주소부터 시작하여 연속적인 공간을 차지한다.
페이징에서 설명했던 것처럼, 실제 물리 프레임은 연속적이지 않을 수 있다.
힙(Heap)은 동적 할당 메모리를 사용하며 주소 공간 상에서 위쪽으로 확장된다.
스택(Stack)은 아래쪽으로 확장된다.
힙과 스택 사이의 빈 공간은 힙이나 스택이 확장될 때나, 동적 라이브러리를 링크할 때 사용된다.
가상 메모리는 페이지 공유를 통해 파일이나 메모리가 다른 프로세스 간 공유되는 것을 가능하게 한다.

프로그램의 필요한 부분만 메모리에 적재하는 것을 말한다.
프로그램 전체를 메모리에 적재하지 않고, 실행에 필요한 부분만 적재한다.
메모리가 더 효율적으로 사용된다.
요구 페이징을 사용하면, 프로세스가 실행되는 동안 페이지가 메모리에 있거나, 보조기억장치에 있을 수 있다.
이를 구분하기 위해, 이전에 설명했던 유효 비트를 사용한다.
유효한 경우(valid) 페이지는 메모리에 적재되어 있다는 것을 나타내고, 유효하지 않은 경우(invalid) 페이지는 보조기억장치에 있다는 것을 나타낸다.

요구 페이징을 지원하기 위해 필요한 하드웨어는 페이징과 스와핑을 위한 하드웨어와 동일하다.
프로세스가 메모리에 적재되지 않은 페이지에 접근하려고 하면 "페이지 폴트"가 발생한다.
페이징 하드웨어가 페이지 주소 변환 과정에서 무효(invalid) 비트를 발견하고 운영체제에 트랩을 건다.
페이지 폴트를 처리하는 방법은 아래와 같다.

공유 중인 페이지에 대해 쓰기를 할 때, 그 페이지의 복사본이 쓰기를 수행한 프로세스를 위해 새로 만들어지는 것이다.
fork() 시스템 콜을 통해 자식 프로세스가 만들어지는 과정을 생각해보자.
자식 프로세스는 부모 프로세스와 동일한 상태의 메모리를 가지게 된다.
자식 프로세스는 부모 프로세스와 동일한 페이지를 사용하며 데이터를 읽는다.
만약, 둘 중 하나의 프로세스가 페이지에 쓰기를 수행하는 경우, 해당 페이지의 사본이 해당 프로세스에게 만들어진다.


페이지 폴트가 발생하여 페이지를 메모리에 적재하려고 하는데, 가용한 프레임이 없는 경우, 가용 프레임을 확보하기 위해 사용하지 않는 페이지를 메모리에서 제거해야 한다.
아래 그림은 가용 프레임이 없어서 페이지 교체의 필요성을 나타낸다.
아래 그림에서, 왼쪽 맨 위에 번역 오류가 있다.
프로세스 2를 위한 논리 메모리가 아니고, 프로세스 1을 위한 논리 메모리이다.

현대 운영체제는 "페이지 스와핑"과 "페이지 교체 알고리즘"을 결합하여 페이지 교체를 수행한다.

기본적인 페이지 폴트 루틴은 다음과 같다.
수정 비트는 특정 페이지가 수정된적이 있는지를 나타낸다.
희생될 페이지가 선정되면, 해당 페이지의 수정 비트를 확인하여 수정된적이 있는지 확인한다.
수정된 경우, 보조기억장치에 해당 페이지를 기록한다.
수정되지 않은 경우 아무런 동작도 수행하지 않는다.
이러한 방법을 통해, 페이지가 변경되지 않았다면 I/O 시간을 반으로 줄일 수 있다.
어떤 프로세스가 실행 시간보다 페이징 교체에 더 많은 시간을 사용하고 있는 상황을 말한다.
프로세스에 특정 작업 수행을 위한 최소한의 프레임 개수도 만족 못 할만큼 "충분한" 프레임이 없는 경우, 프로세스는 곧바로 페이지 폴트를 일으킨다.
이때 페이지 교체가 필요하지만, 이미 활발하게 사용되는 페이지들로만 이루어져 있으므로, 어떤 페이지가 교체되든 바로 다시 필요해질 것이다.
따라서, 바로바로 반복적으로 페이지 폴트가 발생한다.
스레싱은 심각한 성능 문제의 원인이 될 수 있다.

위 그림은 실행 중인 프로세스가 많아진다고 성능이 좋아지지는 않는다는 것을 보여준다.
스레싱 현상을 방지하기 위해서는 각 프로세스가 필요로 하는 최소한의 프레임 개수를 보장해야 한다.
각 프로세스가 필요로 하는 최소한의 프레임 수는 지역성 모델을 기반으로 계산한다.
지역성 모델이란 프로세스가 실행될 때에는 항상 어떤 특정한 지역에서만 메모리를 집중적으로 참조함을 말한다.
지역이란 자주 참조되는 페이지들의 집합을 의미한다.

위 그림은 시간에 따른 지역성의 변화를 보여준다.
시간 (a)에서 지역성은 페이지 집합 {18, 19, 20, 21, 22, 23, 24, 29, 30, 33} 이다.
시간 (b)에서 지역성은 {18, 19, 20, 24, 25, 26, 27, 28, 29, 31, 32, 33} 이다.
만약, 필요로 하는 지역성의 크기보다 적은 프레임을 할당하면 스래싱이 일어나게 된다.
지역성을 토대로 프로세스가 일정 시간 동안 참조한 페이지의 집합을 사용한다.
작업 집합의 정확도는 "일정 시간"의 크기에 따라 좌우된다.
시간이 너무 작으면 전체 지역을 포함하지 못할 것이고, 시간이 너무 크면 지역성을 과도하게 수용할 것이다.

페이지 폴트율의 상한과 하한을 정해놓고, 그에 맞는 대응을 하여 스래싱을 방지하는 방법이다.
페이지 폴트율이 너무 높다면 해당 프로세스는 더 많은 프레임이 필요하다는 것이고, 페이지 폴트율이 너무 낮다면 프로세스가 너무 많은 프레임을 갖고 있다는 것을 의미한다.
만약 페이지 폴트율이 상한을 넘으면 프레임을 더 할당해주고, 하한보다 낮아지면 프로세스의 프레임 수를 줄인다.

Linux, Windows, Solaris는 요구 페이징 및 쓰기 시 복사(Copy On Write)를 사용하여 가상 메모리를 유사하게 관리한다.
또한, 각 시스템은 LRU 근사 알고리즘을 사용한다.