linux 内核源代码漫游,Linux内核源代码漫游——
Linux內核源代碼漫游
Alessandro Rubini著, rubini@pop.systemy.it
趙炯 譯,gohigh@shtdu.edu.cn
本章試圖以順序的方式來解釋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結構已是一個鏈表而不是一個數組了,以允許以一個內核模塊的方式加載
一個新的二進制格式。最后,結構的本身也已經被擴展成能夠訪問與格式相關的
核心轉儲程序了。
訪問文件系統
眾所周知,文件系統是Unix系統中最為基本的資源了,它如此的基本和普遍
存在以至于它需要一個更為便利的名字--我將忠于標準的稱呼簡單地稱之為“fs
”。
我將假設讀者早已知道基本的Unix文件系統的原理--訪問(權限)許可、i節
點(inode)、超級塊、加載(mount)和卸載(umount)文件系統。這些概念在標準的
Unix文獻中由比我聰明的作者給出了很好的解釋,所以我就不重復他們的工作并
且我將只專注于有關Linux方面的問題。
早期的Unix通常只支持一個文件系統(fs)類型,它的代碼散布于整個內核
中,現今的實現是在內核和fs之間使用一個標準的接口,以便于在不同的體系結
構中進行數據的交換。Linux本身提供了一個標準層以在內核和每種fs模塊之間傳
遞數據。這個接口層稱為VFS,即“虛擬文件系統”("virtual filesystem")。
因而文件系統的代碼被分割成了兩層:上層是關于內核表格的管理和數據結
構的,而低層是由與各文件系統相關的函數集構成的,并且是由VFS數據結構進行
調用的。
所有與文件系統獨立的資料都位于fs/*.c文件中。它們涉及如下的問題:
管理緩沖寄存器(buffer.c);
對fcntl()和ioctl()系統調用作出響應(fcntl.c和ioctl.c);
在inode和緩沖區上映射管道和fifo(fifo.c,pipe.c);
管理文件 - 和inode - 表(file_table.c,inode.c);
鎖定和解鎖文件和記錄(lock.c);
將名稱映射到inode(namei.c,open.c);
實現錯綜復雜的select()函數(select.c);
提供信息(stat.c);
加載和卸載文件系統(super.c);
使用exec()執行可執行程序以及轉儲核心程序(exec.c);
加載各種二進制格式(bin_fmt*.c,如上面所述)。
而VFS接口則由一組相對比較高層次的操作組成,并從與文件系統獨立的代碼
中調用而實際上是由每種文件系統類型執行的。最為相關的數據結構是
inode_operations和file_operations,盡管它們不是獨自存在的:同樣存在著其
它一些數據結構。它們都定義在include/linux/fs.h文件中。
到實際文件系統的內核入口點是數據結構file_system_type。
file_system_types的一個數組包含在fs/filesystems.c中,并且每當發出了一個
加載(mount)命令時都會引用它。然后,相應fs類型的函數read_super就負責填
寫結構super_block的一個項,而該項又內嵌了結構super_struct和結構
type_sb_info。前者為當前的fs類型提供了指向一般fs操作的指針,而后者對相
應fs類型內嵌了特定的信息。
文件系統類型數組已經轉換成了一個鏈表,以允許用內核模塊的形式加載新
的fs類型。函數(un-)register_filesystem代碼包含在fs/super.c中。
一個文件系統類型的快速剖析
一個文件系統類型的任務是執行用于映射相應高層VFS操作到物理介質(磁盤
、網絡等等)的低層任務。VFS接口有足夠的靈活性來支持傳統的Unix文件系統和
外來的象msdos和umsdos文件系統類型。
每一個fs類型除了它自己的源代碼目錄以外,是由下列各項組成的:
file_systems[]數組中的一個條目(項) (fs/filesystems.c);
超級塊(superblock)的include文件(include/linux/type_fs_sb.h);
i節點(inode)的include文件(include/linux/type_fs_i.h);
普通自己專用的include文件(include/linux/type_fs.h);
include/linux.fs.h中的兩行#include,以及在結構super_block和inode中的條目。
對于特定fs類型自己的目錄,包含有所有的實際代碼、inode和數據的管理程序。
本手冊中有關procfs的章節,揭示了所有有關那種fs類型的低層代碼和VFS接
口。在閱讀過那個章節之后,fs/procfs中的源代碼就顯得非常容易理解了。
現在我們來觀察VFS機制的內部工作情況,并以minix文件系統的代碼作為一
個實際例子。我選擇minix類型是因為它比較短小但卻是完整的;而且,Linux中
的所有其它的fs類型都衍生于它。在最近Linux安裝中的事實上的標準文件系統類
型ext2,要比它復雜得多,對ext2這個文件系統的探索就留給聰明的讀者作為一
個練習了。
當一個minix-fs被加載后,minix_read_super就會把從被加載的設備中讀取
的數據添入super_block數據結構中。此時,該結構中的s_op域將保留有一個指向
minix_sops的指針,該指針將被一般文件系統代碼用于分派超級塊的操作。
在全局系統樹結構中鏈接新加載的fs依賴于下列各數據項(假設sb是超級塊
數據結構,而dir_i是指向加載點的inode的指針):
sb->s_mounted指向被加載文件系統的根目錄i節點(MINIX_ROOT_INO);
dir_i->i_mount保存有sb->s_mounted;
sb->s_covered保存有dir_i
卸載操作將最終通過do_umount來執行,而它會依次調用minix_put_super。
每當訪問一個文件時,minix_read_inode就會開始執行;它會使用
minix_inode各字段中的數據填寫系統范圍的inode數據結構。inode->i_op字段是
依照inode->i_mode來填寫的,它將負責該文件的任何其它操作。上述minix函數
的代碼可以從fs/minix/inode.c中找到。
inode_operations數據結構是用于把inode操作分派給特定fs類型的內核函數
;該數據結構的第一項是一個指向file_operations項的指針,它等同于數據管理
的i_op。minix文件系統類型允許有inode操作集中的三種方式(用于目錄、文件
和符號鏈接)和文件操作集中的兩種(符號鏈接不需要文件操作)。
目錄操作(僅minix_readdir)位于fs/minix/dir.c中;文件操作(讀read和
寫write)位于fs/minix/file.c中而符號操作(讀取并跟隨著鏈)位于
fs/minix/symlink.c。
minix源代碼目錄中的其余部分用于實現以下任務:
bitmap.c用于管理i節點與塊的分配和釋放(而ext2文件系統卻有兩個不同的代碼文件);
fsynk.c用于fsync()系統調用--它管理直接、間接和雙重間接塊(我假定你
是知道這些術語的,因為這是Unix的普通知識);
namei.c內嵌有所有與名字有關的i節點的操作,比如象節點的創建和消除、
重命名和鏈接;
truncate.c執行文件的截斷操作。
控制臺驅動程序(console driver)
作為大多數Linux系統上的主要I/O設備,控制臺驅動程序是應該受到某些關
注的。有關控制臺和其它字符驅動程序的源代碼可以在drivers/char中找到,當
我們指稱文件時,我們將使用這個特定的目錄。
控制臺的初始化是由tty_io.c中的tty_init()函數來執行的。這個函數僅僅
涉及取得每個設備集的主設備號并調用每個設備集的init函數。而con_init()則
是與控制臺相關的函數,并存在于console.c中。
在內核1.1的開發中,控制臺的初始化已經有了很大的變化。console_init()
已經從tty_init()中脫離出來了,并且是由../../main.c直接調用的。現在虛擬
控制臺是動態分配的,其代碼也已有了很大的變化。所以我將跳過初始化、分配
等等的詳細討論。
文件操作是如何分派給控制臺的
這一節是相當底層的討論,你可以放心地跳過本節。
毫無疑問,Unix設備是通過文件系統來訪問的。本節將詳細描述從設備文件
到實際控制臺函數的所有步驟,而且,以下的信息是從內核的1.1.73源代碼中抽
取來的,它與1.0的代碼可能少許有點不同。
當打開一個設備i節點時,在../../fs/devices.c中的chrdev_open()函數(
或者是blkdev_open(),但我只專注于字符設備)將被執行。這個函數是通過數據
結構def_chr_fops取得的,而它又是被chrdev_inode_operations引用的,是被所
有文件系統類型使用的(見前面有關文件系統的部分)。
chrdev_open通過在當前操作中替換具體設備的file_operations表并且調用
特定的open()函數來管理指定的設備操作的。具體設備的表結構是保存在數組
chrdevs[]中的,并由主設備號作為索引,位于同一個../../devices.c中。
如果該設備是一個tty類型的(我們不是只關注控制臺嗎?),我們就來討論
tty的設備驅動程序,它們的函數在tty_io.c之中,由tty_fops作為索引。這樣,
tty_open()就會調用init_dev(),而init_dev()就會根據次設備號為設備分配任
何所需的數據結構。
次設備號也用于檢索已經使用tty_register_driver()注冊登記過的設備的實
際驅動程序。而且,該驅動程序仍是另一個用于分派計算的數據結構,正如
file_ops一樣;它是與設備的寫操作和控制有關的。最后一個用于管理tty的數據
結構是線路規程,這將在后面敘述。控制臺(以及任何其它的tty設備)的線路規
程是由initialize_tty_struct()設置的,并由init_dev調用的。
在這一節中我們所涉及的所有事情都是與設備無關的,僅有與特定控制臺相
關的是console.c,在con_init()操作期間已經注冊了自己的驅動程序。相反,線
路規程是與設備無關的。
The tty_driver 數據結構在中有著完整的描述。
上述信息是從1.1.73源代碼中取得的。它是有可能與你的內核有所不同的(
“如信息有所變動將不另行通知”)。
控制臺寫操作
當往一個控制臺設備進行寫操作時,就會調用con_write函數。這個函數管理
所有控制字符和換碼字符序列,這些字符給應用程序提供全部的屏幕管理操作。
所實現的換碼序列是vt102終端的;這意味著當你使用telnet連接到一臺非Linux
主機時,你的環境變量應該有TERM=vt102;然而,對于本地操作最佳的選擇是設
置TERM=console,因為Linux控制臺提供了一個vt102功能的超集。
因而,con_write()主要是由轉換語句組成的,用于處理每一次一個字符的有
限長狀態自動換碼序列的解釋。在正常方式下,所打印的字符是使用當前屬性直
接寫到顯示內存中的。在console.c中,數據結構vc的所有域使用宏都是可訪問的
,所以(例如)任何對attr的引用,只要currcons是所指的控制臺的號碼,確實
是引證了數據結構vc_cons[currcons]中的域。
實際上,新內核中的vc_cons已不再是一個數據結構數組了,現在它是指針的
數組,其內容是用kmalloc()操作的。宏的使用大大地簡化了代碼修改的工作,因
為許多代碼都不需要被重寫。
控制臺內存到屏幕內存的實際映射和非映射是由函數set_scrmem()(它把控
制臺緩沖區中的數據拷貝到顯示內存中)和get_srcmem()(它把數據拷貝回控制
臺緩沖區中)執行的。為了減少數據傳輸的次數,當前控制臺的私有緩沖區是物
理地映射到實際顯示RAM上的。這意味著console.c中的get-和set-_scrmem()是靜
態的,并且僅在一個控制臺轉換期間才被調用。
控制臺讀操作
控制臺讀操作是由線路規程來完成的。Linux中默認的(也是唯一的)線路規
程被稱為tty_ldisc_N_TTY。線路規程也就是“通過一線路約束輸入”。它是另一
個函數表(我們已習慣了這種方法,不是嗎?),它是有關于設備讀操作的。在
termios標志的幫助下,線路規程也即是從tty上控制輸入的規程:未處理過的數
據、cbreak和計劃的方式;select();ioctl()等等。
線路規程中的讀(read)函數稱為read_chan(),它讀取tty的緩沖區而不管
數據是從哪里來的。原因是通過一個tty來到的字符是由異步硬件中斷管理的。
線路規程N_TTY也同樣在tty_io.c中,盡管以后出的內核都使用一個不同的
n_tty.c源程序。
控制臺輸入的最底層是鍵盤管理的一部分,因此它是在keyboard.c的
keyboard_interrupt()中處理的。
鍵盤管理
鍵盤管理簡直是一場噩夢。它限于文件keyboard.c中,里面充滿了表示不同
廠家鍵盤的各個鍵碼的十六進制數。
我將不對keyboard.c進行深入討論,因為其中沒有與內核研究者有關的相關
信息。
對于那些對Linux的鍵盤編程確實感興趣的人,最好的方法是從keyboard.c的
最后一行往回看起。最底層的細節是在該文件的上半部分。
轉換當前控制臺
當前控制臺是通過使用函數change_console()來轉換的,它位于tty_io.c中
由keyboard.c和vt.c調用(前者響應按鍵的控制臺轉換,后者是當一個程序通過
引用一個ioctl()調用時轉換控制臺)。
實際的轉換過程是分兩步來執行的,函數complete_change_console()處理其
中的第二部分。轉換的分裂意味著在一個與控制著我們正在離開的tty的進程的可
能的握手以后完成任務。如果控制臺不在進程控制之下,change_console()就會
自己調用complete_change_console()。進程需要足夠的能力來成功地完成從圖形
到文本控制臺或從文本到圖形控制臺的轉換,并且X服務器(例如)是其圖形控制
臺的控制進程。
選擇機制
“選擇(selection)”是Linux文本控制臺的剪切(cut)與粘貼(paste)
功能。這個技巧主要是由用戶級的進程來處理的,它可以用selection或gpm的具
體例子說明。用戶級的程序在控制臺上使用ioctl()通知內核來加亮顯示屏幕的一
個區域。然后,被選擇的文本被拷貝到一個選擇緩沖區。該緩沖區是console.c中
的一個靜態實體。粘貼文本操作是通過“手工地”將字符放入tty輸入隊列中完成
的。整個選擇機制是通過#ifdef受到保護的,所以用戶在內核配置期間可以禁用
它以節省幾千字節的內存。
選擇是一個非常低級的功能,因而它工作是任何其它內核活動所看不見的。
這意味著許多的#ifdef只是屏幕在以任何方式作修改之前簡單地移動加亮部分。
新內核特性改善了選擇的代碼,鼠標指針的加亮可以與被選擇的文本獨立(
內核1.1.23或更高)。而且,從1.1.73版起,被選擇的文本使用了動態的緩沖區
而不是靜態的了,使得內核小了4KB。
使用ioctl()操作設備
ioctl()系統調用是用戶進程控制設備文件行為的入口點。Ioctl管理是從
../../fs/ioctl.c中產生的,實際上sys_ioctl()就是在這個ioctl.c中的。標準
的ioctl請求就是在那里執行的,其它與文件相關的請求是由file_ioctl()處理的
(在同一個源文件中),而其它任何請求都分派給特定設備的ioctl()函數。
控制臺設備的ioctl資料是位于vt.c中的,因為控制臺驅動程序要將ioctl請
求分派給vt_ioctl()。
上述信息是關于內核1.1.7x的。1.0內核是沒有“驅動程序”表的,而且
vt_ioctl()是直接由file_operations()表指向的。
Ioctl的資料確實是相當讓人混淆的。有些請求是與設備相關的,而有些卻是
與線路規程相關的。我將試圖對1.0和1.1.7x內核之間發生的任何事概要總結一下。
1.1.7x系列內核有如下的特性:tty_ioctl.c只實現了線路規程請求(也就是
n_tty_ioctl(),這是唯一在n_tty.c外面的n_tty函數),而file_operations字
段指向tty_io.c中的tty_ioctl()。如果請求號沒有被tty_ioctl()解析出來,它
就會被傳到tty->driver.ioctl或者,如果它失敗時,就到tty->ldisc.ioctl。控
制臺的與驅動程序相關的資料可以從vt.c中找到,而線路規程方面的資料則在
tty_ioctl.c中。
在1.0內核中,tty_ioctl()是在tty_ioctl.c中的并有一般tty的
file_operations所指向。未被解析出的請求將用與1.1.7x相似的方法被傳送到特
定的ioctl函數或到線路規程代碼去。
注意,在這兩種情況中,TIOCLINUX請求是在與設備無關的代碼中的,這暗示
著控制臺選擇操作可以通過ioctl對任何tty進行操作來設置(set_selection()總
是在控制臺前臺上操作的),而這是一個安全上的漏洞。這也是轉移到一個更新
的內核的很好理由,在新內核中,通過僅允許超級用戶來處理選擇彌補了這個漏
洞。
有很多請求可以被發給控制臺設備,而知道它們的最好方法是瀏覽源程序文
件vt.c。
版權所有(c) 1994 Alessandro Rubini, rubini@pop.systemy.it
總結
以上是生活随笔為你收集整理的linux 内核源代码漫游,Linux内核源代码漫游——的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux游戏欢迎界面,制作Linux登
- 下一篇: 手机远程ssh登录Linux,Linux