Sqix

64bit 멀티코어 OS 제작하기 [5] - 3 : 보호 모드의 커널 이미지를 작성하자 본문

MINT64 OS

64bit 멀티코어 OS 제작하기 [5] - 3 : 보호 모드의 커널 이미지를 작성하자

Sqix_ow 2018. 6. 5. 01:26

이제 우리는 보호 모드에서 사용할 코드를 작성해야 합니다. 16비트의 레지스터를 32비트로 변경해 주고, 스택 크기 역시 4바이트로 크기가 증가하였기 때문에 파라미터들에 대한 오프셋 역시 +4n으로, 즉 4의 배수로 늘려주어야 합니다. 이전에 플래그 부분에서 언급했던 내용이기도 하지만, 32비트 오프셋을 이용하기 때문에 우리는 4GB의 메모리 영역에 마음껏 접근할 수 있기 때문에, 비디오 메모리를 접근할 때 굳이 ES 레지스터를 사용할 필요가 없기도 합니다. 


지금까지 우리가 한 과정을 Linux Boot Protocol을 기준삼아서 정리해 보겠습니다. 부트 로더를 이용해서 코드 영역, 데이터 영역, 스택 영역을 설정 및 초기화해 주고, 16비트 커널 이미지를 로드하여 주었습니다. 이제 우리가 할 일은 32비트 커널 이미지를 로드하기 위해서 32비트 커널 엔트리 포인트를 작성하고, 커널 이미지를 작성하여 이를 로드해 주면 됩니다. 


그렇다면, 엔트리 포인트를 먼저 생성하여 보도록 합시다.


01.Kernel32 디렉토리 아래에 EntryPoint.s 파일을 추가합니다(.s 파일은 어셈블리 파일입니다). 그리고 다음 코드를 작성합니다.


[ORG 0x00] ; Start address : 0x00

[BITS 16] ; 16bit code


SECTION .text ; text section(segment)


START:

mov ax, 0x1000 ; EP Start Addr(0x10000)

mov ds, ax

mov es, ax


cli ; Inhibit Interrupt

lgdt [ GDTR ] ; Load GDT Table (load gdt inst)

; Disable Paging, Caching, Align check, use Internal FPU, enable 32bit

mov eax, 0x400000eB ; PG, AM, NW, WP, EM = 0 / CD, NE, ET, TS, MP = 1

mov cr0, eax ; set CR0's flag

; change code segment & set EIP(CS segment selector) 0x00

jmp dword 0x08: ( PROTECTMODE - $$ + 0x10000 )  

[BITS 32] ; 32bit code

PROTECTMODE:

mov ax, 0x10 ; 32bit data segment discriptor

mov ds, ax

mov es, ax

mov fs, ax

mov gs, ax


; Make Stack on 0x00000000 ~ 0x0000FFFF with 64KB size

mov ss, ax

mov esp, 0xFFFE

mov ebp, 0xFFFE

; to use memory efficiently, use 0x0000FFFE(intel processor optimization)


; print message to notify that we changed to 32bit mode

push ( SWITCHSUCCESSMESSAGE - $$ + 0x10000 ) ; push the message's addr

push 2 ; Y location

push 0 ; X location

call PRINTMESSAGE

add esp, 12 ; calling convention


jmp $ ; inf loop


PRINTMESSAGE:

;Stack Frame

push ebp

mov ebp, esp

;Save Registers to recover after return

push esi

push edi

push eax

push ecx

push edx

;Calculate Line address with Y location

mov eax, dword [ ebp + 12 ] ; set Param2(line num) on EAX reg

mov esi, 160 ; line bytes : 2 * 80 == 160

mul esi ; line num * line bytes

mov edi, eax ; set Y addr on EDI reg

;Calculate Video Memory Address with X, Y Location

mov eax, dword [ ebp + 8 ] ; set Param1(X Location) on EAX reg

mov esi, 2 ; set character's byte(2)

