앞서 메모리 가상화가 필요한 이유와 목표에 대해 살펴보았다. 그렇다면 어떻게 효율적이고 유연하게 메모리를 가상화할 수 있을까?
우리가 다룰 기법은 하드웨어 기반 주소 변환(hardware-based address translation), 짧게 말해서 주소 변환(address translation)이다. 하드웨어는 주소 변환을 통해 Load, Store, Fetch 등의 가상 주소를 실제 존재하는 물리 주소로 변환한다. 하지만 하드웨어만으로 메모리 가상화를 구현할 수는 없다. 운영체제가 관여한다.
15.1 가정
몇 가지 비현실적인 가정을 설정하고 시작해보자. 사용자 주소 공간은 물리 메모리에 연속적으로 배치되어야 하며, 주소 공간의 크기는 물리 메모리보다 작고, 각 주소 공간의 크기는 같다. 이렇게 가정하고 시작한 후 점차 가정들을 완화하며 실제적인 메모리의 가상화를 이끌어낼 것이다.
15.2 사례
위와 같은 코드를 검토한다고 해보자. 컴파일러는 이 코드를 어셈블리 코드로 변환할 것이다.
ebx에 저장되어 있는 x의 주소를 movl 명령어를 통해 exa에 넣는다. eax 레지스터에 3을 더하고 exa의 값을 같은 위치의 메모리에 저장한다. 그러면 프로세스 관점에서는 아래와 같은 메모리 접근이 일어난다.
아래와 같은 주소 공간을 갖는다고 가정했을 때, 프로그램 관점에서 주소 공간은 0부터 시작하여 최대 16KB까지이므로 모든 메모리 참조는 이 범위 안에 있어야 한다. 아래 그림이 프로세스가 착각하고 있는 주소 공간이다.
실제 물리 메모리 배치는 아래와 같다. 프로세스가 실제 메모리라고 착각했던 그 메모리에서는 0KB부터 프로그램 코드가 들어갔지만, 실제로는 32KB부터 들어갔다. 프로세스가 모르게 재배치(relocation)한 것이다.
근데 32KB부터 넣어주면 된다는 건 어떻게 알까? 코드 상에서 주소의 기준은 0인데, loading해보니 재배치되어 32KB부터 메모리를 사용한다. 무슨 기준으로 재배치를 하는걸까?
15.3 동적(하드웨어 기반) 재배치
먼저 채택된 방법은 동적 재배치이다. 가상 메모리의 주소 공간을 한 덩어리로 생각하고, 이 주소 공간에 대한 기준 값과 한계 값을 이용해 재배치를 하는 것이다. 기준값과 한계값을 저장하기 위해 CPU에서는 2개의 하드웨어 레지스터를 사용한다. 기준값은 베이스(base) 레지스터에, 한계값은 바운드(bound) 레지스터에 저장한다. 베이스와 바운드 레지스터는 CPU 칩 상에 존재하는 하드웨어 구조이다. (CPU 당 1쌍) 주소 변환에 도움을 주는 프로세서의 일부를 메모리 관리 장치 MMU(Memory Management Unit)라고 부르기도 한다.이 베이스와 바운드 쌍은 우리가 원하는 위치에 주소 공간을 배치할 수 있게 한다.
프로그램은 주소 0에서 탑재되는 것처럼 작성하고 컴파일된다. 프로그램 시작 시, 운영체제가 프로그램이 탑재될 물리 메모리 위치를 결정하는데 그 방법은 아래와 같다. 앞에서 살펴본 예에서는 가상 주소 0과 base 32KB가 더해져 32KB라는 물리 주소로 정해진 것이다.
바운드 레지스터는 보호를 위해 존재한다. 프로세서는 먼저 메모리 참조가 적절한지를 확인하기 위해 가상 주소가 바운드 안에 있는지를 확인한다. 프로세스가 바운드보다 큰 가상 주소 또는 음수인 가상 주소를 참조하면, 다시 말해 프로세스가 생성한 주소가 프로세스의 범위 내에 없다면 CPU는 예외를 발생시키고 프로세스는 종료될 것이다.
예를 들어 살펴보자. 이 예에서는 주소 공간의 크기가 4KB인 프로세스가 물리 주소 16KB에 탑재되어 있다고 가정한다. 그렇다면 베이스 레지스터의 값은 16KB, 바운드 레지스터의 값은 4KB가 될 것이다.
- 가상 주소가 0일 때는 가상 주소 0에 베이스 주소 16KB를 더해 물리 주소 16KB가 되었다.
- 가상 주소가 1일 때는 가상 주소 1KB에 베이스 주소 16KB를 더해 물리 주소 17KB가 되었다.
- 가상 주소가 3000일 때는 가상 주소 3000에 베이스 주소 16KB(16KB * 1024 = 16,384)를 더해 19,384가 되었다.
- 가상 주소가 4400일 때는 오류가 발생했다. 바운드 레지스터가 4KB 즉 4096인데 이것보다 더 큰 값인 4400을 변환하려고 했기 때문이다.
15.4 하드웨어 지원: 요약
동적 재배치를 위해 하드웨어가 지원해주어야 하는 것을 정리해보자.
- CPU는 특권 모드(=커널 모드)와 유저 모드를 지원한다. 그런데 특권 모드에서만 컴퓨터 전체에 대한 접근 권한을 가질 수 있다. 유저 모드에서 실행되는 응용 프로그램이 특권 연산을 실행하는 것을 방지하기 위해서이다. CPU가 현재 어떤 모드인지는 프로세서 상태 워드(processor status word, PSW)레지스터의 한 비트를 통해 나타낸다.
- 하드웨어는 베이스와 바운드 레지스터를 자체적으로 제공한다. CPU는 MMU의 일부인 추가 레지스터 쌍을 가진다. 하드웨어는 프로그램이 생성한 가상 주소에 베이스 값을 더해 주소를 변환하고, 바운드 값을 통해 주소가 유효한지 검사한다.
- 다른 프로세스를 실행시킬 때는 베이스와 바운드 값이 바뀌어야 할 것이다. 따라서 하드웨어는 베이스와 바운드 레지스터 값을 변경하는 명령어를 제공해야 한다. 이 명령어는 특권 명령어로 커널 모드(특권 모드)에서만 실행 가능하다.
- 운영체제가 예외 처리 코드를 하드웨어에게 알려줄 수 있어야 하기 때문에 예외 핸들러 등록을 위한 특권 명령어 제공
- CPU는 유저 프로그램이 불법적인 메모리 접근을 시도하려고 할 때, 예외를 발생시킬 수 있어야 한다.
15.5 운영체제 이슈
동적 재배치를 위해 운영체제가 해야할 것은 아래와 같다.
- 운영체제는 메모리 관리를 해야한다. 프로세스가 하드디스크에 있다가 메모리에 탑재될 때, 메모리의 어느 공간에 탑재할지 배치할 수 있어야 한다. 또 프로세스가 종료되면 다른 프로세스가 사용할 수 있도록 메모리를 회수해주어야 한다. free list라고 불리는 자료 구조를 검색하여 이 역할들을 수행한다.
- 문맥 교환을 할 때 변환 정보에 해당하는 베이스와 바운드 레지스터 쌍을 저장하고 복원해야 한다. 운영체제가 실행 중인 프로세스를 중단시키기로 결정하면 운영체제는 메모리에 존재하는 프로세스 별 자료구조 안에 베이스와 바운드 레지스터의 값을 저장한다. 이 자료구조는 PCB(process control block)이라고 불린다.
- 예외 핸들러 또는 호출될 함수를 제공하여 예외가 발생했을 때 처리해줄 수 있어야 한다.
이렇게 하드웨어 지원과 운영체제 관리가 결합되면 간단한 가상 메모리를 구현할 수 있다.
15.6 요약
타임라인으로 하드웨어와 OS의 상호작용을 정리해보자.
- 부팅할 때 컴퓨터를 사용 가능한 상태로 만들기 위해서 운영체제는 트랩 테이블을 초기화한다.
- 하드웨어는 시스템 콜 핸들러, 타이머 핸들러, 불법적인 메모리 접근 핸들러, 불법적인 명령어 핸들러에 대한 주소를 기억한다.
- 운영체제가 인터럽트 타이머를 구동시키면 하드웨어에서 타이머를 시작한다.
- 운영체제에서 프로세스 테이블과 빈 공간 리스트(free list)를 초기화한다.
- 그러면 이제 프로세스 A를 시작하기 위해 운영체제에서 프로세스 테이블의 항목 할당, 프로세스 메모리 할당, 베이스/바운드 레지스터 설정을 한 후 return-from-trap을 한다.
- 하드웨어에서는 A의 레지스터 복원, 유저 모드로 이동, A의 PC로 분기하는 일이 한 번에 수행된다.
- 프로그램에서 프로세스 A가 실행된다. 명령어를 Fetch하고자 한다.
- 하드웨어에서는 명령어의 가상 주소를 물리 주소로 변환하여 Fetch를 실행한다.
- 프로그램에서 명령어를 실행한다.
- 만약 명령어가 load나 store라면 그 주소가 합법적인지 확인한다. 가상 주소를 변환하고 load나 store 명령을 실행한다.
- 계속하여 프로세스 A가 실행된다.
- 하드웨어에서 타이머 인터럽트가 발생한다. 커널 모드로 전환하고 핸들러 주소로 jump한다.
- 타이머 핸들러에서 스케쥴러에 의해 A의 실행을 중단하고 B를 실행한다. 이 때 프로세스를 전환할 때 문맥 교환을 하는데, A와 B 각각의 베이스/바운드 값도 포함하여 저장 및 복원을 한다. 그리고 다시 return-from-trap!
- 하드웨어에서 B의 레지스터를 복원하고 유저 모드로 이동한다. B의 PC로 분기한다.
- 프로세스 B가 실행되는데 여기서 불법적인 Load 명령어를 실행했다.
- MMU는 바운드 값 밖에 있는 불법적인 실행임을 확인하고 exception을 건다. 커널 모드로 이동, 트랩 핸들러로 분기한다.
- 운영체제에서 트랩 처리를 한다. 프로세스 B를 종료시키고, 메모리 회수, 프로세스 테이블에서 B의 항목을 삭제한다.
'운영체제' 카테고리의 다른 글
[운영체제] 07. 스케줄러 (0) | 2024.04.23 |
---|---|
[운영체제] 18. Paging (1) | 2024.04.07 |
[운영체제] 06. 제한적 직접 실행(LDE, Limited Direct Execution) (1) | 2024.03.31 |
[운영체제] 16. segmentation 세그멘테이션 (1) | 2024.03.24 |
[운영체제] 13. Address Space 주소 공간 (0) | 2024.03.23 |