지금까지 메모리 가상화가 필요한 이유와 목적, 가상화를 하기 위한 동적 재배치 방법에 대해 알아보았다. 동적 재배치는 베이스/바운드 방식을 이용했다. 그런데 주소 공간에서 스택과 힙 사이에 사용되지 않는 빈 공간이 존재하는 것을 보았을 것이다. 그 사이의 빈 공간은 사용되지 않더라도 재배치할 때 물리 메모리를 차지한다. 따라서 메모리 낭비가 심하고, 주소 공간이 물리 메모리보다 크면 실행할 수 없다. 이러한 측면에서 베이스/바운드 방식은 유연성이 없다. 이를 해결하기 위해 등장한 아이디어가 세그멘테이션(segmentaition)이다.
16.1 세그멘테이션: 베이스/바운드(base/bound)의 일반화
이 아이디어는 MMU안에 오직 하나의 베이스/바운드 쌍만 존재하는 것이 아니라 주소 공간의 논리적인 세그멘트마다 베이스/바운드 쌍이 존재한다는 것이다. 세그멘트란 특정 길이를 가지는 연속적인 주소 공간이다. 세그멘트의 종류로는 코드, 스택, 힙 등이 있다. 세그멘테이션을 사용하면 운영체제는 각 세그멘트를 물리 메모리의 각기 다른 위치에 배치할 수 있기 때문에 사용되지 않는 가상 주소 공간이 물리 메모리를 차지해 발생하는 낭비를 막아준다.
예를 들어보자. 아래와 같은 주소 공간을 물리 메모리에 배치하려고 한다.
각 세그멘트의 베이스와 바운드 쌍을 이용하여 세그멘트들을 독립적으로 물리 메모리에 배치할 수 있다. 아래 그림을 보면 64KB의 물리 메모리에 3개의 세그멘트와 운영체제용으로 예약된 16KB 영역이 존재한다. 나머지 사용되지 않은 영역들은 sparse address space라고 한다.
세그멘트 지원을 위해서는 MMU 하드웨어 구조에서 3쌍의 베이스와 바운드 레지스터 집합이 필요하다. 그림 19.1을 보고 가상 주소 100번지를 참조한다고 가정하여 주소 변환을 해보자. 가상 주소 100번지는 코드 세그멘트에 속한다. 참조가 일어나면 하드웨어는 베이스 값이 이 세그멘트의 오프셋을 더해 물리주소는 100 + 32KB(100+32*1024=32868)가 된다. 그 후 주소가 범위 내에 있는지 검사하고, 범위 내에 있을 경우 물리 메모리 주소 32868을 읽는다.
그림 19.1에서 가상 주소 4200의 힙을 살펴보자. 가상 주소 4200을 힙의 베이스 34KB에 더하면 물리주소 39016을 얻는데, 이는 올바른 물리 주소가 아니다. 먼저 힙 안에서의 오프셋, 즉 주소가 참조하는 바이트가 이 세그멘트 시작으로부터 몇 번째 바이트인지를 얻어야 한다. 힙은 가상주소 4KB에서부터 시작하기 때문에 오프셋 4200은 실제로는 4200-4096 = 104가 된다. 이 오프셋 104를 베이스 레지스터의 물리주소 34KB에 더해 물리 주소 34920을 얻을 수 있다.
만약 힙의 마지막을 벗어난 7KB와 같은 잘못된 주소를 접근하려고 한다면, 하드웨어는 그 주소가 범위를 벗어났다는 것을 감지하고 운영체제에게 트랩을 발생시킨다. 운영체제는 아마 프로세스를 종료시킬 것이다. 이게 segment fault이다.
16.2 세그멘트 종류의 파악
코드, 스택, 힙 3개의 세그멘트로 구분해야 한다고 해보자. 주소 공간을 3개의 세그멘트로 나누려면 2비트가 필요하므로 가상 주소 14비트 중 최상위 2비트를 사용한다. 그리고 나머지 12비트는 오프셋으로 사용한다. 오프셋은 세그멘트 시작 주소로부터의 거리를 말하는데, 오프셋으로 사용할 수 있는 비트가 12비트이므로 하나의 세그멘트가 가질 수 있는 최대 공간은 4KB(2^2 * 2^10)이다.
앞에서는 가상 주소 4200의 힙의 주소 변환을 위해서 오프셋 104와 베이스 레지스터 34KB를 더해 34920을 얻었다. 이를 이진 형식으로 나타내보면 아래와 같다.
14비트 중 최상위 2비트가 세그먼트를 나타내는데에 사용되었다. 이 예에서 00은 코드, 01은 힙, 11은 스택을 의미하였으므로 세그멘트를 나타내는 비트가 01이라는 것은 세그멘트의 종류가 힙이라는 것을 나타낸다. 하위 12비트는 세그멘트 내의 오프셋이다. 0000 0110 1000은 10진수로 104를 의미한다.
위와 같은 작업들이 진행될 것이다. 예시에서 SEG_MASK는 0x3000, SEG_SHIFT는 12, 그리고 OFFSET_MASK는 0xFFF로 하면 되겠다.
16.3 스택
근데 스택은 좀 다르다! 스택은 역방향으로 확장된다는 것이다. 우리가 지금까지 살펴본 예(그림 19.3)에서 스택은 27KB부터 역방향으로 메모리를 차지했다. 스택에 해당하는 가상 주소가 15KB라고 해보자. 15KB를 이진수로 나타내면 11 1100 0000 0000이므로 (15KB는 15 * 2^10이므로 1111뒤에 0을 10개 써주면 된다) 하드웨어는 상위 2비트 11을 사용하여 세그멘트를 지정하고, 남은 하위 12비트가 나타내는 3KB는 오프셋이 된다. 하지만 올바른 음수 오프셋을 얻기 위해서는 3KB에서 세그멘트의 최대 크기(이 예에서는 4KB)를 빼야 한다. 3KB - 4KB = -1KB가 되고 이게 올바른 음수 오프셋이다. 베이스 28KB에 음수 오프셋 -1KB를 더하면 올바른 물리주소 27KB가 나온다.
16.4 공유 지원
더욱 메모리를 절약하기 위해서 메모리 세그멘트를 공유하기도 한다. 특히 코드 공유가 일반적이다. 공유를 지원하려면 하드웨어에 protection bit를 추가해야 한다. 세그멘트마다 protection bit를 추가하여 세그멘트를 읽거나(Read) 쓸 수 있는지(Write) 혹은 세그멘트의 코드를 실행시킬 수 있는지(Execution)를 나타낸다.
16.5 소단위 대 대단위 세그멘테이션
우리가 지금까지 본 것은 소수의 세그멘트(코드, 스택, 힙)만을 지원하는 시스템이었다. 우리는 이 세그멘테이션을 대단위(coarse-grained)라고 한다. 주소 공간을 비교적 큰 단위의 공간으로 분할하기 때문이다. 일부 초기 시스템에서는 주소 공간을 더 작은 크기의 공간으로 잘게 나누는 것이 허용되었는데, 이 경우 소단위(fine-grained) 세그멘테이션이라고 부른다. 여러 세그멘트를 지원하기 위해서는 세그멘트들의 정보를 메모리에 저장할 수 있어야 한다. 따라서 세그멘트 테이블 같은 하드웨어의 지원이 필요하다.
16.6, 16.7 운영체제의 지원, 요약
전체 주소 공간이 하나의 베이스 바운드 쌍을 가지는 방식에서는 사용되지 않지만 물리 메모리에 매핑되어 낭비를 초래하는 문제점이 있었다. 그런데 세그멘트 단위로 물리 메모리를 매핑하니 기존의 공간 낭비 문제를 해결했다.
그렇지만 세그멘테이션은 운영체제의 지원을 요구한다. 문맥 교환 시 운영체제는 세그멘트 레지스터까지 저장하고 복원해야 한다. 또 운영체제는 비어있는 물리 메모리 공간을 관리할 수 있어야 한다. 좌측 그림처럼 물리 메모리의 빈 공간들이 작다면 사용하기 애매할 것이다. 이 문제를 외부 단편화(external fragmentation)라고 한다. 운영체제는 기존의 세그멘트를 정리하여 물리 메모리를 압축(compact)하여 우측 그림처럼 빈 공간을 크게 확보할 수 있다.
그런데 이 과정에서 수행되는 세그멘트 복사는 비용이 많이 든다. 따라서 빈 공간 리스트(free list)를 관리하는 알고리즘을 사용한다. 빈 공간 관리 알고리즘은 할당 가능한 메모리 영역들을 리스트 형태로 유지한다. 우리는 다음 장에서 이에 대해 자세히 알아볼 것이다.
세그멘테이션에서 발생하는 또다른 문제는 세그멘테이션이 아직 sparse하게 사용되는 주소 공간을 지원할 만큼 충분히 유연하지 못하다는 것이다. 예를 들어 크기는 크지만 드문드문 사용되는 힙이 하나의 세그멘트에 배정되어 있다고 할 때, 이 힙에 접근하고자 한다면 힙 전체가 물리 메모리에 존재해야 한다. 이를 위한 해결책도 곧 알아보자!
'운영체제' 카테고리의 다른 글
[운영체제] 07. 스케줄러 (0) | 2024.04.23 |
---|---|
[운영체제] 18. Paging (1) | 2024.04.07 |
[운영체제] 06. 제한적 직접 실행(LDE, Limited Direct Execution) (1) | 2024.03.31 |
[운영체제] 15. vm mechanism 주소 변환의 원리 (1) | 2024.03.24 |
[운영체제] 13. Address Space 주소 공간 (0) | 2024.03.23 |