Linux驱动IO篇——阻塞/非阻塞IO

news2024/12/23 0:28:58

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9UdGpV2k-1686380495172)(pic/image-20230417232504849.png)]

文章目录

    • 非阻塞IO
    • 阻塞IO
    • 等待队列
    • 等待队列变体

非阻塞IO


在应用程序中,使用open函数打开一个/dev目录下的一个设备文件时,默认是以阻塞的方式打开。

所谓阻塞,就是当我们请求的资源不可用时(资源被占用,没有数据到达等等),会使得进程休眠,从现象看就是卡在那里。

应用层

如果我们希望以非阻塞方式打开设备文件,则应该在open设备文件时,添加一个O_NONBLOCK的flag参数,例如:

fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);

驱动层

应用层以非阻塞方式打开设备文件,则驱动层也要有对应的处理操作才行。

应用层传入的O_NONBLOCK标志,会保存在struct file结构体的f_flags成员中。当资源不可用时,同时判断f_flags变量是否为O_NONBLOCK,有则代表以非阻塞方式打开,然后返回一个-EAGAIN错误,提示应用层资源暂时不可用。

下面是驱动中read函数非阻塞处理的伪代码,例如:

static ssize_t vser_read(struct file *flip, char __user *buf, size_t count, loff_t *pos)
{
	......
	if (资源不可用)
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
	......
}

当出现资源不可用时,会出现以下提示:

read: Resource temporarily unavailable

阻塞IO


上述非阻塞方式打开设备文件,虽然可以防止进程休眠,无论结果如何都会立即返回,但缺点是必须要定期查询资源是否可以获得,例如上述代码中,每次调用read函数都要去查询一下资源是否可用。这种操作效率非常低。

但是用阻塞IO的话,进程休眠期间就再也不能做其他的事情了。

所以不论是非阻塞IO还是阻塞IO,都有缺点,有没有好的办法呢?

正确做法应该是:使用阻塞IO,驱动中添加唤醒操作。

什么意思呢?既然有休眠,就应该有对应的唤醒操作,否则进程将会一直休眠下去。驱动程序应该在资源可用时负责执行唤醒操作。

要实现既有休眠,又有唤醒的阻塞IO模型,应该使用等待队列。

等待队列


我们以一个虚拟串口设备为例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0uYRE92-1686380495173)(pic/image-20230417225721779.png)]

如图是一个虚拟串口设备示例图,这是一个功能弱化之后的只具备内回环作用的串口。

主要功能:在驱动中实现一个FIFO,驱动接收用户层传来的数据,然后将之放入FIFO,当应用层要获取数据时,驱动将FIFO中的数据读出,然后复制给应用层。

我们以这个虚拟串口设备为例,讲解等待队列的使用。

为了方便理解,简化了不必要的代码,下面是驱动代码:

//定义内核fifo
DEFINE_KFIFO(vsfifo, char ,32)
//定义两个等待队列头
wait_queue_head_t rwqh;//读等待队列
wait_queue_head_t wwqh;//写等待队列

static ssize_t vser_read(struct file *flip, char __user *buf, size_t count, loff_t *pos)
{
......
    /* fifo为空,没有数据可读,进入休眠*/
	if(kfifo_is_empty(&vsfifo))	{
		if(flip->f_flags & O_NONBLOCK)//非阻塞方式,直接返回
			return -EAGAIN;
			
        /* 阻塞方式,没有数据可读,将进程放入读等待队列rwqh,进程休眠;唤醒条件是fifo不为空 */
		if (wait_event_interruptible(rwqh, !kfifo_is_empty(vsfifo)))
			return -ERESTARTSYS;
	}
    
    //将fifo中的数据返回给应用层
    ret = kfifo_to_user(&vsfifo, buf, count, &copied);
	
    /* fifo未满,还有空间,代表可以往fifo写数据,唤醒写等待队列 */
	if (!kfifo_is_full(&vsfifo))
		wake_up_interruptible(&wwqh);
......
}

static ssize_t vser_write(struct file *flip, const char __user *buf, size_t count, loff_t *pos)
{
......   
    /* fifo已满,不可写 */
    if (kfifo_is_full(&vsfifo)) {
        if (flip->f_flags & O_NONBLOCK)//非阻塞方式,直接返回
            return -EAGAIN;
        
        /* 阻塞方式,进程休眠,放入写等待队列,唤醒条件是fifo未满时 */
        if (wati_event_interruptible(wwqh, !kfifo_is_full(&vsfifo)))
            return -ERESTARTSYS;
    }
    
    //从应用层获取数据,写入fifo
    ret = kfifo_from_user(&vsfifo, buf, count, &copied);
    
    /* fifo不为空,唤醒读等待队列rwqh */
    if (!kfifo_is_empty(&vsfifo))
        	wake_up_interruptible(&rwqh);
......
}

