[9] 메인 메모리

준제로·2023년 8월 28일

Chapter9. 메인 메모리

메모리를 관리하는 다양한 방법에 대해 알아보자. 메모리 관리 알고리즘은 기본 베어 머신 방식에서 페이징을 사용하는 전략에 이르기까지 다양하다. 각 접근 방식에는 고유한 장단점이 있다.

특정 시스템에 대한 메모리 관리 방법의 선택은 많은 요소, 특히 시스템의 하드웨어 설계에 따라 달라진다. 앞으로 살펴보겠지만 대부분의 알고리즘에는 하드웨어 지원이 필요하므로 많은 시스템에서 하드웨어와 운영체제 메모리 관리를 밀접하게 통합해야한다.

9.1 배경

메인 메모리는 현대 컴퓨터 시스템의 운영에 중심적인 역할을 한다. 메모리는 각각 주소가 할당된 일련의 바이트들로 구성된다. CPU는 PC가 지시하는 대로 메모리로부터 다음 수행할 명령어를 가져오는데, 그 명령어는 필요한 경우 추가적인 데이터를 더 가져올 수 있으며 반대로 데이터를 메모리로 내보낼 수도 있다.

9.1.1 기본 하드웨어

메인 메모리와 각 처리 코어에 내장된 레지스터들은 CPU가 직접 접근할 수 있는 유일한 범용 저장장치이다. 기계 명령어들은 메모리 주소만을 인수로 취하고, 디스크 주소를 인수로 취하지 않기 때문에 모든 실행되는 명령어와 데이터들은 CPU가 직접적으로 접근할 수 있는 메인 메모리와 레지스터에 있어야한다.

  • 각 CPU 코어에 내장된 레지스터들은 일반적으로 CPU 클록의 1사이클 내에 접근이 가능하다.
  • 일부 처리 코어들은 레지스터에 있는 명령어의 해독과 간단한 연산을 클록 틱 당 하나 또는 그 이상의 속도로 처리한다.

  • 그러나 메모리 버스를 통해 전송되는 메인 메모리의 경우는 앞에서 언급했던 상황과 다르다.
  • 메인 메모리의 접근을 완료하기 위해서는 많은 CPU 클록 틱 사이클이 소요되며, 이 경우 CPU가 필요한 데이터가 없어서 명령어를 수행하지 못하고 지연되는(stall) 현상이 발생하게 된다.

⇒ 이러한 상황은 메인 메모리의 접근이 빈번하게 일어나는 경우에는 용납될 수 없다.

해결방법은 CPU와 메인 메모리 사이에 빠른 속도의 메모리를 추가하는 것이다. (통상 빠르게 접근 가능하도록 CPU안에) ⇒ 이러한 캐시는 1.5.5에서 설명되었다. CPU에 구축된 캐시를 관리하여 하드웨어는 어떠한 운영체제의 도움 없이 메모리 접근 속도를 향상한다.

시스템이 올바르게 동작하기 위해서는 사용자 프로그램으로부터 운영체제 영역을 보호해야할 뿐만 아니라 사용자 프로그램 사이도 서로 보호해야한다. 운영체제가 CPU와 메모리간의 접근 중에 개입하게 되면 성능이 떨어지기 때문에 이러한 보호 기법은 반드시 하드웨어가 제공해야한다.

  • 먼저 각각의 프로세스가 독립된 메모리 공간을 가지도록 보장해야한다.
  • 개별적인 프로세스 별 메모리 공간은 서로를 보호하고, 병행 실행을 위해 여러 프로세스가 메모리에 적재되게 하는 것이 필수적이다.
  • 개별적인 메모리 공간을 분리하기 위해서 특정 프로세스만 접근할 수 있는 합법적인 메모리 주소 영역을 설정하고, 프로세스가 합법적인 영역만을 접근하도록 하는 것이 필요하다.
  • 우리는 base와 limit이라고 불리는 두개의 레지스터를 사용하여 이러한 보호 기법을 제공한다.
    - 기준 레지스터(base): 가장 작은 합법적인 물리 메모리 주소의 값을 저장한다.
    - 상한 레지스터(limit): 주어진 영역의 크기를 저장한다.
    - 만약, 기준 레지스터의 값이 300040이고, 상한 레지스터의 값이 120900이라면 프로그램은 300040에서 430940까지의 모든 주소를 접근할 수 있다.

    사용자 모드에서 수행되는 프로그램이 운영체제의 메모리 공간이나 다른 사용자 프로그램의 메모리 공간에 접근하면 운영체제는 치명적인 오류로 간주하고, trap을 발생시킨다. ⇒ 이러한 기법은 사용자 프로그램이 운영체제나 다른 사용자 프로그램의 코드나 데이터 구조를 수정하는 것을 막는다.
  • 기준과 상한 레지스터는 여러가지 특권 명령(special privileged instruction)을 사용하는 운영체제에 의해서만 적재된다. 왜냐하면 특권명령은 오직 커널 모드에서만 수행되고, 운영체제만 커널 모드에서 수행되기 때문이다. ⇒ 이러한 기법은 운영체제만 레지스터 값을 변경할 수 있도록 허가해 줌으로써 사용자 프로그램이 레지스터의 내용을 변경하는 것을 막는다.

