細(xì)!手把手教你如何制作一個(gè)微型內(nèi)核
前言
在看《linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》的過(guò)程中發(fā)現(xiàn)只看書(shū)對(duì)于學(xué)習(xí)如何設(shè)計(jì)一個(gè)真正的內(nèi)核太勉強(qiáng)了,還是要實(shí)踐下才能真正的了解一個(gè)內(nèi)核是怎么設(shè)計(jì)的,因此在GitHub上找了兩個(gè)極簡(jiǎn)的內(nèi)核(與真正的內(nèi)核相比這兩個(gè)內(nèi)核代碼少的可憐,更像內(nèi)核組件)為例說(shuō)明如何設(shè)計(jì)簡(jiǎn)單的Linux內(nèi)核。閱讀本文需要有一定的匯編語(yǔ)言和C語(yǔ)言功底。
基礎(chǔ)
首先我們來(lái)先了解下Linux內(nèi)核是什么,有什么作用。我們知道操作系統(tǒng)是一個(gè)計(jì)算機(jī)中最重要的部分,用戶需要操作系統(tǒng)來(lái)運(yùn)行各種應(yīng)用進(jìn)程,如果說(shuō)用戶使用的應(yīng)用程序是通過(guò)操作系統(tǒng)間接調(diào)用計(jì)算機(jī)的部分資源來(lái)運(yùn)行的,那么操作系統(tǒng)就相當(dāng)于計(jì)算機(jī)資源的管理器,操作系統(tǒng)可以直接調(diào)用計(jì)算機(jī)的各種資源。操作系統(tǒng)包括一些基本組件,如文本編輯器、編輯器、與用戶交互的程序、內(nèi)核等,而內(nèi)核作為操作系統(tǒng)的基礎(chǔ)核心,在操作系統(tǒng)中是最重要也是最基礎(chǔ)的部分,它是操作系統(tǒng)中最常用的基本模塊,直接與硬件交互,充當(dāng)?shù)讓域?qū)動(dòng)程序,對(duì)系統(tǒng)中的各種設(shè)備和組件進(jìn)行尋址,用于管理系統(tǒng)資源,比如對(duì)進(jìn)程、文件系統(tǒng)、同步、內(nèi)存、網(wǎng)絡(luò)協(xié)議等的操作和權(quán)限控制,通過(guò)內(nèi)核可以將計(jì)算機(jī)的共享資源(CPU,內(nèi)存等)分配給各個(gè)系統(tǒng)進(jìn)程;內(nèi)核還提供了一組面向系統(tǒng)的命令,應(yīng)用程序調(diào)用系統(tǒng)調(diào)用就像C語(yǔ)言中調(diào)用普通函數(shù)。用內(nèi)核和應(yīng)用程序做一個(gè)比較的話,從最外層到最里層就是:用戶應(yīng)用程序>UNIX命令和庫(kù)>系統(tǒng)調(diào)用接口>內(nèi)核>硬件。
再了解下Linux內(nèi)核組成,下面是一張完整的LInux內(nèi)核運(yùn)行原理圖,子系統(tǒng)中的所有函數(shù)有400多個(gè),互相使用連線交互:

