7. 페이징 기능을 활성화하여 64비트 전환을 준비하자

들어가기

페이징 설정을 통한 64비트 모드로의 전환 준비!

본론

7.1 선형 주소화 4단계 페이징 기법

  • 페이징에 사용하는 각 테이블은 512(2^9)개의 엔트리로 구성, 다음 레벨에서 사용할 테이블의 기준 주소를 포함.
  • 그림과 구현한 페이징의 차이점은 2MB 페이지를 만들 계획, 따라서 페이지 테이블 엔트리없이 페이지 디렉터리에서 바로 물리 주소.
    • 선형 주소 역시 테이블 관련 9 비트 없이 20 비트를 오프셋으로 사용.

  • 제작하는 OS를 실행하는데 필요한 페이지의 역할은 다음과 같음.
    1. 선형 주소롸 물리 주소를 1:1로 매핑.
    2. 2MB 페이지를 사용하려 최대 64GB의 물리 메모리 매핑.
    3. 물리 메모리 전체 영역에 대해 캐시를 활성화.

7.2 페이지 테이블 구성과 공간 할당

7.2.1 64GB의 물리 메모리 관리를 위한 메모리 계산

  • 페이지 디렉터리는 8 바이트 크기인 엔트리 512개로 구성, 각 엔트리는 2MB 페이지에 대한 정보 담음.
    • 2MB * 512 = 1GB를 관리함.
  • 페이지 디렉터리의 크기는 8 바이트 * 512 = 4KB.
  • 64GB의 메모리 영역 관리를 위해서는 페이지 디렉터리 64개 필요, 크기는 64 * 4KB = 256KB.
  • 페이지 디렉터리를 포인팅하는 페이지 디렉터리 포인터 테이블 역시 512개의 엔트리로 구성되며, 64개의 페이지 디렉터리를 관리하는데 1개면 충분.
  • PML4 역시 8 바이트 크기로 1개면 충분.
  • 64GB 물리 메모리를 매핑하는데 필요한 페이지 테이블 개수는 66개(264KB).

7.2.2 페이지 테이블을 위한 공간 할당

  • 264KB나 되는 영역을 OS 이미지나 커널 어드레스 공간에 두는 것은 좋지 않음.
  • 커널 이미지가 2MB에서 시작, 1MB ~ 2MB 사이에 저장.

7.2.3 공통 속성 필드 설정

PCD 필드와 PWT 필드

  • 속도 향상을 위해 캐시.
  • 캐시 정책으로 Write-Through & Write-Back 방식이 있으며, Write-Back 방식이 더 효과적.
  • PCD = 0, PWT = 0.

U/S 필드와 R/W 필드

  • 유저 레벨과 커널 레벨의 구분이 필요.
  • 하지만 현재는 따로 구분하지 않고(유저 레벨 어플리케이션이 아직 없기 때문에) 모두 읽기/쓰기 가능하게 설정.
  • U/S = 0, R/W = 1.

EXB 필드, A 필드, P 필드, Avail 필드

  • 페이징 기능외에 다른 기능은 사용하지 않을 예정.
  • EXB = 0.
  • 특정 페이지에 접근 했는지 여부 파악도 X.
  • A = 0.
  • 테이블을 임의의 용도로 사용하지 않음.
  • Avail = 0.
  • 해당 엔트리가 유효하다는 것을 나타내야 함.
  • P = 1.

7.2.4 페이지 디렉터리 엔트리용 속성 필드 설정

PAT 필드, G 필드, D 필드

  • 페이지 별로 특수한 옵션을 지정하지 않음.
  • PAT = 0.
  • 페이지 테이블 고정되어 있음.
  • G = 0.
  • A 필드와 마찬가지로 사용하지 않음.
  • D = 0.

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

7.3.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;
  • 세 종류의 페이지 엔트리는 내부 필드가 거의 유사, 개별적으로 정의하지 않고 형태만 정의.
 #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
  • 속성 필드 정의.

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

 /**
  *  페이지 엔트리에 기준 주소와 속성 플래그를 설정
  **/
 void kSetPageEntryData( PTENTRY* pstEntry, DWORD dwUpperBaseAddress,
         DWORD dwLowerBaseAddress, DWORD dwLowerFlags, DWORD dwUpperFlags )
 {
     pstEntry->dwAttributeAndLowerBaseAddress = dwLowerBaseAddress | dwLowerFlags;
     pstEntry->dwUpperBaseAddressAndEXB = ( dwUpperBaseAddress & 0xFF ) |
         dwUpperFlags;
 }

 /**
  *  IA-32e 모드 커널을 위한 페이지 테이블 생성
  **/
 void kInitializePageTables( void )
 {
         PML4TENTRY* pstPML4TEntry;

         // 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 );
         }
 }
  • 아직 보호 모드이므로 64비트 어드레스를 상위 32비트와 하위 32 비트로 나눔.
  • 어드레스를 계산할 때도 동일하게 적용.
    • dwUpperBaseAddress의 값이 dwLowerBaseAddress의 값이 4GB를 넘을 때마다 증가.
    • 어드레스 계산 도중 범위를 초과하면 안됨.

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

  • CR0의 PG 비트 CR3 레지스터, CR4의 PAE 비트를 1로 설정.
  • PG 비트를 1로 설정하는 순간 체이징 기능이 활성화되므로, 그 전에 미리 CR3 레지스터에 PML4 테이블의 어드레스를 설정.
  • CR4 레지스터의 PAE 비트와 페이지 디렉터리 엔트리의 PS 비트를 1로 설정함으로써 IA-32e 모드 페이징 설정.
; PAE 비트를 1로 설정
mov eax, cr4
or eax, 0x20
mov cr4, eax

;PML4 테이블의 어드레시와 캐시 활성화
mov eax, 0x100000
mov cr3, eax

;페이징 기능 활성화
mov eax, cr0
or eax, 80000000
mov cr0, eax
  • 현재는 보호 모드이므로 해당 코드로 페이징을 활성화하는 것은 IA-32e에서 사용.

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

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

page.h 코드 보기
page.c 코드 보기

  • PML4(0x100000 ~ 101000) -> 페이지 디렉터리 포인터 테이블(0x101000 ~ 102000) -> 페이지 디렉터리(0x102000 ~ 142000)

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

32비트 커널 코드 보기

7.5 빌드와 수행

마치며

끝!

Share