Posted:      Updated:

운영체제 스터디를 하며 ‘운영체제 공룡책’ 교재를 정리한 글입니다.

메인 메모리

메모리는 주소가 할당된 바이트들로 구성된다.
CPU는 PC가 지시하는 대로 메모리에서 다음에 수행할 명령어를 가져온다.
명령어를 해독하고, 메모리에서 피연산자(operand)를 가져와 피연산자에 대해 명령어를 실행한 후 계산 결과를 메모리에 저장한다.
메모리는 주소에 지시한 대로 읽기, 쓰기만 할 뿐 주소가 어떻게 생성됐는지나 가리키는 내용이 무엇인지는 모른다.

개요

기본 하드웨어

CPU는 메모리에 직접 접근하지 못하므로, 메모리에서 레지스터로 옮긴 뒤 처리해야 한다.
각 CPU 코어에 내장된 레지스터들은 일반적으로 CPU 클록(clock)의 1사이클(cycle)내에 접근이 가능하다.
메인 메모리는 레지스터보다 느려서 여러 클록 틱(tick)이 필요하고, 이때 CPU는 필요한 데이터가 없어서 명령어를 수행하지 못하고 지연되는(stall) 현상이 발생한다.
이러한 문제를 해결하기 위해 CPU와 메인 메모리 사이에서 캐시를 사용한다.

시스템이 올바르게 동작하기 위해서는 사용자 프로그램으로부터 운영체제 영역을 보호하고, 사용자 프로그램 사이도 서로 보호해야 한다.
따라서 각각의 프로세스가 독립된 메모리 공간을 가져야 한다.
이를 기준(base) 레지스터와 상한(limit) 레지스터를 사용하여 보호한다.
기준 레지스터는 가장 작은 합법적인 물리 메모리 주소 값을 저장하고, 상한 레지스터는 주어진 영역의 크기를 저장한다.
기준 레지스터 값이 300040이고, 상한 레지스터 값이 120900이라면, 프로그램은 300040에서 420940까지 주소에 접근할 수 있다.
접근 범위를 벗어난다면 치명적인 오류로 간주하고 트랩(trap)을 발생시킨다.
기준, 상한 레지스터는 운영체제의 여러 특권 명령(special privileged instruction)에 의해서만 적재된다.

주소 할당

프로그램을 실행하는 것은 먼저 소스 프로그램을 작성하고, 컴파일러가 소스를 오브젝트 파일로 변환(컴파일 시간)한 다음, 링커가 여러 오브젝트 파일을 묶어 실행 파일을 생성(적재 시간)하면, 로더가 실행 파일을 메모리에 올려(실행 시간) CPU가 명령어와 데이터에 엑세스하는 과정으로 진행된다.

전통적으로 메모리 주소 공간에서 명령어와 데이터 바인딩은 바인딩이 이루어지는 시점에 따라 구분한다.

  • 컴파일 시간(compile time) 바인딩: 컴파일 시간에 프로세스의 메모리 위치를 알 수 있다면 절대 코드를 생성할 수 있다. 주소가 변경되면 다시 컴파일해야 한다.
  • 적재 시간(load time) 바인딩: 컴파일 시점에 프로세스 메모리 내 어디로 올라와야할 지 모른다면 이를 재배치 가능 코드로 만들어 메인 메모리에 실제 적재할 때 메모리 위치가 정해진다.
  • 실행 시간(execution time) 바인딩: 실행 중간에 메모리 내 세그먼트 간 이동과 같은 주소 변경이 발생할 수 있다.

논리 주소 vs 물리 주소

논리 주소(logical address)는 CPU가 생성하는 주소로 프로그램 입장에서의 주소이고, 물리 주소는(physical address)는 메모리가 취급하게 되는 주소로 실제 메모리의 위치이다.
런타임 바인딩 기법에서는 논리 주소와 물리 주소가 다르며, 논리 주소를 가상 주소라고 한다.
프로그램에 의해 생성된 논리 주소 집합을 논리 주소 공간(logical address space)이라 하고, 이 논리 주소와 일치하는 모든 물리 주소 집합으로 물리 주소 공간(physical address space)이라고 한다.