下圖是Linux內(nèi)核的體系結(jié)構(gòu):
Linux內(nèi)核主要包含:系統(tǒng)調(diào)用接口SCI、進(jìn)程管理、內(nèi)存管理、虛擬文件系統(tǒng)、網(wǎng)絡(luò)協(xié)議棧、設(shè)備驅(qū)動(dòng)、硬件架構(gòu)。
1、系統(tǒng)調(diào)用接口
SCI為用戶提供了一組系統(tǒng)調(diào)用函數(shù)作為用戶態(tài)與內(nèi)核態(tài)之間的橋梁,是一個(gè)函數(shù)調(diào)用多路復(fù)用和多路分解服務(wù),同時(shí)該接口還依賴于內(nèi)核的體系結(jié)構(gòu)。
2、進(jìn)程管理
Linux中實(shí)際上并沒(méi)有區(qū)分進(jìn)程和線程這兩個(gè)概念,Linux中把線程稱(chēng)為輕量級(jí)進(jìn)程,沒(méi)有獨(dú)立的地址空間,一個(gè)進(jìn)程下的所有線程共享地址空間、文件系統(tǒng)資源、文件描述符、信號(hào)處理程序等,每個(gè)線程都有私有數(shù)據(jù)、堆棧信息等,內(nèi)核通過(guò)SCI提供的fork、exec API來(lái)創(chuàng)建進(jìn)程,wait API來(lái)暫停進(jìn)程,kill、exit API來(lái)結(jié)束進(jìn)程,并通過(guò)signal或POSIX機(jī)制在進(jìn)程間進(jìn)行通信和同步。使用進(jìn)程描述符用來(lái)記錄進(jìn)程的狀態(tài)。最后通過(guò)調(diào)度系統(tǒng)完成進(jìn)程的執(zhí)行。
3、內(nèi)存管理
內(nèi)核通過(guò)內(nèi)存管理控制多個(gè)進(jìn)程來(lái)安全的共用共享內(nèi)存。內(nèi)存以內(nèi)存頁(yè)為基本單位進(jìn)行管理,從虛擬內(nèi)存角度來(lái)看,頁(yè)是Linux中的內(nèi)存最小單位,大多數(shù)32位系統(tǒng)支持4kb的頁(yè),64位系統(tǒng)支持8kb的頁(yè)。Linux 提供了對(duì) 4KB 緩沖區(qū)的抽象,如slab分配器,使用這種內(nèi)存管理模式時(shí)使用4KB緩沖區(qū)為基數(shù),從中分配結(jié)構(gòu)并跟蹤內(nèi)存頁(yè)使用情況,知道了頁(yè)的存儲(chǔ)情況后根據(jù)系統(tǒng)需求動(dòng)態(tài)調(diào)整內(nèi)存使用。為了支持多個(gè)用戶使用內(nèi)存,有時(shí)會(huì)出現(xiàn)可用內(nèi)存被消耗光的情況,這時(shí)頁(yè)面可以移出內(nèi)存并放入磁盤(pán)中,這個(gè)過(guò)程稱(chēng)為交換,因?yàn)轫?yè)面會(huì)被從內(nèi)存交換到硬盤(pán)上,Linux系統(tǒng)中被用于交換的分區(qū)叫swap分區(qū),windows系統(tǒng)下叫做虛擬內(nèi)存。
4、虛擬文件系統(tǒng)
VFS是Linux內(nèi)核和I/O設(shè)備之間封裝的的一層訪問(wèn)接口,通過(guò)該接口Linux內(nèi)核可以使用同一方式訪問(wèn)各種I/O設(shè)備,應(yīng)用程序可以使用同一接口完成不同介質(zhì)上不同文件系統(tǒng)的數(shù)據(jù)讀寫(xiě)操作。VFS之所以能夠連接各種文件系統(tǒng),是因?yàn)樗x了所有文件系統(tǒng)都支持的、基本的、概念上的接口和數(shù)據(jù)結(jié)構(gòu)。VFS采用面向?qū)ο蟮脑O(shè)計(jì)思路,使用結(jié)構(gòu)體方式實(shí)現(xiàn)既包含數(shù)據(jù)又包含操作數(shù)據(jù)的函數(shù)指針。VFS的4個(gè)主要對(duì)象類(lèi)型為:超級(jí)塊對(duì)象、索引節(jié)點(diǎn)對(duì)象、目錄項(xiàng)對(duì)象、文件對(duì)象。因?yàn)樘摂M文件系統(tǒng)本身就是Linux內(nèi)核的一部分,屬于軟件,所以不需要硬件的支持。
5、網(wǎng)絡(luò)協(xié)議棧
這部分保證了網(wǎng)絡(luò)協(xié)議的實(shí)現(xiàn),在設(shè)計(jì)上遵循模擬協(xié)議本身的分層體系結(jié)構(gòu)。具體可以參考這里。
6、設(shè)備驅(qū)動(dòng)
驅(qū)動(dòng)程序一般指設(shè)備驅(qū)動(dòng)程序(Device Driver),是一種可以使計(jì)算機(jī)和設(shè)備通信的特殊程序,相當(dāng)于硬件的接口,操作系統(tǒng)通過(guò)這個(gè)接口可以控制硬件設(shè)備的工作。內(nèi)核中的大量源代碼都在驅(qū)動(dòng)中實(shí)現(xiàn),用來(lái)控制特定的硬件設(shè)備。Linux 源碼樹(shù)提供了一個(gè)驅(qū)動(dòng)程序子目錄,這個(gè)目錄又進(jìn)一步劃分為各種支持設(shè)備,例如Bluetooth、I2C、serial等。
7、體系結(jié)構(gòu)相關(guān)代碼
Linux很大程度上獨(dú)立于所運(yùn)行的體系結(jié)構(gòu),但一小部分依賴體系結(jié)構(gòu)正常操作并實(shí)現(xiàn)更高效率。linux的arch目錄中定義了內(nèi)核中依賴于體系結(jié)構(gòu)的部分,其中包含各種特定于體系結(jié)構(gòu)的子目錄(共同組成了BSP)。每個(gè)體系結(jié)構(gòu)子目錄中又包含了很多其他子目錄,每個(gè)子目錄都關(guān)注內(nèi)核中的一個(gè)特定方面,例如引導(dǎo)、內(nèi)核、內(nèi)存管理等。
Linux采用宏內(nèi)核架構(gòu),將所有函數(shù)放到一個(gè)大的文件中,這樣可以直接調(diào)用函數(shù)減少了內(nèi)核間的通信開(kāi)銷(xiāo),但也導(dǎo)致一個(gè)函數(shù)出錯(cuò)會(huì)影響后面的所有函數(shù),并且會(huì)影響內(nèi)核的可維護(hù)性,為了提高內(nèi)核的擴(kuò)展性和可維護(hù)性,Linux內(nèi)核采用模塊化設(shè)計(jì),支持添加或刪除軟件組件,添加或刪除的組件被稱(chēng)為可加載內(nèi)核模塊(Loadable Kernel Modules,LKM),可以被單獨(dú)編譯但不能脫離內(nèi)核單獨(dú)使用,使用時(shí)被鏈接到內(nèi)核在內(nèi)核空間中運(yùn)行??杉虞d內(nèi)核模塊包括驅(qū)動(dòng)設(shè)備、內(nèi)核擴(kuò)展模塊,可以在引導(dǎo)時(shí)根據(jù)需要或在任何時(shí)候由用戶插入,用來(lái)實(shí)現(xiàn)文件系統(tǒng)、添加設(shè)備、系統(tǒng)調(diào)用或其他內(nèi)核上層的功能。
介紹到這里大家應(yīng)該對(duì)Linux內(nèi)核的組成有了一個(gè)大致了解,當(dāng)然完整的內(nèi)核還包括更多具體細(xì)節(jié),比如系統(tǒng)調(diào)用、內(nèi)核數(shù)據(jù)結(jié)構(gòu)、調(diào)度算法,中斷和同步等,但由于本文只是說(shuō)明如何制作一個(gè)微型內(nèi)核,所以知道了以上知識(shí)后已經(jīng)足以下面的學(xué)習(xí)了。對(duì)完整的Linux內(nèi)核具體實(shí)現(xiàn)細(xì)節(jié)有興趣的同學(xué)可以閱讀《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》并下載Linux源碼進(jìn)行學(xué)習(xí)。
實(shí)現(xiàn)
該部分通過(guò)GitHub上的兩個(gè)極簡(jiǎn)內(nèi)核來(lái)學(xué)習(xí)編寫(xiě)簡(jiǎn)單的內(nèi)核。
例一
本例的內(nèi)核實(shí)現(xiàn)在屏幕上打印“my first kernel”并掛起。需要一臺(tái)裝有NASM編譯器的虛擬機(jī)(筆者使用Ubuntu20.04用于演示)。
演示前我們需要知道一臺(tái)計(jì)算機(jī)從接入電源啟動(dòng)到用戶應(yīng)用程序的過(guò)程發(fā)生了什么。
第一步計(jì)算機(jī)通電后主板會(huì)得到啟動(dòng)電源的信號(hào)檢查設(shè)備是否正常,并且使用cpu清除寄存器上的所有數(shù)據(jù),并為寄存器設(shè)置預(yù)定義的值,重置向量(CPU重置后第一條執(zhí)行指令的地址)被設(shè)置為0xfffffff0(80386及以后的CPU一般是該地址),0xfffffff0=0xffff0000(CS寄存器值,基地址)+0xfff0(EIP寄存器值,地址偏移),然后0xfffffff0會(huì)有類(lèi)似jne跳轉(zhuǎn)指令,通過(guò)跳轉(zhuǎn)指令去BIOS。第二步BIOS初始化完成后去讀取MBR扇區(qū)(啟動(dòng)盤(pán)上的第一個(gè)扇區(qū))將該扇區(qū)的內(nèi)容(包含主引導(dǎo)程序)復(fù)制到0x7c00地址物理內(nèi)存中,然后通過(guò)主引導(dǎo)程序初始化硬件設(shè)備為調(diào)用內(nèi)核做準(zhǔn)備,同時(shí)主引導(dǎo)程序還負(fù)責(zé)加載執(zhí)行GRUB(引導(dǎo)裝載程序,尋找內(nèi)核并將內(nèi)核加載到內(nèi)存中運(yùn)行)。第三步GRUB解壓縮內(nèi)核完成并將解壓完的內(nèi)核加載進(jìn)內(nèi)存后,會(huì)執(zhí)行調(diào)用start_kernel()函數(shù)啟動(dòng)一系列初始化函數(shù)并初始化各種設(shè)備,這時(shí)內(nèi)核終于完成了加載。第四步內(nèi)核加載后運(yùn)行的第一個(gè)運(yùn)行程序是/sbin/init,它去讀取/etc/inittab文件,并根據(jù)該文件進(jìn)行系統(tǒng)初始化,等系統(tǒng)初始化完成并裝載完內(nèi)核模塊后用戶這時(shí)才會(huì)進(jìn)入登錄頁(yè)面。
下面我們通過(guò)第一個(gè)例子模仿一個(gè)內(nèi)核是如何運(yùn)行的。真正的內(nèi)核在引導(dǎo)加載程序啟動(dòng)內(nèi)核代碼后,跳轉(zhuǎn)到內(nèi)核入口點(diǎn)時(shí)就初始化內(nèi)核設(shè)置堆棧、bss段等,再去執(zhí)行內(nèi)核中的其他函數(shù)(Linux內(nèi)核使用宏內(nèi)核架構(gòu),里邊包含了所有必需的內(nèi)核函數(shù)),這是一個(gè)復(fù)雜且嚴(yán)謹(jǐn)?shù)倪^(guò)程,但本文的例子中內(nèi)核代碼只有一個(gè)函數(shù)(這個(gè)例子只能演示內(nèi)核是怎么啟動(dòng)的)。
首先因?yàn)楦呒?jí)語(yǔ)言如C語(yǔ)言無(wú)法直接與計(jì)算機(jī)交互(從切換到實(shí)模式到保護(hù)模式只能使用匯編完成),所以我們這里使用匯編代碼編寫(xiě)一個(gè)用于啟動(dòng)內(nèi)核代碼的小程序:
可以看到在程序代碼后面做了注釋?zhuān)厦娴某绦蛟谠O(shè)置好數(shù)據(jù)、變量等后去調(diào)用了kmain。然后再去看一下kmain:

