운영체제

[OS/운영체제] 가상 메모리 (Virtual Memory) - (1)

4Legs 2020. 11. 14. 21:03

가상 메모리 (Virtual Memory)

지금까지 살펴본 메모리 관리 기법은 명령어가 반드시 물리적 메모리에서 실행되어야 한다는 필요에 의해 사용되었다. 연산이 반드시 물리적 메모리에서 실행되어야 한다는 것은 당연한 것처럼 보이지만, 물리적 메모리의 크기에 따라 한계를 가지게 된다.

프로그램은 일반적으론 발생하지 않는 오류들에 대한 코드들과 같이 프로그램을 실행하는 동안 어쩌면 절대로 실행되지 않을 부분들이 존재한다. 또한 배열, 테이블과 같은 자료구조들은 보통 실제로 필요한 양보다 더 많은 양의 메모리를 할당받는다. 이런 상황들을 보았을 때, 프로그램의 모든 부분이 항상 동시에 필요하진 않을 수 있다.

따라서, 프로그램의 일부만 실제 메모리를 할당하여 실행하는 방법은 효과적일 것이다. 프로그램은 더 이상 물리적 메모리의 사용 가능한 양에 따라 제약을 받지 않을 것이며, 대신 아주 큰 가상의 주소 공간에 데이터를 쓰게 될 것이다. 각 프로그램이 차지하는 물리적 메모리의 양이 줄어들며 동시에 실행할 수 있는 프로그램의 수가 증가되어 CPU의 활용률도 높일 수 있을 것이다.

 

물리적 메모리보다 더 큰 가상 메모리

가상 메모리(Virtual Memory)는 사용자에게 논리적 메모리와 물리적 메모리를 분리하여 인식하도록 한다. 이러한 분리는 적은 양의 물리적 메모리만 사용 가능할 때에도 프로그래머에게 대용량의 가상 메모리를 제공할 수 있다. 프로그래머들은 개발 중 사용 가능한 물리적 메모리의 양을 더 이상 고려하지 않아도 되기 때문에 개발을 더 쉽게 진행할 수 있게 된다.

가상 주소 공간

프로세스의 가상 주소 공간(Virtual Address Space)은 프로세스가 어떻게 메모리에 저장되어 있는지에 대한 가상의 관점으로 표현한다. 일반적으로 이러한 관점에서 프로세스는 특정 논리적 주소로부터 시작한다. (그림에서는 0이다.)

앞선 내용에서 프로세스는 페이지, 프레임을 통해 물리적 메모리에 비연속적으로 저장될 수 있다고 하였다. 그림에서 함수 호출에 사용되는 Stack과 동적 할당에 사용되는 Heap은 각각 아래, 위 방향으로 커지게 된다. Heap와 Stack 사이의 공간은 논리적 주소 공간으로는 존재하지만, 실제 물리적 페이지를 필요로 하는 순간은 오직 Heap과 Stack이 그만큼 커졌을 때이다. 

가상 메모리를 통한 라이브러리의 공유

또한 논리적 메모리와 물리적 메모리를 분리하였기 때문에, 가상 메모리는 둘 이상의 프로세스가 파일 또는 메모리를 공유하게 해 준다. (이는 앞선 내용의 Page Sharing에서 언급되었다.)

가상 주소 공간에 공유되는 객체를 매핑함으로써 시스템 라이브러리는 여러 프로세스에 공유될 수 있다. 각 프로세스는 공유되는 라이브러리를 자신의 공유 주소 공간의 한 부분으로 생각하지만, 실제로는 여러 프로세스들이 라이브러리가 존재하는 페이지들을 공유하는 것이다.

 

요구 페이징 (Demand Paging)

실행 가능한 프로그램이 어떻게 디스크에서 메모리에 적재되는지를 생각해보자. 만약 프로그램이 사용자가 선택한 일부 기능들의 목록으로 시작한다고 가정할 때, 모든 기능을 포함한 프로그램 전체를 메모리에 적재하는 것은 비효율적이다. 따라서 필요한 만큼만(필요한 기능에 대한 만큼만) 페이지에 적재하는 것이 더 좋은 대안이 될 것이다.

이러한 방법은 가상 메모리 시스템에서 Demand Paging(요구 페이징)라는 기법으로 통용된다. 요구 페이징을 사용하는 가상 메모리는 프로그램이 실행되는 동안, 페이지 사용을 요구받은 경우에만 페이지에 적재한다. 따라서 프로그램의 어떤 기능이 실행 중 단 한 번도 사용되지 않는다면, 그 기능에 해당하는 페이지 또한 물리적 메모리에 적재되지 않는다.

요구 페이징 시스템은 프로세스들이 디스크와 같은 보조 메모리에 담겨 있는 Swapping 페이징 시스템과 유사하다. 하지만 프로세스 전체를 메모리에서 swap하지 않고, Lazy Swapper를 사용한다. 이는 페이지가 요구되기 전까지는 그 페이지를 swap하지 않는다는 것을 의미한다.

우리는 이제 프로세스를 연속적인 커다란 주소 공간이 아닌 페이지의 나열의 관점에서 보고 있기 때문에, 요구 페이징을 설명할 때는 Swapper라는 개념 대신 페이저(Pager)라는 개념을 사용한다.

 

Basic Concepts

