当前位置: 首页 > news >正文

Linux学习第19天:Linux并发与竞争实例: 没有规矩不成方圆

Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长


        先说点题外话,上周参加行业年会,停更了一周。接下来的周五就要开启国庆中秋双节模式,所以有的时候,尤其是工作以后想要好好的学习点东西,是多么的难。并且学习必须是一个连续的过程。大家可能会有这样的体会,本来做研发的过程中,突然临时领导分配其他的工作,并且要立马去做的。当集中几天精力完成了领导的任务再回头去看原来手里的工作时,发现有很多东西都记不太清了。这也是我很头疼的一个问题,大家有好的建议或是处理方法可以评论区给我留言。


        本篇笔记主要是学习并发和竞争的实例,主要内容包括原子操作、自旋锁操作、信号量和互斥体

        本笔记的思维导图很简单,如下:

一、原子操作

        本节要实现的功能是利用原子操作来实现对LED设备的互斥操作。

32 /* gpioled 设备结构体 */
33 struct gpioled_dev{
34 dev_t devid; /* 设备号 */
35 struct cdev cdev; /* cdev */
36 struct class *class; /* 类 */
37 struct device *device; /* 设备 */
38 int major; /* 主设备号 */
39 int minor; /* 次设备号 */
40 struct device_node *nd; /* 设备节点 */
41 int led_gpio; /* led 所使用的 GPIO 编号 */
42 atomic_t lock; /* 原子变量 */
43 };

        第 42 行,原子变量 lock,用来实现一次只能允许一个应用访问 LED 灯, led_init 驱动入口函数会将 lock 的值设置为 1。

54 static int led_open(struct inode *inode, struct file *filp)
55 {
56 /* 通过判断原子变量的值来检查 LED 有没有被别的应用使用 */
57 if (!atomic_dec_and_test(&gpioled.lock)) {
58 atomic_inc(&gpioled.lock);/* 小于 0 的话就加 1,使其原子变量等于 0 */
59 return -EBUSY; /* LED 被使用,返回忙 */
60 }
61
62 filp->private_data = &gpioled; /* 设置私有数据 */
63 return 0;
64 }

        第 57~60 行,每次调用 open 函数打开驱动设备的时候先申请 lock,如果申请成功的话就表
示 LED灯还没有被其他的应用使用,如果申请失败就表示 LED灯正在被其他的应用程序使用。
        每次打开驱动设备的时候先使用 atomic_dec_and_test 函数将 lock 减 1,如果 atomic_dec_and_test函数返回值为真就表示 lock 当前值为 0,说明设备可以使用。如果 atomic_dec_and_test 函数返回值为假,就表示 lock 当前值为负数(lock 值默认是 1), lock 值为负数的可能性只有一个,那就是其他设备正在使用 LED。其他设备正在使用 LED 灯,那么就只能退出了,在退出之前调用函数 atomic_inc 将 lock 加 1,因为此时 lock 的值被减成了负数,必须要对其加 1,将 lock 的值变为 0。

        上面一段说的可能有点绕,总的意思就是要想要先申请

111 * @description : 关闭/释放设备
112 * @param – filp : 要关闭的设备文件(文件描述符)
113 * @return : 0 成功;其他 失败
114 */
115 static int led_release(struct inode *inode, struct file *filp)
116 {
117 struct gpioled_dev *dev = filp->private_data;
118
119 /* 关闭驱动文件的时候释放原子变量 */
120 atomic_inc(&dev->lock);
121 return 0;
122 }

        第 120 行, LED 灯使用完毕,应用程序调用 close 函数关闭的驱动文件, led_release 函数执行,调用 atomic_inc 释放 lcok,也就是将 lock 加 1。

142 /* 初始化原子变量 */
143 atomic_set(&gpioled.lock, 1); /* 原子变量初始值为 1 */

        第 143 行,初始化原子变量 lock,初始值设置为 1,这样每次就只允许一个应用使用 LED
灯。

        在测试的APP中有如下程序:

