10. 32비트 보호모드로 전환하자

리얼모드에서 보호모드로 전환하려면 크게 6단계를 거쳐야한다.
상위 2단계는 보호모드전환에 필요한 자료구조를 생성하는 단계, 나머지 4단계는 생성된 자료구조를 프로세서에 설정하는 단계이다.
보호모드에서 반드시 생성해야하는 자료구조는 세그먼트 디스크립터와, GDT이다.
두가지 자료구조는 보호모드로 전환하는 즉시 프로세서에 의해 참조되므로 미리생성해야 한다.

세그먼트 디스크립터 생성

  • 세그먼트 디스크립터는 세그먼트의 정보를 나타내는 자료구조이다.
  • 우리가 만들 os에서는 보호모드의 기본 기능만 사용하므로 4GB전체 메모리 공간을 지정하는 커널코드와 데이터 세그먼트만 사용한다.
  • 세그먼트 디스크립터는 크게 코드 세그먼트 디스크립터와 데이터 세그먼트 디스크립터로 나누어 진다.
  • 코드 세그먼트 디스크립터는
    • 실행 가능한 코드가 포함된 세그먼트에 대한 정보를 나타내며, CS 세그먼트 셀렉터(리얼모드에서는 레지스터, 보호모드에서는 셀렉터)에 사용된다.
  • 데이터 세그먼트 디스크립터는
    • 데이터가 포함된 세그먼트에 대한 정보를 나타내며, CS를 제외한 나머지 셀렉터에 사용할 수 있다.
    • 스택영역 또한 데이터의 한종류 이므로 데이터 세그먼트 디스크립터를 사용한다.

<세그먼트 디스크립터의 구조>
보호 모드의 세그먼트 디스크립터는 8바이트( 2^(8_8) = 2^64 )로 아래 그림과 같이 필드가 나눠져 있음.
_

** # 왜 기준주소 값이 23:16인가? 7:0 이 아니라? **

<세그먼트 디스크립터의 필드와 의미>

세그먼트의 각필드에 값을 설정하기전에 우리가 설정하고자 하는 세그먼트에 대해서 정리

  1. 커널 코드와 데이터용 세그먼트 디스크립터 각 1개
  2. 커널 코드와 데이터용 세그먼트는 0~4GB까지 모든 영역에 접근할 수 있어야 함
  3. 보호모드용 코드와 데이터에 사용할 기본 오퍼랜드 크기는 32비트여야 함
  4. 보호기능은 사용하지 않으며, 프로세서의 명령을 사용하는데 제약이 없어야 하므로 최상위 권한(0)이어야 함

세그먼트 디스크립터 설정

<타입 필드의 값과 의미>

  • S필드 = 1

    • 코드 세그먼트와 데이터 세그먼트는 세그먼트 디스크립터 이므로
  • 코드 세그먼트 타입 = 0x0A

    • 0x0A(Execute/Read)
  • 데이터 세그먼트 타입

    • 0x02(Read/Write)
  • 기준 주소 = 0

    • 기준이 0부터 시작해야 전체영역(4gb)을 쓸 수 있음.
  • 크기 필드 = 2^20 = 1MB

  • G 필드 = 1

    • 우리가 만들 os의 커널 세그먼트 디스크립터는 4GB 전체 영역에 접근 할 수 있어야 한다.
    • G필드의 값을 1로 설정하면 크기 필드에 4kb를 곱한 것이 실제 세그먼트의 크기가 된다.
    • (크기 필드 * G 필드 ) - 기준 주소 = 세그먼트 디스크립터 크기 = 4GB 가 되어야 된다는 말
  • D/B 필드 = 1

    • D/B필드는 오퍼랜드의 크기를 설정 가능
    • 보호모드는 32비트로 동작하므로 기본 오퍼랜드의 크기도 32비트여야 한다.
  • L 필드 = 0

    • IA-32e모드의 64비트 서브모드 또는 32비트 호환모드를 설정할 수 있음
    • 현재 생성하려는 디스크립터는 보호모드용이므로 0로 설정
  • 권한 필드 = 0

    • 보호 기능을 수행함.
    • 프로세서는 디스크립터의 권한 필드에 설정된 값과 세그먼트 셀렉터의 권한을 비교하여 접근 가능한지 판단
    • 만들고자 하는 os의 보호모드는 권한을 따로 구분하지 않으므로 권한필드를 모두 최상위 권한(0)으로 설정
  • P = 1

    • 유효한 세그먼트 디스크립터라는 것을 알려야 하기 때문에 1
  • AVL = 0

    • 임의로 사용가능 한 필드이지만 현재 만드려는 os는 별도의 값을 쓰지 않으므로 0
  • 세그먼트 타입의 비트 8에 있는 접근 여부 비트는 프로세서에서 설정하는 비트이다.

  • 프로세서는 해당 디스크립터가 참조될때마다 비트8을 1로설정한다.

  • 이를 이용하면 비트8을 이용해서 특정 세그먼트 디스크립터가 사용되었는지 여부를 확인할 수 있다.

  • 비트10에 있는 역방향 확장비트는 아래로(상위 addr -> 하위 addr)자라는 스택을 위한 옵션

    • 스택 세그먼트의 크기를 동적으로 확장/축소할 목적으로 사용한다.
    • 역방향 확장기능을 사용하면 세그먼트 크기(limit)값이 기준주소에서 아래방향(0바이트)방향으로 적용
    • 세그먼트의 범위는(세그먼트의 기준 주소 - 세그먼트 크기) ~ 세그먼트 기준주소가 된다.
    • 또한 비트10에 위치한 접근 승인 비트는 권한에 관계없이 해당 코드 세그먼트에 접근할 수 있음을 나타낸다.

