개발 공부

손승열(Son Seungyeol)

[운영체제][OSTEP] 세그멘테이션

대용량 주소 공간을 지원하는 방법에 대해 공부해봅니다.

손승열(Son Seungyeol)
[운영체제][OSTEP] 세그멘테이션

1️⃣ 세그멘테이션: 베이스/바운드(base/bound)의 일반화

💡아이디어
MMU 안에 오직 하나의 베이스와 바운드 쌍만 존재하는 것이 아니라 주소 공간의 논리적인 세그멘트(segment)마다 베이스와 바운드 쌍이 존재

📏 세그멘트 = 특정 길이를 가지는 연속적인 주소 공간

코드, 스택, 힙 세 종류의 세그멘트
➡️ 세그멘테이션을 사용하면 운영체제는 각 세그멘트를 물리 메모리의 각기 다른 위치에 배치할 수 있음 + 사용되지 않는 가상 주소 공간이 물리 메모리를 차지하는 것을 방지

다음의 예시와 함께 이를 살펴보자.

 0 KB ┌──────────────────────┐
      │                      │
 1 KB │                      │
      │     Program Code     │
 2 KB ├──────────────────────┤
      │                      │
 3 KB │                      │
      │                      │
 4 KB ├──────────────────────┤
      │                      │
 5 KB │         Heap         │
      │                      │
 6 KB ├──────────────────────┤
      │          │           │
      │          │           │
      │          │           │
      │          ↓           │
      │        (free)        │
      │          ↑           │
      │          │           │
      │          │           │
      │          │           │
14 KB ├──────────────────────┤
      │                      │
15 KB │        Stack         │
      │                      │
16 KB └──────────────────────┘

위의 주소 공간을 물리 메모리에 배치하려고 한다. 각 세그멘트의 베이스와 바운드 쌍을 이용하여 세그멘트들을 다음과 같이 독립적으로 물리 메모리에 배치할 수 있다.

 0 KB ┌──────────────────────┐
      │                      │
      │                      │
      │   Operating System   │
      │                      │
      │                      │
16 KB ├──────────────────────┤
      │                      │
      │     (not in use)     │
      │          ↑           │
      ├──────────────────────┤
      │        Stack         │
      ├──────────────────────┤
      │     (not in use)     │
32 KB ├──────────────────────┤
      │         Code         │
      ├──────────────────────┤
      │         Heap         │
      ├──────────────────────┤
      │          ↓           │
      │                      │
48 KB │                      │
      │                      │
      │     (not in use)     │
      │                      │
      │                      │
      │                      │
64 KB └──────────────────────┘

위의 예시에서 확인할 수 있듯이, 사용 중인 메모리에만 물리 공간이 할당된다.
➡️ 사용되지 않은 영역이 많은 대형 주소 공간(sparse address space라고도 부름)을 수용할 수 있다.

위의 경우 3쌍의 베이스와 바운드 레지스터 집합이 필요하며 이는 세그멘트 레지스터에서 다음과 같이 나타낼 수 있다.

Segment     Base     Size
──────────────────────────
Code        32K      2K
Heap        34K      3K
Stack       28K      2K

이때, 가상 주소 100번지를 참조한다고 가정해보자.

첫 번째 그림에서 100번지는 코드 세그멘트에 속하므로 참조가 일어나면 하드웨어는 베이스 값(32KB)에 이 세그멘트의 오프셋(100)을 더해 물리 주소 32868(100+32KB)을 얻어낸다.

그 후, 주소가 범위 내에 있는지 검사(100 < 2KB)하고, 범위 내에 있을 경우 물리 메모리 주소 32868을 읽는다.

가상 주소 4200번지를 참조하는 경우를 다시 가정해보자.

4200번지는 힙 세그먼트에 속하므로 베이스 값(34KB)에 오프셋(4200-4096(힙은 가상 주소가 4KB에서 시작하므로!))을 더하여 34920(104+34KB)을 얻게 된다.

힙의 마지막을 벗어난 7KB와 같은 잘못된 주소를 접근하는 경우를 가정해보자.

⛔ 하드웨어가 그 주소가 범위를 벗어났다는 것을 감지하고 운영체제에 트랩을 발생
➡️ 세그멘트 폴트(segment fault)가 발생