9.1.2 주소의 할당

프로그램은 원래 이진 실행 파일 형태로 디스크에 저장되어있다. 실행하려면 프로그램을 메모리로 가져와서 프로세스 문맥 내에 배치해야한다. 이 시점에 가용한 CPU에서 실행할 수 있게 된다.

대부분의 시스템은 사용자 프로세스가 메모리 내 어느 부분으로도 올라올 수 있도록 지원하고 있다. 사용자 프로세스의 주소가 00000번지부터 시작된다고 해서 이 프로그램이 메모리의 00000번지부터 올라와야할 필요는 없다.

9.1.3 논리 대 물리 주소 공간

논리주소: CPU가 생성하는 주소, 가상주소라고도 한다.(논리주소와 물리주소가 다르면)

물리주소: 메모리가 취급하게 되는 주소, 메모리 주소 레지스트(MAR)에게 주어지는 주소

  • 프로그램의 실행 중에는 이와 같은 가상 주소를 물리주소로 바꾸어줘야하는데 이 변환 작업은 하드웨어 장치인 메모리 관리 장치(memory management unit, MMU)에 의해 실행된다.
  • 기준 레지스터는 재배치 레지스터라하며, 재배치 레지스터에 들어있는 값은 주소가 메모리로 보내질 때마다 그 모든 주소에 더해진다.
  • 사용자 프로그램은 결코 실제적인 물리주소에 접근하지 않는다.
  • 사용자 프로스세는 단지 논리 주소만을 만들어 낼 뿐이므로 주소가 메모리 위치 0에서 max 위치까지만 있다고 생각할 것이다. 그러나 이들 논리 주소는 사용되기 전에 실제 물리 주소로 변환되어야한다.
  • 별도의 물리 주소 공간에 연결되어야하는 논리 주소 공간의 개념은 올바른 메모리 관리에 핵심적인 개념이다.

9.1.4 동적 적재

지금까지의 설명에서는 프로세스가 실행되기 위해 그 프로세스 전체가 미리 메모리에 올라와 있어야했다. ⇒ 이 경우 프로세스의 크기는 메모리의 크기보다 커서는 안된다.

  • 메모리 공간의 더 효율적 이용을 위해서는 동적 적재를 해야한다.

  • 동적 적재에서 각 루틴은 실제 호출되기 전까지는 메모리에 올라오지 않고 재배치 가능한 상태로 디스크에서 대기하고 있다.

  • 먼저 main 프로그램이 메모리에 올라와 실행된다. → 이 루틴이 다른 루틴을 호출하게 되면 호출된 루틴이 이미 메모리에 적재됐는지 조사한다. → 만약 적재되어 있지 않다면, 재배치 가능 연결 적재기(relocatable linking loader)가 불려 요구된 루틴을 메모리로 가져오고, 이러한 변화를 테이블에 기록해둔다. 그 후 CPU 제어는 중단되었던 루틴으로 보내진다.

  • 동적 적재의 장점: 루틴이 필요한 경우에만 적재된다. → 이러한 구조는 오류 처리 루틴과 같이 아주 간혹 발생하면서도 실행할 코드가 많은 경우에 특히 유용하다. 이러한 상황에서는 전체 프로그램 크기가 클 수 있지만 사용되는 부분이 훨씬 작을 수 있다.

  • 동적 적재는 운영체제로부터 특별한 지원이 필요없다. 사용자 자신이 프로그램의 설계를 책임져야한다. 운영체제는 동적 적재를 구현하는 라이브러리 루틴을 제공해줄 수 있다.

