앞서 CSAPP의 1장 7.3에서 가상메모리에 대해 언급된 내용은 다음과 같다.

가상메모리(virtual memory, VM)는 각 프로세스가 메인 메모리 전체를 독점적으로 사용하고 있는 것 같은 환상을 제공하는 추상화다. 각 프로세스는 가상주소공간이라고 하는 균일한 메모리의 모습을 갖게 된다. (..중략..) 가상메모리가 작동하기 위해서는 하드웨어와 운영체제 소프트웨어 간의 복잡한 상호작용이 필요하다. 기본적인 아이디어는 프로세스의 가상메모리의 내용을 디스크에 저장하고, 메인 메모리를 디스크의 캐시로 사용하는 것이다.

리눅스 프로세스들의 가상주소공간 (위쪽으로 갈수록 주소 증가)

9장에서는 가상메모리에 대해 더 자세히 설명하고 있다. 가상메모리의 동작 방식과 필요성에 대해 같이 살펴보자~~

(3판 기준, 오역과 오탈자가 좀 많아서 원서와 함께 보시는 것을 추천드립니다ㅎㅎ)

 

들어가기 전..

  • 가상메모리는 각 프로세스에 사적 주소공간(private address space)을 제공
  • 가상메모리는 하드웨어, 메인 메모리, 디스크 파일, 커널 소프트웨어들 사이의 상호작용
  • 가상메모리의 주요 기능 3가지
    • 캐싱 도구: 메인 메모리를 디스크에 저장된 주소공간에 대한 캐시로 취급
    • 메모리 관리 단순화: 각 프로세스에 통일된 주소공간을 제공
    • 메모리 보호: 각 프로세스의 주소공간을 다른 프로세스에 의한 손상으로부터 보호

물리 및 가상 주소 방식

  • 메인 메모리는 연속적인 셀(바이트 크기)로 구성되며, 각 바이트는 고유의 물리 주소(physical address, PA)를 가진다. 원래는 CPU가 이 물리 주소를 통해 메모리에 접근하는 게 가장 자연스러운 방식이다. 이 방식을 물리 주소 방식(physical addressing)라고 한다. 초기의 PC들은 물리 주소 방식을 사용했지만 현대의 프로세서들은 가상 주소 방식(virtual addressing)을 사용한다.
  • 가상 주소 방식에선 CPU가 가상 주소(virtual address, VA)를 생성해서 메인 메모리에 접근하게 되는데, 이때 가상 주소는 메인 메모리로 보내지기 전에 물리 주소로 변환된다. 👉 주소 번역(address translation)
    • 주소 번역은 CPU 하드웨어와 운영체제의 긴밀한 협력을 요한다.
    • 프로세스 실행 중, CPU 칩 내에 있는 MMU(memory management unit)이라는 하드웨어가 메인 메모리에 저장되어있는 어떤 참조 테이블(lookup table)을 사용하여 가상 주소를 물리 주소로 번역한다.
    • 이때 참조 테이블은 운영체제가 관리한다.

주소공간

  • 주소공간은 음수가 아닌 정수 주소의 정렬된 집합이다. 책에서는 선형 주소공간을 가정하고 있다.
    • 선형 주소공간(linear address space): 연속적인 정수로 이루어진 주소공간
  • 가상메모리를 갖는 시스템에서 CPU는 가상주소공간이라 불리는 주소공간에서 가상의 주소를 생성하며, 현대 시스템은 주로 32비트 또는 64비트 가상 주소공간을 지원한다. 또한 시스템은 물리메모리로 대응되는 물리주소공간도 갖는다.

