|
清單 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]# 注意,內核的輸出進到了內核回環緩沖區中,而不是打印到 stdout 上,這是因為 stdout 是進程特有的環境。要查看內核回環緩沖區中的消息,可以使用 dmesg 工具(或者通過 /proc 本身使用 cat /proc/kmsg 命令)。清單 6 給出了 dmesg 顯示的最后幾條消息。 清單 6. 查看來自 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]# 可以在內核輸出中看到這個模塊的消息。現在讓我們暫時離開這個簡單的例子,來看幾個可以用來開發有用 LKM 的內核 API。 集成到 /proc 文件系統中 內核程序員可以使用的標準 API,LKM 程序員也可以使用。LKM 甚至可以導出內核使用的新變量和函數。有關 API 的完整介紹已經超出了本文的范圍,因此我們在這里只是簡單地介紹后面在展示一個更有用的 LKM 時所使用的幾個元素。 創建并刪除 /proc 項 要在 /proc 文件系統中創建一個虛擬文件,請使用 create_proc_entry 函數。這個函數可以接收一個文件名、一組權限和這個文件在 /proc 文件系統中出現的位置。create_proc_entry 的返回值是一個proc_dir_entry 指針(或者為 NULL,說明在 create 時發生了錯誤)。然后就可以使用這個返回的指針來配置這個虛擬文件的其他參數,例如在對該文件執行讀操作時應該調用的函數。create_proc_entry的原型和 proc_dir_entry 結構中的一部分如清單 7 所示。 清單 7. 用來管理 /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 命令來插入對這個虛擬文件進行讀寫的函數。 要從 /proc 中刪除一個文件,可以使用 remove_proc_entry 函數。要使用這個函數,我們需要提供文件名字符串,以及這個文件在 /proc 文件系統中的位置(parent)。這個函數原型如清單 7 所示。 parent 參數可以為 NULL(表示 /proc 根目錄),也可以是很多其他值,這取決于我們希望將這個文件放到什么地方。表 1 列出了可以使用的其他一些父 proc_dir_entry,以及它們在這個文件系統中的位置。 表 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 中寫入一項。這個函數的原型如下: int mod_write( struct file *filp, const char __user *buff, unsigned long len, void *data ); filp 參數實際上是一個打開文件結構(我們可以忽略這個參數)。buff 參數是傳遞給您的字符串數據。緩沖區地址實際上是一個用戶空間的緩沖區,因此我們不能直接讀取它。len 參數定義了在 buff 中有多少數據要被寫入。data 參數是一個指向私有數據的指針(參見 清單 7)。在這個模塊中,我們聲明了一個這種類型的函數來處理到達的數據。 Linux 提供了一組 API 來在用戶空間和內核空間之間移動數據。對于 write_proc 的情況來說,我們使用了 copy_from_user 函數來維護用戶空間的數據。 讀回調函數 我們可以使用 read_proc 函數從一個 /proc 項中讀取數據(從內核空間到用戶空間)。這個函數的原型如下: int mod_read( char *page, char **start, off_t off, int count, int *eof, void *data ); page 參數是這些數據寫入到的位置,其中 count 定義了可以寫入的最大字符數。在返回多頁數據(通常一頁是 4KB)時,我們需要使用 start 和 off 參數。當所有數據全部寫入之后,就需要設置 eof(文件結束參數)。與 write 類似,data 表示的也是私有數據。此處提供的 page 緩沖區在內核空間中。因此,我們可以直接寫入,而不用調用 copy_to_user。 其他有用的函數 我們還可以使用 proc_mkdir、symlinks 以及 proc_symlink 在 /proc 文件系統中創建目錄。對于只需要一個 read 函數的簡單 /proc 項來說,可以使用 create_proc_read_entry,這會創建一個 /proc 項,并在一個調用中對 read_proc 函數進行初始化。這些函數的原型如清單 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 回頁首 通過 /proc 文件系統實現財富分發 下面是一個可以支持讀寫的 LKM。這個簡單的程序提供了一個財富甜點分發。在加載這個模塊之后,用戶就可以使用 echo 命令向其中導入文本財富,然后再使用 cat 命令逐一讀出。 清單 9 給出了基本的模塊函數和變量。init 函數(init_fortune_module)負責使用 vmalloc 來為這個點心罐分配空間,然后使用 memset 將其全部清零。使用所分配并已經清空的 cookie_pot 內存,我們在 /proc 中創建了一個 proc_dir_entry 項,并將其稱為 fortune。當 proc_entry 成功創建之后,對自己的本地變量和 proc_entry 結構進行了初始化。我們加載了 /proc read 和 write 函數(如清單 9 和清單 10 所示),并確定這個模塊的所有者。cleanup 函數簡單地從 /proc 文件系統中刪除這一項,然后釋放 cookie_pot 所占據的內存。 cookie_pot 是一個固定大小(4KB)的頁,它使用兩個索引進行管理。第一個是 cookie_index,標識了要將下一個 cookie 寫到哪里去。變量 next_fortune 標識了下一個 cookie 應該從哪里讀取以便進行輸出。在所有的 fortune 項都讀取之后,我們簡單地回到了 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 ); 向這個罐中新寫入一個 cookie 非常簡單(如清單 10 所示)。使用這個寫入 cookie 的長度,我們可以檢查是否有這么多空間可用。如果沒有,就返回 -ENOSPC,它會返回給用戶空間。否則,就說明空間存在,我們使用 copy_from_user 將用戶緩沖區中的數據直接拷貝到 cookie_pot 中。然后增大 cookie_index(基于用戶緩沖區的長度)并使用 NULL 來結束這個字符串。最后,返回實際寫入 cookie_pot的字符的個數,它會返回到用戶進程。 清單 10. 對 fortune 進行寫入操作所使用的函數 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 進行讀取也非常簡單,如清單 11 所示。由于我們剛才寫入數據的緩沖區(page)已經在內核空間中了,因此可以直接對其進行操作,并使用 sprintf 來寫入下一個 fortune。如果next_fortune 索引大于 cookie_index(要寫入的下一個位置),那么我們就將 next_fortune 返回為 0,這是第一個 fortune 的索引。在將這個 fortune 寫入用戶緩沖區之后,在 next_fortune 索引上增加剛才寫入的 fortune 的長度。這樣就變成了下一個可用 fortune 的索引。這個 fortune 的長度會被返回并傳遞給用戶。 清單 11. 對 fortune 進行讀取操作所使用的函數 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; } 從這個簡單的例子中,我們可以看出通過 /proc 文件系統與內核進行通信實際上是件非常簡單的事情。現在讓我們來看一下這個 fortune 模塊的用法(參見清單 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 虛擬文件系統可以廣泛地用來報告內核的信息,也可以用來進行動態配置。我們會發現它對于驅動程序和模塊編程來說都是非常完整的。在下面的 參考資料 中,我們可以學習到更多相關知識。 |
我有個問題請教,當用insmod 加載完驅動后為什么用lsmod顯示不出,是文件系統不健全嗎?如果是在文件系統中應該怎樣設置?謝謝! |