您好,歡迎來電子發(fā)燒友網(wǎng)! ,新用戶?[免費注冊]

您的位置:電子發(fā)燒友網(wǎng)>源碼下載>Linux/uClinux/Unix編程>

Linux內(nèi)核源代碼漫游

大?。?/span>92 人氣: 2010-02-09 需要積分:0
{$username}的空間

用戶級別:注冊會員

貢獻文章:

貢獻資料:

Linux內(nèi)核源代碼漫游

本章試圖以順序的方式來解釋Linux源代碼,以幫助讀者對源代碼的體系結(jié)構(gòu)以及很多相關(guān)的unix特性的實現(xiàn)有一個很好的理解。目標是幫助對Linux不甚了解的有經(jīng)驗的C程序員對整個Linux的設(shè)計有所了解。這也就是為什么內(nèi)核漫游的入點選擇為內(nèi)核本身的啟始點:系統(tǒng)引導(啟動)。
這份材料需要對C語言以及對Unix的概念和PC機的結(jié)構(gòu)有很好的了解,然而本章中并沒有出現(xiàn)任何的C代碼,而是直接參考(指向)實際的代碼的。有關(guān)內(nèi)核設(shè)計的最佳篇幅是在本手冊的其它章節(jié)中,而本章仍趨向于是一個非正式的概述。
本章中所參閱的任何文件的路徑名都是指主源代碼目錄樹,通常是/usr/src/linux。
?這里所給出的大多數(shù)信息都是取之于Linux發(fā)行版1.0的源代碼。雖然如此,有時也會提供對后期版本的參考。這篇漫游中開頭有? 圖標的任何小節(jié)都是強調(diào)1.0版本后對內(nèi)核的新的改動。如果沒有這樣的小節(jié)存在,則表示直到版本1.0.9-1.1.76,沒有作過改動。
?有時候本章中會有象這樣的小節(jié),這是指向正確的代碼以對剛討論過的主題取得更多信息的指示符。當然,這里是指源代碼。
引導(啟動)系統(tǒng)
當PC的電源打開后,80x86結(jié)構(gòu)的CPU將自動進入實模式,并從地址0xFFFF0開始自動執(zhí)行程序代碼,這個地址通常是ROM-BIOS中的地址。PC機的BIOS將執(zhí)行某些系統(tǒng)的檢測,在物理地址0處開始初始化中斷向量。此后,它將可啟動設(shè)備的第一個扇區(qū)讀入內(nèi)存地址0x7C00處,并跳轉(zhuǎn)到這個地方。啟動設(shè)備通常是軟驅(qū)或是硬盤。這里的敘述是非常簡單的,但這已經(jīng)足夠理解內(nèi)核初始化的工作過程了。
Linux的最最前面部分是用8086匯編語言編寫的(boot/bootsect.S),它將由BIOS讀入到內(nèi)存0x7C00處,當它被執(zhí)行時就會把自己移到絕對地址0x90000處,并將啟動設(shè)備(boot/setup.S)的下2kB字節(jié)的代碼讀入內(nèi)存0x90200處,而內(nèi)核的其它部分則被讀入到地址0x10000處。在系統(tǒng)加載期間將顯示信息"Loading..."。然后控制權(quán)將傳遞給boot/Setup.S中的代碼,這是另一個實模式匯編語言程序。
啟動部分識別主機的某些特性以及vga卡的類型。如果需要,它會要求用戶為控制臺選擇顯示模式。然后將整個系統(tǒng)從地址0x10000移至0x1000處,進入保護模式并跳轉(zhuǎn)至系統(tǒng)的余下部分(在0x1000處)。
下一步是內(nèi)核的解壓縮。0x1000處的代碼來自于zBoot/head.S,它初始化寄存器并調(diào)用decompress_kernel(),它們依次是由zBoot/inflate.c、zBoot/unzip.c和zBoot/misc.c組成。被解壓的數(shù)據(jù)存放到了地址0x10000處(1兆),這也是為什么Linux不能運行于少于2兆內(nèi)存的主要原因。[在1兆內(nèi)存中解壓內(nèi)核的工作已經(jīng)完成,見 Memory Savers--ED]
?將內(nèi)核封裝在一個gzip文件中的工作是由zBoot目錄中的Makefile以及工具完成的。它們是值得一看的有趣的文件。
?內(nèi)核發(fā)行版1.1.75將boot和zBoot目錄下移到了arch/i386/boot中了,這個改動意味著對不同的體系結(jié)構(gòu)允許真正的內(nèi)核建造,不過我將仍然只講解有關(guān)i386的信息。
解壓過的代碼是從地址0x10100處開始執(zhí)行的[這里我可能忘記了具體的物理地址了,因為我對相應(yīng)的代碼不是很熟],在那里,所有32比特的設(shè)置啟動被完成: IDTGDT以及LDT被加載,處理器和協(xié)處理器也已確認,分頁工作也設(shè)置好了;最終調(diào)用start_kernel子程序。上述操作的源代碼是在boot/head.S中的,這可能是整個內(nèi)核中最有訣竅的代碼了。
注意如果在前述任何一步中出了錯,計算機就會死鎖。在操作系統(tǒng)還沒有完全運轉(zhuǎn)之前是處理不了出錯的。
start_kernel()是位于init/main.c中的,并且沒有任何返回結(jié)果。從現(xiàn)在起的任何代碼都是用C語言編制的,除了中斷管理和系統(tǒng)調(diào)用的入/出代碼(當然,還有大多數(shù)的宏都嵌入了匯編代碼)。
讓輪子轉(zhuǎn)動起來
在處理了所有錯綜復(fù)雜的問題之后,start_kernel()初始化了內(nèi)核的所有部分,尤其是:
??設(shè)置內(nèi)存邊界和調(diào)用paging_init();
??初始化中斷、IRQ通道和調(diào)度;
??分析(解析)命令行;
??如果需要,就分配一個數(shù)據(jù)緩沖區(qū)(profiling buffer)以及其它一些小部分;
??校正延遲循環(huán)(計算“BogoMips”數(shù));
??檢查中斷16是否能與協(xié)處理器工作。
最后,為了生成初始進程,內(nèi)核準備好了移至move_to_user_mode(),它的代碼也是在同一個源代碼文件中的。然后,所謂的空閑任務(wù),進程號0就進入無限的空閑循環(huán)中運行。
接著初始進程(init process)嘗試著運行/etc/init、/bin/init或者/sbin/init。
如果它們沒有一個運行成功的,就會去執(zhí)行代碼“/bin/sh /etc/rc”并且在第一個終端上生成一個根命令解釋程序(root shell)。這段代碼回溯至Linux 0.01,當時操作系統(tǒng)只有一個內(nèi)核,并且沒有登錄進程。
在從一個標準的地方(讓我們假定我們有)用exec()執(zhí)行了init初始化程序之后,內(nèi)核就對程序的執(zhí)行沒有了直接的控制。從現(xiàn)在起它的規(guī)則是提供對系統(tǒng)調(diào)用的處理,以及為異步事件服務(wù)(比如硬件中斷等)。多任務(wù)的環(huán)境已經(jīng)建立,從現(xiàn)在起是init程序通過fork()派生出的系統(tǒng)進程和登錄進程來管理多用戶的訪問了。
由于內(nèi)核是負責提供服務(wù)的,這個漫游文章將通過觀察這些服務(wù)(“系統(tǒng)調(diào)用”)以及通過提供基本數(shù)據(jù)結(jié)構(gòu)的原理和代碼的組織結(jié)構(gòu)繼續(xù)討論下去。
內(nèi)核是如何看見一個進程的
從內(nèi)核的觀點來看,一個進程只是進程表中的一個條目而已。
而進程表以及各個內(nèi)存管理表和緩沖存儲器則是系統(tǒng)中最為重要的數(shù)據(jù)結(jié)構(gòu)。進程表中的各個單項是task_struct結(jié)構(gòu),是定義在include/linux/sched.h中的非常大的數(shù)據(jù)結(jié)構(gòu)。在task_struct中保留著從低層到高層的信息,范圍從某些硬件寄存器的拷貝到進程工作目錄的inode信息。
進程表既是一個數(shù)組和雙鏈表,也是一個樹結(jié)構(gòu)。它的物理實現(xiàn)是一個靜態(tài)的指針數(shù)組,它的長度是定義在include/linux/tasks.h中的常量NR_TASKS,并且每個結(jié)構(gòu)都位于一個保留內(nèi)存頁中。這個列表結(jié)構(gòu)是通過指針next_task和pre_task構(gòu)成的,而樹結(jié)構(gòu)則是非常復(fù)雜的并且我們在此將不加以討論。你可能希望改動NR_TASKS的默認值128,但你要保證所有源文件中相關(guān)的適當文件都要被重新編譯過。
在啟動引導過程結(jié)束后,內(nèi)核將總是代表某個進程而工作,并且全局變量current --- 一個指向某個task_struct條目的指針 --- 被用于記錄正在運行的進程。current僅能通過在kernel/sched.c中的調(diào)度程序來改變。然而,由于所有的進程都必須訪問它,所以使用了宏for_each_task。當系統(tǒng)負荷很輕時,它要比數(shù)組的順序掃描快得多。
進程總是運行于“用戶模式”或“內(nèi)核模式”。用戶程序的主體是運行于用戶模式而其中的系統(tǒng)調(diào)用則運行于內(nèi)核模式中。在這兩種執(zhí)行模式中進程所用的堆棧是不一樣的 -- 常規(guī)的堆棧段用于用戶模式,而一個固定大小的堆棧(一頁,由該進程所有)則用于內(nèi)核模式。內(nèi)核堆棧頁是從不交換出去的,因為每當一個系統(tǒng)調(diào)用進入時它就必須存在著。
內(nèi)核中的系統(tǒng)調(diào)用(system calls)是作為C語言函數(shù)存在的,它們的‘正規(guī)’名稱是以‘sys_’開頭的。例如一個名為burnout的系統(tǒng)調(diào)用將調(diào)用內(nèi)核函數(shù)sys_burnout()。
?系統(tǒng)調(diào)用機制在本手冊的第三章中進行了討論。觀看在include/linux/sched.h中的for_each_task和SET_LINKS能夠幫助理解進程表中的列表和樹結(jié)構(gòu)。
創(chuàng)建和結(jié)束進程
unix系統(tǒng)是通過fork()系統(tǒng)調(diào)用創(chuàng)建一個進程的,而進程的終止是通過exit()或收到一個信號來完成的。它們的Linux實現(xiàn)位于kernel/fork.c和kernel/exit.c中。 派生出一個進程是很容易的,所以fork.c程序很短并易于理解。它的主要任務(wù)是為新的進程填寫數(shù)據(jù)結(jié)構(gòu)。除了填寫各個字段以外,相關(guān)的步驟有:
??取得一個空閑內(nèi)存頁面來保存task_struct
??找到一個空閑的進程槽(find_empty_process())
??為內(nèi)存堆棧頁kernel_stack_page取得另一個空閑的內(nèi)存頁面
??將父輩的LDT拷貝到子進程
??復(fù)制父進程的mmap信息
sys_fork() 同樣也管理文件描述符和inode。
?1.0的內(nèi)核也對線程提供某些不夠完善的支持,所以fork()系統(tǒng)調(diào)用對此也給出了某些示意。內(nèi)核的線程是主流內(nèi)核以外的過程產(chǎn)品。
從一個進程中退出是比較有竅門的,因為父進程必須被通告有關(guān)任何子進程的退出。而且,一個進程可以由另外一個進程使用kill()而退出(這些是Unix的特性),所以除了sys_exit()之外,sys_kill()以及sys_wait()的各種特性也存在于exit.c之中了。
這里不對exit.c的代碼加以討論---因為它一點也不令人感興趣。為了以一致的狀態(tài)退出系統(tǒng),它涉及到許多細節(jié)。而POSIX標準對于信號則是要求相當嚴格的,所以這里必須對其加以敘述。
執(zhí)行程序
在調(diào)用了fork()之后,就有同一個程序的兩個拷貝在運行了,通常一個程序使用exec()執(zhí)行另一個程序。exec()系統(tǒng)調(diào)用必須定位該執(zhí)行文件的二進制映像,加載并執(zhí)行它。詞語‘加載’并不一定意味著“將二進制映像拷貝進內(nèi)存”,因為Linux支持按需加載。 exec()的Linux實現(xiàn)支持不同的二進制格式。這是通過linux_binfmt結(jié)構(gòu)來達到的,其中內(nèi)嵌了兩個指向函數(shù)的指針--一個是用于加載可執(zhí)行文件的,另一個用于加載庫函數(shù),每種二進制格式都實現(xiàn)有這兩個函數(shù)。共享庫的加載是在exec()同一個源程序中實現(xiàn)的,但我們只討論exec()本身。 Unix系統(tǒng)提供了六種exec()函數(shù)。除了一個以外,所有都是以庫函數(shù)的形式實現(xiàn)的,并且,Linux內(nèi)核是單獨實現(xiàn)sys_execve()調(diào)用的。它執(zhí)行一個非常簡單的任務(wù):加載可執(zhí)行文件的頭部,并試著去執(zhí)行它。如果頭兩個字節(jié)是“#!”,那么就會解析該可執(zhí)行文件的第一行并調(diào)用一個解釋器來執(zhí)行它,否則的話,就會順序地試用各個注冊過的二進制格式。 Linux本身的格式是由fs/exec.c直接支持的,并且相關(guān)的函數(shù)是load_aout_binary和load_aout_library。對于二進制,函數(shù)將加載一個“a.out”可執(zhí)行文件并以使用mmap()加載磁盤文件或調(diào)用read_exec()而結(jié)束。前一種方法使用了Linux的按需加載機理,在程序被訪問時使用出錯加載方式(fault-in)加載程序頁面,而后一種方式是在主機文件系統(tǒng)不支持內(nèi)存映像時(例如“msdos”文件系統(tǒng))使用的。
?新近的1.1內(nèi)核內(nèi)嵌了一個修訂的msdos文件系統(tǒng),它支持mmap()。而且linux_binfmt結(jié)構(gòu)已是一個鏈表而不是一個數(shù)組了,以允許以一個內(nèi)核模塊的方式加載一個新的二進制格式。最后,結(jié)構(gòu)的本身也已經(jīng)被擴展成能夠訪問與格式相關(guān)的核心轉(zhuǎn)儲程序了。

非常好我支持^.^

(5) 100%

不好我反對

(0) 0%

      發(fā)表評論

      用戶評論
      評價:好評中評差評

      發(fā)表評論,獲取積分! 請遵守相關(guān)規(guī)定!

      ?