linux驱动 阻塞和非阻塞IO 篇二

2021/9/14 7:04:48

本文主要是介绍linux驱动 阻塞和非阻塞IO 篇二,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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


这篇关于linux驱动 阻塞和非阻塞IO 篇二的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程