개발 공부
[운영체제][OSTEP] 주소 변환의 원리
효율적이고 유연하게 메모리를 가상화하는 방법에 대해 공부해봅니다.

![[운영체제][OSTEP] 주소 변환의 원리](https://www.datocms-assets.com/66479/1686988115-ostep.jpg?auto=format&w=860)
🚪 들어가며
메모리 가상화 또한 CPU 가상화와 비슷한 전략을 추구
➡️ 가상화를 제공하는 동시에 효율성(efficiency)과 제어(control) 모두를 추구
효율성을 높이려면 하드웨어 지원을 활용할 수 밖에 없음.
제어는 응용 프로그램이 자기자신의 메모리 이외에는 다른 메모리에 접근하지 못한다는 것을 운영체제가 보장하는 것을 의미
유연성(flexibility) 측면에서 VM 시스템에서 필요한 사항
➡️ 프로그래머가 원하는 대로 주소 공간을 사용하고, 프로그래밍하기 쉬운 시스템을 만들기 원함.
하드웨어-기반 주소 변환(hardware-based address translation) 또는 주소 변환(address translation)
➡️ 제한적 직접 실행 방식에 부가적으로 사용되는 기능이라고 생각할 수 있음.
주소 변환을 통해 하드웨어는 명령어 반입, 탑재, 저장 등의 가상 주소를 정보가 실제 존재하는 물리 주소로 변환 ➡️ 하드웨어가 주소를 변환
운영체제는 메모리의 빈 공간과 사용 중인 공간을 항상 알고 있어야 하고, 메모리 사용을 제어하고 관리
🚀 이 모든 작업의 목표
프로그램이 자신의 전용 메모리를 소유하고 그 안에 자신의 코드와 데이터가 있다는 환상을 만드는 것
1️⃣ 가정
사용자 주소 공간은 물리 메모리에 연속적으로 배치되어야 한다고 가정
단순화를 위해 주소 공간의 크기가 너무 크지 않다고 가정
각 주소 공간의 크기는 같다고 가정
2️⃣ 사례
다음과 같은 프로세스가 있다고 가정하자.
메모리에 값을 탑재하고, 3을 증가시키고, 다시 메모리에 저장하는 코드이다.
이 코드의 C 언어 표현은 다음과 같이 나타낼 수 있다.
void func() {
int x = 3000;
x = x + 3; // 우리가 관심있는 코드
}
컴파일러는 이 코드를 어셈블리 코드로 변환하고, 그 결과는 x86 어셈블리로 다음과 같을 것이다.
128: movl 0x0(\%ebx), \%eax ; 0+ebx를 eax에 저장
132: addl \$0x03, \%eax ; eax레지스터에 3을 더한다
135: movl \%eax, 0x0(\%ebx) ; eax를 메모리에 다시 저장
다음은 위의 코드와 데이터가 프로세스 주소 공간에 어떻게 배치되어 있는지 나타내고 있다.
0 KB ┌──────────────────────┐
128│ movl 0x0(%ebx), %eax │
132│ addl 0x03, %eax │
135│ movl %eax, 0x0(%ebx) │
1 KB │ │
│ Program Code │
2 KB ├──────────────────────┤
│ │
3 KB │ Heap │
│ │
4 KB ├──────────────────────┤
│ │ │
│ │ │
│ │ │
│ ↓ │
│ (free) │
│ ↑ │
│ │ │
│ │ │
│ │ │
14 KB ├──────────────────────┤
│ │
15 KB │ 3000 Stack │
│ │
16 KB └──────────────────────┘
세 개의 명령어 코드는 주소 128에 위치하고, 변수 x의 값은 주소 15KB에 위치한다.
또한 x의 초기 값은 3000이다.
이 명령어가 실행되면 프로세스의 관점에서 다음과 같은 메모리 접근이 일어난다.
주소 128의 명령어를 반입
해당 명령어 실행(주소 15KB에서 탑재)
주소 132의 명령어를 반입
해당 명령어 실행(메모리 참조 없음)
주소 135의 명령어를 반입
해당 명령어 실행(15KB에 저장)
[프로그램 관점]
주소 공간: 주소 0부터 시작하여 최대 16KB까지
➡️ 프로그램이 생성하는 모든 메모리 참조는 이 범위 내에 있어야 함.
메모리 가상화를 위해 운영체제가 프로세스를 물리 메모리 주소 0이 아닌 다른 곳에 위치시키고 싶을 때, 프로세스 모르게 메모리를 다른 위치에 어떻게 재배치 하느냐가 관건
다음은 이 프로세스의 주소 공간이 메모리에 배치되었을 때 가능한 물리 메모리 배치의 예시이다.
0 KB ┌────────────────────────────┐
│ │
│ Operating System │
│ │
16 KB ├────────────────────────────┤
│ │
│ (not in use) │
│ │
32 KB ├────────────────────────────┤ ┐
│ Code │ │
├────────────────────────────┤ │
│ Heap │ │
├────────────────────────────┤ │ Relocated
│ ↓ │ │ Process
│ (allocated but not in use) │ │
│ ↑ │ │
├────────────────────────────┤ │
│ Stack │ │
48 KB ├────────────────────────────┤ ┘
│ │
│ (not in use) │
│ │
64 KB └────────────────────────────┘
물리 메모리의 첫 번째 슬롯 - 운영체제 자신이 사용
위의 예시에서 프로세스는 물리 주소 32KB에서 시작하는 슬롯에 재배치됨.
다른 두 슬롯(16KB-32KB와 48KB-64KB)은 비어 있음.
3️⃣ 동적(하드웨어-기반) 재배치
1950년대 후반의 첫 번째 시분할 컴퓨터에서 베이스와 바운드(base and bound)라는 간단한 아이디어가 채택됨. ➡️ 동적 재배치(dynamic relocation)라고도 함.
각 CPU마다 2개의 하드웨어 레지스터가 필요
베이스(base) 레지스터
바운드(bound) 레지스터 또는 한계(limit) 레지스터
이러한 CPU 쌍은 우리가 원하는 위치에 주소 공간을 배치할 수 있게 함.
➡️ 배치와 동시에 프로세스가 오직 자신의 주소 공간에만 접근한다는 것을 보장
이 설정에서 각 프로그램은 주소 0에 탑재되는 것처럼 작성되고 컴파일됨.
프로그램 시작 시, 운영체제가 프로그램이 탑재될 물리 메모리 위치를 결정하고 베이스 레지스터를 그 주소로 지정.
➡️ 2️⃣의 재배치 예시에서 운영체제는 프로세스를 물리 주소 32KB에 저장하기로 결정하고 베이스 레지스터를 이 값으로 설정
프로세스에 의해 생성되는 모든 주소가 다음과 같은 방법으로 프로세서에 의해 변환
physical address = virtual address + base
프로세스가 생성하는 메모리 참조는 가상 주소
하드웨어는 베이스 레지스터의 내용을 이 주소에 더하여 물리 주소를 생성
위의 예시 코드의 일부를 통해 명령어 실행 시 동작 방식을 조금 더 알아보자.
128: movl 0x0(\%ebx), \%eax
프로그램 카운터(PC)는 128로 설정됨.
하드웨어가 위 명령어를 반입할 때, PC 값을 베이스 레지스터의 값 32KB(32768)에 더해 32896의 물리 주소를 얻음.
하드웨어는 해당 물리 주소에서 명령어를 가져옴.
프로세서는 명령어의 실행을 시작함.
프로세스는 가상 주소 15KB의 값을 탑재하라는 명령어를 내림.
이 주소를 프로세서가 받아 다시 베이스 레지스터(32KB)를 더하고 물리 주소 47KB에서 원하는 내용을 탑재함.
⭐ 위의 내용 중 가상 주소에서 물리 주소로의 변환이 주소 변환이라고 부르는 기술임.
주소의 재배치가 실행 시에 일어나고, 프로세스가 실행을 시작한 이후에도 주소 공간을 이동할 수 있기 때문에 동적 재배치(dynamic relocation)라고도 불림.
⚠️ 베이스와 바운드 레지스터는 CPU 칩 상에 존재하는 하드웨어 구조임.(CPU당 1쌍)
메모리 관리 장치(memory management unit, MMU): 주소 변환에 도움을 주는 프로세서의 일부
바운드 레지스터는 다음의 두 가지 방식 중 하나로 정의될 수 있음.
[1] 주소 공간의 크기를 저장하는 방식
➡️ 하드웨어는 가상 주소를 베이스 레지스터에 더하기 전 먼저 바운드 레지스터와 비교
[2] 주소 공간의 마지막 물리 주소를 저장하는 방식
➡️ 하드웨어는 먼저 베이스 레지스터를 더하고 그 결과가 바운드 안에 있는지 검사
예제
주소 공간의 크기가 4KB인 프로세스가 물리 주소 16KB에 탑재되어 있다고 가정하자.
주소 변환의 결과는 다음과 같다.
Virtual Address Physical Address
──────────────────────────────────────────
0 → 16KB
1 KB → 17KB
3000 → 19384
4400 → Fault(out of bounds)
위의 예시와 같이 물리 주소를 얻기 위해서는 간단히 가상 주소에 베이스 주소를 더하기만 하면 됨.
가상 주소가 너무 크거나 음수일 경우에 폴트를 일으키고 예외가 발생하게 됨.
그렇다면 소프트웨어-기반 재배치는 무엇일까?
초창기, 하드웨어 지원이 제공되기 전, 일부 시스템은 소프트웨어만으로 재배치를 수행하였음.
➡️ 정적 재배치(static relocation)라고 불림.
로더(loader): 실행하고자 하는 실행 파일의 모든 주소를 원하는 물리 메모리 오프셋으로 변경하는 소프트웨어
⚠️ 정적 재배치의 문제점
[1] 보호 기능이 없음.
➡️ 잘못된 주소를 생성하여 다른 프로세스나 운영체제의 메모리를 불법적으로 접근할 수 있음.
[2] 한 번 배치되면 추후 주소 공간을 재배치하는 것이 어려움.
4️⃣ 하드웨어 지원: 요약
특권 모드(또는 커널 모드)
응용 프로그램이 실행되는 사용자 모드와 달리 컴퓨터 전체에 대한 접근 권한을 가지며 운영체제가 실행되는 CPU 모드이다.
프로세서 상태 워드(processor status word) 레지스터의 한 비트가 CPU의 현재 실행 모드를 나타낸다.
시스템 콜 또는 예외 및 인터럽트 발생 시와 같은 특정 순간에 CPU가 모드를 전환한다.
베이스/바운드 레지스터
CPU는 메모리 관리 장치(MMU)의 일부인 추가의 레지스터 쌍(베이스/바운드 레지스터)을 가짐.
가상 주소를 변환하고 범위 안에 있는지 검사하는 능력
가상 주소의 변환
➡️ 프로그램이 실행 중인 경우, 하드웨어는 프로그램이 생성한 가상 주소에 베이스 값을 더하여 주소를 변환
하드웨어의 주소 유효성 검사
➡️ 바운드 레지스터와 CPU 내의 일부 회로를 사용하여 이루어짐.
베이스/바운드를 갱신하기 위한 특권 명령어
다른 프로세스를 실행시킬 때 운영체제가 이 명령어를 사용하여 베이스와 바운드 레지스터의 값을 변경
이 명령어들은 특권 명령어이므로 특권 모드(커널 모드)에서만 레지스터를 변경할 수 있음.
예외 발생 기능 및 예외 핸들러 등록을 위한 특권 명령어
CPU는 사용자 프로그램이 바운드를 벗어난 주소로 불법적인 메모리 접근을 시도하려는 상황에서 예외를 발생시킬 수 있어야 함.
➡️ "바운드 벗어남" 예외 핸들러(exception handler)가 실행
사용자 프로그램이 베이스와 바운드 레지스터 값의 변경을 시도
➡️ 예외 발생 및 "사용자 모드에서의 특권 연산 발생" 핸들러 실행
5️⃣ 운영체제 이슈
하드웨어 지원 + 운영체제 관리 = 간단한 가상 메모리 구현
베이스와 바운드 방식의 가상 메모리 구현을 위해서 운영체제가 반드시 개입되어야 하는 중요한 몇 시점이 존재
[1. 프로세스 생성 시 운영체제는 주소 공간이 저장될 메모리 공간을 찾아 조치를 취해야 함.]
운영체제는 물리 메모리를 슬롯의 배열로 보고 각 슬롯의 사용여부를 관리
🚀 새로운 프로세스 생성 시
➡️ 운영체제는 새로운 주소 공간 할당에 필요한 영역을 찾기 위해 자료 구조(흔히 빈 공간 리스트(free list)라고 불림)를 검색
➡️ 검색을 통해 선택된 공간을 사용 중이라고 표시
2️⃣의 재배치 예시에서 비어 있는 두 슬롯(16KB-32KB와 48KB-64KB)이 빈 공간 리스트로 구성됨.
[2. 프로세스 종료 시 프로세스가 사용하던 메모리 회수 및 다른 프로세스/운영체제가 사용할 수 있도록 조치]
프로세스가 종료하면, 운영체제는 종료한 프로세스의 메모리를 다시 빈 공간 리스트에 넣고 연관된 자료 구조를 모두 정리
[3. 운영체제는 문맥 교환이 일어날 때에도 몇 가지 추가 조치를 취해야 함.]
CPU마다 한 쌍의 베이스-바운드 레지스터만 존재 & 실행 중인 프로그램마다 다른 베이스-바운드 값
➡️ 운영체제는 프로세스 전환 시 베이스와 바운드 쌍을 저장하고 복원해야 함.
⛔ 실행 중인 프로세스 중단 결정 시
➡️ 운영체제는 메모리에 존재하는 프로세스 별 자료 구조(프로세스 구조체(process structure) 또는 프로세스 제어 블록(process control block, PCB)) 안에 베이스와 바운드 레지스터의 값을 저장
➡️ 운영체제가 메모리의 현 위치에서 다른 위치로 주소 공간을 비교적 쉽게 옮길 수 있음.(새 위치로 주소 공간 복사 후 프로세스 구조체의 베이스 레지스터를 갱신하여 새 위치를 가리키도록 함.)
🚀 프로세스 실행 시
➡️ 해당 프로세스에 맞는 값으로 CPU의 베이스와 바운드 값을 설정
[4. 운영체제는 예외 핸들러 또는 호출될 함수를 제공해야 함.]
운영체제는 부팅할 때 특권 명령어를 사용하여 이 핸들러를 설치
운영체제는 예외(프로세스가 바운드 밖의 메모리에 접근 등)가 발생할 때 조치를 취할 준비가 되어 있어야 함.
불법 행위를 한 프로세스 ➡️ 종료
🧺 주소 변환의 원리 정리 하기
주소 변환 사용 ➡️ 운영체제는 프로세스의 모든 메모리 접근을 제어 & 접근이 항상 주소 공간의 범위 내에서 이루어지도록 보장
주소 변환의 효율성의 열쇠 = 하드웨어 지원
➡️ 가상 주소에서 물리 주소로의 변환을 빠르게 수행
베이스와 바운드(base-and-bound) 또는 동적 재배치
➡️ 베이스 레지스터를 가상 주소에 더하고 생성된 주소가 바운드를 벗어나는지 검사하기 위한 간단한 하드웨어 회로만 추가하면 되므로 매우 효율적
base-and-bound 가상화의 보호 기능의 제공
➡️ 프로세스가 자신의 주소 공간 이외의 메모리는 참조할 수 없도록 함.
동적 재배치는 비효율적
➡️ 2️⃣의 재배치 예시에서 프로세스 스택과 힙이 아주 크지 않기 때문에, 둘 사이의 공간이 낭비됨.
➡️ 내부 단편화(internal fragmentation) 발생
물리 메모리의 이용률을 높이고 내부 단편화를 방지하기 위해 더 정교한 기법이 필요
➡️ 일반화된 base-and-bound 기법 = 세그멘테이션(segmentation)
📚 참고 문헌
Operating Systems: Three Easy Pieces ― 15: Mechanism: Address Translation