기록공간

2-9장. 세그멘테이션 본문

OS

2-9장. 세그멘테이션

입코딩 2020. 3. 13. 16:49
반응형

앞에서 살펴봤던 형태의 주소 공간에 대해 재미있는 사실을 발견했을 것이다. 스택과 힙 사이에 사용되지 않는 큰 공간이 존재한다.

 

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

 

세그멘테이션 : 세그멘트의 주소변환

이 문제를 해결하기 위한 아이디어가 탄생했으니, 바로 세그멘테이션(Segmentation)이다. 상당히 오래된 아이디어이다. 아이디어는 간단하다. MMU(Memory Management Unit : 메모리 관리 장치) 안에 오직 하나의 베이스와 바운드 쌍만 존재하는 것이 아니라 주소 공간의 논리적인 세그멘트(Segment) 마다 베이스와 바운드 쌍이 존재한다. 세그멘트는 특정 길이를 가지는 연속적인 주소공간이다. 세그멘테이션을 사용하면 운영체제는 각 세그멘트를 물리 메모리의 각기 다른 위치에 배치할 수 있고, 사용되지 않는 가상 주소 공간이 물리 메모리를 차지하는 것을 방지할 수 있다. 

 

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

 

실제 메모리 세그멘트 할당

위 그림을 보면 64KB의 물리 메모리에 3개의 세그멘트와 운영체제용으로 예약된 16KB 영역이 존재한다. 

 

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

 

세그멘트 지원을 위한 MMU 하드웨어 구조는 다음과 같다. 이 예제의 경우 3쌍의 베이스와 바운드 레지스터 집합이 필요하다. 위 그림은 앞의 예에 해당하는 각 레지스터의 값을 보여준다. 각 바운드 레지스터는 세그멘트의 크기를 저장한다.

 

위 그림의 주소 공간을 사용하여 주소 변환을 해 보자. 가상 주소 100 번지를 참조한다고 가정하자. 가상 주소 100번지는 코드 세그멘트에 속한다. 참조가 일어나면 하드웨어는 베이스 값에 이 세그멘트의 오프셋(이 경우는 100)을 더해 물리 주소는 100+32KB 또는 32868이 된다. 그 후, 주소가 범위 내에 있는지 검사하고, 범위 내에 있을 경우, 물리 메모리 주소 32868을 읽는다.

 

가상 주소 4200의 힙을 살펴보자. 주의할 점이 있다. 가상 주소 4200을 힙의 베이스(34KB)에 더하면 물리 주소 39016을 얻지만 이 주소는 올바른 물리 주소가 아니다. 먼저 힙 안에서의 오프셋, 즉 주소가 참조하는 바이트가 이 세그멘트 시작으로부터 몇 번째 바이트인지를 얻어야 한다. 힙은 가상 주소 4KB에서 시작하기 때문에 오프셋 4200은 실제로는 4200 - 4096, 즉 104가 된다. 이 오프셋을 베이스 레지스터의 물리 주소(34KB)에 더해 원하는 결과 34920을 얻게 된다. 

 

만일 힙의 마지막을 벗어난 7KB와 같은 잘못된 주소를 접근하려고 한다면 어떤 일이 벌어질까? 하드웨어가 그 주소가 범위를 벗어났다는 것을 감지하고 운영체제에 트랩을 발생시킨다. 운영체제는 아마도 문제의 프로세스를 종료시킬 가능성이 크다. 이 현상은 모든 C 프로그래머들이 두려워하는 세그멘트 위반(Segment violation) 또는 세그멘트 폴트(Segment fault)라고 한다.

 

세그멘트 주소

하드웨어는 변환을 위해 세그멘트 레지스터를 사용한다. 하드웨어는 가상 주소가 어느 세그멘트를 참조하는지 그리고 그 세그멘트 안에서 오프셋은 얼마인지를 어떻게 알 수 있을까?

 

한 가지 일반적인 접근법, 가상 주소의 최상위 몇 비트를 기준으로 주소 공간을 여러 세그멘트로 나누는 것이다. 주소 공간을 세그멘트로 나누기 위해서는 2비트가 필요하다. 앞서 봤던 예에서는 3개의 세그멘트가 있다. 세그멘트를 표시하기 위해 가상 주소 14비트중 최상위 2비트를 사용하는 경우 가상 주소의 모양은 다음과 같이 보일 것이다.

 

우리의 예에서 최상위 2비트가 00이면, 하드웨어는 가상 주소가 코드 세그멘트를 가리킨다는 것을 알고, 따라서 코드 세그멘트의 베이스와 바운드 쌍을 사용하여 주소를 정확한 물리 메모리에 재배치한다. 최상위 2비트가 01이면 하드웨어는 주소가 힙 세그멘트라는 것을 인지하여 힙의 베이스와 바운드를 사용한다.  이해를 돕기 위해 앞에서 사용한 힙에 해당하는 가상 주소(4200)를 변환해 보자. 가상 주소 4200에 해당하는 이진 형식은 다음과 같다.

 

그림에서 볼 수 있듯이 최상위 2비트는 하드웨어에게 참조하는 세그멘트의 종류를 알려준다. 하위 12비트는 세그멘트 내의 오프셋이다. (0000 0110 1000 또는 16진수 0x068 또는 10진수로 104) 하드웨어 세그멘트 레지스터를 파악하는 데 처음 2비트를 이용하고 세그멘트 오프셋으로 다음 12비트를 취한다. 오프셋에 베이스 레지스터 값을 더하여 하드웨어는 최종 물리 주소를 계산한다. 오프셋은 바운드 검사도 쉽게 만든다. 오프셋이 바운드보다 작은지 여부만 검사하면 된다. 

 

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

 

