하나의 프로그램만 돌린다면 메모리 가상화는 필요없다. 여러 프로그램을 동시에 돌리기 위해서 우리는 메모리 가상화를 사용한다. 멀티프로그래밍의 목표는 다음과 같다.

  • Transparency: 프로세스는 메모리가 공유되고 있다는 것을 몰라야 함, 항상 사용 가능해야 함
  • Protection: OS나 다른 프로세스의 메모리 침범할 수 없음
  • Efficiency: 메모리 낭비가 없어야 함
  • Sharing: 주소 공간 공유할 수 있음

 

Memory address space

OS가 프로세스에게 private한 address space를 줘야 하는데, 한 가지 문제가 있다. 메모리 공간이 다이나믹하다는 것이다. 실제로 메모리는 다음의 구조를 가진다.

Code 영역은 static하다. 반면에, Stack이나 Heap은 dynamic하다는 특성을 가지고 있다. 프로그램 컴파일 시점에서는 얼마나 메모리가 필요한지 모르기 때문에, 동적으로 할당해서 사용하게 된다.

 

Stack

Last-In-First-Out의 구조로 OS가 call frame에서 로컬 변수나 파라미터를 저장하기 위해 사용한다. Fragmentation은 발생하지 않는다.

  • alloc(A), alloc(B), alloc(C), free(C), free(B), free(A)

스택에 무언가를 할당하기 위해서는 포인터를 높이면 된다. 반대로 free하기 위해서는 높인 만큼 포인터를 감소시킨다.

 

Heap

프로그램에서 메모리 할당이 이루어질 경우, heap에 할당된다. 대표적으로 malloc(), new()가 있다. Allocated area와 freed area가 공존하며, alloc과 free의 순서는 예측할 수 없다.

 

allocation은 느리며, fragmentation이 발생할 수 있다.

 

실제 코드를 통해 무엇이 stack, heap에 저장되는지 구분할 수 있어야 한다.

저장되는 위치

 

Memory access

위 코드의 실제 어셈블리어를 구해보면 오른쪽과 같다. 우선 %rip에 0x20을 할당해 instruction을 수행하게 된다. instruction 내용은 %rbp + 0x8의 값을 %edi에 옮겨 0x3을 더하고, 그 값을 다시 %rbp + 0x8에 저장한다.

 

코드로 해석하자면, x를 불러서 3을 더하고 x+3을 다시 x에 저장한 것이다.

 

Virtualize Memory

메모리 가상화의 가장 큰 목표는 여러 프로세스의 동시 사용이다. 이게 없었을 때에는, 주소를 하드코딩해서 다 잡아줬는데, 여러개의 프로세스를 하드코딩으로 동시에 관리하는 것은 어려운 일이다. 가장 간단한 방법은 프로세스끼리 격리시키는 방법이다. 다음은 몇 가지 방법이다.

  • Time sharing
  • Static relocation
  • Base register
  • Base + Bounds register
  • Segmentation

 

Time Sharing

각 프로세스의 주소 공간을 계속해서 저장했다, 불렀다 하면서 사용하는 방법이다. 당연하게도 성능이 매우 안좋아진다. 차라리 space sharing을 하는 게 더 좋음을 알 수 있다.

 

실제로 다음 solution부터는 space sharing을 하게 된다.

 

Static Relocation

프로그램이 프로세스에 올라올 때, 메모리 주소를 다시 작성하는 방법이다. 간단하게 offset을 더해 주소 공간을 분리시킬 수 있다.

단점

  • No protection
    옮기는 과정에서 다른 프로세스나 OS를 파괴할 수 있으며, 동적할당에 취약해진다. 또한 privacy 보장이 안된다.
  • Low flexibility
    한번 옮기면, 다시 못 옮긴다.

 

Dynamic Relocation

Memory Management Unit(MMU)의 도움을 받는 방법이다. 프로세스에서는 logical한 주소를 그대로 사용하고, 실제로 메모리 접근이 있을 때, MMU가 실제 physical memory address로 접근할 수 있도록 만들어 준다.

 

또한 operating mode를 나눠 PM 접근을 제한할 수 있다.

  • Kernel mode: OS, MMU 내용 조작 가능
  • User mode: process, VA->PA 변환만 가능

 

MMU에는 base register가 있어서, 프로세스마다 VA<->PA를 관리할 수 있다.

 

Base Register

MMU에서는 VA에 base register를 더해서 PA를 얻을 수 있다. 간단하게 구현할 경우, fixed offset이 프로세스마다 할당되며, 해당 프로세스의 offset을 더함으로 PA를 얻을 수 있게 된다. 물론 각 프로세스는 서로 다른 offset을 가지게 된다.

명령어와 그에 따른 VA, PA

 

Base + Bounds

앞선 방법에서는 load 4KB를 하게 된다면, P1이 P2의 메모리 공간을 침범할 수 있었다. 완벽한 격리를 구현하기 위해서는 이를 제한할 필요가 있고, 가장 쉬운 방법은 limit을 관리하는 것이다.

 

Bound register의 크기는 virtual address space의 크기이다. 이렇게 됨에 따라 프로세스가 접근할 수 있는 메모리 주소는 base ~ base+bound가 된다. 

 

Context switch를 하기 위해서는 프로세스마다 base와 bound registers을 PCB에 저장해야 한다.

장점

  • Protection
  • Dynamic relocation
  • Simple, fast, inexpensive

단점

  • 실제 physical memory에도 VM 크기만큼 할당해야 함(사용 안해도)
  • Partial sharing이 없음

 

Segmentation

앞의 단점이었던, 일체화를 logical하게 나눠서(code, stack, heap) 사용하는 방법이다. 각 segment는 PM에 따로 저장될 수 있으며, 독립적으로 grow&shrink가 가능하며, 보호될 수 있다는 장점이 있다.

가령 14bit의 VA를 가진다 했을 때, 위와 같은 table을 MMU에서 사용해 실제 PM에 접근한다.  Segment가 code, heap, stack이기 때문에, 4bit를 사용하게 되므로 10bit가 offset이 된다.

 

  • 0x1040: 0x0000 + 0x040
  • 0x1108:  0x0000 + 0x108
  • 0x265c: 0x3000 + 0x65c
  • 0x3002: Bounds register에 의해 접근이 제한됨

 

실제로는 다음과 같다.

Segment bit를 잘 읽고 각각의 base인 0x400(heap), 0x1600(stack)을 더해 PA를 얻을 수 있다.

 

장점

  • Segment 크기 조절이 자유로움
  • Protection
  • Sharing (ex. code)
  • Dynamic relocation

단점

  • Segment는 또 붙어서 할당되어야 함. 왕 큰 segment는 할당 안될 수도 있음
    (*paging으로 해결)

'Study > Operating System' 카테고리의 다른 글

6. Threads  (1) 2023.12.08
5. Virtual Memory  (0) 2023.12.08
4. Paging  (0) 2023.12.05
2. CPU Virtualization  (1) 2023.11.27
1. Introduction  (0) 2023.11.21