이전 파트에서는 가장 기초적인 메모리 가상화 기법 개요를 알아봤습니다. 이번에는, 가상화된 메모리 주소가 물리 주소로 변환되는 과정, 그리고 물리 메모리에 배치되는 과정을 다뤄보겠습니다. 본격적인 주소 변환을 다루기 전에, 간단히 정리를 하고 시작하겠습니다. 먼저 메모리 가상화는 효율성을 추구합니다. 레지스터나 TLB(Translation Lookaside Buffer) 등의 하드웨어적 지원을 통해 효율적인 가상화 시스템을 구축합니다. 여기서 TLB 라는 이름의 버퍼는, 최근에 사용된 가상주소와 해당하는 물리주소의 매핑 정보가 유지되는 캐시입니다. 이후 또 나올 겁니다. 결국엔 물리주소로 '변환'하는 과정을 통해 알맞는 물리 주소를 할당받게 되는 과정이니, 이들을 기록하고 유지하기 위한 담당이 존재해야 하며, 이를 TLB가 맡았음을 기억하면 됩니다. 두번째로, 메모리 가상화는 제어의 기능을 보입니다. 각 프로세스 주소 공간을 분리하여 관리해, 특정 프로세스가 타 프로세스 점유 메모리에 무단으로 접근하는 동작을 방지합니다. 이를 반대로 생각한다면, 각 프로세스는 각자만 접근할 수 있는 독립된 주소 공간을일 확보 할 수 있음을 의미하고, 이는 따라서 보안적으로도 비교적 안전하며, 값이 망가지는 경우를 방지할 수 있습니다. 또한 메모리 가상화를 통해 보다 유연한 운영체제 작동이 가능해집니다. 이전 글에서 언급했듯, 가상 메모리 시스템은 일종의 추상화의 역할을 겸합니다. 우리는 전반적인 주소 변환 등을 잘 몰라도 자연스레 진행할 수 있는 유연성 측변의 장점을 보입니다.
주소 변환
시작하기에 앞서, 아래의 세가지 사항을 가장 기초 가정으로 잡고 가겠습니다. 물론 실제와는 많이 동떨어져 있는 가정이긴 하지만, 이를 통해 보다 이해하기 쉬운 기초 설명이 가능합니다. 해당 챕터를 이어 진행하면서 점차 가정을 완화시켜 나가겠습니다.
1. 사용자 주소 공간은 물리 메모리 내에 연속적으로 배치되어야 한다.
2. 주소 공간의 크기는 물리 메모리 크기보다 작다.
3. 각 프로세스의 주소 공간 크기는 동일하다.
주소 변환(Address Translation)이란, 말 그대로 프로그램이 사용하는 가상 주소를 실제 물리 메모리 상의 주소로 매핑시키는 과정을 의미합니다. 이런게 꼭 필요할까요? 그렇습니다. 이전에 우리는, 한정된 물리 메모리를 보다 효율적으로 사용하기 위해, 또는 물리 메모리의 한계성을 극복하여 사용 가능하게 하기 위해 가상 주소를 할당한다고 했습니다. 이는 곧, 할당되는 가상 주소 전체의 양은 실제 물리 메모리의 공간보다 더 큼을 시사합니다. 그렇기 때문에, 단순히 가상 주소가 할당된 상태라고 해서 물리 주소가 존재하는, 즉 메모리에 적재되어 있다는 것이 보장되지 않게 됩니다. 따라서 주소 변환이라 함은, 해당 가상 주소를 실제 물리 메모리 주소와 매핑하며, 메모리에 적재시킴을 의미합니다.
void func() {
int x = 3000;
x = x + 3;
}
위는 매우 간단한 함수입니다. 변수 x를 설정하고, 이 값에 3을 더하여 다시 x에 저장합니다. 해당 함수의 변수 x가 메모리에 적재된다면 어떤 과정을 거칠까요? 아래는 함수 func의 동작 내용에 기초한 어셈블리 코드입니다. x의 주소가 %ebx 레지스터에 저장되어 있다고 가정합니다.
movl 0x0(%ebx), %eax;
addl $%0x3 %eax;
movl %eax, 0x0(%ebx);
먼저 레지스터 %ebx에 저장된 메모리 주소에 존재하는 값을 mov를 통해 %eax 레지스터로 복사합니다. 그리고 %eax에 저장된 값에 3을 더한 뒤, 다시 %ebx에 저장된 메모리 주소에 덮어씁니다. 이를 메모리로 정의해볼까요? 아래의 정리된 그림을 참고하여, 동작 내용을 정리해보겠습니다.
먼저 주소 128의 mov 명령어가 반입됩니다. 그리고 해당 명령어가 실행되며 15KB 주소에서 값이 탑재됩니다. 이후 주소 132 명령어가 반입됩니다. 해당 addl 명령어도 실행됩니다. 이땐 레지스터 값을 직접 사용하니 메모리 접근은 존재하지 않습니다. 이후 주소 135 명령어가 반입되며 다시 mov를 통해 15KB 주소로 레지스터의 값이 복사됩니다.

