Linux中的阻塞机制

news2024/11/28 4:50:11

我们知道在字符设备驱动中,应用层调用read、write等系统调用终会调到驱动中对应的接口。 可以当应用层调用read要去读硬件的数据时,硬件的数据未准备好,那我们该怎么做?

一种办法是直接返回并报错,但是这样应用层要获得数据需要不断的调用read去访问硬件,进程的上下文在用户空间和内核空间不停的切换,耗费了CPU的资源,降低了系统效率。那么有没有更好的办法呢? 答案是有的,在这种情况下我们就可以利用Linux的阻塞机制,实现阻塞访问。

一、阻塞和非阻塞

阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。

二、等待队列

在 Linux 驱动程序中,可以使用等待队列(wait queue)来实现阻塞进程的唤醒。wait queue 很早就作为一个基本的功能单位出现在 Linux 内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制。等待队列可以用来同步对系统资源的访问,信号量在内核中也依赖等待队列来实现。

希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权。因此,等待队列是一组睡眠的进程,当某一条件变为真时,由内核唤醒他们。

等待队列是一个具有头节点的双向循环链表,把所有睡眠的进程连接起来,每个节点元素都有进程相关的信息

等待队列示意图1

等待队列示意图2

1,等待队列头

每个等待队列都有一个等待队列头,驱动注意操作等待队列头来实现阻塞的功能,二等待队列项的内容不需要关心,因为等待队列是由中断处理程序和主要内核函数修改的,其双向链表必须进行保护,防止多进程同时进行访问修改,造成不可预知的后果,所以定义了lock来锁住链表操作的区域

等待队列头结构体的定义:内核使用等待队列头来挂起一个进程,也使用等待队列头来唤醒进程

struct __wait_queue_head {

spinlock_t lock; //自旋锁变量,用于在对等待队列头

struct list_head task_list; // 指向等待队列的list_head

};

typedef struct __wait_queue_head wait_queue_head_t;

操作函数

#include <linux/sched.h>

#include <linux/wait.h>

1).定义“等待队列头”

wait _ queue _ head _ t my _ queue;

2) .初始化“等待队列头”。

void init_waitqueue_head(wait_queue_head_t *);

而下面的 DECLARE_WAIT_QUEUE_HEAD()宏可以作为定义并初始化等待队列头的“快捷方式”。

DECLARE_WAIT_QUEUE_HEAD(name);

3).条件等待/休眠函数 一边休眠等待条件

//当cond条件是false(0)则休眠(不可中断版,不推荐使用)

void wait_event(wait_queue_head_t wq, int cond);

上面程序的执行过程:

1.用当前的进程描述块(PCB)初始化一个wait_queue描述的等待任务。

2.在等待队列锁资源的保护下,将等待任务加入等待队列。

3.判断等待条件是否满足,如果满足,那么将等待任务从队列中移出,退出函数。

4.如果条件不满足,那么任务调度,将CPU资源交与其它任务。

5.当睡眠任务被唤醒之后,需要重复(2)、(3)步骤,如果确认条件满足,退出等待事件函数。

使用举例: flag可以是一个条件表达式

static wait_queue_head_t wq;

init_waitqueue_head(&wq);//初始化等待队列头

//if(!flag){

while(!flag){ //条件不满足

if(wait_event_interruptible(wq,flag)) //如果是被其他信号唤醒则返回错误

return -ERESTARTSYS;

}

使用 while 而不使用if的原因是:wait_event_interruptible

可以被中断及信号打断,使用while(1),可以避免被打断的情况。

注:其实可以不用加while,查看内核源码的用法,如果被中断或者信号打断,直接返回错误。

flag = 1; //先设置条件,再唤醒

wake_up(&wq); //条件满足时唤醒等待队列头上所有的进程

//当cond条件是false(0)则休眠(超时版,timeout是超时值,单位是计数值)

//超时返回值为0 ,被唤醒大于0 需判断返回值

int wait_event_timeout(wait_queue_head_t wq, int condition, unsigend long timeout);

