반응형

페이지 테이블을 다루는데 필요한 자료구조와 매크로를 정의하고, 이를 사용해서 페이지 테이블을 생성하며

 

CR0 컨트롤 레지스터와 CR3 컨트롤 레지스터, CR4 컨트롤 레지스터를 제어하여 페이징 기능을 활성화합니다.

 


 

→ 페이지 엔트리를 위한 자료구조 정의와 매크로 정의

 

페이지 테이블은 각 엔트리의 집합이므로 페이지 엔트리를 나타내는 자료구조를 먼저 정의해야 하는데, 세 종류의 페이지 엔트리는 내부 필드가 거의 유사하므로 한 가지 형태만 정의하여 공용으로 사용합니다.

 

아래는 8byte 크기의 페이지 엔트리 자료구조를 정의한 것입니다.

 

페이지 엔트리의 자료구조를 정의하는 코드

 

이제 각 속성 필드에 대한 값을 정의하는데 8byte 크기의 자료구조가 4byte로 나누어졌으므로, 이를 고려하여 각 속성에 대한 플래그를 정의합니다.

 

페이지 엔트리의 속성 필드를 정의한 매크로

 


→ 페이지 엔트리 생성과 페이지 테이블 생성

 

이제 자료구조와 매크로를 사용하여 페이지 엔트리와 페이지 테이블을 생성하는 예를 살펴보기 위해 페이지 테이블의 세 가지 종류 중 가장 간단한 PML4 테이블을 예로 듭니다.

 

PML4 테이블0x100000(1MB)의 주소에서 시작하여 4KB 영역을 차지합니다.

 

PML4 테이블의 첫 번째 엔트리는 페이지 디렉터리 포인터 테이블의 기준 주소를 포함하고 있으며, 페이지 디렉터리 포인터 테이블의 기준 주소는 PML4 테이블의 바로 다음에 위치하므로 0x101000(1MB + 4KB)가 됩니다.

 

첫 번째 엔트리의 속성 값은 이전 글에서 계산한 값을 그대로 적용하면 되고, 이에 따라 1로 설정해야 할 비트는 P bit와 R/W bit가 됩니다.

 

PML4 테이블은 첫 번째 엔트리만 유효하고 나머지 엔트리는 유효하지 않으므로 모두 0으로 설정합니다.

 

아래의 코드는 페이지 엔트리에 데이터를 설정하는 kSetPageEntryData() 함수를 정의하고, 이 함수를 통해 PML4 테이블을 생성하는 예시이며 kSetPageEntryData() 함수를 사용하지 않고 직접 자료구조에 값을 대입해도 되지만 코드의 가독성과 편의를 위해 별도의 함수로 추가했습니다.

 

PML4 테이블을 생성하는 코드

 

위의 코드의 kSetPageEntryData() 함수에서 보호 모드에서는 단일 레지스터로 최대 32bit 값까지만 표현할 수 있기 때문에 64bit 주소를 표현하려고 dwUpperBaseAddress와 dwLowerBaseAddress 두 변수를 사용하며, 64bit 주소를 전달하기 위해 상위 32bit와 하위 32bit로 나눈 후 넘겨줍니다.

 

이는 주소를 계산할 때도 동일하게 적용됩니다.

 

dwUpperBaseAddress의 값은 dwLowerBaseAddress의 값이 4GB를 넘을 때마다 증가해야 하는데 주소 계산 도중 32bit 범위를 초과하면 안 되므로 이를 고려하여 계산합니다.

 

FS64 OS페이지의 크기가 2MB이므로, 상위 주소를 계산할 때 미리 1MB로 나누어 계산 도중 32bit 값을 넘지 않게 하고, 계산이 끝난 결과를 다시 4KB로 나누어 최종 결과(상위 32bit 주소)를 얻습니다.

 

다음 코드는 앞의 방식으로 64GB 영역을 매핑하는 페이지 디렉터리를 생성하는 예입니다.

 

64GB까지 매핑하는 페이지 디렉터리를 생성하는 코드