62 /* 模拟占用 25S LED */
63 while(1) {
64 sleep(5);
65 cnt++;
66 printf("App running times:%d\r\n", cnt);
67 if(cnt >= 5) break;
68 }

        第 63~68 行的模拟占用 25 秒 LED 的代码。测试 APP 在获取到 LED 灯驱动的使用权以后会使用 25S,在使用的这段时间如果有其他的应用也去获取 LED 灯使用权的话肯定会失败!

二、自旋锁

        自旋锁保护的临界区要尽可能的短。

        考虑驱动的兼容性,合理的选择 API 函数。

33 /* gpioled 设备结构体 */
34 struct gpioled_dev{
35 dev_t devid; /* 设备号 */
36 struct cdev cdev; /* cdev */
37 struct class *class; /* 类 */
38 struct device *device; /* 设备 */
39 int major; /* 主设备号 */
40 int minor; /* 次设备号 */
41 struct device_node *nd; /* 设备节点 */
42 int led_gpio; /* led 所使用的 GPIO 编号 */
43 int dev_stats; /* 设备状态, 0,设备未使用;>0,设备已经被使用 */
44 spinlock_t lock; /* 自旋锁 */
45 };

        第 43 行, dev_stats 表示设备状态,如果为 0 的话表示设备还没有被使用,如果大于 0 的
话就表示设备已经被使用了。第 44 行,定义自旋锁变量 lock。

56 static int led_open(struct inode *inode, struct file *filp)
57 {
58 unsigned long flags;
59 filp->private_data = &gpioled; /* 设置私有数据 */
60
61 spin_lock_irqsave(&gpioled.lock, flags); /* 上锁 */
62 if (gpioled.dev_stats) { /* 如果设备被使用了 */
63 spin_unlock_irqrestore(&gpioled.lock, flags); /* 解锁 */
64 return -EBUSY;
65 }
66 gpioled.dev_stats++; /* 如果设备没有打开,那么就标记已经打开了 */
67 spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */
68
69 return 0;
70 }

        第 61~67 行,使用自旋锁实现对设备的互斥访问,第 61 行调用 spin_lock_irqsave 函数获
取锁,为了考虑到驱动兼容性,这里并没有使用 spin_lock 函数来获取锁。第 62 行判断
dev_stats 是否大于 0,如果是的话表示设备已经被使用了,那么就调用 spin_unlock_irqrestore
函数释放锁,并且返回-EBUSY。如果设备没有被使用的话就在第 66 行将 dev_stats 加 1,表
示设备要被使用了,然后调用 spin_unlock_irqrestore 函数释放锁。自旋锁的工作就是保护
dev_stats 变量, 真正实现对设备互斥访问的是 dev_stats。

121 static int led_release(struct inode *inode, struct file *filp)
122 {
123 unsigned long flags;
124 struct gpioled_dev *dev = filp->private_data;
125
126 /* 关闭驱动文件的时候将 dev_stats 减 1 */
127 spin_lock_irqsave(&dev->lock, flags); /* 上锁 */
128 if (dev->dev_stats) {
129 dev->dev_stats--;
130 }
131 spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */
132
133 return 0;
134 }

        第 126~131 行,在 release 函数中将 dev_stats 减 1,表示设备被释放了,可以被其他的应用程序使用。将 dev_stats 减 1 的时候需要自旋锁对其进行保护。

150 static int __init led_init(void)
151 {
152 int ret = 0;
153
154 /* 初始化自旋锁 */
155 spin_lock_init(&gpioled.lock);
......
212 return 0;
213 }

        第 155 行,在驱动入口函数 led_init 中调用 spin_lock_init 函数初始化自旋锁。


三、信号量

        使用信号量实现了一次只能有一个应用程序访问 LED 灯,信号量可以导致休眠,因此信号量保护的临界区没有运行时间限制,可以在驱动的 open 函数申请信号量,然后在release 函数中释放信号量。但是信号量不能用在中断中。

