11_Linux阻塞与非阻塞

news2024/11/18 3:40:20

目录

阻塞和非阻塞IO简介

等待队列

等待队列头

等待队列项

轮询

Linux驱动下的poll操作函数

阻塞式访问IO实验

阻塞式访问IO驱动程序编写

运行测试

非阻塞式IO实验

运行测试


阻塞和非阻塞IO简介

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

这里的"IO”并不是我们学习STM32或者其他单片机的时候所说的"GPIO”(也就是引脚)。这里的IO指的是Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式IO就会将应用程序对应的线程挂起,直到设备资源可以获取为止。对于非阻塞IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。阻塞式IO如图所示:

 图中应用程序调用read函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序。非阻塞IO如图所示:

 从图可以看出,应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功。

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

 从示例代码可以看出,对于设备驱动文件的默认读取方式就是阻塞式的,所以前面所有的例程测试APP都是采用阻塞IO

如果应用程序要采用非阻塞的方式来访问驱动设备文件,可以使用如下所示代码:

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

等待队列

等待队列头

阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。Linux内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作,如果我们要在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体wait_queue_head_t表示,wait_queue_head_t结构体定义在文件include/linux/wait.h中,结构体内容如下所示:

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

 参数q就是要初始化的等待队列头。

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

等待队列项

等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。结构体wait_queue_t表示等待队列项,结构体内容如下:

 使用宏DECLARE_WAITQUEUE定义并初始化一个等待队列项,宏的内容如下:

 name就是等待队列项的名字,tsk表示这个等待队列项属于哪个任务(进程),一般设置为current,在Linux内核中current相当于一个全局变量,表示当前进程。因此宏DECLARE_ WAITQUEUE就是给当前正在运行的进程创建并初始化了一个等待队列项。

将队列项添加/移除等待队列头

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

 等待队列项要加入的等待队列头。

wait:要加入的等待队列项。

返回值:无。

 

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

 q:要删除的等待队列项所处的等待队列头。

wait:要删除的等待队列项。

返回值:无。

等待唤醒

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

 参数q就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒。wake-up函数可以唤醒处于TASK-INTERRUPTIBLE和TASK-UNINTERRUPTIBLE状态的进程,而wake_up_interruptible函数只能唤醒处于TASK_INTERRUPTIBLE状态的进程。

等待事件

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

 

轮询

如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。poll、epoll和select可以用于处理轮询,应用程序通过select、epoll或poll函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。当应用程序调用select、epoll或poll函数的时候设备驱动程序中的poll函数就会执行,因此需要在设备驱动程序中编写 poll函数。先来看一下应用程序中使用的select、poll和epoll这三个函数。

select函数

 nfds:所要监视的这三类文件描述集合中,最大文件描述符加1。

readfds, writefds和exceptfds:这三个指针指向描述符集合,这三个参数指明了关心哪些描述符、需要满足哪些条件等等,这三个参数都是fd_set类型的, fd_set类型变量的每一个位都代表了一个文件描述符。readfds用于监视指定描述符集的读变化,也就是监视这些文件是否可以读取,只要这些集合里面有一个文件可以读取那么seclect就会返回一个大于0的值表示文件可以读取。如果没有文件可以读取,那么就会根据 timeout参数来判断是否超时。可以将readfs设置为NULL,表示不关心任何文件的读变化。writefds和readfs类似,只是writefs用于监视这些文件是否可以进行写操作。exceptfds 用于监视这些文件的异常。

比如我们现在要从一个设备文件中读取数据,那么就可以定义一个fd_set变量,这个变量要传递给参数readfds。当定义好一个fd_set变量以后可以使用如下所示几个宏进行操作:

 FD_ZERO用于将fd_set变量的所有位都清零,FD_SET用于将fd_set变量的某个位置1,也就是向fd_set添加一个文件描述符,参数fd就是要加入的文件描述符。FD_CLR用于将fd_set变量的某个位清零,也就是将一个文件描述符从fd_set中删除,参数fd就是要删除的文件描述符。FD_ISSET用于测试一个文件是否属于某个集合,参数fd就是要判断的文件描述符。