자료구조와 메모리공간, 어셈블리어 코드와의 관계

  • 인텔 문서는 하위방향에서 상위방향, 오른쪽에서 왼쪽으로 어드레스가 증가한다.
  • 어셈블리어 코드는 하위 어드레스의 데이터부터 상위 어드레스의 데이터 순서로 기술하므로 메모리 공간에 나타난 순서와 반대이다.

GDT 정보생성

  • GDT(Global Descriptor Table)자체는 연속된 디스크립터의 집합
  • 코드세그먼트 디스크립터와 데이터 세그먼트 디스크립터를 연속된 어셈블리어 코드로 나타내면 그 전체 영역이 GDT가 된다.
  • null 디스크립터를 가장 앞부분에 추가 해야한다.
  • null 디스크립터는 프로세서에 의해 예약된 디스크립터로 모든 필드가 0으로 초기화된 디스크립터이며 일반적으로 참조되지 않음
  • 프로세서에 GDT의 시작 어드레스와 크기 정보를 로딩해야 한다. 따라서 이것을 저장하는 아래와 같은 자료구조가 필요하다.

<GDT 정보를 저장하는 자료구조>

  • GDT정보를 저장하는 자료구조의 기준주소는
    • 32비트 크기
    • 데이터 세그먼트의 기준주소와 관계없이 어드레스 0을 기준으로 하는 선형주소
    • 선형주소이기 때문에 물리메모리주소로 변환해야 한다.
      1. GDT의 선형주소는 현재 코드가 실행되고 있는 세그먼트의 기준주소를 알고 있으므로 현재 세그먼트의 시작을 기준으로 GDT오프셋을 구하고
      2. 세그먼트 기준 주소를 더해주면 구할 수 있다.
    • 현재 코드는 부트로더에 의해 0x10000에 로딩되어 실행되고 있으므로 자료구조를 생성할 때 GDT 오프셋에 0x10000을 더해주면 선형주소가 된다.

보호모드로 전환

보호모드로 전환하려면 GDTR 레지스터 설정, CR0 컨트롤레지스터 설정, jmp 명령 수행 3단계를 수행하면 된다.

프로세서에 GDT정보 설정

  • 프로세서에 GDT정보를 설정하려면 lgdt명령어를 사용하면 됨
    • 2바이트 크기와 4바이트 기준주소로 된 GDT 정보 자료구조를 오퍼랜드로 받는다.(아래 코드 처럼)
lgdt [ GDTR ] ; GDTR 자료구조를 프로세서에 설정하여 GDT 테이블을 로드
  • 보호모드로 전환하는 과정과 전환 후 인터럽트 설정을 완료하기 전까지는 인터럽트가 발생하지 않게 하는 것이 좋다.
    • 인터럽트가 발생하면 프로세서는 인터럽트 처리함수(인터럽트 핸들러)를 찾을 수 없음
    • 인터럽트를 발생할 수 없게 설정하려면 cli명령어를 사용하고 인터럽트를 발생할 수 있게 설정하려면 sti명령어를 사용한다.

CR0 컨트롤 레지스터 설정

CR0컨트롤 레지스터에는 Cache, Paging, FPU(Floating Process Unit)등과 관련된 필드가 포함되어 있다.