프로그램의 관점에서, 주소 공간은 0에서 시작해 최대 16KB까지 존재합니다. 즉, 해당 프로그램에서 생성되는 모든 메모리 참조는 해당 범위 내에 존재해야합니다. 하지만 우리는 메모리 가상화를 통해서, 프로그램의 주소 0부터 시작하는 가상 주소 환상을 유지시키되, 실제로는 0이 아닌 다른 곳에 위치시키도록 재배치할 것입니다. 물론 실제에선 완전히 다릅니다. 하지만 위에서 언급한 구현적 가정 하에, 연속되어 있음을 토대로 설명합니다. (대표적인 예시로, 0은 비워둬야 합니다. 뭔 일이 일어날지 몰라요)
동적(하드웨어 기반) 재배치
첫 하드웨어 기반 주소 변환은, 베이스와 바운드(Base and Bound) 기법의 채택에서 시작되었습니다. 해당 기법은, 동적 재배치(Dynamic Relocation)라고도 부릅니다. 해당 기법에서는 두개의 하드웨어 레지스터가 요구됩니다. 베이스(base) 레지스터와, 바운드(bound) 레지스터(또는 한계(limit) 레지스터 라고도 합니다.) 쌍을 사용하는 해당 기법은, 배치와 동시에, 해당 프로세스의 주소 공간에만 접근됨을 보장하는 효과를 보입ㄴ디ㅏ. 해당 설정에서, 각 프로그램은, 주소 0에 탑재되는 것처럼 작성되며 컴파일됩니다. 그리고 프로그램이 시작될 때, 운영체제가 프로그램이 실제 탑대될 물리 메모리 주소를 결정하고, 베이스 레지스터에 저장됩니다. 예를 들어, 아래 그림을 참고해 32KB에 저장시켰다고 해보겠습니다. 이어서 프로세스가 실행됩니다. 그리고 이 프로세스에 의해 생성되는 모든 주소 값은, 베이스 레지스터에 저장된 주소를 상수값(오프셋)으로 활용하여 연산됩니다. PA = VA + %base 레지스터 가 되는거죠. 즉, 프로세스가 생성하는 가상 주소에 대해, 하드웨어가 베이스 레지스터에 저장된 주소값을 상수로 활용하여 물리 주소로 변환시키는 굉장히 간단한 기법입니다. 아래 그림을 예시로, 베이스 레지스터의 32KB가 상수값으로 작용하여, 프로세스는 스스로 0에서 시작하는 것으로 인지하고 있겠지만, 32KB가 더해져 실제 물리 메모리에서는 32KB에서부터 작성되는 것을 확인할 수 있습니다.

