본문 바로가기
프로그래밍/PC

FPC 로 OS 커널 만들기... - 1 -

by 사악신 2014. 4. 9.


예전부터 파스칼 OS 제작을 꿈꿔오고 있었는데요. 현실적인 제약 때문에 그냥 은퇴하면 해야겠거니...하고 생각했습니다. 그러다 최근 시간적 여유가 생겨 미뤘던 작업을 하나 둘 해보고 있습니다.(얼마나 갈런지요? ㅠㅠ 먹고 살아야하니...)


일단, Intel 계열 CPU 의 경우~ 처음 부팅시 리얼 모드에서 동작하다 보호 모드로 전환하는 과정을 거쳐야하는데... 리얼 모드는 16비트 기계어로 동작합니다. 따라서 부팅의 시작을 담당하는 부트로더의 경우, 16비트 기계어 코드가 필요한 것이죠. 약간의 결벽증이 있던 저는 2006년경 TP(Turbo Pascal)로 관련 작업을 해보았습니다. 2012/04/09 - [프로그래밍/OS] - 파스칼로 만든 부트로더... - 1 -


당시에는 TP 와 델파이로만 OS 를 만들 생각(물론, 인라인 어셈을 활용할 생각인지라 TASM 매뉴얼도 파고 있었죠.)이었지만, 지금은 프리 파스칼에 끌려 컴파일러를 FPC로 교체하였습니다.


FPC 로 바꾸자 막혀있던 장애물들이 훌훌 사라져버리더군요. :) 엔트리 함수 지정, 네이키드 함수와 같은 소소한 것에서부터 다양한 플랫폼에 맞춰 rtl 을 쉽게 갈아치울 수 있다던지... 말그대로 신천지고 대박이더군요. 별도 툴을 제작할 필요가 없습니다. 하지만, 16비트 컴파일의 경우, 관련 옵션($F, $G)은 존재하지만 지원하지 않는 것(ignored)으로 확인되었습니다. 하마터면 이 지점에서 다시 TP 로 돌아갈 뻔했습니다.


그러다 문득 그런 생각이 들더군요.


뭔 개고생이냐... 어차피 인라인 어셈을 덕지덕지 사용할 거라면... 그냥 어셈블리어 정도는 용납하는 건 어때? ^^


그렇게 조금 생각을 누그러뜨리자 불현듯 GRUB 이 떠올랐습니다. 그렇다면 공개 부트로더가 있고 표준화된 방식이 있다면 그것을 쓰는게 더 합리적이지 않을까? 특수한 부트로더가 필요하다면 그때 제작하면 그만이고...




GRUB 의 경우 버전 1 에 해당하는 Legacy 버전과 GRUB 2 버전이 존재합니다. 현재 GRUB Legacy 를 사용하여 작업하고 있지만, 특별한 이유는 없고 그냥 처음 검색해서 팠더니 그렇게 됐습니다. ㅠㅠ GRUB 2 도 비슷하겠죠. ㅋㅋ


아무튼 GRUB 에 대한 내용을 살펴보니 Multiboot Specification 이란 놈이 존재하고, 또한 GRUB 이 이 규격을 지원하고 있음을 알게 되었습니다. 대충 살펴봤습니다.



1995년 GNU 에서 만들었고 부트 로더 및 커널과 부트로더간 인터페이스를 규정한 문서입니다. 와우~ 심봤다!! 그냥 이 규격을 서로 만족하면 커널과 부트로더는 어떤 놈이든 상관없게 되는 셈입니다.(물론, 조금 오버한 표현이지만요.ㅎ)


자자... 커널을 만들되 Multiboot Specification 에 맞게 구현하면 되는 겁니다. 그럼 GRUB 과 통하리라~