<CR0의 구조와 보호모드 전환을 위한 설정 값>

  • 우리가 만들 os에서 보호모드는 거쳐가는 임시모드에 불과

  • 세그먼테이션 기능 이외에는 사용하지 않음

  • 페이징, 캐시, 메모리 정렬 검사, 쓰기 금지 기능은 모두 사용하지 않음으로 설정

  • FPU도 안쓰니까 임시값으로 설정

  • FPU관련 필드

    • EM = 0

      • FPU 명령을 소프트웨어로 에뮬레이션하지 않게 한다.
    • ET = 1

      • ?
    • MP = 1, TS = 1, NE = 1

      • 현재 FPU를 사용하면 정상적으로 작동하지 않는다.

      • 따라서 FPU명령이 실행되었을 때 예외가 발생하게 설정한다.
        위의 설명대로 CR0 설정하는 코드

        mov eax, 0x4000003B ; PG=0, CD=1, NW=0, AM=0, WP=0, NE=1, ET=1, TS=1, EM=0, MP=1, PE=1
        mov cr0, eax        ; CR0에 eax를 넣어서 플래그 설정 후 보호모드로 전환
  • CR0를 설정했으니 이제 남은 작업은 jmp명령으로 cs를 갱신하고 32bit코드로 변경하는 것

외부메모리, 캐시 동기화 정책

  • 외부메모리의 내용과 캐시의 내용을 동기화 시키는 정책은 크게 Write-through방식, Write-back 방식으로 구분됨
  • Write-through
    • 메모리에 쓰기가 수행될 때마다 캐시의 내용과 외부메모리의 내용을 모두 갱신
  • Write-back
    • 방금쓴 내용을 캐시에만 갱신하고 외부메모리에 쓰는 작업은 최대한 뒤로 미룬다.
    • 캐시는 작아서 새로운 데이터를 캐시에 넣으려면 덮어씌어야 함
    • Write-back방식은 캐시의 내용을 버릴 때 캐시의 내용을 외부메모리에 갱신함으로서 외부메모리에 접근하는 횟수를 줄이는 것

보호모드로 전환과 세그먼트 셀렉터 초기화

  • 준비는 끝났다. 이제 남은 일은 32비트 코드를 준비한 후, 한 줄의 어셈블리어 코드로 cs의 값을 바꾸는 것
    • [ BITS 32 ] 로 이 명령어 아래의 모든 코드를 32비트 코드로 생성
  • cs를 교체하려면 jmp명령과 세그먼트 레지스터 접두사를 사용해야 한다.
    • jmp dword 0x08: ( PROTECTEDMODE - $$ + 0x10000 )
      • 세그먼트 정보는 디스크립터에 저장
      • 세그먼트를 실행시키는 것은 셀렉터가 하는 일
        • 셀렉터에 어드레스를 설정하면 된다.
        • GDT내의 디스크립터의 어드레스를 사용, 이는 GDT의 시작 어드레스로부터 떨어진 거리(offset)를 의미한다.
      • 명령어 분석
        • 커널 코드 세그먼트가 0x00을 기준으로 하는 반면 실제코드는 0x10000을 기준으로 실행되고 있으므로 오프셋에 0x10000을 더해서 세그먼트 교체 후에도 같은 선형주소를 가리키게 함

보호모드로 전환하는 코드

; 커널 코드 세그먼트를 0x00을 기준으로 하는 것으로 교체하고 EIP의 값을 0x00을 기준으로 재설정
; cs : EIP
jmp dword 0x08: ( PROTECTEDMODE - $$ + 0x10000 )
[BITS 32]       ; 이하의 코드는 32비트 코드로 설정
PROTECTEDMODE:
  mov ax, 0x10  ; 보호모드 커널용 데이터 세그먼트 디스크립터를 ax에 저장
  mov ds, ax
  mov es, ax
  mov fs, ax
  mov gs, ax

  ; 스택을 0x00000000 ~ 0x0000FFFF 영역에 64kb크기로 생성
  mov ss, ax
  mov esp, 0xFFFE
  mov ebp, 0xFFFE

보호모드용 PRINTSTRING 함수

보호모드용 PRINTSTRING함수인 PRINTMESSAGE함수