9.1.5 동적 연결 및 공유 라이브러리

동적 연결 라이브러리(DLL)은 사용자 프로그램이 실행될 때, 사용자 프로그램에 연결되는 시스템 라이브러리다.

  • 동적 연결 개념은 동적 적재의 개념과 유사하다. 동적 적재에서는 로딩(loading)이 실행 시까지 미루어졌었지만 동적 연결에서는 연결(linking)이 실행시기까지 미루어지는 것이다.
  • DDL의 두번째 장점은 이러한 라이브러리를 여러 프로세스 간에 공유할 수 있어 메인 메모리에 DDL 인스턴스가 하나만 있을 수 있다. → 이러한 이유로 DDL은 공유라이브러리라고 하며 Windows 및 Linux 시스템에서 광범위하게 사용된다.
  • 동적 적재와 달리 동적 연결과 공유 라이브러리는 일반적으로 운영체제의 도움이 필요하다.

9.2 연속 메모리 할당

메인 메모리는 운영체제뿌만 아니라 여러 사용자 프로세스도 수용해야한다. 그리고 각 영역은 각각 목적에 맞도록 효율적으로 관리되어야한다. 이 절에서는 초기 메모리 할당 방법의 하나인 연속 메모리 할당에 관해 설명한다.

메모리는 일반적으로 두 개의 부분으로 나누어지는데,

  1. 하나는 운영체제를 위한 것
  2. 다른 하나는 사용자 프로세스를 위한것이다.

운영체제를 낮은 메모리 주소 또는 높은 메모리 주소에 배치할 수 있다. 이 결정은 인터럽트 벡터의 위치와 같은 많은 요소에 따라 달라진다. 그러나 많은 운영체제는 운영체제를 높은 메모리에 배치하므로 그 경우만 논의한다.

일반적으로 여러 사용자 프로세스가 동시에 메모리에 상주하기를 원한다. 연속적인 메모리 할당에서 각 프로세스는 다음 프로세스가 적재된 영역과 인접한 하나의 메모리 영역에 적재된다.


그러나 이 메모리 할당 기법에 대해 더 논의하기 전에 메모리 보호 문제를 해결해야한다.

9.2.1 메모리 보호

CPU 스캐줄러가 다음으로 수행할 프로세스를 선택할 때, 디스패처(dispatcher)는 문맥교환의 일환으로 재배치 레지스터와 상한 레지스터에 정확한 값을 적재한다. CPU에 의해서 생성되는 모든 주소는 이 레지스터들의 값을 참조해서 확인 작업을 거치기 때문에, 우리는 운영체제와 다른 사용자 프로그램을 현재 수행 중인 사용자 프로그램의 접근으로부터 보호할 수 있다.

  • 여기서 재배치 레지스터를 사용함으로 해서 운영체제의 크기는 실행 중이라도 얼마든지 변경될 수 있음을 알 수 있다. 이러한 기능은 매우 유용하게 쓰일 수 있다.
  • 예를 들어, 운영체제에는 장치 드라이버를 위한 코드 및 버퍼 공간이 있는데, 장치 드라이버가 필요한 경우에만 메모리에 적재하고, 필요하지 않은 경우 장치 드라이버를 제거하고 메모리를 다른 요청에 할당할 수 있다.

이제 메모리 할당을 논의해보자

9.2.2 메모리 할당

메모리를 할당하는 가장 간단한 방법 중 하나는 프로세스를 메모리의 가변 크기 파티션에 할당하는 것이다.

  • 각 파티션에는 정확히 하나의 프로세스만 적재될 수 있다.
  • 가변 파티션 기법에서 운영체제는 사용가능한 메모리 부분과 사용 중인 부분을 나타내는 테이블을 유지한다.
    • 처음에는 모든 메모리가 사용자 프로세스에 사용가능하며, 하나의 큰 사용가능한 메모리 블록인 hole로 간주한다. → (앞으로 보겠지만, 결국에는 메모리에는 다양한 크기의 hole이 생기게 된다.)


위 그림은 이 기법을 보여준다.