//当cond条件是false则休眠(可中断版)

//返回值:打断:负数,绝对值是错误码,成功0 返回值需要做判断

int wait_event_interruptible(wait_queue_head_t wq, int condition);

//当cond条件是false则休眠(可超时中断版)

//打断:负数,绝对值是错误码; 超时:0; 条件满足:>0

int wait_event_interruptible_timeout(wait_queue_head_t wq, int condition, unsigend long timeout);

4).唤醒函数 另一边条件成熟时唤醒

void wake_up(wait_queue_head_t *) //能唤醒所以状态的进程

void wake_up_interruptible(wait_queue_head_t *) //只适用于interruptible,配对使用

注意:唤醒函数当条件满足时,一定要先设置条件condition,再唤醒调用唤醒函数。因为等待睡眠函数返回后会首先检查condition是否满足,若不满足会继续睡

如: counter = count;

wake_up_interruptible(&wq);

2,等待队列项

定义等待对列:

struct __wait_queue {

unsigned int flags; //prepare_to_wait()里有对flags的操作,查看以得出其含义

#define WQ_FLAG_EXCLUSIVE 0x01 //一个常数,在prepare_to_wait()用于修改flags的值

void * private //通常指向当前任务控制块

wait_queue_func_t func; //唤醒阻塞任务的函数 ,决定了唤醒的方式

struct list_head task_list; // 阻塞任务链表

};

typedef struct __wait_queue wait_queue_t;

1) 定义一个等待队列

wait_queue_t wait;

2) 初始化等待队列

内核中定义的接口如下:

static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)

{

q->flags = 0;

q->private = p; //私有数据指针,一般指向当前任务控制块

q->func = default_wake_function; //使用默认的唤醒函数

}

使用范例:

init_waitqueue_entry(&wait, current);

3) 添加/ 等待队列。

void fastcall add _ wait _ queue(wait _ queue _ head _ t *q, wait _ queue _ t *wait);

add_wait_queue()用于将等待队列 wait 添加到等待队列头 q 指向的等待队列链表

4)移除等待队列。

void fastcall remove _ wait _ queue(wait _ queue _ head _ t *q, wait _ queue _ t *wait);

remove_wait_queue()用于将等待队列 wait 从附属的等待队列头 q 指向的等待队列链表中移除。

5)判断等待队列是否为空。

static inline int waitqueue_active(wait_queue_head_t *q)

{

return ! list_empty(&q->task_list);

}

判断等待对列头是否为空,当一个进程访问设备而得不到资源时就会被放入等待队列头指向的等待队列中。

三、函数 sleep_on的实现

Sleep()函数相信大家早已耳熟能详了,可是内部究竟是怎么实现的呢?让我们一起来揭开它的面纱

void sleep_on(wait_queue_head_t *wq)

{

wait_queue_t wait; //定义等待队列

init_waitqueue_entry(&wait, current); //初始化等待队列

current->state = TASK_UNINTERRUPTIBALE; //设置进程状态

add_wait_queue(wq,&wait); //加入等待队列

schedule(); //调度,当前进程进入睡眠

remove_wait_queue(wq,&wait); //醒后从等待队列中移除

}

可以发现,程序之所以能睡眠,是因为他改变了自己的状态,并执行调度,放弃了占用CPU。但是我们要唤醒进程,必须要找到它,怎么找到它呢,关键就在于进程在睡眠前我们把它加入了等待对应,只要找到等待队列我们就能找到挂起的进程并唤醒它。

相信进过前面的介绍大家应该对等待队列和linux中的阻塞机制有更深的了解了吧。

对嵌入式物联网感兴趣的小伙伴,可以多了解一下相关信息。(看过来)

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

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

相关文章

linux通配符和正则表达式深层解析...

目录&#xff1a; (一)了解通配符和正则的作用 (二)通配符的使用 (三)正则表达式的使用 (四)扩展正则表达式的使用 (一)了解通配符和正则的作用 (1.1)在我们日常的工作中&#xff0c;我们都会使用到通配符或者正则表达式。通配符是一种特殊语句&#xff0c;主要有星号(*)和问号…