2️⃣ 세그멘트 종류의 파악

하드웨어는 변환을 위해 세그멘트 레지스터를 사용

가상 주소가 참조하는 세그멘트와 세그멘트 내 오프셋의 크기를 알아내는 일반적인 접근법
➡️ 가상 주소의 최상위 몇 비트를 기준으로 주소 공간을 여러 세그멘트로 나누는 것
➡️ VAX/VMS 시스템에서 사용된 기법

1️⃣의 예시에서는 3개의 세그멘트가 존재 ➡️ 주소 공간을 세그멘트로 나누기 위해 2비트가 필요

세그멘트 표시를 위해 가상 주소 14비트 중 최상위 2비트를 사용하는 경우 가상 주소의 모양은 다음과 같다.

 13 12 11 10  9  8  7  6  5  4  3  2  1  0
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
└─────┴───────────────────────────────────┘
Segment              Offset

최상위 2비트의 세그멘트 표기는 다음과 같이 나타낼 수 있다.

  • 00: 코드 세그멘트

  • 01: 힙 세그멘트

  • 11: 스택 세그멘트

따라서 최상위 2비트의 세그멘트 표기를 통해 하드웨어는 그에 해당하는 베이스와 바운드를 사용할 수 있다.

예를 들어, 위의 예시 중 가상 주소 4200번지를 참조하는 경우 그에 해당하는 이진 형식은 다음과 같다.

13 12 11 10  9  8  7  6  5  4  3  2  1  0
┌────────────────────────────────────────┐
 0  1  0  0  0  0  0  1  1  0  1  0  0  0
└────────────────────────────────────────┘
└────┴───────────────────────────────────┘
Segment              Offset

하드웨어는 최상위 2비트 01을 통해 힙 세그먼트를, 하위 12비트 0000 0110 1000을 통해 오프셋 104를 알아낼 수 있다.

오프셋은 바운드 검사도 쉽게 만듦.
➡️ 오프셋이 바운드 보다 작은지 여부만 검사하면 됨.

베이스와 바운드 쌍을 배열 형식으로 저장할 경우(세그멘트당 하나의 항목), 원하는 물리 주소를 얻기 위해 다음과 같은 작업을 수행

// 14 bit VA중의 상위 2 bit를 얻어옴
Segment = (VirtualAddress & SEG_MASK) >> SEG_SHIFT
// 이제 오프셋을 얻어옴
Offset = VirtualAddress & OFFSET_MASK
if (Offset >= Bounds[Segment])
  RaiseException(PROTECTION_FAULT)
else
  PhysAddr = Base[Segment] + Offset
  Register = AccessMemory(PhysAddr)

위의 예시를 기준으로 코드의 상수 값들을 다음과 같이 정할 수 있다.

  • SEG_MASK: 0x3000

  • SEG_SHIFT: 12

  • OFFSET_MASK: 0xFFF

주소 공간에는 세 개의 세그멘트(코드, 힙, 스택)만 존재하기 때문에 지정 가능한 세그멘트 하나는 미사용으로 남음. ➡️ 전체 주소 공간의 1/4은 사용이 불가능

이 문제의 해결을 위해 일부 시스템은 코드와 힙을 하나의 세그멘트에 저장하고 세그멘트 선택을 위해 1비트만 사용

특정 주소의 세그멘트를 하드웨어적으로 파악하는 다른 방법 ➡️ 묵시적(implicit) 접근 방식

묵시적 접근 방식에서는 주소가 어떻게 형성되었나를 관찰하여 세그멘트를 결정

주소가 프로그램 카운터에서 생성(명령어 반입): 주소는 코드 세그멘트 내에 있을 것임.

주소가 스택 또는 베이스에 기반을 둠: 주소는 스택 세그멘트 내에 있음.

다른 모든 주소: 힙 세그멘트 내에 있음.


3️⃣ 스택

다른 세그멘트들과는 반대 방향으로 확장
➡️ 다른 방식의 변환이 필요

간단한 하드웨어가 추가로 필요
➡️ 베이스와 바운드 값뿐 아니라 하드웨어는 세그멘트가 어느 방향으로 확장하는지도 알아야 함.
ex) 하나의 비트를 사용하여 주소가 커지는 쪽으로 확장하면 1, 작아지는 쪽으로 확장하면 0으로 설정