timeout:超时时间,当我们调用select函数等待某些文件描述符可以设置超时时间,超时时间使用结构体timeval表示,结构体定义如下所示:

 当timeout为NULL的时候就表示无限期的等待。

返回值: 0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作; -1,发生错误;其他值,可以进行操作的文件描述符个数。

使用select函数对某个设备驱动文件进行读非阻塞访问的操作示例如下所示:

 

poll函数

在单个线程中, select函数能够监视的文件描述符数量有最大的限制,一般为1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用poll函数,poll函数本质上和select没有太大的差别,但是poll函数没有最大文件描述符限制, Linux应用程序中poll函数原型如下所示:

 fds:要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体pollfd类型的, pollfd结构体如下所示:

 fd是要监视的文件描述符,如果fd无效的话那么events监视事件也就无效,并且revents返回0。events是要监视的事件,可监视的事件类型如下所示:

 revents是返回参数,也就是返回的事件,由Linux内核设置具体的返回事件。

nfds: poll函数要监视的文件描述符数量。

timeout:超时时间,单位为ms。

返回值:返回revents域中不为0的pollfd结构体个数,也就是发生事件或错误的文件描述符数量; 0,超时; -1,发生错误,并且设置errno为错误类型。

使用poll函数对某个设备驱动文件进行读非阻塞访问的操作示例如下所示:

 epoll函数

传统的selcet和poll函数都会随着所监听的fd数量的增加,出现效率低下的问题,而且poll函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。为此,epoll应运而生,epoll就是为处理大并发而准备的,一般常常在网络编程中使用epoll函数。应用程序需要先使用epoll_create函数创建一个epoll句柄, epoll_create函数原型如下:

 size:从Linux2.6.8开始此参数已经没有意义了,随便填写一个大于0的值就可以。

返回值: epoll句柄,如果为-1的话表示创建失败。

epoll句柄创建成功以后使用epoll_ctl函数向其中添加要监视的文件描述符以及监视的事件, epoll ctl函数原型如下所示:

 epfd:要操作的epoll句柄,也就是使用epoll_create函数创建的epoll句柄。

op:表示要对epfd(epoll句柄)进行的操作,可以设置为:

 fd:要监视的文件描述符。

event:要监视的事件类型,为epoll_event结构体类型指针, epoll_event 结构体类型如下所示:

 结构体epoll_event的events成员变量表示要监视的事件,可选的事件如下所示:

 上面这些事件可以进行“或”操作,也就是说可以设置监视多个事件。

返回值: 0,成功: -1,失败,并且设置errno的值为相应的错误码。一切都设置好以后应用程序就可以通过epoll_wait 函数来等待事件的发生,类似select函数。epoll wait函数原型如下所示:

 epfd:要等待的epoll。

events:指向epoll_event结构体的数组,当有事件发生的时候Linux内核会填写events,调用者可以根据events判断发生了哪些事件。

maxevents:events数组大小,必须大于0。

timeout:超时时间,单位为ms。

返回值:0,超时;-1,错误;其他值,准备就绪的文件描述符数量。

epoll更多的是用在大规模的并发服务器上,因为在这种场合下select和poll并不适合。当设计到的文件描述符(fd)比较少的时候就适合用selcet和poll。

Linux驱动下的poll操作函数

当应用程序调用select或poll函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations操作集中的poll函数就会执行。所以驱动程序的编写者需要提供对应的poll 函数,poll函数原型如下所示:

 filp:要打开的设备文件(文件描述符)。

wait:结构体poll_table_struct类型指针,由应用程序传递进来的。一般将此参数传递给poll_wait函数。

返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下:

 我们需要在驱动程序的poll函数中调用poll_wait函数, poll_wait函数不会引起阻塞,只是将应用程序添加到poll_table中,poll_wait函数原型如下:

参数wait_address是要添加到poll_table中的等待队列头,参数p就是poll_table,就是file_operations中poll函数的wait参数。

阻塞式访问IO实验

