在Linux下,驅動程序是內核的一部分,運行在內核態下,你可以將驅動靜態的和內核編譯在一起,這樣的缺點是內核會比較大,而且如果驅動出錯,會導致整個系統崩潰;也可以以module的方式編譯,在需要的時候動態的載入。如果你編譯過內核,應該記得在make menuconfig中,選項前面是可以選擇和的,就分別表示"編譯到內核中"和"編譯成模塊"。
.
下面介紹下模塊,一個簡單的“helloworld module”如下所示:
/* hello-1.c - The simplest kernel module. */ #include /* Needed by all modules */ #include /* Needed for KERN_ALERT */ int init_module(void) { printk("<1>Hello world 1.\n"); // A non 0 return means init_module failed; module can't be loaded. return 0; } void cleanup_module(void) { printk(KERN_ALERT "Goodbye world 1.\n"); }
其中,init_module函數是加載模塊時會被調用的,一般作一些初始化的工作;cleanup_module函數是卸載模塊時會被調用的,做一些清理的工作。因為模塊是運行在內核態的,你自然不能使用庫函數,因此要打印信息,需要使用printk函數而不是printf函數。另外,你可以使用任意函數名(只要同內核函數名不沖突)來替換init_module和cleanup_module這兩個函數名,但必須使用module_init(初始化函數名),module_exit(卸載時函數名)這兩個宏來聲明一下,也就是說,下面這個模塊和上面的模塊是等價的:
/* hello-1.c - The simplest kernel module. */ #include /* Needed by all modules */ #include /* Needed for KERN_ALERT */ int helloworld(void) { printk("<1>Hello world 1.\n"); // A non 0 return means init_module failed; module can't be loaded. return 0; } void goodbyeworld(void) { printk(KERN_ALERT "Goodbye world 1.\n"); } module_init(helloworld); module_exit(goodbyeworld);
.
編譯和加載一個模塊也很容易:
從上面的圖可以看到,我們通過make編譯生成一個模塊文件".ko"之后,使用"insmod"命令來加載模塊,那么“insmod”具體做了什么呢?
.
下面讓我們來介紹下insmod這個工具:
insmod是linux下加載模塊的工具,路徑一般是/sbin/insmod,當你調用這個工具后,它的工作基本如下:
- 在用戶空間打開待安裝的module
- 調用query_module()系統調用詢問無法落實的符號在內核或其他模塊中的地址
- 鏈接操作,落實模塊中的符號引用
- 調用create_module()系統調用在內核中創建module數據結構,並申請所需的內核空間
- 調用init_module()系統調用將鏈接好的module映像裝入內核空間,然后調用模塊中的init_module()函數(注意:這里面的兩個init_module函數不一樣,一個是系統調用,一個是你寫在模塊里面的函數)
.
內核導出的符號清單可以由下面的命令來查看:
more /proc/k[all]syms c0400000 T _text c0400000 T startup_32 c040007b t default_entry c04000d0 T startup_32_smp c0400152 t checkCPUtype c04001d3 t is486 c04001da t is386 c0400247 t check_x87 c040027a t setup_idt … c06adb25 T printk …
.
從已加載模塊中卸載模塊使用的是“rmmod”,"rmmod"所做工作如下:
- 調用delete_module()系統調用釋放模塊的module結構,同時釋放模塊所占的內核空間
- 調用模塊中的cleanup_module()的函數
.
那么,一般情況下,驅動程序會在init_module()和cleanu_module()函數中做些什么呢?
- init_module(): 向內核登記本模塊中一些包含着函數指針的數據結構(file_operations)
- cleanup_module(): 向內核撤銷本模塊提供的數據結構的登記,使內核在模塊拆卸后不至於再企圖訪問這些數據結構
.
一個字符型驅動的注冊函數如下:
#include #inlcude int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
- major: 設備的主設備號,若為空則系統動態分配
- name: 設備名
- fops: 函數指針結構,各個調用的入口
- 操作成功,設備名出現在/proc/devices文件
至於怎么創建一個有設備名和設備號的文件,可以通過man mknod獲取信息。
.
file_operantions的結構如下:
Linux-2.6.27.25 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_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 (*fsync) (struct file *, struct dentry *, int datasync); …… }
.
此外,初始化部分還負責為設備驅動申請系統資源,如:內存,時鍾,中斷,I/O端口等。