前言

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. SSM框架MavenWeb项目的测试

    由于SSM项目的类都是由Spring容器托管,所以直接进行用new对象调用方法进行测试是不行不通的,会出现空指针异常NullPointExpection. 因为我们的对象由spring进行托管,调用的 ...

  2. CDN 加速配置

    1 https://cloud.tencent.com/document/product/228/3149 2 https://cloud.tencent.com/document/product/4 ...

  3. SpringBoot系列——自定义统一异常处理

    前言 springboot内置的/error错误页面并不一定适用我们的项目,这时候就需要进行自定义统一异常处理,本文记录springboot进行自定义统一异常处理. 1.使用@ControllerAd ...

  4. C++ primer plus读书笔记——第2章 开始学习C++

    第2章 开始学习C++ 1. endl确保程序继续运行前刷新输出(将其立即显示在屏幕上),而使用"\n"不提供这样的保证,这意味着在有些系统中,有时可能在您输入信息后才会出现提示. ...

  5. 【SecureCRT配置】修改默认卷屏行数当做一个操作,屏幕输出有上百行,当需要将屏幕回翻时,这个设置会有很大帮助,默认为500行,可以改为10000行,不用担心找不到了。 选项 => 全局选项 => Default Session => Edit Default Settings => Terminal => Emulation => Scrollback 修改为32000。

    SecureCRT配置屏幕内容输出到log文件 SecureCRT看不到前几分钟操作的内容,或者想把通过vi命令查看的日志输出到log文件(在懒得下载日志文件的情况下),所以接下来就这样操作: 文件保 ...

  6. Linux_WEB访问控制示例(使用IPADDR类型)

    前言: WEB服务使用访问控制,可以控制IP.主机名.以及某个网段的IP去访问我们的WEB服务,从而加减少流量的访问 一.使用IP控制访问 1.在/var/www/html下创建一个可访问的测试页面 ...

  7. Linux_搭建Samba服务(匿名访问)

    [RHEL8]-SMBserver:[RHEL7]-SMBclient !!!测试环境我们首关闭防火墙和selinux(SMBserver和SMBclient都需要) [root@localhost ...

  8. rsync 服务配置_rsync命令使用方法

    rsync介绍 rsync用来定时备份服务器中的文件或者目录,有三种工作模式,本地复制,使用系统用户认证,守护进程方式,开源高效.同步工具,把一台机器上的文件同步都另一台机器 .默认使用873端口 选 ...

  9. shell初学之nginx(负载均衡)

    创建三个以域名区分的网站a.com,b.com,c.com:访问a.b时,分别显示a.b两个网站的内容:访问c时,会出现依次显示两次a网站的内容,一次b网站的内容. 1 #!/bin/bash 2 s ...

  10. linux进阶之gitlab仓库搭建及免密使用

    一.Gitlab简介 GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务. 可通过Web界面进行访问公开的或者私人项目.它拥有与Github类 ...