메모리 가상화

d·2023년 5월 7일
0

Reference - Operating Systems: Three Easy Pieces


멀티 프로그래밍과 시분할

컴퓨터가 발전하면서 사람들은 더욱더 효과적으로 컴퓨터를 공유하는 방법을 모색했다. 그러면서 등장 한 것이 멀티프로그래밍 인데, 여러 프로세스가 준비 상태에 있고 운영체제는 프로세스들을 전환하면서 실행하는 것이다. 이런 프로세스 간의 전환은 CPU 이용률을 증가시켰고, 효율성의 개선이 더욱 더 중요해졌다.

시간이 지나 많은 사람들이 컴퓨터를 동시에 이용하게 되고, 현재 실행 중인 작업으로부터 조금더 빠르게 응답을 받기 원해 대화식 처리 의 개념이 중요하게 되었다.
시분할 운영체제 는 CPU 스케줄링과 다중 프로그래밍을 이용해서 각 사용자들에게 컴퓨터 자원을 시간적으로 분할하여 사용할 수 있게 해 주었다. 시분할 시스템은 하나의 프로세스를 짧은 시간 동안 실행시키다가, 해당 프로세스를 중단시키면서 물리 메모리를 포함한 모든 상태를 저장하고 또 다른 프로세스의 상태를 탑재해 짧은 시간 동안 실행시키는 방식으로 이루어진다. 이러한 방식의 시분할은 메모리가 커질수록 느려진다는 단점이 있었다. 그리고 다수의 프로그램이 메모리에 동시에 존재하면서 프로세스 간의 보호와 고립의 중요성이 대두되었다. 프로세스가 OS나 여타 다른 프로세스에 접근 할 수 없도록 하려면 어떻게 해야될까? 주소 공간과 가상 주소에 대해서 알아본다.


주소 공간

주소 공간 은 실행 중인 '프로그램이 알고 있는' 메모리의 모습이다. 주소 공간은 실행 프로그램의 모든 상태를 가지고 있는데, 흔히 알고 있는 코드, 스택, 힙 영역을 모두 포함하고있다.

주소 공간을 설명할 때, 운영체제가 프로세스에게 제공하는 abstraction을 말하게 된다. 위의 그림에서 실제로 프로그램이 물리 주소 0에서 16KB 사이에 존재하는 것이 아닌, 임의의 물리 주소에 탑재된다. 우리가 c언어로 프로그램을 작성해 메모리 주소를 살펴보면 우리가 보게 되는 것은 실제 주소가 아니라 가상 주소(Virtual Address) 인것이다. 실제 주소와 다른 가상의 주소, 우리는 이것을 운영체제가 메모리를 가상화(Virtualize) 했다고 부른다. 그렇다면, 메모리 가상화의 목표는 무엇이며, 운영체제는 메모리를 어떤식으로 가상화 할까?

가상 메모리의 목표

  • 투명성(Transparency) : 운영체제는 실행 중인 프로그램이 가상 메모리의 존재를 인지하지 못하도록 해 프로그램은 자신만이 전용 물리 메모리를 소유한 것 처럼 행동해야 한다.
  • 효율성(Efficiency) : 운영체제는 가상화가 시간과 공간적 측면에서 효율적으로 이뤄지도록 해야한다. 프로그램이 너무 느리게 실행되어서는 안되고 가상화를 지원하기 위해 너무 많은 메모리를 사용해서는 안된다.
  • 보호(Protection) : 운영체제는 프로세스를 다른 프로세스로부터 보호하고 운영체제 자신도 프로세스로부터 지켜야한다. 운영체제도 결국엔 하나의 소프트웨어다. 프로세스들을 고립(Isolate) - 만약 악성 프로세스가 있다면 다른 프로세스와 운영체제에 손을 뻗치지 못하게 한다.

하드웨어 기반 주소 변환


하드웨어 기반 주소 변환은 하드웨어를 통해 명령어 반입, 탑재, 저장 등의 가상 주소를 정보가 실제 존재하는 물리 주소로 변환한다. 이를 통해 프로그램이 자신의 전용 메모리를 소유하고, 그 안에 자신의 코드와 데이터가 있다는 환상을 만드는 것이다.

프로그램의 관점에서 자신의 주소 공간은 주소 0부터 시작해 최대 16KB까지인데, 실제로는 아니다. 운영체제는 어떻게 메모리를 재배치 하여 프로세스에게 환상을 제공 할까?


동적 재배치


하드웨어 기반 주소변환의 동적 재배치는 베이스와 바운드(Base and Bound) 라고도 불린다.
동적 재배치는 CPU 마다 한 쌍의 레지스터를 사용해 베이스와 바운드를 저장하는데, 프로그램을 시작 할 때 운영체제가 프로그램이 탑재될 물리 메모리 위치를 결정하고 베이스 레지스터를 그 주소로 지정한다. 그리고 실행 시간에 프로세스에 의해 생성되는 주소가 physical address = virtual address + base 로 변환된다. 하드웨어가 베이스 레지스터의 내용을 주소에 더해 물리 주소를 생성하는 것이다.

