跟我一起写操作系统(二)——史上最简单的内核
上一講地址:http://www.cnblogs.com/lucasysfeng/p/4846119.html
項目地址:https://github.com/lucasysfeng/lucasOS
上一講我們介紹了計算機的啟動流程,并給出了一份簡單的主引導記錄代碼,此份代碼僅僅是顯示幾個字符,并沒有做它本應該做的事--啟動內核。本講我們首先看下內核是如何被啟動的,然后寫一個簡單的內核,用已經實現的主引導記錄配合GRUB啟動它。
如何啟動內核
前一講我們說到,計算機讀取"主引導記錄"前面446字節的機器碼之后,會運行事先安裝的“啟動管理器”bootloader,由用戶選擇啟動哪個內核,之后就會載入內核,將控制權交給內核。GNU GRUB(GRand Unified Bootloader)就是一種bootloader,滿足多重引導規范(The Multiboot Specification),GRUB可選擇操作系統分區上的不同內核,下圖就是GRUB的圖形界面:
圖 GRUB界面
能夠被GRUB啟動的內核需要滿足兩個的條件:
(1) 內核的前8K字節內必須要包含多重引導規范的頭信息(Multiboot Header);
(2) 內核要加載在內存地址的1MB以上。
那么Multiboot Header是什么樣子的呢?它必須包含4字節對齊的3個域(還有其他非必須域,我們不討論),如下:
魔數域(magic):標志頭的魔數,必須等于 0x1BADB002。。標志域(flag):是否需要引導程序支持某些特性,我們不關心這些特性,這個標志置為0。
校驗域(checksum):校驗等式是否成立(magic + flags + checksum = 0)
? 本文不討論GRUB的實現,我們會用前人已經寫好的GRUB(筆者會給出),我們要做的是完成符合GRUB啟動規范的內核。為了完成這個內核,我們需要寫少量的匯編用來在內核中加入Multiboot Header,然后用C語言寫內核入口,最后將匯編目標代碼和C語言目標代碼鏈接起來生成真正的內核。下面就讓我們一步步地完成這些吧!
?
第一步 匯編入口
1. 匯編代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | ; 文件名 boot.asm ; Copyright: www.cnblogs.com/lucasysfeng 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下一節實現。
2. 編譯生成目標文件boot.o
# nasm -f elf boot.asm -o boot.o
運行上面命令后會生成目標文件boot.o,-f ?elf的意思是生成ELF格式的目標代碼。
?
第二步 內核入口
? 1. 內核代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /**************************************************** # Copyright(c) www.cnblogs.com/lucasysfeng, all rights reserved # File??????? : kernel.c # 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 kernel!"; ????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代碼的其余部分相信讀者都能看得懂,我就不過多解釋了。?
2. 編譯生成目標文件kernel.o
# 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,筆者的另一篇文章《鏈接到底干了什么》可以參考):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /*************************** * 文件名: 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(大小端問題,反向存儲)
?
第四步 將內核拷貝到軟盤鏡像
我們這里不制作軟盤鏡像,而是使用已經制作好的軟盤鏡像,鏡像名稱lucasOS.img,已經放在github上了。我們也無需制作GRUB,這個軟盤鏡像已經包含了GRUB.我們要做的是把內核文件kernel拷貝到軟盤鏡像lucasOS.img中。
1. 獲取lucasOS.img軟盤鏡像。
lucasOS目錄下的lucasOS.img就是我們要的軟盤鏡像。
# git clone https://github.com/lucasysfeng/lucasOS.git
2. 創建掛載點。
# sudo mkdir /mnt/lucasOS
3. 掛載軟盤鏡像。
注意把lucasOS.img改為你的lucasOS.img所在路徑。
# sudo mount lucasOS.img /mnt/lucasOS ? ??
4. 把內核文件拷貝到軟盤鏡像中。
注意把kernel改為你的kernel所在路徑。
# sudo cp kernel /mnt/lucasOS/kernel ? ? ? ? ?
? 5. 卸載軟盤鏡像。
#?sudo umount /mnt/lucasOS
?
第五步 啟動內核
? 上一講我們用軟盤鏡像啟動了一個空的虛擬機,下面用同樣地操作啟動虛擬機,要記得把軟盤鏡像lucasOS.img從ubuntu拷貝到windows下。這里我們使用VMware創建虛擬機,當然也可以使用其他軟件創建虛擬機。
1. 創建空的虛擬機,去掉開機從CD/DVD啟動選項。
2. 網絡選擇host-only模式。
3. 選擇從軟盤驅動,路徑選擇已經拷貝到windows下的鏡像lucasOS.img.
4. 開啟虛擬機電源,看到如下的畫面(約停留2s),恭喜你,GRUB成功了!
5. GRUB啟動成功后,會自動載入內核,出現下面畫面,恭喜你,載入內核成功!
好了,至此,我們完成了內核的啟動流程,下一講我們開始內核之旅!
?
代碼獲取
本系列GitHub地址?https://github.com/lucasysfeng/lucasOS,用下面命令獲取代碼:
#?git clone https://github.com/lucasysfeng/lucasOS.git
本講的代碼是code/chapter2,筆者已經將上面的命令集成到Makefile中了,讀者只需進入目錄,按ReadMe.txt說明執行即可,有問題請留言。
?
參考
?1.?http://www.jamesmolloy.co.uk/tutorial_html/2.-Genesis.html
總結
以上是生活随笔為你收集整理的跟我一起写操作系统(二)——史上最简单的内核的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自己动手实现操作系统引导程序(OS bo
- 下一篇: 8086汇编学习笔记10-端口