這里的kmain就是我們的內(nèi)核,kmain沒(méi)有調(diào)用其他函數(shù),它的作用也非常簡(jiǎn)單,通過(guò)vidptr指針指向0xb8000地址(受保護(hù)模式下顯存開(kāi)始地址),第一次while循環(huán)寫(xiě)入0x07屬性(0x07屬性將顯示的字符串以淺灰色打印,屬性可變)的空白字符清空屏幕,第二次while循環(huán)將具有0x07屬性的“my first kernel”字符串寫(xiě)入顯存在屏幕上打印。
除了上面的兩個(gè)文件還需要一個(gè)鏈接程序腳本link.ld,將該腳本作為參數(shù)傳遞給我們的鏈接程序。link.ld鏈接程序腳本為:
上面的link.ld文件將輸出可執(zhí)行文件的輸出格式設(shè)置為32位可執(zhí)行文件,ENTRY(start)指定符號(hào)名稱(chēng),將start作為可執(zhí)行文件起點(diǎn),位置計(jì)數(shù)器.設(shè)置為0x100000,內(nèi)核代碼從該處開(kāi)始執(zhí)行(只是演示的話可以修改)。.text:{*(.text)}、.data:{*(.data)}、.bss:{*(.bss)}使鏈接器將目標(biāo)文件的所有text部分合并到可執(zhí)行文件的text部分,data部分合并到可執(zhí)行文件的data部分,bss部分合并到可執(zhí)行文件的bss部分。等鏈接器放置文本輸出部分后,位置計(jì)數(shù)器的值將變?yōu)?x1000000+文本輸出部分的大小。
有了上面的3個(gè)文件還需要使用GRUB在符合Multiboot規(guī)范(多重引導(dǎo)規(guī)范)的情況下加載內(nèi)核,Multiboot規(guī)范規(guī)定內(nèi)核在前8kb中有一個(gè)頭文件,所以下面的kernel.asm的section .text中就多了下面4行代碼,align指定符號(hào)對(duì)齊不重要,magic字段設(shè)置為0x1BADB002用于識(shí)別標(biāo)題,flag字段也不重要設(shè)置為0即可,最后的校驗(yàn)和字段添加到magic字段和flag字段時(shí)必須為0,最后kernel.asm變成了:

