linux 内核源代码漫游,Linux内核源代码漫游——
Linux內(nèi)核源代碼漫游
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ì)的最佳篇幅是在本手冊(cè)的其它章節(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ù)雜的并且我們?cè)诖藢⒉患右杂懻摗D憧赡?/p>
希望改動(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ī)制在本手冊(cè)的第三章中進(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)存”,因
為Linux支持按需加載。 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è)注冊(cè)過的二進(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ù)的管理程序。
本手冊(cè)中有關(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)嗖僮鳌?/p>
控制臺(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)用的。現(xiàn)在虛擬
控制臺(tái)是動(dòng)態(tài)分配的,其代碼也已有了很大的變化。所以我將跳過初始化、分配
等等的詳細(xì)討論。
文件操作是如何分派給控制臺(tái)的
這一節(jié)是相當(dāng)?shù)讓拥挠懻?#xff0c;你可以放心地跳過本節(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()注冊(cè)登記過的設(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)注冊(cè)了自己的驅(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()操作的。宏的使用大大地簡化了代碼修改的工作,因
為許多代碼都不需要被重寫。
控制臺(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()中處理的。
鍵盤管理
鍵盤管理簡直是一場噩夢(mèng)。它限于文件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
總結(jié)
以上是生活随笔為你收集整理的linux 内核源代码漫游,Linux内核源代码漫游——的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux游戏欢迎界面,制作Linux登
- 下一篇: 手机远程ssh登录Linux,Linux