直接在应用程序中通过read函数不断的读取按键状态,当按键有效的时候就打印出按键值。这种方法有个缺点,那就是imx6uirqApp这个测试应用程序拥有很高的CPU占用率,大家可以在开发板中加载驱动程序模块imx6uirq.ko,然后以后台运行模式打开imx6uirgApp这个测试软件,命令如下:

 测试驱动是否正常工作,如果驱动工作正常的话输入"top”命令查看imx6uirqApp这个应用程序的CPU使用率,结果如图所示:

从图52.2.1可以看出, imx6uirqApp这个应用程序的CPU使用率竟然高达99.6%,这仅仅是一个读取按键值的应用程序,这么高的CPU使用率显然是有问题的!原因就在于我们是直接在while循环中通过read函数读取按键值,因此imx6uirqApp这个软件会一直运行,一直读取按键值,CPU使用率肯定就会很高。最好的方法就是在没有有效的按键事件发生的时候,imx6uirqApp这个应用程序应该处于休眠状态,当有按键事件发生以后imx6uirqApp这个应用程序才运行,打印出按键值,这样就会降低CPU使用率,节下来就使用阻塞IO来实现此功能。

阻塞式访问IO驱动程序编写

 第32行,修改设备文件名字为“blockio”,当驱动程序加载成功以后就会在根文件系统中出现一个名为"/dev/blockio”的文件。

第61行,在设备结构体中添加一个等待队列头r_wait,因为在Linux驱动中处理阻塞IO需要用到等待队列。

第107-110行,定时器中断处理函数执行,表示有按键按下,先在107行判断一下是否是一次有效的按键,如果是的话就通过|wake_up或者wake_up_interruptible函数来唤醒等待队列r_wait

第168行,调用init_waitqueue_head函数初始化等待队列头r_wait。

第200-206行,采用等待事件来处理read的阻塞访问, wait_event_interruptible函数等待releasekey有效,也就是有按键按下。如果按键没有按下的话进程就会进入休眠状态,因为采用了wait_event_interruptible函数,因此进入休眠态的进程可以被信号打断。

第208~218行,首先使用DECLARE_WAITQUEUE宏定义一个等待队列,如果没有按键按下的话就使用add_wait_queue函数将当前任务的等待队列添加到等待队列头r-wait中。随后调用-set_current_state函数设置当前进程的状态为TASK_INTERRUPTIBLE,也就是可以被信号打断。接下来调用schedule函数进行一次任务切换,当前进程就会进入到休眠态。如果有按键按下,那么进入休眠态的进程就会唤醒,然后接着从休眠点开始运行。在这里也就是从第213行开始运行,首先通过 signal_pending函数判断一下进程是不是由信号唤醒的,如果是由信号唤醒的话就直接返回-ERESTARTSYS这个错误码。如果不是由信号唤醒的(也就是被按键唤醒的)那么就在217行调用-setcurrent_state函数将任务状态设置为TASK_RUNNING,然后在218行调用remove_wait_queue函数将进程从等待队列中删除。

使用等待队列实现阻塞访问重点注意两点:

1.将任务或者进程加入到等待队列头

2.在合适的点唤醒等待队列,一般都是中断处理函数里面。

运行测试

按下开发板上的KEY0按键,结果如图所示:

 当按下KEY0按键以后blockioApp这个测试APP就会打印出按键值。输入"top"命令,查看blockioAPP这个应用APP的CPU使用率,如图所示:

 从图可以看出,当在按键驱动程序里面加入阻塞访问以后, blockioApp这个应用程序的CPU使用率降低到了0.0%。大家注意,这里的0.0%并不是说blockioApp这个应用程序不使用CPU了,只是因为使用率太小了, CPU使用率可能为0.00001%,但是图只能显示出小数点后一位,因此就显示成了0.0%。

非阻塞式IO实验

 第32行,修改设备文件名字为“noblockio”,当驱动程序加载成功以后就会在根文件系统中出现一个名为“/dev/noblockio”的文件。

第202~204行,判断是否为非阻塞式读取访问,如果是的话就判断按键是否有效,也就是判断一下有没有按键按下,如果没有的话就返回-EAGAIN。

