Linux设备驱动工程师之路——高级字符设备驱动程序
K-Style
转载请注明来自于衡阳师范学院08电2 K-Style http://blog.csdn.net/ayangke,QQ:843308498 邮箱:[email protected]
高级字符设备驱动在简单字符驱动的基础上添加ioctl方法、阻塞非阻塞读写、poll方法、和自动创建设备文件的功能。
一、重要知识点
1.ioctl
ioctl命令:使用4个字段定义一个ioctl命令,包括
type: 幻数,一般使用一个字符定义,在内核中唯一。
number: 序数。
direction: 数据传输方向,当不涉及数据传输时,此字段无效。
size: 所涉及用户数据的大小,当不涉及数据传输时,此字段无效。
_IOC_NONE
_IOC_READ
_IOC_WRITE
“方向”字段的可能值。“读”和“写”是不同的位,可以用“OR”在一起指定读写。
_IOC(dir, type, size)
_IO(type,nr)
_IOR(type, nr, size)
_IOW(type, nr, size)
用于生产ioctl命令的宏
_IOC_DIR(cmd)
_IOC_TYPE(cmd)
_IOC_NR(cmd)
_IOC_SIZE(cmd)
用于解码ioctl命令的宏
intaccess_ok(int type, const void *addr, unsigned long size)
这个函数验证指向用户空间的指针是否可用,如果允许访问,access_ok返回非0值。
int put_user(datum, ptr)
int get_user(local, ptr)
int __put_user(datum, ptr)
int __get_user(local, ptr)
用于向(或从)用户空间保存(或获取)单个数据项的宏。传送的字节数目由sizeof(*ptr)决定。前两个要先调用access_ok,后两个(__put_user和__get_user)则假设access_ok已经被调用过了。
2.阻塞型I/O
typedef struct {/*…..*/} wait_queue_head_t
void init_waitqueue_head(wait_queue_head_t*queue)
DECLARE_WAIT_QUEUE_HEAD(queue)
预先定义的Linux内核等待队列类型。wait_queue_head_t类型必须显示地初始化,初始化方法可以在运行时调用init_waitqueue_head,或在编译时DECLARE_WAIT_QUEUE_HEAD。
void wait_event((wait_queue_head_t q, intcondition)
int wait_event_interruptible(wait_queue_head_tq, int condition)
int wait_event_timeout(wait_queue_head_t q,int condition, int time)
int wait_event_interruptible_timeout(wait_queue_head_tq, int condition, int time)
使进程在指定的队列上休眠,直到给定的condition值为真。
void wake_up(struct wait_queue **q)
void wake_up_interruptible(structwait_queue **q)
这些函数唤醒休眠在队列q上的进程。_interruptible形式的函数只能唤醒可中断的进程。在实践中约定做法是在使用wait_event时用wake_up,而在使用wait_event_interruptible时使用wake_up_interruptible。
3.poll方法
poll方法分两步处理,第一步调用poll_wait指定等待队列,第二步返回是否可操作的掩码。
POLLIN表示设备可读的掩码,POLLRDORM表示数据可读的掩码。POLLOUT表示设备可写的掩码,POLLWRNORM表示数据可读的掩码。一般同时返回POLLIN和POLLRDORM或者POLLOUT和POLLWRNORM。
4.select系统调用
原型为intselect(int mafdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set*restrict exceptfds, struct timeval *restrict tvptr)
返回值:就绪的描述符数,若超时则返回0,若出错则返回-1
void FD_ISSET(int fd, fd_set *fdset)
void FD_CLR(int fd, fd_set *fdset)
void FD_SET(int fd, fd_set *fdset)
void FD_ZERO(fd_set *fdset)
调用FD_ZERO将一个指定的fd_set变量的所有位设置为0。调用FD_SET设置一个fd_set变量指定位。调用FD_CLR则将一指定位清除。最后,调用FD_ISSET测试一指定位是否设置。
5.自动创建设备文件
struct class *class_create(struct module*owner, const char *name)
struct device *device_create(struct class*class, struct device *parent, dev_t devt, const char *fmt, ...)
通过这两个函数可以专门用来创建一个字符设备文件节点,class_create 第一个参数指定所有者,第二参数指定类得名字。class_device_create第一个参数指定第一个参数指定所要创建的设备所从属的类,第二个参数是这个设备的父设备,如果没有就指定为NULL,第三个参数是设备号,第四个参数是设备名称。
二、驱动代码
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #include <linux/ioctl.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/device.h> #define MEMDEV_MAJOR 251 #define MEMDEV_NUM 2 #define MEMDEV_SIZE 1024 //定义设备IOCTL命令 #define MEMDEV_IOC_MAGIC 'k' #define MEMDEV_IOC_NR 2 #define MEMDEV_IOC_PRINT_IO(MEMDEV_IOC_MAGIC, 0) #define MEMDEV_IOC_RD_IOR(MEMDEV_IOC_MAGIC, 1, int) #define MEMDEV_IOC_WT_IOW(MEMDEV_IOC_MAGIC, 2, char) struct mem_dev { unsignedint size; char*data; structsemaphore sem; wait_queue_head_t inque; }; static int mem_major = MEMDEV_MAJOR; struct cdev mem_cdev; struct mem_dev *mem_devp; bool havedata = false; static int mem_open(struct inode *inode,struct file *filp) { structmem_dev *dev; unsignedint num; printk("mem_open.\n"); num= MINOR(inode->i_rdev);//获得次设备号 if(num> (MEMDEV_NUM -1)) //检查次设备号有效性 return-ENODEV; dev= &mem_devp[num]; filp->private_data= dev; //将设备结构保存为私有数据 return0; } static int mem_release(struct inode *inode,struct file *filp) { printk("mem_release.\n"); return0; } static ssize_t mem_read(struct file *filp,char __user *buf, size_t size, loff_t *ppos) { intret = 0; structmem_dev *dev; unsignedlong p; unsignedlong count; printk("mem_read.\n"); dev= filp->private_data;//获得设备结构 count= size; p= *ppos; //检查偏移量和数据大小的有效性 if(p> MEMDEV_SIZE) return0; if(count> (MEMDEV_SIZE-p)) count= MEMDEV_SIZE - p; if(down_interruptible(&dev->sem))//锁定互斥信号量 return-ERESTARTSYS; while(!havedata) { up(&dev->sem); if(filp->f_flags& O_NONBLOCK) return-EAGAIN; printk("readyto go sleep"); if(wait_event_interruptible(dev->inque,havedata))//等待数据 return-ERESTARTSYS; if(down_interruptible(&dev->sem)) return-ERESTARTSYS; } //读取数据到用户空间 if(copy_to_user(buf,dev->data+p, count)){ ret= -EFAULT; printk("copyfrom user failed\n"); } else{ *ppos+= count; ret= count; printk("read%ld bytes from dev\n", count); havedata= false;//数据已经读出 } up(&dev->sem);//解锁互斥信号量 returnret; } static ssize_t mem_write(struct file *filp,const char __user *buf, size_t size, loff_t *ppos)//注意:第二个参数和read方法不同 { intret = 0; structmem_dev *dev; unsignedlong p; unsignedlong count; printk("mem_write.\n"); dev= filp->private_data; count= size; p= *ppos; if(p> MEMDEV_SIZE) return0; if(count> (MEMDEV_SIZE-p)) count= MEMDEV_SIZE - p; if(down_interruptible(&dev->sem))//锁定互斥信号量 return-ERESTARTSYS; if(copy_from_user(dev->data+p,buf, count)){ ret= -EFAULT; printk("copyfrom user failed\n"); } else{ *ppos+= count; ret= count; printk("write%ld bytes to dev\n", count); havedata= true; wake_up_interruptible(&dev->inque);//唤醒等待数据的队列 } up(&dev->sem);//解锁互斥信号量 returnret; } static loff_t mem_llseek(struct file *filp,loff_t offset, int whence) { intnewpos; printk("mem_llseek.\n"); switch(whence) { case0://从文件头开始 newpos= offset; break; case1://从文件当前位置开始 newpos= filp->f_pos + offset; break; case2://从文件末尾开始 newpos= MEMDEV_SIZE - 1 + offset; break; default: return-EINVAL; } if((newpos<0)|| (newpos>(MEMDEV_SIZE - 1))) return-EINVAL; filp->f_pos= newpos; returnnewpos; } static int mem_ioctl(struct inode *inode,struct file *filp, unsigned int cmd, unsigned long arg) { interr = 0, ret = 0; intioarg = 0; charrdarg = '0'; //参数检查 if(_IOC_TYPE(cmd)!= MEMDEV_IOC_MAGIC)//参数类型检查 return-ENOTTY; if(_IOC_NR(cmd)> MEMDEV_IOC_NR)//参数命令号检查 return-ENOTTY; //用户空间指针有效性检查 if(_IOC_DIR(cmd)& _IOC_READ) err= !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); elseif(_IOC_DIR(cmd) & _IOC_WRITE) err= !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); if(err) return-ENOTTY; //根据命令执行操作 switch(cmd) { case MEMDEV_IOC_PRINT: printk("memdevioctl print excuting...\n"); break; caseMEMDEV_IOC_RD: ioarg= 1024; ret = __put_user(ioarg, (int *)arg);//用户空间向内核空间获得数据 printk("memdevioctl read excuting... \n"); break; caseMEMDEV_IOC_WT: ret= __get_user(rdarg, (char *)arg);//用户空间向内核空间传输数据 printk("memdevioctl write excuting... arg:%c\n", rdarg); break; default: return-ENOTTY; } returnret; } static unsigned int mem_poll(struct file*filp, poll_table *wait) { structmem_dev *dev; unsignedint mask = 0; dev= filp->private_data; if(down_interruptible(&dev->sem))//锁定互斥信号量 return-ERESTARTSYS; poll_wait(filp,&dev->inque, wait); if(havedata) mask|= POLLIN | POLLRDNORM;//返回可读掩码 up(&dev->sem);//释放信号量 returnmask; } static const struct file_operationsmem_fops = { .owner= THIS_MODULE, .open= mem_open, .write= mem_write, .read= mem_read, .release= mem_release, .llseek= mem_llseek, .ioctl= mem_ioctl, .poll= mem_poll, }; static int __init memdev_init(void) { intresult; interr; inti; structclass *memdev_class; //申请设备号 dev_tdevno = MKDEV(mem_major, 0); if(mem_major) result= register_chrdev_region(devno, MEMDEV_NUM, "memdev");//注意静态申请的dev_t参数和动态dev_t参数的区别 else{ //静态直接传变量,动态传变量指针 result= alloc_chrdev_region(&devno, 0, MEMDEV_NUM, "memdev"); mem_major= MAJOR(devno); } if(result< 0){ printk("can'tget major devno:%d\n", mem_major); returnresult; } //注册设备驱动 cdev_init(&mem_cdev,&mem_fops); mem_cdev.owner= THIS_MODULE; err= cdev_add(&mem_cdev, MKDEV(mem_major, 0), MEMDEV_NUM);//如果有N个设备就要添加N个设备号 if(err) printk("add cdev faild,err is%d\n", err); //分配设备内存 mem_devp= kmalloc(MEMDEV_NUM*(sizeof(struct mem_dev)), GFP_KERNEL); if(!mem_devp){ result = - ENOMEM; goto fail_malloc; } memset(mem_devp,0, MEMDEV_NUM*(sizeof(struct mem_dev))); for(i=0;i<MEMDEV_NUM; i++){ mem_devp[i].size= MEMDEV_SIZE; mem_devp[i].data= kmalloc(MEMDEV_SIZE, GFP_KERNEL); memset(mem_devp[i].data,0, MEMDEV_SIZE); init_MUTEX(&mem_devp[i].sem);//初始化互斥锁 //初始化等待队列 init_waitqueue_head(&mem_devp[i].inque); } //自动创建设备文件 memdev_class= class_create(THIS_MODULE, "memdev_driver"); device_create(memdev_class,NULL, MKDEV(mem_major, 0), NULL, "memdev0"); returnresult; fail_malloc: unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM); returnresult; } static void memdev_exit(void) { cdev_del(&mem_cdev); unregister_chrdev_region(MKDEV(mem_major,0), MEMDEV_NUM);//注意释放的设备号个数一定要和申请的设备号个数保存一致 //否则会导致设备号资源流失 printk("memdev_exit\n"); } module_init(memdev_init); module_exit(memdev_exit); MODULE_AUTHOR("Y-Kee"); MODULE_LICENSE("GPL");