序言
Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别。在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调试也不方便。本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,获得了一些经验,愿Linux fans共享,有不当之处,请予指正。
以下的一些文字主要来源于khg,johnsonm的Write linux device driver,Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关device driver的一些资料。 这些资料有的已经过时,有的还有一些错误,我依据自己的试验结果进行了修正。
一 Linux device driver 的概念
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:
1 对设备初始化和释放。
2 把数据从内核传送到硬件和从硬件读取数据。
3 读取应用程序传送给设备文件的数据和回送应用程序请求的数据。
4 检测和处理设备出现的错误。
在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是块设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。
已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。//hehe
二 实例剖析
我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。不过我的kernel是2。0。34,在低版本的kernel上可能会出现问题,我还没测试过。//xixi
#define __NO_VERSION__ #include #include char kernel_version [] = UTS_RELEASE; |
struct file_operations { int (*seek) (struct inode * ,struct file *, off_t ,int); int (*read) (struct inode * ,struct file *, char ,int); int (*write) (struct inode * ,struct file *, off_t ,int); int (*readdir) (struct inode * ,struct file *, struct dirent * ,int); int (*select) (struct inode * ,struct file *, int ,select_table *); int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *); int (*open) (struct inode * ,struct file *); int (*release) (struct inode * ,struct file *); int (*fsync) (struct inode * ,struct file *); int (*fasync) (struct inode * ,struct file *,int); int (*check_media_change) (struct inode * ,struct file *); int (*revalidate) (dev_t dev); } |
#include #include #include #include #include unsigned int test_major = 0; static int read_test(struct inode *node,struct file *file, char *buf,int count) { int left; if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT ) return -EFAULT; for(left = count ; left > 0 ; left--) { __put_user(1,buf,1); buf++; } return count; } |
static int write_tibet(struct inode *inode,struct file *file, const char *buf,int count) { return count; } static int open_tibet(struct inode *inode,struct file *file ) { MOD_INC_USE_COUNT; return 0; } static void release_tibet(struct inode *inode,struct file *file ) { MOD_DEC_USE_COUNT; } |
struct file_operations test_fops = { NULL, read_test, write_test, NULL, /* test_readdir */ NULL, NULL, /* test_ioctl */ NULL, /* test_mmap */ open_test, release_test, NULL, /* test_fsync */ NULL, /* test_fasync */ /* nothing more, fill with NULLs */ }; |
int init_module(void) { int result; result = register_chrdev(0, "test", &test_fops); if (result < 0) { printk(KERN_INFO "test: can't get major number "); return result; } if (test_major == 0) test_major = result; /* dynamic */ return 0; } |
void cleanup_module(void) { unregister_chrdev(test_major, "test"); } |
$ gcc -O2 -DMODULE -D__KERNEL__ -c test。c |
ld -r file1。o file2。o -o modulename。 |
$ insmod -f test |
$ rmmod test |
mknod /dev/test c major minor |
$ cat /proc/devices | awk "/$2=="test" {print /$1}" |
#include #include #include #include main() { int testdev; int i; char buf[10]; testdev = open("/dev/test",O_RDWR); if ( testdev == -1 ) { printf("Cann't open file "); exit(0); } read(testdev,buf,10); for (i = 0; i < 10;i++) printf("%d ",buf[i]); close(testdev); } |
int request_irq(unsigned int irq , void(*handle)(int,void *,struct pt_regs *), unsigned int long flags, const char *device); |