박종훈 기술블로그

운영체제 10장 - 가상 메모리 (1) - 기본

가상 메모리

이 장의 목표

  • 가상 메모리를 정의하고 그 이점을 설명한다.
  • 요구 페이징을 사용하여 페이지가 메모리에 로드되는 방법을 설명한다.
  • FIFO, 최적 및 LRU 페이지 교체 알고리즘을 적용한다.
  • 프로세스의 작업 집합을 설명하고 프로그램 지역성과 어떤 관련이 있는지 설명한다.
  • Linux Windows 10 및 Solaris가 가상 메모리를 관리하는 방법을 설명한다.
  • C 프로그래밍 언어로 가상 메모리 관리자 시뮬레이션을 설계한다.

10.1 배경

실행중인 코드는 반드시 물리 메모리에 있어야 한다는 것은 타당한 요구 조건으로 보이지만, 프로그램의 크기를 물리 메모리의 크기로 제한한다는 점 때문에 환영할만한 요구 조건은 아니다.

프로그램을 일부분만 메모리에 올려놓고 실행할 수 있다면 다음과 같은 이점이 있다.

  • 프로그램은 물리 메모리 크기에 의해 더는 제약받지 않게 된다. 가상의 매우 큰 공간을 가정하고 프로그램을 만들 수 있다.
  • 각 프로그램이 더 작은 메모리를 차지하므로 더 많은 프로그램을 동시에 수행할 수 있게 된다. 이에 따라 응답 시간은 늘어나지 않으면서도 CPU 이용률과 처리율이 높아진다.
  • 프로그램을 메모리에 올리고 스왑(swap)하는 데 필요한 I/O 횟수가 줄어들기 때문에 프로그램들이 보다 빨리 실행된다.

왜 응답 시간이 늘어나지 않을까?
프로그램의 일부만 필요한 때에 메모리에 로드하더라도 CPU는 이미 있는 부분을 실행하면서 나머지 부분을 백그라운드에서 가져올 수 있습니다.
이로써 사용자 관점에서 시스템이 응답성을 유지하는 동안 전체 응답 시간 및 처리 시간이 크게 증가하지 않습니다.
ChatGTP 답변

Response Time과 Turnaround Time의 차이
Turnaround Time는 프로세스가 완료된 시간.
(원문에는 두 가지 다 언급되지만 번역본에서는 응답 시간만 이야기 해둬서 찾아봄.)

스왑(swap)하는 데 필요한 I/O 횟수가 줄어드는 이유
추가로 필요한 것만 올리고 내리면 되기 때문

diagram-showing-virtual-memory-that-is-larger-than-physical-memory

시스템과 사용자 모두에게 이득이 된다.

가상 메모리는 실제의 물리 메모리 개념과 개발자의 논리 메모리 개념을 분리한 것이다.
이렇게 하면 작은 메모리를 가지고도 얼마든지 큰 가상 주소 공간을 프로그래머에게 제공할 수 있다.
가상 메모리는 메모리 크기에 관련한 문제를 염려할 필요 없이 실제 해결하려는 문제에만 집중할 수 있게 해준다.

프로세스의 가상 주소 공간은 프로세스가 메모리에 저장되는 논리적인 모습을 말한다.

보통 0번지에서 시작하여 연속적인 공간을 차지한다. 물리적으로는 연속되지 않을 수 있으며 이 부분은 MMU(메모리 관리 장치)의 처리에 달려있다.
힙(heap)은 동적 할당 메모리를 사용함에 따라 주소 공간상에서 위쪽으로 확장된다. 스택은 함수 호출을 거듭함에 따라 주소 공간상에서 아래쪽으로 확장된다.
힙과 스택 사이의 공백도 가상 주소 공간의 일부이지만, 힙 또는 스택이 확장되어야만 실제 물리 페이지를 요구하게 된다.

공백을 포함하는 가상 주소 공간을 희소(sparse) 주소 공간이라고 한다.

책에서는 sparse 를 성긴 이라고 적었지만, 희소가 더 적합하다고 생각한다. 그냥 sparse를 쓰는게 좋을지도 모르겠다 싶기 때문에 이 글에서는 sparse를 사용한다.
성긴이 어디서 나온건가 했더니 성기다가 ‘물건의 사이가 뜨다.’ 라는 뜻이라고 한다.

virtual-address-space-of-a-process-in-memory

shared-library-using-virtual-memory

sparse 주소 공간의 공백은 스택이나 힙 세그먼트가 확장될 때 사용되거나 프로그램 실행 중 동적으로 라이브러리를 링크할 필요가 있을 때 사용한다.

가상 메모리는 페이지 공유를 통해 파일이나 메모리가 둘 또는 그 이상의 프로세스들에 의해 공유되는 것을 가능하게 한다. 이는 다음과 같은 장점이 있다.

  • 표준 C 라이브러리와 같은 시스템 라이브러리가 여러 프로세스들에 공유될 수 있다.
  • 프로세스들이 메모리를 공유할 수 있다. → 공유 메모리를 통한 통신 가능
  • fork() 시스템 콜을 통한 프로세스 생성 과정 중에 공유가 될 수 있기 때문에 프로세스 생성 속도를 높일 수 있다.

3번째 장점이 non-blocking을 의미하는건가?
No. 그것이 아닌 실제 주소가 아닌 가상 주소를 반환해도 되므로 거기서 생성 속도를 높일 수 있다고 한 것.
가상 주소를 반환해 주면 이후 페이지 폴트를 통해 실제 메모리로 복사시킬 수 있음.