메모리 관리 장치(memory management unit, MMU)는 프로그램 실행 중에 가상 주소를 물리 주소로 바꿔주는 하드웨어 장치이다.
재배치 레지스터(relocation)는 논리 주소에 더해지는 고정값이다.
재배치 레지스터 값이 14000일 때, 프로세스가 346번지에 액세스하고자 하면 메인 메모리의 14346번지에 액세스하게 된다.

이를 통해 사용자 프로그램은 항상 0부터 시작하는 것처럼 자겅할 수 있으며, 실제 메모리 주소는 실행 시간에 결정되므로 동적 메모리 배치가 가능하다.
사용자 프로그램이 실제 물리 주소를 알 수 없어 보안 및 안전성이 향상된다.

동적 적재

메모리 공간을 효율적으로 사용하기 위해서는 동적 적재(dynamic loading)가 필요하다.
동적 적재 시 각 루틴이 실제 호출되기 전까지 메모리에 올라오지 않고 재배치 가능한 상태로 디스크에서 대기한다.
main 프로그램이 먼저 메모리에 올라와 실행되면, 이 루틴이 다른 루틴을 호출할 때 이미 적재된 루틴인지 확인하여 적재되어 있지 않다면 재배치 가능 연결 적재기(relocatable linking load-er)가 루틴을 메모리로 가져와 테이블에 기록한다.

동적 적재를 통해 루틴이 필요할 때만 적재하여 전체 프로그램의 크기가 크더라도 실제로 적재하는 부분을 이보다 작게 유지할 수 있다.

동적 연결 및 공유 라이브러리(DLL)

사용자 프로그램이 실행될 때 사용자 프로그램에 연결되는 시스템 라이브러리이다.
프로그램이 동적 라이브러리에 있는 루틴을 참조하면 로더가 DLL을 찾아 필요한 경우 메모리에 적재한다.
동적 연결을 통해 연결(linking)이 실행 시기까지 미루어지게 할 수 있고, 여러 프로세스 간 공유하도록 할 수 있다.
주로 표준 C언어 라이브러리와 같은 시스템 라이브러리에 사용되며, 공유 라이브러리로서 Windows 및 Linux 시스템에서 광범위하게 사용된다.

연속 메모리 할당

메모리는 일반적으로 운영체제를 위한 부분과 사용자 프로세스를 위한 부분으로 나뉜다.
대부분의 운영체제는 운영체제를 높은 메모리에 배치한다.
연속 메모리 할당에서 각 프로세스는 다음 프로세스가 적재된 영역과 인접한 메모리 영역에 적재된다.

메모리 보호

프로세스가 자신이 소유하지 않은 메모리에 접근할 수 없도록 해야 한다.
재배치 레지스터와 상한 레지스터를 통해 이를 해결할 수 있다.
CPU 스케줄러가 다음에 실행할 프로세스를 선택할 때, 디스패처(dispatcher)가 컨텍스트 스위칭 과정 중 하나로 재배치 레지스터와 상한 레지스터에 정확한 값을 적재한다.

메모리 할당

메모리를 할당하는 가장 간단한 방법은 프로세스를 메모리의 가변 크기 파티션에 할당하는 가변 파티션 기법이다.
운영체제는 사용 가능한 부분과 사용 중인 부분을 나타내는 테이블을 유지하는데, 이 때 사용 중인 부분들 사이에는 큰 사용 가능한 메모리 블록인 hole 이 생긴다.