14 #include <linux/semaphore.h>
33 /* gpioled 设备结构体 */
34 struct gpioled_dev{
35 dev_t devid; /* 设备号 */
36 struct cdev cdev; /* cdev */
37 struct class *class; /* 类 */
38 struct device *device; /* 设备 */
39 int major; /* 主设备号 */
40 int minor; /* 次设备号 */
41 struct device_node *nd; /* 设备节点 */
42 int led_gpio; /* led 所使用的 GPIO 编号 */
43 struct semaphore sem; /* 信号量 */
44 };

        第 43 行,在设备结构体中添加一个信号量成员变量 sem。

48 /*
49 * @description : 打开设备
50 * @param – inode : 传递给驱动的 inode
51 * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
52 * 一般在 open 的时候将 private_data 指向设备结构体。
53 * @return : 0 成功;其他 失败
54 */
55 static int led_open(struct inode *inode, struct file *filp)
56 {
57 filp->private_data = &gpioled; /* 设置私有数据 */
58
59 /* 获取信号量,进入休眠状态的进程可以被信号打断 */
60 if (down_interruptible(&gpioled.sem)) {
61 return -ERESTARTSYS;
62 }
63 #if 0
64 down(&gpioled.sem); /* 不能被信号打断 */
65 #endif
66
67 return 0;
68 }

        第 60~65行,在 open函数中申请信号量,可以使用 down 函数,也可以使用 down_interruptible函数。如果信号量值大于等于 1 就表示可用,那么应用程序就会开始使用 LED 灯。如果信号量值为 0 就表示应用程序不能使用 LED 灯,此时应用程序就会进入到休眠状态。等到信号量值大于 1 的时候应用程序就会唤醒,申请信号量,获取 LED 灯使用权。

114 /*
115 * @description : 关闭/释放设备
116 * @param – filp : 要关闭的设备文件(文件描述符)
117 * @return : 0 成功;其他 失败
118 */
119 static int led_release(struct inode *inode, struct file *filp)
120 {
121 struct gpioled_dev *dev = filp->private_data;
122
123 up(&dev->sem); /* 释放信号量,信号量值加 1 */
124
125 return 0;
126 }

        第 123 行,在 release 函数中调用 up 函数释放信号量,这样其他因为没有得到信号量而进
入休眠状态的应用程序就会唤醒,获取信号量。

142 static int __init led_init(void)
143 {
144 int ret = 0;
145
146 /* 初始化信号量 */
147 sema_init(&gpioled.sem, 1);
......
204 return 0;
205 }

        第 147 行,在驱动入口函数中调用 sema_init 函数初始化信号量 sem 的值为 1,相当于 sem
是个二值信号量。

四、互斥体

33 /* gpioled 设备结构体 */
34 struct gpioled_dev{
35 dev_t devid; /* 设备号 */
36 struct cdev cdev; /* cdev */
37 struct class *class; /* 类 */
38 struct device *device; /* 设备 */
39 int major; /* 主设备号 */
40 int minor; /* 次设备号 */
41 struct device_node *nd; /* 设备节点 */
42 int led_gpio; /* led 所使用的 GPIO 编号*/
43 struct mutex lock; /* 互斥体 */
44 };

        第 43 行,定义互斥体 lock。

55 static int led_open(struct inode *inode, struct file *filp)
56 {
57 filp->private_data = &gpioled; /* 设置私有数据 */
58
59 /* 获取互斥体,可以被信号打断 */
60 if (mutex_lock_interruptible(&gpioled.lock)) {
61 return -ERESTARTSYS;
62 }
63 #if 0
64 mutex_lock(&gpioled.lock); /* 不能被信号打断 */
65 #endif
66
67 return 0;
68 }

        第 60~65 行,在 open 函数中调用 mutex_lock_interruptible 或者 mutex_lock 获取 mutex,成功的话就表示可以使用 LED 灯,失败的话就会进入休眠状态,和信号量一样。

119 static int led_release(struct inode *inode, struct file *filp)
120 {
121 struct gpioled_dev *dev = filp->private_data;
122
123 /* 释放互斥锁 */
124 mutex_unlock(&dev->lock);
125
126 return 0;
127 }

        第 124 行,在 release 函数中调用 mutex_unlock 函数释放 mutex,这样其他应用程序就可以
