所謂驅動程序,本質上講是硬件接口,因為操作系統不可能實現每種硬件的接口,所以只對廠商提供接口,只要廠商實現這些接口,就可被操作系統調用,Linux系統驅動程序分為字符設備驅動和塊設備驅動,所謂字符設備驅動就是例如鍵盤驅動,只能順次讀取數據,塊設備驅動入硬盤等,可以隨機分塊讀取。而有些程序雖然符合驅動程序規范,但卻不真正驅動硬件,而是對操作系統功能的擴充,也稱作內核模塊。所以驅動程序和內核模塊本質上講屬於同一種類別。
操作系統對字符設備驅動提供 file_operations 結構,該結構成員大部分都是回調函數(以下代碼摘自Linux 2.6.34內核源碼):
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 *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, 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);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
作為驅動程序,只需要實現部分回調函數,注冊該結構體,用戶態進程便可通過系統調用open,read等調用相應回調函數指針。
首先,系統提供若干宏聲明驅動程序的屬性,如,入口,作者,描述信息等等。
初始化程序原型為 static int __init initialization(void);若初始化成功則返回0,否則返回錯誤碼。
清理函數原型為static void __exit cleanup(void);其中__init __exit是指定代碼段屬性的宏,當然也可不指定此屬性。
另外宏MODULE_AUTHOR指明作者等等
實現該函數後便可通過宏指明入口點:
module_init(initialization);
module_exit(cleanup);
最簡單的驅動程序就是僅僅實現這兩個函數,文件simple.c如下:
#include<linux/init.h> #include<linux/module.h> static int __init initialization(void) { printk(KERN_INFO " init simple\n"); return 0; } static void __exit cleanup(void) { printk(KERN_INFO " cleanup simple\n"); } module_init(initialization); module_exit(cleanup); MODULE_AUTHOR("alloc [email protected]"); MODULE_DESCRIPTION("A simple linux kernel module"); MODULE_VERSION("V0.1"); MODULE_LICENSE("Dual BSD/GPL");
驅動的編譯需要寫Makefile文件,內容如下
obj-m := simple.o
編譯時需指定Linux內核源碼所處位置:
make -C /usr/src/linux M=$PWD modules
其中/usr/src/linux為當前內核源碼目錄,$PWD為驅動程序所處目錄,PWD為當前目錄。
執行成功後,會生成simple.ko文件,此文件即為驅動程序。
加載驅動程序可執行 insmod simple.ko 卸載驅動執行 rmmod simple
命令 lsmod 可以查看目前系統加載的驅動程序,modinfo simple.ko 查看程序的基本信息,輸出即為程序聲明信息:
filename: simple.ko
license: Dual BSD/GPL
version: V0.1
description: A simple linux kernel module
author: alloc [email protected]
srcversion: 95E3CE3AB899900656E9CAD
depends:
vermagic: 2.6.33.3-85.fc13.x86_64 SMP mod_unload
在程序中調用了兩次printk,為內核輸出函數,這裡的輸出不會顯示到控制台,只會輸出到內核,可以通過讀取/proc/kmsg文件查看信息,或者調用dmesg命令查看,此為內核跟蹤錯誤的重要手段。KERN_INFO宏只是一個數字字符串,含義為日志級別,可以通過echo num > /proc/sys/kernel/printk 來控制輸出信息的級別。
當然,只有此兩個函數只能正常加載卸載驅動程序,並沒有任何意義,下面通過注冊回調函數來實現字符設備的功能,只舉一個簡單的例子,實現open,read,close函數。
根據file_operations結構成員的原型,這裡我們需要實現如下回調:
int (*open) (struct inode *, struct file *);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
int (*release) (struct inode *, struct file *);
open和close函數為了簡單起見不做任何處理,只是簡單的輸出kernel信息:
int simple_open(struct inode * pnode, struct file * pfile)
{
printk(KERN_INFO "open simple\n");
return 0;
}
int simple_release(struct inode * pnode, struct file * pfile)
{
printk(KERN_INFO "close simple\n");
return 0;
}