이 사실을 반영하여 세그멘트 레지스터 상에서 하드웨어가 관리해야하는 정보를 다음과 같이 표현할 수 있다.

Segment     Base     Size(max 4K)     Grows Positive?
─────────────────────────────────────────────────────
Code(00)    32K          2K                 1
Heap(01)    34K          3K                 1
Stack(11)   28K          2K                 0

예를 들어, 1️⃣의 예시 상황에서 가상 주소 15KB에 접근한다고 가정해보자.

이 주소는 물리 주소 27KB에 매핑 되어야 한다.

이 가상 주소를 이진 형태로 바꾸면 다음과 같다.

13 12 11 10  9  8  7  6  5  4  3  2  1  0
┌────────────────────────────────────────┐
 1  1  1  1  0  0  0  0  0  0  0  0  0  0
└────────────────────────────────────────┘
└────┴───────────────────────────────────┘
Segment              Offset

하드웨어는 최상위 2비트(11)을 사용하여 스택 세그먼트를 지정하고, 3KB의 오프셋이 남게 된다.

올바른 음수 오프셋을 얻기 위해 3KB에서 최대 크기(4KB)를 뺀 -1KB를 얻는다.

이 음수 오프셋을 베이스에 더하여 올바른 물리 주소 27KB(-1KB + 28KB)

바운드 검사는 음수 오프셋의 절댓값이 세그멘트의 크기보다 작다는 것을 확인하여 계산할 수 있음.


4️⃣ 공유 지원

메모리 절약을 위해 때로는 주소 공간들 간에 특정 메모리 세그멘트를 공유하는 것이 유용
➡️ 특히, 코드 공유가 일반적(현재 시스템에서도 광범위하게 사용 중)

공유 지원을 위해 하드웨어에 protection bit의 추가가 필요
➡️ 세그멘트를 읽거나 쓸 수 있는지 혹은 세그멘트의 코드를 실행시킬 수 있는지를 나타냄.

코드 세그멘트를 읽기 전용으로 설정 ➡️ 주소 공간의 독립성을 유지하면서도, 여러 프로세스가 주소 공간의 일부를 공유할 수 있음.

이를 반영하여 세그멘트 레지스터 값에 부가 정보를 추가하면 다음과 같이 나타낼 수 있다.

Segment    Base  Size(max 4K)  Grows Positive?  Protection
───────────────────────────────────────────────────────────
Code(00)   32K       2K              1         Read-Execute
Heap(01)   34K       3K              1         Read-Write
Stack(11)  28K       2K              0         Read-Write

protection bit를 사용하면 앞의 하드웨어 알고리즘이 수정되어야 함.
➡️ 가상 주소가 범위 내에 있는 지 확인 + 특정 엑세스가 허용되는지 확인

사용자 프로세스가 읽기 전용 페이지에 쓰기를 시도하는 경우 또는 실행 불가 페이지에서 실행하려고 하는 경우
➡️ 하드웨어는 예외를 발생시켜 운영체제가 위반 프로세스를 처리할 수 있게 해야 함.


5️⃣ 소단위 대 대단위 세그멘테이션

지금까지 예제의 대부분은 소수의 세그멘트(코드, 스택, 힙)만들 지원하는 시스템에 주로 초점을 맞춤.
➡️ 주소 공간을 비교적 큰 단위의 공간으로 분할하므로 이 세그멘테이션을 대단위(coarse-grained)라고 생각할 수 있음.

일부 초기 시스템은 주소 공간을 작은 크기의 공간으로 잘게 나누는 것이 허용되었음.
➡️ 소단위(fine-grained) 세그멘테이션

많은 수의 세그멘트를 지원하기 위해서는 여러 세그멘트의 정보를 메모리에 저장할 수 있는 세그멘트 테이블과 같은 하드웨어가 필요
➡️ 매우 많은 세그멘트를 손쉽게 생성하고 융통성 있게 세그멘트를 사용할 수 있음.

💡 당시 소단위 세그멘테이션의 목표: 메인 메모리를 더 효율적으로 활용하자!


6️⃣ 운영체제의 지원

