一步步编写操作系统 48 加载内核1
其實,我們等了這一刻好久好久,即使我不說,大家也有這樣的認識,linux內核是用c 語言寫的,咱們肯定也要用c語言。其實...說點傷感情的話,今后的工作只是大部分(99%)都要用c語言來寫,還有一些要用到匯編的地方。大家也不要因此氣餒心灰(其實突然不用匯編還會想它呢,這不是玩笑),我在此過程中一定會盡我所能讓內容簡單易接受。
我們的內核文件是kernel.bin,這個文件是由loader將其從硬盤上讀出并加載到內存中的,到此,接力棒傳到了最后一個選手的手里。也就是說,咱們需要事先把kernel.bin定入硬盤。好久不往虛擬硬盤上寫東西了,甭說是大家,我都有點陌生了呢,不過好在操作很簡單,寫之前讓我們先看看這塊虛擬硬盤上的文件布局吧。
MBR是寫在了硬盤的第0扇區,第1扇區是空著的,原因是個人喜好,其實不空著也行,不過硬盤那么大,何必搞得那么擁擠呢。因此loader是寫在硬盤的第2扇區,由于loader.bin目前的大小是1342字節,占用3個扇區,所以第2~4扇區不能再用啦,從第5扇區起我們可以自由使用。但此時我的強迫癥又發作啦,我這里并沒有接著第5扇區寫,而是選的第9扇區(要是起始為1的話算是第10個扇區)。一是為了loader萬一哪天要擴展,得預留出硬盤空間,二是您可能已經預計到了,隔開點顯得更放心,這純屬是出于個人喜好做出的選擇。
好,既然已經確定了寫入扇區的位置,我們還是要通過dd命令往磁盤上寫,命令如下:
dd if= kernel.bin of=/your_path/hd60M.img bs=512 count=200 seek=9 conv=notrunc回車
seek為9,目的是跨過前9個扇區(第0~8個扇區),我們是在第9個扇區寫入。
count為200,目的是一次往參數of指定的文件中寫入200個扇區。
至于為什么把count設成這么大,原因是這樣的:每次寫完內核后,咱們要往磁盤中同步內核文件,這樣才能驗證內核的正確性。按理說,咱們現在的內核文件不足4扇區,count=4最合適。不過,內核發展越來越大時,每次都要根據實際內核文件大小去改寫count參數,這樣就難免會有忘記修改的情況。之前我就深受其苦,內核文件變大了,而count忘記調整,造成寫入硬盤中的內核文件不完整,所以到后來,程序運行不受控制,以至于調試的時候都調暈啦,看著cpu中跑的指令我完全蒙圈了,根本不是自己寫的?;腥淮笪蛑?#xff0c;我就干脆一步到位,因為我們將來的內核大小不會超過100KB,所以直接把count改為200塊扇區。另外請大家不用擔心,dd命令會自己判斷寫入的數據量,如果參數if指定的文件體積小于count*bs,只按實際文件大小寫入。
不過,估計您也覺得參數太多了,為了方便,我通常是把下面三個命令,編譯、鏈接、再寫入硬盤一起完成,您可以將它們寫成一個腳本,腳本內容如下:
gcc -c -o main.o main.c && ld main.o -Ttext 0xc0001500 -e main -o kernel.bin && dd if= kernel.bin of=/your_path/hd60M.img bs=512 count=200 seek=9 conv=notrunc
好啦,上面命令在回車之后,這樣我們的內核文件就成功寫進磁盤了。
菜配好啦,就等下鍋啦,我們的內核是由loader加載的,所以我們還要去修改下loader.S。
loader.S需要修改兩個地方:
- λ加載內核:需要把內核文件加載到內存緩沖區。
- λ初始化內核:需要在分頁后,將加載進來的elf內核文件安置到相應的虛擬內存地址,然后跳過去執行,從此loader的工作結束。
先說第一個加載內核,這里所說的加載內核只是把內核從硬盤上拷貝到內存中,并不是運行內核代碼。這項工作在開啟分頁前后都可以,不過為了簡單,咱們把它安排在分頁開啟之前加載。
話說內核加載到內存中,得有個加載地址,也就是緩沖區。其實開發經驗少的同學對緩沖區這個概念總是覺得有點“只可意會不可言傳”的意思。借此機會多說兩句。緩沖區,buffer,意味存放物品的地點,也就是用于加工處理中暫存數據的地方。生活中的緩沖區例子有很多,比如水杯是水的緩沖區,水不是直接入口的,總有個中間載體做為中轉,然后才入口。而且,水杯的作用相當于暖瓶或水房的緩存,咱們不是喝一口水就跑到水房接一口水,而是一次接一大杯,回來慢慢喝,這樣就減少了去水房的次數。由此可見,緩沖區,既有存放數據的空間之意,又有提高效率的緩存之意。換在計算機世界里,緩沖區必然也是個能存儲數據的介質,比如咱們這里所說的內存。
好啦,不能扯太遠啦,咱們的緩沖區在設在哪里呢,這不是亂放的,得參考下目前內存中哪個地方還有可用的空間,千萬不能覆蓋了重要數據。也許大家首先想到的是很久之前說到的那個內存布局圖,贊,答對啦,不過,大家不用往前翻看啦,一向體貼的我已經將其重點部分摘到這里啦,大家請看圖
內核被加載到內存后,loader還要通過分析其elf結構將其展開到新的位置,所以說,內核在內存中是有兩份拷貝,一份是elf格式的原文件kernel.bin,另一份是loader解析elf格式的kernel.bin后在內存中生成的內核映像(也就是將程序中的各種段segment復制到內存后的程序體),這個映像才是真正運行的內核。
將來內核肯定是越來越大,為了多預留出生長空間,咱們要將內核文件kernel.bin加載到地址較高的空間,而內核映像要放置到較低的地址。內核文件經過loader解析后就沒用啦,這樣內核映像將來往高地址處擴展時,也可以覆蓋原來的內核文件kernel.bin。所以咱們的結論是,在0x7e00~0x9fbff這片區域的高地址中找一畝地給kernel.bin,這里我擅自做主啦,幫大家選的是0x70000。為什么?沒有為什么,隨意選的,取了個整而已,就是覺得0x70000~0x9fbff有0x2fbff=190KB字節的空間,而我們的內核不超過100KB,夠用就行。
好,萬事俱備啦,代碼走起,請大家過目代碼
147 ; ------------------------- 加載kernel ---------------------- 148 mov eax, KERNEL_START_SECTOR ; kernel.bin所在的扇區號 149 mov ebx, KERNEL_BIN_BASE_ADDR; 從磁盤讀出后,寫入到ebx指定的地址 150 mov ecx, 200 ; 讀入的扇區數 151 152 call rd_disk_m_32 153 154 ; 創建頁目錄及頁表并初始化頁內存位圖 155 call setup_page代碼屬于loader的一部分,它的作用是把內核文件從硬盤上加載到內存中,下面簡要說一下。
第148~149行的KERNEL_START_SECTOR和KERNEL_BIN_BASE_ADDR是在boot/include/boot.inc中定義,其值分別為0x9和0x70000。
第150行的ecx為200,這是讀入的扇區數,這里應該同前面用dd命令往硬盤上寫入內核文件時的參數count保持一致,原因你懂的不解釋。
以上的eax、ebx、ecx是函數rd_disk_m_32的三個參數,是為調用下面的函數做準備。
第152行的函數是rd_disk_m_32,用于從硬盤上讀取文件。它的三個參數已經在上面賦值了。由于目前已經在32位保護模式下,所以相比之前位于mbr中的函數rd_disk_m_16,rd_disk_m_32只是版本由16位變成了32位的,函數實現原理相差無幾,主要體現在里面所用的寄存器變成了32位。所以,就不細說啦,大家一看就明白啦。
接下來的第155行就是開始創建頁表啦,把它放在這是為了讓大家知道代碼是加到了哪里,承上啟下。setup_page函數實現沒變,無須多說。
內核加載到緩沖區中后,現在該說要修改的第二處啦,也就是初始化內核。
總結
以上是生活随笔為你收集整理的一步步编写操作系统 48 加载内核1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: maven 公共模块依赖_「spring
- 下一篇: 一步步编写操作系统 65 标准调用约定s