프로세스가 시스템에 들어오면, 운영체제는 각 프로세스가 메모리를 얼마나 요구하며, 또 사용가능한 메모리 공간이 어디에 얼마나 있는지를 고려하여 공간을 할당한다. 프로세스가 공간을 할당받게 되면, 이후로는 CPU를 할당받기 위해 경쟁한다. 프로세스가 끝내면 메모리를 반납하고, 운영체제는 다른 프로세스에게 이 공간을 할당할 수 있다.

  • 만약, 도착 프로세스의 요구를 충족시키기에 메모리가 충분하지 않으면?
    • 한가지 옵션은 단순히 프로세스를 거부하고 적절한 오류 메세지를 제공하는 것이다.
    • 또는 이러한 프로세스를 대기 큐에 넣을 수 있다. 메모리가 나중에 해제되면 운영체제는 대기 큐를 검사하여 대기 프로세스의 메모리 요구를 충족시킬지 여부를 결정한다.

언급한 것처럼 일반적으로 메모리에는 다양한 크기의 hole이 여기저기 산재하게 된다.

프로세스에 공간이 필요할 때 운영체제는 이 hole의 집합에서 적절한 것을 찾아내야한다.

만약 hole을 찾았는데 그것이 요청한 것보다 크면 두개로 나누어 한 조각은 프로세스에 할당하고 다른 하나는 hole 집합으로 돌아간다.


동적 메모리 할당 문제: 일련의 가용공간 리스트로부터 크기 n바이트 블록을 요구하는 것을 어떻게 만족시켜 줄 것이냐를 결정하는 문제이다. 이러한 문제에 대한 해결책은 여러개가 제시되어있다.

  • 최초 적합: 첫번째 사용가능한 가용 공간을 할당한다. 검색은 집합의 시작에서부터하거나 지난번 검색이 끝났던 곳에서 시작할 수 있다. 충분히 큰 가용 공간을 찾았을 때 검색을 끝낼 수 있다.
  • 최적 적합: 사용가능한 공간 중에서 가장 작은 것을 택한다. 리스트가 크기 순으로 되어 있지 않다면 전 리스트를 검색해야만 한다. 이 방법은 아주 작은 가용 공간을 만들어 낸다.
  • 최악 적합: 가장 큰 가용 공간을 택한다. 이 방식에서 할당해주고 남게 되는 가용 공간은 충분히 커서 다른 프로세스들을 위하여 유용하게 사용될 수 있다. 이 때 가용 공간들이 크기 순으로 정렬되어 있지 않다면 전 리스트를 다 검색해야만한다.

⇒ simulation을 통해 연구해보면 최초적합과 최적 적합 모두가 시간과 메모리 이용효율측면에서 최악 적합보다 좋다는 게 입증되었다. 최초 적합이나 최적 적합이나 공간 효율성 측면에서는 어느 것이 항상 더 좋다고 말할 수는 없지만 최초적합이 일반적으로 속도가 더 빠르다.

9.2.3 단편화

최초 적합 전략과 최적 적합 전략 모두 외부 단편화로 인해 어려움을 겪는다.

프로세스들이 메모리에 적재되고 제거되는 일이 반복되다 보면, 어떤 가용 공간들은 너무 작은 조각이 되어 버린다.

외부 단편화: 외부 단편화는 이처럼 유휴 공간들을 모두 합치면 충분한 공간이 되지만, 그것들이 너무 작은 조각으로 여러 곳에 분산되어 있을 때 발생한다. 즉, 메모리는 너무 많은 수의 매우 작은 조각들로 단편화되어있는 것이다. 이 모든 가용 공간들을 합쳐 하나의 큰 가용 공간을 만들면 여기에 여러 개의 프로세스를 실행 시킬 수 있을 것이다.


메모리의 전체 크기와 프로세스의 크기들은 모두 외부 단편화에 따라 큰 영향을 미칠 수 있다. 예를 들어, 최초 적합의 경우 통계적인 분석을 해보면 N개의 블록이 할당되었을 때 0.5N개의 블록이 단편화 때문에 소실될 수 있다는 것을 알 수 있다. 이 현상은 50% 규칙으로 알려져있다.

  • 일반적으로는 메모리를 먼저 아주 작은 공간들로 분할하고 프로세스가 요청하면 할당을 항상 이 분할된 크기의 정수배로만 해주는 것이 보통이다.
  • 이 경우 할당된 공간은 요구된 공간보다 약간 더 클 수 있다.
  • 이들 두 크기 사이의 남는 부분이 바로 내부 단편화이다.