이때 동적 메모리 할당 문제(dynamic storage allocation problem)가 발생한다.
일련의 가용 공간 리스트에서 크기 n-바이트 블록을 요구할 때 어떻게 만족시켜줄 것이냐를 결정하는 문제이다.

  • 최초 적합(first-fit): 집합의 시작, 지난번 검색이 끝난 곳 등 검색의 시작부터 첫 번째 사용 가능한 가용 공간을 할당한다.
  • 최적 적합(best-fit): 사용 가능 공간 중 가장 작은 것을 선택한다. 리스트가 크기 순이 아니라면 모든 리스트를 검색해야 한다.
  • 최악 적합(worst-fit): 가장 큰 가용 공간을 선택한다.

최초 적합과 최적 적합 모두 시간, 메모리 이용 효율이 최악 적합보다 좋다.
그리고 일반적으로 최초 적합이 최적 적합보다 빠르다.

단편화(Fragmentation)

최초 적합과 최적 적합 전략은 외부 단편화(external fragmentation) 문제가 발생한다.
프로세스를 메모리에 적재하고 제거하는 일을 반복하다 보면 메모리들이 많은 수의 매우 작은 조각들로 단편화될 수 있다.
프로세스 사이마다 못 쓰는 가용 공간을 많이 가지게 되면 이 공간을 합쳐 하나의 큰 가용 공간으로 만들 수 있다.

이때 압축(compction)이라는 방법을 사용하여 메모리 모든 내용을 한군데로 몰고 가용 공간을 다른 한군데로 몰아서 큰 블록을 만들 수 있다.
그러나 재배치가 어셈블 또는 적재 시 정적으로 행해지면 압축은 실행할 수 없고, 프로세스의 재배치가 실행 시간에 동적으로 이루어질 때만 가능하다.
압축이 가능하더라도 프로세스를 한 쪽에 몰고 가용 공간을 다른 한쪽으로 모는 과정은 비용이 매우 많이 든다.

보통 메모리는 아주 작은 사이즈의 여러 블록으로 나누어 프로세스가 필요한 만큼의 개수를 할당한다.
그렇다면 프로세스의 크기가 블록과 정확히 일치하지 않을 때, 프로세스에 할당한 블록 내부에 남는 공간이 생길 수 있다.
이렇게 남는 공간을 내부 단편화(internal fragmentation)이라고 한다.

페이징

물리 메모리는 프레임(frame), 논리 메모리는 페이지(page)라 불리는 일정 크기 블록으로 나누어진다.
프로세스가 실행될 때 프로세스의 페이지는 메인 메모리의 프레임으로 매핑되어 적재된다.

CPU에서 나오는 모든 논리 주소는 페이지 번호(p)와 페이지 오프셋(d: offset)으로 구성된다.
페이지 번호는 프로세스 페이지 테이블(page table)을 액세스할 때 사용된다.
페이지 테이블은 물리 메모리의 각 프레임의 시작 주소를 저장한다.
오프셋은 참조되는 프레임 안에서의 위치이다.
따라서 프레임의 시작 주소와 페이지 오프셋이 결합해 물리 메모리 주소가 된다.

페이지 크기는 2의 거듭제곱으로, 페이지당 4KB와 1GB사이이다.
논리 주소 공간 크기가 $2^m$이고 페이지 크기가 $2^n$바이트라면 논리 주소의 상위 $m-n$ 비트는 페이지 번호를 지정하고, 하위 $n$ 비트는 페이지 오프셋을 지정한다.

페이징을 사용하면 모든 프레임이 프로세스에 할당될 수 있어 외부 단편화가 발생하지 않지만, 할당은 항상 프레임의 정수배로 이루어지므로 내부 단편화가 발생한다.
페이지 크기가 작아지면 페이지 테이블의 크기가 커지므로 테이블이 차지하는 공간이 낭비될 수 있어 디스크 입장에서는 페이지 크기가 클 수록 효율적이다.

프로세스의 실행을 위해서는 크기가 페이지 몇 개 분에 해당하는지 조사한다.
n개의 페이지를 요구하면 메모리에 n개의 프레임이 필요하며, 사용 가능하면 프로세스에 할당한다.
프로세스의 각 페이지는 할당된 프레임 중 하나에 적재되어 프레임 번호가 페이지 테이블에 기록된다.
프로그래머는 메모리를 하나의 연속적인 공간이라 인식하지만, 실제로 프로그램은 여러 곳에 프레임 단위로 분산되어 있다.

