linux设备驱动中的I/O

  1. 阻塞于非阻塞I/O
    1. 等待队列
  2. 轮询操作

阻塞于非阻塞I/O

阻塞I/O与非阻塞I/O是两种I/O访存的方式,一种当I/O资源不可用时,会阻塞进程执行,并放弃CPU资源;另一种当I/O资源不可用时会直接返回一个失败值。对这两种I/O方式都有其各自不同的应用场景,相对于非阻塞I/O来说,阻塞I/O在内核中的实现更为复杂。

阻塞I/O的实现依赖于等待队列机制,等待队列机制也是信号量机制实现的基础,它可以让依赖某事件的阻塞进程睡眠等待,直到适当的时机被唤醒。

等待队列

linux提供了等待队列的实现及接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
wait_queue_head_t wq;

//定义并初始化头部
init_waitqueue_head(&wq);
//DECLARE_WAIT_QUEUE_HEAD(name)

//定义等待队列元素
DECLARE_WAITQUEUE(name, tsk)

//添加/移除等待队列
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

//等待事件
wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)

//唤醒队列
void wake_up(wait_queue_head_t *q);
void wake_up_interruptible(wait_queue_head_t *q);

//在等待队列上睡眠
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);

如下为等待队列使用的一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static ssize_t xxx_write(struct file *filp, const char *buffer, size_t count, loff_t *ppos)
{
...
DECLARE_WAITQUEUE(wait, current);
add_wait_queue(&xxx_wait, &wait);

/* 等待设备缓冲区可写 */
do {
avail = device_writable(...);
if (avail < 0) {
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
schedule();
if (signal_pending(current)) {
ret = -ERESTARTSYS;
goto out;
}
}
} while (avail < 0);

/* 写设备缓冲 */
device_write(...);
out:
remove_wait_queue(&xxx_wait, &wait);
__set_current_state(TASK_RUNNING);
return ret;
}

阻塞与非阻塞I/O的实现与进程状态密切相关,因此在做相应开发时,需要深刻理解进程状态的切换。同时通过下图可以了解进程与等待队列之间的关系。

wait\_queue\_head\_t、wait\_queue与task\_struct之间的关系

轮询操作

一般I/O操作能够满足我们通常的需求,但随着计算机技术和市场需求的发展,特别是高并发网络服务器的需求,对于I/O操作的多路复用问题成为一个不得不考虑的问题。在这样的背景下,select、poll、epoll等这样的事件模型应运而生。

linux中提供了select,poll这样的系统调用,使得对多个设备进行无阻塞的访问变得更加方便,效率也更高。这为用户空间的I/O编程提供了更统一的策略。但这一切都是建立在内核的相应实现上的。

我们要对一个设备进行select、poll的操作,需要在相应设备驱动中实现如下接口:

1
unsigned int (*poll)(struct file *filp, struct poll_table *wait);

其中filp是相应的文件指针,而wait则是轮询表指针。可以使用poll_wait接口将可能引起设备状态变化的等待队列头部添加到轮询表中。其定义如下:

1
void poll_wait(struct file *filp, wait_queue_head_t *queue, struct poll_table *wait);

而实现的poll接口返回值则为设备资源的可获取状态,如POLLIN、POLLOUT、POLLPRI、POLLERR、POLLNVAL的‘或’结果。

因此驱动中应实现的poll接口应如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static unsigned int xxx_poll(struct file *filp, struct poll_table *wait)
{
unsigned int mask = 0;
struct xxx_dev *dev = filp->private_data;

...
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait);

if (...) {
mask |= POLLIN | POLLRDNORM;
}

if (...) {
mask |= POLLOUT | POLLWRNORM;
}
...
return mask;
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 yxhlfx@163.com

文章标题:linux设备驱动中的I/O

本文作者:红尘追风

发布时间:2016-06-23, 18:55:30

原始链接:http://www.micernel.com/2016/06/23/linux%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E7%9A%84I_O/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录