본문 바로가기

MINT64 OS 개발

9장. 페이징 기능을 활성화하여, 64비트 전환 준비

해당 과정부터는 자세한 설명을 적지 않을 예정이다.

적을 시간에 한 글자라도 더 보는 것이 좋을 것 같아서, 간단히 기록만 해둔다.


Ⅰ. 페이지 테이블 생성과 페이징 기능 활성화

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);
}