단순히 베이스 레지스터만 더하는 방식의 물리 주소 변환 방식은 수많은 위험성이 내포됩니다. 따라서 바운드(왜 한계라고 불리는지 여기서 나옵니다) 레지스터를 이용해 그 한계선을 설정합니다. 설명 중인 예제 기준, 16KB 더 떨어진 영역이 이 바운드 레지스터에 저장되어 유지되며, 프로세서에서 해당 메모리 참조가 하법적인지를 확인하기 위해, 해당 바운드 레지스터를 조회하여 검토합니다.
즉, 음수 또는 바운드 레지스터를 넘어가는 영역에 대한 조회가 시도될 시, CPU에서는 이를 권한 밖의 시도로 판단 예외(fault)를 발생시켜 프로세스를 종료시킵니다. 이 베이스와 바운드 렞시ㅡ터는 CPU 침 상에 존재하는 하드웨어 구조로, 이러한 주소 변환에 도움을 주는 프로세서의 일부를 메모리 관리 장치(Memory Management Unit, MMU)라고 부릅니다.
이 베이스와 바운드 방식의 가상 메모리 구현에서는, 세 개의 중요한 지점에서 운영체제가 반드시 개입되어야 합니다. 먼저 프로세스가 생성 될 때, 운영체제에서는 새 프로세스가 할당될 물리 메모리 영역을 탐색합니다. 해당 물리 메모리는 슬롯(slot) 배열로 관리되며, 각 슬롯의 사용 여부를(PintOS 비트맵 ptsd가...) 연결 리스트 형태 등의 자료구조로 관리합니다. 해당 자료구조를 통해 빈 슬롯을 식별, 이를 할당시켜줍니다. 이후 프로세스가 종료될 때에도 운영체제가 개입합니다. 종료된 프로세스가 사용하던 메모리 영역을 회수하고, 다시 다른 프로세스나 운영체제가 사용할 수 있도록 해야 하니 어쩌면 당연한 수순입니다. 프로세스가 정상 또는 강제 종료되었을 때, 해당 프로세스가 점유 중인 메모리 영역을 다시 비었다는 판정을 내리고, 이를 각 자료구조에 반영합니다. 콘텍스트 스위칭에서도 운영체제가 개입합니다. CPU에는 단 한 쌍의 베이스 바운드 레지스터 쌍이 존재합니다. 이는 실행 중인 프로세스마다 각기 다른 값을 가지는 특성상, 해당 값의 백업이나 로드가 필요함을 의미합니다. 운영체제는 이러한 전환 과정에서, 현재 프로세스의 베이스, 바운드 레지스터 값을 저장하고 이후 실행될 새 프로세스의 값으로 재설정합니다. 이들 값은 프로세스 제어 블록(Process Control Block, PCB)에 저장되며, 운영체제는 이 PCB에서 값을 읽어와 CPU 레지스터에 로드하는 방식으로 동작이 이루어집니다. 추가적으로, 불법적인, 예외 케이스의 동작 필요 시에도 운영체제가 개입합니다.
세그멘테이션
위 가정에서는 프로세스 주소 공간 전체를 메모리에 탑재하는 것이 가정되었습니다. 베이스와 바운드 레지스터 기법은 이러한 가정 하에서 동작할 땐 매우 간단하고 효과적인 기법입니다. 하지만 위 그림에서 볼 수 있는 것처럼, 힙과 스택 사이에는 사용하지 않는 빈 공간이 있습니다. 그렇다면, 조금 더 대용량의 메모리 관리로 나아간다면, 상당한 낭비가 될지도 모를 일입니다. 이러한 문제점을 해소하기 위해 채택된 아이디어가 세그멘테이션(Segmentation)입니다. 해당 기법을 통해 보다 효율적인 메모리 사용과 유연한 배치를 기대할 수 있습니다.
세그멘테이션은 주소 공간을 논리적으로 분할한 뒤, 각각의 세그먼트에 대해 별도의 베이스와 바운드 쌍을 형성시켜 MMU에 저장하는 기법입니다. 이 세그멘트는, 특정 길이를 가지는 연속적인 주소 공간을 나타내며, 해당 주소 공간의 설명에서는 코드와 스택, 힙세 종류의 세그멘트가 등장합니다.
세그멘테이션이 적용되는 예시로 아래의 그림을 예로 들 수 있겠습니다. 운영체제의 영역인 0~16KB를 제외하고, 스택과 코드, 힙의 세그멘트가 할당되며, 상승 또는 하강의 방식으로 확장됩니다. 또한 이때, 각 세그멘트의 크기가 바운드 레지스터에 해당합니다. 해당 경우에는, 코드 세그멘트의 베이스는 32KB이며 크기(바운드)는 2KB, 스택의 베이스는 28KB이며 크기 2KB, 힙의 경우 베이스 34KB, 크기 3KB로 존재합니다. 불법적인 접근을 의미하는 세그멘트 폴트(Segment Fault)가 여기서 발생합니다.

