linux驱动-阻塞与非阻塞I/O
阻塞和非阻塞i/o是两种不同的模式,驱动应灵活支持这两种用户空间对设备的访问
阻塞和非阻塞
-
阻塞:对设备操作时,如果无法获得资源,应当挂起进程直到满足可操作条件后再操作。被挂起的进程进入睡眠状态,等到满足条件后会被唤醒。
当用户以阻塞的方式进行访问,对设备资源进行read(), write()操作时,如果设备资源不能获取,则驱动程序在设备驱动的read(), write()等操作中将进程阻塞直到资源可以获取,之后应用程序的read(), write()的调用才能返回。 -
非阻塞:操作时如无法获得资源,会直接放弃或循环尝试
如果应用程序进行上述操作时,用户以非阻塞的方式进行访问时,设备资源不能获取时,设备驱动的read(),write()等操作立即返回,应用程序的read(),write()等系统调用立即返回,应用程序受到-EGAIN
返回值。
fd = open("/dev/ttyS1", O_RDWR); //阻塞的形式;
fd = open("/dev/ttyS1", O_RDWR| O_NONBLOCK); //非阻塞的形式,一般会在循环中读取;
//除了在open时指定是和否阻塞,还可以在文件打开后通过ioctl()、fnctl()改变读写方式,例:
fnctl(fd,F_SETFL,ONONBLOCK);
等待队列
在内核中可以使用等待队列实现阻塞进程的唤醒。它以队列为数据结构,与进程调度机制结合,用来同步对系统资源的访问。使用方法如下:
-
定义等待队列的头部
wait_queue_head_t my_queue; -
初始化等待队列的头部
init_waitqueue_head(&my_queue); -
或通过宏定义来快捷定义及初始化等待队列头部
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);
add用于将等待队列元素wait添加到等待队列头部q指向的双向链表中。remove 函数执行移除操作 -
等待事件
//程序会卡在此处
wait_event(queue, condition); //等待第一个参数queue作为等待队列头部的队列被唤醒,而且第二个参数condition必须满足,否则继续阻塞。
wait_event_interruptible(queue, condition); //除了满足条件唤醒,也可以被信号中断
wait_event_timeout(queue, condition, timeout); //加了等待超时时间,超时时间到不论condition是否满足均返回。
wait_event_interruptible_timeout(queue, condition, timeout);
-
唤醒队列
void wake_up(wait_queue_head_t *queue);//用来唤醒,可唤醒TASK_INTERRUPTIBLE + TASK_UNINTERRUPTIBLE的进程
void wake_up_interruptible(wait_queue_head_t *queue);//只能唤醒TASK_INTERRUPTIBLE -
在等待队列上睡眠
sleep_on(wait_queue_head_t *q ); //将目前进程的状态置成TASK_UNINTERRUPTIBLE,并定义一个等待队列元素,之后把它挂到等待队列头部q指向的双向链表,直到资源可获得,q队列指向链接的进程被唤醒。
interruptible_sleep_on(wait_queue_head_t *q );//目前进程的状态置成TASK_INTERRUPTIBLE,并定义一个等待队列元素,之后把它附属到q指向的队列,直到资源可获得(q指引的等待队列被唤醒)或者进程收到信号。
与‘等待事件’的区别是不需要condition参数
例程
static ssize_t xxx_write(struct file *file, 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); /* 将元素移出 xxx_wait 指引的队列 */
set_current_state(TASK_RUNNING); /* 设置进程状态为 TASK_RUNNING */
return ret;
}