著: David A Rusling 翻譯: Banyan & FIFA 第十二章 模塊 本章主要描敘Linux核心動態加載功能模塊(如文件系統)的工作原理。 Linux核心是一種monolithic類型的內核,即單一的大程序,核心中所有的功能部件都可以對其全部內部數據結構和例程進行訪問。核心的另外一種形式是微內核結構,此時核心的所有功能部件都被拆成獨立部分, 這些部分之間通過嚴格的通訊機制進行聯系。這樣通過配置進程將新部件加入核心的方式非常耗時。比如說我們想為一個NCR 810 SCSI卡配置SCSI驅動,但是核心中沒有這個部分。那麼我們必須重新配置並重構核心。 Linux可以讓我們可以隨意動態的加載與卸載操作系統部件。Linux模塊就是這樣一種可在系統啟動後的任何時候動態連入核心的代碼塊。當我們不再需要它時又可以將它從核心中卸載並刪除。Linux模塊多指設備驅動、偽設備驅動, 如網絡設備和文件系統。 Linux為我們提供了兩個命令:使用insmod來顯式加載核心模塊,使用rmmod來卸載模塊。同時核心自身也可以請求核心後台進程kerneld來加載與卸載模塊。 動態可加載代碼的好處在於可以讓核心保持很小的尺寸同時非常靈活。在我的Intel系統中由於使用了模塊,整個核心僅為406K字節長。由於我只是偶爾使用VFAT文件系統, 所以我將Linux核心構造成當mount VFAT分區時自動加載VFAT文件系統模塊。當我卸載VFAT分區時系統將檢測到我不再需要VFAT文件系統模塊,將把它從系統中卸載。模塊同時還可以讓我們無需重構核心並頻繁重新啟動來嘗試運行新核心代碼。盡管使用模塊很自由,但是也有可能同時帶來與核心模塊相關的性能與內存損失。可加載模塊的代碼一般有些長並且額外的數據結構可能會占據一些內存。同時對核心資源的間接使用可能帶來一些效率問題。 一旦Linux模塊被加載則它和普通核心代碼一樣都是核心的一部分。它們具有與其他核心代碼相同的權限與職 責;換句話說Linux核心模塊可以象所有核心代碼和設備驅動一樣使核心崩潰。 模塊為了使用所需核心資源所以必須能夠找到它們。例如模塊需要調用核心內存分配例程kmalloc()來分配 內存。模塊在構造時並不知道kmalloc()在內存中何處,這樣核心必須在使用這些模塊前修改模塊中對 kmalloc()的引用地址。核心在其核心符號表中維護著一個核心資源鏈表這樣當加載模塊時它能夠解析出模塊 中對核心資源的引用。Linux還允許存在模塊堆棧,它在模塊之間相互調用時使用。例如VFAT文件系統模塊 可能需要FAT文件系統模塊的服務,因為VFAT文件系統多少是從FAT文件系統中擴展而來。某個模塊對其他模 塊的服務或資源的需求類似於模塊對核心本身資源或服務的請求。不過此時所請求的服務是來自另外一個事先 已加載的模塊。每當加載模塊時,核心將把新近加載模塊輸出的所有資源和符號添加到核心符號表中。 當試圖卸載某個模塊時,核心需要知道此模塊是否已經沒有被使用,同時它需要有種方法來通知此將卸載模塊。 模塊必須能夠在從核心種刪除之前釋放其分配的所有系統資源,如核心內存或中斷。當模塊被卸載時,核心將從核心符號表中刪除所有與之對應的符號。 可加載模塊具有使操作系統崩潰的能力,而編寫較差的模塊會帶來另外一種問題。當你在一個或早或遲構造的核心而不是當前你運行的核心上加載模塊時將會出現什麼結果?一種可能的情況是模塊將調用具有錯誤參數的核心例程。核心應該使用嚴格的版本控制來對加載模塊進行檢查以防止這種這些情況的發生。 12.1 模塊的加載 圖12.1 核心模塊鏈表s 核心模塊的加載方式有兩種。首先一種是使用insmod命令手工加載模塊。另外一種則是在需要時加載模塊;我們稱它為請求加載。當核心發現有必要加載某個模塊時,如用戶安裝了核心中不存在的文件系統時,核心將請求核心後台進程(kerneld)准備加載適當的模塊。這個核心後台進程僅僅是一個帶有超級用戶權限的普通用戶進程。當系統啟動時它也被啟動並為核心打開了一個進程間通訊(IPC)通道。核心需要執行各種任務時用它來向kerneld發送消息。 kerneld的主要功能是加載和卸載核心模塊, 但是它還可以執行其他任務, 如通過串行線路建立PPP連接並在適當時候關閉它。kerneld自身並不執行這些任務,它通過某些程序如insmod來做此工作。它只是核心的代理,為核心進行調度。 insmod程序必須找到要求加載的核心模塊。請求加載核心模塊一般被保存在/lib/modules/kernel-version 中。這些核心模塊和系統中其他程序一樣是已連接的目標文件,但是它們被連接成可重定位映象。即映象沒有被連接到在特定地址上運行。這些核心模塊可以是a.out或ELF文件格式。insmod將執行一個特權級系統調用來找到核心的輸出符號。這些都以符號名以及數值形式,如地址值成對保存。核心輸出符號表被保存在核心維護的模塊鏈表的第一個module結構中,同時module_list指針指向此結構。只有特殊符號被添加到此表中,它們在核心編譯與連接時確定,不是核心每個符號都被輸出到其模塊中。例如設備驅動為了控制某個特定系統中斷而由核心例程調用的"request_irq"符號。在我的系統中,其值為0x0010cd30。我們可以通過使用ksyms工具或者查看/proc/ksyms來觀看當前核心輸出符號。ksyms工具既可以顯示所有核心輸出符號也可以只顯示那些已加載模塊的符號。insmod將模塊讀入虛擬內存並通過使用來自核心輸出符號來修改其未解析的核心例程和資源的引用地址。這些修改工作采取由insmod程序直接將符號的地址寫入模塊中相應地址來修改內存中的模塊映象。 當insmod修改完模塊對核心輸出符號的引用後,它將再次使用特權級系統調用來申請足夠的空間來容納新核 心。核心將為其分配一個新的module結構以及足夠的核心內存來保存新模塊, 並將它放到核心模塊鏈表的尾部。 然後將其新模塊標志為UNINITIALIZED。 圖12.1給出了一個加載兩個模塊:VFAT和FAT後的核心鏈表示意圖。不過圖中沒有畫出鏈表中的第一個模塊: 用來存放核心輸出符號表的一個偽模塊。lsmod可以幫助我們列出系統中所有已加載的核心模塊以及相互間 依賴關系。它是通過重新格式化從核心module結構中建立的/proc/modules來進行這項工作的。核心為其分配的內存被映射到insmod的地址空間, 這樣它就能訪問核心空間。insmod將模塊拷貝到已分配空間中, 如果為它分配的核心內存已用完,則它將再次申請。不過不要指望多次將加載模塊到相同地址,更不用說在兩個不同 Linux系統的相同位置。另外此重定位工作包括使用適當地址來修改模塊映象。 這個新模塊也希望將其符號輸出到核心中,insmod將為其構造輸出符號映象表。每個核心模塊必須包含模塊 初始化和模塊清除例程,它們的符號被設計成故意不輸出, 但是insmod必須知道這些地址, 這樣它可以將它們傳遞給核心。所有這些工作做完之後,insmod將調用初始化代碼並執行一個特權級系統調用將模塊的初始化與清除例程地址傳遞給核心。 當將一個新模塊加載到核心中間時,核心必須更新其符號表並修改那些被新模塊使用的老模塊。那些依賴於其他模塊的模塊必須維護在其符號表尾部維護一個引用鏈表並在其module數據結構中指向它。圖12.1中VFAT 依賴於FAT文件系統模塊。所以FAT模塊包含一個對VFAT模塊的引用;這個引用在加載VFAT模塊時添加。核心調用模塊的初始化例程,如果成功它將安裝此模塊。模塊的清除例程地址被存儲在其module結構中,它將在 模塊卸載時由核心調用。最後模塊的狀態被設置成RUNNING。 12.2 模塊的卸載 模塊可以通過使用rmmod命令來刪除, 但是請求加載模塊將被kerneld在其使用記數為0時自動從系統中刪除。 kerneld在其每次idle定時器到期時都執行一個系統調用以將系統中所有不再使用的請求加載模塊從系統中 刪除。這個定時器的值在啟動kerneld時設置;我系統上的值為180秒。這樣如果你安裝一個iso9660 CDROM並且你的iso9660文件系統是一個可加載模塊, 則在卸載CD ROM後的很短時間內此iso9660模塊將從核心中刪除。 如果核心中的其他部分還在使用某個模塊, 則此模塊不能被卸載。例如如果你的系統中安裝了多個VFAT文件系統則你將不能卸載VFAT模塊。執行lsmod我們將看到每個模塊的引用記數。如: Module: #pages: Used by: msdos 5 1 vfat 4 1 (autoclean) fat 6 [vfat msdos] 2 (autoclean) 此記數表示依賴此模塊的核心實體個數。在上例中VFAT和msdos模塊都依賴於fat模塊, 所以fat模塊的引用記數為2。vfat和msdos模塊的引用記數都為1,表示各有一個已安裝文件系統。如果我們安裝另一個VFAT文件系統則vfat模塊的引用記數將為2。模塊的引用記數被保存在其映象的第一個長字中。這個字同時還包含AUTOCLEAN和VISITED標志。請求加載模塊使用這兩個標志域。如果模塊被標記成AUTOCLEAN則核心知道此模 塊可以自動卸載。VISITED標志表示此模塊正被一個或多個文件系統部分使用;只要有其他部分使用此模塊則這個標志被置位。每次系統被kerneld要求將沒有誰使用的請求模塊刪除時,核心將在所有模塊中掃描可能的候選者。但是一般只查看那些被標志成AUTOCLEAN並處於RUNNING狀態的模塊。如果某模塊的VISITED 標記被清除則它將被刪除出去。如果某模塊可以卸載,則可以調用其清除例程來釋放掉分配給它的核心資源。它所對應的module結構將被標記成DELETED並從核心模塊鏈表中斷開。其他依賴於它的模塊將修改它們各自的引用域來表示它們間的依賴關系不復存在。