以上文件準(zhǔn)備好后使用nasm組裝kernel.asm到一個(gè)目標(biāo)文件,再編譯kernel.c為另一個(gè)目標(biāo)文件,最后使用link.ld將編譯好的兩個(gè)目標(biāo)文件鏈接到一起就完成了:

可以配置GRUB將虛擬機(jī)內(nèi)核設(shè)置為上面的內(nèi)核,為了方便我們這里只用qemu模擬器看看效果:
例二
本例與例一相比進(jìn)行了擴(kuò)展,本例內(nèi)核使用I/O端口(從內(nèi)核角度看I/O端口就是I/O總線上的特定內(nèi)存地址)與I/O設(shè)備(輸入輸出設(shè)備)進(jìn)行通信,I/O端口通過(guò)配置控制寄存器(一個(gè)處理器寄存器,可以控制CPU和其他數(shù)字設(shè)備)和讀取數(shù)據(jù)寄存器來(lái)操控外部設(shè)備接收a-z、0-9和部分符號(hào)并打印到屏幕上。環(huán)境與例一相同。
和例一相同,本例也需要一個(gè)kernel.asm啟動(dòng)內(nèi)核,下面去掉第6到10行就是編譯前的kernel.asm文件:

上面代碼都做了注釋?zhuān)@里簡(jiǎn)單說(shuō)一下和例一的kernel.asm不同的地方。keyboard.handler跳轉(zhuǎn)keyboard_handler_main,負(fù)責(zé)處理與鍵盤(pán)相關(guān)的input_handler,包括鍵碼轉(zhuǎn)換和輸出。read_port負(fù)責(zé)讀取I/O端口,使用in指令將dx寄存器指定I/O端口號(hào),將讀取數(shù)據(jù)傳入al寄存器中,write_port負(fù)責(zé)寫(xiě)入I/O端口,使用out指令將al寄存器中的數(shù)據(jù)寫(xiě)入dx寄存器指定的I/O端口中。load_idt處理與中斷描述符表IDT有關(guān)的東西。
keyboard_map.h:

