PS:要轉(zhuǎn)載請(qǐng)注明出處,,本人版權(quán)所有,。
PS: 這個(gè)只是基于《我自己》的理解,
如果和你的原則及想法相沖突,,請(qǐng)諒解,,勿噴。
前置說明
??本文作為本人csdn blog的主站的備份,。(BlogID=102)
環(huán)境說明
- Ubuntu 18.04
- gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
- Bochs 2.6
- As86 version: 0.16.17
前言
??自從我近段時(shí)間開始溫習(xí)一些基礎(chǔ)知識(shí)以來,,其中覺得以前學(xué)的很淺的就是OS原理。為啥這樣說呢,?因?yàn)榫褪菧\,,知道一些瑣碎的知識(shí)。以前我自負(fù)的認(rèn)為OS就是硬件的抽象,,然后把這些硬件資源合理的分配給用戶使用就完了,,因?yàn)槲矣X得合理的整合這些硬件資源是非常'簡(jiǎn)單’的。
??由于我本身對(duì)底層是非常著迷的,。帶著覺得OS很簡(jiǎn)單的想法,,想著去看看LinuxKernel的源碼。在以前,,我對(duì)LinuxKernel的認(rèn)知很膚淺,,就知道一些驅(qū)動(dòng)移植的事情。如果硬要說一件我在LinuxKernel中玩的很深的事情,,那就是自己理解并實(shí)現(xiàn)了一個(gè)類似Anonymous Shared Memory的Linux驅(qū)動(dòng),詳見以下兩篇文章,。
??帶著這樣的想法其實(shí)已經(jīng)很久了,,由于現(xiàn)在的LinuxKernel太大了,對(duì)新手不友好,。我就想著去找一個(gè)老一點(diǎn)的版本內(nèi)核看看,。結(jié)果去網(wǎng)上一找,,就發(fā)現(xiàn)了前人已經(jīng)做了許多許多了,比如這個(gè)之前就有了解的《linux 0.11內(nèi)核完全注釋》,,還比如其他許許多多前人種的'樹’,,看到了許多,最終我決定跟著國(guó)內(nèi)現(xiàn)在比較好和新的資料從'遠(yuǎn)古’開始學(xué)習(xí)它,。它就是《Linux內(nèi)核完全注釋(PDF) v5.0 by 趙炯.pdf》,。它是基于LinuxKernel0.12 講述的,它是我在ubuntu1804上編譯通過LinuxKernel0.12的主要參考和學(xué)習(xí)資料,,同時(shí)也是我在Bochs上運(yùn)行成功的主要參考和學(xué)習(xí)資料,。
??好的多說無益,直接看運(yùn)行效果,。
??說來也慚愧,,利用斷斷續(xù)續(xù)的時(shí)間,我花了約2月,,把LinuxKernel0.12在Ubuntu1804上編譯通過,,并在1804上通過Bochs運(yùn)行成功。而且要命的事情是我其實(shí)只加了一些打印調(diào)試函數(shù),,和根據(jù)實(shí)際的調(diào)試情況修改了一些代碼,,卻花了那么久的時(shí)間,搞得我很不自信了QAQ,。
??我修改好的源碼已經(jīng)開源,,立即想要源碼的請(qǐng)直接去文末兩個(gè)rep clone即可。
??本文主要還是簡(jiǎn)單介紹LinuxKernel從上電到進(jìn)入sh的中間的簡(jiǎn)要流程,。這些流程網(wǎng)上已經(jīng)有很多了,,可能我會(huì)挑選一些我覺得比較重要的來說。
??本文適用于:
- 會(huì)編譯和使用bochs的人,。不會(huì)可以去網(wǎng)上找找,,很多這方面的資料。
- 對(duì)Intel AT&T 匯編有點(diǎn)了解的人,。
- 會(huì)GDB調(diào)試的人,。
- 知道C語言常識(shí)的人。
- 對(duì)LinuxKernel感興趣的人,。
搭環(huán)境
??工欲善其事必先利其器,。本文主要是在Ubuntu1804上編譯生成LinuxKernel,然后用Bochs運(yùn)行我們的內(nèi)核,。
Ubuntu18.04環(huán)境安裝
我們應(yīng)該首先安裝make,gcc,gcc-multilib,bin86,。
- sudo apt install build-essential cmake make gcc-multilib g++-multilib module-assistant bin86
然后進(jìn)入源碼目錄。
更多的詳情信息查看開源的rep,。
編譯兩個(gè)bochs版本備用
??我們首先就得把Linux0.12的運(yùn)行環(huán)境搭建起來,,方便我們調(diào)試,。我們使用的是Bochs2.6 和 GDB遠(yuǎn)程調(diào)試。并編譯出兩個(gè)bochs版本,,一個(gè)是帶本身調(diào)試功能(命名為:bochs),,一個(gè)是和gdb聯(lián)調(diào)(命名為:bochsdbg)。bochs 主要是調(diào)試在init/main()函數(shù)之前的內(nèi)容以及查看更多的x86寄存器,。 bochsdbg主要是調(diào)試進(jìn)入init/main()函數(shù)之后到sh成功執(zhí)行的事情,。
- 通過 ./configure --enable-debugger 生成bochs。
- 通過 ./configure --enable-gdb-stub 生成bochsdbg,。
運(yùn)行我們編譯的內(nèi)核
??通過本文介紹生成的文件是Linux內(nèi)核鏡像,,稍微懂點(diǎn)行的人都知道還差一個(gè)RootFS。這個(gè)文件系統(tǒng)我們?cè)诰W(wǎng)上下載的例如: http:///Linux.old/bochs/linux-0.12-080324.zip ,。本文生成的Linux內(nèi)核鏡像使用的是rootimage-0.12-hd這個(gè)文件系統(tǒng),。
??我建議這里自己配置兩個(gè).bxrc文件,一個(gè)對(duì)應(yīng)bochs,,一個(gè)對(duì)應(yīng)bochsdbg遠(yuǎn)程調(diào)試,。這樣在遇到問題的時(shí)候我們可以很方便的調(diào)試。
LinuxKernel啟動(dòng)簡(jiǎn)介
??本節(jié)簡(jiǎn)述LinuxKernel的啟動(dòng)流程,。根據(jù)我近段時(shí)間的學(xué)習(xí)來看,,這里包含了許多的歷史性的東西,大家不要去細(xì)究為啥是這樣,,很多都是為了兼容,。
??此外在整個(gè)學(xué)習(xí)期間,由于涉及到許多的x86 硬件體系知識(shí),,除了參考上文我說的文檔以外,,還必須參考以下Intel官方文檔:
- Intel? 64 and IA-32 architectures software developer's manual combined volumes 2A, 2B, 2C, and 2D:Instruction set reference, A-Z
- Intel? 64 and IA-32 architectures software developer's manual combined volumes 3A,
3B, 3C, and 3D: System programming guide
- 《Linux內(nèi)核完全注釋(PDF) v5.0 by 趙炯.pdf》 第4章,全篇精華,。
boot/bootsect.S 階段
??當(dāng)我們的計(jì)算機(jī)上電以后,,IntelCPU進(jìn)入實(shí)模式,并且PC指向了0xfff0整個(gè)地址,,如下圖,。什么意思呢?就是開機(jī)的時(shí)候執(zhí)行的第一句指令放在0xffff0這個(gè)地方,,通常這里有一個(gè)很重要的東西叫做BIOS,。我們可以看到下圖,cs=0xf000,base=0xffff0000,在實(shí)模式下面,,cs:pc 就是真實(shí)的指向地址0xffff0,。到了這里不知道大家發(fā)現(xiàn)沒有,這里還差一個(gè)東西,那就是bios本來是放在rom里面的,,怎么被指向了內(nèi)存地址0xffff0的地方呢?是誰在之前自動(dòng)搬運(yùn)的嗎,?經(jīng)過查詢后發(fā)現(xiàn),,大部分人說開機(jī)的時(shí)候,對(duì)特殊地址的訪問會(huì)被仲裁器件指向BIOS-ROM器件,。仲裁器還可以把地址翻譯并指向我們熟悉的MEM和IO,。所以這里我理解對(duì)0xffff0的訪問就是對(duì)BIOS-ROM器件的直接訪問和執(zhí)行。
??BIOS主要是做自檢,,并且在物理地址0x0開始初始化BIOS的中斷向量,,同時(shí)通過BIOS訪問存儲(chǔ)設(shè)備的中斷,將可啟動(dòng)設(shè)備的第一個(gè)扇區(qū)512字節(jié)給搬運(yùn)到絕對(duì)地址0x7c00(31k)處,。然后跳轉(zhuǎn)到0x7c00繼續(xù)執(zhí)行,,這里被搬運(yùn)的512字節(jié)就是bootsect.S生成的指令。這一段沒啥營(yíng)養(yǎng),,都是一些約定好的,,到了CPU執(zhí)行到絕對(duì)地址0x7c00的時(shí)候,才是真正的我們能控制的地方,。其實(shí)這里也能夠看到,,我們的bootsect.S生成的指令最大只能夠512字節(jié),超過了就會(huì)出問題,。下圖為我們的0x7c00處的開始幾句指令和bootsect.S的幾句指令,同時(shí)也能夠看到BIOS初始化和自檢打印的一些內(nèi)容:
??在上圖的圖中,,我打印了0x7c00開始的一部分反匯編代碼??梢钥吹胶拖旅娴腷ootsect.S的代碼是一致的,。
entry start
start:
! start at 0x07c0:0
! add by sky
mov ax,#BOOTSEG
mov es,ax
mov bp,#msg2 ! sky-notes: src-str is es:bp
mov si,#15 ! sky-notes: src-str-len is cx
call pirnt_str
! add by sky
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep
movw
jmpi go,INITSEG
??從0x7c00開始,就是我們自己的可以編程的領(lǐng)域了,,也開始有了一些我自己特有的內(nèi)容,。主要是各種方法實(shí)現(xiàn)的print語句。這種調(diào)試方法簡(jiǎn)直不要太好,。
??下面簡(jiǎn)要說明一下bootsect.S的功能:
- 首先用rep movw把自己從0x7c00搬運(yùn)到0x90000,,并跳轉(zhuǎn)cs=0x9000, pc=go 標(biāo)號(hào)的地址。繼續(xù)執(zhí)行剩下的內(nèi)容,。
- 通過讀取0x1E號(hào)中斷向量位置的軟驅(qū)參數(shù)(由BIOS初始化時(shí)候通過BIOS中斷讀取的)到內(nèi)存,,然后修改其中的最大扇區(qū)數(shù),并重新寫回到0x1E中斷向量位置絕對(duì)地址0x78去,。最后重置軟驅(qū),,使其加載最新的參數(shù)。
- 使用BIOS INT 0x13的2號(hào)功能,將第一個(gè)軟盤第2,,3,,4,5扇區(qū)讀取到0x90200開始的位置,。這里讀取的就是setup.S的指令內(nèi)容,,最大共2k(4*512)。0x90000-0x90200存放的是bootsect.S,, 0x90200-0x90A00 為setup.S,。
- 使用BIOS INT 0x13的8號(hào)功能,讀取磁盤參數(shù):每磁道扇區(qū)數(shù),。并保存到變量sectors中,。
- 使用BIOS INT 0x13的2號(hào)功能,使用剛剛的參數(shù),,讀取system模塊到0x10000,,我們的bootsect.S放在0x90000,所以我們system模塊最大只能夠占用0x10000~0x8ffff。這里的system模塊就是除了bootsect和setup模塊之外的所有內(nèi)核代碼,。
- 判斷bootsect模塊第508,,509字節(jié)是否為0,來判斷我們是否指定根文件系統(tǒng)的設(shè)備號(hào),。我們的內(nèi)核定義為0x0301,,代表第一個(gè)磁盤第一個(gè)分區(qū)為我們的根文件系統(tǒng)。
- 然后通過jmpi 0:9020跳轉(zhuǎn)到cs=0x9020,,pc=0的地方去執(zhí)行setup.S的代碼,。
??在我的bootsect模塊,我定義了一個(gè)打印字符串的函數(shù),,主要是通過使用BIOS INT 0x10的0x13號(hào)功能實(shí)現(xiàn),。主要還是為了調(diào)試,注意,,這里不能夠隨意添加代碼,,因?yàn)樯傻拇a超過512byte后,鏈接器會(huì)報(bào)錯(cuò),。只能夠少量的添加我們的調(diào)試代碼,。
??至此,我們就執(zhí)行完了bootsect模塊,。本模塊的主要內(nèi)容還是加載setup和system到指定位置,。bootsect執(zhí)行的一些調(diào)試日志如下圖(在0x90200下斷點(diǎn)):
注意:圖中話框的部分就是我們上文貼出的call pirnt_str打印的。
boot/setup.S 階段
??首先我們還是來看一下0x90200的位置是否是setup.S,,換句話來說是否加載好了setup模塊,。
??這里和bootsect一樣,,我也弄了一個(gè)prtstr函數(shù),這個(gè)prtstr和bootsect里面的是一樣的,,原理也是一致的,。
??剛剛我們提到,setup是從0x90200開始存放的,。那么0x90000~0x901ff中的bootsect已經(jīng)無用了,,于是我們setup中,用這里的內(nèi)存存放一些參數(shù),。下面簡(jiǎn)要說明一下setup.S的功能:
- 用BIOS INT 0x15功能號(hào)0x88取系統(tǒng)所含擴(kuò)展內(nèi)存大小并保存在內(nèi)存0x90002~0x90003處。共兩個(gè)字節(jié),。
- 用BIOS INT 0x10功能號(hào)0x12讀取顯卡參數(shù),,0x9000A 顯存大小,0x9000B 顯卡類型(單色/彩色),0x9000C顯卡特性參數(shù),。
- 用BIOS中斷讀取屏幕的行列存放到0x9000E 0x9000F
- 用BIOS INT 0x10功能號(hào)0x03讀取當(dāng)前光標(biāo)位置存放到0x90000 0x90001
- 用BIOS INT 0x10功能號(hào)0x0f讀取當(dāng)前顯示頁(yè),,顯示模式,字符列數(shù),。 0x90004~0x90005 存放當(dāng)前顯示頁(yè),。 0x90006 顯示模式, 0x90007 字符列數(shù),。
- 讀取第一個(gè)硬盤參數(shù)表和第二個(gè)硬盤參數(shù)表,,并放到0x90080 0x90090。每個(gè)表共16byte,。注意,,這里和之前的軟盤參數(shù)一樣,在BIOS自檢過程中,,就被放到了中斷向量0x41 0x46 的位置,。
- 用BIOS INT 0x13功能號(hào)0x15讀取當(dāng)前硬盤設(shè)備情況,如果硬盤2不存在,,則把0x90090之后的16byte清零,。
??下面我們將使CPU從實(shí)模式變更為保護(hù)模式,下面繼續(xù)說明一下setup.S的功能:
- 禁用中斷,。
- 然后我們把system模塊0x10000~0x8ffff整體下移到0x0開始的位置,。就是把最大0x80000(512k)的system模塊向下移動(dòng)0x10000(64k)。
- 首先加載LDT和GDT,。
- 開啟A20地址線,,支持1M以上的內(nèi)存。
- 初始化兩個(gè)8259A中斷控制器,。
- 通過lmsw 設(shè)置cr0最低位位1,,進(jìn)入保護(hù)模式。
- 通過jmpi 0:0x8跳轉(zhuǎn)到絕對(duì)地址0x0開始執(zhí)行system的代碼。system是從boot/head.s開始的,。
??這里需要說明幾個(gè)事情:
- 我們?cè)谙乱苨ystem模塊的時(shí)候,,覆蓋了BIOS中斷向量表。所以通過BIOS中斷打印字符串是行不通的,。
- 在實(shí)模式中,,cs:pc就是真實(shí)執(zhí)行的地址。但是在保護(hù)模式中,,cs是一個(gè)選擇符號(hào),,根據(jù)選擇符號(hào)值不同,分表在GDT或者LDT中查找對(duì)應(yīng)的CS段描述符,,其中最重要的就是base地址,,當(dāng)未開啟分頁(yè)的時(shí)候,這里的base+pc就是我們真實(shí)的執(zhí)行地址,。上面我們加載了LDT和GDT,。這里的LDT是空,GDT有3項(xiàng),,第零項(xiàng)是空,,第一項(xiàng)是代碼段描述符,第二項(xiàng)是數(shù)據(jù)段描述符,,他們的基地址都是0x0,。當(dāng)cs=0x08,ds=0x10時(shí),分別指向這里的第一項(xiàng)和第二項(xiàng),。
??剛剛說了,,system下移導(dǎo)致BIOS中斷向量表被沖掉了,于是我們不能夠通過BIOS打印字符串,,于是這里我們使用的是直接操作顯存內(nèi)存地址顯示字符,,這個(gè)原理和LinuxKernel tty顯示原理差別不是很大。
??這里我們?cè)O(shè)計(jì)了print_str函數(shù),,通過直接操控顯存然后寫入字符進(jìn)行顯示,,這里還使用到了剛剛我們保存的當(dāng)前光標(biāo)位置(0x90000 0x90001)。寫這個(gè)主要還是為了調(diào)試,。
??到此,,我們已經(jīng)開始去執(zhí)行system的內(nèi)容,其中head.s是入口,。下圖是在0x0下斷點(diǎn)得到的setup模塊的一些打印日志,。
??這里我們可以看到,紅框還是BIOS中斷打印的,,黃框是通過直接操縱顯存顯示的,。注意,,我這里設(shè)計(jì)的直接操作顯存的函數(shù),是通過循環(huán)在當(dāng)前顯存頁(yè)顯示的,,并不是我們常見的整頁(yè)上移的方式,。
boot/head.s 階段
??首先我們還是來看一下0x0的位置是否是head.s,換句話來說是否加載好了system模塊,。并且,,從這里開始,我們就是進(jìn)入了真正的LinuxKernel的世界,,前面都是做一些環(huán)境初始化,,都是一些固定的內(nèi)容。
??這里我們需要說明的是,,bootsect.S和setup.S用的是intel匯編,,而從head.s開始,我們用的都是AT&T匯編,。同理,這里我也弄了一個(gè)safe_mode_print_str_no_page,,打印字符串,,為了調(diào)試,還是用的直接操作顯存的方式,。
??從這里開始,,CPU開始工作于保護(hù)模式,下面簡(jiǎn)要介紹一下工作流程:
- 剛剛我們通過jmpi切換到0x0開始執(zhí)行,,這時(shí)cs=0x8,根據(jù)setup設(shè)置好的GDT,,base為0x0,同理我們?cè)O(shè)置其他段寄存器,。
- 設(shè)置堆棧為stack_start,,這個(gè)就是內(nèi)核堆棧。此符號(hào)定義于kernel/sched.c中,,如下文,。
long user_stack [ PAGE_SIZE>>2 ] ;
struct {
long * a;
short b;
// } stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
} stack_start = { & user_stack , 0x10 };
- 設(shè)置IDT,所有的中斷向量指向ignore_int,,一個(gè)預(yù)定義的中斷服務(wù)程序,。共256項(xiàng),每項(xiàng)8byte,。
- 重新設(shè)置GDT,。共256項(xiàng),每項(xiàng)8byte,。重新設(shè)置GDT的原因是setup的GDT可能會(huì)被沖掉,,于是把GDT設(shè)置到合理的內(nèi)存位置,。這里設(shè)置好的GDT有4個(gè)。和setup中類似,。第0,,3個(gè)為0.第1,2項(xiàng)為cs和ds的段描述符,。
- 檢查A20是否開通,,主要是通過判斷0x100000 和 0x0值是否相等。
- 檢查數(shù)學(xué)協(xié)處理器是否存在,。
??到這里,,我們就開始準(zhǔn)備正式進(jìn)入到init/main.c中的main函數(shù)了,但是還差最后一個(gè)重要的事情,,那就是啟用分頁(yè)機(jī)制,,下面繼續(xù)介紹其工作流程:
after_page_tables:
# sky print
push %ebp
lea msg5, %ebp
call safe_mode_print_str_no_page
pop %ebp
#
pushl $0 # These are the parameters to main :-)
pushl $0
pushl $0
pushl $L6 # return address for main, if it decides to.
pushl $main
jmp setup_paging
- 從上面的代碼我們可只,我們?cè)趩⒂梅猪?yè)前,,把init/main.c中的main函數(shù)地址設(shè)置到了堆棧中,。
- 首先我們把從0x0開始的5頁(yè)內(nèi)存清零。每頁(yè)4096字節(jié),。其中第一頁(yè)為頁(yè)表目錄,,第2-5頁(yè)為頁(yè)表。
- 設(shè)置頁(yè)表目錄的前4項(xiàng)為第2-5頁(yè)頁(yè)表地址,。注意頁(yè)表目錄為1024項(xiàng),,每項(xiàng)4字節(jié)。
- 倒序設(shè)置每一個(gè)頁(yè)表的每一項(xiàng)內(nèi)容,,第5頁(yè)最后一項(xiàng)為0xfff000,。映射之后,2-5頁(yè)分別映射好了16MB內(nèi)存的空間,。
- 操作cr0,,開啟分頁(yè)
- 通過ret指令,從堆棧中把main地址彈出去執(zhí)行,。
??到這里,,我們正式進(jìn)入到init/main.c中的main函數(shù)中,進(jìn)入c語言相關(guān)代碼的地界,。下面是進(jìn)入main之前的一些日志輸出,。
init/main.c 到進(jìn)入shell
??這里我們進(jìn)入了init/main.c中的main函數(shù),可從下圖看到,。從這里開始,,也是我們大家都熟知的Linux內(nèi)核部分。
void main(void) /* This really IS void, no error here. */
{ /* The startup routine assumes (well, ...) this */
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
char _my_msg_buf[100];
sprintf(_my_msg_buf, "kernel main() start, root_dev=%x, swap_dev=%x ... ...\0", ORIG_ROOT_DEV, ORIG_SWAP_DEV);
__asm__ (
"push %%ebp\n\t"
"mov %0, %%ebp\n\t"
"call safe_mode_print_str_after_page\n\t"
"pop %%ebp\n\t"
:
:"p"((char *)&_my_msg_buf)
:);
ROOT_DEV = ORIG_ROOT_DEV;
SWAP_DEV = ORIG_SWAP_DEV;
sprintf(term, "TERM=con%dx%d", CON_COLS, CON_ROWS);
envp[1] = term;
envp_rc[1] = term;
drive_info = DRIVE_INFO;
memory_end = (1<<20) + (EXT_MEM_K<<10);
memory_end &= 0xfffff000;//align 4k
if (memory_end > 16*1024*1024)//if memory_end > 16MB, set it to be 16 MB
memory_end = 16*1024*1024;
if (memory_end > 12*1024*1024)
buffer_memory_end = 4*1024*1024;
else if (memory_end > 6*1024*1024)
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024;
main_memory_start = buffer_memory_end;
sprintf(_my_msg_buf, "Mem size is %x, buf-mem size is %x, main-mem start %x ... ...\0", memory_end, main_memory_start, buffer_memory_end);
__asm__ (
"push %%ebp\n\t"
"mov %0, %%ebp\n\t"
"call safe_mode_print_str_after_page\n\t"
"pop %%ebp\n\t"
:
:"p"((char *)&_my_msg_buf)
:);
#ifdef RAMDISK
sprintf(_my_msg_buf, "ramdisk init ... ...\0");
__asm__ (
"push %%ebp\n\t"
"mov %0, %%ebp\n\t"
"call safe_mode_print_str_after_page\n\t"
"pop %%ebp\n\t"
:
:"p"((char *)&_my_msg_buf)
:);
main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
sprintf(_my_msg_buf, "memory init ... ...\0");
__asm__ (
"push %%ebp\n\t"
"mov %0, %%ebp\n\t"
"call safe_mode_print_str_after_page\n\t"
"pop %%ebp\n\t"
:
:"p"((char *)&_my_msg_buf)
:);
mem_init(main_memory_start,memory_end);
sprintf(_my_msg_buf, "trap init ... ...\0");
__asm__ (
"push %%ebp\n\t"
"mov %0, %%ebp\n\t"
"call safe_mode_print_str_after_page\n\t"
"pop %%ebp\n\t"
:
:"p"((char *)&_my_msg_buf)
:);
trap_init();
sprintf(_my_msg_buf, "blk init ... ...\0");
__asm__ (
"push %%ebp\n\t"
"mov %0, %%ebp\n\t"
"call safe_mode_print_str_after_page\n\t"
"pop %%ebp\n\t"
:
:"p"((char *)&_my_msg_buf)
:);
blk_dev_init();
sprintf(_my_msg_buf, "chr init ... ...\0");
__asm__ (
"push %%ebp\n\t"
"mov %0, %%ebp\n\t"
"call safe_mode_print_str_after_page\n\t"
"pop %%ebp\n\t"
:
:"p"((char *)&_my_msg_buf)
:);
chr_dev_init();
sprintf(_my_msg_buf, "tty init ... ...\0");
__asm__ (
"push %%ebp\n\t"
"mov %0, %%ebp\n\t"
"call safe_mode_print_str_after_page\n\t"
"pop %%ebp\n\t"
:
:"p"((char *)&_my_msg_buf)
:);
tty_init();
printk("time init ... ...\n\r");
time_init();
printk("sched init ... ...\n\r");
sched_init();
/*
After sched_init()
gdt[0] = NULL
gdt[1] = kernel cs
gdt[2] = kernel ds
gdt[3] = NULL
gdt[4] = task0.tss
gdt[5] = task0.ldt
tr=task0.tss
ldtr=task0.ldt
*/
printk("buffer init ... ...\n\r");
buffer_init(buffer_memory_end);
printk("hd init ... ...\n\r");
hd_init();
printk("floppy init ... ...\n\r");
floppy_init();
printk("enable interrupts ... ...\n\r");
sti();
printk("go to user mode ... ...\n\r");
/*
movl %%esp,%%eax
pushl $0x17
pushl %%eax
pushfl
pushl $0x0f
pushl $1f
iret
1:
movl $0x17,%%eax
mov %%ax,%%ds
mov %%ax,%%es
mov %%ax,%%fs
mov %%ax,%%gs
iret instruction will do follow op:
popl eip
popl cs
popl eflag
popl esp
popl ss
*/
move_to_user_mode();
printf("user_mode: fork() task0 ... ...");
if (!fork()) { /* we count on this going ok */
printf("user_mode: task1 call init ... ...");
init();
}
/*
* NOTE!! For any other task 'pause()' would mean we have to get a
* signal to awaken, but task0 is the sole exception (see 'schedule()')
* as task 0 gets activated at every idle moment (when no other tasks
* can run). For task0 'pause()' just means we go check if some other
* task can run, and if not we return here.
*/
printf("user_mode: task0 call sys_pause() in while ... ...");
for(;;)
__asm__("int $0x80"::"a" (__NR_pause):);
}
??注意,,這里我們?nèi)匀辉O(shè)計(jì)了一個(gè)函數(shù)為safe_mode_print_str_after_page,,通過直接操作顯存進(jìn)行顯示字符串,,知道tty_init之后,我們才能夠調(diào)用printk類似的函數(shù)進(jìn)行打印,。
??下面簡(jiǎn)要介紹一下main函數(shù)主要做的事情:
- 根據(jù)我們?cè)趕etup中保存到內(nèi)存中的內(nèi)存參數(shù)初始化高速緩沖區(qū)和主存的位置,。
- 然后就是我們常見的初始化mm模塊。
- 初始化中斷向量,。
- 初始化塊設(shè)備,。
- 初始化字符串設(shè)備。
- 初始化tty設(shè)備,。
- 初始化時(shí)間,。
- 初始化調(diào)度模塊。
- 初始化緩沖區(qū),。
- 初始化硬盤,。
- 初始化軟盤。
- 開啟中斷,。
- 把當(dāng)前任務(wù)切換到用戶態(tài),。
??當(dāng)我們切換到用戶態(tài)之后,并且當(dāng)前我們的進(jìn)程是0號(hào)進(jìn)程,,我們內(nèi)核的一些重要初始化基本設(shè)置完畢,。然后就像我們常見的linux編程那樣,通過fork,,創(chuàng)建我們的1號(hào)進(jìn)程。然后我們繼續(xù)進(jìn)行下面的事情:
- task0在fork出task1之后,,就循環(huán)調(diào)用sys_pause, 這里主要還是執(zhí)行schedule()開始執(zhí)行進(jìn)程調(diào)度,。
- task1成功創(chuàng)建后,調(diào)用setup,,開始加載根文件系統(tǒng),。然后task1 通過fork創(chuàng)建了task2。
- task2通過execve開始運(yùn)行/bin/sh,,進(jìn)入shell,。后續(xù)就是一些其他的事情。
??到這里,,我們已經(jīng)把kernel跑起來了,。在我調(diào)試的過程中,主要還是mm模塊和schedule模塊有些問題,,可能和編譯器版本有關(guān)系,,反正我生成的代碼,總會(huì)報(bào)錯(cuò),。哪怕到現(xiàn)在,,我開源出來的我修改的內(nèi)核,,也非常的不穩(wěn)定,經(jīng)常崩潰,。但是好在正常工作了,。
??下面給出兩種不同打印的日志:
??此工具是生成LinuxKernel鏡像的手段。但是我們?cè)赨buntu上生成的內(nèi)核,,由于gcc版本變更的原因,,需要做一些變更。主要還是把生成的elf格式system模塊通過objcopy 生成二進(jìn)制內(nèi)存鏡像,。主要原因就是elf格式需要一個(gè)elf加載器進(jìn)行各個(gè)段的重定位,,但是由于我們是內(nèi)核,所以沒有,。詳情,,請(qǐng)查看tool/build.c 及 Makefile。
開源
??https://github.com/flyinskyin2013/LinuxKernel-src0.12
??https:///sky-X/LinuxKernel-src0.12 (鏡像)
后記
??為啥想要在ubuntu1804環(huán)境下弄這個(gè)東西呢,?一方面是想學(xué)習(xí)一下,,通過踩坑的方式加深自己的理解。另一方面還是太懶了,,我只想在我的ubuntu1804上編譯內(nèi)核,,不想安裝其他虛擬機(jī)了,我的電腦太卡了(畢竟8年的電腦了QAQ),。
??經(jīng)過了這一波調(diào)試,,我對(duì)LinuxKernel有了更深的認(rèn)知,我覺得很不錯(cuò),,如果以后有必要,,我還可以分別對(duì)這些模塊進(jìn)行詳細(xì)的查看,在這里,,我只是簡(jiǎn)單的說明了init/main中的內(nèi)容,,其實(shí),還有許多其他的內(nèi)容是運(yùn)行在背后的,。比如system_call,sys_table等等內(nèi)容,。還有do_fork do_execve等等內(nèi)容都是我在調(diào)試過程中踩過的坑。
??這里還是要說明,,深入調(diào)試學(xué)習(xí)這個(gè)的原因還是想看看OS是怎么運(yùn)行起來,,雖然不能說已經(jīng)100%的熟知,但是也可管中窺豹,。
??注意,,這個(gè)版本的內(nèi)核和現(xiàn)代的2.0,4.0,,5.0還缺了一些主要的知識(shí),,比如網(wǎng)絡(luò)棧,,VFS等。但是其他的一些內(nèi)容,,在現(xiàn)在的最新內(nèi)核中,,多多少少都能夠看到這個(gè)版本的一些影子。這也是學(xué)習(xí)這個(gè)內(nèi)核的原因之一,。
打賞,、訂閱、收藏,、丟香蕉,、硬幣,請(qǐng)關(guān)注公眾號(hào)(攻城獅的搬磚之路)
PS: 請(qǐng)尊重原創(chuàng),,不喜勿噴,。
PS: 要轉(zhuǎn)載請(qǐng)注明出處,本人版權(quán)所有,。
PS: 有問題請(qǐng)留言,,看到后我會(huì)第一時(shí)間回復(fù)。
|