해당 과정부터는 자세한 설명을 적지 않을 예정이다.
적을 시간에 한 글자라도 더 보는 것이 좋을 것 같아서, 간단히 기록만 해둔다.
Ⅰ. 페이지 테이블 생성과 페이징 기능 활성화
1. 페이지 엔트리를 위한 자료구조 정의 매크로 정의
- 페이지 테이블은 각 엔트리의 집합이므로, 페이지 엔트리를 나타내는 자료구조를 먼저 정의한다.
- 아래는 8바이트 크기의 페이지 엔트리 자료구조를 정의한 것이다.
typedef struct pageTableEntryStruct{
DWORD dwAttributeAndLowerBaseAddress;
DWORD dwUpperBaseAddressAndEXB;
} PWL4TENTRY, PDPTENTRY, PDENTRY, PTENTRY;
- 페이지 엔트리 속성 필드를 매크로로 정의한 것이다.
// 하위 32비트용 속성 필드
#define PAGE_FLAGS_P 0x00000001 //Present
#define PAGE_FLAGS_RW 0X00000002 //Read/Write
#define PAGE_FLAGS_US 0X00000004 //User/Supervisor (플래그 설정시, 유저 레벨)
#define PAGE_FLAGS_PWT 0X00000008 //Page Level Write-through
#define PAGE_FLAGS_PCD 0x00000010 //Page Level Cache Disable
#define PAGE_FLAGS_A 0x00000020 //Accessed
#define PAGE_FLAGS_D 0x00000040 //Dirty
#define PAGE_FLAGS_PS 0x00000080 //Page Size
#define PAGE_FLAGS_G 0x00000100 //Global
#define PAGE_FLAGS_PAT 0x00001000 //Page Attribute Table Index
// 상위 32비트용 속성 필드
#define PAGE_FLAGS_EXB 0x80000000 //Execute Disable 비트
// 기타
#define PAGE_FLAGS_DEFAULT ( PAGE_FLAGS_P | PAGE_FLAGS_RW )
2. 페이지 엔트리 생성과 페이지 테이블 생성
- 자료구조와 매크로를 사용하여, 페이지 엔트리와 페이지 테이블을 생성한다.
- PML4 테이블 생성 코드
- 페이지 엔트리에 데이터를 설정하는 kSetPageEntryData( )함수 정의하였으며, 해당 함수를 통해 PML4 테이블을 생성하고 있다.
- kSetPageEntryData( )함수를 사용하지 않고 직접 자료구조에 값을 대입해도 되지만, 편의와 가독성을 위해 추가하였다.
// 페이지 엔트리에 데이터를 설정하는 함수 void kSetPageEntryData( PTENTRY* pstEntry, DWORD dwUpperBaseAddress, DWORD dwLowerFlags, DWORD dwUpperFlags){ pstEntry -> dwAttributeAndLowerBaseAddress = dwLowerBaseAddress | dwLowerFlags; pstEntry -> dwUpperBaseAddressAndEXB = (dwUpperBaseAddress & 0xFF ) | dwUpperFlags; } // 페이지 테이블을 생성하는 함수 void kInitializePageTables ( void ){ PML4TENTRY* pstPML4TEntry; int i; pstPML4Entry = (PWL4ENTRY*) 0X100000; kSetPageEntryData( &( pstPML4TEntry [ 0 ] ), 0x00, 0x101000, PAGE_FLAGS_DEFAULT, 0); for (i = 1; i <512; i++){ kSetPageEntryData ( &(pstPMLTEntry[i]), 0, 0, 0, 0); } }
- 64GB까지 매핑하는 페이지 디렉터리를 생성하는 코드
- 어드레스 계산 도중, 32bit 범위를 초과하지 않도록 계산해야 한다.
- MINT64 OS는 페이지의 크기가 2MB이므로, 상위 어드레스 계산할 경우 미리 1MB로 나누어 계산 도중 32bit 값이 넘지 않도록 작성한다.
- 계산이 끝난 결과를 다시 4KB로 나누어 최종결과(상위32비트 어드레스)를 얻는다.
pstPDEntry = (PDENTRY*) 0x102000; dwMappingAddress = 0; for(i = 0; i<512 * 64; i++){ //64GB 영역을 매핑하는데 필요한 페이지 디렉터리의 개수 kSetPageEntryData ( &( psPDEntry[i]), (i * (0x200000 >> 20))>> 12, dwMappingAddress, PAGE_FLAGS_DEFAULT | PAGE_FLAGS_PS, 0); dwMappingAddress += PAGE_DEFAULTSIZE; }
3. 프로세서의 페이징 기능 활성화
- 페이징 기능을 활성화하는 방법은 CR0 레지스터의 PG 비트와 CR3 레지스터, CR4 레지스터의 PAE 비트만 1로 설정하면 페이징 기능을 사용할 수 있다.
- IA-32e 모드에서 동작하며, 2MB 크기를 가지는 페이징을 활성화하는 것이 목적이다.
- 프로세서의 페이징 기능을 활성화하는 코드
- CR3 레지스터를 통해, PML4 테이블의 어드레스를 설정하고 이를 CR4를 통해 프로세서에 알려준다. (CR4 의 PAE 비트를 1로 설정한다.)
; PAE 비트를 1로 설정 mov eax, cr4 ;CR4 컨트롤 레지스터의 값을 EAX 레지스터에 저장 or eax, 0x20 ;PAE 비트(비트5)를 1로 설정 mov cr4, eax ;설정된 값을 다시 CR4 컨트롤 레지스터에 저장 ; PML4 테이블의 어드레스와 캐시 활성화 mov eax, 0x100000 ;EAX 레지스터에 PML4 테이블이 존재하는 0x100000(1MB)를 저장 mov cr3, eax ;CR3 컨트롤 레지스터에 0x100000(1MB)을 저장 ;프로세서의 페이징 기능 활성화 mov eax, cr0 ;EAX 레지스터에 CR0 컨트롤 레지스터를 저장 or eax, 0x80000000 ;PG 비트(비트31)를 1로 설정 mov cr0, eax ;설정된 값을 다시 CR0 컨트롤 레지스터에 저장
Ⅱ. 보호 모드 커널에 페이지 테이블 생성 기능 추가
- C 커널 엔트리 포인트에서 호출하게 하여, 커널에 페이지 테이블 생성 기능을 추가한다.
1. 페이징 기능 관련 파일 생성
- (1.Kernel32/Source) 디렉토리 밑에 Page.h, Page.c 파일을 추가한다.
- Page.c 파일은 IA-32e 모드 커널용 페이지 테이블을 생성하는 소스 파일로, 64GB까지 물리 주소를 선형 주소와 1:1로 매핑하는 역할을 담당한다.
- 페이지 테이블 위치와 순서는 PML4 테이블 → 페이지 디렉터리 포인터 테이블 → 페이지 디렉터리 순이다. (각각 0x100000 ~ 0x101000, 0x101000 ~ 0x102000, 0x102000 ~ 0x142000영역에 생성됨)
- Page.h
#ifndef __PAGE_H__
#define __PAGE_H__
#include "Types.h"
// << 매크로 >>
// 하위 32비트용 속성 필드
#define PAGE_FLAGS_P 0x00000001 // Present
#define PAGE_FLAGS_RW 0x00000002 // Read/Write
#define PAGE_FLAGS_US 0x00000004 // User/Supervisor(플래그 설정 시 유저 레벨)
#define PAGE_FLAGS_PWT 0x00000008 // Page Level Write-through
#define PAGE_FLAGS_PCD 0x00000010 // Page Level Cache Disable
#define PAGE_FLAGS_A 0x00000020 // Accessed
#define PAGE_FLAGS_D 0x00000040 // Dirty
#define PAGE_FLAGS_PS 0x00000080 // Page Size
#define PAGE_FLAGS_G 0x00000100 // Global
#define PAGE_FLAGS_PAT 0x00001000 // Page Attribute Table Index
// 상위 32비트용 속성 필드
#define PAGE_FLAGS_EXB 0x80000000 // Execute Disable 비트
// 아래는 편의를 위해 정의한 플래그
#define PAGE_FLAGS_DEFAULT ( PAGE_FLAGS_P | PAGE_FLAGS_RW )
// 기타 페이징 관련
#define PAGE_TABLESIZE 0x1000
#define PAGE_MAXENTRYCOUNT 512
#define PAGE_DEFAULTSIZE 0x200000
// << 구조체 >>
#pragma pack( push, 1 )
// 페이지 엔트리에 대한 자료구조
typedef struct kPageTableEntryStruct
{
// PML4T와 PDPTE의 경우
// 1 비트 P, RW, US, PWT, PCD, A, 3 비트 Reserved, 3 비트 Avail,
// 20 비트 Base Address
// PDE의 경우
// 1 비트 P, RW, US, PWT, PCD, A, D, 1, G, 3 비트 Avail, 1 비트 PAT, 8 비트 Avail,
// 11 비트 Base Address
DWORD dwAttributeAndLowerBaseAddress;
// 8 비트 Upper BaseAddress, 12 비트 Reserved, 11 비트 Avail, 1 비트 EXB
DWORD dwUpperBaseAddressAndEXB;
} PML4TENTRY, PDPTENTRY, PDENTRY, PTENTRY;
#pragma pack( pop )
// 함수
void kInitializePageTables( void );
void kSetPageEntryData( PTENTRY* pstEntry, DWORD dwUpperBaseAddress,
DWORD dwLowerBaseAddress, DWORD dwLowerFlags, DWORD dwUpperFlags );
#endif /*__PAGE_H__*/
- Page.c
#include "Page.h"
// IA-32e 모드 커널을 위한 페이지 테이블 생성
void kInitializePageTables( void )
{
PML4TENTRY* pstPML4TEntry;
PDPTENTRY* pstPDPTEntry;
PDENTRY* pstPDEntry;
DWORD dwMappingAddress;
int i;
// PML4 테이블 생성
// 첫 번째 엔트리 외에 나머지는 모두 0으로 초기화
pstPML4TEntry = ( PML4TENTRY* ) 0x100000;
kSetPageEntryData( &( pstPML4TEntry[ 0 ] ), 0x00, 0x101000, PAGE_FLAGS_DEFAULT, 0 );
for( i = 1 ; i < PAGE_MAXENTRYCOUNT ; i++ )
{
kSetPageEntryData( &( pstPML4TEntry[ i ] ), 0, 0, 0, 0 );
}
// 페이지 디렉터리 포인터 테이블 생성
// 하나의 PDPT로 512GByte까지 매핑 가능하므로 하나로 충분함
// 64개의 엔트리를 설정하여 64GByte까지 매핑함
pstPDPTEntry = ( PDPTENTRY* ) 0x101000;
for( i = 0 ; i < 64 ; i++ )
{
kSetPageEntryData( &( pstPDPTEntry[ i ] ), 0, 0x102000 + ( i * PAGE_TABLESIZE ), PAGE_FLAGS_DEFAULT, 0 );
}
for( i = 64 ; i < PAGE_MAXENTRYCOUNT ; i++ )
{
kSetPageEntryData( &( pstPDPTEntry[ i ] ), 0, 0, 0, 0 );
}
// 페이지 디렉터리 테이블 생성
// 하나의 페이지 디렉터리가 1GByte까지 매핑 가능
// 여유있게 64개의 페이지 디렉터리를 생성하여 총 64GB까지 지원
pstPDEntry = ( PDENTRY* ) 0x102000;
dwMappingAddress = 0;
for( i = 0 ; i < PAGE_MAXENTRYCOUNT * 64 ; i++ )
{
// 32비트로는 상위 어드레스를 표현할 수 없으므로, Mbyte 단위로 계산한 다음
// 최종 결과를 다시 4Kbyte로 나누어 32비트 이상의 어드레스를 계산함
kSetPageEntryData( &( pstPDEntry[ i ] ),
( i * ( PAGE_DEFAULTSIZE >> 20 ) ) >> 12, dwMappingAddress, PAGE_FLAGS_DEFAULT | PAGE_FLAGS_PS, 0 );
dwMappingAddress += PAGE_DEFAULTSIZE;
}
}
// 페이지 엔트리에 기준 주소와 속성 플래그를 설정
void kSetPageEntryData( PTENTRY* pstEntry, DWORD dwUpperBaseAddress,
DWORD dwLowerBaseAddress, DWORD dwLowerFlags, DWORD dwUpperFlags )
{
pstEntry->dwAttributeAndLowerBaseAddress = dwLowerBaseAddress | dwLowerFlags;
pstEntry->dwUpperBaseAddressAndEXB = ( dwUpperBaseAddress & 0xFF ) |
dwUpperFlags;
}
- C 커널 엔트리 포인트를 수정하여, kInitializePageTables( )함수를 호출한다.
- (1.Kernel32/Source) 디렉터리의 Main.c 파일을 수정하여, IA-32e 모드 커널용 페이지 테이블을 생성한다.
#include "Types.h"
#include "Page.h"
void kPrintString (int iX, int iY, const char* pcString);
BOOL kInitializeKernel64Area( void );
BOOL kIsMemoryEnough( void );
//Main 함수
void Main(void){
DWORD i;
kPrintString( 0, 3, "C Language Kernel Start.....................[Pass]");
// 최소 메모리 크기를 만족하는지 검사
kPrintString(0, 4, "Minimum Memory Size Chek....................[ ]");
if(kIsMemoryEnough() == FALSE){
kPrintString(45, 4, "Fail");
kPrintString(0, 5, "Not Enough Memory~!! MINT64 OS Requires Over " "64Mbyte Memory~!!");
while(1);
}else{
kPrintString(45, 4, "Pass");
}
// IA-32e 모드의 커널 영역을 초기화
kPrintString(0, 5, "IA-32e Kernel Area Initialization...........[ ]");
if(kInitializeKernel64Area() == FALSE){
kPrintString(45, 5, "Fail");
kPrintString(0, 6, "Kernel Area Initialization Fail~!!");
while(1);
}
kPrintString(45, 5, "Pass");
// IA-32e 모드 커널을 위한 페이지 테이블 생성
kPrintString(0, 6, "IA-32e Page Tables Initialization...........[ ]");
kInitializePageTables();
kPrintString(45, 6, "Pass");
while(1);
}
'MINT64 OS 개발' 카테고리의 다른 글
11장. 키보드 디바이스 드라이버 추가 (0) | 2023.07.20 |
---|---|
10장. 64비트 모드로 전환 (0) | 2023.07.20 |
8장. A20 게이트를 활성화하여 1MB이상 영역에 접근 (0) | 2023.07.20 |
7장. C언어로 커널 작성 (0) | 2023.07.20 |
6. 32비트 보호 모드로 전환 (0) | 2023.05.06 |