그 다음 해결할 문제는 가상 머신에서 부팅할 때 사용할 이미지 파일 생성이었습니다. 과거에는 플로피 디스크 이미지(IMA)를 생성하였지만, 이제부터 만들 OS 는 1.44MB 로는 무리일 거란 생각이 들었거든요. 게다가 요즘 세상에 플로피 디스크 드라이브가 있긴하나요. 하여 CD 부팅 이미지를 생성할 필요가 있었습니다.


그래서 열심히 삽질했습니다. ㅋㅋ


2014/04/03 - [프로그래밍/OS] - GRUB Legacy 를 이용한 부팅가능한 CD 이미지 만들기


이 무렵... 문득, 나와 같은 짓(파스칼로 OS 제작)을 하고자하는 사람들이 수두룩할 거란 생각이 들더군요. 제 평소 모토가...


세상에 천재란 없다. 잘났다고 떠드는 아이디어는 최소 100명 이상의 사람이 생각하고 있고...

다만, 누가 적시에 구현하여 발표하느냐의 차이밖에 없다.


이거든요. ㅡㅡ;; 역시나 찾아보니 뙇~하고 나오더군요. 일단, 제가 만들고자하는 OS 와 근접한 놈이 있는지 참고할만한 놈은 있는지 쭉 훒어보았습니다. 무엇보다...



이 곳의 자료는 두고두고 활용할 수 있을 거 같습니다. 우선 이 녀석을 바탕으로 커널 개발환경을 구축해보기로 결정하였습니다. 제약 사항이 크게 두 가지가 존재합니다.

  • 32비트 커널
  • ELF 실행파일 포맷


64비트 커널은 추후 진행하기로 하고... 우선은 32비트 커널을 개발할 생각인데, 현재 제가 사용하는 윈도우 및 리눅스 개발 환경은 64비트입니다. 하여 ELF 파일이 생성가능한 32비트 크로스 컴파일러를 생성해야했습니다.


2014/04/04 - [프로그래밍/OS] - FPC 64비트(x86_64) 리눅스에서 32비트(i386) 크로스 컴파일러 생성하기...

2014/04/08 - [프로그래밍/OS] - CodeTyphon 으로 FPC 크로스 컴파일러 생성하기...


익숙한 PE 가 아닌 ELF 를 선택한 이유는 GRUB 때문입니다. 그냥 얻어 쓰는 건데 까짓 따라가야죠. ㅎ


이제 준비는 끝났습니다. 소스를 빌드하고 이미지를 생성한 다음 부팅을 해보는 단계만 남았을 뿐~ ^^




상기 Pascal Bare Bones 의 소스를 입력합니다.


stub.asm


[bits 32]


;  header flag

MULTIBOOT_MODULE_ALIGN equ 1<<0

MULTIBOOT_MEMORY_MAP equ 1<<1

MULTIBOOT_GRAPHICS_FIELDS equ 1<<2

MULTIBOOT_ADDRESS_FIELDS equ 1<<16


; header defines

MULTIBOOT_HEADER_MAGIC equ 0x1BADB002

MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_MODULE_ALIGN | MULTIBOOT_MEMORY_MAP

MULTIBOOT_HEADER_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)


; kernel stack size

KERNEL_STACKSIZE equ 0x4000




section .text


global _start

extern kernelmain


; multiboot header

align 4

dd MULTIBOOT_HEADER_MAGIC

dd MULTIBOOT_HEADER_FLAGS

dd MULTIBOOT_HEADER_CHECKSUM


; entry point

_start:

mov esp, KERNEL_STACK+KERNEL_STACKSIZE ; create kernel 

push eax

push ebx

call kernelmain

cli

hlt



section .bss


; kernel stack

align 32

KERNEL_STACK:

resb KERNEL_STACKSIZE


여기서 붉은색으로 표기한 부분이 GNU 멀티 부트 규격에 필요한 파트입니다. 부트로더는 저 헤더를 찾아서 커널의 엔트리 함수를 호출하게 됩니다. GRUB 의 경우 생성된 커널 이미지의 앞부분 8KB 이내~ 상기 헤더가 존재해야 합니다. 그리고 nasm 으로 소스를 컴파일합니다.