외부 단편화 문제를 해결하는 다른 방법으로는 압축(compaction)이 있다. 이 방법은 메모리 모든 내용을 한군데로 몰고 모든 가용 공간을 다른 한군데로 몰아서 큰 블록을 만드는 것이다. 그러나 항상 압축이 가능한 것은 아니다.

재배치가 어셈블 또는 적재시에 정적으로 행해진다면, 압축을 실행될 수 없다. ⇒ 압축은 프로세스들의 재배치가 실행시간에 동적으로 이루어지는 경우에만 가능하다.


외부 단편화 문제를 해결할 수 있는 다른 방법은 한 프로세스의 논리 주소공간을 여러개의 비연속적인 공간으로 나누어 필요한 크기의 공간이 가용해지는 경우 물리 메모리를 프로세스에 할당하는 방법이다. ⇒ 이것은 컴퓨터 시스템에 가장 일반적인 메모리 관리 기법인 페이징에서 사용되는 전략이다.

9.3 페이징

지금까지 논의된 메모리 관리는 프로세스의 물리 주소 공간이 연속적이어야했다. 이제 프로세스의 물리 주소 공간이 연속되지 않아도 되는 메모리 관리 기법인 페이징을 소개한다.

⇒ 페이징은 연속 메모리 할당을 괴롭히는 두가지 문제인 외부 단편화와 관련 압축의 필요성을 피한다. 많은 이점을 제공하기 때문에 대형 서버용 시스템에서 모바일 장치용 시스템에 이르기까지 대부분의 운영체제에서 다양한 형태의 페이징이 사용된다.

페이징은 운영체제와 컴퓨터 하드웨어 간의 협력을 통해 구현된다.

9.3.1 기본 방법

  • 물리메모리는 frame이라 불리는 같은 크기 블록으로 나누어진다.
  • 논리메모리는 page라 불리는 같은 크기의 블록으로 나누어진다.
  • 논리 주소 공간은 물리 주소공간으로부터 완전히 분리 되었기 때문에 물리 메모리의 크기가 2^64바이트보다 적게 장착된 시스템에서도 프로세스는 64비트로 이루어진 논리 주소 공간을 사용할 수 있다.
  • CPU에서 나오는 모든 주소는 페이지 번호(p)와 페이지 오프셋(d:offset) 두개의 부분으로 나누어진다.
  • 페이지 번호는 페이지 테이블을 액세스할 때 사용된다.
  • 페이지 테이블은 물리 메모리의 각 프레임의 시작 주소를 저장하고 있으며 오프셋은 참조되는 프로임 안에서의 위치이다.
  • 프레임의 시작주소와 페이지 오프셋이 결합하여 물리 메모리 주소가 된다.


예시) 4B의 페이지 크기와 32B의 물리메모리(8페이지)를 사용하여 프로그래머가 보는 메모리가 물리 메모리로 사상되는 예를 보자.

  • 페이징 자체는 일종의 동적 재배치이다.
  • 모든 논리주소는 페이징 하드웨어에 의해 실제 주소로 바인딩된다.
  • 페이징을 사용하는 것은 각 메모리 프레임마다 하나씩 기준 레지스터를 테이블로 유지하는 것과 유사하다.
  • 페이징 기법을 사용하면 외부 단편화가 일어나지 않는다. 모든 놀고 있는 프레임이 프로세스에 할당될 수 있기때문이다.
  • 그러나 이제는 내부 단편화가 발생한다. 할당은 항상 프레임의 정수배로 할당되기 때문이다.
    • 만약 프로세스가 페이지 경계와 일치하지 않는 크기의 메모리를 요구한다면, 마지막 페이지 프레임은 전부 할당되지 않는다.
    • 프로세스 크기가 페이지 크기와 무관하다면 평균적으로 프로레스 당 반 페이지 정도의 내부 단편화가 예상된다.
    • 이런 측면에서는 작은 페이지 크기가 바람직하다는 것을 알 수 있다.
    • 그러나 페이지 크기가 작아지면 그에 반비례하여 페이지 테이블의 크기가 커지게 되고 이 테이블이 차지하는 공간은 낭비된다.
    • 디스크의 입장에서는 페이지의 크기가 클수록 효율적이다.(11장 참조)
    • 일반적인 추세는 페이지 크기가 프로세스, 자료, 그리고 메인 메모리가 커짐에 따라 함께 커져왔다.
  • 페이징의 가장 중요한 특징은 메모리에 대한 프로그래머의 인식과 실제 내용이 서로 다르다는 것이다.
    • 프로그래머는 메모리가 하나의 연속적인 공간이며, 메모리에는 이 프로그램만 있다고 생각한다.

    • 그러나 실제로는 프로그램은 여러 곳에 프레임 단위로 분산되어 있고, 많은 다른 프로그램이 올라와있다.

    • 프로그래머가 생각하는 메모리와 실제 물리 메모리의 차이는 주소 변환 하드웨어에 의해 해소된다.

    • 논리 주소는 물리 주소로 변환된다. 이 사상은 프로그래머에게는 안보이고 운영체제에 의해 조정된다.

    • 사용자 프로세스는 자기의 것이 아닌 메모리는 접근 조차 할 수 없다. 페이지 테이블을 통하지 않고서는 다른 공간에 접근할 길이 없으며, 페이지 테이블은 그 프로세스가 소유하고 잇는 페이지들만을 가리키고 있기 때문이다.