운영체제는 프레임 테이블을 유지하여 모든 프로세스의 주소를 실제 메모리의 물리 주소로 변환해주어야 한다.
프레임 테이블(frame table)은 각 프레임당 하나의 항목을 가지고 있으며, 프레임이 비어 있는지, 할당되어 있는지, 할당되었다면 어느 프로세스의 어느 페이지에 할당되었는지 나타낸다.

프로세스마다 별도의 페이지 테이블을 가지며, CPU 스케줄러가 프로세스를 바꿀 때 페이지 테이블 포인터를 포함한 레지스터 값을 다시 적재해야 한다.
대부분의 현대 CPU는 큰 사이즈의 페이지 테이블을 사용하며 페이지 테이블을 메인 메모리에 저장하고 페이지 테이블 기준 레지스터(page-table base register, PTBR)로 페이지 테이블을 가리키게 한다.

Translation Lookaside Buffer (TLB)

페이지 테이블을 메인 메모리에 저장하면 1. 페이지 번호를 기준으로 PTBR 오프셋 값으로 페이지 테이블 항목 찾기와 2. 이렇게 얻은 프레임 번호와 페이지 오프셋으로 실제 주소 생성하기 총 2번의 메모리 액세스가 필요하다.
따라서 메모리 접근 시간이 2배로 느려진다.

이를 해결하기 위해 매우 빠른 연관 메모리(associative memory)인 TLB를 사용한다.
페이지 테이블 접근을 빠르게 하기 위해 사용하는 특수한 소형 하드웨어 캐시이다.

TLB는 페이지 번호(key)와 프레임 번호(값)로 구성된다.
CPU가 논리 주소를 보낼 때 TLB를 확인하고 있다면(TLB hit) 물리 주소를 바로 제공하고 없다면(TLB miss) 페이지 테이블을 메모리에서 확인하고 TLB에 저장한다.

TLB가 가득 차면 기존 항목 중에서 교체될 항목을 선택해야 한다.
교체 정책은 LRU, 라운드 로빈, 무작위 등 다양한 정책을 사용할 수 있다.
커널 코드와 같이 일부 중요한 항목들은 TLB에 고정시킨다.

일부 TLB는 각 항목에 ASID(address-space identifiers)를 저장하기도 한다.
ASID는 TLB 안의 항목이 어느 프로세스의 것인지 구분하기 위한 ID이다.
ASID가 있으면 프로세스가 바뀌어도 TLB를 비우지 않아도 되고, ASID가 없으면 프로세스가 바뀔 때마다 TLB를 전부 초기화(Flush)해야 한다.

TLB에서 페이지 번호를 찾을 확률을 적중률(hit ratio)이라고 한다.
적중률이 80%라면 CPU가 요청한 주소 중 80%는 TLB에서 바로 찾을 수 있다는 의미이다.

메인 메모리 접근 시간은 10ns, TLB에서 못 찾으면 페이지 테이블을 보고 다시 메모리에 접근해야 해서 총 20ns가 걸린다고 가정할 때, 실질 메모리 접근 시간(Effective Memory Access Time)을 계산할 수 있다.

0.80 × 10 + 0.20 × 20 = 12ns

더 현실적인 적중률인 99%일 경우의 계산이다.

0.99 × 10 + 0.01 × 20 = 10.1ns

현대 CPU는 한 단계의 TLB만 쓰지 않고 여러 단계로 TLB를 구성한다.

예를 들어 Intel Core i7 CPU는 128개의 항목을 가진 L1 명령어 TLB와 64개의 항목을 가진 L1 데이터 TLB를 가지고 있다.
만약 L1에서 못 찾으면 512개의 항목을 가진 L2 TLB를 검색하며, 이때 6 CPU 사이클이 걸린다.
L2에서도 못 찾으면 메모리에서 페이지 테이블을 확인해야 하고, 이 연산은 수백 CPU 사이클이 소요된다.

