前言

13. 阻塞与非阻塞

本章内容为驱动基石之一

驱动只提供功能,不提供策略

阻塞与非阻塞 都是应用程序主动访问的。从应用角度去解读阻塞与非阻塞。

原文:https://www.cnblogs.com/lizhuming/p/14912496.html

13.1 阻塞与非阻塞

阻塞

  • 指在执行设备操作时,若不能获得资源,则挂起进程,直至满足操作的条件后再继续执行。

非阻塞

  • 指在执行设备操作时,若不能获得资源,则不挂起,要么放弃,要么不停查询,直至设备可操作。

实现阻塞的常用技能包括:(目的其实就是阻塞)

  • 休眠与唤醒机制和等待队列相辅相成)。
  • 等待队列和休眠与唤醒机制相辅相成)。
  • poll机制

13.2 休眠与唤醒

若需要实现阻塞式访问,可以使用休眠与唤醒机制。

相关函数其实在 等待队列 小节有说明了,现在只是函数汇总。

13.2.1 内核休眠函数

内核源码路径:include\linux\wait.h。

函数名 描述
wait_event(wq, condition) 休眠,直至 condition 为真;休眠期间不能被打断。
wait_event_interruptible(wq, condition) 休眠,直至 condition 为真;休眠期间可被打断,包括信号。
wait_event_timeout(wq, condition, timeout) 休眠,直至 condition 为真或超时;休眠期间不能被打断。
wait_event_interruptible_timeout(wq, condition, timeout) 休眠,直至 condition 为真或超时;休眠期间可被打断,包括信号。

13.2.2 内核唤醒函数

内核源码路径:include\linux\wait.h。

函数名 描述
wake_up_interruptible(x) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,只唤醒其中的一个线程
wake_up_interruptible_nr(x, nr) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,只唤醒其中的 nr 个线程
wake_up_interruptible_all(x) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”的线程,唤醒其中的所有线程
wake_up(x) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中的一个线程
wake_up_nr(x, nr) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的线程,只唤醒其中 nr 个线程
wake_up_all(x) 唤醒 x 队列中状态为“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的线程,唤醒其中的所有线程

13.3 等待队列(阻塞)

等待队列

  • 其实就是内核的一个队列功能单位&API。
  • 在驱动中,可以使用等待队列来实现阻塞进程的唤醒。

使用方法

  1. 定义等待队列头部。
  2. 初始化等待队列头部。
  3. 定义等待队列元素。
  4. 添加/移除等待队列。
  5. 等待事件。
  6. 唤醒队列。

另外一种使用方法就是 在等待队列上睡眠

等待队列头部结构体

struct wait_queue_head {
spinlock_t lock;
struct list_head head;
};
typedef struct wait_queue_head wait_queue_head_t;

等待队列元素结构体

struct wait_queue_entry {
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head entry;
};

13.3.1 定义等待队列头部

定义等待队列头部方法:wait_queue_head_t my_queue;

13.3.2 初始化等待队列头部

初始化等待队列头部源码:void init_waitqueue_head(wait_queue_head_t *q);



定义&初始化等待队列头部:使用宏 DECLARE_WAIT_QUEUE_HEAD

13.3.3 定义等待队列元素

定义等待队列元素源码:#define DECLARE_WAITQUEUE(name, tsk);

  • name:该等待队列元素的名字。
  • tsk:该等待队列元素归属于哪个任务进程。

13.3.4 添加/移除等待队列元素

添加等待队列元素源码:void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);

  • wq_head:等待队列头部。
  • wq_entry:等待队列。

移除等待队列元素源码:void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);

  • wq_head:等待队列头部。
  • wq_entry:等待队列。

13.3.5 等待事件

睡眠,直至事件发生:wait_event(wq_head, condition)

  • wq_head:等待队列头。
  • condition:事件。当其为真时,跳出。