pstPDEntry = (PDENTRY*) 0x102000;
dwMappingAddress = 0;
for(i = 0; i < 512 * 64; i++)
{
    // 페이지 크기인 2MB 를 1MB(20bit)로 나누어 값을 낮춘 후 루프 변수와 곱하고,
    // 계산 결과를 다시 4KB(12bit)로 나누어 상위 32bit의 주소를 계산
    kSetPageEntryData( &(pstPDEntry[i]), (i * (0x200000 >> 20)) >> 12, dwMappingAddress, PAGE_FLAGS_DEFAULT | PAGE_FLAGS_PS, 0);

    // PAGE_DEFAULTSIZE : 2MB
    dwMappingAddress += PAGE_DEFAULTSIZE;
}

 

페이지 디렉터리 포인터 테이블 역시 kSetPageEntryData() 함수를 사용하여 같은 방법으로 생성합니다.

 

페이지 디렉터리 포인터 테이블을 생성하는 예는 후반부에서 보시면 됩니다.

 

참고)

보호 모드에서 실행되는 코드라도 64bit 정수 타입을 사용할 수 있지만, 이는 프로세서에서 지원하는 것이 아니라 컴파일러에서 지원하는 것으로, 64bit 타입을 사용하면 내부적으로 상위 32bit와 하위 32bit로 나누어 저장합니다.

 


 

→ 프로세서의 페이징 기능 활성화

 

페이징 기능을 활성화 하는 방법은 페이지 테이블을 생성하는 것보다 훨씬 간단한데 아래와 같이 설정하면 페이징 기능을 사용할 수 있습니다.

 

● CR0 레지스터의 PG 비트와 CR3 레지스터, CR4 레지스터의 PAE 비트만 1로 설정

 

▶ CR0 레지스터의 PG 비트

PG 비트CR0 레지스터의 최상위 비트에 위치하며, 프로세서의 페이징 기능을 제어하는 비트입니다

 

PG 비트를 1로 설정하는 순간 바로 프로세서의 페이징 기능이 활성화되므로 미리 CR3 레지스터에 최상위 페이지 테이블인 PML4 테이블의 주소를 설정한 뒤 PG 비트를 1로 설정합니다.

 

 

▶ CR3 레지스터

페이지 디렉터리 베이스 레지스터(PDBR, Page-Directory Base Register)라고도 불리며, 최상위 페이지 테이블의 주소를 프로세서에 알리는 역할을 합니다.

 

아래의 그림에서 위는 CR3 컨트롤 레지스터의 구조를 나타낸 것으로, 페이지 엔트리와 같은 역할을 하는 PCD bit와 PWT bit를 포함합니다.

 

CR3 컨트롤 레지스터의 구조(위)와 CR2 컨트롤 레지스터의 구조(아래)

 

페이징 기능과 밀접하게 관련된 컨트롤 레지스터에는 CR3 레지스터만 있는 것이 아닙니다.

 

페이징 기능을 활성화한 후, 잘못된 페이지에 접근하거나 페이징 기능에 오류가 발생하면 페이지 폴트 예외(Page-Fault Exception)가 발생합니다.

 

이때 페이지 폴트 예외를 처리하려면 예외가 발생한 선형 주소가 필요하고, CR2 레지스터가 이러한 역할을 담당합니다.

 

CR2 레지스터를 참고하여 페이지 폴트 예외를 처리하는 예는 후에 알아봅니다.

 

 

보호 모드에서 페이지 크기가 4KB인 3단계 페이징 기능을 사용하는 것이 목적이었다면, CR0 레지스터와 CR3 레지스터를 설정하는 것으로 충분하지만, 최종 목적은 IA-32e 모드에서 동작하며 2MB의 크기를 가지는 페이징을 활성화하는 것이므로 이를 프로세서에게 알려줘야 합니다.

 

이러한 작업은 CR4 레지스터의 PAE(Physical Address Extensions) 비트와 페이지 디렉터리 엔트리의 PS bit를 1로 설정함으로써 처리합니다.

 

페이지 디렉터리 엔트리의 PS bit는 이전에 1로 설정했으므로 이번에는 CR4 레지스터의 PAE 비트만 1로 설정합니다.

 

CR4 레지스터의 구조는 아래와 같으며 각 필드의 의미는 아래의 두 번째 사진과 같습니다.

 

