Linux字符设备驱动框架详解

所谓驱动程序,本质上讲是硬件接口,因为操作系统不可能实现每种硬件的接口,所以只对厂商提供接口,只要厂商实现这些接口,就可被操作系统调用,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 cppbreak@gmail.com"); 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 cppbreak@gmail.com 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函数。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/a85f8713204b6bdf29d85079eb2fc96b.html