nasm -f elf32 stub.asm


GRUB 이 elf 포맷을 지원하는 관계로 파일포맷을 ELF 로 지정하였습니다. 따라서 커널이미지 또한 elf32 로 생성하여야합니다. 일단, rtl 을 새로 작성하여야하는 관계로 system 을 대충 비어있는 형태로 작성합니다.


system.pas

### delphi
unit system;

interface

type
cardinal = 0..$FFFFFFFF;
hresult = cardinal;
dword = cardinal;
integer = longint;

pchar = ^char;

implementation

end.


그리고 부트로더로부터 전달 받은 값을 처리하기 위한 기본 유닛으로 multiboot.pas 와 콘솔 화면 제어 유닛인 console.pas 가 있지만, 마음이 급한 관계로 생략하고 일부를 떼어와 바로 kernel.pas 에 추가하였습니다. 이 녀석이 하는 일은 부팅이 되면 화면을 초기화하는게 전부입니다.


kernel.pas

### delphi
unit kernel;

interface

procedure kernelmain; stdcall;

implementation

procedure kernelmain; stdcall; [public, alias: 'kernelmain'];
var
vidmem: PChar = PChar($b8000);
i: Integer;
begin
for i := 0 to 3999 do
vidmem[i] := #0;

asm
@loop:
jmp @loop
end;
end;

end.


솔직히 소스 스타일은 마음에 들지 않지만... ^^ 뭐, 지금은 잘 되는지 확인하는게 급하니까요. 끝으로 링커 스크립트를 작성합니다.


linker.script


ENTRY(_start)

SECTIONS

{

  .text  0x100000 :

  {

    text = .; _text = .; __text = .;

    *(.text)

    . = ALIGN(4096);

  }

  .data  :

  {

    data = .; _data = .; __data = .;

    *(.data)

    kimage_text = .;

    LONG(text);

    kimage_data = .;

    LONG(data);

    kimage_bss = .;

    LONG(bss);

    kimage_end = .;

    LONG(end);

    . = ALIGN(4096);

  }

  .bss  :

  {

    bss = .; _bss = .; __bss = .;

    *(.bss)

    . = ALIGN(4096);

  }

  end = .; _end = .; __end = .;

}



컴파일을 합니다.


ppcross386 -Aelf -n -O3 -Op3 -Si -Sc -Sg -Xd -CX -XXs -Rintel -Tlinux kernel.pas


링크 작업을 합니다.(옵션을 제가 조금 추가하였습니다.)


ld -m elf_i386 --gc-sections -s -Tlinker.script -o kernel.bin stub.o kernel.o system.o


kernel.bin 이 생성되었으면, GRUB 를 사용하여 부팅 CD 이미지를 생성합니다. 저는 그냥 간단하게 스크립트를 하나 생성해서 사용했습니다.


makeiso


#!/bin/sh


genisoimage -R -b boot/grub/stage2_eltorito -no-emul-boot -boot-load-size 4 -boot-info-table -input-charset utf8 -o misihi.iso /포함할/디렉토리/경로





Virtual Box 등을 이용하여 생성된 이미지로 시스템을 부팅합니다.




grub 이 나타나면, 로드할 커널 이미지를 지정합니다. 이때 kernel.bin 은 이미지 생성시 "포함할 디렉토리 경로"의 루트에 위치하고 있어야합니다.


kernel /kernel.bin


입력하고 엔터를 치면...



몇 가지 메시지가 나옵니다. 이때 boot 를 입력하고 엔터~



원하는대로 화면이 초기화되었습니다. ^^


사실 리얼모드에서 보호모드로 넘어갈 때 GDT 설정을 하여야하는데, GRUB 의 경우... Virtual Memory, Physical Memory, Video Memory 를 모두 동일하게 설정하는 것으로 보입니다.


이상 최근 잉여력의 산물을 마무리할까합니다. ㅋㅋ


반응형

댓글