운영체제는 물리 메모리를 관리하기 때문에 물리 메모리의 자세한 할당에 대해 파악하고 있어야한다. 즉, 어느 프레임이 할당되어 있고, 어느 프레임이 사용가능한지, 총 프레임은 몇개나 되는지 등을 알아야한다. ⇒ 이런 정보는 일반적으로 프레임 테이블(frame table)이라는 시스템에 하나 밖에 없는 자료 구조에 있다

frame table: 각 프레임당 하나의 항목을 가지고 있으며, 프레임이 비어있는지, 할당되었는지, 그리고 할당되었다면 어느 프로세스의 어느 페이지에 할당되었는지를 나타낸다.

9.3.2 하드웨어 지원

  • 대부분의 컴퓨터는 페이지 테이블을 메인 메모리에 저장하고, 페이지 테이블 기준 레지스터(page-table base register, PTBR)로 하여금 페이지 테이블을 가리키도록한다.
  • 다른 페이지 테이블을 사용하려면 단지 이 레지스터만을 변화시키면 되고, 따라서 문맥교환시간을 줄일 수 있다.

9.3.2.1 TLB(Translation Look-Aside Buffer)

  • 메인 메모리에 페이지 테이블을 저장하면 문맥교환 속도가 빨라지지만, 메모리 액세스 시간이 느려질 수도 있다.
    • 메모리 i에 액세스하려고한다고 가정하자. 먼저 페이지 번호를 기준으로 PTBR 오프셋의 값을 사용하여 페이지 테이블의 항목으로 찾는다.
    • 이 작업에는 한번의 메모리 액세스가 필요하다
    • 이렇게 얻은 프레임 버호와 페이지 오프셋을 결합하여 실제 주소를 생성한다.
    • 그런 다음 메모리에서 원하는 위치에 액세스 할 수 있다.

⇒ 이 기법을 사용하면 데이터에 액세스하려면 두번의 메모리 액세스가 필요하다. (한번은 페이지 테이블 항목과 한번은 실제 데이터)

⇒ 따라서 메모리 접근 시간은 2배로 느려지고 이는 대부분의 상황에서 허용할 수 없는 지연시간이다.