/**
* wait_event - sleep until a condition gets true
* @wq_head: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_UNINTERRUPTIBLE) until the
* @condition evaluates to true. The @condition is checked each time
* the waitqueue @wq_head is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*/
#define wait_event(wq_head, condition) \
do { \
might_sleep(); \
if (condition) \
break; \
__wait_event(wq_head, condition); \
} while (0)
  • TASK_INTERRUPTIBLE:处于等待队伍中,等待资源有效时唤醒(比如等待键盘输入、socket连接等等),可被信号中断唤醒。可被 信号wake_up() 唤醒。
  • TASK_UNINTERRUPTIBLE:处于等待队伍中,等待资源有效时唤醒(比如等待键盘输入、socket连接等等),但会忽略信号、不可以被中断唤醒。即是只能由 wake_up() 唤醒。

睡眠,直至事件发生或超时:wait_event_timeout(wq_head, condition, timeout)

等待事件发生,且可被信号中断唤醒:wait_event_interruptible(wq_head, condition)

等待事件发生或超时,且可被信号中断唤醒:wait_event_interruptible_timeout(wq_head, condition, timeout)

io_wait_event()

/*
* io_wait_event() -- like wait_event() but with io_schedule()
*/
#define io_wait_event(wq_head, condition) \
do { \
might_sleep(); \
if (condition) \
break; \
__io_wait_event(wq_head, condition); \
} while (0)

13.3.6 唤醒队列

以下两个函数对应等待事件使用

  • 唤醒队列:void wake_up(wait_queue_head_t *queue);
  • 唤醒队列,信号中断可唤醒:void wake_up_interruptible(wait_queue_head_t *queue);

13.3.7 在等待队列上睡眠

函数源码:

  • sleep_on(wait_queue_head_t *q)
  • interruptible_sleep_on(wait_queue_head_t *q)
  • sleep_on()
    • 把当前进程状态设置为 TASK_INTERRUPTIBLE,并定义一个等待队列元素,并添加到 q 中。
    • 直到资源可用或 q 队列指向链接的进程被唤醒。
    • wake_up() 配套使用。interruptible_sleep_on()wake_up_interruptible() 配套使用。

13.4 轮询

当用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式。

pollepollselect 可以用于处理轮询。这三个 API 均在 应用层 使用。

注意,轮询也是在APP实现轮询的。

13.4.1 select 函数

select()

  • 函数原型:int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • numfds:需要检查的 fd 中最大的 fd + 1
  • readfds:读 文件描述符集合。NULL 不关心这个。
  • writefds:写 文件描述符集合。NULL 不关心这个。
  • exceptfds:异常 文件描述符集合。NULL 不关心这个。
  • timeout:超时时间。NULL 时为无限等待。
  • 时间结构体
struct timeval{
long tv_sec; // 秒
long tv_usec; // 微妙
};
  • 返回

    • 0:超时。
    • -1:错误。
    • 其他值:可进行操作的文件描述符个数。
  • 原理fd_set 为一个 N 字节类型,需要操作的 fd 值在对应比特上置为 1 即可。若 fd 的值为 6,需要检查读操作,则把 readfds6 个 bit 置 1。调用该函数后,先把对应 fd_set 清空,再检查、标记可操作情况。Linux 提供以下接口操作:
FD_CLR(int fd, fd_set *set); // 把 fd 对应的 set bit 清空。
FD_ISSET(int fd, fd_set *set); // 查看 d 对应的 set bit 是否被置 **1**。
FD_SET(int fd, fd_set *set); // 把 fd 对应的 set bit 置 **1**。
FD_ZERO(fd_set *set); // 把 set 全部清空。

fd_set 是有限制的,可以查看源码,修改也可。但是改大会影响系统效率。

13.4.2 poll 函数

由于 fd_set 是有限制的,所以当需要监测大量文件时,便不可用。

这时候,poll() 函数就应运而生。

poll()select() 没什么区别,只是前者没有最大文件描述符限制。

  • 函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout)
  • fds:要监视的文件描述符集合。
  • nfds:要监视的文件描述符数量。
  • timeout:超时时间。单位 ms
  • 返回
    • 0:超时。
    • -1:发生错误,并设置 error 为错误类型。
    • 其它:返回 revent 域值不为 0pollfd 个数。即是发生事件或错误的文件描述符数量。

被监视的文件描述符格式

struct pollfd{
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的时间 */
}

可请求的事件 events

