欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
Linux內核源代碼漫游-執行
Linux內核源代碼漫游
藍森林http://www.lslnet.com 2000年6月7日 09:42

作 者: 趙炯

Alessandro Rubini著(zhù), rubini@pop.systemy.it

趙炯 譯,gohigh@shtdu.edu.cn

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

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

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

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

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

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

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

啟動(dòng)部分識別主機的某些特性以及vga卡的類(lèi)型。如果需要,它會(huì )要求用戶(hù)為控制臺選擇顯示模式。然后將整個(gè)系統從地址0x10000移至0x1000處,進(jìn)入保護模式并跳轉至系統的余下部分(在0x1000處)。

下一步是內核的解壓縮。0x1000處的代碼來(lái)自于zBoot/head.S,它初始化寄存器并調用decompress_kernel(),它們依次是由zBoot/inflate.c、zBoot/unzip.c和zBoot/misc.c組成。被解壓的數據存放到了地址0x10000處(1兆),這也是為什么Linux不能運行于少于2兆內存的主要原因。[在1兆內存中解壓內核的工作已經(jīng)完成,見(jiàn) Memory Savers--ED]

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

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

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

注意如果在前述任何一步中出了錯,計算機就會(huì )死鎖。在操作系統還沒(méi)有完全運轉之前是處理不了出錯的。

start_kernel()是位于init/main.c中的,并且沒(méi)有任何返回結果。從現在起的任何代碼都是用C語(yǔ)言編制的,除了中斷管理和系統調用的入/出代碼(當然,還有大多數的宏都嵌入了匯編代碼)。

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

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

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

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

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

由于內核是負責提供服務(wù)的,這個(gè)漫游文章將通過(guò)觀(guān)察這些服務(wù)(“系統調用”)以及通過(guò)提供基本數據結構的原理和代碼的組織結構繼續討論下去。

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

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

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

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

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

內核中的系統調用(systemcalls)是作為C語(yǔ)言函數存在的,它們的‘正規’名稱(chēng)是以‘sys_’開(kāi)頭的。例如一個(gè)名為burnout的系統調用將調用內核函數sys_burnout()。

系統調用機制在本手冊的第三章中進(jìn)行了討論。觀(guān)看在include/linux/sched.h中的for_each_task和SET_LINKS能夠幫助理解進(jìn)程表中的列表和樹(shù)結構。

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

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

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

這里不對exit.c的代碼加以討論---因為它一點(diǎn)也不令人感興趣。為了以一致的狀態(tài)退出系統,它涉及到許多細節。而POSIX標準對于信號則是要求相當嚴格的,所以這里必須對其加以敘述。

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

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

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

我將假設讀者早已知道基本的Unix文件系統的原理--訪(fǎng)問(wèn)(權限)許可、i節點(diǎn)(inode)、超級塊、加載(mount)和卸載(umount)文件系統。這些概念在標準的Unix文獻中由比我聰明的作者給出了很好的解釋?zhuān)晕揖筒恢貜退麄兊墓ぷ鞑⑶椅覍⒅粚?zhuān)注于有關(guān)Linux方面的問(wèn)題。

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

因而文件系統的代碼被分割成了兩層:上層是關(guān)于內核表格的管理和數據結構的,而低層是由與各文件系統相關(guān)的函數集構成的,并且是由VFS數據結構進(jìn)行調用的。

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

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

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

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

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

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

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

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

現在我們來(lái)觀(guān)察VFS機制的內部工作情況,并以minix文件系統的代碼作為一個(gè)實(shí)際例子。我選擇minix類(lèi)型是因為它比較短小但卻是完整的;而且,Linux中的所有其它的fs類(lèi)型都衍生于它。在最近Linux安裝中的事實(shí)上的標準文件系統類(lèi)型ext2,要比它復雜得多,對ext2這個(gè)文件系統的探索就留給聰明的讀者作為一個(gè)練習了。

當一個(gè)minix-fs被加載后,minix_read_super就會(huì )把從被加載的設備中讀取的數據添入super_block數據結構中。此時(shí),該結構中的s_op域將保留有一個(gè)指向minix_sops的指針,該指針將被一般文件系統代碼用于分派超級塊的操作。

在全局系統樹(shù)結構中鏈接新加載的fs依賴(lài)于下列各數據項(假設sb是超級塊數據結構,而dir_i是指向加載點(diǎn)的inode的指針):

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

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

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

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

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

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

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

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

文件操作是如何分派給控制臺的
這一節是相當底層的討論,你可以放心地跳過(guò)本節。

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

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

chrdev_open通過(guò)在當前操作中替換具體設備的file_operations表并且調用特定的open()函數來(lái)管理指定的設備操作的。具體設備的表結構是保存在數組chrdevs[]中的,并由主設備號作為索引,位于同一個(gè)../../devices.c中。

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

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

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

The tty_driver 數據結構在中有著(zhù)完整的描述。

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

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

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

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

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

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

線(xiàn)路規程中的讀(read)函數稱(chēng)為read_chan(),它讀取tty的緩沖區而不管數據是從哪里來(lái)的。原因是通過(guò)一個(gè)tty來(lái)到的字符是由異步硬件中斷管理的。

線(xiàn)路規程N_TTY也同樣在tty_io.c中,盡管以后出的內核都使用一個(gè)不同的n_tty.c源程序。

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

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

我將不對keyboard.c進(jìn)行深入討論,因為其中沒(méi)有與內核研究者有關(guān)的相關(guān)信息。

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

轉換當前控制臺
當前控制臺是通過(guò)使用函數change_console()來(lái)轉換的,它位于tty_io.c中由keyboard.c和vt.c調用(前者響應按鍵的控制臺轉換,后者是當一個(gè)程序通過(guò)引用一個(gè)ioctl()調用時(shí)轉換控制臺)。

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

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

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

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

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

控制臺設備的ioctl資料是位于vt.c中的,因為控制臺驅動(dòng)程序要將ioctl請求分派給vt_ioctl()。

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

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

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

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

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

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


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

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
【轉】由一些小問(wèn)題引起的東西
LINUX 內核源文件介紹以及頭文件介紹
Linux下/proc目錄簡(jiǎn)介
從串口驅動(dòng)到Linux驅動(dòng)模型
Linux 源碼之旅(英文版)
Linux內核啟動(dòng)流程及根文件系統加載過(guò)程
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久