캐싱 도구로서의 VM

  • 가상메모리는 디스크에 저장된 N개의 바이트 크기의 셀 배열로 구성되며, 이때 각 바이트는 특정한 가상주소를 가지며 셀 배열의 인덱스로 작용한다. 디스크 안의 배열 정보는 메인 메모리에 캐시된다.
  • 메모리 계층구조에서의 다른 캐시들과 마찬가지로, 디스크(상대적으로 낮은 레벨)에 있는 데이터는 블록 단위로 구분되고, 이러한 블록은 디스크와 메인 메모리(상대적으로 높은 레벨) 사이의 전송 단위로 사용된다.
    • 블록(block): 고정된 길이의 패킷(packet - 데이터 전송에서 사용되는 데이터의 묶음) 정보
  • VM 시스템은 가상메모리를 특정 사이즈의 블록 단위로 분할하여 관리하며, 이렇게 분할된 블록들은 가상페이지라고 부른다.
    • 이러한 가상페이지들의 집합은 다음과 같이 3개의 부분집합으로 나누어진다. (이 부분집합들은 서로 중첩되지 않는다.)
      • Unallocated: VM 시스템에 의해 아직 할당되지 않은 페이지로, 어떠한 데이터도 가지고 있지 않아 디스크상에 아무런 공간도 차지하지 않음. (반면 할당된 페이지는 이와 달리 데이터를 가지고 있을 것이고 따라서 디스크 상에서 공간을 차지하고 있을 것이다.)
      • Cached: 할당된 페이지들 중 현재 물리 메모리에 캐시된 페이지들
      • Uncached: 할당된 페이지들 중 현재 물리 메모리에 캐시되지 않은 페이지들
  • 물리페이지도 이런 식으로 물리페이지로 분할되어 사용된다.
  • 이런 식으로 가상 메모리 및 물리 메모리를 블록 단위(페이지)로 분할하여 관리하는 이유는, 메모리를 하나하나 가상주소에서 물리주소로 번역하게 되면 작업 부하가 너무 높아지기 때문이다.

DRAM 캐시의 구성

DRAM? SRAM?
SRAM은 DRAM에 비해 다음과 같은 장점을 가진다. : 빛이나 전기적 잡음과 같은 외란에 민감하지 않고, DRAM은 주기적으로 내용을 갱신해줘야 하는 반면 SRAM은 기억 장치에 전원이 공급되는 한 그 내용이 계속 보존되며 따로 리프레시할 필요가 없다. 또한 DRAM보다 더 빨리 접근할 수 있다.
하지만 다음과 같은 단점도 가진다. : SRAM은 DRAM에 비해 트랜지스터를 더 많이 사용하기 때문에 밀도가 더 낮고, 더 비싸며, 전력도 더 많이 소모한다.

보통 SRAM은 캐시 메모리에, DRAM은 메인 메모리에 사용된다.
  • DRAM 캐시는 완전 결합성(fully associative cache)이다. 👉 어떠한 가상페이지든 어떠한 물리페이지에 들어갈 수 있다.
    • cf.) SRAM 캐시에서의 완전 결합성: 하나의 캐시 집합에 모든 캐시 라인이 들어간다.
  • DRAM 캐시 미스는 SRAM 캐시 미스에 비해 미스 비용이 매우 높다.
    • 이는 SRAM 캐시 미스의 경우 메인 메모리에서 처리되는 반면 DRAM 캐시 미스는 디스크에서 처리되기 때문이다.

페이지 테이블

  • 페이지 테이블은 물리 메모리에 저장된 자료구조로, 가상페이지를 물리페이지로 매핑한다.
  • MMU 내의 주소 번역 하드웨어는 가상주소를 물리주소로 변환할 때마다 페이지 테이블을 본다.
  • 운영체제는 페이지 테이블의 콘텐츠를 관리하고, 페이지들이 디스크와 DRAM 사이에서 오가는 것을 관리한다.
  • 페이지 테이블은 배열이며, 페이지 테이블의 원소 하나하나를 PTE(page table entry)라고 한다.
    • 여기선 PTE가 하나의 유효비트주소 필드로 구성된다고 가정한다.
    • 유효비트는 가상페이지가 DRAM에 현재 캐시되어 있는지를 나타낸다.
      • 유효비트가 세팅되었다면, 주소 필드는 해당 가상페이지와 대응되는 DRAM의 물리페이지의 시작을 나타낸다.
      • 유효비트가 세팅되지 않았다면, 주소 필드는 다음 두 가지 케이스로 나뉜다. : ❶NULL 주소 - 가상페이지가 아직 할당되지 않았음을 나타낸다. ❷주소 필드의 주소는 디스크 상의 가상페이지의 시작 부분을 가리킨다.

페이지 적중(page hits)

CPU가 어떤 가상페이지에 접근하려고 할 때, 페이지 적중이 일어난 경우에 대해 살펴보자.

  • CPU가 가상주소를 생성하고, 이 가상주소를 가지고 CPU 칩 내에 있는 MMU로 요청한다.
  • MMU는 가상주소를 물리주소로 번역하기 위해 메인메모리에 있는 페이지 테이블을 보는데 이때 가상주소를 페이지 테이블이라는 자료구조의 인덱스로서 사용한다.
  • 인덱스로 특정 PTE에 접근했을 때 이 PTE의 유효비트가 1이라면, 이는 찾고 있는 가상페이지가 물리메모리에 캐시되어 있다는 의미이다. MMU는 이를 보고 이 PTE의 주소 필드의 물리페이지 시작 주소를 사용하여 CPU가 찾고 있는 가상페이지에 대응되는 물리 주소를 구성하게 된다.