세그멘테이션의 사용 = 하나의 베이스-바운드 쌍을 사용하는 방식에 비해 물리 메모리를 엄청나게 절약

⚠️ 세그멘테이션은 새로운 많은 문제를 제기

[1. 문맥 교환 시 운영체제는 어떤 일을 해야 하는가?]
세그멘트 레지스터를 저장하고 복원해야 함.

[2. 미사용 중인 물리 메모리 공간의 관리]
새로운 주소 공간이 생성 ➡️ 운영체제는 이 공간의 세그멘트를 위한 빈 물리 메모리 영역을 찾을 수 있어야 함.
일반적으로 생길 수 있는 문제 ➡️ 물리 메모리가 빠르게 작은 크기의 빈 공간들로 채워짐.
➡️ 외부 단편화(external fragmentation)

          Not Compacted                       Compacted
 0 KB ┌──────────────────────┐    0 KB ┌──────────────────────┐
      │                      │         │                      │
 8 KB │   Operating System   │    8 KB │   Operating System   │
      │                      │         │                      │
16 KB ├──────────────────────┤   16 KB ├──────────────────────┤
      │     (not in use)     │         │                      │
24 KB ├──────────────────────┤   24 KB │                      │
      │      Allocated       │         │      Allocated       │
32 KB ├──────────────────────┤   32 KB │                      │
      │     (not in use)     │         │                      │
      ├──────────────────────┤         │                      │
40 KB │      Allocated       │   40 KB ├──────────────────────┤
      ├──────────────────────┤         │                      │
48 KB │                      │   48 KB │                      │
      │     (not in use)     │         │     (not in use)     │
56 KB ├──────────────────────┤   56 KB │                      │
      │      Allocated       │         │                      │
64 KB └──────────────────────┘   64 KB └──────────────────────┘

이와 같은 문제의 해결책 중 하나로 위와 같이 기존의 세그멘트를 정리하여 물리 메모리를 압축(compact)하는 방법이 있다.

위의 그림에서 왼쪽과 같이 압축이 되지 않은 상태에서는 20KB의 크기를 갖는 새로운 프로세스를 위한 공간을 할당할 수 없다.

반면 운영체제가 현재 실행 중인 프로세스를 중단하고, 그들의 데이터를 하나의 연속된 공간에 복사하고, 세그멘트 레지스터가 새로운 물리 메모리 위치를 가리키게 하여 작업을 위한 큰 빈 공간을 확보할 수 있다.

⚠️ 하지만 세그먼트 복사는 메모리에 부하가 큰 연산 & 일반적으로 상당량의 프로세서 시간을 사용

💡 간단한 방법: 빈 공간 리스트를 관리하는 알고리즘을 사용

  • 최적 적합(best-fit)

  • 최악 적합(worst-fit)

  • 최초 적합(first-fit)

  • 버디 알고리즘(buddy algorithm)

등을 포함한 수백 개의 방식이 존재

알고리즘이 아무리 정교하게 동작한다고 해도 외부 단편화는 여전히 존재
➡️ 좋은 알고리즘은 외부 단편화를 가능한 줄이는 것이 목표


🧺 세그멘테이션 정리 하기

메모리 가상화를 효과적으로 실현할 수 있음.

✅ 단순한 동적 재배치를 넘어 주소 공간 상의 논리 세그멘트 사이의 큰 공간에 대한 낭비를 피함.
➡️ sparse address space를 지원

✅ 세그멘테이션에 필요한 산술 연산은 쉽고 하드웨어 구현에 적합 ➡️ 속도가 빠름.

변환 오버헤드도 최소임.

코드 공유의 장점도 부가적으로 발생

⚠️ 세그멘트의 크기가 일정하지 않으므로 외부 단편화 문제 발생

⚠️ sparse address space를 지원할 만큼 충분히 유연하지 못함.


📚 참고 문헌

Operating Systems: Three Easy Pieces ― 16: Segmentation

운영체제 아주 쉬운 세 가지 이야기 ― 19: 세그멘테이션

관련있는 게시물


Made with React, Gatsby and DatoCMS by @smastrom

Contribute or star on GitHub

© 2022-2023 손승열 for 🅒🅞🅝🅣🅔🅝🅣🅢

모두 좋은 하루 보내세요! 😊