CR4 컨트롤 레지스터의 구조

 

필드 설명
SMXE - Safer Mode Extensions Enable의 약자로 SMX 명령어를 사용할지 여부를 설정

- 1로 설정하면 SMX 명령어를 사용함을 나타내며, 0으로 설정하면 사용하지 않음을 나타냄
VMXE - virtual Machine Extensions Enable의 약자로 VMX 명령어를 사용할지 여부를 설정

- 1로 설정하면 VMX 명령어를 사용함을 나타내며, 0으로 설정하면 사용하지 않음을 나타냄
OSXMMEXCPT - Operating System Support for Unmasked SIMD Floating-Point Exceptions를 의미하며, SIMD 관련 실수 연산시 마스크 되지 않은 예외가 발생했을 때 예외 처리 방법을 설정

- 1로 설정하면 예외가 SIMD Floating-Point Exception로 발생하며, 0으로 설정하면 예외가 Invalid Opcode Exception으로 발생함

- 실수 연산을 사용하는 경우, 정확한 예외 처리를 위해 1로 설정하는 것을 권장
OSFXSR - Operating System Support for FXSAVE and FXRSTOR instructions을 의미하며, OS가 FXSAVE/FXRWTOR 명령 및 실수 연산 관련 명령을 지원하는지 여부를 설정

- 1로 설정하면 실수 연산 관련 명령을 지원함을 나타내며, 0으로 설정하면 실수 연산 관련 명령을 지원하지 않음을 나타냄

- 0으로 설정하면 실수를 연산할 때마다 Invalid Opcode Exception이 발생하므로 1로 설정하는 것을 권장
PCE - Performance-Monitoring Counter Enable의 약자로 RDPMC 명령어를 사용할 수 있는 권한 레벨을 설정

- 1로 설정하면 모든 레벨에서 사용 가능함을 나타내며, 0으로 설정하면 최상위 레벨(0)에서만 사용 가능함을 나타냄
PGE - Page Global Enable의 약자로 Global Page Feature를 사용할지 여부를 설정

- 1로 설정하면 Global Page Feature를 사용함을 나타내며, CR3 레지스터가 교체되어 페이지 테이블이 바뀌는 경우 페이지 엔트리의 PG 비트가 1로 설정된 페이지는 TLB에서 교체 안됨

- 0으로 설정하면 Global Page Feature 기능을 사용하지 않음을 나타냄
MCE - Machine-Check Enable의 약자로 Machine-Check 예외를 사용할지 여부를 설정

- 1로 설정하면 Machine-Check 예외를 사용함을 나타내며, 0으로 설정하면 사용하지 않음을 나타냄
PAE - Physical Address Extensions의 약자로 36bit 이상의 물리 메모리를 사용할지 여부를 설정

- 1로 설정하면 36bit 이상의 물리 메모리를 사용함을 나타내며, 0으로 설정하면 사용하지 않음을 나타냄

- IA-32e 모드에서는 필수적으로 1로 설정해야 함
PSE - Page Size Extensions의 약자로 4KB 또는 그 이상의 페이지 크기를 사용할지 여부를 설정

- 1로 설정할 경우 2MB 또는 4MB 페이지를 사용함을 나타내며, 0으로 설정할 경우 4KB 페이지를 사용함을 나타냄

- PAE가 1로 설정될 경우, PSE bit는 무시되며 페이지 디렉터리 엔트리의 PS bit에 의해 페이지 크기가 결정됨
DE - Debugging Extensions의 약자로 DR4와 DR5 레지스터에 접근을 허락할지 여부를 설정

- 1로 설정하면 DR4, DR5 레지스터는 프로세서에 의해 예약(Reserved)되며, 해당 레지스터에 접근할 경우 Undefined Opcode Exception이 발생

- 0으로 설정하면 DR4, DR5 레지스터는 각각 DR6, DR7 레지스터의 다른 이름(Alias) 역할을 함
TSD - Time Stamp Disable의 약자로 RDTSC 명령어를 사용할 수 있는 권한 레벨을 설정