페이지 오류(page faults)

반대로, CPU가 어떤 가상페이지에 접근하려고 할 때, 페이지 오류(=DRAM 캐시 미스)가 일어난 경우에 대해 살펴보자.

  • MMU가 가상주소를 페이지 테이블에 대한 인덱스로 사용하여 특정 PTE에 접근했을 때 이 PTE의 유효비트가 0이라면, 이는 찾고 있는 가상페이지가 물리메모리에 캐시되어 있지 않다는 의미이다. MMU는 이를 보고 페이지 오류 예외를 유발시킨다.
  • 페이지 오류 예외는 커널 내에 페이지 오류 예외 핸들러를 호출한다. 그리고 특정 물리페이지에 저장된 희생자 페이지를 선택한다. 커널은 우선 이 희생자 페이지에 대응되는 PTE를 수정하여, 이 페이지가 더 이상 물리메모리에 캐시되어 있지 않음을 표시한다.
    • 이때 희생자 페이지가 변경되었던 이력이 있으면 이 페이지를 디스크에 다시 복사한다.
  • 이제 커널은 희생자 페이지가 위치해 있던 물리페이지에, 디스크에 있던 가상페이지(CPU가 찾고자 하는 그 가상페이지)를 복사한다. 그리고 커널의 예외 핸들러는 이 가상페이지에 대응되는 PTE를 갱신하고 리턴한다.
  • 핸들러가 리턴하면서 오류 인스트럭션을 재시작하는데, 이 인스트럭션은 아까 실패했던 요청을 MMU로 다시 요청하게 된다.
가상메모리는 1960년대 초에 발명되었고, 그땐 SRAM 캐시가 없었다. 그래서 개념적으로는 가상메모리와 SRAM 캐시가 비슷한 부분이 많음에도 불구하고 서로 다른 용어들을 사용하고 있다. 예를 들어 가상메모리 시스템에서 블록은 페이지에 대응되고, 캐시 적중 및 캐시 미스는 페이지 적중, 페이지 미스로 알려져 있다.

페이지의 할당(allocating pages)

malloc 등의 함수를 호출하게 되면 그 결과로 운영체제가 가상메모리에 새로운 페이지를 할당한다. 이때 유효비트가 0이면서 주소 필드가 NULL인 PTE 중 하나의 주소 필드에 NULL 대신 디스크에 새롭게 만든 페이지의 주소가 들어간다.

메모리 관리를 위한 도구로서의 VM

물리메모리보다 더 작은 가상주소공간을 지원하던 시스템들도 존재했다. 가상메모리는 메모리가 실제 메모리보다 많아 보이게 하는 기술로 알려져있는데, 이렇게 실제 메모리보다 더 작은 가상주소공간을 지원한다면 이점이 뭐가 있지?라고 생각할 수도 있지만 사실 가상메모리는 앞서 살펴본 캐싱 도구로서의 역할 외에도 중요한 일을 한다.

그 중 하나가 메모리 관리를 단순화한다는 것인데, 가상메모리 시스템 하에선 각 프로세스에 통일된 형태의 주소공간이 제공되기 때문에 운영체제가 자신과 사용자 프로세스 사이에 공유를 일정한 방식으로 관리할 수 있게 된다. 또한 가상메모리는 사용자 프로세스에 추가적인 메모리를 할당할 수 있는 단순한 방식을 제공함으로써 메모리 관리를 단순화 한다.

메모리 보호를 위한 도구로서의 VM

