OS/OS from Scratch

[OS] 09. GDT

jschang 2021. 6. 22. 15:04

서론

이번 강의에서는 GDT를 구현해보겠습니다. 본 강의에서는 GDT에 대한 이해가 반드시 필요합니다. 해당 Github 강의는 다음과 같습니다.

https://github.com/cfenollosa/os-tutorial/tree/master/09-32bit-gdt

 

cfenollosa/os-tutorial

How to create an OS from scratch. Contribute to cfenollosa/os-tutorial development by creating an account on GitHub.

github.com


이론

GDT

GDT (Global Descriptor Table)은 메모리의 Segment와 Protected 모드의 구성 요소를 담은 메모리에 존재하는 자료구조입니다. 다음 블로그에 GDT에 대해 잘 설명되어 있으며 (https://itguava.tistory.com/14) University of Birmingham의 Nick Blundell이 쓰신 Writing a Simple Operating System-from Scratch도 꼭 참고해보시길 바랍니다.

06 Boot Sector Segmentation 강의에서는 16bit offset이 메모리를 다루게 하는 한계를 극복하기 위해 Segment를 사용했습니다. 즉, 16bit에서 0x4fe56과 같은 메모리 주소를 segment를 사용하지 않을 경우, 해당 메모리 주소를 16bit로 표현할 수 없는데 segment를 다음과 같이 이용하면:

mov bx, 0x4000
mov es, bx
mov [es:0xfe56], ax

0x4fe56를 표현할 수 있었습니다.

32 bit 모드에서도 segmenting 이용해 접근 가능한 메모리의 범위를 넓혀주지만, 이를 구현하는 형식은 완전히 다릅니다. 16bit 에서 단순히 segment 레지스터에 저장된 값에 16을 곱한 후 메모리 주소에 더해주는 것이 아니라, segment 레지스터는 GDT안의 Segment Descriptor (SD)에 대한 인덱스가 됩니다. Segment Descriptor는 다음과 같은 구성 요소를 가집니다.

  • Base Address (32bits): Segment가 시작되는 물리적 주소
  • Segment Limit (20bits): Segment의 크기를 의미합니다
  • 기타 Flag, Privilige 등 CPU가 Segment를 해석하는데 필요한 정보

Segment Descriptor의 실제 구조

실제로 Segment Desciptor에서 Base Address와 Segment Limit은 연속적으로 저장되지 않고 조각으로 나누어져 저장되어 있습니다. 또한 CPU는 GDT의 초기값이 Null Descriptor, 즉 8byte가 모두 0으로 되어 있도록 요구합니다. 이를 통해 protected 모드에 진입한 후 Segment Descriptor의 설정을 잊은 상태에서 메모리를 접근할 시 CPU가 에러를 발생시키게 해 문제점을 찾아 해결할 수 있습니다.

Code Segment의 Segment Descriptor은 다음과 같습니다.

  • Base: 0x0
  • Limit: 0xffff
  • Present: 1 (메모리에 Segment가 존재함을 의미, 가상 메모리에 사용됨)
  • Privilige: 0 (0이 가장 높은 권한)
  • Descriptor Type: 1 (1은 Code나 Data Segment을 의미)
  • Type
    • Code: 1
    • Conforming: 0 (0으로 설정함으로써 더 낮은 권한을 가진 Segment에서는 해당 Segment의 코드를 실행할 수 없게 함)
    • Readable: 1 (Readable 하면 코드에 정의된 모든 상수를 읽을 수 있음)
    • Accessed: 0 (디버깅에 사용됨)
  • 기타 Flag
    • Granularity: 1 (Limit에 16x16x16을 곱함, 즉 0xffff은 0xffff0000으로 됨)
    • 32-bit default: 1
    • 64-bit code segment: 0
    • AVL: 0 (디버깅 등에 사용)

Data Segment의 Segment Descriptor은 Code Segment와 같은 구성요소와 내용을 가지고 있지만 Flag부분만 다릅니다.

  • 기타 Flag
    • Code: 0
    • Expand down: 0 (어려운 내용)
    • Writable: 1
    • Accessed: 0

위와 같이 구성된 GDT를 CPU에서 접근할 때는 GDT Descriptor을 이용합니다. GDT Descriptor는 6byte 크기의 구조로

  • GDT Size (16bits)
  • GDT Address (32bits)

을 포함하고 있습니다.


코드

32bit_gdt.asm

코드는 위 GDT 내용을 그대로 구현한 것입니다. 자세한 설명은 주석에 있습니다.

gdt_start:
    ; GDT는 8byte Null로 시작함 
    dd 0x0
    dd 0x0

; GDT Code Segment
gdt_code:
    ; Segment Limit
    ; 0-15 bit
    dw 0xffff
    ; Segement Base
    ; 16-31 bit
    dw 0x0
    ; Segment Base
    ; 0-7 bit
    db 0x0
    ; flag 8bit
    db 10011010b
    ; flag 4bit + Segment Limit 16-19
    db 11001111b
    ; Segment Base
    ; 24-31 bit
    db 0x0

; Code Segment와 base와 segment limit 동일 
; flag 만 다름 
gdt_data:
    dw 0xffff
    dw 0x0
    db 0x0 
    ; flag 
    db 10010010b
    ; flag + Segment Limit 
    db 11001111b
    db 0x0 

gdt_end:

gdt_descriptor:
    ; GDT Size 
    ; 실제 크기는 gdt_end gdt_start 사이 크기이므로 1 뺀다
    dw gdt_end - gdt_start - 1
    ; GDT Address
    dd gdt_start

CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

실행 결과

아직도 위 코드를 실행하기 위한 모든 준비가 되지 않았습니다. 다음 강의에서 이제 이 내용들을 직접 테스트해보겠습니다.