第241~252行, imx6uirq_poll函数就是file_operations驱动操作集中的poll函数,当应用程序调用select或者poll函数的时候imx6uirq_poll函数就会执行。

第246行调用poll_wait 函数将等待队列头添加到poll_table中

第248-250行判断按键是否有效,如果按键有效的话就向应用程序返回POLLIN这个事件,表示有数据可以读取。

第259行,设置file_operations的poll成员变量为imx6uirq_poll

 第52-73行,这段代码使用 poll函数来实现非阻塞访问,在while循环中使用poll函数不断的轮询,检查驱动程序是否有数据可以读取,如果可以读取的话就调用read函数读取按键数据。第75-101行,这段代码使用select函数来实现非阻塞访问。

运行测试

 驱动加载成功以后使用如下命令打开noblockioApp这个测试APP,并且以后台模式运行:

 按下开发板上的KEY0按键,结果如图所示:

 当按下KEY0按键以后noblockioApp这个测试APP就会打印出按键值。输入"top"命令,查看noblockioAPP这个应用APP的CPU使用率,如图所示:

从图可以看出,采用非阻塞方式读处理以后,noblockioApp的CPU占用率也低至0.0%,和图中的blockioApp一样,这里的0.0%并不是说noblockioApp这个应用程序不使用CPU了,只是因为使用率太小了,而图中只能显示出小数点后一位,因此就显示成了0.0%。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/734162.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

充分利用测试自动化的 10 个最佳实践

目录 前言: 实践1:手动和自动测试结合 实践2:特别注意回归测试 实践3:包括端到端测试 实践4:为自动化测试提供集体所有权 实践5:详细计划与测试相关的所有流程 实践6:选择适合您需求的自…

Python随机生成2堆三维点云点,有固定的重复率并可视化