뿐만 아니라 가상메모리는 각 프로세스의 주소공간을 다른 프로세스에 의한 손상으로부터 보호함으로써 메모리를 보호하기도 한다. 주소 번역 메커니즘을 확장하여, 보다 섬세한 접근 제어를 제공할 수 있다. CPU가 가상주소를 생성할 때마다 MMU가 PTE를 읽으니까, PTE에 허가 비트(permission bits)를 추가하여 간단하게 접근을 제어할 수 있다. 교재에서는 각 PTE에 다음의 3가지 허가 비트를 추가함으로써 접근 제어를 구현하는 사례를 보여준다. :

  • SUP 비트: 프로세스들이 PTE의 주소 필드가 가리키는 페이지에 접근하기 위해 접근하기 위해 수퍼바이저 모드(커널 모드)로 돌고 있어야 하는지 여부를 나타낸다. 커널 모드가 아닌 사용자 모드로 돌고 있는 프로세스들은 SUP 값이 0인 페이지에만 접근이 가능하다.
  • READ 비트: 페이지에 대한 읽기 접근을 제어한다.
  • WRITE 비트: 페이지에 대한 읽기 접근을 제어한다.

어떤 인스트럭션이 이러한 허가사항을 위반할 경우, CPU는 일반 보호 오류(general protection fault)를 발생시키고 커널 내의 예외 핸들러로 제어를 이동시킨다. 리눅스 쉘은 이러한 예외를 segmentation fault라고 보고한다.

주소의 번역

  • 주소 번역(address translation)은 가상주소공간(virtual address space, VAS)과 물리주소공간(physical address space, PAS)의 원소들간의 매핑이다.
  • 페이지 적중에선 하드웨어가 모든 것을 처리하는 반면, 페이지 오류가 나면 하드웨어와 운영체제 커널의 협력이 필요하다.
  • PTE는 페이지 테이블의 배열 요소다. 페이지 테이블은 메인 메모리에 있다. 메인 메모리의 데이터는 캐시 메모리에 캐싱된다. 따라서 PTE도 캐시 메모리에 캐싱될 수 있다.
  • CPU가 가상주소를 생성할 때마다 MMU는 가상주소를 물리주소로 변환하기 위해 PTE를 참조해야 한다. 이때, 페이지 테이블이 메인메모리에 있으므로 MMU는 메인 메모리에 2번 접근하게 되는 것이다. (1번: 페이지 테이블에 접근, 2번: 물리페이지에 접근)
    • 바로 앞에 언급했던 것처럼 해당 PTE가 캐시 메모리(L1)에 캐싱되어 있다면 비용을 줄일 수 있겠지만, 많은 시스템들은 이보다 더 비용을 절감할 수 있는 방법을 채택하고 있다. 바로 TLB를 두는 것!
  • TLB(translation lookaside buffer, 번역 참조 버퍼)
    • MMU 내의 작은 가상주소지정 캐시
    • 각 라인은 하나의 PTE로 구성된 하나의 블록을 저장한다.

인텔 코어 i7에서의 주소 번역

  • 코어 i7은 4단 페이지 테이블 계층구조를 사용한다.
  • 각 프로세스는 자신만의 사적인 페이지 테이블 계층구조를 갖는다.
  • 페이지 테이블 엔트리 비교
    • 1단, 2단, 3단 페이지 테이블에서 각 엔트리는 4KB의 자식 페이지 테이블을 참조한다.
    • 4단 페이지 테이블에서 각 엔트리는 4KB의 자식 페이지를 참조한다.
  • 각 PTE는 세 개의 허가 비트를 가지고 있다.
    • R/W 비트: 페이지 내용이 읽기/쓰기 또는 읽기 허용인지 결정
    • U/S 비트: 페이지가 사용자 모드에서도 접근할 수 있는지, 커널 모드에서만 접근할 수 있는지 결정
    • XD 비트: 인스트럭션 선입 비활성화를 위해 사용되며, 이를 통해 버퍼 오버플로우 공격의 위험을 줄일 수 있다고 함.

메모리 매핑

리눅스는 가상메모리 영역의 내용을 디스크의 객체에 연결하여 초기화하며, 이를 메모리 매핑이라고 한다. 가상메모리 영역은 다음 두 종류의 객체 중 하나로 매핑될 수 있다.

 

  1. 일반 디스크 파일
    • 예) 실행가능 목적파일
    • 파일 섹션은 페이지 크기의 조각들로 나누어짐.
    • 요구 페이징으로 인해, CPU가 가상주소를 생성하기 전까지는 이 가상페이지들 중 아무것도 물리메모리로 스왑인 되지 않음.
  2. 무기명 파일
    • 무기명 파일은 커널이 만든 파일로, 이진수 0으로만 채워져있음.
    • 무기명 파일로 매핑된 영역 내의 페이지들은 demand-zero 페이지라고도 불림.

 

복사했습니다!