阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。
阻塞从字面上听起来似乎意味着低效率,实则不然,如果设备驱动不阻塞,则用户想获取设备资源只能不停地查询,这反而会无谓地耗费 CPU 资源。而阻塞访问时,不能获取资源的进程将进入休眠,它将 CPU 资源让给其他进程。因为阻塞的进程会进入休眠状态,因此,必须确保有一个地方能够唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随着一个中断。
下面以两个实例演示阻塞和非阻塞的读取串口一个字符:

  • 阻塞地读取串口一个字符
char buf;
fd = open("/dev/ttyS1", O_RDWR);
...
res = read(fd,&buf,1); //当串口上有输入时才返回
if(res==1) printf("%c\n", buf);
  • 非阻塞地读取串口一个字符
char buf;
fd = open("/dev/ttyS1", O_RDWR| O_NONBLOCK);
...
while(read(fd,&buf,1)!=1); //串口上无输入也返回,所以要循环尝试读取串口
printf("%c\n", buf);

1、等待队列(阻塞)

在 Linux 驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。wait queue 很早就作为一个基本的功能单位出现在 Linux 内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问,信号量在内核中也依赖等待队列来实现。

  • 定义等待队列头:wait_queue_head_t my_queue;
  • 初始化等待队列头:init_waitqueue_head(&my_queue);
  • 定义并初始化等待队列头:DECLARE_WAIT_QUEUE_HEAD (name)
  • 定义并初始化等待队列name:DECLARE_WAITQUEUE(name, tsk)
  • 添加等待队列:void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
  • 移除等待队列:void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
  • 阻塞等待事件(condition)发生:
wait_event(queue, condition);
wait_event_interruptible(queue, condition);
wait_event_timeout(queue, condition, timeout);  //time以jiffy 为单位
wait_event_interruptible_timeout(queue, condition, timeout);
  • 唤醒队列:
void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

wake_up() 可 唤 醒 处 于TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 的进程, 而wake_up_interruptible()只能唤醒处于 TASK_INTERRUPTIBLE 的进程。

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

sleep_on()函数的作用就是将目前进程的状态置成 TASK_UNINTERRUPTIBLE,并定义一个等待队列,之后把它附属到等待队列头 q,直到资源可获得,q 引导的等待队列被唤醒。interruptible_sleep_on()与 sleep_on()函数类似,区别是其将目前进程的状态置成TASK_ INTERRUPTIBLE。

  • 改变进程状态:
set_current_state();
__add_wait_queue();
current->state = TASK_UNINTERRUPTIBLE/TASK_RUNNING/TASK_INTERRUPTIBLE   //对进程状态直接赋值

set_current_state()函数在任何环境下都可以使用,不会存在并发问题,但是效率要低于__add_ wait_queue()。因此,在许多设备驱动中,并不调用 sleep_on()interruptible_sleep_on(),而是亲自进行进程的状态改变和切换。

  • 以下为在驱动程序中改变进程状态并调用 schedule() 的实例:
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); //添加等待队列ret = count; /* 等待设备缓冲区可写 */ do{ avail = device_writable(...); if (avail < 0) _ _set_current_state(TASK_INTERRUPTIBLE);//改变进程状态if (avail < 0) { if (file->f_flags &O_NONBLOCK) //非阻塞{ if (!ret) ret = - EAGAIN; goto out; }schedule(); //调度其他进程执行if (signal_pending(current))//如果是因为信号唤醒{ if (!ret) ret = - ERESTARTSYS; goto out; } } }while (avail < 0); /* 写设备缓冲区 */ device_write(...) out: remove_wait_queue(&xxx_wait, &wait);//将等待队列移出等待队列头set_current_state(TASK_RUNNING);//设置进程状态为 TASK_RUNNINGreturn ret;
}

2、轮询操作(非阻塞)

使用非阻塞 I/O 的应用程序通常会使用 select()和 poll()系统调用查询是否可对设备进行无阻塞的访问。select()和 poll()系统调用最终会引发设备驱动中的 poll()函数被执行。

2.1 应用程序中的轮询编程

应用程序中最广泛用到的是 BSD UNIX 中引入的 select()系统调用,其原型如下:

int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

其中 readfds、writefds、exceptfds 分别是被 select()监视的读、写和异常处理的文件描述符集合,numfds 的值是需要检查的号码最高的文件描述符加 1。timeout 参数是一个指向 struct timeval 类型的指针,它可以使 select()在等待 timeout 时间后若没有文件描述符准备好则返回。struct timeval 数据结构的定义如下代码:

struct timeval
{ int tv_sec; /* 秒 */ int tv_usec; /* 微妙 */
};

FD_ZERO(fd_set *set):清除一个文件描述符集。
FD_SET(int fd,fd_set *set):将一个文件描述符加入文件描述符集中。
FD_CLR(int fd,fd_set *set) :将一个文件描述符从文件描述符集中清除。
FD_ISSET(int fd,fd_set *set) :判断文件描述符是否被置位。

2.2 驱动程序中的轮询编程

设备驱动中 poll()函数的原型如下:

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

第一个参数为 file 结构体指针,第二个参数为轮询表指针。这个函数应该进行以下两项工作。

  • 对可能引起设备文件状态变化的等待队列调用 poll_wait()函数,将对应的等待队列头添加到poll_table。
  • 返回表示是否能对设备进行无阻塞读、写访问的掩码。
    关键的用于向 poll_table 注册等待队列的 poll_wait()函数的原型如下:
void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);