上面是鍵盤(pán)映射表,后面keyboard_handler_main()通過(guò)它把掃描代碼轉(zhuǎn)換為ASCII碼。
kernel.c:




上面的IDT_entry,idt_init用于創(chuàng)建IDT讓用戶讀取I/O端口。中斷描述符表IDT是一個(gè)系統(tǒng)表,通過(guò)IDT可以在CPU中斷后找到中斷的進(jìn)程繼續(xù)去執(zhí)行。這里說(shuō)一下中斷,早期的計(jì)算機(jī)如果想知道設(shè)備執(zhí)行哪個(gè)事件需要對(duì)設(shè)備狀態(tài)進(jìn)行監(jiān)測(cè),早期用于監(jiān)測(cè)的方法叫做輪詢,但因?yàn)槌绦蛑荒艽袌?zhí)行,計(jì)算機(jī)的資源利用率低,所以為了提高資源利用率,并且方便用戶控制,于是引入了中斷機(jī)制,中斷保證了多程序并發(fā)執(zhí)行。簡(jiǎn)單的講,內(nèi)核使用中斷一方面可以讓所有的進(jìn)程都有一定的資源可以使用不至于進(jìn)程“餓死”,另一方面用戶還能通過(guò)觸發(fā)中斷使CPU可以隨時(shí)停止當(dāng)前運(yùn)行的進(jìn)程進(jìn)入核心態(tài),內(nèi)核再對(duì)不同的中斷號(hào)去做不同的處理,完成用戶自己可以操控計(jì)算機(jī)資源的目的。這里我們需要通過(guò)中斷監(jiān)測(cè)鍵盤(pán)的輸入并作出處理,當(dāng)檢測(cè)到鍵盤(pán)有輸入時(shí),鍵盤(pán)會(huì)通過(guò)IRQ(中斷向量)發(fā)送信號(hào)給給PIC(可編程中斷控制器,它能接收中斷并根據(jù)中斷對(duì)硬件作出處理),PIC在初始化期間就儲(chǔ)存了一個(gè)偏移量,偏移量和輸入線號(hào)相加就是中斷號(hào),然后處理器通過(guò)查詢IDT找到中斷號(hào)相對(duì)應(yīng)的中斷地址程序入口,運(yùn)行該地址的代碼對(duì)鍵盤(pán)輸入進(jìn)行處理。上面的兩個(gè)函數(shù)IDT_entry是IDT的具體實(shí)現(xiàn)。idt_init會(huì)先填充鍵盤(pán)中斷的IDT條目,然后設(shè)置兩個(gè)PIC,PIC1使用0x20作為命令端口,0x21作為數(shù)據(jù)端口,PIC2使用0xA0作為命令端口,0xA1作為數(shù)據(jù)端口。再通過(guò)初始化命令I(lǐng)CW1傳給PIC3個(gè)數(shù)據(jù)端口的初始化字,ICW2寫(xiě)入PIC的數(shù)據(jù)端口,設(shè)置PIC的偏移量,與輸入線的數(shù)據(jù)相加獲得中斷號(hào)。ICW3告訴PIC如何做主/從設(shè)備,這里不需要設(shè)置PIC間互相輸入輸出,也就不需要級(jí)聯(lián),將所有位設(shè)置為0即可。ICW4給出環(huán)境附加信息,設(shè)置低位使PIC為8086模式。
然后我們將鍵盤(pán)的中斷號(hào)映射到鍵盤(pán)處理函數(shù)的地址,這需要知道鍵盤(pán)處理函數(shù)的地址和IDT哪個(gè)中斷號(hào)映射,上面的代碼中PIC1的偏移量初始化為了0x20,加上1就是中斷號(hào)0x21,鍵盤(pán)處理函數(shù)地址需要映射到0x21中斷,映射需要填充IDT中的0x21中斷,我們不光設(shè)置了類(lèi)型捕捉中斷,還給出了內(nèi)核代碼偏移量0x08,中斷門(mén)0x8e,用0填充了GDT(GRUB建立的)條目的其他位完成了填充與鍵盤(pán)中斷對(duì)應(yīng)的IDT。我們會(huì)把中斷映射到keyboard_handler函數(shù)中,并且上面的kernel.asm中寫(xiě)入了該函數(shù)。最后完成上面的任務(wù)后我們通過(guò)load_idt()傳遞指向描述IDT描述符結(jié)構(gòu)的指針即idt_ptr給lidt指令告訴CPU IDT的位置,它還因?yàn)閟ti指令會(huì)啟動(dòng)中斷。
還有一點(diǎn)前面沒(méi)有說(shuō),PIC中有IMR(中斷屏蔽寄存器),它在PIC的IRQ線上,通過(guò)設(shè)置IMR的值可以禁用或啟動(dòng)IRQ線,前面我們將IMR第n位的值設(shè)置為1禁用了第n跳IRQ線,現(xiàn)在IDT建立并加載完成后我們使用kb_init啟用IRQ線。
前面我們通過(guò)IDT 0x21中斷映射鍵盤(pán)中斷到keyboard_handler函數(shù)中了,keyboard_handler()會(huì)調(diào)用keyboard_handler_main()處理鍵盤(pán)的輸入。keyboard_handler_main()會(huì)發(fā)送EOI信號(hào)給PIC使其對(duì)中斷作出處理,讀取0x64端口確定緩沖區(qū)狀態(tài),確定緩沖區(qū)是否為空,讀取0x60端口讀取緩沖區(qū)內(nèi)容,0x60端口會(huì)通過(guò)前面說(shuō)過(guò)的keyboard_map_.h確定按鍵的鍵碼判斷字符,并將字符打印到屏幕上。
最后編譯并鏈接kernel.c和kernel.asm兩個(gè)可執(zhí)行文件:

使用qemu模擬器看一下效果:
結(jié)語(yǔ)
由于筆者是第一次接觸內(nèi)核,所以本文寫(xiě)的很粗糙,如果有什么錯(cuò)誤和不足的地方還請(qǐng)大佬斧正、見(jiàn)諒,同時(shí)也很希望有大佬能夠作出指點(diǎn),拜謝。
參考
《linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》
https://github.com/arjun024/mkernel
https://github.com/arjun024/mkeykernel
https://tldp.org/LDP/lkmpg/2.6/html/lkmpg.html
https://zh.wikipedia.org/wiki/%E5%86%85%E6%A0%B8
https://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-1.html
https://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap.html系列文章
https://0xax.gitbooks.io/linux-insides/content/Initialization/系列文章
最后:需要Linux網(wǎng)絡(luò)安全資料的請(qǐng)加微信:gogquick 免費(fèi)限時(shí)獲取