; 메시지를 출력하는 함수
; 스택에 x좌표, y좌표, 문자열
PRINTMESSAGE:
    push ebp    ; bp를 스택에 삽입
    mov ebp, esp    ; bp에 sp 저장
    ; 함수에서 임시로 사용하는 레지스터들로 함수의 마지막 부분에서 스택에 삽입된 값을 꺼내 원래 값으로 복원
    push esi    
    push edi
    push eax
    push ecx
    push edx

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; x,y의 좌표로 비디오 메모리의 어드레스를 계산함
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; y 좌표를 이용해서 먼저 라인 어드레스를 구함
    mov eax, dword [ ebp +12 ]    ; 파라미터 2(화면 좌표 y)를 eax에 설정
    mov esi, 160            ; 한라인의 바이트 수(2*80컬럼)를 esi에 설정
    mul esi                ; eax와 esi를 곱하여 화면 y addr 계산
    mov edi, eax            ; 계산된 화면 y addr를 edi에 설정

    ; x좌표를 이용해서 2를 곱한 후 최종 addr구함
    mov eax, dword [ ebp +8 ]    ; vkfkalxj 1(화면 좌표 x)를 eax에 설정
    mov esi, 2            ; 한 문자를 나타내는 바이트 수(2)를 esi에 설정
    mul esi                ; eax와 esi를 곱하여 화면 x addr를 계산
    add edi, eax            ; 화면 y addr와 계산된 x addr를 더해서 실제 비디오 메모리 어드레스를 계산

    ; 출력할 문자열의 어드레스
    mov esi, dword [ ebp + 16 ]    ; vkfkalxj 3(출력할 문자열의 어드레스)

.MESSAGELOOP:            ; 메세지를 출력하는 루프
    mov cl, byte [ esi ]    ; esi가 가리키는 문자열 위치에서 한문자를 cl에 복사
                ; cl은 ecx의 하위 1바이트를 의미
                ; 문자열은 1바이트면 충분하므로 ecx의 하위 1바이트만 사용

    cmp cl, 0        ; 복사된 문자와 0을 비교
    je .MESSAGEEND        ; 복사된 문자의 값이 0이면 문자열이 종료되었음을 의미하므로 .MESSAGEEND로 이동하여 문자 출력 종료

    mov byte [ edi +0xB8000 ], cl    ; 0이 아니라면 비디오 메모리 addr
                    ; 0xB8000 + edi에 문자를 출력

    add esi, 1        ; 다음 문자열로 이동
    add edi, 2        ; 비디오 메모리의 다음 문자 위치로 이동
                ; 비디오 메모리는 (문자, 속성) 형식이므로 문자만 출력하려면 2를 더해야 함

    jmp .MESSAGELOOP    ; 메시지 출력 루프로 이동하여 다음 문자를 출력

.MESSAGEEND:
    ; 함수 사용이 끝나면 스택 복원
    pop edx
    pop ecx
    pop eax
    pop edi
    pop esi
    pop ebp        ; bp 복원
    ret
  • 리얼모드의 PRINTMESSAGE 함수와의 차이점은
    • 오프셋이 4의 배수로 바뀌었다.
    • 대부분 32비트 범용 레지스터로 수정되었다.
    • 리얼모드에서 비디오 메모리 어드레스 지정을 위해 사용하던 es가 사라졌다는 것
      • 리얼모드에서는 64kb 범위의 어드레스만 접근가능했으므로 화면 표시를 위해 세그먼트가 필요했음
      • 그러나 보호모드는 4gb 전 영역에 접근이 가능함
      • 따라서 es를 제거하고 직접 비디오 메모리에 접근해서 데이터를 쓰도록 수정

보호모드용 커널 이미지 빌드와 가상 os이미지 교체

커널 엔트리 포인트 생성

./01.Kernel32/Source/EntryPoint.s
EntryPoint.s 파일은 보호모드 커널의 가장 앞부분에 위치하는 코드
보호모드 전환과 초기화를 수행하여 이후에 위치하는 코드를 위한 환경을 제공한다.

  • EntryPoint는 우ㅚ부에서 해당 모듈을 실행할 때 실행을 시작하는 지점을 의미
[ORG 0x00]    ; 코드의 시작 어드레스를 0x00으로 설정
[BITS 16]    ; 이하 코드는 16비트 코드로 설정

