프로세스가 실행되는 방식은 크게 두가지로 볼 수 있다. 먼저 OS의 개입없이 HW에서 직접 실행되는 것이다. 이를 'Direct execution'이라 한다. 이러한 방식은 OS 작동에 따른 오버헤드가 없기에 효율성이 좋을 수 있다.
반면 OS가 개입을 하는 'Limited execution' 방식에서는 필요 시 OS가 개입을 하게 된다. 프로세스들이 잘 동작하도록 제어를 하기 위해서인데, 대표적으로 자원 할당, 보호 등이 있다.
이러한 두 방식의 결점을 보완하면서 합쳐진 메커니즘이 'Limited direct execution'이다. 프로세스(애플리케이션)가 '유저 모드'에서 동작하다 '시스템 콜'을 호출하며 '커널 모드'로 넘어가고, 이 후 커널이 프로세스가 요구하는 서비스를 제공하게 된다.
또한 타이머 인터럽트를 통해 프로세스를 선점하며 사용자가 동시에 프로그램이 동작하고 있다는 illusion을 심어준다. 타이머 장치로 부터 인터럽트가 발생하여 커널 모드로 진입하게 되고 커널은 '문맥교환(context-switching)'을 수행한다.
메모리 가상화를 위한 핵심 메커니즘은 바로 주소 변환이다. 각 프로세스는 자신들에게 할당된 주소공간을 활용할 수 있는데, 이 주소공간의 주소는 가상 주소이다. 따라서 실제 물리 메모리에 접근하기 위해선 가상 주소를 물리 주소로 바꾸어주는 메커니즘이 필요한데 이것이 주소 변환이라 할 수 있다.
이 주소 변환 작업은 직접적으로 물리 메모리를 사용하는 것에 비해 당연 오버헤드를 내포하고 있다. 따라서 오버헤드를 최소화하기 위해 HW 기반(지원)의 주소 변환의 도움을 받을 수 있다.
메모리 관리의 제어권은 OS가 가지고 있다. OS는 어떠한 프로세스든 자신의 주소 공간 이외의 영역에 침범하지 않는 것을 보장해야한다. '메모리 관리'는 OS의 핵심적인 역할이다.
순서상 '페이징(paging)' 기법에 대한 설명은 이후에 하기에, 아래와 같은 가정을 바탕으로 주소 변환을 설명한다.
주소 변환 메커니즘
주소 변환은 크게 3가지 메커니즘이 존재.
- 연속 할당(contiguous allocation)
- Base/limit register
(+): simple and offer protection
(-): 내부 단편화- 불연속 할당(non-contiguous allocation)
- segmentation 기법: 가변 사이즈
- paging 기법: 고정 사이즈
1) 하나의 프로세스 주소 공간은 물리 메모리 상에 연속적으로 놓여진다.
2) 주소 공간이 모두 물리 메모리에 놓여질 수 있다 가정한다.
3) 각 주소 공간은 같은 크기를 같는다.
아래와 같은 소스코드가 있다.
void func() {
int x = 3000;
x = x + 3;
}
이 상위레벨의 언어는 어셈블러를 거쳐 보다 하위의 언어인 어셈블리어로 변환된다.
128: movl 0x0(%ebx), %eax ; load 0+ebx into eax
132: addl $0x03, %eax ; add 3 to eax register
135: movl %eax, 0x0(%ebx) ; store eax back to mem
여기서 주소 공간 상의 주소에 대해 말하자면,
먼저 어셈블리어를 보면 명령어들의 주소가 128~135임을 확인할 수 있다.
주소 공간 레이아웃이 위 그림가 같을 때 0~2KB의 코드 영역에 해당 명령어들이 로드됨을 확인할 수 있다.
이어 정수 3000이 대입된 공간(변수 x)는 스택 영역에 존재함을 확인할 수 있다.
Execution viewpoint (fetch + execution)
주소 공간의 크기는 위 그림에서 확인하였듯 16KB로 가정하고, 물리 메모리의 실제 크기를 64KB로 가정한다.
하나의 프로세스의 주소 공간이 메모리에 로드되고, 프로세스가 종료되면 해당 공간이 free-space가 될 수 있다. 이는 다른 프로세스의 주소 공간이 이 공간에 다시 로드될 수 있다는 것이다. 이를 realocation, 재배치 가능한 물리 메모리의 특성이라 할 수 있다.
위 그림에서 볼 수 있듯 하나의 프로세스가 물리 메모리 상 32KB ~ 48KB에 로드되어 있다. 실제 주소 공간을 거쳐 물리 메모리로 접근하기 위해선 주소 공간의 시작 주소인 0KB(가상 주소)를 실제 물리 주소인 32KB에 맵핑시켜야 한다. 이러한 과정을 주소 변환이라 한다.
OS도 당연히 물리 메모리에 로드되어 있다. 항상 메모리에 상주하는 OS의 부분을 '커널'이라고 한다. 임의의 프로세스가 이 커널 공간에 접근하려 할 때 'protection fault' 에러가 발생하면서 죽는다.
주소 변환 시 HW 지원 메커니즘 중 대표적인 것
1) base/limit 레지스터
2) segmentation related 레지스터
3) paging related 레지스터
4) TLB
이러한 HW 자원들은 CPU의 일부분인 "MMU(Memory management unit)"에 존재한다.
주소 변환에 있어 HW의 지원을 받는다. 앞서 주소 공간의 시작 주소인 0KB를 물리 주소인 32KB로 변환해야 하는 예시를 보았다.
만약 주소 공간의 128 주소의 실제 물리 주소는 32KB+128(32768+128)가 되어야 한다.
일반적으로 base address + offset 방식으로 계산된다. 32KB가 base address가 되는 것이고, 128이 offest이 되는 것이다.
base address는 base 레지스터라는 HW 레지스터에 저장되어 있고, 이 레지스터에 저장된 값을 주소 변환에 바로 사용하여 빠르게 연산을 수행할 수 있다. 이러한 의미에서 HW의 지원을 받는다라고 한다.
이에 주소 변환은 HW의 지원을 포함하여 3가지 주요 요소들이 잘 협력하여 이루어진다. '컴파일러', 'OS' 그리고 'HW'이다.
큰 관점에서 주소 변환은 다음과 같이 이루어진다.
1) 프로그램 마치 0(가상 주소 시작점)에서부터 로드되는 것처럼 컴파일된다. 컴파일러가 가상 메모리를 정의하는 것과 같다.
2) OS는 프로그램의 로드를 담당한다. 구체적으로 OS가 제공하는 exec() 유형의 시스템 콜을 통해서 이루어지는 OS의 서비스이다(시스템 콜이 로더(loader)의 역할).
프로그램은 물리 메모리 상에 어느 곳이든 로드될 수 있으며(relocatable), base 레지스터와 limit 레지스터의 값이 적절히 셋팅시킨다.
3) 이 후 프로세스가 동작하면서 base 레지스터의 지원을 통해 주소 변환이 이루어진다.
Limit register(bound register): upper bound
upper bound 또는 주소 공간의 크기가 들어간다.
이 limit register를 통해 실제 물리 메모리 상의 범위가 정해지며 segmentation fault 여부 판단에 사용된다.
Base/Limit 레지스터 값
base/limit 레지스터 값은 문맥 교환시 변경된다. 새로 run 상태가 된 프로세스의 주소 변환에 필요한 값들로 교환된다.
MMU(Memory management unit)
HW support 요약
참조하면 좋은 글: https://icksw.tistory.com/68