说明
POLLIN 有数据可读
POLLPRI 有紧急的数据需要读取
POLLOUT 可以写数据
POLLERR 指定的文件描述符发生错误
POLLHUP 指定的文件描述符被挂起
POLLNVAL 无效的请求
POLLRDNORM 等同于 POLLIN

13.4.3 epoll 函数

select()poll() 会随着监测的 fd 数量增加,而出现效率低下的问题。

poll() 每次监测都需要历遍所有被监测的描述符。

epoll() 函数就是为大量并大而生的。在网络编程中比较常见。

epoll() 使用方法:

  1. 创建一个 epoll 句柄:

    • 函数原型:int epoll_creat(int size);
    • size:随便大于 0 即可。 Linux2.6.8 后便不再维护了。
    • 返回
      • epoll 句柄。
      • -1:创建失败。
  2. epoll 添加要监视的文件及监测的事件。
    • 函数原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    • epfdepoll 句柄。
    • op:操作标识。
      • EPOLL_CTL_ADD:向 epfd 添加 fd 表示的描述符。
      • EPOLL_CTL_MOD:修改 fdevent 时间。
      • EPOLL_CTL_DEL:从 epfd 中删除 fd 描述符。
    • fd:要监测的文件。
    • event:要监测的事件类型。
  3. 等待事件发生。
    • 函数原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    • epfdepoll 句柄。
    • events:指向 epoll_event 结构体数组。
    • maxeventsevents 数组大小,必须大于 0。
    • timeout:超时时间。

epoll_event 结构体

struct epoll_event{
uint32_t events; /* epoll 事件 */
epoll_data_t data; /* 用户数据 */
}

可请求的事件 events

说明
EPOLLIN 有数据可读
EPOLLPRI 有紧急的数据需要读取
EPOLLOUT 可以写数据
EPOLLERR 指定的文件描述符发生错误
EPOLLHUP 指定的文件描述符被挂起
EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发
EPOLLONESHOT 一次性的监视,当监视完成后,还需要监视某个 fd,那就需要把 fd 重新添加到 epoll

13.5 驱动中的 poll 函数

当应用程序调用 select() 函数和 poll() 函数时,驱动程序会调用 file_operations 中的 poll

  • 函数原型:unsigned int(*poll)(struct file *filp, struct poll_table_struct *wait)
  • filefile 结构体。
  • wait:轮询表指针。主要传给 poll_wait 函数。
  • 该函数主要工作:
    • 对可能引起设备文件状态变化的等待队列调用 poll_wait() 函数,将对应的等待队列头部添加到 poll_table 中。
    • 返回表示是否能对设备进行无阻塞读、写访问的掩码。可以返回以下值:
      • POLLIN:有数据可读。
      • POLLPRI:有紧急的数据需要读取。
      • POLLOUT:可以写数据。
      • POLLERR:指定的文件描述符发生错误。
      • POLLHUP:指定的文件描述符挂起。
      • POLLNVAL:无效的请求。
      • POLLRDNORM:等同于 POLLIN,普通数据可读。

poll_wait()

  • 函数原型:void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)
  • 该函数不会阻塞进程,只是将当前进程添加到 wait 参数指定的等待列表中。
  • filp:要操作的设备文件描述符。
  • wait_address:要添加到 wait 轮询表中的等待队列头。
  • p:file_operations 中 poll 的 wait 参数。
  • 建议:找个例程看看就明白了。