交换机和路由器到底有什么区别???

我&#xff1a;度娘度娘&#xff0c;交换机和路由器的区别是什么呢&#xff1f; 度娘&#xff1a;一个工作在第二层数据链路层&#xff0c;一个工作在第三层网络层。 我&#xff1a;哈&#xff1f;那工作在不同层会有什么区别&#xff1f;为什么要工作在不同层&#xff1f; …

2023五一数学建模A题完整思路

已更新五一数学建模A题思路&#xff0c;文章末尾获取&#xff01; A题完整思路&#xff1a; A题是一个动力学问题&#xff0c;需要我们将物理学概念运用到实际生活中&#xff0c;我们可以先看题目 问题1&#xff1a; 假设无人机以平行于水平面的方式飞行&#xff0c;在空中投…

Windows11安装sqlserver2012失败后解决方案

首先卸载 WinR打开运行输入services.msc查看所有服务/或者我的电脑管理找到服务列表/任务管理器进入服务列表&#xff0c;停止所有与Sql Server有关的服务&#xff0c;如下&#xff1a; 打开控制面板-卸载sqlserver所有相关软件&#xff1b; 删除SQL Server相关注册表&#…

【观察】中国软件行业进入“重构期”,看浪潮海岳如何“开新局”

众所周知&#xff0c;改开四十多来年&#xff0c;中国软件产业在经历了萌芽与低谷、摸索与转型后&#xff0c;逐步进入了快速发展期。特别是过去几年&#xff0c;在新的发展格局&#xff0c;信创替代的进程中&#xff0c;整个中国软件业更是加速进入了全新的“重构期”。 在此过…

Unity API详解——Quaternion类

Quaternion类又称四元数&#xff0c;由x、y、z和w这4个分量组成&#xff0c;属于struct类型。在Unity中&#xff0c;用Quaternion来存储和表示对象的旋转角度。Quaternion的变换比较复杂&#xff0c;对于GameObject一般的旋转及移动&#xff0c;可以用Transform中的相关方法实现…

CH32V307环境参数在线监测系统(一)

CH32V307环境参数在线监测系统是以CH32V307VCT6为核心&#xff0c;由ESP8266模块、DHT11温湿度传感器模块、TFT LCD显示屏组成。系统实物图如下所示&#xff1a; 系统功能主要有RTC实时时钟、WIFI网络授时、DHT11温度测量、温湿度数据实时上传到onenet平台、屏幕定时刷新等功能…

在Docker上安装和运行MySQL容器(纯步骤)

在Docker上安装和运行MySQL步骤 本文章只有操作步骤&#xff0c;没有原理解释&#xff0c;只是在学习当中提醒自己安装步骤。 第一步&#xff1a;从远程仓库拉取MySQL镜像 1.从远程仓库搜索mysql镜像 docker search mysql2.pull拉取镜像 这里我选择的是mysql的5.7版本 docker…

盘点 5 个 yyds 的 AI 绘画辅助工具

国外著名的 AI 作图工具 Midjourney、Stable Diiffusion 都可以根据你输入的指令生成一张图片。 如果你想输出高质量的图片&#xff0c;需要掌握一些 prompt 指令技巧。本文章便盘点了 5 个 GitHub 上的开源项目&#xff0c;引领你更好的上手 AI 作图。 本期推荐开源项目目录&…

软件杯龙源风电赛题培训!千万分钟数据和全流程基线等你来战

‍‍ “中国软件杯”大学生软件设计大赛是一项面向中国在校学生的公益性赛事&#xff0c;大赛由国家工业和信息化部、教育部、江苏省人民政府共同主办&#xff0c;是全国软件行业规格最高、最具影响力的国家级一类赛事。其中&#xff0c;作为重点赛题的龙源风电赛&#xff0c;上…

