乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Linux內(nèi)核源代碼漫游-執(zhí)行

       todaytomo 2006-12-30
      Linux內(nèi)核源代碼漫游
      藍(lán)森林 http://www. 2000年6月7日 09:42

      作 者: 趙炯

      Alessandro Rubini著, rubini@pop.systemy.it

      趙炯 譯,gohigh@shtdu.edu.cn

      本 章試圖以順序的方式來解釋Linux源代碼,以幫助讀者對(duì)源代碼的體系結(jié)構(gòu)以及很多相關(guān)的unix特性的實(shí)現(xiàn)有一個(gè)很好的理解。目標(biāo)是幫助對(duì)Linux不 甚了解的有經(jīng)驗(yàn)的C程序員對(duì)整個(gè)Linux的設(shè)計(jì)有所了解。這也就是為什么內(nèi)核漫游的入點(diǎn)選擇為內(nèi)核本身的啟始點(diǎn):系統(tǒng)引導(dǎo)(啟動(dòng))。

      這份材料需要對(duì)C語言以及對(duì)Unix的概念和PC機(jī)的結(jié)構(gòu)有很好的了解,然而本章中并沒有出現(xiàn)任何的C代碼,而是直接參考(指向)實(shí)際的代碼的。有關(guān)內(nèi)核設(shè)計(jì)的最佳篇幅是在本手冊的其它章節(jié)中,而本章仍趨向于是一個(gè)非正式的概述。

      本章中所參閱的任何文件的路徑名都是指主源代碼目錄樹,通常是/usr/src/linux。

      這里所給出的大多數(shù)信息都是取之于Linux發(fā)行版1.0的源代碼。雖然如此,有時(shí)也會(huì)提供對(duì)后期版本的參考。這篇漫游中開頭有 圖標(biāo)的任何小節(jié)都是強(qiáng)調(diào)1.0版本后對(duì)內(nèi)核的新的改動(dòng)。如果沒有這樣的小節(jié)存在,則表示直到版本1.0.9-1.1.76,沒有作過改動(dòng)。

      有時(shí)候本章中會(huì)有象這樣的小節(jié),這是指向正確的代碼以對(duì)剛討論過的主題取得更多信息的指示符。當(dāng)然,這里是指源代碼。

      引導(dǎo)(啟動(dòng))系統(tǒng)
      當(dāng)PC 的電源打開后,80x86結(jié)構(gòu)的CPU將自動(dòng)進(jìn)入實(shí)模式,并從地址0xFFFF0開始自動(dòng)執(zhí)行程序代碼,這個(gè)地址通常是ROM-BIOS中的地址。PC機(jī) 的BIOS將執(zhí)行某些系統(tǒng)的檢測,在物理地址0處開始初始化中斷向量。此后,它將可啟動(dòng)設(shè)備的第一個(gè)扇區(qū)讀入內(nèi)存地址0x7C00處,并跳轉(zhuǎn)到這個(gè)地方。 啟動(dòng)設(shè)備通常是軟驅(qū)或是硬盤。這里的敘述是非常簡單的,但這已經(jīng)足夠理解內(nèi)核初始化的工作過程了。

      Linux的最最前面部分是用8086匯編語言編寫的(boot/bootsect.S),它將 由BIOS讀入到內(nèi)存0x7C00處,當(dāng)它被執(zhí)行時(shí)就會(huì)把自己移到絕對(duì)地址0x90000處,并將啟動(dòng)設(shè)備(boot/setup.S)的下2kB字節(jié)的 代碼讀入內(nèi)存0x90200處,而內(nèi)核的其它部分則被讀入到地址0x10000處。在系統(tǒng)加載期間將顯示信息"Loading..."。然后控制權(quán)將傳遞 給boot/Setup.S中的代碼,這是另一個(gè)實(shí)模式匯編語言程序。

      啟動(dòng)部分識(shí)別主機(jī)的某些特性以及vga卡的類型。如果需要,它會(huì)要求用戶為控制臺(tái)選擇顯示模式。然后將整個(gè)系統(tǒng)從地址0x10000移至0x1000處,進(jìn)入保護(hù)模式并跳轉(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不能運(yùn)行于少于2兆內(nèi)存的主要原因。[在1兆內(nèi)存中解壓內(nèi)核的工作已經(jīng)完成,見 Memory Savers--ED]

      將內(nèi)核封裝在一個(gè)gzip文件中的工作是由zBoot目錄中的Makefile以及工具完成的。它們是值得一看的有趣的文件。

      內(nèi)核發(fā)行版1.1.75將boot和zBoot目錄下移到了arch/i386/boot中了,這個(gè)改動(dòng)意味著對(duì)不同的體系結(jié)構(gòu)允許真正的內(nèi)核建造,不過我將仍然只講解有關(guān)i386的信息。

      解壓過的代碼是從地址0x10100處開始執(zhí)行的[這里我可能忘記了具體的物理地址了,因?yàn)槲覍?duì)相應(yīng)的代碼不是很熟],在那里,所有32比特的設(shè)置啟動(dòng)被完成: IDT、GDT以及LDT被加載,處理器和協(xié)處理器也已確認(rèn),分頁工作也設(shè)置好了;最終調(diào)用start_kernel子程序。上述操作的源代碼是在boot/head.S中的,這可能是整個(gè)內(nèi)核中最有訣竅的代碼了。

      注意如果在前述任何一步中出了錯(cuò),計(jì)算機(jī)就會(huì)死鎖。在操作系統(tǒng)還沒有完全運(yùn)轉(zhuǎn)之前是處理不了出錯(cuò)的。

      start_kernel()是位于init/main.c中的,并且沒有任何返回結(jié)果。從現(xiàn)在起的任何代碼都是用C語言編制的,除了中斷管理和系統(tǒng)調(diào)用的入/出代碼(當(dāng)然,還有大多數(shù)的宏都嵌入了匯編代碼)。

      讓輪子轉(zhuǎn)動(dòng)起來
      在處理了所有錯(cuò)綜復(fù)雜的問題之后,start_kernel()初始化了內(nèi)核的所有部分,尤其是:

      設(shè)置內(nèi)存邊界和調(diào)用paging_init();
      初始化中斷、IRQ通道和調(diào)度;
      分析(解析)命令行;
      如果需要,就分配一個(gè)數(shù)據(jù)緩沖區(qū)(profiling buffer)以及其它一些小部分;
      校正延遲循環(huán)(計(jì)算“BogoMips”數(shù));
      檢查中斷16是否能與協(xié)處理器工作。
      最后,為了生成初始進(jìn)程,內(nèi)核準(zhǔn)備好了移至move_to_user_mode(),它的代碼也是在同一個(gè)源代碼文件中的。然后,所謂的空閑任務(wù),進(jìn)程號(hào)0就進(jìn)入無限的空閑循環(huán)中運(yùn)行。

      接著初始進(jìn)程(init process)嘗試著運(yùn)行/etc/init、/bin/init或者/sbin/init。

      如果它們沒有一個(gè)運(yùn)行成功的,就會(huì)去執(zhí)行代碼“/bin/sh /etc/rc”并且在第一個(gè)終端上生成一個(gè)根命令解釋程序(root shell)。這段代碼回溯至Linux 0.01,當(dāng)時(shí)操作系統(tǒng)只有一個(gè)內(nèi)核,并且沒有登錄進(jìn)程。

      在 從一個(gè)標(biāo)準(zhǔn)的地方(讓我們假定我們有)用exec()執(zhí)行了init初始化程序之后,內(nèi)核就對(duì)程序的執(zhí)行沒有了直接的控制。從現(xiàn)在起它的規(guī)則是提供對(duì)系統(tǒng) 調(diào)用的處理,以及為異步事件服務(wù)(比如硬件中斷等)。多任務(wù)的環(huán)境已經(jīng)建立,從現(xiàn)在起是init程序通過fork()派生出的系統(tǒng)進(jìn)程和登錄進(jìn)程來管理多 用戶的訪問了。

      由于內(nèi)核是負(fù)責(zé)提供服務(wù)的,這個(gè)漫游文章將通過觀察這些服務(wù)(“系統(tǒng)調(diào)用”)以及通過提供基本數(shù)據(jù)結(jié)構(gòu)的原理和代碼的組織結(jié)構(gòu)繼續(xù)討論下去。

      內(nèi)核是如何看見一個(gè)進(jìn)程的
      從內(nèi)核的觀點(diǎn)來看,一個(gè)進(jìn)程只是進(jìn)程表中的一個(gè)條目而已。

      而 進(jìn)程表以及各個(gè)內(nèi)存管理表和緩沖存儲(chǔ)器則是系統(tǒng)中最為重要的數(shù)據(jù)結(jié)構(gòu)。進(jìn)程表中的各個(gè)單項(xiàng)是task_struct結(jié)構(gòu),是定義在 include/linux/sched.h中的非常大的數(shù)據(jù)結(jié)構(gòu)。在task_struct中保留著從低層到高層的信息,范圍從某些硬件寄存器的拷貝到 進(jìn)程工作目錄的inode信息。

      進(jìn)程表既是一個(gè)數(shù)組和雙鏈表,也是一個(gè)樹結(jié)構(gòu)。它的物理實(shí)現(xiàn)是一個(gè)靜態(tài)的指針數(shù)組,它的長度是定 義在include/linux/tasks.h中的常量NR_TASKS,并且每個(gè)結(jié)構(gòu)都位于一個(gè)保留內(nèi)存頁中。這個(gè)列表結(jié)構(gòu)是通過指針 next_task和pre_task構(gòu)成的,而樹結(jié)構(gòu)則是非常復(fù)雜的并且我們在此將不加以討論。你可能希望改動(dòng)NR_TASKS的默認(rèn)值128,但你要 保證所有源文件中相關(guān)的適當(dāng)文件都要被重新編譯過。

      在啟動(dòng)引導(dǎo)過程結(jié)束后,內(nèi)核將總是代表某個(gè)進(jìn)程而工作,并且全局變量current --- 一個(gè)指向某個(gè)task_struct條目的指針 --- 被用于記錄正在運(yùn)行的進(jìn)程。current僅能通過在kernel/sched.c中的調(diào)度程序來改變。然而,由于所有的進(jìn)程都必須訪問它,所以使用了宏for_each_task。當(dāng)系統(tǒng)負(fù)荷很輕時(shí),它要比數(shù)組的順序掃描快得多。

      進(jìn)程總是運(yùn)行于“用戶模式”或“內(nèi)核模式”。用戶程序的主體是運(yùn)行于用戶模式而其中的系統(tǒng)調(diào)用則運(yùn)行于內(nèi)核模式中。在這兩種執(zhí)行模式中進(jìn)程所用的堆棧是不一樣的 -- 常規(guī)的堆棧段用于用戶模式,而一個(gè)固定大小的堆棧(一頁,由該進(jìn)程所有)則用于內(nèi)核模式。內(nèi)核堆棧頁是從不交換出去的,因?yàn)槊慨?dāng)一個(gè)系統(tǒng)調(diào)用進(jìn)入時(shí)它就必須存在著。

      內(nèi)核中的系統(tǒng)調(diào)用(system calls)是作為C語言函數(shù)存在的,它們的‘正規(guī)’名稱是以‘sys_’開頭的。例如一個(gè)名為burnout的系統(tǒng)調(diào)用將調(diào)用內(nèi)核函數(shù)sys_burnout()。

      系統(tǒng)調(diào)用機(jī)制在本手冊的第三章中進(jìn)行了討論。觀看在include/linux/sched.h中的for_each_task和SET_LINKS能夠幫助理解進(jìn)程表中的列表和樹結(jié)構(gòu)。

      創(chuàng)建和結(jié)束進(jìn)程
      unix系統(tǒng)是通過fork()系統(tǒng)調(diào)用創(chuàng)建一個(gè)進(jìn)程的,而進(jìn)程的終止是通過exit()或收到一個(gè)信號(hào)來完成的。它們的Linux實(shí)現(xiàn)位于kernel/fork.c和kernel/exit.c中。 派生出一個(gè)進(jìn)程是很容易的,所以fork.c程序很短并易于理解。它的主要任務(wù)是為新的進(jìn)程填寫數(shù)據(jù)結(jié)構(gòu)。除了填寫各個(gè)字段以外,相關(guān)的步驟有:

      取得一個(gè)空閑內(nèi)存頁面來保存task_struct
      找到一個(gè)空閑的進(jìn)程槽(find_empty_process())
      為內(nèi)存堆棧頁kernel_stack_page取得另一個(gè)空閑的內(nèi)存頁面
      將父輩的LDT拷貝到子進(jìn)程
      復(fù)制父進(jìn)程的mmap信息
      sys_fork() 同樣也管理文件描述符和inode。
      1.0的內(nèi)核也對(duì)線程提供某些不夠完善的支持,所以fork()系統(tǒng)調(diào)用對(duì)此也給出了某些示意。內(nèi)核的線程是主流內(nèi)核以外的過程產(chǎn)品。

      從 一個(gè)進(jìn)程中退出是比較有竅門的,因?yàn)楦高M(jìn)程必須被通告有關(guān)任何子進(jìn)程的退出。而且,一個(gè)進(jìn)程可以由另外一個(gè)進(jìn)程使用kill()而退出(這些是Unix的 特性),所以除了sys_exit()之外,sys_kill()以及sys_wait()的各種特性也存在于exit.c之中了。

      這里不對(duì)exit.c的代碼加以討論---因?yàn)樗稽c(diǎn)也不令人感興趣。為了以一致的狀態(tài)退出系統(tǒng),它涉及到許多細(xì)節(jié)。而POSIX標(biāo)準(zhǔn)對(duì)于信號(hào)則是要求相當(dāng)嚴(yán)格的,所以這里必須對(duì)其加以敘述。

      執(zhí)行程序
      在 調(diào)用了fork()之后,就有同一個(gè)程序的兩個(gè)拷貝在運(yùn)行了,通常一個(gè)程序使用exec()執(zhí)行另一個(gè)程序。exec()系統(tǒng)調(diào)用必須定位該執(zhí)行文件的二 進(jìn)制映像,加載并執(zhí)行它。詞語‘加載’并不一定意味著“將二進(jìn)制映像拷貝進(jìn)內(nèi)存”,因?yàn)長inux支持按需加載。 exec()的Linux實(shí)現(xiàn)支持不同的二進(jìn)制格式。這是通過linux_binfmt結(jié)構(gòu)來達(dá)到的,其中內(nèi)嵌了兩個(gè)指向函數(shù)的指針--一個(gè)是用于加載可 執(zhí)行文件的,另一個(gè)用于加載庫函數(shù),每種二進(jìn)制格式都實(shí)現(xiàn)有這兩個(gè)函數(shù)。共享庫的加載是在exec()同一個(gè)源程序中實(shí)現(xiàn)的,但我們只討論exec()本 身。 Unix系統(tǒng)提供了六種exec()函數(shù)。除了一個(gè)以外,所有都是以庫函數(shù)的形式實(shí)現(xiàn)的,并且,Linux內(nèi)核是單獨(dú)實(shí)現(xiàn)sys_execve()調(diào)用 的。它執(zhí)行一個(gè)非常簡單的任務(wù):加載可執(zhí)行文件的頭部,并試著去執(zhí)行它。如果頭兩個(gè)字節(jié)是“#!”,那么就會(huì)解析該可執(zhí)行文件的第一行并調(diào)用一個(gè)解釋器來 執(zhí)行它,否則的話,就會(huì)順序地試用各個(gè)注冊過的二進(jìn)制格式。 Linux本身的格式是由fs/exec.c直接支持的,并且相關(guān)的函數(shù)是load_aout_binary和load_aout_library。對(duì)于 二進(jìn)制,函數(shù)將加載一個(gè)“a.out”可執(zhí)行文件并以使用mmap()加載磁盤文件或調(diào)用read_exec()而結(jié)束。前一種方法使用了Linux的按 需加載機(jī)理,在程序被訪問時(shí)使用出錯(cuò)加載方式(fault-in)加載程序頁面,而后一種方式是在主機(jī)文件系統(tǒng)不支持內(nèi)存映像時(shí)(例如“msdos”文件 系統(tǒng))使用的。

      新近的1.1內(nèi)核內(nèi)嵌了一個(gè)修訂的msdos文件系統(tǒng),它支持mmap()。而且linux_binfmt結(jié)構(gòu)已是一個(gè)鏈表而不是一個(gè)數(shù)組了,以允許以一個(gè)內(nèi)核模塊的方式加載一個(gè)新的二進(jìn)制格式。最后,結(jié)構(gòu)的本身也已經(jīng)被擴(kuò)展成能夠訪問與格式相關(guān)的核心轉(zhuǎn)儲(chǔ)程序了。

      訪問文件系統(tǒng)
      眾所周知,文件系統(tǒng)是Unix系統(tǒng)中最為基本的資源了,它如此的基本和普遍存在以至于它需要一個(gè)更為便利的名字--我將忠于標(biāo)準(zhǔn)的稱呼簡單地稱之為“fs”。

      我 將假設(shè)讀者早已知道基本的Unix文件系統(tǒng)的原理--訪問(權(quán)限)許可、i節(jié)點(diǎn)(inode)、超級(jí)塊、加載(mount)和卸載(umount)文件系 統(tǒng)。這些概念在標(biāo)準(zhǔn)的Unix文獻(xiàn)中由比我聰明的作者給出了很好的解釋,所以我就不重復(fù)他們的工作并且我將只專注于有關(guān)Linux方面的問題。

      早期的Unix通常只支持一個(gè)文件系統(tǒng)(fs)類型,它的代碼散布于整個(gè)內(nèi)核中,現(xiàn)今的實(shí)現(xiàn)是在 內(nèi)核和fs之間使用一個(gè)標(biāo)準(zhǔn)的接口,以便于在不同的體系結(jié)構(gòu)中進(jìn)行數(shù)據(jù)的交換。Linux本身提供了一個(gè)標(biāo)準(zhǔn)層以在內(nèi)核和每種fs模塊之間傳遞數(shù)據(jù)。這個(gè) 接口層稱為VFS,即“虛擬文件系統(tǒng)”("virtual filesystem")。

      因而文件系統(tǒng)的代碼被分割成了兩層:上層是關(guān)于內(nèi)核表格的管理和數(shù)據(jù)結(jié)構(gòu)的,而低層是由與各文件系統(tǒng)相關(guān)的函數(shù)集構(gòu)成的,并且是由VFS數(shù)據(jù)結(jié)構(gòu)進(jìn)行調(diào)用的。

      所有與文件系統(tǒng)獨(dú)立的資料都位于fs/*.c文件中。它們涉及如下的問題:

      管理緩沖寄存器(buffer.c);
      對(duì)fcntl()和ioctl()系統(tǒng)調(diào)用作出響應(yīng)(fcntl.c和ioctl.c);
      在inode和緩沖區(qū)上映射管道和fifo(fifo.c,pipe.c);
      管理文件 - 和inode - 表(file_table.c,inode.c);
      鎖定和解鎖文件和記錄(lock.c);
      將名稱映射到inode(namei.c,open.c);
      實(shí)現(xiàn)錯(cuò)綜復(fù)雜的select()函數(shù)(select.c);
      提供信息(stat.c);
      加載和卸載文件系統(tǒng)(super.c);
      使用exec()執(zhí)行可執(zhí)行程序以及轉(zhuǎn)儲(chǔ)核心程序(exec.c);
      加載各種二進(jìn)制格式(bin_fmt*.c,如上面所述)。
      而VFS 接口則由一組相對(duì)比較高層次的操作組成,并從與文件系統(tǒng)獨(dú)立的代碼中調(diào)用而實(shí)際上是由每種文件系統(tǒng)類型執(zhí)行的。最為相關(guān)的數(shù)據(jù)結(jié)構(gòu)是 inode_operations和file_operations,盡管它們不是獨(dú)自存在的:同樣存在著其它一些數(shù)據(jù)結(jié)構(gòu)。它們都定義在 include/linux/fs.h文件中。

      到實(shí)際文件系統(tǒng)的內(nèi)核入口點(diǎn)是數(shù)據(jù)結(jié)構(gòu)file_system_type。 file_system_types的一個(gè)數(shù)組包含在fs/filesystems.c中,并且每當(dāng)發(fā)出了一個(gè)加載(mount)命令時(shí)都會(huì)引用它。然 后,相應(yīng)fs類型的函數(shù)read_super就負(fù)責(zé)填寫結(jié)構(gòu)super_block的一個(gè)項(xiàng),而該項(xiàng)又內(nèi)嵌了結(jié)構(gòu)super_struct和結(jié)構(gòu) type_sb_info。前者為當(dāng)前的fs類型提供了指向一般fs操作的指針,而后者對(duì)相應(yīng)fs類型內(nèi)嵌了特定的信息。

      文件系統(tǒng)類型數(shù)組已經(jīng)轉(zhuǎn)換成了一個(gè)鏈表,以允許用內(nèi)核模塊的形式加載新的fs類型。函數(shù)(un-)register_filesystem代碼包含在fs/super.c中。

      一個(gè)文件系統(tǒng)類型的快速剖析
      一個(gè)文件系統(tǒng)類型的任務(wù)是執(zhí)行用于映射相應(yīng)高層VFS操作到物理介質(zhì)(磁盤、網(wǎng)絡(luò)等等)的低層任務(wù)。VFS接口有足夠的靈活性來支持傳統(tǒng)的Unix文件系統(tǒng)和外來的象msdos和umsdos文件系統(tǒng)類型。

      每一個(gè)fs類型除了它自己的源代碼目錄以外,是由下列各項(xiàng)組成的:

      file_systems[]數(shù)組中的一個(gè)條目(項(xiàng)) (fs/filesystems.c);
      超級(jí)塊(superblock)的include文件(include/linux/type_fs_sb.h);
      i節(jié)點(diǎn)(inode)的include文件(include/linux/type_fs_i.h);
      普通自己專用的include文件(include/linux/type_fs.h);
      include/linux.fs.h中的兩行#include,以及在結(jié)構(gòu)super_block和inode中的條目。
      對(duì)于特定fs類型自己的目錄,包含有所有的實(shí)際代碼、inode和數(shù)據(jù)的管理程序。

      本手冊中有關(guān)procfs的章節(jié),揭示了所有有關(guān)那種fs類型的低層代碼和VFS接口。在閱讀過那個(gè)章節(jié)之后,fs/procfs中的源代碼就顯得非常容易理解了。

      現(xiàn) 在我們來觀察VFS機(jī)制的內(nèi)部工作情況,并以minix文件系統(tǒng)的代碼作為一個(gè)實(shí)際例子。我選擇minix類型是因?yàn)樗容^短小但卻是完整的;而且, Linux中的所有其它的fs類型都衍生于它。在最近Linux安裝中的事實(shí)上的標(biāo)準(zhǔn)文件系統(tǒng)類型ext2,要比它復(fù)雜得多,對(duì)ext2這個(gè)文件系統(tǒng)的探 索就留給聰明的讀者作為一個(gè)練習(xí)了。

      當(dāng)一個(gè)minix-fs被加載后,minix_read_super就會(huì)把從被加載的設(shè)備中讀取的數(shù)據(jù)添入super_block數(shù)據(jù)結(jié)構(gòu)中。此時(shí),該結(jié)構(gòu)中的s_op域?qū)⒈A粲幸粋€(gè)指向minix_sops的指針,該指針將被一般文件系統(tǒng)代碼用于分派超級(jí)塊的操作。

      在全局系統(tǒng)樹結(jié)構(gòu)中鏈接新加載的fs依賴于下列各數(shù)據(jù)項(xiàng)(假設(shè)sb是超級(jí)塊數(shù)據(jù)結(jié)構(gòu),而dir_i是指向加載點(diǎn)的inode的指針):

      sb->s_mounted指向被加載文件系統(tǒng)的根目錄i節(jié)點(diǎn)(MINIX_ROOT_INO);
      dir_i->i_mount保存有sb->s_mounted;
      sb->s_covered保存有dir_i
      卸載操作將最終通過do_umount來執(zhí)行,而它會(huì)依次調(diào)用minix_put_super。

      每 當(dāng)訪問一個(gè)文件時(shí),minix_read_inode就會(huì)開始執(zhí)行;它會(huì)使用minix_inode各字段中的數(shù)據(jù)填寫系統(tǒng)范圍的inode數(shù)據(jù)結(jié)構(gòu)。 inode->i_op字段是依照inode->i_mode來填寫的,它將負(fù)責(zé)該文件的任何其它操作。上述minix函數(shù)的代碼可以從 fs/minix/inode.c中找到。

      inode_operations數(shù)據(jù)結(jié)構(gòu)是用于把inode操作分派給特定fs類型的內(nèi)核函 數(shù);該數(shù)據(jù)結(jié)構(gòu)的第一項(xiàng)是一個(gè)指向file_operations項(xiàng)的指針,它等同于數(shù)據(jù)管理的i_op。minix文件系統(tǒng)類型允許有inode操作集 中的三種方式(用于目錄、文件和符號(hào)鏈接)和文件操作集中的兩種(符號(hào)鏈接不需要文件操作)。

      目錄操作(僅minix_readdir)位于fs/minix/dir.c中;文件操作(讀read和寫write)位于fs/minix/file.c中而符號(hào)操作(讀取并跟隨著鏈)位于fs/minix/symlink.c。

      minix源代碼目錄中的其余部分用于實(shí)現(xiàn)以下任務(wù):

      bitmap.c用于管理i節(jié)點(diǎn)與塊的分配和釋放(而ext2文件系統(tǒng)卻有兩個(gè)不同的代碼文件);
      fsynk.c用于fsync()系統(tǒng)調(diào)用--它管理直接、間接和雙重間接塊(我假定你是知道這些術(shù)語的,因?yàn)檫@是Unix的普通知識(shí));
      namei.c內(nèi)嵌有所有與名字有關(guān)的i節(jié)點(diǎn)的操作,比如象節(jié)點(diǎn)的創(chuàng)建和消除、重命名和鏈接;
      truncate.c執(zhí)行文件的截?cái)嗖僮鳌?
      控制臺(tái)驅(qū)動(dòng)程序(console driver)
      作為大多數(shù)Linux系統(tǒng)上的主要I/O設(shè)備,控制臺(tái)驅(qū)動(dòng)程序是應(yīng)該受到某些關(guān)注的。有關(guān)控制臺(tái)和其它字符驅(qū)動(dòng)程序的源代碼可以在drivers/char中找到,當(dāng)我們指稱文件時(shí),我們將使用這個(gè)特定的目錄。

      控制臺(tái)的初始化是由tty_io.c中的tty_init()函數(shù)來執(zhí)行的。這個(gè)函數(shù)僅僅涉及取得每個(gè)設(shè)備集的主設(shè)備號(hào)并調(diào)用每個(gè)設(shè)備集的init函數(shù)。而con_init()則是與控制臺(tái)相關(guān)的函數(shù),并存在于console.c中。

      在 內(nèi)核1.1的開發(fā)中,控制臺(tái)的初始化已經(jīng)有了很大的變化。console_init()已經(jīng)從tty_init()中脫離出來了,并且是由../.. /main.c直接調(diào)用的?,F(xiàn)在虛擬控制臺(tái)是動(dòng)態(tài)分配的,其代碼也已有了很大的變化。所以我將跳過初始化、分配等等的詳細(xì)討論。

      文件操作是如何分派給控制臺(tái)的
      這一節(jié)是相當(dāng)?shù)讓拥挠懻?,你可以放心地跳過本節(jié)。

      毫無疑問,Unix設(shè)備是通過文件系統(tǒng)來訪問的。本節(jié)將詳細(xì)描述從設(shè)備文件到實(shí)際控制臺(tái)函數(shù)的所有步驟,而且,以下的信息是從內(nèi)核的1.1.73源代碼中抽取來的,它與1.0的代碼可能少許有點(diǎn)不同。

      當(dāng) 打開一個(gè)設(shè)備i節(jié)點(diǎn)時(shí),在../../fs/devices.c中的chrdev_open()函數(shù)(或者是blkdev_open(),但我只專注于字 符設(shè)備)將被執(zhí)行。這個(gè)函數(shù)是通過數(shù)據(jù)結(jié)構(gòu)def_chr_fops取得的,而它又是被chrdev_inode_operations引用的,是被所有 文件系統(tǒng)類型使用的(見前面有關(guān)文件系統(tǒng)的部分)。

      chrdev_open通過在當(dāng)前操作中替換具體設(shè)備的file_operations表并且調(diào) 用特定的open()函數(shù)來管理指定的設(shè)備操作的。具體設(shè)備的表結(jié)構(gòu)是保存在數(shù)組chrdevs[]中的,并由主設(shè)備號(hào)作為索引,位于同一個(gè)../.. /devices.c中。

      如果該設(shè)備是一個(gè)tty類型的(我們不是只關(guān)注控制臺(tái)嗎?),我們就來討論tty的設(shè)備驅(qū)動(dòng)程 序,它們的函數(shù)在tty_io.c之中,由tty_fops作為索引。這樣,tty_open()就會(huì)調(diào)用init_dev(),而init_dev() 就會(huì)根據(jù)次設(shè)備號(hào)為設(shè)備分配任何所需的數(shù)據(jù)結(jié)構(gòu)。

      次設(shè)備號(hào)也用于檢索已經(jīng)使用tty_register_driver()注冊登記過的設(shè)備的實(shí)際 驅(qū)動(dòng)程序。而且,該驅(qū)動(dòng)程序仍是另一個(gè)用于分派計(jì)算的數(shù)據(jù)結(jié)構(gòu),正如file_ops一樣;它是與設(shè)備的寫操作和控制有關(guān)的。最后一個(gè)用于管理tty的數(shù) 據(jù)結(jié)構(gòu)是線路規(guī)程,這將在后面敘述??刂婆_(tái)(以及任何其它的tty設(shè)備)的線路規(guī)程是由initialize_tty_struct()設(shè)置的,并由 init_dev調(diào)用的。

      在這一節(jié)中我們所涉及的所有事情都是與設(shè)備無關(guān)的,僅有與特定控制臺(tái)相關(guān)的是console.c,在con_init()操作期間已經(jīng)注冊了自己的驅(qū)動(dòng)程序。相反,線路規(guī)程是與設(shè)備無關(guān)的。

      The tty_driver 數(shù)據(jù)結(jié)構(gòu)在 中有著完整的描述。

      上述信息是從1.1.73源代碼中取得的。它是有可能與你的內(nèi)核有所不同的(“如信息有所變動(dòng)將不另行通知”)。

      控制臺(tái)寫操作
      當(dāng) 往一個(gè)控制臺(tái)設(shè)備進(jìn)行寫操作時(shí),就會(huì)調(diào)用con_write函數(shù)。這個(gè)函數(shù)管理所有控制字符和換碼字符序列,這些字符給應(yīng)用程序提供全部的屏幕管理操作。 所實(shí)現(xiàn)的換碼序列是vt102終端的;這意味著當(dāng)你使用telnet連接到一臺(tái)非Linux主機(jī)時(shí),你的環(huán)境變量應(yīng)該有TERM=vt102;然而,對(duì)于 本地操作最佳的選擇是設(shè)置TERM=console,因?yàn)長inux控制臺(tái)提供了一個(gè)vt102功能的超集。

      因而,con_write()主要是由轉(zhuǎn)換語句組成的,用于處理每一次一個(gè)字符的有限長狀態(tài)自動(dòng) 換碼序列的解釋。在正常方式下,所打印的字符是使用當(dāng)前屬性直接寫到顯示內(nèi)存中的。在console.c中,數(shù)據(jù)結(jié)構(gòu)vc的所有域使用宏都是可訪問的,所 以(例如)任何對(duì)attr的引用,只要currcons是所指的控制臺(tái)的號(hào)碼,確實(shí)是引證了數(shù)據(jù)結(jié)構(gòu)vc_cons[currcons]中的域。

      實(shí)際上,新內(nèi)核中的vc_cons已不再是一個(gè)數(shù)據(jù)結(jié)構(gòu)數(shù)組了,現(xiàn)在它是指針的數(shù)組,其內(nèi)容是用kmalloc()操作的。宏的使用大大地簡化了代碼修改的工作,因?yàn)樵S多代碼都不需要被重寫。

      控 制臺(tái)內(nèi)存到屏幕內(nèi)存的實(shí)際映射和非映射是由函數(shù)set_scrmem()(它把控制臺(tái)緩沖區(qū)中的數(shù)據(jù)拷貝到顯示內(nèi)存中)和get_srcmem()(它把 數(shù)據(jù)拷貝回控制臺(tái)緩沖區(qū)中)執(zhí)行的。為了減少數(shù)據(jù)傳輸?shù)拇螖?shù),當(dāng)前控制臺(tái)的私有緩沖區(qū)是物理地映射到實(shí)際顯示RAM上的。這意味著console.c中的 get-和set-_scrmem()是靜態(tài)的,并且僅在一個(gè)控制臺(tái)轉(zhuǎn)換期間才被調(diào)用。

      控制臺(tái)讀操作
      控制臺(tái)讀操作是由線路規(guī)程來完成 的。Linux中默認(rèn)的(也是唯一的)線路規(guī)程被稱為tty_ldisc_N_TTY。線路規(guī)程也就是“通過一線路約束輸入”。它是另一個(gè)函數(shù)表(我們已 習(xí)慣了這種方法,不是嗎?),它是有關(guān)于設(shè)備讀操作的。在termios標(biāo)志的幫助下,線路規(guī)程也即是從tty上控制輸入的規(guī)程:未處理過的數(shù)據(jù)、 cbreak和計(jì)劃的方式;select();ioctl()等等。

      線路規(guī)程中的讀(read)函數(shù)稱為read_chan(),它讀取tty的緩沖區(qū)而不管數(shù)據(jù)是從哪里來的。原因是通過一個(gè)tty來到的字符是由異步硬件中斷管理的。

      線路規(guī)程N(yùn)_TTY也同樣在tty_io.c中,盡管以后出的內(nèi)核都使用一個(gè)不同的n_tty.c源程序。

      控制臺(tái)輸入的最底層是鍵盤管理的一部分,因此它是在keyboard.c的keyboard_interrupt()中處理的。

      鍵盤管理
      鍵盤管理簡直是一場噩夢。它限于文件keyboard.c中,里面充滿了表示不同廠家鍵盤的各個(gè)鍵碼的十六進(jìn)制數(shù)。

      我將不對(duì)keyboard.c進(jìn)行深入討論,因?yàn)槠渲袥]有與內(nèi)核研究者有關(guān)的相關(guān)信息。

      對(duì)于那些對(duì)Linux的鍵盤編程確實(shí)感興趣的人,最好的方法是從keyboard.c的最后一行往回看起。最底層的細(xì)節(jié)是在該文件的上半部分。

      轉(zhuǎn)換當(dāng)前控制臺(tái)
      當(dāng)前控制臺(tái)是通過使用函數(shù)change_console()來轉(zhuǎn)換的,它位于tty_io.c中由keyboard.c和vt.c調(diào)用(前者響應(yīng)按鍵的控制臺(tái)轉(zhuǎn)換,后者是當(dāng)一個(gè)程序通過引用一個(gè)ioctl()調(diào)用時(shí)轉(zhuǎn)換控制臺(tái))。

      實(shí) 際的轉(zhuǎn)換過程是分兩步來執(zhí)行的,函數(shù)complete_change_console()處理其中的第二部分。轉(zhuǎn)換的分裂意味著在一個(gè)與控制著我們正在離 開的tty的進(jìn)程的可能的握手以后完成任務(wù)。如果控制臺(tái)不在進(jìn)程控制之下,change_console()就會(huì)自己調(diào)用 complete_change_console()。進(jìn)程需要足夠的能力來成功地完成從圖形到文本控制臺(tái)或從文本到圖形控制臺(tái)的轉(zhuǎn)換,并且X服務(wù)器(例 如)是其圖形控制臺(tái)的控制進(jìn)程。

      選擇機(jī)制
      “選擇(selection)”是 Linux文本控制臺(tái)的剪切(cut)與粘貼(paste)功能。這個(gè)技巧主要是由用戶級(jí)的進(jìn)程來處理的,它可以用selection或gpm的具體例子 說明。用戶級(jí)的程序在控制臺(tái)上使用ioctl()通知內(nèi)核來加亮顯示屏幕的一個(gè)區(qū)域。然后,被選擇的文本被拷貝到一個(gè)選擇緩沖區(qū)。該緩沖區(qū)是 console.c中的一個(gè)靜態(tài)實(shí)體。粘貼文本操作是通過“手工地”將字符放入tty輸入隊(duì)列中完成的。整個(gè)選擇機(jī)制是通過#ifdef受到保護(hù)的,所以 用戶在內(nèi)核配置期間可以禁用它以節(jié)省幾千字節(jié)的內(nèi)存。

      選擇是一個(gè)非常低級(jí)的功能,因而它工作是任何其它內(nèi)核活動(dòng)所看不見的。這意味著許多的#ifdef只是屏幕在以任何方式作修改之前簡單地移動(dòng)加亮部分。

      新內(nèi)核特性改善了選擇的代碼,鼠標(biāo)指針的加亮可以與被選擇的文本獨(dú)立(內(nèi)核1.1.23或更高)。而且,從1.1.73版起,被選擇的文本使用了動(dòng)態(tài)的緩沖區(qū)而不是靜態(tài)的了,使得內(nèi)核小了4KB。

      使用ioctl()操作設(shè)備
      ioctl ()系統(tǒng)調(diào)用是用戶進(jìn)程控制設(shè)備文件行為的入口點(diǎn)。Ioctl管理是從../../fs/ioctl.c中產(chǎn)生的,實(shí)際上sys_ioctl()就是在這 個(gè)ioctl.c中的。標(biāo)準(zhǔn)的ioctl請(qǐng)求就是在那里執(zhí)行的,其它與文件相關(guān)的請(qǐng)求是由file_ioctl()處理的(在同一個(gè)源文件中),而其它任 何請(qǐng)求都分派給特定設(shè)備的ioctl()函數(shù)。

      控制臺(tái)設(shè)備的ioctl資料是位于vt.c中的,因?yàn)榭刂婆_(tái)驅(qū)動(dòng)程序要將ioctl請(qǐng)求分派給vt_ioctl()。

      上述信息是關(guān)于內(nèi)核1.1.7x的。1.0內(nèi)核是沒有“驅(qū)動(dòng)程序”表的,而且vt_ioctl()是直接由file_operations()表指向的。

      Ioctl的資料確實(shí)是相當(dāng)讓人混淆的。有些請(qǐng)求是與設(shè)備相關(guān)的,而有些卻是與線路規(guī)程相關(guān)的。我將試圖對(duì)1.0和1.1.7x內(nèi)核之間發(fā)生的任何事概要總結(jié)一下。

      1.1.7x 系列內(nèi)核有如下的特性:tty_ioctl.c只實(shí)現(xiàn)了線路規(guī)程請(qǐng)求(也就是n_tty_ioctl(),這是唯一在n_tty.c外面的n_tty函 數(shù)),而file_operations字段指向tty_io.c中的tty_ioctl()。如果請(qǐng)求號(hào)沒有被tty_ioctl()解析出來,它就會(huì) 被傳到tty->driver.ioctl或者,如果它失敗時(shí),就到tty->ldisc.ioctl??刂婆_(tái)的與驅(qū)動(dòng)程序相關(guān)的資料可以從 vt.c中找到,而線路規(guī)程方面的資料則在tty_ioctl.c中。

      在1.0內(nèi)核中,tty_ioctl()是在tty_ioctl.c中的并有一般tty的file_operations所指向。未被解析出的請(qǐng)求將用與1.1.7x相似的方法被傳送到特定的ioctl函數(shù)或到線路規(guī)程代碼去。

      注 意,在這兩種情況中,TIOCLINUX請(qǐng)求是在與設(shè)備無關(guān)的代碼中的,這暗示著控制臺(tái)選擇操作可以通過ioctl對(duì)任何tty進(jìn)行操作來設(shè)置 (set_selection()總是在控制臺(tái)前臺(tái)上操作的),而這是一個(gè)安全上的漏洞。這也是轉(zhuǎn)移到一個(gè)更新的內(nèi)核的很好理由,在新內(nèi)核中,通過僅允許 超級(jí)用戶來處理選擇彌補(bǔ)了這個(gè)漏洞。

      有很多請(qǐng)求可以被發(fā)給控制臺(tái)設(shè)備,而知道它們的最好方法是瀏覽源程序文件vt.c。


      版權(quán)所有(c) 1994 Alessandro Rubini, rubini@pop.systemy.it

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多