阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式, 在编写驱动的时候一定要考虑到阻塞和非阻塞。

阻塞与非阻塞简介

阻塞操作是指在执行设备操作时, 若不能获得资源, 则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态, 被从调度器的运行队列移走, 直到等待的条件被满足。 而非阻塞操作的进程在不能进行设备操作时, 并不挂起, 它要么放弃, 要么不停地查询, 直至可以进行操作为止。

在阻塞访问时, 不能获取资源的进程将进入休眠, 它将 CPU 资源“礼让” 给其他进程。 因为阻塞的进程会进入休眠状态, 所以必须确保有一个地方能够唤醒休眠的进程, 否则, 进程就真的“寿终正寝” 了。唤醒进程的地方最大可能发生在中断里面, 因为在硬件资源获得的同时往往伴随着一个中断。 而非阻塞的进程则不断尝试, 直到可以进行 I/O。 阻塞访问如图所示:

若用户以非阻塞的方式访问设备文件, 则当设备资源不可获取时, 设备驱动的 xxx_read() 、 xxx_write( ) 等操作应立即返回, read( ) 、 write( ) 等系统调用也随即被返回, 应用程序收到-EAGAIN 返回值。

应用程序可以使用如下所示示例代码来实现阻塞访问:

int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

可以看出对于设备驱动文件的默认读取方式就是阻塞式的, 所以我们前面所有的例程测试 APP 都是采用阻塞 IO。
如果应用程序要采用非阻塞的方式来访问驱动设备文件, 可以使用如下所示代码:

int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

使用 open 函数打开“ /dev/xxx_dev” 设备文件的时候添加了参数“ O_NONBLOCK” , 表示以非阻塞方式打开设备, 这样从设备中读取数据的时候就是非阻塞方式的了。

等待队列

当我们进程去访问设备的时候, 经常需要等待有特定事件发生以后再继续往下运行, 这个时候就需要在驱动里面实现当条件不满足的时候进行休眠, 当条件满足的时候在由内核唤醒进程。 在 Linux 驱动程序中, 可以使用等待队列( Wait Queue) 来实现阻塞进程的唤醒。 等待队列很早就作为一个基本的功能单位出现在 Linux 内核里了, 它以队列为基础数据结构, 与进程调度机制紧密结合, 可以用来同步对系统资源的访问。 队列是一种特殊的线性表, 特殊之处在于它只允许在表的前端( front) 进行删除操作, 而在表的后端( rear) 进行插入操作, 和栈一样, 队列是一种操作受限制的线性表。 进行插入操作的端称为队尾, 进行删除操作的端称为队头。 即满足先进先出的形式 FIFO。

举个例子, 比如说我现在去食堂打饭, 阿姨和我说现在没有饭, 你需要等一会, 等我做好了我再叫你,那么我当前不能获得资源, 我被阻塞在这儿了, 那么等待队列就是让我们阻塞在这儿, 然后等特定的事件发生以后, 再继续运行。 那么等待队列阻塞在这儿的这件事情就相当于阿姨和我们说现在没有饭, 你需要等一会。 为什么我们要先讲完中断以后再讲等待队列呢? 举个例子来说, 比如说阿姨和你说现在没饭, 你需要在旁边等一会, 等我做好了我再叫你, 如果说阿姨做完了不叫你, 你又睡着了, 那么你今天是不是吃不上饭了, 所以说在我们阻塞访问的时候不能获得资源的进程, 将进入休眠状态, 他将 cpu 的资源全部让给别的进程, 必须保证有一个地方可以唤醒休眠进程, 否则的话将会长睡不醒。 进程唤醒最大可能的地方发生在中断里面, 伴随着一个中断的发生我们可以唤醒该进程, 对应的事件是阿姨说饭好了, 小王你过来打吧。 所以说, 我们学习等待队列在中断之后, 这样用等待队列可以极大的降低 cpu 的占用率。

Linux 内核的等待队列是以双循环链表为基础数据结构, 与进程调度机制紧密结合, 能够用于实现核心的异步事件通知机制。 它有两种数据结构: 等待队列头(wait_queue_head_t) 和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个 list_head 类型的域作为”连接件”。 它通过一个双链表和把等待 task的头, 和等待的进程列表链接起来。