Python随机生成2堆三维点云点,有固定的重复率并可视化 1. 效果图2. 源码 这篇博客源于博友的提问,刚好电脑在旁边没啥事,那就开整吧。 np.random 生成随机点(提供了俩种方法,1. xyz限制都是0~MAX值,2. xyz分…

IDEA中使用.env文件配置信息

一、说明 我们以配置阿里云的 Access Key 的信息为例(配置别的信息当然也可以,我只是举个例子!!!),假设我们的代码中需要用到它。Access Key有两个属性,分别为【ALIBABA_CLOUD_ACCE…

【剧前爆米花--前端三剑客】html的一些常用标签及其实例

作者:困了电视剧 专栏:《JavaEE初阶》 文章分布:这是一篇关于html前端的文章,在这篇文章中我会简单介绍一些常用的html标签,并给出他们的应用实例,希望对你有所帮助! 目录 html常见标签 标题标…

python_day3_tuple

元组tuple :无法修改(只读的列表) t1 () t2 tuple() t3 (1, java, True, ()) print(f"t1的数据类型是:{type(t1)}") print(f"t2的数据类型是:{type(t2)}") print(f"t3的数据类型是&#…

ChatLaw:北大团队智能法律助手,国产大模型成功应用普惠法律服务

“ 技术发展的本质是普惠,用技术降低普通人获取法律知识的成本,向社会输出普惠的公平正义。—— 北京大学 ChatLaw 项目组” 刚刚清华团队升级了国产大模型:ChatGLM2-6B,ChatGLM2-6B 初体验。 转眼这两天北大团队推出的智能法律助…

DAY36:贪心算法(三)最大子数组和+买卖股票最佳时机

文章目录 53.最大子数组和枚举思路暴力解法贪心思路完整版时间复杂度 122.买卖股票的最佳时机Ⅱ(解法比较巧妙)思路完整版总结 53.最大子数组和 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元…

Java开发基础系列(一):Java设计概述

😊 作者: 一恍过去 💖 主页: https://blog.csdn.net/zhuocailing3390 🎊 社区: Java技术栈交流 🎉 主题: Java开发基础系列(一):Java设计概述 ⏱️ 创作时间: 2023年…

【漏洞复现】nginxWebUI 存在前台远程命令执行漏洞

文章目录 前言声明一、nginxWebUI 简介二、漏洞描述三、影响版本四、漏洞复现五、修复建议 前言 nginxWebUI 存在前台远程命令执行漏洞,攻击者通过该漏洞获取服务器控制权限进而进一步获取敏感数据信息。 声明 请勿利用文章内的相关技术从事非法测试,由…

# 文盘Rust -- FFI 浅尝

作者: jiashiwen 原文来源: https://tidb.net/blog/cfa03c39 notice"Rust is a trademark of the Mozilla Foundation in the US and other countries." rust FFI 是rust与其他语言互调的桥梁,通过FFI rust 可以有效继承 C 语言…

道德与社会问题简报 #4: 文生图模型中的偏见

简而言之: 我们需要更好的方法来评估文生图模型中的偏见 介绍 文本到图像 (TTI) 生成 现在非常流行,成千上万的 TTI 模型被上传到 Hugging Face Hub。每种模态都可能受到不同来源的偏见影响,这就引出了一个问题: 我们如何发现这些模型中的偏见&#xff1…

Android启动流程优化 上篇

Android启动流程优化 上篇 本文链接:Android启动流程优化 上篇_猎羽的博客-CSDN博客 启动流程 各个阶段图 1、各个阶段的概括总结 分为5个大阶段或者10个小阶段 【字节跳动团队】内部论坛分享也是这么处理的 补充一些只是细节点: application#onCreate()运行…

基于多案例系统学习防洪评价报告编制方法与水流数学模型建模(HECRAS、MIKE、EFDC、Delft3D、FVCOM、SWAT、SWMM等模型应用)

目录 ​专题一 《防洪评价报告编制导则解读河道管理范围内建设项目编制导则》(SL/T808- 2021)解读编制导则解读 专题二 防洪评价相关制度与解析 ★专题三 案例演练解析 专题四 防洪评价地形获取及常用计算实践 专题五 HEC-RAS软件原理及应用案例解析…

leetcode160.相交链表

https://leetcode.cn/problems/intersection-of-two-linked-lists/solution/ 相交链表是指两个单向链表在某个节点处相交,之后形成了共同的后续部分。通常,两个链表的长度不相等。在相交节点之前,两个链表的节点数可能不同,但在相…

Java后端编译与优化

如果我们将字节码看作是程序语言的一种中间表示形式,那编译器无论在何时、在何种状态下把Class文件转换成与本地基础设施相关的二进制机器码,它都可以视为整个编译过程的后端。 1 即时编译器 即时编译器是指运行时将热点代码编译成本地机器码&#xff…

程序员必须掌握哪些算法?——前端开发工程师需要掌握的算法

文章目录 📋前言🎯什么是算法?🎯前端开发工程师需要掌握的算法🧩排序算法(如快速排序、归并排序)🧩搜索算法(如二分搜索)🧩图算法(如广…

【Unity造轮子】2D横版平台跳跃游戏实现多段跳完美手感(含源码)

文章目录 前言先看效果,手感很丝滑原理开始1. 看到检测点的检测范围2. 二段跳实现3. 动画控制器配置 源码扩展完结 前言 随着游戏技术的不断发展,2D横版平台跳跃游戏成为许多玩家的最爱。这类游戏以其简单而有趣的玩法和精致的视觉效果吸引着无数游戏爱…

Kotlin~Decorator装饰器模式

概念 装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。可以运行时动态添加新的功能,而无需改变原来的代码。 特点: 灵活扩展新的功能动态…

接口设计的总结

目录 前言 设计好接口的 36 个锦囊 总结 前言 作为后端开发,不管是什么语言,Java、Go 还是 C,其背后的后端思想都是类似的。我们做后端开发工程师,主要工作就是:如何把一个接口设计好。所以,今天就给大家…

北京大学考研机试题:二叉树

【题目来源】https://www.acwing.com/problem/content/description/3474/【题目描述】 如上图所示,由正整数 1,2,3…… 组成了一颗特殊二叉树。 我们已知这个二叉树的最后一个结点是 n。 现在的问题是,结点 m 所在的子树中一共包括…