ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [OS] 주소 공간, 메모리 관리 API, 주소 변환의 원리
    ComputerScience/OS 2024. 3. 26. 01:13
    728x90
    이 글은 'OSTEP: Operating Systems Three Easy Pieces'를 참고하여 쓴 글로, 학습 기록용으로 작성된 글입니다.
    따라서, 정확하지 않은 내용이 있을 수 있습니다.

    옳지 않은 정보가 있다면 지나치지 마시고 수정 요청 댓글 달아주시면 감사하겠습니다. 🙂

     

    16. 주소 공간의 개념

    16. 1 초기 시스템

    초기 시스템에는 메모리에 하나의 프로세스만 적재되었다. 특별한 가상화는 존재하지 않았다.

    16. 2 멀티프로그래밍과 시분할

    멀티프로그래밍

    고가의 장비를 효율적으로 다루기 위한 시도로 멀티프로그래밍이 대두되었다.

    여러 프로세스가 실행 준비 상태에 있으면 OS는 이 사이를 전환해가면서 실행했다.

    시분할

    일괄처리방식(batch computing)의 한계를 느껴 많은 사용자가 동시에 컴퓨터를 사용하고 응답을 즉시 받을 수 있게 하는 대화식 이용(interactivity)가 중요시 되었다.

     

    이를 위해 하나의 프로세스를 짧은 시간동안 모든 메모리를 사용하게 하고, 다른 프로세스를 탑재하는 방법이 있었으나 메모리가 커질수록 context switch를 위한 상태 정보를 저장하고 복원하는 과정이 느려졌다.

     

    이후에는 이런 문제를 해결하기 위해 프로세스의 모든 부분이 아닌 필요한 부분만 메모리에 적재하여 context switching의 비용을 줄였다.

    하지만 메모리에 여러 프로세스의 데이터가 적재됨에 따라 CPU가 실행할 프로세스 이외에 다른 프로세스의 데이터를 침범할 수도 있는 가능성이 생기며 보호에 대한 문제가 생긴다.

    16. 3 주소공간

    이러한 보호 문제를 해결하기 위한 것이 바로 '주소 공간(address space)'이다.

    주소 공간은 실행중인 프로그램이 '메모리는 이럴 것이다' 하고 가정한 것이지, 실제 메모리의 모양은 아니라고 볼 수 있다.

    그리고 주소 공간은 실행 프로그램의 모든 메모리 상태를 갖고 있다. 즉 힙, 스택, 데이터, 코드 등... 모든 것을 갖고 있는 것이다.

     

    주소 공간은 크게 코드, 스택, 힙으로 분류할 수 있다.

    • 코드
      반드시 메모리에 존재해야 하기 때문에 주소공간에 존재한다.
      이 때 코드란 기계어로 작성된 것을 말한다.
    • 스택
      함수 호출 상의 현재 위치, 지역 변수, 함수 인자, 반환값 등을 저장한다.

    • 동적으로 할당되는 메모리를 위해 사용된다.

    16KB의 주소 공간

    코드는 추가적인 메모리를 필요로 하지 않는다.

    하지만 힙과 스택은 확장에 대한 가능성이 있으므로 그림처럼 위아래로 위치한다.

    하지만 이러한 배치는 멀티쓰레딩에서는 유효하지 않다.

     

    주소공간을 설명할 때 실제로 프로그램이 물리주소 0에서 16KB 사이에 존재하는 것은 아니다.

    실제로는 임의의 물리주소에 탑재된다.

     

    여러 프로세스가 있는 물리메모리

     

    이 그림을 보아도 프로세스 C는 128 ~ 192KB 주소에 적재됨을 알 수 있다.

    중요한 것은, 다수의 프로세스로 하여금 어떻게 프로세스 전용의 '주소 공간'을 제공할 수 있는가? 이다.

    이렇게 운영체제가 프로세스에게 '너의 주소 공간은 이렇게 크단다'하고 환상을 심어주는 것을 '메모리 가상화(virtualizing memory)'라고 한다.

     

    왜 속이고 가상화한다고 하냐면, 실행중인 프로그램은 자신이 특정 주소의 메모리(0 같은)에 탑재되고 매우 큰 주소 공간을 가진다고 느끼게끔 하기 때문에 '속인다, 가상화한다'고 표현하는 것이다.

     

    예를 들어 프로세스 A가 주소 0으로부터(가상 주소) load 연산을 수행할 때 운영체제는 하드웨어의 지원을 통해 물리주소 0이 아니라 물리주소 320KB(A가 탑재된 메모리)를 읽도록 보장해야 한다.

     

    16. 4 목표

    '고립의 원칙'은 신뢰할 수 있는 시스템에서 중요한 개념이다.

    두 개체가 고립이 잘 되어서 하나가 실패해도 다른 개체에게 영향을 주지 않는다는 것을 의미한다.

    운영체제는 프로세스간 고립을 시작으로 메모리간 고립을 실현하여 프로세스가 운영체제에 영향을 미치지 못하도록 한다.

     

    이것이 마이크로 커널과 모놀리식 커널의 차이점이다.

     

    이러한 고립의 원칙을 사수하기 위한 VM의 주요 목표는 다음과 같다.

    1. 투명성
      운영체제는 실행중인 프로그램이 가상메모리의 존재를 인지하지 못하도록 VM을 구현해야 한다.
      많은 프로세스들이 각각 RAM을 가진 것처럼 느끼게 해주어야 한다.
    2. 효율성
      가상화가 시간적으로 프로그램의 실행이 너무 늦어서는 안되며, 공간적으로는 가상화 지원을 위해 너무 많은 메모리를 소모해서는 안된다. 이를 위해 TLB를 사용한다.
    3. 보호
      운영체제는 한 프로세스를 다른 프로세스로부터 보호해야 한다.
      즉, 자신의 주소 공간 이외의 것을 이용할 수 없게 해야한다.
      그리고 이러한 보호를 통해 우리는 프로세스들을 고립시킬 수 있다.

    17. 메모리 관리 API

    17. 1 메모리 공간의 종류

    C 프로그램이 실행되면 스택과 힙이 할당된다.

    • 스택(stack)
      할당과 반환이 컴파일러에 의해 암묵적으로 이루어져 '자동 메모리'라고도 부른다.
      예를 들어 func()안에 x를 위한 공간이 필요하다고 치면, 별 다른 것 없이 선언만 해주면 컴파일러가 스택에 공간을 확보한다.
      반환은 컴파일러가 알아서 해준다.
      함수 리턴 이후에도 값 유지가 필요하면 힙에 저장해야 한다.
    void func() {
    	int x; // 스택에 int형 x를 선언
    }
    • 힙(heap)
      모든 할당과 반환이 프로그래머에 의해 이루어진다.
      정수형 포인터를 힙에 할당하는 예시를 보자.
    void func() {
    	int *x = (int *)malloc(sizeof(int));
    }

     

    여기서 한 행에 스택과 힙에 대한 할당이 모두 이루어지고 있다.

    컴파일러가 int *x 를 만나면 정수 포인터를 위한 공간이 할당되어야 함을 알게 된다.

    malloc()이 호출 되면 정수를 위한 공간을 힙으로부터 요구한다.

    malloc()은 그 정수의 주소를 반환하고, 그 때 반환된 주소는 스택에 저장되어 프로그램에서 사용된다.

    18. 주소 변환의 원리

    CPU 가상화에서 LDE(제한적 직접 실행)을 통해 system call이나 인터럽트 상황에서도 가상화를 이뤄 냈듯, 메모리 가상화에서도 가상화를 제공하여 효율과 OS의 제어 능력 모두를 실현해낸다. 이를 위해 TLB, 레지스터 등의 HW적 지원이 필요하다.

     

    핵심 문제는 다음과 같다.

    1. 어떻게 효율적인 메모리 가상화를 구축할 것인가?
    2. 프로그램의 메모리에 대한 위치 접근을 어떻게 제어할 것인가?

    등과 같이 이러한 문제를 해결하기 위해 '하드웨어-기반 주소 변환' 혹은 '주소 변환'을 사용한다.

    주소 변환을 통해 명령어 반입, 탑재, 저장 등 가상주소를 정보가 실제 존재하는 물리주소로 변환한다.

    운영체제는 이를 위해 비어있는 물리 메모리와 사용 중인 메모리를 항상 알고 있어야 한다.

     

    이 모든 작업의 목표는 프로그램에게 환상을 심어주는 것이다!

     

    18. 1 가정

    사용자 주소 공간을 물리 메모리에 연속적으로 배치되어야 한다고 가정하자

    주소공간은 물리메모리보다 작다고 가정하자

    각 주소공간의 크기는 같다고 가정하자

     

    ... 너무 이상적이고 터무니 없을 수 있지만 이를 차차 완화해 나가며 가상화를 이끌어낸다.

     

    18. 2 사례

    void func() {
    	int x = 3000;
        x = x + 3;
    }

     

    다음과 같은 프로세스가 있을 때 컴파일러는 이를 어셈블리어로 변환하여,

    128: movl 0x0(\% ebx), \%eax;    // 0 + ebx를 eax에 저장
    132: addl \$ 0x03, \% eax;		 // eax 레지스터에 3을 더함.
    135: movl \%eax, 0x0(\%ebx);     // eax를 메모리에 다시 저장함.

     

    이렇게 표현하고, 이것을 그림으로 나타내면 다음과 같다.

    프로세스 관점에서 메모리 접근은 다음과 같다.

    1. 128에 있는 명령어 반입
    2. 15KB에서 x = 3000 탑재하여 명령어 실행
    3. 132에 있는 명령어 반입
    4. 명령어 실행(참조할 메모리 없음)
    5. 135에 있는 명령어 반입
    6. 명령어 실행한 결과 15KB에 저장

    프로그램 관점에서 주소공간이 0 ~ 16KB인데 그 말은 즉 이 범위 내에서 모든 메모리 참조가 이뤄져야 한다는 말이다.

    우리는 가상화를 위해 이 주소를 프로세스 모르게 재배치 해야한다.

    다음 그림은 우리가 원하는 재배치가 이뤄지고 난 후의 모습이다.

     

    재배치 이후의 메모리 모습

    0 ~ 16KB에서 32 ~ 48KB로 재배치 되었다.

    18. 3 동적(하드웨어-기반) 재배치

    1950년대 후반 이를 위해 베이스와 바운드(base and bound) 또는 동적 재배치(dynamic relocation)을 이용하기 시작한다.

    각 CPU마다 베이스 레지스터와 바운드 혹은 한계 레지스터를 갖는다.

    각 프로그램은 주소 0에 탑재되는 것처럼 작성되고, 컴파일된다.

    프로그램 시작 시 운영체제가 프로그램이 탑재될 물리 메모리 위치를 결정하고 베이스 레지스터를 그 주소로 지정한다.

     

    예를 들어, 위에서의 예시로는 운영체제는 프로세스를 물리주소 32KB에 저장하기로 하고 베이스 레지스터를 32KB로 설정한다.

    프로세스가 실행되면서 모든 주소가 CPU에 의해 다음과 같이 변환된다.

     

    physical address = virtual address + base

     

    프로세스가 생성하는 메모리 참조는 가상 주소이다.

    HW는 이 가상 주소에 베이스 레지스터 값을 더해 물리주소를 생성한다.

    다시 어셈블리 코드로 돌아가보면

     

    128: movl 0x0(%EBX), %eax;

     

    프로그램 카운터는 128이 되고, HW가 이 명령어를 반입할 때 PC값에 레지스터 값을 더하여 물리주소를 얻는다.

    즉 128 + 32768(32KB) = 32896이 물리 주소가 된다.

     

    이 주소로 가서 HW는 명령어를 가져온다.

    그리고 CPU는 명령어를 실행하는데, 프로세스는 15KB에 위치한 x의 값을 탑재할 것을 요구한다.

    CPU는 이 15KB에 다시 베이스 레지스터 32KB를 더해 47KB에서 x값을 찾아온다.

    이것이 바로 가상 to 물리 주소 변환 기술이다.

     

    이러한 재배치는 실행 시에 일어나고, 프로세스가 실행을 시작한 이후에도 주소공간을 이동할 수 있기 때문에 '동적 재배치'라고 하는 것이다. 그렇다면 '바운드 레지스터'는 어디에 사용되는가?

     

    바운드 레지스터는 항상 16KB로 설정되어, 프로세스가 바운드보다 큰 가상주소 혹은 음수인 가상주소를 참조하면 예외처리를 하고 프로세스는 종료된다. 베이스와 바운드 레지스터 모두 CPU 내의 하드웨어 구조이고, 주소 변환에 도움을 주는 장치들은 MMU라고 한다.

    MMU에는 TLB, 베이스 레지스터 등이 포함된다고 볼 수 있다.

     

    18. 4 하드웨어 지원: 요약

    이러한 지원을 받았을 때 필요한 것들이 있다.

    1. 커널, 유저 모드
      프로세스 상태 워드 레지스터의 한 비트가 CPU의 현재 실행 모드를 나타낸다.
    2. HW는 베이스와 바운드 레지스터를 자체적으로 제공한다.
      CPU는 MMU의 일부인 추가 레지스터 쌍을 갖는다.
      MMU는 가상 주소에 베이스 레지스터 값을 더해 주소 변환을 수행한다.
    3. HW는 이러한 주소가 유효한지 검사할 수 있어야 한다.
      바운드 레지스터가 이 역할을 수행한다.
    4. HW는 베이스와 바운드 레지스터 값을 변경하는 '특권 명령어'를 제공해야 한다.
      특권인 이유는 유저가 마음대로 사용해선 안되기 때문이다.
    5. 예외를 발생시키고 처리할 수 있어야 한다.
      이를 위해 exception handler가 실행되어야 한다.

    18. 5 운영체제 이슈

    동적 재배치 지원을 위해 HW가 새로운 기능을 제공하는 것과 더불어 OS에도 즉, SW 단에도 새로운 이슈가 생긴다.

    베이스와 바운드 방식의 가상 메모리 구현을 위해 OS가 반드시 개입해야하는 세 개의 시점이 있다.

    1. 프로세스가 생성될 때 운영체제는 주소공간이 저장될 메모리 공간을 찾아서 조취를 취해야 한다.
      다행히 주소공간은 물리메모리보다 작고 크기가 일정하다는 우리의 가정 하에서는 처리가 어렵지 않다.

      운영체제는 물리메모리를 슬롯(slot)의 배열로 보고 슬롯의 사용 여부를 관리한다.
      이를 위해 가용 리스트(free list)를 검색한다.
    2. 프로세스가 종료할 때 메모리를 회수할 수 있어야 한다.
      회수된 메모리는 다시 free list에 추가되어야 한다.
    3. context switch 시에도 추가 조취를 취해야 한다.
      CPU에는 한 쌍의 베이스-바운드 레지스터가 있고 프로그램마다 다른 값들을 가지기 때문에 프로세스가 중단되면 프로세스 제어 블록(PCB)에 베이스, 바운드 레지스터 값들을 저장해 두어야 한다.
      context switch가 일어날 때, 운영체제는 현재 프로세스를 중단시키고 현재 위치에서 새로운 위치로 주소공간을 복사한다.
      운영체제는 PCB의 베이스 레지스터를 새로운 위치의 주소공간을 가리키게끔 한다.
      프로세스가 나중에 복원되면 새로운 위치에서의 값으로 레지스터가 실행된다.
    4. OS는 예외처리를 해야 한다.
      따라서 이를 위한 특권 명령어가 필요하다.
      OS는 부팅 시에 이 명령어를 사용하여 핸들러를 설정한다.
      기본 컨셉은 예외 상황에 대해 적대적으로 반응하여 프로세스를 kill하는 것이다.

    18. 6 요약

    이러한 방식의 동적 재배치는 고정된 크기의 슬롯을 이용하여 사용되지 않는 공간을 낭비하는 내부 단편화를 발생시킨다.

    이런 낭비를 막기 위해 나온 것이 바로 '세그먼테이션'이다.

    'ComputerScience > OS' 카테고리의 다른 글

    [OS] Swap, File-backed Page, DMA  (0) 2024.03.22
    [OS] File Descriptor  (0) 2024.03.18
    [OS] Virtual Address, Page Table, PML4  (2) 2024.03.16
    [OS] User mode & Kernel mode  (0) 2024.03.12
    [OS] 32 bit OS vs 64 bit OS  (1) 2024.03.12
Designed by Tistory.