/* 驱动入口函数 */
static int __init vser_init(void)
{
......
    /* 初始化等待队列头 */
	init_waitqueue_head(rwqh);
    init_waitqueue_head(wwqh);
......
}

首先用wait_queue_head_t定义两个等待队列头rwqhwwqh,分表代表读等待队列和写等待队列,然后在驱动入口函数vser_init中调用init_waitqueue_head初始化等待队列头。

读操作处理

当应用程序想要从驱动中读取数据,而fifo为空时,此时代表没有数据可读。如果是非阻塞方式则直接返回,如果是阻塞方式,调用wait_event_interruptible函数将进程放入读等待队列rwqh,进程休眠。当fifo不为空时,即fifo有数据时,才会被唤醒。

写操作处理

当应用层想往驱动中写数据,而fifo已经满了,此时代表不可写。如果是非阻塞方式直接返回,如果是阻塞方式,则调用wati_event_interruptible函数将进程放入写等待队列,进程休眠。当fifo未满时,才会被唤醒。

唤醒处理

调用wake_up_interruptible函数唤醒对应的等待队列

通过在上述加入等待队列的操作,当以阻塞方式打开设备文件时,资源可用时会被唤醒,而不至于一直休眠。

等待队列变体


上述只是使用了等待队列最常用的函数,等待队列还有其他很多的变体,下面列举常见的:

DECLARE_WAIT_QUEUE_HEAD(name)
init_waitqueue_head(q)

wait_event(wq, condition)
wait_event_timeout(wq, condition, timeout)
wake_up(x)

wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wq, condition, timeour)
wait_event_interruptible_exclusive(wq, condition)
wake_up_interruptible(x)

wait_event_interruptible_locked(wq, condition)
wait_event_interruptible_locked_irq(wq, condition)
wake_up_locked(x)

虽然有很多的变体,但简单的说,使进程休眠调用wait_event_xxx函数,唤醒进程调用wake_up_xxx函数。其余的后缀一一对应即可。

关于这些宏或函数的更多信息请参考“include/linux/wait.h”

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

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

相关文章

让车载系统与外部系统无缝对接——掌握SOA跨系统通信技术

车载SOA架构原理 车载 SOA(Service-Oriented Architecture,面向服务的架构)是一种基于服务的体系结构,旨在提高车载电子系统的可维护性、可扩展性和互操作性。它将车载电子系统划分为独立的、可复用的服务单元,这些服…

NodeJS MongoDB⑦

文章目录 ✨文章有误请指正,如果觉得对你有用,请点三连一波,蟹蟹支持😘前言Node&MongoDB 第一步 连接数据库 第二步 创建User Mongodb模型 第三步 简单使用 Mongodb命令 第四步 规范使用 Mongodb命令 &#xff08…

爆款视频批量剪辑生成系统源码部署

创建视频 该接口用于创建抖音视频(支持话题, 小程序等功能)。该接口适用于抖音。 使用限制 抖音的 OAuth API 以https://open.douyin.com/ 开头。挂载小程序请先完成注册开发者平台账号。创建抖音视频后, 会有一个审核过程, 期间只有自己可见。如果发…

docker数据管理与网络通信

一、管理docker容器中数据 管理Docker 容器中数据主要有两种方式:数据卷(Data Volumes)和数据卷容器( DataVolumes Containers) 。 1、 数据卷 数据卷是一个供容器使用的特殊目录,位于容器中。可将宿主机的目录挂载到数据卷上,对数据卷的修改操作立刻…

leetcode11. 盛最多水的容器(java)

盛最多水的容器 leetcode11. 盛最多水的容器题目描述 解题思路代码演示二叉树专题 leetcode11. 盛最多水的容器 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/container-with-most-water 题目描述 给定一个长度为 n 的…

物联网Lora模块从入门到精通(二) LED灯泡闪烁与呼吸灯

目录 一、前言 二、实践与代码 1.电亮LED1 2.熄灭LED1 3.翻转LED电平 4.LED1与LED2交替闪烁 5.LED1呼吸灯 三、程序代码 一、前言 本篇内容属于新大陆物联网Lora模块开发,使用给定的Lora基础例程,并在其基础上开发完成,并可为其他版本的Lo…

Atcoder Beginner Contest 294

A - Filter AC代码&#xff1a; #include<iostream> #include<algorithm> #include<cstring> using namespace std; int main() {int n;cin>>n;for(int i0;i<n;i){int x;cin>>x;if(x%20)cout<<x<<" ";}return 0; } …

​​INNODB和MyISAM区别

1 存储引擎是MyISAM 如下&#xff1a; CREATE table test_myisam (cli int ) ENGINEMyISAM 存储目录里会有三个文件 test_myisam.frm为“表定义”&#xff0c;是描述数据表结构的文件 test_myisam.MYI文件是表的索引 test_myisam.MYD文件是表的数据 2 存储引擎是INNODB…

