Home

Published

- 15 min read

[ OS ] 7. 세그멘테이션(Segmentation)

img of [ OS ] 7. 세그멘테이션(Segmentation)
  • 주소공간

주소공간

위 그림을 보면 스택과 힙 사이의 공간은 사용되지 않더라도 주소 공간을 물리 메모리에 재배치할 때 물리 메모리를 차지한다. 베이스와 바운드 레지스터 방식은 메모리 낭비가 심하다. 또한, 주소 공간이 물리 메모리보다 큰 경우 실행이 매우 어렵다. 이런 측면에서 볼 때 베이스와 바운드 방식은 유연성이 없다.

Segmentation: 베이스 / 바운드(base / bound)의 일반화

이 문제를 해결하기 위해 세그멘테이션(segmentation)이 등장했다. MMU에서 하나의 베이스와 바운드 값이 존재하는 것이 아니라 세그먼트(segment)마다 베이스와 바운드 값이 존재한다.

세그먼트는 특정 길이를 가지는 연속적인 주소공간이다. 우리가 기준으로 삼는 주소 공간에는 코드, 스택, 및 힙의 세 종류의 세그먼트가 있다. 세그멘테이션을 사용하면 운영체제는 각 세그먼트를 물리 메모리의 각기 다른 위치에 배치할 수 있고, 사용되지 않는 가상 주소 공간이 물리 메모리를 차지하는 것을 방지할 수 있다.

  • 물리 메모리에 세그먼트 배치

물리 메모리에 세그먼트 배치

위 그림을 보면, 사용중인 메모리에만 주소 공간이 할당된다. 사용되지 않은 영역이 많은 대형 주소 공간을 수용할 수 있다.

세그먼트 지원을 위한 MMU 하드웨어 구조는 위와 같다. 위 예제의 경우, 어느 경우던 간에 3쌍의 베이스와 바운드 레지스터 집합이 필요하다. 위에서 보여준 표는 위 그림에서 해당되는 각 레지스터 값을 보여준다. 각 바운드 헤지스터는 세그먼트의 크기를 저장한다.

위 그림에서는 코드 세그먼트가 물리 주소 32KB에 배치되고 크기는 2KB이며, 힙 세그먼트가 34KB에 배치되고 크기는 2KB인 것을 알 수 있다.(표 참고) 세그먼트의 사이즈는 앞서 소개한 바운드 레지스터와 일치한다. 이 세그먼트에 몇 바이트가 유효한지 하드웨어에게 알려준다.

세그먼트 종류의 파악

세그먼트 종류의 파악

일반적으로는 가상주소의 최상위 비트 몇 개를 세그먼트 종류를 나타내는 데 사용한다. 위 예(위의 위 그림)에서는 3개의 세그멘트가 있었다. 주소공간을 세그멘트로 나누기 위해서는 총 2비트가 필요하다.(00 01 10 11)

위 예에서, 최상위 2비트가 00이면, 하드웨어는 가상 주소가 코드 세그멘트를 가리킨다는 것을 알고, 따라서 코드 세그멘트의 베이스와 바운드 쌍을 사용하여 주소를 정확한 물리 메모리에 재배치한다. 최상위 2비트가 01이면 하드웨어는 주소가 힙 세그멘트라는 것을 인지하여 힙의 베이스와 바운드를 사용한다.

가상주소 예시

예를 들어 앞에서 사용한 힙의 가상주소(4200)을 변환해보자. 4200에 해당하는 형식은 위와 같다.

0000 0110 10000 또는 16진수 0x68 또는 10진수로 104이다. 하드웨어는 세그멘트 레지스터를 파악하는 데 처음 2비트를 이용하고 세그멘트 오프셋으로 다음 12비트를 취한다. 오프셋에 베이스 레지스터 값을 더하여 하드웨어는 최종 물리 주소를 계산한다. 오프셋은 바운드 검사도 쉽게 만든다. 오프셋이 바운드보다 작은지 여부만 검사하면 된다. 그렇지 않다면 주소가 잘못된 것이다.

스택

스택 세그먼트

위 그림을 다시 보자. 위 그림에서 스택은 물리 주소 28KB에 위치해 있지만 한 가지 아주 중요한 차이가 있다. 다른 세그먼트들과는 다른 방향으로 확장된다는 것이다.(낮은 주소 방향으로 확장). 물리 메모리 28KB에서 “시작” 해서 26KB까지 차지한다. 가상 주소 16KB에서 14KB에 해당한다. 다른 방식의 변환이 필요하다.

간단한 하드웨어가 추가로 필요하다. 베이스와 바운드 값뿐 아니라 하드웨어는 세그멘트가 어느 방향으로 확장하는지도 알아야 한다. 예를 들어, 하나의 비트를 사용하여 주소가 커지는 쪽(+ 방향)으로 확장하면 1, 작아지는 쪽(- 방향)으로 확장하면 0으로 설정할 수 있다. 아래 그림이 예시이다.

세그먼트 확장 방향

하드웨어는 세그멘트가 반대 방향으로 늘어날 수 있다는 것을 알고 있기 때문에, 그런 가상 주소에 대해서는 다른 방식으로 변환한다. 기존에는 세그멘트를 지정, 남는 오프셋만큼을 더했다면, 스택의 경우에는 반대로 빼야 한다.

공유 자원

세그멘테이션 기법이 발전함에 따라 시스템 설계자들은 간단한 하드웨어 지원으로 새로운 종류의 효율성을 성취할 수 있다는 것을 깨달았다. 예를 들어, 메모리를 절약하기 위해 때로는 주소 공간들 간에 특정 메모리 세그멘트를 공유하는 것이 유용하다. 특히, 코드 공유가 일반적이며, 현재 시스템에서도 광범위하게 사용 중이다.