mul esi ; X location * sizeof(char)

add edi, eax ; X + Y -> Video Memory Addr

mov esi, dword [ ebp + 16 ] ; Param3 (string addr)


.MESSAGELOOP:

mov cl, byte [ esi ] ; copy 1 byte on cl

cmp cl, 0 ; check string end

je .MESSAGEEND

mov byte [ edi + 0xB8000 ], cl ; print character

add esi, 1 ; go to next index

add edi, 2 ; go to next location(addr)


jmp .MESSAGELOOP ; loop


.MESSAGEEND:

; Recover Register

pop edx

pop ecx

pop eax

pop edi

pop esi

; Stack Frame

pop ebp

ret


; DATA SECTION


; to align GDT data with 8byte

align 8, db 0

; to align GDTR

dw 0x0000


GDTR:

dw GDTEND - GDT - 1 ; whole size of GDT Table

dd ( GDT - $$ + 0x10000 ) ; base address of GDT Table


GDT:

NULLDESCRIPTOR:

dw 0x0000

dw 0x0000

db 0x00

db 0x00

db 0x00

db 0x00


CODEDESCRIPTOR:

dw 0xFFFF ; Limit [ 15 : 00 ]

dw 0x0000 ; Base  [ 15 : 00 ]

db 0x00 ; Base  [ 23 : 16 ]

db 0x9A ; P = 1, DPL = 0, Code Segment, Execute / Read

db 0xCF ; G = 1, D = 0, L = 0, Limit [ 19 : 16 ]

db 0x00 ; Base  [ 31 : 24 ]

DATADESCRIPTOR:

dw 0xFFFF ; Limit [ 15 : 00 ]

dw 0x0000 ; Base  [ 15 : 00 ]

db 0x00 ; Base  [ 23 : 16 ]

db 0x92 ; P = 1, DPL = 0, Data Segment, R/W

db 0xCF ; G = 1, D = 1, L = 0, Limit [ 19 : 16 ]

db 0x00 ; Base  [ 31 : 24 ]

GDTEND:


SWITCHSUCCESSMESSAGE: db 'Successfully Switched To Protected Mode.'


times 512 - ( $ - $$ ) db 0x00 ; fill empty space with 0x00


코드 작성을 완료하였습니다. 이제 makefile을 수정해서 이를 빌드합시다. 또한, 생성한 커널 이미지의 섹터의 수가 1개 뿐이니 bootloader.asm의 TOTALSECTIONCOUNT를 1로 수정하여 줍시다.


all: Kernel32.bin


Kernel32.bin: Source/EntryPoint.s

nasm -o Kernel32.bin $<


clean:

rm -f Kernel32.bin


$<는 상위 의존성 부분의 첫 번째 파일(Source/EntryPoint.s)을 의미하는 매크로입니다. 또한, 혼란을 주기 위해 VirtualOS 파일은 삭제합시다.


#rm -rf VirtualOS*


이제 상위 폴더의 makefile 역시 수정합시다.


all: BootLoader Kernel32 Disk.img


BootLoader:

@echo

@echo ============== Build Boot Loader ==============

@echo


make -C 00.BootLoader

@echo

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

@echo


Kernel32:

@echo

@echo ============== Build 32bit Kernel =============

@echo

make -C 01.Kernel32

@echo

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

@echo


Disk.img: 00.BootLoader/BootLoader.bin 01.Kernel32/Kernel32.bin

@echo

@echo ============== Disk Image Build Start ==============

@echo


cat 00.BootLoader/BootLoader.bin 01.Kernel32/Kernel32.bin > Disk.img

@echo

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

@echo


clean:

make -C 00.BootLoader clean

make -C 01.Kernel32 clean

rm -f Disk.img


바뀐 부분은 Disk.img 부분입니다. 이제 빌드하고 QEMU를 실행해봅시다.




# make

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


이제 32비트로 전환했으니, 커널을 작성해 보도록 합시다!

Comments