보호

메모리 보호는 각 페이지에 붙어 있는 보호 비트(protection bits)로 구현되며, 보호 비트는 페이지 테이블에 포함된다.
각 비트는 읽고 쓰기 또는 읽기 전용(read-only)인지를 정의할 수 있다.
읽기 전용 페이지에 쓰기를 시도하면 운영체제가 하드웨어로 메모리 보호 위반(memory protection violation) 트랩을 발생시킨다.

페이지 테이블의 각 엔트리에는 유효(valid) 또는 무효(invalid) 상태를 나타내는 비트가 있다.
유효 비트가 설정된 페이지는 프로세스의 합법적인 페이지임을 나타내고, 무효 비트가 설정된 페이지는 프로세스의 논리 주소 공간에 속하지 않음을 나타낸다.
운영체제는 이 비트를 이용해 해당 페이지 접근을 허용하거나 거부한다.

대부분의 프로세스는 자신의 모든 주소 범위를 다 쓰지 않고 많은 프로세스들이 일정한 시간 동안 일부만 집중적으로 사용한다.
이런 경우 모든 페이지에 대해 테이블 항목을 배정하는 것은 낭비이다.
이를 위해 페이지 테이블 길이 레지스터(page table length register, PTLR)를 제공한다.
프로세스가 제시한 주소가 유효 범위 내에 있는지 확인하기 위해 모든 논리 주소 값을 PTLR 값과 비교하여 오류가 나면 트랩을 발생시킨다.

공유 페이지

페이징의 장점은 여러 프로세스가 공통의 코드를 공유할 수 있다는 점이다.
만약 각 프로세스가 libc 사본을 개별적으로 주소 공간에 적재한다면, 동일한 코드가 불필요하게 여러 번 메모리에 올라가게 된다.
예를 들어, 시스템에 40개의 사용자 프로세스가 있고 libc 라이브러리 크기가 2MB라면, 총 80MB의 메모리가 필요하다.

재진입 코드(reentrant code)는 실행 중에 코드가 변경되지 않는 코드를 말한다.
재진입 코드는 여러 프로세스가 동시에 실행해도 상태가 변하지 않기 때문에, 한 사본만 메모리에 올려두고 여러 프로세스가 이를 공유할 수 있다.

각 프로세스는 자신이 실행하는 동안 필요한 데이터는 개별적인 레지스터나 데이터 영역에 저장한다.
libc 사본을 여러 번 적재할 필요 없이 한 사본만 적재하고, 모든 프로세스의 페이지 테이블을 동일한 물리적 사본으로 매핑하면 된다.
이렇게 하면 40개 프로세스를 실행할 때 필요한 공간은 80MB가 아니라 2MB로 줄어든다.

공유 코드는 반드시 재진입 코드로 작성되어야 하고, 읽기 전용을 유지하는 것이 중요하다.

페이지 테이블의 구조

계층적 페이징

페이지 테이블을 여러 개의 작은 조각으로 나눌 수 있다.
2단계 페이징 기법(two-level paging scheme)은 페이지 테이블 자체를 다시 페이징되게 한다.
페이지 테이블도 페이지로 나누어지므로, 페이지 번호는 10비트짜리 페이지 번호($p_1$)와 10비트짜리 페이지 오프셋($p_2$)으로 나뉘게 된다.
$p_1$은 바깥 페이지 테이블의 인덱스이고, $p_2$는 안쪽 페이지 테이블 내의 오프셋이다.
여기서 주소 변환이 바깥 페이지 테이블에서 시작해 안쪽으로 ㄷ르어오므로 forward-mapped 페이지 테이블이라고 부른다.

해시 페이지 테이블