이 문제에 대한 해결에는 TLB(translation lokk-aside buffers)라고 불리는 특수한 소형 하드웨어 캐시가 사용된다.

  • TLB는 매우 빠른 연관 메모리로 구성된다.
  • TLB 내의 각 항목은 key와 value의 두부분으로 구성된다.
  • TLB에 페이지를 찾아달라고 요청이 들어오면 이 찾고자하는 페이지를 동시에 여러개의 내부키(페이지 번호)와 비교한다.
  • 검색의 속도는 빠르고 현대 하드웨어에서의 TLB 검색은 명령어 파이프라인의 일부로 동작하여 성능에 추가적인 손해를 끼치지 않는다.
    • 그러나 파이프라인 단계 동안 검색을 하기 위해서는 TLB의 크기는 작게 유지할 수밖에 없으며 통상 32개에서 1,024개의 항목을 유지한다.
  • 페이지 테이블과 함께 다음과 같이 사용된다.
    - TLB는 페이지 테이블의 일부분만을 저장한다.
    - CPU가 논리 주소를 생성하면 MMU는 해당 페이지 번호가 TLB에 있는지 확인한다.
    - 페이지 번호가 발견되면 해당 프레임 번호를 즉시 알 수 있고, 메모리를 접근하는데 사용된다.
    - 페이지 번호가 TLB에 없으면(TLB미스) 주소 변환은 9.3.1절에서 설명한 단계에 따라 진행되며, 여기서 페이지 테이블에 대한 메모리 참조가 이루어져야한다. → 프레임 번호가 확보되면 이를 사용하여 메모리에 엑세스 가능하며 페이지 번호와 프레임 번호를 TLB에 추가하여 다음 참조에서 빠르게 찾을 수 있도록 한다.
    - 만약 TLB가 가득차면, 기존 항목 중에서 교체될 항목을 선택해야한다. 교체 정책은 LRU부터 라운드로빈, 무작위등 다양한 정책이 있다.
    - 어떤 TLB는 각 항목에 ASIDs(address-space identifiers)를 저장하기도한다.
    - ASID는 그 TLB항목이 어느 프로세스에 속한 것인지를 알려주며 그 프로세스 정보를 보호하기 위해 사용된다.
    - ASID지원이 없으면 새로운 페이지 테이블이 선택될 때마다, 다음 실행 프로세스가 잘못 변환하지 않도록 TLB는 전부 플러시(flush)되어야한다. 그렇지 않으면 TLB에는 이전 프로세스가 사용하던 페이지 번호와 프레임 번호가 남아 무효가 된 주소를 공급해줄 수 있다.
    - hit ratio: 접근하려는 메모리의 페이지 번호가 TLB에서 발견되는 비율

9.3.3 보호

페이징 환경에서 메모리 보호는 각 페이지에 붙어 있는 보호 비트에 의해 구현된다. 이 비트들은 보통 페이지 테이블에 속해있다.

  • 페이지 테이블의 각 엔트리에는 유효/ 무효라는 하나의 비트가 더 있다.
    • 유효(valid) : 관련된 페이지가 프로세스의 합법적인 페이지임을 나타낸다.
    • 무효(invalid) : 그 페이지는 프로세스의 논리 주소 공간에 속하지 않는다.
  • 페이지 테이블 길이 레지스터 (PTLR) : 페이지 테이블의 크기를 나타내기 위한 레지스터
  • 프로세스가 제시한 주소가 유효한 범위 내에 있는 지를 확인하기 위해 모든 논리 주소값이 PTLR 값과 비교된다.

9.3.4 공유 페이지

페이징의 장점은 공통의 코드를 공유할 수 있다는 점이다. 여러 프로세스가 있는 환경에서 특히 중요하게 고려해야할 점이다.

  • 코드가 재진입 코드인 경우 공유할 수 있다.

  • 재진입 코드: 자체 수정을 할 수 없는 코드로서 실행 중에는 절대 변경되지 않는다. 따라서 두 개 이상의 프로세스가 동일한 코드를 동시에 실행할 수 있다.

    표준 C라이브러리 공유 사례

  • 표준 C 라이브러리는 물리 메모리에 하나의 사본만 저장하면 되고, 각 사용자 프로세스의 페이지 테이블은 동일한 물리적 사본으로 매핑시킨다.

  • 따라서 40개의 프로세스를 지원하려면 라이브러리 사본이 하나만 필요하며 이제 필요한 총 공간은 80MB가 아니라 2MB로 크게 절약할 수 있다.

  • libc와 같은 실행 시간 라이브러리 외에도 컴파일러, 윈도 시스템, 데이터 베이스 시스템 등과 같이 많이 사용되는 다른 프로그램도 공유할 수 있다.

9.4 페이지 테이블의 구조

페이지 테이블을 구성하는 가장 일반적인 방법을 소개한다.

  1. 계층적 페이징
  2. 해시 페이지 테이블
  3. 역 페이지 테이블

9.4.1 계층적 페이징

  • 요즘 현대 컴퓨터 들은 메우 큰 주소 공간을 가진다. → 각 프로세스는 페이지 테이블 만을 위해서라도 많은 공간이 필요하게 된다.
  • 이러한 경우 모든 페이지 테이블을 메인 메모리에서 연속적으로 할당하기를 고집하지는 않을 것이다.
  • 한가지 방법은 페이지 테이블을 여러개의 작은 조각으로 나누는 것인데. 이것도 여러 가지 방법이 가능하다
  • 한가지 방법은 2단계 페이징 기법으로서 페이지 테이블 자체가 다시 페이징되게 하는 것이다.