poll_wait()函数的名称非常容易让人误会它会阻塞地等待某事件的发生,但这个函数并不会引起阻塞。poll_wait()函数所做的工作是把当前进程添加到 wait 参数指定的等待列表(poll_table)中。
驱动程序 poll()函数应该返回设备资源的可获取状态,即 POLLIN、POLLOUT、POLLPRI、POLLERR、POLLNVAL 等宏的位“或”结果。每个宏的含义都表明设备的一种状态,如 POLLIN(定义为 0x0001)意味着设备可以无阻塞地读,POLLOUT(定义为 0x0004)意味着设备可以无阻塞地写。
通过以上分析,可得出设备驱动中 poll()函数的典型模板:

static unsigned int xxx_poll(struct file *filp, 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; }

Linux 设备驱动中的阻塞与非阻塞 I/O相关推荐

  1. linux 设备驱动阻塞,深入浅出:Linux设备驱动中的阻塞和非阻塞I/O

    今天写的是Linux设备驱动中的阻塞和非阻塞I/0,何谓阻塞与非阻塞I/O?简单来说就是对I/O操作的两种不同的方式,驱动程序可以灵活的支持用户空间对设备的这两种访问方式. 一.基本概念: 阻塞操作 ...

  2. linux write引起进程挂起,Linux设备驱动中的阻塞与非阻塞总结

    Linux设备驱动中的阻塞与非阻塞总结 阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作. 非阻塞操作的进程在不能进行设备操作时,并不挂起.被挂起的进程进入sl ...

  3. Linux设备驱动中的阻塞和非阻塞IO

    这篇文章我们来了解下Linux设备驱动中阻塞和非阻塞. 阻塞:阻塞是指执行设备操作时,如果不能获得设备资源,则挂起进程,是进程进入休眠模式,直到设备资源可以获取. 非阻塞:非阻塞是在不能获取设备资源时 ...

  4. Linux设备驱动中的并发控制总结

    并发(concurrency)指的是多个执行单元同时.并行被执行.而并发的执行单元对共享资源(硬件资源和软件上的全局.静态变量)的访问则容易导致竞态(race conditions).   SMP是一 ...

  5. linux 两个驱动 竞态,第7章 Linux设备驱动中的并发控制之一(并发与竞态)

    本章导读 Linux设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发的访问会导致竞态(竞争状态). Linux提供了多种解决竞态问题的方式,这些方式适合不同的应用场景. 7.1讲解了并 ...

  6. Linux设备驱动开发详解:第7章 Linux设备驱动中的并发控制

    7.1并发与竞态 (1).竞态的发生场景:CPU0的进程与CPU1的进程之间.CPU0的中断与CPU1的进程之间.CPU0的中断与CPU1的中断之间: (2).解决竞态问题的途径是保证对共享资源的互斥 ...

  7. Linux 设备驱动中的 I/O模型(一)—— 阻塞和非阻塞I/O

    在前面学习网络编程时,曾经学过I/O模型 Linux 系统应用编程--网络编程(I/O模型),下面学习一下I/O模型在设备驱动中的应用. 回顾一下在Unix/Linux下共有五种I/O模型,分别是: ...

  8. Linux设备驱动中的阻塞与非阻塞I/O

    阻塞和非阻塞I/O是设备访问的两种不同模式,驱动程序可以灵活的支持用户空间对设备的这两种访问方式 本例子讲述了这两者的区别 并实现I/O的等待队列机制, 并进行了用户空间的验证 基本概念: 1> ...

  9. linux设备驱动中的阻塞与非阻塞(一)

    这两天在搞linux驱动的阻塞和非阻塞,困扰了两天,看了不少博客,有了点自己的想法,也不知是否对错,但还是写写吧,让各位大神给我指点指点.       首先说说什么是阻塞和非阻塞的概念:阻塞操作就是指 ...

最新文章

  1. 树状数组的理解(前缀和 and 差分)
  2. 科大讯飞:让世界听见AI的声音
  3. php注册登录遍写入 遍验证,在文件指定行中写入内容的php...-自动注册登录验证机制的php代...-php中出现Undefined index报错的修复方法_169IT.COM...
  4. c/c++整理--c++面向对象(2)
  5. “四不像”病毒冒充多款知名软件 窃取电脑隐私
  6. 探索 Word 2007 开发(四):上传图片
  7. go语言环境搭建以及监测命令
  8. 简单的Markdown解析器
  9. jsp汽车4S店维修管理系统
  10. 前端工程师的摸鱼日常(15)
  11. SQL根据身份证判断性别
  12. 现金流量表编制(经典总结)
  13. sleeptown睡眠时间设置教程(2021)
  14. 正确地使用“respectively“
  15. 狼性教育——让孩子成为主宰命运地强者
  16. 总结之:CentOS 6.5 MySQL/MariaDB日志及事物详解和基本操作语句
  17. 岳父岳母-寄快递的特殊方式
  18. 蓝桥杯之二阶魔方旋转
  19. 小米air2se耳机只有一边有声音怎么办_小米耳机只有一边有声音,这问题怎么解决...
  20. 会议预定管理系统php,会议小管家会议预约管理系统

热门文章

  1. (转载)C单元测试框架——cmocka
  2. 在单位波长与单位频率意义下的能量密度的转换
  3. 面试总结之[JDK1.8新特性]
  4. 飞控起飞前的准备工作
  5. jetson nano 命令行连接wifi
  6. 导出数据到excel表格
  7. golang中的strings.TrimLeft
  8. vs2017 调试 chromium 频繁崩溃
  9. java写helloworld_java编写helloworld怎么编写?HelloWorld案例常见问题
  10. Z字形扫描(对矩阵进行Z字形扫描)