주소 공간이 32비트보다 커지면 가상 주소를 해시로 사용하는 해시 페이지 테이블을 많이 쓴다.
해시 페이지 테이블의 각 항목은 연결 리스트를 가지고, 원소들은 가상 페이지 번호, 해당하는 프레임 번호, 연결 리스트 상의 다음 원소 포인터를 가진다.

가상 주소 공간에서 페이지 번호가 오면 이걸 해싱하고, 해시 페이지 테이블에서 연결 리스트를 따라가며 원소와 가상 페이지 번호를 비교한다.
일치하면 대응하는 페이지 프레임 번호를 가져와 물리 주소를 얻는다.
일치하지 않으면 다음 원소로 같은 일을 반복한다.

64비트 시스템에서는 해시 테이블과 비슷한 클러스터 페이지 테이블을 사용한다.
해시 페이지 테이블은 각 항목이 한 개의 페이지만 가리키지만 클러스터 페이지 테이블은 여러 페이지를 가리킨다.
따라서 한 개의 페이지 테이블 항목이 여러 페이지 프레임에 대한 변환 정보를 가져 성긴(sparse) 주소 공간에 유용하다.

역 페이지 테이블

보통 프로세스는 각 페이지 테이블을 하나씩 가지고 페이지 테이블은 프로세스가 사용하는 페이지마다 하나의 항목을 가진다.
페이지 테이블 항목의 개수가 수백만 개가 되면, 물리 메모리 사용 추적을 위해 많은 물리 메모리를 소비하게 된다.

역 페이지 테이블(inverted page table)은 메모리 프레임마다 한 항목씩 할당하고, 각 항목은 프레임에 올라와 있는 페이지 주소와 페이지를 소유하고 있는 프로세스의 ID를 표시한다.
시스템에는 하나의 페이지 테이블만 존재하고, 테이블 내 각 항목이 메모리 한 프레임씩을 가리키게 된다.

논리 페이지마다 항목을 가지지 않고 물리 프레임에 대응되는 항목만 테이블에 저장하므로 메모리 훨씬 적게 사용한다.
하지만 물리 주소에 따라 정렬되어 있고 가상 주소를 기준으로 탐색해야 하므로 테이블 전체를 탐색해야할 수 있다.
이를 해결하기 위해 TLB, 해시 테이블 등을 활용할 수 있다.

역 페이지 테이블은 메모리 공유 기능을 구현하기 어렵고, 공유 메모리를 쓰려면 다른 프로세스가 접근할 때마다 가상 주소 매핑을 바꿔야 한다.

Oracle SPARC Solaris

SPARC CPU에서 실행되는 Solaris는 메모리를 소진하지 않으면서 가상 메모리 문제를 해결하기 위해 해시 테이블을 활용한다.
두 개의 해시 테이블을 사용하며, 각 테이블은 가상 메모리 주소로부터 물리 메모리 주소로 변환한다.
각 해시 테이블 항목은 매핑된 가상 메모리의 연속 영역을 나타낸다.

변환 때마다 해시 테이블을 검색하면 주소 변환에 시간이 오래 걸리므로 CPU는 변환 테이블 항목(translation table entires, TTE)을 저장하고 있는 TLB를 구현한다.
TTE를 위한 캐시가 변환 저장 버퍼(translation storage buffer, TSB)에 있으며 최근에 접근된 페이지 항목을 저장한다.
가상 주소 참조가 발생하면 TLB를 검색하고, 주소가 발견되지 않으면 TSB를 탐색해 TTE를 찾는다.
TSB에서 발견되면 CPU는 TSB 항목을 TLB로 복사해 메모리 변환이 끝난다.
TSB에서 발견되지 않으면 커널이 해시 테이블을 탐색하도록 인터럽트를 발생시킨다.
인터럽트가 발생하면 커널은 해시 테이블로부터 TTE를 생성해 TSB에 저장한다.

스와핑