我们分析了9.12亿篇博客文章,得出了11条内容营销发现

我们分析了 9.12 亿篇博客文章&#xff0c;想要更好地了解目前的内容营销领域。 具体来说&#xff0c;我们研究了这些文章的内容格式、字数和标题等因素与社交媒体分享和反向链接的关联性。 在我们的数据合作伙伴BuzzSumo的帮助下&#xff0c;我们有了一些非常有趣的发现。 …

Dex-Net使用手册————GQCNN网络

1. Dex-Net Package GitHub - BerkeleyAutomation/dex-net &#x1f388;dex-net Python 包用于打开、读取和编写 HDF5 数据库的 3D 对象模型、平行下颚抓取和掌握鲁棒性指标。 &#x1f388;HDF5数据库还可用于生成大量数据集&#xff0c;将点云和抓取的元组与二进制抓取鲁棒…

【Python】【进阶篇】12、搭建Django开发环境

目录 搭建Django开发环境1. 安装Python解释器2. 安装MySQL数据库3. 安装Python包管理器4. 安装 Pycharm IDE 搭建Django开发环境 上一章我们介绍了 Django 框架的重要组件&#xff0c;常言道“工欲善其事&#xff0c;必先利其器”&#xff0c;在正式创建 Django 项目之前&…

AI绘画——Lora模型Niji-Expressive V2 launch(灵动优雅,张力尽显)

目录 Niji-Expressive V2 launch!! 灵动优雅&#xff0c;张力尽显 Vivid and elegant, full of tension 模型演示&#xff08;多图预警&#xff09; Niji-Expressive V2 launch!! 灵动优雅&#xff0c;张力尽显 本次升级泛用性&#xff0c;能在更多模型和采样方式上适用 …

《一文带你读懂:云原生时代业务监控》

点击上方蓝字关注我们&#xff01; 对业务来说&#xff0c;完备的应用健康性和数据指标的监控非常重要&#xff0c;通过采集准确的监控指标、配置合理的告警机制&#xff0c;我们能够提前或者尽早发现问题&#xff0c;并做出响应、解决问题&#xff0c;进而保证产品的稳定性&am…

vue性能优化之虚拟列表滚动

一、前言 前端的性能瓶颈那就是页面的卡顿&#xff0c;当然这种页面的卡顿包含了多种原因。 例如HTTP请求过多导致数据加载变慢&#xff0c;下载的静态文件非常大导致页面加载时间很长&#xff0c;js中一些算法响应的时间过长等。很多前端工程师都花费很多的精力在dom渲染上来…

Java 入门 - 语法基础

hello world public class Hello {public static void main(String[] args) {System.out.println("hello world");} } 复制代码 public: 是关键字&#xff1b;表示公开的class: 是关键字&#xff1b;用来定义类Hello: 是类名&#xff1b;大小写敏感&#xff1b;命名…

BFS算法专题

BFS算法专题 框架篇LeetCode 111. 二叉树的最小深度解题思路代码实现 LeetCode 752. 打开转盘锁解题思路代码实现 LeetCode 773. 滑动谜题解题思路代码实现 总结 不要纠结&#xff0c;干就完事了&#xff0c;熟练度很重要&#xff01;&#xff01;&#xff01;多练习&#xff0…

Vivado约束添加方法:一文全面解析IO和时序约束

FPGA开发离不开IO约束和时序约束&#xff0c;IO约束用于确定输入/输出端口的物理端口和电气特性&#xff0c;与芯片和电路设计有关。而时序约束则用于设定FPGA设计中的时序特性&#xff0c;以确保系统能够在预期时钟频率下正常运行。本文将介绍vivado中常见的设置约束的方法。 …

mysql 如何避免索引失效

案例演示 建表及初始化数据 CREATE TABLE staffs (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(24) NOT NULL DEFAULT ,age INT NOT NULL DEFAULT 0,pos VARCHAR(20) NOT NULL DEFAULT ,#职位add_time TIMESTAMP NOT NULL DEFAULT CURREN…