SECTION .text    ; text 섹션(세그먼트)을 정의

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
START:
    mov ax, 0x1000    ; 보호모드 엔트리 포인트의 시작 어드레스(0x10000)를 세그먼트 레지스터 값으로 변환
    mov ds, ax    ; ds에 설정
    mov es, ax    ; es에 설정

    cli        ; 인터럽트 발생 못하게
    lgdt [ GDTR ]    ; gdtr 자료구조를 프로세서에 설정하여 gdt 테이블을 로드
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; 보호 모드로 진입
    ; Disable Pagin, Disable Cache, Internal FPU, Disable Align Check,
    ; Enable ProtectedMode
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    mov eax, 0x4000003B    ; PG=0, NW=0, AM=0, WP=0, NE=1, ET=1, TS=1, EM=0, MP=1, PE=1
    mov cr0, eax        ; cr0에 위에서 저장한 플래그를 설정하여 보호모드로 전환

    ; 커널 코드 세그먼트를 0x00을 기준으로 하는 것으로 교체하고 EIP의 값을 0x00을 기준으로 재설정
    ; cs 세그먼트 셀렉터 : EIP
    jmp dword 0x08: ( PROTECTEDMODE - $$ + 0X10000 )

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; 보호모드로 진입
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[BITS 32]    ; 이하 코드는 32비트 코드로 설정
PROTECTEDMODE:
    mov ax, 0x10    ; 보호모드 커널용 데이터 세그먼트 디스크립터를 ax에 저장
    mov ds, ax    
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; 스택을 0x00000000 ~ 0x0000FFFF영역에 64kb크기로 생성
    mov ss, ax
    mov esp, 0xFFFE    ; esp addr = 0xfffe
    mov ebp, 0xFFFE    ; ebp addr = 0xfffe

    ; 화면에 보호모드로 전환되었다는 메시지를 찍는다.
    push ( SWITCHSUCCESSMESSAGE - $$ + 0X10000 )    ; 출력할 메시지의 어드레스를 스택에 삽입
    push 2                        ; 화면 y 좌표(2)를 스택에 삽입
    push 0                        ; 화면 x 좌표(0)를 스택에 삽입
    call PRINTMESSAGE                ; PRINTMESSAGE 함수 호출
    add esp, 12                    ; 삽입한 파라미터 제거

    jmp $    ; 현재 위치에서 무한루프 수행

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 함수 코드 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 메시지를 출력하는 함수
; 스택에 x좌표, y좌표, 문자열
PRINTMESSAGE:
    push ebp    ; bp를 스택에 삽입
    mov ebp, esp    ; bp에 sp 저장
    ; 함수에서 임시로 사용하는 레지스터들로 함수의 마지막 부분에서 스택에 삽입된 값을 꺼내 원래 값으로 복원
    push esi    
    push edi
    push eax
    push ecx
    push edx

    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; x,y의 좌표로 비디오 메모리의 어드레스를 계산함
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; y 좌표를 이용해서 먼저 라인 어드레스를 구함
    mov eax, dword [ ebp +12 ]    ; 파라미터 2(화면 좌표 y)를 eax에 설정
    mov esi, 160            ; 한라인의 바이트 수(2*80컬럼)를 esi에 설정
    mul esi                ; eax와 esi를 곱하여 화면 y addr 계산
    mov edi, eax            ; 계산된 화면 y addr를 edi에 설정

    ; x좌표를 이용해서 2를 곱한 후 최종 addr구함
    mov eax, dword [ ebp +8 ]    ; vkfkalxj 1(화면 좌표 x)를 eax에 설정
    mov esi, 2            ; 한 문자를 나타내는 바이트 수(2)를 esi에 설정
    mul esi                ; eax와 esi를 곱하여 화면 x addr를 계산
    add edi, eax            ; 화면 y addr와 계산된 x addr를 더해서 실제 비디오 메모리 어드레스를 계산

    ; 출력할 문자열의 어드레스
    mov esi, dword [ ebp + 16 ]    ; vkfkalxj 3(출력할 문자열의 어드레스)

.MESSAGELOOP:            ; 메세지를 출력하는 루프
    mov cl, byte [ esi ]    ; esi가 가리키는 문자열 위치에서 한문자를 cl에 복사
                ; cl은 ecx의 하위 1바이트를 의미
                ; 문자열은 1바이트면 충분하므로 ecx의 하위 1바이트만 사용

    cmp cl, 0        ; 복사된 문자와 0을 비교
    je .MESSAGEEND        ; 복사된 문자의 값이 0이면 문자열이 종료되었음을 의미하므로 .MESSAGEEND로 이동하여 문자 출력 종료

    mov byte [ edi +0xB8000 ], cl    ; 0이 아니라면 비디오 메모리 addr
                    ; 0xB8000 + edi에 문자를 출력

    add esi, 1        ; 다음 문자열로 이동
    add edi, 2        ; 비디오 메모리의 다음 문자 위치로 이동
                ; 비디오 메모리는 (문자, 속성) 형식이므로 문자만 출력하려면 2를 더해야 함

    jmp .MESSAGELOOP    ; 메시지 출력 루프로 이동하여 다음 문자를 출력