等待队列头

等待队列头就是一个等待队列的头部, 每个访问设备的进程都是一个队列项, 当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。

等待队列头使用结构体 wait_queue_head_t 来表示, 这个结构体定义在文件 include/linux/wait 里面, 结构体内容如下:

struct __wait_queue_head {spinlock_t lock; //自旋锁struct list_head task_list; //链表头
};
typedef struct __wait_queue_head wait_queue_head_t;

类型名是 wait_queue_head_t, 只需要记住这个即可。

定义一个等待队列头:

wait_queue_head_t test_wq; //定义一个等待队列的头

定义等待队列头以后需要初始化, 可以使用 init_waitqueue_head 函数初始化等待队列头, 函数原型如下:

函数 void init_waitqueue_head(wait_queue_head_t *q)
q wait_queue_head_t 指针
功能 动态初始化等待队列头结构

也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义和初始化。

DECLARE_WAIT_QUEUE_HEAD (wait_queue_head_t *q);

等待队列项

等待队列头就是一个等待队列的头部, 每个访问设备的进程都是一个队列项, 当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。 结构体 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就是给当前正在运行的进程创建并初始化了一个等待队列项。

添加/删除队列

当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中, 只有添加到等待队列头中以后进程才能进入休眠态。 当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可, 等待队列项添加队列函数如下所示:

函数 void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
q 等待队列项要加入的等待队列头。
wait 要加入的等待队列项
返回值
功能 从等待队列头中添加队列

等待队列项移除队列函数如下:

函数 void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
q 要删除的等待队列项所处的等待队列头
wait 要删除的等待队列项
返回值

等待唤醒

当设备可以使用的时候就要唤醒进入休眠态的进程, 唤醒可以使用如下两个函数。

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 状态的进程。

等待事件

除了主动唤醒以外, 也可以设置等待队列等待某个事件, 当这个事件满足以后就自动唤醒等待队列中的进程, 相关函数:

#define wait_event(wq, condition)
do {
if (condition)
break;
__wait_event(wq, condition);
} while (0)
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)

wait_event()宏
功能: 不可中断的阻塞等待, 让调用进程进入不可中断的睡眠状态, 在等待队列里面睡眠直到 condition 变成真, 被内核唤醒。

wait_event_interruptible() 函数
功能: 可中断的阻塞等待, 让调用进程进入可中断的睡眠状态, 直到 condition 变成真被内核唤醒或被信号打断唤醒。

wait_event_timeout() 宏:
也与 wait_event()类似.不过如果所给的睡眠时间为负数则立即返回.如果在睡眠期间被唤醒,且
condition 为真则返回剩余的睡眠时间,否则继续睡眠直到到达或超过给定的睡眠时间,然后返回 0。

wait_event_interruptible_timeout() 宏:
与 wait_event_timeout()类似,不过如果在睡眠期间被信号打断则返回 ERESTARTSYS 错误码。

wait_event_interruptible_exclusive() 宏:
同样和 wait_event_interruptible()一样,不过该睡眠的进程是一个互斥进程
注意: 调用的时要确认 condition 值是真还是假, 如果调用 condition 为真, 则不会休眠。