세그멘테이션은 프로그램의 논리 구조에 기반하여 메모리를 관리합니다. 따라서 프로그래머에게 직관적이며 유연한 메모리 모델을 제공할 수 있습니다. 세그먼트 단위의 메모리 보호와 공유 또한 제공되기 때문에 보안적인 측면에서 보다 안정적이라 할 수 있습니다. 물론 세그멘테이션 기법 자체의 특성인, 산개된 빈 공간이라는 점으로 인해, 외부 단편화가 심화될 수 있으며, 세그먼트 테이블 관리로 인해 오버헤드가 일부 존재하게 될 수 있습니다. 현대에 이르러, 대부분의 운영체제에서는 이러한 세그멘테이션 보다는 페이징(paging) 기법을 이용해 메모리를 관리합니다. 고정된 크기의 페이지로 나누어 메모리를 관리하기 때문에 언급한 외부 단편화의 문제를 최대한 해소할 수 있다는 장점이 있습니다..만, 고정된 큰 크기의 페이지가 지속 할당되기 때문에, 단수의 큰 공백 공간이 남을 수 있다는 특유의 단편성 또한 존재하기 때문에 이 또한 완벽한 기법이라 하긴 어렵습니다. 따라서 일부 운영체제에서는 세그멘테이션과 페이징을 혼합하여 사용하기도 합니다.
하드웨어는 주소 변환을 위해, 세그멘트 레지스터를 사용합니다. 그렇다면 가상주소마다 어떤 세그멘트를 참조하는지 하드웨어 측에서 알 수 있는 수단이 필요합니다. 일반적인 접근 방법은 최상위 몇 비트를 사용해 주소 공간을 여러 세그멘트로 구별시키는 방법입니다. VAX/VMS 시스템에서 채택된 이 기법은, 각 세그멘트마다 임의 비트를 부여한 뒤, 남은 공간을 오프셋으로 활용하는 기법입니다. 위 그림을 활용해 이어서 예를 들자면, 세개의 세그멘트는, 2개의 비트로 나타낼 수 있습니다(00, 01, 10 등) 따라서 아래의 그림과 같이 2개의 비트를 세그멘트 구분용으로, 나머지 오프셋을 주소 형태로 가져갈 수 있게 됩니다.

구별 비트가 나왔으니, 조금 더 생각해볼 만한 요소를 떠올려 봅시다. 위 세그멘트를 활용한 물리 메모리 그림을 보면, 코드와 힙의 확장 방향과 스택의 확장 방향이 상반됨을 확인할 수 있습니다. 그렇다면, 하드웨어는 이들의 확장 방향을 자동으로 식별할 수 있을까요? 어려울 가능성이 높습니다. 세그멘트의 구분 비트를 처음부터 정의된 상태로 둔다면 가능할지도 모를 일이겠습니다만, 그렇다면 불필요하게 주소 공간의 최대 크기가 줄어들 여지가 있기에 그렇게 해두지 않은 거지 않을까 조심스레 예상해봅니다. 그렇다면 스택의 확장 또는 힙이나 코드 세그멘트의 확장의 방향으니 어떻게 정의내려 하드웨어가 식별할 수 있도록 할 수 있을까요? 이 또한 세그멘트 레지스터에 반영하는 것입니다. 양의 방향으로 확장되는(코드나 힙 등) 경우 1, 반대의 경우 0으로 방향비트를 설정하여 이를 세그멘트 레지스터에 반영하면 됩니다. 조금 더 생각해볼까요? 이번엔 코드를 보겠습니다. 코드를 임의로 건드려도 괜찮을까요? 아닙니다. 하지만 실행은 가능해야겠죠. 힙이나 스택는 읽기와 쓰기가 가능해야합니다. 이 또한 하드웨어가 자동으로 식별하긴 어렵겠죠. 이 역시 세그멘트 레지스터에 반영시킨다면 좋을 것 같습니다. 따라서 최종적인 세그멘트 레지스터는 보호 정보까지 포함하게되며, 아래와 같은 내용을 담고 있습니다.