获取 mutex 了。

143 static int __init led_init(void)
144 {
145 int ret = 0;
146
147 /* 初始化互斥体 */
148 mutex_init(&gpioled.lock);
......
205 return 0;
206 }

        第 148 行,在驱动入口函数中调用 mutex_init 初始化 mutex。

五、总结

        本片笔记主要是巩固上次课学习的并发和竞争相关内容,主要包括原子操作、自旋锁、信号量和互斥体。


本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

相关文章:

Linux学习第19天:Linux并发与竞争实例: 没有规矩不成方圆

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 先说点题外话&#xff0c;上周参加行业年会&#xff0c;停更了一周。接下来的周五就要开启国庆中秋双节模式&#xff0c;所以有的时候&#xff0c;尤其是工作以后…...

Unity添加自定义菜单按钮

如果你想在Unity编辑器中添加自定义菜单按钮&#xff0c;你可以使用Unity的MenuSystem API。这是一个简单的示例&#xff1a; 首先需要引用using UnityEditor; using UnityEngine; using UnityEditor; 两个命名空间 然后在方法前添加 [MenuItem("原菜单名/自定义名…...

PHP8的类与对象的基本操作之类的实例化-PHP8知识详解

定义完类和方法后&#xff0c;并不是真正创建一个对象。类和对象可以描述为如下关系。类用来描述具有相同数据结构和特征的“一组对象”&#xff0c;“类”是“对象”的抽象&#xff0c;而“对象”是“类”的具体实例&#xff0c;即一个类中的对象具有相同的“型”&#xff0c;…...

C/S架构学习之TCP服务器

TCP服务器的实现流程&#xff1a;一、创建套接字&#xff08;socket函数&#xff09;&#xff1a;通信域选择IPV4网络协议、套接字类型选择流式&#xff1b; int sockfd socket(AF_INET,SOCK_STREAM,0); //通信域选择IPV4、套接字类型选择流式二、填充服务器的网络信息结构体&…...