嵌入式Linux 阻塞和非阻塞 IO 驱动设备访问模式相关推荐

  1. linux驱动开发 - 10_阻塞和非阻塞 IO

    文章目录 1 阻塞和非阻塞 IO 1.1 阻塞和非阻塞简介 1.2 等待队列 1.等待队列头 2.等待队列项 3.将队列项添加/移除等待队列头 4.等待唤醒 5.等待事件 1.3 Linux驱动下的p ...

  2. linux驱动学习笔记(三)阻塞与非阻塞IO

    Linux驱动中阻塞与非阻塞IO 前言 阻塞 非阻塞 一.等待队列 1.等待队列头 2.等待队列 模板 二.轮询 模板 总结 前言 阻塞和非阻塞io是两种不同的设备访问方式. 阻塞 阻塞IO表示在执行 ...

  3. linux驱动系列学习之阻塞与非阻塞IO(六)

    一. 阻塞与非阻塞IO概念     阻塞操作是指在执行设备操作时,若不能获取资源,则挂起进程进入休眠状态,等待可满足条件后进行操作.被挂起的进程从调度器队列移动到挂起队列(睡眠状态).当操作驱动程序r ...

  4. Linux驱动(六)设备驱动中的阻塞与非阻塞IO

    我们在Linux学习(二十三)IO模型中了解了LINUX中IO模型,IO模型最简单的可以分为阻塞IO和非阻塞IO.并且学习了一个用如何使用阻塞操作和非阻塞操作.而应用层之所以能实现阻塞操作和非阻塞操作 ...

  5. linux驱动 阻塞和非阻塞IO 篇二

    @上一篇介绍了linux阻塞与非阻塞的基本概念,以及应用程序的小demo和kernel层对应的api函数.那接下来就以实例来分析,如何在linux驱动层添加等待队列和轮询的方法,以及区别. ** 一: ...

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

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

  7. 嵌入式驱动初级-阻塞与非阻塞

    文章目录 前言 一.五种IO模型 二.阻塞与非阻塞 三.多路复用 四.信号驱动 前言 记录嵌入式驱动学习笔记 一.五种IO模型 当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 ...

  8. linux函数的阻塞与非阻塞IO及错误处理

    linux函数的阻塞与非阻塞IO及错误处理 1.阻塞是指进程等待某一个事件的发生而处于等待状态不往下执行,如果等待的事件发生了则会继续执行该进程.调用系统阻塞函数可能会导致进程阻塞进入睡眠状态. 2. ...

  9. Linux 阻塞和非阻塞IO 实验

    目录 阻塞和非阻塞IO 阻塞和非阻塞简介 等待队列 轮询 Linux 驱动下的poll 操作函数 阻塞IO 实验 硬件原理图分析 实验程序编写 运行测试 非阻塞IO 实验 硬件原理图分析 实验程序编写 ...

最新文章

  1. Bitcoin.com| 比特币现金在澳大利亚的零售支出大幅超过BTC
  2. mac 多java环境变量配置_java_Mac安装多个JDK版本并设置环境变量
  3. win10 Java JDK环境变量配置
  4. HTML搜索框中加入提示文字,HTML 5 input placeholder 属性 实现搜索框提示文字点击输入后消失 - 尚码园...
  5. 快手抢占短视频第一股,增长惊人,腾讯又是大赢家!
  6. 渣渣的leetcode刷题笔记-树(1)
  7. 利用kd树实现最近邻搜索
  8. Linux挂载OneDrive
  9. vmware未识别网络
  10. 瀚高数据库命令备份还原
  11. oracle官网下载旧版
  12. PDMS插件_三维地形工具
  13. 计算机硬盘是输出还是输入,输入输出
  14. 分享简单的记账方法,轻松搜索账目查看
  15. [递推式求解、多指针、前缀和]XATU第七届算法大赛
  16. 动态网页和静态网页的差异
  17. 3.25 使用钢笔工具选择平滑形状的叶子 [原创Ps教程]
  18. word中使用mathtype插入公式编号出现Equation Chapter (Next) Section 1字样
  19. IT人如何开始自己创业(轉)
  20. 操作系统高级课程-1

热门文章

  1. FTP地址格式如下:“ftp://用户名:密码@FTP服务器IP”
  2. pytorch 安装笔记
  3. 【Syslinux Grub Grub2】万能优盘启动盘 (WinPE、LinuxPE)-- 方法2 U盘ISO写入(推荐)
  4. HDU 2069 母函数模版题
  5. supesite 标签语法
  6. 基于Python的拉勾网Python工程师招聘信息的爬取和处理分析
  7. 封装insertAfter
  8. 更新驱动后电脑蓝屏问题解决
  9. matlab .cdata,Matlab:bar没有公共财产CData存在
  10. 【软件】Excel打开空白,需要在里面再次选择文件位置打开