스와핑은 프로세스 전체 혹은 일부분을 실행 중에 임시로 백업 저장장치(backing store)로 내보냈다가 다시 메모리로 되돌리는 것이다.
표준 스와핑에는 메인 메모리와 백업 저장장치 간에 전체 프로세스를 이동한다.
백업 저장장치는 일반적으로 빠른 보조저장장치로, 프로세스 크기와 상관 없이 수용할 수 있도록 커야 하고 메모리 이미지에 직접 액세스 할 수 있어야 한다.

프로세스와 관련된 자료구조도 백업 저장장치로 스왑할 때 백업 저장장치에 기록한다.
운영체제는 스왑아웃된 프로세스에 대한 메타데이터를 유지해 메모리로 스왑인 할때 복원시킨다.

표준 스와핑을 통해 실제 물리 메모리보다 더 많은 프로세스를 수용하도록 할 수 있다.
대부분의 시간을 유휴 상태로 보내는 비활성 프로세스에 할당된 메모리를 활성 메모리에 할당할 수 있다.
스왑된 비활성 프로세스가 활성화되면 다시 스왑인 시킨다.

페이징에서의 스와핑

Linux, Windows와 같은 대부분의 시스템은 프로세스 페이지를 스왑할 수 있는 변형 스와핑을 사용한다.

  • 페이지-아웃 연산: 페이지를 메모리에서 백업 저장장치로 이동
  • 페이지-인 연산: 페이지를 백업 저장장치에서 메모리로 이동

모바일 시스템에서의 스와핑

모바일 시스템은 대부분 스와핑을 지원하지 않는다.
모바일 시스템에서는 하드디스크보다 플래시 메모리를 사용하여 저장 공간이 줄어들고, 플래시 메모리에는 허용되는 쓰기 횟수가 정해져 있으며, 처리량이 저조하기 때문이다.

Apple의 iOS에서는 가용 메모리가 정해진 임계값보다 떨어지면 응용에 할당된 메모리를 자발적으로 반환하도록 요청한다.
읽기 전용 데이터는 필요에 따라 메인 메모리에서 제거하고 나중에 플래시 메모리로부터 적재한다.

Android도 마찬가지로 가용 메모리가 부족하면 프로세스를 종료시킨다.
다만 종료 전에 응용의 상태를 플래시 메모리에 저장해 나중에 빨리 재시작할 수 있게 한다.

Intel 32비트, 64비트

IA-32 구조

세그먼테이션과 페이징 두 부분으로 나누어지며, MMU와 같은 역할을 한다.

  1. CPU가 논리 주소를 생성해 세그먼테이션 장치에 전달
  2. 세그먼테이션 장치가 선형 주소를 생성하고 페이징 장치에 전달
  3. 페이징 장치가 메인 메모리의 물리 주소로 변환

IA-32 세그먼테이션

하나의 세그먼트는 최대 4GB의 크기를 가지고, 한 프로세스당 16KB개의 세그먼트를 가질 수 있다.

각 프로세스 주소 공간을 두 개의 파티션(partition)으로 나눈다.
첫 번째 파티션은 프로세스가 독점적으로 사용하는 8KB 세그먼트이고, 지역 디스크립터 테이블(local descriptor table, LDT)에 정보를 저장한다.
두 번째 파티션은 모든 프로세스 사이에서 공유 가능한 8KB 세그먼트이고, 전역 디스크립터 테이블(global descriptor table, GDT)에 정보를 저장한다.

논리 주소는 (selector, offset) 쌍으로 구성된다.
셀렉터는 13비트의 세그먼트 번호 $s$, 1비트의 GDT LDT 구분 $g$, 2비트의 보호(protection)와 관련된 정보 $p$ 총 16비트로 구성된다.
오프셋은 세그먼트 내에서 찾과 하는 바이트의 위치로 32비트 수이다.

CPU는 6개의 세그먼트용 레지스터를 가지고 있어서 한 번에 6개의 세그먼트를 가리킬 수 있다.
그리고 LDT, GDT 참조 시 캐시(microprogram 레지스터)를 활용한다.

