<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > Linux內(nèi)核模塊編程

          Linux內(nèi)核模塊編程

          作者: 時間:2011-02-25 來源:網(wǎng)絡(luò) 收藏

          簡介
          模塊(module)是在內(nèi)核空間運行的程序,實際上是一種目標(biāo)對象文件,沒有鏈接,不能獨立運行,但是可以裝載到系統(tǒng)中作為內(nèi)核的一部分運行,從而可以動態(tài)擴(kuò)充內(nèi)核的功能。模塊最主要的用處就是用來實現(xiàn)設(shè)備驅(qū)動程序。

          使用模塊的優(yōu)點:
          1,將來修改內(nèi)核時,不必全部重新編譯整個內(nèi)核,可節(jié)省不少時間
          2,系統(tǒng)中如果需要使用新模塊,不必重新編譯內(nèi)核,只要插入相應(yīng)的模塊即可

          模塊的使用方式:(modules-1.3.57.tar.gz)
          命令 功能 備注
          modprobe symbolic link to modprobe
          depmod make module dependency file,以告訴將來的 insmod 要去哪抓 modules 來插。 這個 dependency file 就在/lib/modules/[您的kernel版本]/modules.dep

          insmod 把某個 module 插入 kernel 中
          rmmod 把某個沒在用的 module 拔出 kernel symbolic link to insmod
          lsmod 把現(xiàn)在 kernel 中插入的 modules 列出來a shell script in 1.3.69f
          ksyms symbolic link to insmod
          kerneld 一個 daemon,可負(fù)責(zé)自動呼叫 insmod 插入 module,是一個很方便的 daemon。它也同時查看,若某 module 插入後但很久沒人在用了,就會把它拔出來,以省記憶體。

          相關(guān)文件:
          /etc/rc.d/ rc.modules
          /etc/rc.d/rc.S or /etc/rc.d/rc.syinit
          /etc/conf.modules or /etc/modules.conf

          模塊的裝載/卸載:
          靜態(tài):在系統(tǒng)啟動時就裝載
          動態(tài):使用insmod等命令在系統(tǒng)運行過程中裝載

          注:
          1.現(xiàn)在kerneld已經(jīng)被kmod所取代,原因主要是kerneld是使用System V IPC,經(jīng)過了中間層,比較復(fù)雜。
          2.需要超級用戶權(quán)限。
          3.形式:
          Module: #pages: Used by:
          msdos 5 1
          vfat 4 1 (autoclean)
          fat 6 [vfat msdos] 2 (autoclean)

          工作原理
          摘要:
          *內(nèi)核符號表
          *模塊裝載
          *模塊卸載
          *多個模塊間的依賴關(guān)系。


          Kernel 里有一個變量叫 module_list,每當(dāng) user 將一個 module 載到 kernel 里的時候,這個 module 就會被記錄在 module_list 里面。當(dāng) kernel 要使用到這個 module 提供的 function 時,它就會去 search 這個 list,找到 module,然后再使用其提供的 function 或 variable。每一個 module 都可以 export 一些 function 或變量來讓別人使用。除此之外,module 也可以使用已經(jīng)載到 kernel 里的 module 提供的 function。這種情形叫做 module stack。比方說,module A 用到 module B 的東西,那在加載 module A 之前必須要先加載 module B。否則 module A 會無法加載。除了 module 會 export 東西之外,kernel 本身也會 export 一些 function 或 variable。同樣的,module 也可以使用 kernel 所 export 出來的東西。由于大家平時都是撰寫 user space 的程序,所以,當(dāng)突然去寫 module 的時候,會把平時寫程序用的 function 拿到 module 里使用。像是 printf 之類的東西。我要告訴各位的是,module 所使用的 function 或 variable,要嘛就是自己寫在 module 里,要嘛就是別的 module 提供的,再不就是 kernel 所提供的。你不能使用一般 libc 或 glibc所提供的 function。像 printf 之類的東西。這一點可能是各位要多小心的地方。(也許你可以先 link 好,再載到 kernel,我好象試過,但是忘了)。

          Linux核心是一種monolithic類型的內(nèi)核,即單一的大程序,核心中所有的功能部件都可以對其全部內(nèi)部數(shù)據(jù)結(jié)構(gòu)和例程進(jìn)行訪問。核心的另外一種形式是微內(nèi)核結(jié)構(gòu),此時核心的所有功能部件都被拆成獨立部分, 這些部分之間通過嚴(yán)格的通訊機(jī)制進(jìn)行聯(lián)系。這樣通過配置進(jìn)程將新部件加入核心的方式非常耗時。比如說我們想為一個NCR 810 SCSI卡配置SCSI驅(qū)動,但是核心中沒有這個部分。那么我們必須重新配置并重構(gòu)核心。 Linux可以讓我們可以隨意動態(tài)的加載與卸載操作系統(tǒng)部件。Linux模塊就是這樣一種可在系統(tǒng)啟動后的任何時候動態(tài)連入核心的代碼塊。當(dāng)我們不再需要它時又可以將它從核心中卸載并刪除。Linux模塊多指設(shè)備驅(qū)動、偽設(shè)備驅(qū)動, 如網(wǎng)絡(luò)設(shè)備和文件系統(tǒng)。

          Linux為我們提供了兩個命令:使用insmod來顯式加載核心模塊,使用rmmod來卸載模塊。同時核心自身也可以請求核心后臺進(jìn)程kerneld來加載與卸載模塊。
          動態(tài)可加載代碼的好處在于可以讓核心保持很小的尺寸同時非常靈活。在我的Intel系統(tǒng)中由于使用了模塊,整個核心僅為406K字節(jié)長。由于我只是偶爾使用VFAT文件系統(tǒng), 所以我將Linux核心構(gòu)造成當(dāng)mount VFAT分區(qū)時自動加載VFAT文件系統(tǒng)模塊。當(dāng)我卸載VFAT分區(qū)時系統(tǒng)將檢測到我不再需要VFAT文件系統(tǒng)模塊,將把它從系統(tǒng)中卸載。模塊同時還可以讓我們無需重構(gòu)核心并頻繁重新啟動來嘗試運行新核心代碼。盡管使用模塊很自由,但是也有可能同時帶來與核心模塊相關(guān)的性能與內(nèi)存損失。可加載模塊的代碼一般有些長并且額外的數(shù)據(jù)結(jié)構(gòu)可能會占據(jù)一些內(nèi)存。同時對核心資源的間接使用可能帶來一些效率問題。
          一旦Linux模塊被加載則它和普通核心代碼一樣都是核心的一部分。它們具有與其他核心代碼相同的權(quán)限與職 責(zé);換句話說Linux核心模塊可以象所有核心代碼和設(shè)備驅(qū)動一樣使核心崩潰。

          模塊為了使用所需核心資源所以必須能夠找到它們。例如模塊需要調(diào)用核心內(nèi)存分配例程kmalloc()來分配 內(nèi)存。模塊在構(gòu)造時并不知道kmalloc()在內(nèi)存中何處,這樣核心必須在使用這些模塊前修改模塊中對 kmalloc()的引用地址。核心在其核心符號表中維護(hù)著一個核心資源鏈表這樣當(dāng)加載模塊時它能夠解析出模塊 中對核心資源的引用。Linux還允許存在模塊堆棧,它在模塊之間相互調(diào)用時使用。例如VFAT文件系統(tǒng)模塊 可能需要FAT文件系統(tǒng)模塊的服務(wù),因為VFAT文件系統(tǒng)多少是從FAT文件系統(tǒng)中擴(kuò)展而來。某個模塊對其他模 塊的服務(wù)或資源的需求類似于模塊對核心本身資源或服務(wù)的請求。不過此時所請求的服務(wù)是來自另外一個事先 已加載的模塊。每當(dāng)加載模塊時,核心將把新近加載模塊輸出的所有資源和符號添加到核心符號表中。

          當(dāng)試圖卸載某個模塊時,核心需要知道此模塊是否已經(jīng)沒有被使用,同時它需要有種方法來通知此將卸載模塊。 模塊必須能夠在從核心種刪除之前釋放其分配的所有系統(tǒng)資源,如核心內(nèi)存或中斷。當(dāng)模塊被卸載時,核心將從核心符號表中刪除所有與之對應(yīng)的符號。

          可加載模塊具有使操作系統(tǒng)崩潰的能力,而編寫較差的模塊會帶來另外一種問題。當(dāng)你在一個或早或遲構(gòu)造的核心而不是當(dāng)前你運行的核心上加載模塊時將會出現(xiàn)什么結(jié)果?一種可能的情況是模塊將調(diào)用具有錯誤參數(shù)的核心例程。核心應(yīng)該使用嚴(yán)格的版本控制來對加載模塊進(jìn)行檢查以防止這種這些情況的發(fā)生。


          1 模塊的加載

          圖1 核心模塊鏈表

          核心模塊的加載方式有兩種。首先一種是使用insmod命令手工加載模塊。另外一種則是在需要時加載模塊;我們稱它為請求加載。當(dāng)核心發(fā)現(xiàn)有必要加載某個模塊時,如用戶安裝了核心中不存在的文件系統(tǒng)時,核心將請求核心后臺進(jìn)程(kerneld)準(zhǔn)備加載適當(dāng)?shù)哪K。這個核心后臺進(jìn)程僅僅是一個帶有超級用戶權(quán)限的普通用戶進(jìn)程。當(dāng)系統(tǒng)啟動時它也被啟動并為核心打開了一個進(jìn)程間通訊(IPC)通道。核心需要執(zhí)行各種任務(wù)時用它來向kerneld發(fā)送消息。
          kerneld的主要功能是加載和卸載核心模塊, 但是它還可以執(zhí)行其他任務(wù), 如通過串行線路建立PPP連接并在適當(dāng)時候關(guān)閉它。kerneld自身并不執(zhí)行這些任務(wù),它通過某些程序如insmod來做此工作。它只是核心的代理,為核心進(jìn)行調(diào)度。
          insmod程序必須找到要求加載的核心模塊。請求加載核心模塊一般被保存在/lib/modules/kernel-version 中。這些核心模塊和系統(tǒng)中其他程序一樣是已連接的目標(biāo)文件,但是它們被連接成可重定位映象。即映象沒有被連接到在特定地址上運行。這些核心模塊可以是a.out或ELF文件格式。insmod將執(zhí)行一個特權(quán)級系統(tǒng)調(diào)用來找到核心的輸出符號。這些都以符號名以及數(shù)值形式,如地址值成對保存。核心輸出符號表被保存在核心維護(hù)的模塊鏈表的第一個module結(jié)構(gòu)中,同時module_list指針指向此結(jié)構(gòu)。只有特殊符號被添加到此表中,它們在核心編譯與連接時確定,不是核心每個符號都被輸出到其模塊中。例如設(shè)備驅(qū)動為了控制某個特定系統(tǒng)中斷而由核心例程調(diào)用的"request_irq"符號。在我的系統(tǒng)中,其值為0x0010cd30。我們可以通過使用ksyms工具或者查看/proc/ksyms來觀看當(dāng)前核心輸出符號。ksyms工具既可以顯示所有核心輸出符號也可以只顯示那些已加載模塊的符號。insmod將模塊讀入虛擬內(nèi)存并通過使用來自核心輸出符號來修改其未解析的核心例程和資源的引用地址。這些修改工作采取由insmod程序直接將符號的地址寫入模塊中相應(yīng)地址來修改內(nèi)存中的模塊映象。
          當(dāng)insmod修改完模塊對核心輸出符號的引用后,它將再次使用特權(quán)級系統(tǒng)調(diào)用來申請足夠的空間來容納新核 心。核心將為其分配一個新的module結(jié)構(gòu)以及足夠的核心內(nèi)存來保存新模塊, 并將它放到核心模塊鏈表的尾部。 然后將其新模塊標(biāo)志為UNINITIALIZED。

          圖1給出了一個加載兩個模塊:VFAT和FAT后的核心鏈表示意圖。不過圖中沒有畫出鏈表中的第一個模塊: 用來存放核心輸出符號表的一個偽模塊。lsmod可以幫助我們列出系統(tǒng)中所有已加載的核心模塊以及相互間 依賴關(guān)系。它是通過重新格式化從核心module結(jié)構(gòu)中建立的/proc/modules來進(jìn)行這項工作的。核心為其分配的內(nèi)存被映射到insmod的地址空間, 這樣它就能訪問核心空間。insmod將模塊拷貝到已分配空間中, 如果為它分配的核心內(nèi)存已用完,則它將再次申請。不過不要指望多次將加載模塊到相同地址,更不用說在兩個不同 Linux系統(tǒng)的相同位置。另外此重定位工作包括使用適當(dāng)?shù)刂穪硇薷哪K映象。

          這個新模塊也希望將其符號輸出到核心中,insmod將為其構(gòu)造輸出符號映象表。每個核心模塊必須包含模塊 初始化和模塊清除例程,它們的符號被設(shè)計成故意不輸出, 但是insmod必須知道這些地址, 這樣它可以將它們傳遞給核心。所有這些工作做完之后,insmod將調(diào)用初始化代碼并執(zhí)行一個特權(quán)級系統(tǒng)調(diào)用將模塊的初始化與清除例程地址傳遞給核心。

          當(dāng)將一個新模塊加載到核心中間時,核心必須更新其符號表并修改那些被新模塊使用的老模塊。那些依賴于其他模塊的模塊必須維護(hù)在其符號表尾部維護(hù)一個引用鏈表并在其module數(shù)據(jù)結(jié)構(gòu)中指向它。圖12.1中VFAT 依賴于FAT文件系統(tǒng)模塊。所以FAT模塊包含一個對VFAT模塊的引用;這個引用在加載VFAT模塊時添加。核心調(diào)用模塊的初始化例程,如果成功它將安裝此模塊。模塊的清除例程地址被存儲在其module結(jié)構(gòu)中,它將在 模塊卸載時由核心調(diào)用。最后模塊的狀態(tài)被設(shè)置成RUNNING。

          2 模塊的卸載

          模塊可以通過使用rmmod命令來刪除, 但是請求加載模塊將被kerneld在其使用記數(shù)為0時自動從系統(tǒng)中刪除。 kerneld在其每次idle定時器到期時都執(zhí)行一個系統(tǒng)調(diào)用以將系統(tǒng)中所有不再使用的請求加載模塊從系統(tǒng)中 刪除。這個定時器的值在啟動kerneld時設(shè)置;我系統(tǒng)上的值為180秒。這樣如果你安裝一個iso9660 CDROM并且你的iso9660文件系統(tǒng)是一個可加載模塊, 則在卸載CD ROM后的很短時間內(nèi)此iso9660模塊將從核心中刪除。
          如果核心中的其他部分還在使用某個模塊, 則此模塊不能被卸載。例如如果你的系統(tǒng)中安裝了多個VFAT文件系統(tǒng)則你將不能卸載VFAT模塊。執(zhí)行l(wèi)smod我們將看到每個模塊的引用記數(shù)。如:
          Module: #pages: Used by:
          msdos 5 1
          vfat 4 1 (autoclean)
          fat 6 [vfat msdos] 2 (autoclean)
          此記數(shù)表示依賴此模塊的核心實體個數(shù)。在上例中VFAT和msdos模塊都依賴于fat模塊, 所以fat模塊的引用記數(shù)為2。vfat和msdos模塊的引用記數(shù)都為1,表示各有一個已安裝文件系統(tǒng)。如果我們安裝另一個VFAT文件系統(tǒng)則vfat模塊的引用記數(shù)將為2。模塊的引用記數(shù)被保存在其映象的第一個長字中。這個字同時還包含AUTOCLEAN和VISITED標(biāo)志。請求加載模塊使用這兩個標(biāo)志域。如果模塊被標(biāo)記成AUTOCLEAN則核心知道此模 塊可以自動卸載。VISITED標(biāo)志表示此模塊正被一個或多個文件系統(tǒng)部分使用;只要有其他部分使用此模塊則這個標(biāo)志被置位。每次系統(tǒng)被kerneld要求將沒有誰使用的請求模塊刪除時,核心將在所有模塊中掃描可能的候選者。但是一般只查看那些被標(biāo)志成AUTOCLEAN并處于RUNNING狀態(tài)的模塊。如果某模塊的VISITED 標(biāo)記被清除則它將被刪除出去。如果某模塊可以卸載,則可以調(diào)用其清除例程來釋放掉分配給它的核心資源。它所對應(yīng)的module結(jié)構(gòu)將被標(biāo)記成DELETED并從核心模塊鏈表中斷開。其他依賴于它的模塊將修改它們各自的引用域來表示它們間的依賴關(guān)系不復(fù)存在。此模塊需要的核心內(nèi)存都將被回收。


          編程實現(xiàn)


          模塊的組織結(jié)構(gòu)
          2.0/2.2
          至少需要兩個函數(shù):init_module()和cleanup_module()。一般在init_module()完成初始化工作,例如內(nèi)存分配(kmalloc);在cleanup_module()中完成回收工作。

          /* The necessary header files */

          /* Standard in kernel modules */
          #include /* We e doing kernel work */
          #include /* Specifically, a module */


          /* Deal with CONFIG_MODVERSIONS */
          #if CONFIG_MODVERSIONS==1
          #define MODVERSIONS /這兩行應(yīng)該修改為#ifdef MODVERSIONS
          #include
          #endif


          /* Initialize the module */
          int init_module()
          {
          Printk("Hello, world - this is the kernel speakingn");

          /* If we return a non zero value, it means that
          * init_module failed and the kernel module
          * can be loaded */
          Return 0;
          }


          /* Cleanup - undid whatever init_module did */
          void cleanup_module()
          {
          Printk("Short is the life of a kernel modulen");
          }

          2.3.*/2.4.*中的用法:
          2.3/2.4中的用法不同,應(yīng)該使用:
          module_init(init_proc_fs)
          module_exit(exit_proc_fs)
          優(yōu)點:有了返回值。

          static int __init init_proc_fs(void)
          {
          int err = register_filesystem(proc_fs_type);
          if (!err) {
          proc_mnt = kern_mount(proc_fs_type);
          err = PTR_ERR(proc_mnt);
          if (IS_ERR(proc_mnt))
          unregister_filesystem(proc_fs_type);
          Else
          err = 0;
          }
          return err;
          }

          static void __exit exit_proc_fs(void)
          {
          unregister_filesystem(proc_fs_type);
          kern_umount(proc_mnt);
          }

          module_init(init_proc_fs)
          module_exit(exit_proc_fs)

          模塊的Makefile
          Options:
          -D__KERNEL__ / in kernel space
          -DMODULE / create module
          -DLINUX / which can be compiled on more than one operating system
          -D__SMP__ / Symmetrical MultiProcessing
          -DCONFIG_MODVERSIONS / should include /usr/include/linux/modversions.h
          fomit-frame-pointer告訴gcc不要為那些不需要的函數(shù)保存頁面指針.這會使得我們的寄存器在調(diào)用init_module以后保持不變.

          多個源程序組成的模塊的Makefile
          1.在除了一個以外的所有源文件中,增加一行#define __NO_VERSION__。這是很重要的,因為module.h一般包括kernel_version的定義,這是一個全局變量,包含模塊編譯的內(nèi)核版本。如果你需要version.h,你需要把自己把它包含進(jìn)去,因為如果有__NO_VERSION__的話module.h不會自動包含。
          2.象通常一樣編譯源文件。
          3.把所有目標(biāo)文件聯(lián)編成一個。在X86下,用ld –m elf_i386 –r –o .o 1st source file>

          printk
          printk是內(nèi)核空間中printf的替代品,其用法為:printk("format string", var)。n定義了日志等級,范圍從0到7,其中0到4是緊急事件,5和6是普通信息,7為調(diào)試(例如SMP_DEBUG)。其它用戶空間的函數(shù)也不能使用;程序員在內(nèi)核空間中可以使用的函數(shù)都記錄在/proc/ksyms中,也可以使用ksyms -a命令查看。
          注:在終端下insmod模塊,printk信息送往終端
          By the way, the reason why the Makefile recommends against doing insmod from X is because when the kernel has a message to print with printk, it sends it to the console. When you don use X, it just goes to the virtual terminal you e using (the one you chose with Alt-F) and you see it. When you do use X, on the other hand, there are two possibilities. Either you have a console open with xterm -C, in which case the output will be sent there, or you don , in which case the output will go to virtual terminal 7 -- the one `covered by X.


          函數(shù)/變量和內(nèi)核版本的關(guān)系
          剛才我們說到 kernel 本身會 export 出一些 function 或 variable 來讓 module 使用,但是,我們不是萬能的,我們怎幺知道 kernel 有開放那里東西讓我們使用呢 ? Linux 提供一個 command,叫 ksyms,你只要執(zhí)行 ksyms -a 就可以知道 kernel 或目前載到 kernel 里的 module 提供了那些 function 或 variable。底下是我的系統(tǒng)的情形:

          c0216ba0 drive_info_R744aa133
          c01e4a44 boot_cpu_data_R660bd466
          c01e4ac0 EISA_bus_R7413793a
          c01e4ac4 MCA_bus_Rf48a2c4c
          c010cc34 __verify_write_R203afbeb
          . . . . .

          在 kernel 里,有一個 symbol table 是用來記錄 export 出去的 function 或 variable。除此之外,也會記錄著那個 module export 那些 function。上面幾行中,表示 kernel 提供了 drive_info 這個 function/variable。所以,我們可以在 kernel 里直接使用它,等載到 kernel 里時,會自動做好 link 的動作。由此,我們可以知道,module 本身其實是還沒做 link 的一些 object code。一切都要等到 module 被加載 kernel 之后,link 才會完成。各位應(yīng)該可以看到 drive_info 后面還接著一些奇怪的字符串。_R744aa133,這個字符串是根據(jù)目前 kernel 的版本再做些 encode 得出來的結(jié)果。為什幺額外需要這一個字符串呢 ?

          Linux 不知道從那個版本以來,就多了一個 config 的選項,叫做 Set version number in symbols of module。這是為了避免對系統(tǒng)造成不穩(wěn)定。我們知道 Linux 的 kernel 更新的很快。在 kernel 更新的過程,有時為了效率起見,會對某些舊有的 data structure 或 function 做些改變,而且一變可能有的 variable 被拿掉,有的 function 的 prototype 跟原來的都不太一樣。如果這種情形發(fā)生的時候,那可能以前 2.0.33 版本的 module 拿到 2.2.1 版本的 kernel 使用,假設(shè)原來 module 使用了 2.0.33 kernel 提供的變量叫 A,但是到了 2.2.1 由于某些原因必須把 A 都設(shè)成 NULL。那當(dāng)此 module 用在 2.2.1 kernel 上時,如果它沒去檢查 A 的值就直接使用的話,就會造成系統(tǒng)的錯誤。也許不會整個系統(tǒng)都死掉,但是這個 module 肯定是很難發(fā)揮它的功能。為了這個原因,Linux 就在 compile module 時,把 kernel 版本的號碼 encode 到各個 exported function 和 variable 里。

          所以,剛才也許我們不應(yīng)該講 kernel 提供了 drive_info,而應(yīng)該說 kernel 提供了 driver_info_R744aa133 來讓我們使用。這樣也許各位會比較明白。也就是說,kernel 認(rèn)為它提供的 driver_info_R744aa133 這個東西,而不是 driver_info。所以,我們可以發(fā)現(xiàn)有的人在加載 module 時,系統(tǒng)都一直告訴你某個 function 無法 resolved。這就是因為 kernel 里沒有你要的 function,要不然就是你的 module 里使用的 function 跟 kernel encode 的結(jié)果不一樣。所以無法 resolve。解決方式,要嘛就是將 kernel 里的 set version 選項關(guān)掉,要嘛就是將 module compile 成 kernel 有辦法接受的型式。

          那有人就會想說,如果 kernel 認(rèn)定它提供的 function 名字叫做 driver_info_R744aa133 的話,那我們寫程序時,是不是用到這個 funnction 的地方都改成 driver_info_R744aa133 就可以了。答案是 Yes。但是,如果每個 function 都要你這樣寫,你不會覺得很煩嗎 ? 比方說,我們在寫 driver 時,很多人都會用到 printk 這個 function。這是 kernel 所提供的 function。它的功能跟 printf 很像。用法也幾乎都一樣。是 debug 時很好用的東西。如果我們 module 里用了一百次 printk,那是不是我們也要打一百次的 printk_Rdd132261 呢 ? 當(dāng)然不是,聰明的人馬上會想到用 #define printk printk_Rdd132261 就好了嘛。所以啰,Linux 很體貼的幫我們做了這件事。
          如果各位的系統(tǒng)有將set version的選項打開的話,那大家可以到/usr/src/linux/include/linux/modules這個目錄底下。這個目錄底下有所多的..ver檔案。這些檔案其實就是用來做#define用的。我們來看看ksyms.ver 這個檔案里,里面有一行是這樣子的 :
          #define printk _set_ver(printk)
          set_ver是一個macro,就是用來在printk后面加上version number的。有興趣的朋友可以自行去觀看這個macro的寫法。用了這些ver檔,我們就可以在module里直接使用printk這樣的名字了。而這些ver檔會自動幫我們做好#define的動作??墒牵覀兛梢园l(fā)現(xiàn)這個目錄有很多很多的ver檔。有時候,我們怎幺知道我們要呼叫的function是在那個ver檔里有定義呢?Linux又幫我們做了一件事。/usr/src/linux/include/linux/modversions.h這個檔案已經(jīng)將全部的ver檔都加進(jìn)來了。所以在我們的module里只要include這個檔,那名字的問題都解決了。但是,在此,我們奉勸各位一件事,不要將modversions.h這個檔在module里include進(jìn)來,如果真的要,那也要加上以下數(shù)行:
          #ifdef MODVERSIONS
          #include linux/modversions.h
          #endif
          加入這三行的原因是,避免這個 module 在沒有設(shè)定 kernel version 的系統(tǒng)上,將 modversions.h 這個檔案 include 進(jìn)來。各位可以去試試看,當(dāng)你把 set version 的選項關(guān)掉時,modversions.h 和 modules 這個目錄都會不見。如果沒有上面三行,那 compile 就不會過關(guān)。所以一般來講,modversions.h 我們會選擇在 compile 時傳給 gcc 使用。就像下面這個樣子。
          gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS main.c
          -include usr/src/linux/include/linux/modversions.h
          在這個 command line 里,我們看到了 -D__KERNEL__,這是說要定義 __KERNEL__ 這個 constant。很多跟 kernel 有關(guān)的 header file,都必須要定義這個 constant 才能 include 的。所以建議你最好將它定義起來。另外還有一個 -DMODVERSIONS。這個 constant 我剛才忘了講。剛才我們說要解決 fucntion 或 variable 名字 encode 的方式就是要 include modversions.h,其實除此之外,你還必須定義 MODVERSIONS 這個 constant。再來就是 MODULE 這個 constant。其實,只要是你要寫 module 就一定要定義這個變量。而且你還要 include module.h 這個檔案,因為 _set_ver 就是定義在這里的。
          講到這里,相信各位應(yīng)該對 module 有一些認(rèn)識了,以后遇到 module unresolved 應(yīng)該不會感到困惑了,應(yīng)該也有辦法解決了。

          變量/函數(shù)的導(dǎo)出(export)
          剛才講的都是使用別人的 function 上遇到的名字 encode 問題。但是,如果我們自己的 module 想要 export 一些東西讓別的 module 使用呢。很簡單。在 default 上,在你的 module 里所有的 global variable 和 function 都會被認(rèn)定為你要 export 出去的。所以,如果你的 module 里有 10 個 global variable,經(jīng)由 ksyms,你可以發(fā)現(xiàn)這十個 variable 都會被 export 出去。這當(dāng)然是個很方便的事啦,但是,你知道,有時候我們根本不想把所有的 variable 都 export 出去,萬一有個 module 沒事亂改我們的 variable 怎幺辦呢 ? 所以,在很多時候,我們都只會限定幾個必要的東西 export 出去。在 2.2.1 之前的 kernel (不是很確定) 可以利用 register_symtab 來幫我們。但是,現(xiàn)在更新的版本早就出來了。所以,在此,我會介紹 kernel 2.2.1 里所提供的。kernel 2.2.1 里提供了一個 macro,叫做 EXPORT_SYMBOL,這是用來幫我們選擇要 export 的 variable 或 function。比方說,我要 export 一個叫 full 的 variable,那我只要在 module 里寫:

          EXPORT_SYMBOL(full);

          就會自動將 full export 出去,你馬上就可以從 ksyms 里發(fā)現(xiàn)有 full 這個變量被 export 出去。在使用 EXPORT_SYMBOL 之前,要小心一件事,就是必須在 gcc 里定義 EXPORT_SYMTAB 這個 constant,否則在 compile 時會發(fā)生 parser error。所以,要使用 EXPORT_SYMBOL 的話,那 gcc 應(yīng)該要下:

          gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS -DEXPORT_SYMTAB
          main.c -include /usr/src/linux/include/linux/modversions.h

          如果我們不想 export 任何的東西,那我們只要在 module 里下

          EXPORT_NO_SYMBOLS;

          就可以了。使用 EXPORT_NO_SYMBOLS 用不著定義任何的 constant。其實,如果各位使用過舊版的 register_symbol 的話,一定會覺得新版的方式比較好用。至少我是這樣覺得啦。因為使用 register_symbol 還要先定義出自己的 symbol_table,感覺有點麻煩。

          當(dāng)我們使用 EXPORT_SYMBOL 把一些 function 或 variable export 出來之后,我們使用 ksyma -a 去看一些結(jié)果。我們發(fā)現(xiàn) EXPORT_SYMBOL(full) 的確是把 full export出來了 :

          c8822200 full [my_module]
          c01b8e08 pci_find_slot_R454463b5
          . . .

          但是,結(jié)果怎幺跟我們想象中的不太一樣,照理說,應(yīng)該是 full_Rxxxxxx 之類的東西才對啊,怎幺才出現(xiàn) full 而已呢 ? 奇怪,問題在那里呢 ?

          其實,問題就在于我們沒有對本身的 module 所 export 出來的 function 或 variable 的名字做 encode。想想,如果在 module 的開頭。我們加入一行

          #define full full_Rxxxxxx

          之后,我們再重新 compile module 一次,載到 kernel 之后,就可以發(fā)現(xiàn) ksyms -a 顯示的是

          c8822200 full_Rxxxxxx [my_module]
          c01b8e08 pci_find_slot_R454463b5
          . . . . .

          了。那是不是說,我們要去對每一個 export 出來的 variable 和 function 做 define 的動作呢 ? 當(dāng)然不是啰。記得嗎,前頭我們講去使用 kernel export 的 function 時,由于 include 了一些 .ver 的檔案,以致于我們不用再做 define 的動作。現(xiàn)在,我們也要利用 .ver 的檔案來幫我們,使我們 module export 出來的 function 也可以自動加入 kernel version 的 information。也就是變成 full_Rxxxxxx 之類的東西。

          Linux 里提供了一個 command,叫 genksyms,就是用來幫我們產(chǎn)生這種 .ver 的檔案的。它會從 stdin 里讀取 source code,然后檢查 source code 里是否有 export 的 variable 或 function。如果有,它就會自動為每個 export 出來的東西產(chǎn)生一些 define。這些 define 就是我們之前說的。等我們有了這些 define 之后,只要在我們的 module 里加入這些 define,那 export 出來的 function 或 variable 就會變成上面那個樣子。

          假設(shè)我們的程序都放在一個叫 main.c 的檔案里,我們可以使用下列的方式產(chǎn)生這些 define。

          gcc -E -D__GENKSYMS__ main.c | genksyms -k 2.2.1 > main.ver

          gcc 的 -E 參數(shù)是指將 preprocessing 的結(jié)果 show 出來。也就是說將它 include 的檔案,一些 define 的結(jié)果都展開。-D__GENKSYMS__ 是一定要的。如果沒有定義這個 constant,你將不會看到任何的結(jié)果。用一個管線是因為 genksyms 是從 stdin 讀資料的,所以,經(jīng)由管線將 gcc 的結(jié)果傳給 genksyms。-k 2.2.1 是指目前使用的 kernel 版本是 2.2.1,如果你的 kernel 版本不一樣,必須指定你的 kernel 的版本。產(chǎn)生的 define 將會被放到 main.ver 里。產(chǎn)生完 main.ver 檔之后,在 main.c 里將它 include 進(jìn)來,那一切就 OK 了。有件事要告訴各位的是,使用這個方式產(chǎn)生的 module,其 export 出來的東西會經(jīng)由 main.ver 的 define 改頭換面。所以如果你要讓別人使用,那你必須將 main.ver 公開,不然,別人就沒辦法使用你 export 出來的東西了。

          內(nèi)核模塊和系統(tǒng)的交互
          /proc和設(shè)備驅(qū)動程序是內(nèi)核和系統(tǒng)進(jìn)行交互的兩種方式。
          /proc
          /proc的使用:

          defined in /kerneltree/include/linux/proc_fs.h
          2.4.0:
          struct proc_dir_entry {
          unsigned short low_ino;
          unsigned short namelen;
          const char *name;
          mode_t mode;
          nlink_t nlink;
          uid_t uid;
          gid_t gid;
          unsigned long size;
          struct inode_operations * proc_iops;
          struct file_operations * proc_fops;
          get_info_t *get_info;
          struct module *owner;
          struct proc_dir_entry *next, *parent, *subdir;
          void *data;
          read_proc_t *read_proc;
          write_proc_t *write_proc;
          atomic_t count; /* use count */
          int deleted; /* delete flag */
          kdev_t rdev;
          };

          extern struct proc_dir_entry *proc_sys_root;

          #ifdef CONFIG_SYSCTL
          EXPORT_SYMBOL(proc_sys_root);
          #endif
          EXPORT_SYMBOL(proc_symlink);
          EXPORT_SYMBOL(proc_mknod);
          EXPORT_SYMBOL(proc_mkdir);
          EXPORT_SYMBOL(create_proc_entry);
          EXPORT_SYMBOL(remove_proc_entry);
          EXPORT_SYMBOL(proc_root);
          EXPORT_SYMBOL(proc_root_fs);
          EXPORT_SYMBOL(proc_net);
          EXPORT_SYMBOL(proc_bus);
          EXPORT_SYMBOL(proc_root_driver);

          2.2.18
          struct proc_dir_entry {
          unsigned short low_ino;
          unsigned short namelen;
          const char *name;
          mode_t mode;
          nlink_t nlink;
          uid_t uid;
          gid_t gid;
          unsigned long size;
          struct inode_operations * ops;
          int (*get_info)(char *, char **, off_t, int, int);
          void (*fill_inode)(struct inode *, int);
          struct proc_dir_entry *next, *parent, *subdir;
          void *data;
          int (*read_proc)(char *page, char **start, off_t off,
          int count, int *eof, void *data);
          int (*write_proc)(struct file *file, const char *buffer,
          unsigned long count, void *data);
          int (*readlink_proc)(struct proc_dir_entry *de, char *page);
          unsigned int count; /* use count */
          int deleted; /* delete flag */
          };


          #ifdef CONFIG_SYSCTL
          EXPORT_SYMBOL(proc_sys_root);
          #endif
          EXPORT_SYMBOL(proc_register);
          EXPORT_SYMBOL(proc_unregister);
          EXPORT_SYMBOL(create_proc_entry);
          EXPORT_SYMBOL(remove_proc_entry);
          EXPORT_SYMBOL(proc_root);
          EXPORT_SYMBOL(proc_root_fs);
          EXPORT_SYMBOL(proc_get_inode);
          EXPORT_SYMBOL(proc_dir_inode_operations);
          EXPORT_SYMBOL(proc_net_inode_operations);
          EXPORT_SYMBOL(proc_net);
          EXPORT_SYMBOL(proc_bus);

          example:
          struct proc_dir_entry Our_Proc_File =
          {
          0, /* Inode number - ignore, it will be filled by
          * proc_register[_dynamic] */
          4, /* Length of the file name */
          "test", /* The file name */
          S_IFREG | S_IRUGO, /* File mode - this is a regular
          * file which can be read by its
          * owner, its group, and everybody
          * else */
          1, /* Number of links (directories where the
          * file is referenced) */
          0, 0, /* The uid and gid for the file - we give it
          * to root */
          80, /* The size of the file reported by ls. */
          NULL, /* functions which can be done on the inode
          * (linking, removing, etc.) - we don
          * support any. */
          procfile_read, /* The read function for this file,
          * the function called when somebody
          * tries to read something from it. */
          NULL /* We could have here a function to fill the
          * files inode, to enable us to play with
          * permissions, ownership, etc. */
          };

          使用:
          2.2.* 2.0.*
          #if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)
          /* In version 2.2, proc_register assign a dynamic
          * inode number automatically if it is zero in the
          * structure , so theres no more need for
          * proc_register_dynamic
          */
          return proc_register(proc_root, Our_Proc_File);
          #else
          return proc_register_dynamic(proc_root, Our_Proc_File);
          #endif


          proc_unregister(proc_root, Our_Proc_File.low_ino);

          2.4.*


          #ifndef _LINUX_SYSCTL_H
          #define _LINUX_SYSCTL_H

          #include
          #include
          #include

          /* A sysctl table is an array of struct ctl_table: */
          struct ctl_table
          {
          int ctl_name; /* Binary ID */
          const char *procname; /* Text ID for /proc/sys, or zero */
          void *data;
          int maxlen;
          mode_t mode;
          ctl_table *child;
          proc_handler *proc_handler; /* Callback for text formatting */
          ctl_handler *strategy; /* Callback function for all r/w */
          struct proc_dir_entry *de; /* /proc control block */
          void *extra1;
          void *extra2;
          };

          /* struct ctl_table_header is used to maintain dynamic lists of
          ctl_table trees. */
          struct ctl_table_header
          {
          ctl_table *ctl_table;
          struct list_head ctl_entry;
          };

          struct ctl_table_header * register_sysctl_table(ctl_table * table,
          int insert_at_head);
          void unregister_sysctl_table(struct ctl_table_header * table);

          例:
          /kerneltree/net/khttpd/sysctl.c

          1.#inlclude
          2.對/kerneltree/inlcude/linux/sysctl.h進(jìn)行修改

          設(shè)備驅(qū)動程序
          設(shè)備文件的使用:

          /usr/include/linux/fs.h

          struct file_operations {
          loff_t (*llseek) (struct file *, loff_t, int);
          ssize_t (*read) (struct file *, char *, size_t, loff_t *);
          ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
          int (*readdir) (struct file *, void *, filldir_t);
          unsigned int (*poll) (struct file *, struct poll_table_struct *);
          int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
          int (*mmap) (struct file *, struct vm_area_struct *);
          int (*open) (struct inode *, struct file *);
          int (*flush) (struct file *);
          int (*release) (struct inode *, struct file *);
          int (*fsync) (struct file *, struct dentry *);
          int (*fasync) (int, struct file *, int);
          int (*check_media_change) (kdev_t dev);
          int (*revalidate) (kdev_t dev);
          int (*lock) (struct file *, int, struct file_lock *);
          };

          重新定義相應(yīng)的函數(shù)。

          設(shè)備和系統(tǒng)的交互:

          #include /* The character device
          * definitions are here */
          #include /* A wrapper which does
          * next to nothing at
          * at present, but may
          * help for compatibility
          * with future versions
          * of Linux */

          主碼/從碼
          printk("Device: %d.%dn", inode->i_rdev >> 8, inode->i_rdev 0xFF);
          primary key secondery key
          1. 字符設(shè)備
          Major = module_register_chrdev(0,
          DEVICE_NAME,
          Fops);
          ret = module_unregister_chrdev(Major, DEVICE_NAME);
          2. 塊設(shè)備
          Major = module_register_blkdev(0,
          DEVICE_NAME,
          Fops);
          ret = module_unregister_blkdev(Major, DEVICE_NAME);

          /defined in

          系統(tǒng)調(diào)用
          和模塊有關(guān)的系統(tǒng)調(diào)用
          sys_create_module()
          sys_init_module()
          sys_delete_module()
          sys_get_kernel_syms()
          get_module_list()
          get_ksyms_list()
          sys_query_module()
          int request_module (const char *name);
          int release_module (const char* name, int waitflag);
          int delayed_release_module (const char *name);
          int cancel_release_module (const char *name);
          系統(tǒng)調(diào)用原型:
          syscalln(rettype, name, type1, parm1,……)
          #include
          系統(tǒng)調(diào)用的使用:
          1. 系統(tǒng)調(diào)用表
          #include

          asmlinkage int (*original_call)(const char *, int, int);
          asmlinkage int our_sys_open(const char *filename,
          int flags,
          int mode)

          original_call = sys_call_table[__NR_open];
          sys_call_table[__NR_open] = our_sys_open;
          if (sys_call_table[__NR_open] != our_sys_open) {
          printk("Somebody else also played with the ");
          printk("open system calln");
          printk("The system may be left in ");
          printk("an unstable state.n");
          }

          sys_call_table[__NR_open] = original_call;

          2. syscalln

          static inline _syscall1(int, brk, void *, end_data_segment);

          ...

          int ret, tmp;
          char *truc = OLDEXEC;
          char *nouveau = NEWEXEC;
          unsigned long mmm;
          mmm = current->mm->brk; /*定位當(dāng)前進(jìn)程數(shù)據(jù)段大小*/
          ret = brk((void ) (mmm 256)); /*利用系統(tǒng)調(diào)用brk為當(dāng)前進(jìn)程增加內(nèi)存256個字節(jié)*/
          if (ret 0)
          return ret; /*分配不成功*/
          memcpy_tofs((void *) (mmm 2), nouveau, strlen(nouveau) 1);

          3. 擴(kuò)展
          #define _syscall1(type,name,type1,arg1)
          type name(type1 arg1)
          {
          long __res;
          __asm__ volatile ("int $0x80"
          : "=a" (__res)
          : "0" (__NR_##name),"b" ((long)(arg1)));
          if (__res >= 0)
          return (type) __res;
          errno = -__res;
          return -1;
          }

          .................
          end

          linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)


          評論


          相關(guān)推薦

          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();