@上一篇介绍了linux阻塞与非阻塞的基本概念,以及应用程序的小demo和kernel层对应的api函数。那接下来就以实例来分析,如何在linux驱动层添加等待队列和轮询的方法,以及区别。
**
**
在linux驱动中,存在很常见的两种设备访问模式,所以在编写驱动的时候,一定要考虑到阻塞和非阻塞。这样做有以下好处
1. linux驱动标准的的写法,让你写的驱动正式,拿的出手,也能锻炼个人的规划能力、编程能力、思维能力;
2. 提高个人的审美能力,当你去看一个驱动,里面的各种框架,组件的交叉的逻辑之美,你会不禁赞叹,linux内核原来是这样,会有一种恍然大悟的感觉;
3.以上两点均是个人见解,阻塞和非阻塞有它存在的意义:针对不同的驱动设备,应运而生,提高驱动对设备资源访问的及时性,有效性,提高cpu的使用效率;
4.在应用程序对具体设备进行编程时,加阻塞和非阻塞对cpu的使用率,占有率是有巨大的影响。
举个例子:我们编写一个应用程序,读写按键的状态。在应用程序中,在while中轮询访问设备节点。如果应用中不加入阻塞方式,驱动中不加载阻塞方式,你应用程序在执行时,可以top看一下,执行文件在系统中占用cpu的使用率,接近100%,这样极大影响系统的整体性能,也非常不合理。加入阻塞方式,使用率会几乎为0%,这样才是我们寻求的正确方式。
**
**
1. 实例分析:
以usb/class/usblp.c 为例,在probe中init rwait wwrite 等待队列头,注册设备类—>设备类里加入设备文件操作接口—>在接口里添加文件poll属性—>编写对应的poll函数,当应用程序读访问时,宏定义一个read等待队列,如果没有数据可读,切换状态进入休眠状态,等待唤醒;如果可读,设置状态为runing,删除read等待队列。写同样,如下:
#include <linux/poll.h> struct usblp { ...... wait_queue_head_t rwait, wwait; ...... }; static void usblp_bulk_read(struct urb *urb) { ...... //如果有数据可读,就唤醒read队列 wake_up(&usblp->rwait); ...... } static void usblp_bulk_write(struct urb *urb) { ...... //如果有数据可写,就唤醒write队列 wake_up(&usblp->wwait); ...... } /* No kernel lock - fine */ static __poll_t usblp_poll(struct file *file, struct poll_table_struct *wait) { __poll_t ret; unsigned long flags; struct usblp *usblp = file->private_data; /* Should we check file->f_mode & FMODE_WRITE before poll_wait()? */ poll_wait(file, &usblp->rwait, wait); poll_wait(file, &usblp->wwait, wait); spin_lock_irqsave(&usblp->lock, flags); ret = ((usblp->bidir && usblp->rcomplete) ? EPOLLIN | EPOLLRDNORM : 0) | ((usblp->no_paper || usblp->wcomplete) ? EPOLLOUT | EPOLLWRNORM : 0); spin_unlock_irqrestore(&usblp->lock, flags); return ret; } static ssize_t usblp_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { ...... if ((rv = usblp_wwait(usblp, !!(file->f_flags & O_NONBLOCK))) < 0) goto raise_wait; ...... wake_up(&usblp->wwait); /* * Step 2: Wait for transfer to end, collect results. */ rv = usblp_wwait(usblp, !!(file->f_flags&O_NONBLOCK)); ...... } static int usblp_wwait(struct usblp *usblp, int nonblock) { DECLARE_WAITQUEUE(waita, current);//创建一个等待队列 int rc; int err = 0; //如果不可write,就将当前任务添加到wwait中宝股份, add_wait_queue(&usblp->wwait, &waita); for (;;) { if (mutex_lock_interruptible(&usblp->mut)) { rc = -EINTR; break; } //设置可以被TASK_INTERRUPTIBLE 信号打断。 set_current_state(TASK_INTERRUPTIBLE); rc = usblp_wtest(usblp, nonblock); mutex_unlock(&usblp->mut); if (rc <= 0) break; //进行任务切换,当前进程进入睡眠状态 if (schedule_timeout(msecs_to_jiffies(1500)) == 0) { if (usblp->flags & LP_ABORT) { err = usblp_check_status(usblp, err); if (err == 1) { /* Paper out */ rc = -ENOSPC; break; } } else { /* Prod the printer, Gentoo#251237. */ mutex_lock(&usblp->mut); usblp_read_status(usblp, usblp->statusbuf); mutex_unlock(&usblp->mut); } } } //如果可write,将当前任务状态设置为running,调用remove_wait_queue将进程从等待队列中删除 set_current_state(TASK_RUNNING); remove_wait_queue(&usblp->wwait, &waita); return rc; } static int usblp_rwait_and_lock(struct usblp *usblp, int nonblock) { DECLARE_WAITQUEUE(waita, current); int rc; add_wait_queue(&usblp->rwait, &waita); for (;;) { if (mutex_lock_interruptible(&usblp->mut)) { rc = -EINTR; break; } set_current_state(TASK_INTERRUPTIBLE); if ((rc = usblp_rtest(usblp, nonblock)) < 0) { mutex_unlock(&usblp->mut); break; } if (rc == 0) /* Keep it locked */ break; mutex_unlock(&usblp->mut); schedule(); } set_current_state(TASK_RUNNING); remove_wait_queue(&usblp->rwait, &waita); return rc; } static const struct file_operations usblp_fops = { .owner = THIS_MODULE, .read = usblp_read, .write = usblp_write, .poll = usblp_poll, .unlocked_ioctl = usblp_ioctl, .compat_ioctl = usblp_ioctl, .open = usblp_open, .release = usblp_release, .llseek = noop_llseek, }; static struct usb_class_driver usblp_class = { .name = "lp%d", .devnode = usblp_devnode, .fops = &usblp_fops, .minor_base = USBLP_MINOR_BASE, }; static int usblp_probe(struct usb_interface *intf, const struct usb_device_id *id) { ...... init_waitqueue_head(&usblp->rwait); init_waitqueue_head(&usblp->wwait); ...... retval = usb_register_dev(intf, &usblp_class); ...... return 0; } static void usblp_disconnect(struct usb_interface *intf) { ...... wake_up(&usblp->wwait); wake_up(&usblp->rwait); ...... } static struct usb_driver usblp_driver = { .name = "usblp", .probe = usblp_probe, .disconnect = usblp_disconnect, .suspend = usblp_suspend, .resume = usblp_resume, .id_table = usblp_ids, .supports_autosuspend = 1, }; ......
2.注意事项
a.将任务或者进程加入到等待队列头;
b.在合适的点唤醒等待队列,一般在中断中。
**
实例分析:
以kernel/usb/misc/ldusb.c 为例,在probe中init read write 等带队列,注册设备类—>设备类里加入设备文件操作接口—>在接口里添加文件poll属性—>编写对应的poll函数,当应用程序读写访问时,判断是否为非阻塞访问,根据poll函数pollin状态,就会调用相关的wake up函数,实现数据访问,实现非阻塞式方式。
#include <linux/poll.h>//kernel轮询头文件 /* Structure to hold all of our device specific stuff */ struct ld_usb { ...... wait_queue_head_t read_wait; //读队列头 wait_queue_head_t write_wait; //写队列头 ...... }; /** * ld_usb_write */ static ssize_t ld_usb_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) { ...... /* wait until previous transfer is finished */ //判断是否为非阻塞式访问,如果是write有效,没有返回-EAGAIN。 if (dev->interrupt_out_busy) { if (file->f_flags & O_NONBLOCK) { retval = -EAGAIN; goto unlock_exit; } //同read retval = wait_event_interruptible(dev->write_wait, !dev->interrupt_out_busy); if (retval < 0) { goto unlock_exit; } } ...... } /** * ld_usb_read */ static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { ...... //判断是否为非阻塞式访问,如果是read有效,没有返回-EAGAIN。 if (file->f_flags & O_NONBLOCK) { retval = -EAGAIN; goto unlock_exit; } //加入等待队列,等待唤醒,也就是有数据读,实现非阻塞read retval = wait_event_interruptible(dev->read_wait, dev->interrupt_in_done); ...... }; /** * ld_usb_poll * 用于处理非阻塞访问函数 * 参数file:要打开的设备文件 * 参数wait:等待列表(poll_table) */ static __poll_t ld_usb_poll(struct file *file, poll_table *wait) { struct ld_usb *dev; __poll_t mask = 0; dev = file->private_data; if (!dev->intf) return EPOLLERR | EPOLLHUP; //将等待队列头加到poll_table中 poll_wait(file, &dev->read_wait, wait); poll_wait(file, &dev->write_wait, wait); if (dev->ring_head != dev->ring_tail) //如果有数据,就返回EPOLLIN,表示有数据 mask |= EPOLLIN | EPOLLRDNORM; if (!dev->interrupt_out_busy) mask |= EPOLLOUT | EPOLLWRNORM; } /* file operations needed when we register this driver */ static const struct file_operations ld_usb_fops = { .owner = THIS_MODULE, .read = ld_usb_read, .write = ld_usb_write, .open = ld_usb_open, .release = ld_usb_release, .poll = ld_usb_poll, .llseek = no_llseek, }; /* * usb class driver info in order to get a minor number from the usb core, * and to have the device registered with the driver core */ static struct usb_class_driver ld_usb_class = { ...... .fops = &ld_usb_fops, ...... }; static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) { ...... //初始化read,write等待队列头 init_waitqueue_head(&dev->read_wait); init_waitqueue_head(&dev->write_wait); //注册类,实现poll,供应用程序访问 ...... retval = usb_register_dev(intf, &ld_usb_class); ...... } /** * ld_usb_disconnect * * Called by the usb core when the device is removed from the system. */ static void ld_usb_disconnect(struct usb_interface *intf) { ...... usb_deregister_dev(intf, &ld_usb_class); ...... //唤醒poller wake_up_interruptible_all(&dev->read_wait); wake_up_interruptible_all(&dev->write_wait); ...... }; /* usb specific object needed to register this driver with the usb subsystem */ static struct usb_driver ld_usb_driver = { .name = "ldusb", .probe = ld_usb_probe, .disconnect = ld_usb_disconnect, .id_table = ld_usb_table, }; module_usb_driver(ld_usb_driver);
a.等待队列:在read、write函数中,初始化化一个等待队列,并且实现状态切换,进入睡眠状态。 b.轮询:在read、write中判断函数是否为非阻塞式访问,在poll函数根据pollin状态唤醒等待队列,读写数据。