IA-32에서 선형 주소의 길이는 32비트이다.

  1. 세그먼트 레지스터가 LDT, GDT 내의 적절한 항목을 가리킨다
  2. 찾고자 하는 세그먼트에 대한 기준, 상한 정보를 가져와서 실제 선형 주소(linear address)를 만든다
  3. 주소의 타당성 검사 후 1) 타당하면 오프셋 값이 기준 값에 더해져 32비트 선형 주소 생성 2) 한계치를 넘으면 메모리 오류가 발생하고 운영체제에 트랩 발생

IA-32 페이징

페이지는 4KB 또는 4MB이고, 4KB 크기의 페이지일 때 2단계 페이징 기법을 사용한다.

32bits의 선형 주소는 페이지 디렉터리(page directory)를 나타내는 상위 10비트 $p_1$, 페이지 테이블을 나타내는 다음의 10비트 $p_2$, 페이지 테이블이 가리키는 오프셋인 하위 12비트 $d$로 구성된다.
페이지 디렉터리는 최상위 페이지 테이블의 엔트리로, CR3 레지스터가 현재 페이지 디렉터리를 가리킨다.

  1. CR3에서 페이지 디렉터리 확인
  2. $p_1$로 페이지 디렉터리를 인덱싱하고 페이지 테이블 확인
  3. $p_2$로 페이지 테이블 인덱싱해서 실제 페이지 접근
  4. $d$를 더해 최종 물리 주소 산출

IA-32 페이지 테이블은 디스크로 스왑될 수 있으며, invalid 비트를 통해 페이지 테이블이 메모리 또는 디스크에 있는지 확인한다.
페이지 테이블이 디스크에 있다면 필요할 때 메모리로 테이블을 가져올 수 있다.

4GB보다 큰 물리 주소 공간에 접근할 수 있도록 하기 위해 페이지 주소 확장(page address extension, PAE)를 채택했다.
PAE를 통해 2단계 기법에서 3단계 기법으로 바뀌고, 최상위 2비트가 페이지 디렉터리 포인터 테이블을 가리키게 된다.

x86-64

64비트 주소 공간을 지원하므로 $2^{64}$ 바이트의 메모리에 접근할 수 있지만, 실제로는 훨씬 적은 비트만 주소 표현에 사용한다.
4KB, 2MB, 1GB 페이지를 지원하며 4단계 페이지 테이블을 사용하는 48비트 가상 주소 공간을 지원한다.
PAE를 사용할 수 있어 48비트더라도 물리 주소 공간은 52비트 물리 주소 공간을 지원한다.

ARMv8

스마트폰과 태블릿 등의 이동 장치 컴퓨터는 ARM 처리기를 사용한다.

ARMv8은 4KB, 16KB, 64KB 세 가지 변환 단위(translation granules)가 있으며, 각 변환 단위마다 다른 페이지 크기와 region을 제공한다.
4KB, 16KB 단위의 경우 최대 4단계 페이징이 가능하고, 64KB 단위의 경우 최대 3단계 페이징이 가능하다.
64비트 아키텍처이지만 48비트만 사용되며, TTBR 레지스터(변환 테이블 기본 레지스터, translation table base register)가 현재 스레드의 레벨 0 테이블을 가리킨다.
4KB 변환 단위에서 네 가지 레벨을 모두 사용하는 경우 63-48비트는 미사용, 47-39비트는 레벨 0 인덱스, 38-30비트는 레벨 1 인덱스, 29-21비트는 레벨 2 인덱스, 20-12비트는 레벨 3 인덱스, 11-0비트는 오프셋이다.

또한 2단계 TLB 구조를 지원한다.

  1. 마이크로 TLB: 데이터, 명령어 용 각각의 TLB가 존재, ASID 지원, 주소 변환 시작
  2. 메인 TLB: 마이크로TLB에서 미스 발생 시 확인

두 TLB에서 모두 미스가 발생하면 하드웨어가 페이지 테이블을 순회한다.

댓글남기기