目录
一、阻塞和非阻塞IO简介
二、等待队列
2.1 等待队列头
2.2 等待队列项
2.3 将队列项添加/移除等待队列头
2.4 等待唤醒
2.5 等待事件
三、轮询
四、驱动代码
4.1 阻塞IO
4.2 非阻塞IO
一、阻塞和非阻塞IO简介
IO指的是Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。
当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式IO就会将应用程序对应的线程挂起,直到设备资源可以获取为止:
对于非阻塞IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功:
应用程序使用如下代码来实现阻塞访问驱动设备文件:
int fd;
int data = 0; fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */
应用程序使用如下代码来实现非阻塞访问驱动设备文件:
int fd;
int data = 0; fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */
二、等待队列
2.1 等待队列头
阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里完成唤醒工作。
Linux内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作,如果在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体wait_queue_head_t表示,wait_queue_head_t结构体定义在文件include/linux/wait.h中:
struct __wait_queue_head { spinlock_t lock; struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
使用init_waitqueue_head函数初始化等待队列头:
void init_waitqueue_head(wait_queue_head_t *q)
q:初始化的等待队列头。
也可以使用宏DECLARE_WAIT_QUEUE_HEAD来一次性完成等待队列头的定义和初始化。
2.2 等待队列项
每个访问设备的进程都是一个队列项,当设备不可用时要将这些进程对应的等待队列项添加到等待队列里面。结构体wait_queue_t表示等待队列项:
struct __wait_queue { unsigned int flags; void *private; wait_queue_func_t func; struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
使用宏DECLARE_WAITQUEUE来一次性完成等待队列项的定义和初始化:
DECLARE_WAITQUEUE(name, tsk)
name:等待队列项的名字;
tsk:表示这个等待队列项属于哪个任务 (进程),一般设置为current,在 Linux内核中current相当于一个全局变量,表示当前进程。
因此宏DECLARE_WAITQUEUE就是给当前正在运行的进程创建并初始化一个等待队列项。
2.3 将队列项添加/移除等待队列头
当设备不可访问的时候需要将进程对应的等待队列项添加到前面创建的等待队列头中,添加到等待队列头中以后进程进入休眠态。当设备可访问后再将进程对应的等待队列项从等待队列头中移除。
等待队列项添加API函数:
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
q:等待队列项要加入的等待队列头。
wait:要加入的等待队列项。
返回值:无。
等待队列项移除API函数:
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
q:要删除的等待队列项所处的等待队列头。
wait:要删除的等待队列项。
返回值:无。
2.4 等待唤醒
当设备可使用时就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数:
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
q:要唤醒的等待队列头,这两个函数会将该等待队列头中的所有进程都唤醒。
wake_up函数可以唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进
程;wake_up_interruptible函数只能唤醒处于TASK_INTERRUPTIBLE状态的进程。
2.5 等待事件
除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程:
函数 | 描述 |
---|---|
wait_event(wq, condition) | 等待以wq为等待队列头的等待队列被唤醒,前提是condition条件必须满足 (为真 ),否则一直阻塞。此函数会将进程设置为TASK_UNINTERRUPTIBLE状态。 |
wait_event_timeout(wq, condition, timeout) | 功能和wait_event类似,但是此函数可以添加超时时间,以jiffies为单位。此函数有返回值,如果返回0的话表示超时时间到,而且condition为假,返回1的话表示condition为真,也就是条件满足了。 |
wait_event_interruptible(wq, condition) | 与wait_event函数类似,但此函数将进程设置为TASK_INTERRUPTIBLE,即可以被信号打断。 |
wait_event_interruptible_timeout(wq, condition, timeout) | 与wait_event_timeout函数和wait_event_interruptible函数类似。 |
三、轮询
如果用户应用程序非阻塞访问设备,设备驱动程序就要提供非阻塞的处理方式,即轮询。
当应用程序通过select、epoll或poll函数来查询设备是否可以操作时,设备驱动程序中的poll操作函数就会执行,如果可以操作的话就从设备读取或者向设备写入数据:
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
filp:要打开的设备文件(文件描述符)。
wait:结构体poll_table_struct类型指针,由应用程序传递进来的。一般将此参数传递给poll_wait函数。
返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如表:
POLLIN | 有数据可以读取。 |
POLLPRI | 有紧急的数据需要读取。 |
POLLOUT | 可以写数据。 |
POLLERR | 指定的文件描述符发生错误。 |
POLLHUP | 指定的文件描述符挂起。 |
POLLNVAL | 无效的请求。 |
POLLRDNORM | 等同于 POLLIN,普通数据可读。 |
需要在驱动程序的poll函数中调用poll_wait函数,poll_wait函数不会引起阻塞,只是将应用程序添加到poll_table中:
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
wait_address:要添加到poll_table中的等待队列头;
p:poll_table,即file_operations中poll操作函数的wait参数。
四、驱动代码
以Linux驱动开发——(六)按键中断实验的驱动代码为模板修改。
4.1 阻塞IO
添加宏:
#define IMX6UIRQ_NAME "blockio"
在imx6uirq设备结构体内添加变量:
wait_queue_head_t r_wait;
在定期器服务函数添加:
/* 唤醒进程 */
if(atomic_read(&dev->releasekey)) { /* 完成一次按键过程 */ /* wake_up(&dev->r_wait); */ wake_up_interruptible(&dev->r_wait);
}
在按键初始化函数添加:
init_waitqueue_head(&imx6uirq.r_wait);
在read操作函数添加:
DECLARE_WAITQUEUE(wait, current); /* 定义一个等待队列 */ if(atomic_read(&dev->releasekey) == 0) { /* 没有按键按下 */ add_wait_queue(&dev->r_wait, &wait); /* 添加到等待队列头 */ __set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */ schedule(); /* 进行一次任务切换 */ if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */ ret = -ERESTARTSYS; goto wait_error; } __set_current_state(TASK_RUNNING); /*设置为运行状态 */ remove_wait_queue(&dev->r_wait, &wait); /*将等待队列移除 */
}wait_error: set_current_state(TASK_RUNNING); /* 设置任务为运行态 */ remove_wait_queue(&dev->r_wait, &wait); /* 将等待队列移除 */return ret;
4.2 非阻塞IO
添加宏:
#define IMX6UIRQ_NAME "noblockio"
在read操作函数添加:
if (filp->f_flags & O_NONBLOCK) { /* 非阻塞访问 */ if(atomic_read(&dev->releasekey) == 0) /* 没有按键按下 */ return -EAGAIN;
} else { /* 阻塞访问 */ /* 加入等待队列,等待被唤醒,也就是有按键按下 */ ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); if (ret) { goto wait_error; }
} wait_error: return ret;
data_error: return -EINVAL;
添加poll操作函数:
unsigned int imx6uirq_poll(struct file *filp,
struct poll_table_struct *wait)
{ unsigned int mask = 0; struct imx6uirq_dev *dev = (struct imx6uirq_dev *) filp->private_data; poll_wait(filp, &dev->r_wait, wait); if(atomic_read(&dev->releasekey)) { /* 按键按下 */ mask = POLLIN | POLLRDNORM; /* 返回PLLIN */ }return mask;
}static struct file_operations imx6uirq_fops = { .poll = imx6uirq_poll,
};