@上一篇介绍了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状态唤醒等待队列,读写数据。