⇒ 각 논리 주소를 사상하기 위해 너무 많은 메모리 접근을 필요로 하기 떄문에 비현실적이다. 이 예에서 일반적으로 64비트 구조에서는 계층적 페이지 테이블이 부적합하다는 것을 알 수 있다.

계층적 페이징은 논리 주소를 여러부분으로 나누고 각 단계는 서로 다른 페이지 테이블 수준을 나타낸다. 주소가 32비트 이상으로 확장됨에 따라 계층 레벨 수가 커질 수 있다. 이 문제를 해결하는 두 가지 전략은 해시 페이지 테이블과 역 페이지 테이블이다.

9.4.2 해시 페이지 테이블

주소 공간이 32비트보다 커지면 가상 주소를 해시로 사용하는 해시 페이지 테이블을 많이 쓴다.

  • 해시 페이지 테이블의 각 항목은 연결 리스트를 가지고 있다.
  • 각 원소는 세 개의 필드를 가진다.
    • 가상 페이지 번호
    • 사상되는 페이지 프레임 번호
    • 연결 리스트 상의 다음 원소 포인터
  • 알고리즘
    • 가상 주소 공간으로부터 페이지 번호가 오면 그것을 해싱한다.
    • 그것으로 해시 페이지 테이블에서 연결 리스트를 따라가며 첫번째 원소와 가상 페이지 번호를 비교해본다.
    • 일치되면 그에 대응하는 펭지 프레임 번호를 가져와 물리 주소를 얻는다.
    • 일치되지 않으면 연결리스트의 그 다음 원소와 똑같은 일을 반복한다.
    • 그림 9.17이 이 과정을 보여주고 있다.
  • 64비트 시스템에서 유용하도록 변형된 해시 테이블 기법 : 클러스터 페이지 테이블

9.4.3 역 페이지 테이블

보통 프로세스는 각자 하나씩 페이지 테이블을 가지고 또 페이지 테이블은 프로세스가 사용하는 페이지마다 하나의 항목을 가진다.

이 기법의 단점 중 하나는 각 페이지 테이블 항목의 개수가 수백만개가 될 수 있다는 것이다. 이러한 테이블은 물리 메모리의 사용을 추적하기 위해 많은 양의 물리 메모리를 소비한다.


이 문제를 해결하는 한 방법이 역페이지 테이블이다.(inverted page table).

  • 역 페이지 테이블에서는 메모리 프레임 마다 한 항목씩을 할당한다.
  • 각 항목은 그 프레임에 올라와 있는 페이지 주소, 그리고 그 페이지를 소유하고 있는 프로세스의 ID를 표시하고 있다.
  • 이렇게 되면 시스템에는 단 하나의 페이지 테이블만이 존재하게 되고, 테이블 내 각 항목은 메모리 한 프레임씩을 가리키게 된다.

9.5 스와핑

스와핑을 통해 시스템은 프로세스에 속하는 페이지를 디스크로 이동하여 다중 프로그래밍 정도를 높일 수 있다.

9.5.1 기본 스와핑

  • 메모리와 백업 저장 장치 간에 프로세스 전체를 이동하는 데 걸리는 시간이 엄청나기 때문에 일반적으로 최신 운영체제에서는 더는 사용되지 않는다.

9.5.2 페이징에서의 스와핑

  • Linux 및 Windows를 포함한 대부분의 시스템은 이제 프로세스 전체가 아닌 프로세스 페이지를 스왑할 수 있는 변형 스와핑을 사용한다.
  • 이 전략은 여전히 물리 메모리를 초과 할당할 수 있지만 프로세스 전체를 스왑하는 비용은 발생하지 않는다.
  • 페이징에서의 스와핑은 가상 메모리와 함께 잘 작동한다.

9.6 사례: Intel 32비트와 64비트 구조

Intel 32비트 아키텍처에는 두가지 수준의 페이지 테이블이 있으며 4KB 또는 4MB 페이지 크기를 지원한다. 이 아키텍처는 또한 페이지 주소 확장을 지원하므로 32비트 프로세스가 4GB보다 큰 물리적 주소 공간에 엑세스 할 수 있다. x86-64 및 ARMv9 아키텍처는 계층적 페이징을 사용하는 64비트 아키텍처다.

profile
.....☆

0개의 댓글