前言

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 轮询

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

poll、epoll 和 select 可以用于处理轮询。这三个 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,需要检查读操作,则把 readfds 第 6 个 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 域值不为 0 的 pollfd 个数。即是发生事件或错误的文件描述符数量。

被监视的文件描述符格式:

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);。
    • epfd:epoll 句柄。
    • op:操作标识。
      • EPOLL_CTL_ADD:向 epfd 添加 fd 表示的描述符。
      • EPOLL_CTL_MOD:修改 fd 的 event 时间。
      • EPOLL_CTL_DEL:从 epfd 中删除 fd 描述符。
    • fd:要监测的文件。
    • event:要监测的事件类型。
  3. 等待事件发生。
    • 函数原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);。
    • epfd:epoll 句柄。
    • events:指向 epoll_event 结构体数组。
    • maxevents:events 数组大小,必须大于 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)
  • file:file 结构体。
  • 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. HTML <video>

    HTML <video> 元素 用于在HTML或者XHTML文档中嵌入媒体播放器,用于支持文档内的视频播放.你也可以将 <video> 标签用于音频内容,但是 <audi ...

  2. 标签页切换title改变

    js实现如下 并将代码加入博客侧边栏就成了 <script> document.addEventListener("visibilitychange", functio ...

  3. spring源码解析之属性编辑器propertyEditor

    异常信息造成此异常的原因bean配置文件调用代码特别说明:异常解决注册springt自带的属性编辑器 CustomDateEditor控制台输出属性编辑器是何时并如何被注册到spring容器中的?查看 ...

  4. Java_抽象

    抽象的基本使用 抽象的关键字是abstract,可以用来修饰类(抽象类),还可以修饰方法(抽象方法). 1 //抽象类 2 public abstract class Animal{ 3 //抽象方法 ...

  5. Python自动扫描出微信不是好友名单

    前言 最近找几个老友准备聊天发现几个已经被删除好友名单,做为潜水党多年的我已经不知道成为多少人的黑名单,但是好友列表却依然有不是好友的名单,面对庞大的好友数量想要清除谈何容易.虽然可以发消息给所有人, ...

  6. 中国排名前100的IC设计公司

    中国排名前100的IC设计公司 北京地区大唐微电子技术有限公司北京北大众志微系统科技有限责任公司北京中星微电子有限公司中国华大集成电路设计中心  北京中电华大电子设计有限责任公司  北京清华同方微电子 ...

  7. 面向对象JML系列作业总结

    面向对象JML系列作业总结 一.综述 本单元作业,由简到难地迭代式实现了三种JML需求,主要学习了面向规格的编程方法. 第一次:实现Path类和PathContainer类 第二次:继承PathCon ...

  8. java集合-哈希表HashMap

    一.简介 HashMap是一个散列表,是一种用于存储key-value的数据结构. 二.类图 public class HashMap<K,V> extends AbstractMap&l ...

  9. MSSQL·将一对多的数据合并为以指定分隔符的数据

    阅文时长 | 0.05分钟 字数统计 | 142.4字符 主要内容 | 1.引言&背景 2.Stuff函数语法&模拟场景 3.声明与参考资料 『MSSQL·将一对多的数据合并为以指定分 ...

  10. Redis泛泛而谈(详细2W字)

    本文适合于刚接触redis的,文章内容比较基础,大佬请绕道. 一.NoSQL入门和概述 Ⅰ-入门概述 1.为什么用NoSQL 1)单机MySQL的美好年代 在90年代,一个网站的访问量一般都不大,用单 ...