Welcome 微信登录
编程资源 图片资源库 蚂蚁家优选 PDF转换器

首页 / 操作系统 / Linux / 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 ");return 0;}static void __exit cleanup(void){printk(KERN_INFO " cleanup simple ");}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.kolicense:Dual BSD/GPLversion:V0.1description:A simple linux kernel moduleauthor: alloc cppbreak@gmail.comsrcversion: 95E3CE3AB899900656E9CADdepends: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 ");return 0;}int simple_release(struct inode * pnode, struct file * pfile){printk(KERN_INFO "close simple ");return 0;}