基于微信小程序的线上教育课程付费商城(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…...

Linux基础指令(五)

目录 前言1. 打包和压缩1.1 是什么1.2 为什么1.3 怎么办&#xff1f; 2. zip & unzip3. tar 指令结语&#xff1a; 前言 欢迎各位伙伴来到学习 Linux 指令的 第五天&#xff01;&#xff01;&#xff01; 在上一篇文章 Linux基本指令(四) 当中&#xff0c;我们学习了 fin…...

C语言结构体的一些鲜为人知的小秘密

目录 一、结构体内存对齐规则&#xff1a; 1.1范例 1.2结构体内存对齐规则 1.3自定义默认对齐数 二、位段 2.1什么是位段 2.2位段的内存分配 2.3位段的不足 三、枚举和联合体 3.1枚举 3.1.1枚举类型的定义 3.1.2枚举类型的使用 3.2联合体 3.2.1联合体的定义 3.…...

kubernetes问题(一)-探究Pod被驱逐的原因及解决方法

1 k8s evicted是什么 k8s evicted是Kubernetes中的一个组件&#xff0c;主要用于处理Pod驱逐的情况。在Kubernetes中&#xff0c;当Node节点资源不够用时&#xff0c;为了保证整个集群的运行稳定&#xff0c;会按照一定的优先级和策略将其中的Pod驱逐出去。这时就需要一个组件…...

论文速览【序列模型 seq2seq】—— 【Ptr-Net】Pointer Networks

标题&#xff1a;Pointer Networks文章链接&#xff1a;Pointer Networks参考代码&#xff08;非官方&#xff09;&#xff1a;keon/pointer-networks发表&#xff1a;NIPS 2015领域&#xff1a;序列模型&#xff08;RNN seq2seq&#xff09;改进 / 深度学习解决组合优化问题【…...

Denoising diffusion implicit models 阅读笔记

Denoising diffusion probabilistic models (DDPMs)从马尔科夫链中采样生成样本&#xff0c;需要迭代多次&#xff0c;速度较慢。Denoising diffusion implicit models (DDIMs)的提出是为了加速采样过程&#xff0c;减少迭代的次数&#xff0c;并且要求DDIM可以复用DDPM训练的网…...

【Java 基础篇】Executors工厂类详解

在多线程编程中&#xff0c;线程池是一项重要的工具&#xff0c;它可以有效地管理和控制线程的生命周期&#xff0c;提高程序的性能和可维护性。Java提供了java.util.concurrent包来支持线程池的创建和管理&#xff0c;而Executors工厂类是其中的一部分&#xff0c;它提供了一些…...

SpringBoot MongoDB操作封装

1.引入Jar包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency> 2.MongoDbHelper操作 /*** MongoDB Operation class* author Mr.Li* date 2022-12-05*…...

PyTorch 模型性能分析和优化 — 第 1 部分

一、说明 这篇文章的重点将是GPU上的PyTorch培训。更具体地说&#xff0c;我们将专注于 PyTorch 的内置性能分析器 PyTorch Profiler&#xff0c;以及查看其结果的方法之一&#xff0c;即 PyTorch Profiler TensorBoard 插件。 二、深度框架 训练深度学习模型&#xff0c;尤其是…...

Unity3D 简易音频管理器

依赖于Addressable 依赖于单例模板&#xff1a;传送门 using System.Collections.Generic; using System.Security.Cryptography; using System; using UnityEngine; using UnityEngine.AddressableAssets;namespace EasyAVG {public class AudioManager : MonoSingleton<…...

【李沐深度学习笔记】线性回归

课程地址和说明 线性回归p1 本系列文章是我学习李沐老师深度学习系列课程的学习笔记&#xff0c;可能会对李沐老师上课没讲到的进行补充。 线性回归 如何在美国买房&#xff08;经典买房预测问题&#xff09; 一个简化的模型 线性模型 其中&#xff0c; x → [ x 1 , x 2 ,…...

微信收款码费率0.38太坑了

作为一个有多年运营经验的商家&#xff0c;我本人在申请收款功能时曾经走过了不少弯路。我找遍了市面上的知名的支付公司&#xff0c;但了解到的收款手续费率通常都在0.6左右&#xff0c;最低也只能降到0.38。这个过程吃过不少苦头。毕竟&#xff0c;收款功能是我们商家的命脉&…...

【学习笔记】CF1103D Professional layer

首先分析不出啥性质&#xff0c;所以肯定是暴力优化&#x1f605; 常见的暴力优化手段有均摊&#xff0c;剪枝&#xff0c;数据范围分治&#xff08;points&#xff09;&#xff0c;答案值域分析之类的。 比较经典的题目是 CF1870E Another MEX Problem&#xff0c;可以用剪枝…...

vue之Pinia

定义 Store | Pinia 开发文档 1.什么是Pinaia Pinia 是 Vue 的专属状态管理库&#xff0c;它允许你跨组件或页面共享状态。 2.理解Pinaia核心概念 定义Store 在深入研究核心概念之前&#xff0c;我们得知道 Store 是用 defineStore() 定义的&#xff0c;它的第一个参数要求是一…...

antd-vue 级联选择器默认值不生效解决方案

一、业务场景&#xff1a; 最近在使用Vue框架和antd-vue组件库的时候&#xff0c;发现在做编辑回显时** 级联选择器** 组件的默认值不生效。为了大家后面遇到和我一样的问题&#xff0c;给大家分享一下 二、bug信息&#xff1a; 三、问题原因&#xff1a; 确定不了唯一的值&a…...

分享53个Python源码源代码总有一个是你想要的

分享53个Python源码源代码总有一个是你想要的 链接&#xff1a;https://pan.baidu.com/s/1ew3w2_DXlSBrK7Mybx3Ttg?pwd8888 提取码&#xff1a;8888 项目名称 100-Python ControlXiaomiDevices DRF-ADMIN 后台管理系统 FishC-Python3小甲鱼 Flask框架的api项目脚手架 …...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...