공유 자원

공유를 지원하기 위해, 하드웨어에 protection bit의 추가가 필요하다. 세그멘트마다 protection bit를 추가하여 세그멘트를 읽거나 쓸 수 있는지 혹은 세그멘트의 코드를 실행시킬 수 있는지를 나타낸다. 코드 세그멘트를 읽기 전용으로 설정하면 주소 공간의 독립성을 유지하면서도, 여러 프로세스가 주소 공간의 일부를 공유할 수 있다. 각 프로세스는 자신이 자신의 전용 메모리를 사용하고 있다고 생각하지만 운영체제는 이 변경이 불가능하도록 설정된 메모리 영역을 비밀리에 공유시켜 프로세스를 착각하게 한다.

coarse-grained & fine-grained

지금까지 우리 예제의 대부분은 소수의 세그멘트(즉, 코드, 힙, 스택)만을 지원하는 시스템에만 초점을 맞추었다. 우리는 이 세그멘테이션을 coarse-grained(대단위)라고 생각할 수 있다. 주소공간을 비교적 큰 단위의 공간으로 분할하기 때문이다. 일부 초기 시스템은 주소 공간을 작은 크기의 공간으로 잘게 나누는 것이 허용되었기 때문에, fine-grained(소단위) 세그멘테이션이라고 부른다.

운영체제의 지원

시스템이 각 주소공간(세그멘트) 단위로 다상 주소 공간을 물리 메모리에 재배치하기 때문에, 전체 주소 공간이 아니라 하나의 베이스-바운드 값을 갖는 방식에 비해 물리 메모리를 절약할 수 있다. 스택과 힙 사이의 사용하지 않는 공간에 대해서는 물리 메모리를 할당할 필요가 없기 때문이다.

세그멘테이션의 도입을 위해서는 운영체제가 몇가지 문제를 해결해야 한다.

첫 번째는 context-switching(문맥교환)이다. 세그멘테이션을 사용할 경우 context-switching시 운영체제는 어떤 일을 해야 할까? 세그멘트 레지스터의 저장과 복원이다. 각 프로세스는 자신의 가상 주소공간을 갖는다. 운영체제는 프로세스 실행 시 레지스터들을 올바르게 설정해야 한다.

두 번째는 세그멘트 크기의 변경이다. 예를 들어, 어떤 프로그램이 malloc()을 호출하여 객체를 할당했다고 하자. 어떤 경우에는 기존의 힙에서 해당 요청을 처리할 수 있을 것이다. malloc()이 빈 공간을 찾고 해당 공간에 대한 포인터를 호출자에게 돌려줄 것이다. 빈 공간이 없다면 힙 세그먼트의 크기를 증가시켜야 한다. 운영체제는 대부분의 경우 공간을 할당한다.

마지막으로, 아마도 가장 중요한 문제는 미사용중인 물리 메모리 공간의 관리이다. 새로운 주소 공간이 생성되면 운영체제는 이 공간의 세그멘트를 위한 비어있는 물리 메모리 영역을 찾을 수 있어야 한다. 이전에 우리는 각 주소 공간의 크기가 동일하다고 가정했다. 물리 메모리는 프로세스가 탑재될 슬롯의 집합이라고 생각할 수 있었다. 지금은 프로세스가 많은 세그멘트를 가질 수 있고, 각 세그멘트는 크기가 다를 수 있다.

일반적으로 생길 수 있는 문제는 물리 메모리가 빠르게 작은 크기의 빈 공간들로 채워진다는 것이다. 이 작은 빈 공간들은 새로이 생겨나는 세그멘트에 할당하기도 힘들고 기존 세그멘트를 확장하는 데에도 도움이 되지 않는다. 우리는 이 문제를 external fragmentation(외부 단편화)이라고 부른다.

외부 단편화 예시

위 예시를 보면, 24KB의 빈 공간이 존재하지만, 하나의 연속된 공간이 아니라 세개의 chunk로 나눠져 있다. 그래서 운영체제는 20KB의 요청을 충족시킬 수 없다.

이 문제의 해결책중 한가지는 기존의 세그멘트를 정리하여 물리 메모리를 압축하는 것이다. 예를 들어, 운영체제는 현재 실행 중인 프로세스를 중단하고, 그들의 데이터를 하나의 연속된 공간에 복사하고, 세그멘트 레지스터가 새로운 물리 메모리 위치를 가리키게 하여, 자신이 작업할 큰 빈공간을 확보할 수 있다. 이렇게 함으로써 운영체제는 새로운 할당 요청을 충족시킬 수 있다. 하지만 세그멘트 복사는 메모리에 연산으로 부하가 크고, 보통 상당량의 프로세서 시간을 사용하기 때문에 압축은 비용이 많이 든다.

간단한 방법은 빈 공간 리스트를 관리하는 알고리즘을 사용하는 것이다. 빈 공간 관리 알고리즘은 할당 가능한 메모리 영역들을 리스트 형태로 유지한다. best-fit, worst-fit, first-fit, buddy algorithm과 같은 고전적인 알고리즘을 포함하여 말 그대로 수백개의 방식이 존재한다. 알고리즘이 아무리 정교하게 동작한다고 해도 external fragmentation(외부 단편화)는 여전히 존재한다. 좋은 알고리즘은 external fragmentation을 가능한 한 줄이는 것이 목표이다.