10.2 요구 페이징

어떻게 실행 프로그램을 보조저장장치에서 메모리로 로드해야할까?

가장 간단한 방법은 프로그램 전부를 물리 메모리에 로드하는 것이다.
그러나 이 방법은 과연 프로그램 전체가 메모리에 있어야 하는가? 라는 의문이 생기게 된다.

다른 전략은 필요한 페이지만 로드하는 것이다. 이 전략을 요구 페이징(demand paging)이라고 하며 가상 메모리 시스템에서 일반적으로 사용된다. 요구 페이징 시스템은 스와핑을 사용하는 페이징 시스템과 유사하다.

필요한 프로그램의 일부만 적재하여 메모리가 더 효율적으로 사용된다.

10.2.1 기본개념

요구 페이징의 기본 개념은 필요할 때만 페이지를 메모리에 로드하는 것이다.

그러면 일부 페이지는 메모리에 있고 일부 페이지는 보조저장장치에 있을 수 있다.

이를 구분하기위해 9장에서 사용한 valid/invalid 비트를 사용한다. 메모리에 있는 경우에는 valid로 설정하며 올라와 있지 않은 경우에는 invalid로 설정한다.

page-table-with-valid-invalid-bit

프로세스가 메모리에 올라와 있지 않은 페이지에 접근하려고 페이지 폴트 트랩을 발생시킨다.

페이지 폴트를 처리하는 과정은 다음과 같다.

steps-in-handling-a-page-fault

  1. 프로세스에 대한 내부 테이블(internal table)을 검사해서 valid/invalid 여부를 체크한다.
  2. 만약 invalid 한 요청이라면 프로세스를 중단(terminate)한다. 만약 valid 요청이지만 아직 메모리에 올라오지 않았다면, 그것을 보조저장장치로부터 가져와야 한다.
  3. 빈 공간(가용 프레임, free frame)을 찾는다.
  4. 보조장치에 해당 가용 프레임으로 해당 페이지를 읽어들이도록 요청한다.
  5. 읽기가 끝나면, 페이지 테이블의 상태를 업데이트 한다.
  6. 트랩에 의해 중단되었던 명령어를 다시 수행한다. 프로세스는 그 페이지가 메모리에 있었던 것처럼 해당 페이지에 접근할 수 있다.

순수 요구 페이징은 메모리에 페이지가 하나도 안 올라와 있는 상태에서 프로세스를 시작한다. 페이지 폴트를 통해 일단 필요한 모든 페이지가 로드되면 더 폴트가 발생하지 않는다.

모든 프로그램은 참조의 지역성(locality of reference) 이라는 성질이 있다. 프로그램의 어느 한 특정 작은 부분만 집중적으로 참조하는 것을 말한다. 이러한 성질로 인해 요구 페이징은 만족할 만한 성능을 보인다.

요구 페이징을 지원하기 위해 필요한 하드웨어는 페이징과 스와핑을 위한 하드웨어와 동일하다. (페이지 테이블, 보조저장장치)

요구 페이징을 위한 필수적인 요구 사항은 페이지 폴트 오류 처리 후에 명령어 처리를 다시 시작할 수 있어야 한다는 것이다.

IBM System360/370 MVC (move character) instruction 을 예로 들어보자.

이 명령어는 최대 256바이트를 한 메모리 주소에서 다른 메모리 주소로 옮길 수 있다. (이때 서로 겹칠 수 있다.)

어느 블록이라도 페이지 경계(page boundary)에서 양쪽에 걸쳐 있으면 이동이 다 끝나지도 않은 상태에서 페이지 폴트가 발생할 수 있다. 더구나 원천 블록(source block)과 목적 블록(destination block)이 서로 겹쳐져 있는 경우에는 단순히 명령어를 실행하는 것만으로는 문제가 해결되지 않는다.

두 가지 해결 방법이 있다.

한가지 해결책은 마이크로코드가 두 블록의 양쪽 끝을 모두 계산하여 겹치지 않는다는 것을 확인한 후 액세스를 시도하는 것이다. 만약 페이지 폴트가 발생할 가능성이 있다면 미리 페이지 폴트를 발생시키고 그 다음 이동을 한다. (관련 페이지가 미리 모두 로드된 상태이므로 페이지 폴트가 일어나지 않는다.)

다른 해결책은 이동에 의해서 이전의 내용이 지워질 기억 장소들의 값을 보존하기 위해 임시 레지스터를 사용하는 것입니다. 페이지 폴트가 발생하였을 때, 이전 상태를 보존해두었기 때문에 다시 복구하여 명령어를 다시 수행합니다.

위에서 소개한 것은 요구 페이징을 지원하기 위해 하드웨어가 해결해줘야 할 문제 중 일부다. 요구 페이징을 지원해 주기 위해서는 복잡한 여러 문제가 발생할 수 있다.

예시가 다소 이해하기 어렵다.
정리 하면 “페이지 폴트 오류 처리 후에 명령어 처리를 다시 시작할 수 있어야 한다는 것이다.” 를 만족해야 하는데 어떻게 만족할 수 있는가에 대한 부분이고 이에 대해서 2가지 방법을 제시하는 내용이다. 방법은 아래와 같다.
방법 1. 페이지 폴트가 날 것 같으면 미리 페이지 폴트를 발생시켜 다 로드를 시킨 후 연산을 처리하자.
방법 2. 임시 레지스터에 데이터를 보관해 두어, 페이지 폴트가 발생했을 때를 대비하자.