.MESSAGEEND:
    ; 함수 사용이 끝나면 스택 복원
    pop edx
    pop ecx
    pop eax
    pop edi
    pop esi
    pop ebp        ; bp 복원
    ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 데이터 영역
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 아래의 데이터들을 8바이트에 맞춰 정렬하기 위해 추가
align 8, db 0

; GDTR의 끝을 8byte로 정렬하기 위해 추가
dw 0x0000
; GDTR 자료구조 정의
GDTR:
    dw GDTEND - GDT - 1        ; 아래에 위치하는 gdt테이블의 전체 크기
    dd ( GDT - $$ + 0x10000 )    ; 아래에 위치하는 gdt테이블의 시작 addr

;GDT 테이블 정의
GDT:
    ; null디스크립터, 반드시 0으로 초기화해야 함
    NULLDescriptor:
        dw 0x0000
        dw 0x0000
        db 0x00
        db 0x00
        db 0x00
        db 0x00

    ; 보호 모드 커널용 코드 세그먼트 디스크립터
    CODEDESCRIPTOR:
        dw 0xFFFF    ; Limit [15:0]
        dw 0x0000    ; Base [15:0]
        db 0x00        ; Base [23:16]
        db 0x9A        ; P=1, DPL=0, code segment, Execute/Read
        db 0xCF        ; G=1, D=1, L=0, Limit[19:16]
        db 0x00        ; Base [31:24]

    ; 보호모드 커널용 데이터 세그먼트 디스크립터
    DATADESCRIPTOR:
        dw 0xFFFF    ; limit [15:0]
        dw 0x0000    ; base [15:0]
        db 0x00        ; base [23:16]
        db 0x92        ; P=1, DPL=0, data segment, read/write
        db 0xCF        ; G=1, D=1, L=0, limit[19:16]
        db 0x00        ; base [31:24]
GDTEND:

; 보호모드로 전환되었다는 메시지
SWITCHSUCCESSMESSAGE: db 'Switch To Protected Mode Success!', 0

times 512 - ( $ - $$ ) db 0x00    ; 512바이트를 맞추기 위해 남은 부분을 0으로 채움

makefile 수정과 가상 os 이미지 파일 교체

./01.Kernel32/makefile

all: Kernel32.bin

Kernel32.bin: Source/EntryPoint.s
  nasm -o Kernel32.bin $<

clean:
  rm -f Kernel32.bin
  • $< 매크로: Dependency(:의 오른쪽)의 첫번 째 파일을 의미하는 매크로

./makefile


...이상 동일...

Disk.img: 00.BootLoader/BootLoader.bin 01.Kernel32/Kernel32.bin
  @echo
  @echo ================ Disk Image Build Start ================
  @echo

  car $^ > Disk.img

  @echo
  @echo ================ All Build Complete ================
  @echo

...이하 동일...
  • $^ 매크로: Dependency(:의 오른쪽)에 나열된 전체파일을 의미
    • 위에서는 00.BootLoader/BootLoader.bin 01.Kernel32/Kernel32.bin 으로 치환된다.
    • 위의 두개의 파일을 합쳐 Disk.img를 생성한다.

os이미지 통합과 qemu 실행

빌드한 보호모드 커널의 이미지의 크기는 512바이트(1섹터)밖에 되지 않으므로 부트로더가 한섹터를 로딩한 후 나머지 1023섹터를 읽으려다 정지한다.

00.BootLoader/BootLoader.asm 의 TOTALSECTORCOUNT를 아래와 같이 바꾸자

TOTALSECTORCOUNT: dw 1

sudo qemu-system-x86_64 -m 64 -fda ./Disk.img -localtime -M pc

'Project > OS' 카테고리의 다른 글

9. 테스트를 위한 가상 os이미지 생성  (0) 2019.07.16
8. os 이미지 로딩 기능 구현  (0) 2019.07.16
7. 디스크에서 os이미지 로딩  (0) 2019.07.16
6. Bootloader 만들기  (0) 2019.07.11
5. 부팅과 부트로더  (0) 2019.07.11

+ Recent posts