?概要
??Linux操作系統(tǒng)內核是服務端學習的根基,也是提高編程能力、源碼閱讀能力和進階知識學習能力的重要部分,本文開始將記錄Linux操作系統(tǒng)中的各個部分源碼學習歷程。
?
1. 理解代碼的組織結構
以Linux源碼舉例,首先你得知道操作系統(tǒng)分為哪幾個部分,他們單獨做了什么功能,如何進行配合完成更為具體的功能。建立整體的印象有助于后續(xù)深入學習的時候方便理解,畢竟代碼是用的不是看的,理解他的作用有利于理解為什么要這么做。
2. 深入各個模塊學習
模塊接口:這里推薦微軟的畫圖工具visio或者思維導圖xmind,用其畫圖可以將各個模塊的接口列出,并繪制各個模塊之間的關系,通過了解接口可以清楚各個模塊之間的關系,即繪制模塊組織圖
工作流程:通過上面一步得到各模塊間的關系,然后實際用斷點或log等方式看一看整體的工作流程,在模塊組織圖的基礎上繪制程序流程圖
模塊粘合層:我們的代碼有很多都是用來粘合代碼的,比如中間件(middleware)、Promises 模式、回調(Callback)、代理委托、依賴注入等。這些代碼模塊間的粘合技術是非常重要的,因為它們會把本來平鋪直述的代碼給分裂開來,讓你不容易看明白它們的關系。這些可以作為程序流程圖的補充,讓其中本來無法順暢銜接的地方變得通暢無阻。
模塊具體實現(xiàn) :這是最難得地方,涉及到大量具體源碼的學習。深入細節(jié)容易迷失在細節(jié)的海洋里,因此需要有一些重點去關注,將非重點的內容省略。通過學習繪制模塊具體架構圖和模塊的算法時序圖,可以幫助你更好的掌握源碼的精髓。
3. 需要關注的包括
代碼邏輯。代碼有兩種邏輯,一種是業(yè)務邏輯,這種邏輯是真正的業(yè)務處理邏輯;另一種是控制邏輯,這種邏輯只是用控制程序流轉的,不是業(yè)務邏輯。比如:flag 之類的控制變量,多線程處理的代碼,異步控制的代碼,遠程通訊的代碼,對象序列化反序列化的代碼等。這兩種邏輯你要分開,很多代碼之所以混亂就是把這兩種邏輯混在一起了。
重要的算法。一般來說,我們的代碼里會有很多重要的算法,我說的并不一定是什么排序或是搜索算法,可能會是一些其它的核心算法,比如一些索引表的算法,全局唯一 ID 的算法、信息推薦的算法、統(tǒng)計算法、通讀算法(如 Gossip)等。這些比較核心的算法可能會非常難讀,但它們往往是最有技術含量的部分。
底層交互。有一些代碼是和底層系統(tǒng)的交互,一般來說是和操作系統(tǒng)或是 JVM 的交互。因此,讀這些代碼通常需要一定的底層技術知識,不然,很難讀懂。
4. 可以忽略的包括
出錯處理。根據(jù)二八原則,20% 的代碼是正常的邏輯,80% 的代碼是在處理各種錯誤,所以,你在讀代碼的時候,完全可以把處理錯誤的代碼全部刪除掉,這樣就會留下比較干凈和簡單的正常邏輯的代碼。排除干擾因素,可以更高效地讀代碼。
數(shù)據(jù)處理。只要你認真觀察,就會發(fā)現(xiàn),我們好多代碼就是在那里倒騰數(shù)據(jù)。比如 DAO、DTO,比如 JSON、XML,這些代碼冗長無聊,不是主要邏輯,可以不理。
忽略過多的實現(xiàn)細節(jié)。在第一遍閱讀源碼時,已弄懂整體流程為主,至于具體的實現(xiàn)細節(jié)先簡單的理清處過一遍,不用過于糾結。當梳理清楚全部的框架邏輯后,第二遍再深入的學習研究各個模塊的實現(xiàn),此時應該解決第一遍中的疑惑。第三遍可以跳出代碼的實現(xiàn),來看Linux的設計思路、編程藝術和演進之路。
重在實踐。Linux的代碼都是可以調試的,看很多遍也許不如跟著調試走一遍,然后再自己修改修改做一些小測試。
傳授知識。當你能將知識講述給別人聽,并讓別人聽懂時,你已經(jīng)可以自豪的說洞悉了這些知識。所以不妨從一個小的例子開始自說自話,看能不能自圓其說,甚至寫成博客、做成PPT給大家講解。
??說了一大堆的廢話,下面就正式開始操作系統(tǒng)的深入學習記錄之旅了。
5. 混沌初開
??本文分析從按下電源鍵到加載BIOS以及后續(xù)bootloader的整個過程。猶如盤古開天辟地一般,該過程將混沌的操作系統(tǒng)世界分為清晰的內核態(tài)和用戶態(tài),并經(jīng)歷從實模式到保護模式的變化。這里先簡單介紹一下名詞,便于后續(xù)理解。
實模式(Real Mode):又名 Real Address Mode,在此模式下地址訪問的是真實地內存地址所在位置。在此模式下,可以使用20位(1MB)的地址空間,軟件可以不受限制的操作所有地址的空間和IO設備。
保護模式(Protected Mode):又名 Protected Virtual Address Mode,采用虛擬內存、頁等機制對內存進行了保護,比起實模式更為安全可靠,同時也增加了靈活性和擴展性。
?5.1 從啟動電源到BIOS?
當我們按下電源鍵,主板會發(fā)向電源組發(fā)出信號,接收到信號后,電源會提供合適的電壓給計算機。當主板收到電源正常啟動的信號后,主板會啟動CPU。CPU重置所有寄存器數(shù)據(jù),并設置初始化數(shù)據(jù),這個初始化數(shù)據(jù)在X86架構里如下所示:
1IP??????????0xfff0
2CS?selector?0xf000
3CS?base?????0xffff0000
4IP/EIP?(Instruction?Pointer)?:?指令指針寄存器,記錄將要執(zhí)行的指令在代碼段內的偏移地址
5CS(Code Segment Register):代碼段寄存器,指向CPU當前執(zhí)行代碼在內存中的區(qū)域(定義了存放代碼的存儲器的起始地址)
實模式采取內存段來管理 0 - 0xFFFFF的這1M內存空間,但是由于只有16位寄存器,所以最大地址只能表示為0xFFFFF(64KB),因此不得不采取將內存按段劃分為64KB的方式來充分利用1M空間。也就是上所示的,采取段選擇子 + 偏移量的表示法。這種方法在保護模式中對于頁的設計上也沿用了下來,可謂祖?zhèn)鞯闹腔哿?。具體的計算公式如下所示:
1PhysicalAddress?=?Segment?Selector?*?16?+?Offset
??該部分由硬件完成,通過計算訪問0XFFFF0,如果該位置沒有可執(zhí)行代碼則計算機無法啟動。如果有,則執(zhí)行該部分代碼,這里也就是我們故事的開始,BIOS程序了。
?5.2 BIOS到BootLoader?
??BIOS執(zhí)行程序存儲在ROM中,起始位置為0XFFFF0,當CS:IP指向該位置時,BIOS開始執(zhí)行。BIOS主要包括以下內存映射:
?10x00000000?-?0x000003FF?-?Real?Mode?Interrupt?Vector?Table
?20x00000400?-?0x000004FF?-?BIOS?Data?Area
?30x00000500?-?0x00007BFF?-?Unused
?40x00007C00?-?0x00007DFF?-?Our?Bootloader
?50x00007E00?-?0x0009FFFF?-?Unused
?60x000A0000?-?0x000BFFFF?-?Video?RAM?(VRAM)?Memory
?70x000B0000?-?0x000B7777?-?Monochrome?Video?Memory
?80x000B8000?-?0x000BFFFF?-?Color?Video?Memory
?90x000C0000?-?0x000C7FFF?-?Video?ROM?BIOS
100x000C8000?-?0x000EFFFF?-?BIOS?Shadow?Area
110x000F0000?-?0x000FFFFF?-?System?BIOS
12
其中最重要的莫過于中斷向量表和中斷服務程序。BIOS程序在內存最開始的位置(0x00000)用1 KB的內存空間(0x00000~0x003FF)構建中斷向量表,在緊挨著它的位置用256字節(jié)的內存空間構建BIOS數(shù)據(jù)區(qū)(0x00400~0x004FF),并在大約57 KB以后的位置(0x0E05B)加載了8 KB左右的與中斷向量表相應的若干中斷服務程序。中斷向量表中有256個中斷向量,每個中斷向量占4字節(jié),其中兩個字節(jié)是CS的值,兩個字節(jié)是IP的值。每個中斷向量都指向一個具體的中斷服務程序。
?BIOS程序會選擇一個啟動設備,并將控制權轉交給啟動扇區(qū)中的代碼。主要工作即使用中斷向量和中斷服務程序完成BootLoader的加載,最終將boot.img加載至0X7C00的位置啟動。Linux內核通過Boot Protocol定義如何實現(xiàn)該引導程序,有如GRUB 2和syslinux等具體實現(xiàn)方式,這里僅介紹GRUB2。
5.3 BootLoader的工作
??boot.img由boot.S編譯而成,512字節(jié),安裝在啟動盤的第一個扇區(qū),即MBR。由于空間有限,其代碼十分簡單,僅僅是起到一個引導的作用,指向后續(xù)的核心鏡像文件,即core.img。core.img包括很多重要的部分,如lzma_decompress.img、diskboot.img、kernel.img等,結構如下圖。
?整個流程如下:
1、boot.img加載core.img的第一個扇區(qū),即diskboot.img,對應代碼為diskboot.S
2、diskboot.img加載core.img的其他部分模塊,先是解壓縮程序 lzma_decompress.img,再往下是 kernel.img,最后是各個模塊 module 對應的映像。這里需要注意,它不是 Linux 的內核,而是 grub 的內核。注意,lzma_decompress.img 對應的代碼是 startup_raw.S,本來 kernel.img 是壓縮過的,現(xiàn)在執(zhí)行的時候,需要解壓縮。
3、加載完core之后,啟動grub_main函數(shù)。
4、grub_main函數(shù)初始化控制臺,計算模塊基地址,設置 root 設備,讀取 grub 配置文件,加載模塊。最后,將 GRUB 置于 normal 模式,在這個模式中,grub_normal_execute (from grub-core/normal/main.c) 將被調用以完成最后的準備工作,然后顯示一個菜單列出所用可用的操作系統(tǒng)。當某個操作系統(tǒng)被選擇之后,grub_menu_execute_entry 開始執(zhí)行,它將調用 GRUB 的 boot 命令,來引導被選中的操作系統(tǒng)。
? ? ? ?在這之前,我們所有遇到過的程序都非常非常小,完全可以在實模式下運行,但是隨著我們加載的東西越來越大,實模式這 1M 的地址空間實在放不下了,所以在真正的解壓縮之前,lzma_decompress.img 做了一個重要的決定,就是調用 real_to_prot,切換到保護模式,這樣就能在更大的尋址空間里面,加載更多的東西。
??開機時的16位實模式與內核啟動的main函數(shù)執(zhí)行需要的32位保護模式之間有很大的差距,這個差距誰來填補?head.S做的就是這項工作。就像 kernel boot protocol 所描述的,引導程序必須填充 kernel setup header (位于 kernel setup code 偏移 0x01f1 處) 的必要字段,這些均在head.S中定義。在這期間,head程序打開A20,打開pe、pg,廢棄舊的、16位的中斷響應機制,建立新的32位的IDT……這些工作都做完了,計算機已經(jīng)處在32位的保護模式狀態(tài)了,調用32位內核的一切條件已經(jīng)準備完畢,這時順理成章地調用main函數(shù)。后面的操作就可以用32位編譯的main函數(shù)完成,從而正式啟動內核,進入波瀾壯闊的Linux內核操作系統(tǒng)之中。
?6. 總結?
??本文介紹了從按下電源開關至加載完畢BootLoader的整個過程,后續(xù)將繼續(xù)分析從實模式進入保護模式,從而啟動內核創(chuàng)建0號、1號、2號進程的整個過程。文章來源:http://www.zghlxwxcb.cn/news/detail-486279.html
歡迎轉發(fā)點贊評論,感謝!文章來源地址http://www.zghlxwxcb.cn/news/detail-486279.html
到了這里,關于Linux操作系統(tǒng)學習——啟動的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!