LeetCode——比较字符串最小字母出现频次

1、题目 1170. 比较字符串最小字母出现频次 - 力扣&#xff08;Leetcode&#xff09; 定义一个函数 f(s)&#xff0c;统计 s 中&#xff08;按字典序比较&#xff09;最小字母的出现频次 &#xff0c;其中 s 是一个非空字符串。 例如&#xff0c;若 s "dcce"&am…

httprunner 2.x的基本使用(一)

上一章&#xff1a; 下一章&#xff1a; httprunner 2.x的基本使用&#xff08;二&#xff09;_做测试的喵酱的博客-CSDN博客 一、参考地址&#xff1a; 使用说明_httprunner2.0 概述及使用说明 二、介绍 HttpRunner是一款面向 HTTP(S) 协议的通用测试框架&#xff0c;只需…

location.href 和 document.URL 与 document.documentURI

location.href 和 document.URL 与 document.documentURI 相同点 获取到的值相同 不同点 location.hrefurl可以赋值, 效果类似location.assign(url) , 可以后退 document.URL 与 document.documentURI 是只读的, 赋值无效 location.href locationwindow.location true lo…

从源码角度看Linux线程是怎么创建出来的

这篇文章来学习一下线程的创建过程。 线程不是一个完全由内核实现的机制&#xff0c;它是由内核态和用户态合作完成的。 用户态创建线程 pthread_create 不是一个系统调用&#xff0c;是 glibc 库的一个函数&#xff0c;位于 nptl/pthread_create.c 中&#xff1a; int __pth…

redis商户查询缓存

1 什么是缓存? 前言:什么是缓存? 就像自行车,越野车的避震器。 举个例子:越野车,山地自行车,都拥有"避震器",防止车体加速后因惯性,在酷似"U"字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样; 同样,实际开发中,系统也需要"避震器",防止过…

Unity Shader - 兰伯特漫反射

兰伯特漫反射公式&#xff1a; 漫反射&#xff08;Diffuse&#xff09; 光源颜色 * max&#xff08;0&#xff0c;cos&#xff08;光方向和法线的夹角&#xff09;&#xff09; 公式原理&#xff1a; 从上面图片可以看出光照方向 L 与物体法相 N形成的 余弦值越大&#xff0c;反…

力扣笔记(每日随机一题)—— 打折购买糖果的最小开销

问题&#xff08;简单&#xff09; 一家商店正在打折销售糖果。每购买 两个 糖果&#xff0c;商店会 免费 送一个糖果。 免费送的糖果唯一的限制是&#xff1a;它的价格需要小于等于购买的两个糖果价格的 较小值 。 比方说&#xff0c;总共有 4 4 4 个糖果&#xff0c;价格…

开源代码分享(3)—微电网鲁棒定价策略(附matlab代码)

1背景介绍 1.1摘要 本论文聚焦于微电网中的能量失衡管理问题&#xff0c;并从电力市场的角度进行研究。与传统电力网不同&#xff0c;微电网可从可再生能源&#xff08;RES&#xff09;如太阳能电池板或风力涡轮机等获得额外能源。然而&#xff0c;来自RES的随机输入给平衡供需…

简述Vue的生命周期以及每个阶段做的事情

03_简述Vue的生命周期以及每个阶段做的事情 思路 给出概念 列举出生命周期各个阶段 阐述整体流程 结合实际 扩展&#xff1a;vue3变化 回答范例 每个vue组件实例被创建后都会经过一系列步骤。比如它需要数据观测、模板编译、挂载实例到dom、以及数据变化的时候更新dom、…

Android系统的启动过程(三):Launcher启动过程

Android系统的启动过程(三)&#xff1a;Launcher启动过程 摘要&概述 前两篇文章中我们已经将系统启动的过程推进到了系统服务启动完毕之后&#xff0c;本篇文章就来介绍Android系统启动的最后一步&#xff1a;启动Launcher。 这个Launcher我们可以通俗地理解为桌面&#…

深度相机介绍

一、什么是深度相机 &#xff08;五&#xff09;深度相机&#xff1a;结构光、TOF、双目相机 - 知乎 传统的RGB彩色普通相机称为2D相机&#xff0c;只能拍摄相机视角内的物体&#xff0c;没有物体到相机的距离信息&#xff0c;只能凭感觉感知物体的远近&#xff0c;没有明确的数…

V90 PN伺服驱动器转矩控制(750报文)

主要介绍通过标准报文加附加报文 750 实现发送驱动报文的控制字、速度给定、转矩限幅及附加转矩给定的功能,首先就是V90在博途环境下的组态,安装GSD文件,GSD文件下载地址如下: https://download.csdn.net/download/m0_46143730/86542047https://download.csdn.net/downloa…