세그멘테이션도 운영체제 입장에선 손이 많이 가는 기법입니다. 먼저 세그멘트 레지스터의 저장과 복원의 문제가 존재합니다. 이는 위의 기초적인 베이스 바운드 기법에서부터 존재했던 필연적 문제이니 그렇다 치지만, 미사용 중인 물리 메모리 공간 관리 측면의 문제 또한 존재합니다. 이쪽이 더 중요합니다. 새 주소 공간이 할당되면, 운영체제는 이 공간의 세그멘트를 위한 빈 메모리 영역을 탐색합니다. 각 공간 크기가 동일하다 가정했지만, 세그멘트의 크기는 각기 다르니 이 경우 가정이 소용없습니다. 탐색 방법에서부터 여러 로직이 존재하게 되며, 각각의 장단점을 보입니다. 단순히 탐색 뿐만이 아닙니다. 외부 단편화의 문제도 존재합니다. 중요한건 이 외부 단편화를 최소화 시켜야된다는겁니다. 해당 문제점의 해결책 중 하나로, 기존 세그멘트를 정리해 물리 메모리를 압축(compact)시키자는 아이디어가 등장했습니다. 현재 프로세스의 실행을 중단하고, 해당 데이터들을 하나의 연속된 공간에 복사하고 새 물리 메모리 위치를 부여하여 외부 단편화된 빈 공간을 종합하여 사용 가능한 새 공간으로 재창조하는겁니다. 하지만 세그멘트 복사 자체도 메모리 부하가 큰 연산이며, 상당한 오버헤드가 발생하기 때문에 굉장한 고비용의 아이디어였습니다.
빈 공간리스트를 활용하는 알고리즘도 존재합니다. 이는 위의 재압축보다 적은 오버헤드를 보이며 효율적입니다. 최적적합(best-fit) 또는 최악적합(worst-fit), 최초 적합(first-fit), 버디 알고리즘(buddy-algorithm) 등 각각의 장단이 존재하는 여러 리스트를 활용해 종합 관리할 수 있습니다. 이 또한 사용 경향에 따라 외부단편화가 적은 방법으로 채택하는 것이 합리적입니다.(CSAPP에서 했던 그겁니다. 너무무서워)
소단위(Fine-grained) 대 대단위(Coarse-grained)
위와 같은 소수의 세그멘트(코드나 스택, 힙 등)만을 지원하는 시스템은, 비교적 큰 단위의 공간으로 분할되기 때문에 대단위(Coarse-grained) 세그멘테이션으로 간주합니다. 반대로 일부 초기 시스템의 경우 주소 공간을 작은 크기로 잘게 나누는 것을 하용하여 소단위(Fine-grained) 세그멘테이션으로도 존재합니다. 소단위 세그멘테이션은 주소 공간을 작은 크기로 분할한다는 기법 상, 메모리를 작은 공간으로 나눠 보다 세밀하게 관리가 가능합니다. 따라서 보다 정밀한 보안 제어가 가능합니다. 하지만, 많이 분할했다는 것은 이들의 정보를 종합하여 유지할 세그멘트 테이블 등이 더 많은 자원을 소모하게됨을 의미합니다.
대단위 세그멘테이션은 반대로, 지금까지의 예시처럼 큰 단위로 세그멘트를 분할합니다. 따라서 세그멘트 테이블이 사용하는 자원이 보다 절약된다는 장점을 보입니다. 물론 적은 자원소모와 용이한 관리라는 장점과 대비하여 세밀한 메모리 관리나 보안의 정밀성에 대해선 소단위에 비해 비적합함을 알 수 있습니다. 따라서 필요에 따라 알맞는 기법을 채택할 필요가 있습니다.
'운영체제 OStudy' 카테고리의 다른 글
| [OStudy] 8주차 - 메모리 가상화(4) (0) | 2025.10.31 |
|---|---|
| [OStudy] 7주차 - 메모리 가상화(3) (0) | 2025.10.21 |
| [OStudy] 5주차 - 메모리 가상화(1) (0) | 2025.09.29 |
| [OStudy] 4주차 - 스케줄링(2) (1) | 2025.09.22 |
| [OStudy] 3주차 - 스케줄링(1) (0) | 2025.09.15 |