요구 페이징 기법에서 메모리에 있는 페이지와 디스크에 있는 페이지를 구분하기 위해 하드웨어의 지원이 필요하다. 이전에 설명한 Valid-Invalid bit 기법은 이러한 목적에 부합하므로 사용할 수 있을 것이다.

단 요구 페이징 기법에서 Valid-Invalid bit은 조금 다른 의미를 갖는다. bit가 Valid라면 이는 해당 페이지가 legal하며 메모리에 존재하고 있음을 나타낸다. bit가 Invalid라면 Valid가 아닌 상태, 즉 프로세스의 논리적 주소 공간에 존재하지 않은 상태에 더해 legal하지만 페이지가 디스크에 존재할 때도 포함한다.

메인 메모리에 일부 페이지가 존재하지 않을 때 페이지 테이블

메모리로 가져오는 페이지에 대한 페이지 테이블 항목은 원래와 같이 설정되지만, 현재 메모리에 없는 페이지에 대한 페이지 테이블 항목은 단순히 Invalid로 표시되거나 디스크에 페이지 주소가 포함되어 있다.

페이지에 Invalid를 표시하는 것은 프로세스가 해당 페이지에 아예 접근하지 않는다면 효과가 없다. 따라서, 만약 우리가 실제로 필요한 페이지들을 올바르게 추측할 수 있다면, 이는 프로세스에 대한 모든 페이지들을 사용하는 것과 동일한 효과를 가질 것이다. 

 

페이지 결함 (Page Fault)

하지만 만약 프로세스가 아직 메모리에 적재되지 않은 페이지를 접근하려 하면 어떻게 될까? Invalid로 표시된 페이지에 접근하는 것을 페이지 결함(Page Fault)이라고 한다. 페이지 결함이 발생하면 다음과 같은 과정을 통해 그 페이지를 메모리에 적재한다.

Steps in handling a page fault

페이지 결함이 발생하면 운영체제에 페이지 결함 트랩(Page Fault Trap)을 발생시킨다. (그림의 2번에 해당한다.)

 PCB에 존재하는 내부 테이블을 통해 참조의 유효성을 검사한다. 만약 프로세스의 가상 주소 공간을 벗어난 참조라면 프로세스는 종료된다. (그림의 3번에 해당한다.)

사용 가능한 빈 프레임을 찾아 디스크 입출력을 통해 페이지를 프레임에 적재한다. (그림의 4번에 해당)

PCB의 내부 테이블과 페이지 테이블을 메모리에 적재된 페이지를 가리키도록 수정한다. (그림의 5번에 해당)

⑤ 페이지 결함 트랩을 발생시켰던 명령어를 재실행한다. (그림의 6번에 해당)

 

극단적으로는 메모리의 페이지를 하나도 가지지 않은 상태로도 프로세스를 실행할 수도 있다. 이를 순수 요구 페이징(Pure Demand Paging)이라 한다. 이 방법은 프로세스 실행 초기에 많은 페이지 결함을 발생시킨다.

 

요구 페이징의 중요한 조건은 페이지 결함이 발생한 이후 페이지 결함을 발생시켰던 명령어를 재수행할 수 있는 능력이다. 인터럽트가 발생한 프로세스를 원래 실행되어야 했던 동일한 환경과 위치에서 재실행하기 위해 프로세스의 상태를 저장하기 때문이다. 일반적인 경우에 이는 그다지 어려운 문제가 아니다.

하지만 어떤 명령어가 여러 다른 위치를 수정할 수 있다면 이는 꽤 큰 문제가 된다. 한 명령어가 둘 이상의 다른 페이지에 접근하는 경우에는 그 명령어를 간단히 다시 실행할 수 없을 수도 있다. 이러한 문제는 두 가지 방법으로 해결될 수 있다.

 - 명령어 수행에 필요한 모든 페이지를 미리 메인 메모리에 적재한 다음 명령어를 다시 수행한다.

 - 임시 레지스터를 이용해 갱신 전의 값을 저장해둔 후, 페이지 결함이 발생했을 때 이전 값을 다시 원래 위치에 쓴다.

 

* 참조의 지역성(Locality of Reference) : 어떤 프로그램이 수행되는 동안 실행되는 명령어들에 의해 여러 개의 새 페이지에 접근할 수도 있다. 이는 다수의 페이지 결함을 일으킬 수 있지만, 참조의 지역성에 의해 실제로 이런 현상은 드물게 일어난다.

 

요구 페이징의 성능 (Performance of Demand Paging)

요구 페이징의 성능을 측정하기 위해, 우리는 유효 접근 시간(Effective Access Time)을 계산해볼 수 있다. 대부분의 컴퓨터 시스템에서의 메모리 접근 시간(ma)은 10 ~ 200nanoseconds 사이이다. 만약 페이지 결함이 발생하지 않는다면, 유효 접근 시간은 이 메모리 접근 시간과 같을 것이다. 하지만 페이지 결함이 발생한다면, 관련된 페이지를 디스크에서 읽은 후에야 원하는 동작이 가능할 것이다.

페이지 결함이 발생할 확률을 p라고 하자. p는 0과 1 사이의 값을 갖는다. 즉 페이지 결함이 발생하는 횟수를 최소로 하고자 한다는 것은 p의 값을 0에 최대한 가깝게 하고자 하는 것과 같다.

이 때 유효 접근 시간은 다음과 같다.

 

 

 

※ 본 게시글은 『Operating System Concepts』 를 참고하여 작성되었습니다.