- 1로 설정하면 최상위 레벨(0)에서만 사용 가능함을 나타내며, 0으로 설정하면 모든 레벨에서 사용 가능함을 나타냄
PVI - Protected-Mode Virtual Interrupts의 약자로 Virtual Interrupt Flag를 사용할지 여부를 설정

- 1로 설정하면 VIF를 사용함을 나타내고 0으로 설정하면 VIF를 사용하지 않음을 나타냄
VME - Virtual-8086 Mode Extensions의 약자로 가상 8086 모드에서 Interrupt And Exception-Handling Extensions 사용 여부를 설정

-1로 설정하면 Interrupt And Exception-Handling Extensions을 사용함을 나타내고, 0으로 설정하면 사용하지 않음을 나타냄

 

 

지금까지의 페이징 활성화하는 데 필요한 단계들을 실제 코드로 보면 다음과 같습니다.

 

CR0 레지스터와 CR3 레지스터를 조작하여 페이징 기능을 활성화하는 예입니다.

 

프로세서의 페이징 기능을 활성화 하는 코드

 

사실 보호 모드에서 IA-32e 모드용으로 생성된 페이지 테이블을 지정하면, 잘못된 엔트리 정보를 참조하므로 커널이 멈추거나 PC가 리부팅되기 때문프로세서의 페이징 기능을 활성화하는 코드는 IA-32e 모드용 디스크립터가 생성된 후에 사용합니다.

 


 

→ 보호 모드 커널에 페이지 테이블 생성 기능 추가

 

지금까지 알아본 페이징 기능을 사용하는 데 필요한 정보들을 통합하여 하나의 파일로 만든 후 C 커널 엔트리 포인트에서 호출하게 하여 커널에 페이지 테이블 생성 기능을 추가합니다.

 


 

→ 페이징 기능 관련 파일 생성

 

01.Kernel32/Source 디렉터리 밑에 Page.h, Page.c 파일을 추가합니다.

 

Page.c 파일IA-32e 모드 커널용 페이지 테이블을 생성하는 소스 파일64GB까지 물리 주소를 선형 주소와 1:1로 매핑하는 역할을 담당합니다.

 

페이지 테이블의 위치와 순서PML4 테이블 → 페이지 디렉터리 포인터 테이블 → 페이지 디렉터리 입니다.

 

이는 각각 0x100000~0x101000, 0x101000~0x102000, 0x102000~0x142000의 영역에 생성됩니다.

 

 

페이지 헤더 파일(01.Kernel32/Source/Page.h) 1

 

페이지 헤더 파일(01.Kernel32/Source/Page.h) 2

 

 

 

페이지 소스 파일(01.Kernel32/Source/Page.c) 1

 

페이지 소스 파일(01.Kernel32/Source/Page.c) 2

 


 

→ C 커널 엔트리 포인트 수정

 

이제 C 커널 엔트리 포인트를 수정하여 kInitializePageTables() 함수를 호출합니다.

 

C 커널 엔트리 포인트 파일은 01.Kernel32/Source 디렉터리에 있습니다.

 

해당 디렉터리에 Main.c 파일을 아래와 같이 수정하면, IA-32e 모드 커널용 페이지 테이블을 생성할 수 있습니다.

 

C언어 엔트리 포인트 소스 파일(01.Kernel32/Source/Main.c) 1

 

C언어 엔트리 포인트 소스 파일(01.Kernel32/Source/Main.c) 2

 


 

→ 빌드와 실행

 

지금까지의 내용을 잘 따라왔다면 빌드 수행 시 정상적으로 Disk.img 파일이 생성되고, 이 이미지 파일을 QEMU나 실제 PC에서 실행하면 아래와 같이 표시됩니다.

 

메시지 내용으로 보아 정상적으로 페이지 테이블이 생성되었습니다.

 

IA-32e 모드 커널을 위한 페이지 테이블이 정상적으로 생성된 화면

 


이것으로 페이징 기능을 활성화하는 데 필요한 작업에 대해 모두 알아보았습니다.

 

이제 IA-32e 모드로 전환하기 위한 모든 준비가 끝났으므로 이후 IA-32e 모드 커널용 디스크립터와 테스트용 커널 코드만 생성하여 64bit로 돌입합니다.

반응형

+ Recent posts