현재 예를 기준으로 위 코드에서 사용된 상수 값들을 정해 보면 다음과 같다.

 

세그멘트 종류를 나타내는 데 최상위 2비트(총 4종류가 가능)를 사용하고 주소 공간에는 세 개의 세그멘트만 존재하기 떄문에 지정 가능한 세그멘트 하나는 미사용으로 남는다. 즉, 전체 주소 공간의 1/4는 사용이 불가능하다. 이 문제를 해결하기 위해 일부 시스템은 코드와 힙을 하나의 세그멘트에 저장하고 세그멘트 선택을 위해 1비트만 사용한다. 

 

스택 세그멘트 변환

지금까지 주소 공간의 중요한 구성 요소인 스택을 다루지 않았다. 앞의 그림에서 스택은 물리 주소 28KB에 배치되어 있지만 한 가지 아주 중요한 차이가 있다. 다른 세그멘트들과는 반대 방향으로 확장된다는 것이다. 물리 메모리 28KB에서 시작해서 26KB까지 차지한다. 가상 주소 16KB에서 14KB에 해당한다. 다른 방식의 변환이 필요하다.

 

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

 

하드웨어는 세그멘트가 반대 방향으로 늘어날 수 있다는 것을 알고 있기 때문에, 그런 가상 주소에 대해서는 다른 방식으로 변환한다. 이 과정을 이해하기 위하여 스택에 해당하는 가상 주소를 예로 들어 변환해 보자.

 

이 예에서 가상 주소 15KB에 접근한다고 가정하자. 이 주소는 물리 주소 27KB에 매핑되어야 한다. 이 가상 주소를 이진 형태로 바꾸면 11 1100 0000 0000(0x3C00)이 된다. 하드웨어는 상위 2비트를 사용하여 세그멘트를 지정한다. 이를 고려하면 3KB의 오프셋이 남는다. 올바른 음수 오프셋을 얻기 위해서 3KB에서 세그멘트 최대 크기를 빼야 한다. 이 예에서는 세그멘트의 최대 크기가 4KB이고 따라서 올바른 오프셋은 3KB에서 4KB를 뺀 -1KB이다. 이 음수 오프셋을 베이스 (28KB)에 더하면 올바른 물리 주소 27KB를 얻게 된다. 바운드 검사는 음수 오프셋의 절댓값이 세그멘트의 크기보다 작다는 것을 확인하여 계산할 수 있다. 

 

공유 지원

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

 

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

 

하드웨어가 유지하는 부가 정보의 예가 그림에 나와 있다. 코드 세그멘트는 읽기 및 실행으로 설정되어 같은 물리 세그멘트가 여러 가상 주소 공간에 매핑될 수 있다.

 

세그멘트 레지스터 값 (보호 정보 포함)

protection bit를 사용하면 앞서 언급한 하드웨어 알고리즘이 수정되어야 한다. 가상 주소가 범위 내에 있는지 확인하는 것 이외에 특정 액세스가 허용되는지를 확인해야 한다. 사용자 프로세스가 읽기 전용 페이지를 쓰기를 시도하는 경우 또는 실행 불가 페이지에서 실행하려고 하면 하드웨어는 예외를 발생시켜서 운영체제가 위반 프로세스를 처리할 수 있게 해야 한다.

 

소단위 대 대단위 세그멘테이션

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

 

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

 

OS 지원

세그멘테이션은 새로운 많은 문제를 제기한다. 이 문제들은 운영체제에서 해결할 수 있도록 지원을 해야 한다. 첫 번째 문제는 문맥 교환 시 세그멘트 레지스터의 저장과 복원이다. 각 프로세스는 자신의 주소 공간을 가지며, 운영체제는 프로세스가 다시 실행하기 전에 레지스터들을 올바르게 설정해야 한다.  

 

두 번째 문제는 미사용 중인 물리 메모리 공간의 관리이다. 새로운 주소 공간이 생성되면 운영체제는 이 공간의 세그멘트를 위한 비어있는 물리 메모리 영역을 찾을 수 있어야 한다. 

 

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

 

압축 전와 압축 후의 메모리 상태

위 예에서 새로운 프로세스가 생성되어 20KB를 할당하려고 한다 가정하자. 위 예에서 24KB의 빈 공간이 존재하기는 하지만 하나의 연속된 공간이 아니라 세 개의 청크(chunk)로 나누어져 있다. 운영체제는 20KB의 요청을 충족시킬 수 없다.

 

이 문제의 해결책 중 하나는 기존의 세그멘트를 정리하여 물리 메모리를 압축(Compact)하는 것이다. 운영체제는 현재 실행 중인 프로세스를 중단하고, 그들의 데이터를 하나의 연속된 공간에 복사하고, 세그멘트 레지스터가 새로운 물리 메모리 위치를 가리키게 하여, 작업할 큰 빈 공간을 확보할 수 있다. 다시 말해, 기존의 세그멘트들의 물리적 위치를 재조정하는 것이다. 이렇게 함으로써 운영체제는 새로운 할당 요청을 충족시킬 수 있다. 

반응형

'OS' 카테고리의 다른 글

2-11장. 페이징 : 개요  (0) 2020.03.20
2-10장. 빈 공간 관리(메모리)  (0) 2020.03.18
2-8장. 주소 변환의 원리  (0) 2020.03.09
2-7장. 메모리 관리 API  (0) 2020.03.04
2-6장. 주소 공간의 개념  (0) 2020.03.02
Comments