項目地址:https://github.com/lucasysfeng/lucasOShtml
上一講咱們介紹了計算機的啓動流程,並給出了一份簡單的主引導記錄代碼,此份代碼僅僅是顯示幾個字符,並無作它本應該作的事--啓動內核。本講咱們首先看下內核是如何被啓動的,而後寫一個簡單的內核,用已經實現的主引導記錄配合GRUB啓動它。ios
前一講咱們說到,計算機讀取"主引導記錄"前面446字節的機器碼以後,會運行事先安裝的「啓動管理器」bootloader,由用戶選擇啓動哪一個內核,以後就會載入內核,將控制權交給內核。GNU GRUB(GRand Unified Bootloader)就是一種bootloader,知足多重引導規範(The Multiboot Specification),GRUB可選擇操做系統分區上的不一樣內核,下圖就是GRUB的圖形界面:git
圖 GRUB界面github
可以被GRUB啓動的內核須要知足兩個的條件:shell
(1) 內核的前8K字節內必需要包含多重引導規範的頭信息(Multiboot Header);
(2) 內核要加載在內存地址的1MB以上。sass
那麼Multiboot Header是什麼樣子的呢?它必須包含4字節對齊的3個域(還有其餘非必須域,咱們不討論),以下:app
魔數域(magic):標誌頭的魔數,必須等於 0x1BADB002。。
標誌域(flag):是否須要引導程序支持某些特性,咱們不關心這些特性,這個標誌置爲0。
校驗域(checksum):校驗等式是否成立(magic + flags + checksum = 0)函數
本文不討論GRUB的實現,咱們會用前人已經寫好的GRUB(筆者會給出),咱們要作的是完成符合GRUB啓動規範的內核。爲了完成這個內核,咱們須要寫少許的彙編用來在內核中加入Multiboot Header,而後用C語言寫內核入口,最後將彙編目標代碼和C語言目標代碼連接起來生成真正的內核。下面就讓咱們一步步地完成這些吧!ui
第一步 彙編入口google
MBOOT_MAGIC equ 0x1BADB002 ; multiboot magic域,必須爲此值 MBOOT_FLAGS equ 0x00 ; multiboot flag域, GRUB啓動時是否要作一些特殊操做 MBOOT_CHECKSUM equ -(MBOOT_MAGIC + MBOOT_FLAGS) ; multiboot checksum域,校驗上面兩個域是否正確 [BITS 32] ; 以32位編譯 section .text dd MBOOT_MAGIC dd MBOOT_FLAGS dd MBOOT_CHECKSUM dd start [GLOBAL start] [EXTERN kernel_main] ; 內核入口函數, EXTERN代表此符號在外部定義 start: cli ; 禁用中斷 call kernel_main ; 調用內核入口函數 jmp $ ; 無限循環
在上面彙編中,咱們定義了GRUB啓動須要的域MBOOT_MAGIC、MBOOT_FLAGS和MBOOT_CHECKSUM,並調用了內核入口函數kernel_main, kernel_main下一節實現。
$nasm -f elf boot.asm -o boot.o
運行上面命令後會生成目標文件boot.o,-f elf的意思是生成ELF格式的目標代碼。
/**************************************************** # File : kernel.c # Blog : www.cnblogs.com/lucasysfeng # Author : lucasysfeng # Description : 內核入口函數 ****************************************************/ int kernel_main() { // 顯存開始地址 char *display_buf = (char*)0xb8000; // 清屏 unsigned int i = 0; const unsigned int total = 80 * 25 * 2; // 一屏25行,每行80個字符,每一個字符2個字節 while(i < total) { display_buf[i++] = ' '; display_buf[i++] = 0x04; // 顏色 } // 顯示字符 const char *str = "Hello World, welcome to mykernel!"; for (i = 0; '\0' != *str;) { display_buf[i++] = *(str++); display_buf[i++] = 0x04; } return 0; }
0xb8000h是顯存開始的地址,讀者能夠看第一講(http://www.cnblogs.com/lucasysfeng/p/4846119.html)「實模式內存地址空間分佈」那張圖,找到0xb8000h這個地址。從0xb8000h這個地址開始,每2個字節表示一個字符,前一個字節是字符的ASCII碼,後一個字節是這個字符的顏色和屬性,顏色和屬性此處先不用關心。這段C代碼的其他部分相信讀者都能看得懂,我就不過多解釋了。
$gcc -m32 -c -o kernel.o kernel.c
運行上面命令後,目標文件kernel.o就生成了。
上面講到了能被GRUB啓動的內核須要知足的條件:
(1) 內核的前8K字節內必需要包含多重引導規範的頭信息(Multiboot Header);
(2) 內核要加載在內存地址的1MB以上。
咱們將頭信息放在了彙編生成的目標文件boot.o中,所以咱們須要將boot.o和kernel.o連接到一塊兒生成真正的kernel,而且這個真正的內核要加載到1MB內存上,爲此,咱們須要下面的連接腳本和命令(關於連接腳本的使用自行google,筆者的另外一篇文章《連接到底幹了什麼》能夠參考):
/*************************** * 文件名: link.ld ***************************/ ENTRY(start) SECTIONS { . = 0x100000; .text : { *(.text) . = ALIGN(4096); } .data : { *(.data) *(.rodata) . = ALIGN(4096); } }
咱們用ld命令連接目標文件boot.o和kernel.o,指明使用連接腳本link.ld:
$ ld -T link.ld -m elf_i386 -nostdlib boot.o kernel.o -o kernel
運行上面命令後,會生成咱們要啓動的真正的內核kernel,那麼這個kernel是否知足GRUB啓動規範呢?咱們能夠經過反彙編來看一下:
$ objdump -d kernel | head -n30
結果以下圖所示,咱們看到100000了嗎,這個就是起始的地址即1M,看到02 b0 ad 1b 00 00了嗎,這個就是GRUB魔數域1b ad b0 02(大小端問題,反向存儲)
$ objdump -d kernel | head -n30 kernel: 文件格式 elf32-i386 Disassembly of section .text: 00100000 <start-0x10>: 100000: 02 b0 ad 1b 00 00 add 0x1bad(%eax),%dh 100006: 00 00 add %al,(%eax) 100008: fe 4f 52 decb 0x52(%edi) 10000b: e4 10 in $0x10,%al 10000d: 00 10 add %dl,(%eax) ... 00100010 <start>: 100010: fa cli 100011: bc 03 80 00 00 mov $0x8003,%esp 100016: bd 00 00 00 00 mov $0x0,%ebp 10001b: 83 e4 f0 and $0xfffffff0,%esp 10001e: 89 1d 00 a0 10 00 mov %ebx,0x10a000 100024: e8 03 00 00 00 call 10002c <kernel_main> 00100029 <stop>: 100029: f4 hlt 10002a: eb fd jmp 100029 <stop> 0010002c <kernel_main>: 10002c: 55 push %ebp 10002d: 89 e5 mov %esp,%ebp 10002f: 83 ec 08 sub $0x8,%esp
咱們這裏不製做軟盤鏡像,而是使用已經制做好的軟盤鏡像,鏡像名稱lucasOS.img,已經放在github上了。咱們也無需製做GRUB,這個軟盤鏡像已經包含了GRUB.咱們要作的是把內核文件kernel拷貝到軟盤鏡像lucasOS.img中。
1. 獲取lucasOS.img軟盤鏡像。lucasOS目錄下的lucasOS.img就是咱們要的軟盤鏡像。
$ git clone https://github.com/lucasysfeng/lucasOS.git $cd lucasOS/code/chapter2
2. 建立掛載點。
mkdir lucasOS
3. 掛載軟盤鏡像。注意把lucasOS.img改成你的lucasOS.img所在路徑。
sudo mount lucasOS.img lucasOS
4. 把內核文件拷貝到軟盤鏡像中。注意把kernel改成你的kernel所在路徑。
sudo cp kernel lucasOS/kernel
5. 卸載軟盤鏡像。
sudo umount mnt/lucasOS
上面步驟可寫成Makefile(your-rootpasswd改成你的sudo密碼)以下:
CC = gcc ASM = nasm LD = ld # 這裏掛載點採用相對路徑,即當前目錄下的lucasOS MOUNT_POINT = lucasOS CC_FLAGS = -c -Wall -m32 -ggdb -gstabs+ -nostdinc -fno-builtin -fno-stack-protector LD_FLAGS = -T link.ld -m elf_i386 -nostdlib all: boot.o kernel.o link update_kernel boot.o: boot.asm @echo '編譯boot, 生成GRUB須要的信息..' $(ASM) -f elf boot.asm kernel.o: kernel.c @echo '編譯kernel..' $(CC) $(CC_FLAGS) kernel.c -o kernel.o .PHONY: kernel link: @echo '連接boot和kernel, 生成最終kernel..' $(LD) $(LD_FLAGS) boot.o kernel.o -o kernel .PHONY: update_kernel update_kernel: @echo '將kernel拷貝到軟盤鏡像..' @if [ ! -d $(MOUNT_POINT) ]; then mkdir $(MOUNT_POINT); fi # 掛載點不存在則建立 echo "your-rootpasswd" | sudo -S mount lucasOS.img $(MOUNT_POINT) sudo cp kernel $(MOUNT_POINT)/kernel sleep 1 sudo umount $(MOUNT_POINT) .PHONY: clean clean: rm *.o kernel
用bochs,bochsrc配置以下(須要根據bochs安裝路徑改變):
############################################################### # Configuration file for Bochs ############################################################### # how much memory the emulated machine will have megs: 32 # filename of ROM images romimage: file=/opt/local/share/bochs/BIOS-bochs-latest #/opt/share/bochs/BIOS-bochs-latest vgaromimage: file=/opt/local/share/bochs/VGABIOS-lgpl-latest #/usr/share/vgabios/vgabios.bin # what disk images will be used floppya: 1_44=lucasOS.img, status=inserted # choose the boot disk. boot: floppy # where do we send log messages? # log: bochsout.txt # disable the mouse mouse: enabled=0 # enable key mapping, using US layout as default. # keyboard_mapping: enabled=1, map=/opt/local/share/bochs/keymaps/x11-pc-us.map keyboard: keymap=/opt/local/share/bochs/keymaps/x11-pc-us.map
也可用vbox來啓動:
建立的虛擬機lucasOS須要添加軟驅控制器以下圖:
而後添加虛擬軟盤選中lucasOS.img,這樣就能夠啓動了,而且有GRUB效果:
本系列GitHub地址 https://github.com/lucasysfeng/lucasOS,用下面命令獲取代碼:
git clone https://github.com/lucasysfeng/lucasOS.git
本講的代碼是code/chapter2,筆者已經將上面的命令集成到Makefile中了,讀者只需進入目錄,按ReadMe.txt說明執行便可,有問題請留言。
相似項目hurlex
git clone https://github.com/hurley25/hurlex-doc.git
下載後Makefile修改以下,bochsrc如前面
#!Makefile # # -------------------------------------------------------- # # hurlex 這個小內核的 Makefile # 默認使用的C語言編譯器是 GCC、彙編語言編譯器是 nasm # # -------------------------------------------------------- # # patsubst 處理全部在 C_SOURCES 字列中的字(一列文件名),若是它的 結尾是 '.c',就用 '.o' 把 '.c' 取代 C_SOURCES = $(shell find . -name "*.c") C_OBJECTS = $(patsubst %.c, %.o, $(C_SOURCES)) S_SOURCES = $(shell find . -name "*.s") S_OBJECTS = $(patsubst %.s, %.o, $(S_SOURCES)) CC = gcc LD = ld ASM = nasm MOUNT_POINT = ../kernel C_FLAGS = -c -Wall -m32 -ggdb -gstabs+ -nostdinc -fno-builtin -fno-stack-protector -I include LD_FLAGS = -T scripts/kernel.ld -m elf_i386 -nostdlib ASM_FLAGS = -f elf -g -F stabs all: $(S_OBJECTS) $(C_OBJECTS) link update_image # The automatic variable `$<' is just the first prerequisite .c.o: @echo 編譯代碼文件 $< ... $(CC) $(C_FLAGS) $< -o $@ .s.o: @echo 編譯彙編文件 $< ... $(ASM) $(ASM_FLAGS) $< link: @echo 連接內核文件... $(LD) $(LD_FLAGS) $(S_OBJECTS) $(C_OBJECTS) -o hx_kernel .PHONY:clean clean: $(RM) $(S_OBJECTS) $(C_OBJECTS) hx_kernel .PHONY:update_image update_image: @echo '將kernel拷貝到軟盤鏡像..' @if [ ! -d $(MOUNT_POINT) ]; then mkdir $(MOUNT_POINT); fi # 掛載點不存在則建立 echo "wxwpxh" | sudo -S mount floppy.img $(MOUNT_POINT) sudo cp hx_kernel $(MOUNT_POINT)/hx_kernel sleep 1 sudo umount $(MOUNT_POINT) .PHONY:mount_image mount_image: sudo mount floppy.img $(MOUNT_POINT) .PHONY:umount_image umount_image: sudo umount $(MOUNT_POINT) .PHONY:qemu qemu: qemu -fda floppy.img -boot a .PHONY:bochs bochs: bochs -f scripts/bochsrc.txt .PHONY:debug debug: qemu -S -s -fda floppy.img -boot a & sleep 1 cgdb -x scripts/gdbinit
1. http://www.jamesmolloy.co.uk/tutorial_html/2.-Genesis.html