|
清單 5. 插入、檢查和刪除 LKM [root@plato]# insmod simple-lkm.ko [root@plato]# lsmod Module Size Used by simple_lkm 1536 0 autofs4 26244 0 video 13956 0 button 5264 0 battery 7684 0 ac 3716 0 yenta_socket 18952 3 rsrc_nonstatic 9472 1 yenta_socket uhci_hcd 32144 0 i2c_piix4 7824 0 dm_mod 56468 3 [root@plato]# rmmod simple-lkm [root@plato]# 注意,內核的輸出進(jìn)到了內核回環(huán)緩沖區中,而不是打印到 stdout 上,這是因為 stdout 是進(jìn)程特有的環(huán)境。要查看內核回環(huán)緩沖區中的消息,可以使用 dmesg 工具(或者通過(guò) /proc 本身使用 cat /proc/kmsg 命令)。清單 6 給出了 dmesg 顯示的最后幾條消息。 清單 6. 查看來(lái)自 LKM 的內核輸出 [root@plato]# dmesg | tail -5 cs: IO port probe 0xa00-0xaff: clean. eth0: Link is down eth0: Link is up, running at 100Mbit half-duplex my_module_init called. Module is now loaded. my_module_cleanup called. Module is now unloaded. [root@plato]# 可以在內核輸出中看到這個(gè)模塊的消息,F在讓我們暫時(shí)離開(kāi)這個(gè)簡(jiǎn)單的例子,來(lái)看幾個(gè)可以用來(lái)開(kāi)發(fā)有用 LKM 的內核 API。 集成到 /proc 文件系統中 內核程序員可以使用的標準 API,LKM 程序員也可以使用。LKM 甚至可以導出內核使用的新變量和函數。有關(guān) API 的完整介紹已經(jīng)超出了本文的范圍,因此我們在這里只是簡(jiǎn)單地介紹后面在展示一個(gè)更有用的 LKM 時(shí)所使用的幾個(gè)元素。 創(chuàng )建并刪除 /proc 項 要在 /proc 文件系統中創(chuàng )建一個(gè)虛擬文件,請使用 create_proc_entry 函數。這個(gè)函數可以接收一個(gè)文件名、一組權限和這個(gè)文件在 /proc 文件系統中出現的位置。create_proc_entry 的返回值是一個(gè)proc_dir_entry 指針(或者為 NULL,說(shuō)明在 create 時(shí)發(fā)生了錯誤)。然后就可以使用這個(gè)返回的指針來(lái)配置這個(gè)虛擬文件的其他參數,例如在對該文件執行讀操作時(shí)應該調用的函數。create_proc_entry的原型和 proc_dir_entry 結構中的一部分如清單 7 所示。 清單 7. 用來(lái)管理 /proc 文件系統項的元素 struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode, struct proc_dir_entry *parent ); struct proc_dir_entry { const char *name; // virtual file name mode_t mode; // mode permissions uid_t uid; // File's user id gid_t gid; // File's group id struct inode_operations *proc_iops; // Inode operations functions struct file_operations *proc_fops; // File operations functions struct proc_dir_entry *parent; // Parent directory ... read_proc_t *read_proc; // /proc read function write_proc_t *write_proc; // /proc write function void *data; // Pointer to private data atomic_t count; // use count ... }; void remove_proc_entry( const char *name, struct proc_dir_entry *parent ); 稍后我們就可以看到如何使用 read_proc 和 write_proc 命令來(lái)插入對這個(gè)虛擬文件進(jìn)行讀寫(xiě)的函數。 要從 /proc 中刪除一個(gè)文件,可以使用 remove_proc_entry 函數。要使用這個(gè)函數,我們需要提供文件名字符串,以及這個(gè)文件在 /proc 文件系統中的位置(parent)。這個(gè)函數原型如清單 7 所示。 parent 參數可以為 NULL(表示 /proc 根目錄),也可以是很多其他值,這取決于我們希望將這個(gè)文件放到什么地方。表 1 列出了可以使用的其他一些父 proc_dir_entry,以及它們在這個(gè)文件系統中的位置。 表 1. proc_dir_entry 快捷變量 proc_dir_entry 在文件系統中的位置 proc_root_fs /proc proc_net /proc/net proc_bus /proc/bus proc_root_driver /proc/driver 回調函數 我們可以使用 write_proc 函數向 /proc 中寫(xiě)入一項。這個(gè)函數的原型如下: int mod_write( struct file *filp, const char __user *buff, unsigned long len, void *data ); filp 參數實(shí)際上是一個(gè)打開(kāi)文件結構(我們可以忽略這個(gè)參數)。buff 參數是傳遞給您的字符串數據。緩沖區地址實(shí)際上是一個(gè)用戶(hù)空間的緩沖區,因此我們不能直接讀取它。len 參數定義了在 buff 中有多少數據要被寫(xiě)入。data 參數是一個(gè)指向私有數據的指針(參見(jiàn) 清單 7)。在這個(gè)模塊中,我們聲明了一個(gè)這種類(lèi)型的函數來(lái)處理到達的數據。 Linux 提供了一組 API 來(lái)在用戶(hù)空間和內核空間之間移動(dòng)數據。對于 write_proc 的情況來(lái)說(shuō),我們使用了 copy_from_user 函數來(lái)維護用戶(hù)空間的數據。 讀回調函數 我們可以使用 read_proc 函數從一個(gè) /proc 項中讀取數據(從內核空間到用戶(hù)空間)。這個(gè)函數的原型如下: int mod_read( char *page, char **start, off_t off, int count, int *eof, void *data ); page 參數是這些數據寫(xiě)入到的位置,其中 count 定義了可以寫(xiě)入的最大字符數。在返回多頁(yè)數據(通常一頁(yè)是 4KB)時(shí),我們需要使用 start 和 off 參數。當所有數據全部寫(xiě)入之后,就需要設置 eof(文件結束參數)。與 write 類(lèi)似,data 表示的也是私有數據。此處提供的 page 緩沖區在內核空間中。因此,我們可以直接寫(xiě)入,而不用調用 copy_to_user。 其他有用的函數 我們還可以使用 proc_mkdir、symlinks 以及 proc_symlink 在 /proc 文件系統中創(chuàng )建目錄。對于只需要一個(gè) read 函數的簡(jiǎn)單 /proc 項來(lái)說(shuō),可以使用 create_proc_read_entry,這會(huì )創(chuàng )建一個(gè) /proc 項,并在一個(gè)調用中對 read_proc 函數進(jìn)行初始化。這些函數的原型如清單 8 所示。 清單 8. 其他有用的 /proc 函數 /* Create a directory in the proc filesystem */ struct proc_dir_entry *proc_mkdir( const char *name, struct proc_dir_entry *parent ); /* Create a symlink in the proc filesystem */ struct proc_dir_entry *proc_symlink( const char *name, struct proc_dir_entry *parent, const char *dest ); /* Create a proc_dir_entry with a read_proc_t in one call */ struct proc_dir_entry *create_proc_read_entry( const char *name, mode_t mode, struct proc_dir_entry *base, read_proc_t *read_proc, void *data ); /* Copy buffer to user-space from kernel-space */ unsigned long copy_to_user( void __user *to, const void *from, unsigned long n ); /* Copy buffer to kernel-space from user-space */ unsigned long copy_from_user( void *to, const void __user *from, unsigned long n ); /* Allocate a 'virtually' contiguous block of memory */ void *vmalloc( unsigned long size ); /* Free a vmalloc'd block of memory */ void vfree( void *addr ); /* Export a symbol to the kernel (make it visible to the kernel) */ EXPORT_SYMBOL( symbol ); /* Export all symbols in a file to the kernel (declare before module.h) */ EXPORT_SYMTAB 回頁(yè)首 通過(guò) /proc 文件系統實(shí)現財富分發(fā) 下面是一個(gè)可以支持讀寫(xiě)的 LKM。這個(gè)簡(jiǎn)單的程序提供了一個(gè)財富甜點(diǎn)分發(fā)。在加載這個(gè)模塊之后,用戶(hù)就可以使用 echo 命令向其中導入文本財富,然后再使用 cat 命令逐一讀出。 清單 9 給出了基本的模塊函數和變量。init 函數(init_fortune_module)負責使用 vmalloc 來(lái)為這個(gè)點(diǎn)心罐分配空間,然后使用 memset 將其全部清零。使用所分配并已經(jīng)清空的 cookie_pot 內存,我們在 /proc 中創(chuàng )建了一個(gè) proc_dir_entry 項,并將其稱(chēng)為 fortune。當 proc_entry 成功創(chuàng )建之后,對自己的本地變量和 proc_entry 結構進(jìn)行了初始化。我們加載了 /proc read 和 write 函數(如清單 9 和清單 10 所示),并確定這個(gè)模塊的所有者。cleanup 函數簡(jiǎn)單地從 /proc 文件系統中刪除這一項,然后釋放 cookie_pot 所占據的內存。 cookie_pot 是一個(gè)固定大。4KB)的頁(yè),它使用兩個(gè)索引進(jìn)行管理。第一個(gè)是 cookie_index,標識了要將下一個(gè) cookie 寫(xiě)到哪里去。變量 next_fortune 標識了下一個(gè) cookie 應該從哪里讀取以便進(jìn)行輸出。在所有的 fortune 項都讀取之后,我們簡(jiǎn)單地回到了 next_fortune。 清單 9. 模塊的 init/cleanup 和變量 #include #include #include #include #include #include MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Fortune Cookie Kernel Module"); MODULE_AUTHOR("M. Tim Jones"); #define MAX_COOKIE_LENGTH PAGE_SIZE static struct proc_dir_entry *proc_entry; static char *cookie_pot; // Space for fortune strings static int cookie_index; // Index to write next fortune static int next_fortune; // Index to read next fortune int init_fortune_module( void ) { int ret = 0; cookie_pot = (char *)vmalloc( MAX_COOKIE_LENGTH ); if (!cookie_pot) { ret = -ENOMEM; } else { memset( cookie_pot, 0, MAX_COOKIE_LENGTH ); proc_entry = create_proc_entry( "fortune", 0644, NULL ); if (proc_entry == NULL) { ret = -ENOMEM; vfree(cookie_pot); printk(KERN_INFO "fortune: Couldn't create proc entry\n"); } else { cookie_index = 0; next_fortune = 0; proc_entry->read_proc = fortune_read; proc_entry->write_proc = fortune_write; proc_entry->owner = THIS_MODULE; printk(KERN_INFO "fortune: Module loaded.\n"); } } return ret; } void cleanup_fortune_module( void ) { remove_proc_entry("fortune", &proc_root); vfree(cookie_pot); printk(KERN_INFO "fortune: Module unloaded.\n"); } module_init( init_fortune_module ); module_exit( cleanup_fortune_module ); 向這個(gè)罐中新寫(xiě)入一個(gè) cookie 非常簡(jiǎn)單(如清單 10 所示)。使用這個(gè)寫(xiě)入 cookie 的長(cháng)度,我們可以檢查是否有這么多空間可用。如果沒(méi)有,就返回 -ENOSPC,它會(huì )返回給用戶(hù)空間。否則,就說(shuō)明空間存在,我們使用 copy_from_user 將用戶(hù)緩沖區中的數據直接拷貝到 cookie_pot 中。然后增大 cookie_index(基于用戶(hù)緩沖區的長(cháng)度)并使用 NULL 來(lái)結束這個(gè)字符串。最后,返回實(shí)際寫(xiě)入 cookie_pot的字符的個(gè)數,它會(huì )返回到用戶(hù)進(jìn)程。 清單 10. 對 fortune 進(jìn)行寫(xiě)入操作所使用的函數 ssize_t fortune_write( struct file *filp, const char __user *buff, unsigned long len, void *data ) { int space_available = (MAX_COOKIE_LENGTH-cookie_index)+1; if (len > space_available) { printk(KERN_INFO "fortune: cookie pot is full!\n"); return -ENOSPC; } if (copy_from_user( &cookie_pot[cookie_index], buff, len )) { return -EFAULT; } cookie_index += len; cookie_pot[cookie_index-1] = 0; return len; } 對 fortune 進(jìn)行讀取也非常簡(jiǎn)單,如清單 11 所示。由于我們剛才寫(xiě)入數據的緩沖區(page)已經(jīng)在內核空間中了,因此可以直接對其進(jìn)行操作,并使用 sprintf 來(lái)寫(xiě)入下一個(gè) fortune。如果next_fortune 索引大于 cookie_index(要寫(xiě)入的下一個(gè)位置),那么我們就將 next_fortune 返回為 0,這是第一個(gè) fortune 的索引。在將這個(gè) fortune 寫(xiě)入用戶(hù)緩沖區之后,在 next_fortune 索引上增加剛才寫(xiě)入的 fortune 的長(cháng)度。這樣就變成了下一個(gè)可用 fortune 的索引。這個(gè) fortune 的長(cháng)度會(huì )被返回并傳遞給用戶(hù)。 清單 11. 對 fortune 進(jìn)行讀取操作所使用的函數 int fortune_read( char *page, char **start, off_t off, int count, int *eof, void *data ) { int len; if (off > 0) { *eof = 1; return 0; } /* Wrap-around */ if (next_fortune >= cookie_index) next_fortune = 0; len = sprintf(page, "%s\n", &cookie_pot[next_fortune]); next_fortune += len; return len; } 從這個(gè)簡(jiǎn)單的例子中,我們可以看出通過(guò) /proc 文件系統與內核進(jìn)行通信實(shí)際上是件非常簡(jiǎn)單的事情,F在讓我們來(lái)看一下這個(gè) fortune 模塊的用法(參見(jiàn)清單 12)。 清單 12. 展示 fortune cookie LKM 的用法 [root@plato]# insmod fortune.ko [root@plato]# echo "Success is an individual proposition. Thomas Watson" > /proc/fortune [root@plato]# echo "If a man does his best, what else is there? Gen. Patton" > /proc/fortune [root@plato]# echo "Cats: All your base are belong to us. Zero Wing" > /proc/fortune [root@plato]# cat /proc/fortune Success is an individual proposition. Thomas Watson [root@plato]# cat /proc/fortune If a man does his best, what else is there? General Patton [root@plato]# /proc 虛擬文件系統可以廣泛地用來(lái)報告內核的信息,也可以用來(lái)進(jìn)行動(dòng)態(tài)配置。我們會(huì )發(fā)現它對于驅動(dòng)程序和模塊編程來(lái)說(shuō)都是非常完整的。在下面的 參考資料 中,我們可以學(xué)習到更多相關(guān)知識。 |
我有個(gè)問(wèn)題請教,當用insmod 加載完驅動(dòng)后為什么用lsmod顯示不出,是文件系統不健全嗎?如果是在文件系統中應該怎樣設置?謝謝! |