linux驱动-阻塞与非阻塞I/O

阻塞和非阻塞i/o是两种不同的模式,驱动应灵活支持这两种用户空间对设备的访问


阻塞和非阻塞

  1. 阻塞:对设备操作时,如果无法获得资源,应当挂起进程直到满足可操作条件后再操作。被挂起的进程进入睡眠状态,等到满足条件后会被唤醒。
    当用户以阻塞的方式进行访问,对设备资源进行read(), write()操作时,如果设备资源不能获取,则驱动程序在设备驱动的read(), write()等操作中将进程阻塞直到资源可以获取,之后应用程序的read(), write()的调用才能返回。

  2. 非阻塞:操作时如无法获得资源,会直接放弃或循环尝试
    如果应用程序进行上述操作时,用户以非阻塞的方式进行访问时,设备资源不能获取时,设备驱动的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;
}
posted @ 2022-11-08 11:54  月的光景  阅读(109)  评论(0编辑  收藏  举报