바운드 레지스터는 보호를 지원하기 위해 존재한다. CPU는 먼저 메모리 참조가 합법적인가 확인하기 위해 가상 주소가 바운드 안에 있는지 확인한다. 프로세스가 바운드보다 큰 가상 주소 또는 음수인 가상 주소를 참조하면 CPU는 exception을 발생시키고 프로세스는 종료된다.

동적 재배치는 베이스 레지스터를 가상 주소에 더하고 생성된 주소가 바운드를 벗어나는지 검사하기 위해 간단히 하드웨어 회로만 추가하면 되기 때문에 효율적이지만 문제점이 있다.

  • 고정 크기의 슬롯에 프로세스의 주소 공간을 재배치해 내부 단편화가 일어나게 되고
  • 물리 메모리의 이용률이 떨어지게 된다
    이를 해결하기 위해 더 정교한 기법이 사용되는데, 그것이 바로 세그멘테이션(Segmentation) 이다.

MMU(Memory Management Unit)
베이스와 바운드 레지스터를 포함해 CPU 칩 상에 존재하는 주소 변환에 도움을 주는 하드웨어를 메모리 관리 장치, MMU 라고 부른다.


세그멘테이션


세그멘테이션은 베이스 & 바운드를 일반화 시킨 것으로서, MMU 안에 한 쌍의 베이스 & 바운드가 있는 것이 아니라 주소 공간의 논리적인 세그먼트(segment) 마다 베이스와 바운드 쌍이 존재하는 방식 이다. 세그먼트는 특정 길이를 가지는 연속적인 주소 공간을 말하고, 코드, 스택, 힙 세 영역을 세그먼트라고 할 수 있겠다.

세그먼테이션을 위해서 MMU 하드웨어는 3쌍의 베이스 & 바운드 레지스터가 필요하고, 위 그림에서 보이는 것 처럼 각 레지스터 쌍이 하나의 세그먼트를 담당해 실제로 사용중인 메모리만 물리 공간이 할당된다.
여기서 각 세그먼트의 바운드 레지스터는 세그먼트의 크기를 저장한다.

프로그램이 인식하고있는 메모리 공간이다. 이 공간의 코드 세그먼트에 속한 가상 주소 100번지를 주소변환 해 본다면:

  • 베이스(32kb)에 이 세그먼트의 오프셋 100byte 를 더해 물리 주소는 100byte + 32kb = 32868 이 된다.

가상주소를 변환할 때에는 주의해야 할 것이, 주소공간 안에서의 오프셋(주소가 참조하는 바이트가 세그먼트 시작으로부터 몇번째 바이트인지)를 얻어 계산해야한다는것. 예를 들어 힙에 속하는 4567번지를 주소변환 할 때 4567을 힙의 베이스(34Kb)에 더하면 물리 주소 39383를 얻지만, 이것은 틀린 주소고 올바르게 계산된 주소는:

  • (4567 - 4096(4Kb)) + 34Kb 👉 471(오프셋) + 32Kb = 35287 이 된다.

또 스택은 다른 세그먼트들과 달리 반대 방향으로 확장 되기때문에 다른 방식이 적용되어야한다. 하나의 비트를 사용해 주소가 커지는 방향을 나타내(양의 방향으로 커지면 1, 음의 방향이면 0) 음의 방향으로 커진다면 음수 오프셋을 얻어 계산해 주어야 한다.

세그먼트 종류의 파악

세그먼트의 종류를 파악하기 위해 하드웨어는 세그먼트 레지스터를 이용해 가상 주소의 최상위 몇 개의 비트를 보는데, 우리가 아는 3개의 세그먼트로 나누는 방법에선 최상위 2개의 비트를 보게된다.

최상위 2비트는 세그먼트의 종류, 그리고 나머지 12비트는 해당 세그먼트 내의 오프셋을 나타낸다.

// 상위 2비트를 가져온다
segment = (VirtualAddress & SEG_MASK) >> SEG_SHIFT
// 오프셋을 얻어온다
Offset = VirtualAddress & OFFSET_MASK

세그멘테이션은 단순한 동적 재배치를 넘어 base and bound를 일반화 함으로써 많은 문제들을 해결 해 준다. 간단한 산술 연산과 하드웨어 구현에 적합하기 때문에 속도가 빠르고, 주소 공간들 간에 특정 메모리 세그먼트를 공유 할 수 있도록 해 주어 메모리를 절약 할 수도 있게 해준다.

0개의 댓글