歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux綜合 >> Linux內核 >> Linux內核源代碼漫游

Linux內核源代碼漫游

日期:2017/2/27 12:18:51   编辑: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結構已是一個鏈表而不是一個數組了,以允許以一個內核模塊的方式加載一個新的二進制格式。最後,結構的本身也已經被擴展成能夠訪問與格式相關的核心轉儲程序了。

訪問文件系統
眾所周知,文件系統是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, [email protected]


Copyright © Linux教程網 All Rights Reserved