<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>

您好,歡迎來電子發燒友網! ,新用戶?[免費注冊]

您的位置:電子發燒友網>源碼下載>Linux/uClinux/Unix編程>

Linux內核源代碼漫游

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

用戶級別:注冊會員

貢獻文章:

貢獻資料:

Linux內核源代碼漫游

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

非常好我支持^.^

(5) 100%

不好我反對

(0) 0%

      發表評論

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

      發表評論,獲取積分! 請遵守相關規定!

      ?
      亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
      <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
      <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
      <acronym id="s8ci2"></acronym>
      <acronym id="s8ci2"><center id="s8ci2"></center></acronym>