【linux】驱动-13-阻塞与非阻塞的更多相关文章

  1. Linux设备驱动中的阻塞和非阻塞I/O <转载>

    Green 博客园 首页 新随笔 联系 订阅 管理 Linux设备驱动中的阻塞和非阻塞I/O   [基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件 ...

  2. Linux设备驱动中的阻塞和非阻塞I/O

    [基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到 ...

  3. Linux驱动技术(五) _设备阻塞/非阻塞读写

    等待队列是内核中实现进程调度的一个十分重要的数据结构,其任务是维护一个链表,链表中每一个节点都是一个PCB(进程控制块),内核会将PCB挂在等待队列中的所有进程都调度为睡眠状态,直到某个唤醒的条件发生 ...

  4. 蜕变成蝶~Linux设备驱动中的阻塞和非阻塞I/O

    今天意外收到一个消息,真是惊呆我了,博客轩给我发了信息,说是俺的博客文章有特色可以出本书,,这简直让我受宠若惊,俺只是个大三的技术宅,写的博客也是自己所学的一些见解和在网上看到我一些博文以及帖子里综合 ...

  5. linux驱动编写之阻塞与非阻塞

    一.概念 应用程序使用API接口,如open.read等来最终操作驱动,有两种结果--成功和失败.成功,很好处理,直接返回想要的结果:但是,失败,是继续等待,还是返回失败类型呢?  如果继续等待,将进 ...

  6. Linux驱动技术(五) _设备阻塞/非阻塞读写【转】

    转自:http://www.cnblogs.com/xiaojiang1025/p/6377925.html 等待队列是内核中实现进程调度的一个十分重要的数据结构,其任务是维护一个链表,链表中每一个节 ...

  7. Linux设备驱动中的IO模型---阻塞和非阻塞IO【转】

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

  8. Linux 驱动层实现阻塞和非阻塞

    linux应用层的函数默认是阻塞型的,但是要想真正实现阻塞,还需要驱动的支持才行. 例:open().scanf().fgets().read().accept() 等 1.默认情形,驱动层不实现阻塞 ...

  9. 《linux设备驱动开发详解》笔记——8阻塞与非阻塞IO

    8.1 阻塞与非阻塞IO 8.1.0 概述 阻塞:访问设备时,若不能获取资源,则进程挂起,进入睡眠状态:也就是进入等待队列 非阻塞:不能获取资源时,不睡眠,要么退出.要么一直查询:直接退出且无资源时, ...

随机推荐

  1. spring mvc @Repository 注入不成功 的原因?

    这样的代码会影响 @Repository 注入

  2. MySQL5.7升级到8.0过程详解

    前言: 不知不觉,MySQL8.0已经发布好多个GA小版本了.目前互联网上也有很多关于MySQL8.0的内容了,MySQL8.0版本基本已到稳定期,相信很多小伙伴已经在接触8.0了.本篇文章主要介绍从 ...

  3. NAG博客目录

    一.Scrum Meeting 1. Alpha Scrum meeting 1 Scrum meeting 2 Scrum meeting 3 Scrum meeting 4 Scrum meeti ...

  4. a标签美化

    具体选中a标签给予宽高,这样才能在整个a标签范围内才能跳转 但是因为a标签是行内元素,所以要用display:blcok 转化为块状元素 且a标签不继承父元素的color 对a标签设置颜色,要选中a标 ...

  5. [Scala] 高级特性

    泛型 泛型类 1 package day0603 2 3 class GenericClassInt { 4 private var content:Int=10 5 def set(value:In ...

  6. [刷题] 104 Maximum Depth of Binary Tree

    要求 求一棵二叉树的最高深度 思路 递归地求左右子树的最高深度 实现 1 Definition for a binary tree node. 2 struct TreeNode { 3 int va ...

  7. 攻防世界(六)supersqli

    攻防世界系列:supersqli 方法一: 用逗号包裹回显正常,说明存在注入 1';--+(注释符也可用 -- 或 # 发现均未被过滤!) 有order by 语句可知表由2个字段,使用联合查询 (想 ...

  8. spring MyBatis的相关面试题

    (相关面试题! 供参考!) 1.ORM框架有哪些? MyBatis:半自动化框架(不是纯ORM) 需要写动态SQL语句,实体类和SQL语句之间建立映射关系 Spring:轻量级框架, Java EE的 ...

  9. 趣谈网络协议-第3讲 | ifconfig:最熟悉又陌生的命令行

    如何查看IP地址呢? windows  查看IP地址命令  IPCONFIG LINUX    查看IP 命令   IFCONFIG   IP ADDR ifconfig 和ADDR的区别  这是一个 ...

  10. JS实现前台表格排序功能

    JS实现前台表格排序功能 虽然数据量不大的情况下,前台排序速度比较快,但一般情况下,我们的项目只使用后台排序,原因有二: 一是代码简单:二是前台JS排序对于有分页的情况无法处理. 前段时间,有个功能需 ...