星期二

Kernel in C

之前的Kernel都是用Assembly寫的,不太方便,我又不是要寫什麼高效能的OS,不要自尋煩惱,以下是嘗試把Kernel改為用C來寫。

首先下載兩個工具DJGPP的gcc及ld:

DJGPP的安裝有點麻煩,首先來這兒下載檔案,我下載了下面幾個:
  • djdev203.zip - DJGPP Basic Development Kit
  • bnu219b.zip - Basic assembler, linker
  • gcc432b.zip - Basic GCC compiler
  • mak3791b.zip - Make (processes makefiles)

解壓之後,測試執行失敗,查找原因,好像是因為DJGPP的Windows version暫時不支援長檔案名稱,只能用8.3的format,而且用cmd的dos prompt也有問題,只能用commmand了。做了一個設定environment的.bat:
---------- initc.bat ----------
@echo off
set PATH=d:\os\tools\djgpp\bin;%PATH%
set DJGPP=d:\os\tools\djgpp\djgpp.env
d:
cd d:\os
command
---------- initc.bat ----------

寫好了code,可是搞了很久也搞不定。

拿上回的kernel.asm來試,把頭頂的[BITS 16] -> [BITS 32],編譯,執行... 行不了啊! 這一下我想通了,因為我沒有把OS轉為保護模式(Protected Mode, 32bits),而DJGPP的output,卻一定是32bits的,所以怎麼樣也行不了。

之後嘗試過下載Open Watcom,因為她可以output 16bits,不過參數實在太複雜,有關的網上文章又不多。考慮了一下,之前因為不想太複雜所以沒有轉去保護模式,現在不轉的話反而更加麻煩,而且遲早也要轉到保護模式的,所以決定現在就先轉去了。(最底限度先把必要的轉過去)

開動保護模式,是由Boot Loader負責的,下面的code主要加入GDT,set CR0等,因為我也不是完全了解,不想亂解釋,有興趣的請看這 ->
http://osdever.net/tutorials/brunmar/tutorial_02.php
http://osdever.net/tutorials/brunmar/tutorial_03.php

---------- loader.asm ----------
[BITS 16] ;tell the assembler that its a 16 bit code
[ORG 0x7C00] ;Origin, tell the assembler that where the code will
;be in memory after it is been loaded

reset_drive:
   mov ah, 0 ;not very sure why i need this ^^"
   int 13h
   or ah, ah
   jnz reset_drive

   mov ax, 0
   mov es, ax
   mov bx, 0x1000 ;copy the kernel to 0000:1000

   mov ah, 02h ;Command - 02h for 'Read sector from disk'
   mov al, 02h ;Number of sectors to read
   mov ch, 0 ;Disk cylinder
   mov cl, 02h ;Disk sector (starts with 1, not 0)
   mov dh, 0 ;Disk head
   int 13h
   or ah, ah
   jnz reset_drive ;any problem, back to reset_drive label

   cli ;to protect mode

   xor ax, ax ;clear DS
   mov ds, ax

   lgdt [gdt_desc] ;execute LGDT instruction

   mov eax, cr0 ;set CR0
   or eax, 1
   mov cr0, eax

   jmp 08h:clear_pipe

[BITS 32]
clear_pipe:

   mov ax, 10h
   mov ds, ax
   mov ss, ax

   mov esp, 090000h ;set up stack

   jmp 08h:01000h ;jump to start of the kernel

gdt: ;making Global Description Table

gdt_null: ;Null Segment
   dq 0 ;all 64bits 0

gdt_code: ;Code Segment
   dw 0FFFFh ;16 bits
   dw 0 ;16 bits
   db 0 ;8 bits
   db 10011010b ;8 bits
   db 11001111b ;8 bits
   db 0 ;8 bits

gdt_data: ;Data Segment
   dw 0FFFFh
   dw 0
   db 0
   db 10010010b
   db 11001111b
   db 0

gdt_end:

gdt_desc:
   dw gdt_end - gdt - 1
   dd gdt

   TIMES 510 - ($ - $$) db 0 ;fill the rest of sector with 0
   DW 0xAA55 ; add boot signature at the end of bootloader
---------- loader.asm ----------


跟著是一個用C寫的Hello World Kernel,這個Kernel會清空畫面,打出Kernel Loaded by C.字樣:
---------- kernel.c ----------
#define WHITE_TXT 0x07 // white on black text

void k_clear_screen();
void k_printf();

int main(void)
{
   k_clear_screen();
   k_printf();

   for (;;);
}

void k_clear_screen() // clear the entire text screen
{
   char *vidmem = (char *) 0xb8000;
   unsigned int i=0;
   while(i < (80*25*2))
   {
      vidmem[i]=' ';
      i++;
      vidmem[i]=WHITE_TXT;
      i++;
   }
};

void k_printf()
{
   char *vidmem = (char *) 0xb8000;
   unsigned int i=0;
   char *message = "Kernel Loaded by C.";
   unsigned int line = 1;

   i=(line*80*2);

   while(*message!=0)
   {
      if(*message=='\n') // check for a new line
      {
         line++;
         i=(line*80*2);
         *message++;
      } else {
         vidmem[i]=*message;
         *message++;
         i++;
         vidmem[i]=WHITE_TXT;
         i++;
      }
   }
};
---------- loader.asm ----------

之前的print_f是這樣的 -> k_printf(char *message, unsigned int line),沒法運行,之後把parameters除掉,卻能執行。原因未找到,可能是Stack的問題,要再研究。

不知道什麼問題,那個initc.bat搞出來,DJGPP環境的command prompt異常難用,完全沒辦法重複之前的輸入,連copy and paste也不能,只好寫一個.bat來執行編譯指令 (其實用makefile應該比較好的,Anyway ^^)
---------- c.bat ----------
nasm loader.asm -f bin -o loader.bin
gcc -ffreestanding -c kernel.c -o kernel_b4link.o
ld -e _main -Ttext 0x1000 -o kernel.o kernel_b4link.o
objcopy -R .note -R .comment -S -O binary kernel.o kernel.bin
copy /b /y loader.bin+kernel.bin boot.img
---------- c.bat ----------

nasm編譯Boot Loader。

gcc編譯用C寫的Kernel,-ffreestanding參數指明可能沒有standard library,也可以不是由main function開始執行。

ld把開始的function定為main,並把Text section的絕對位置定在0x1000 (Kernel在記憶體內的位置)

objcopy,我是第一次用的,不太確定為什麼需要,不過她會把Note,Comment,Relocation及Symbol清除。

copy把loader及kernel放在一起。

跑到d:\os\,執行initc.bat,在跳出來的視窗執行c.bat,開動Bochs,測試成功!

5 則留言:

匿名 說...

請問,骨仔大大這些電腦知識都是從哪裡學得的呢?

珍妮佛麥走

bonep 說...

書本,網上,工作的經驗等等... 每天都學到一點,時間一長,就變成現在的樣子了 ^^

匿名 說...

自修就可以這麼厲害@.@~

珍妮佛麥走

bonep 說...

這個~ 比起未學過的人我當然是知道多一點,可是比照箇中高手,我只是路旁的小石子而已~

匿名 說...

對初學者來講C語言並不容易上手,由此可知,骨仔大大不是初學者,必須不停的使用同一個CGI 程式碼,如果編譯器不能正確辨認句子上的謬誤,除錯的工作將會變得更困難,所以骨仔大大是明知山有虎,偏向虎山行,加油!

珍妮佛麥走