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。

五、总结

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


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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA学习-全网最详细

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

表格内日期比较计算

需求&#xff1a;在表格中新增数据&#xff0c;计算开始日期中最早的和结束日期中最晚的&#xff0c;回显到下方。 <el-formref"formRef":model"ruleForm":rules"rules"style"margin-top: 20px;"label-position"top">…

Golang 的 GMP:并发编程的艺术

前言 在 Golang 的并发编程中&#xff0c;GMP 是一个重要的概念&#xff0c;它代表了 Goroutine、M&#xff08;线程&#xff09;和 P&#xff08;调度器&#xff09;。这个强大的三位一体的并发模型使得 Golang 在处理并发任务时非常高效和灵活。通过 GMP 的组合&#xff0c;…

美轮美奂,尽在眼前——Aerial for Mac 高清鸟瞰屏保程序

想要让您的 Mac 屏幕焕发别样风采&#xff1f;那么&#xff0c;Aerial for Mac 高清鸟瞰屏保程序一定不容错过。这款应用程序将为您带来最优质的高清鸟瞰视频壁纸&#xff0c;让您的屏幕焕发无限活力和美感。 Aerial for Mac 高清鸟瞰屏保程序是一款专为 Mac 设计的屏幕保护程…

2023-9-25 货仓选址

题目链接&#xff1a;货仓选址 #include <iostream> #include <algorithm>using namespace std;const int N 100010;int n; int a[N];int main() {cin >> n;for(int i 0; i < n; i ) cin >> a[i];sort(a, a n);int res 0;for(int i 0; i < …

翻译像机翻?4点教会你ChatGPT高质量翻译

如果完全靠自己的英文和中文水平&#xff0c;要达到这样的翻译速度和质量那是不太可能的&#xff0c;主要还是得益于ChatGPT的帮助&#xff0c;首先用GPT-4的API粗翻&#xff0c;再用ChatGPT Plus精翻。很多人都用过ChatGPT翻译&#xff0c;但翻译出来的结果比起Google翻译和De…

C#通过重写Panel改变边框颜色与宽度的方法

在C#中,Panel控件是一个容器控件,用于在窗体或用户控件中创建一个可用于容纳其他控件的面板。Panel提供了一种将相关控件组合在一起并进行布局的方式。以下是Panel控件的详细使用方法: 在窗体上放置 Panel 控件: 在 Visual Studio 的窗体设计器中,从工具箱中拖动并放置一…

离散小波变换(概念与应用)

目录 概念光伏功率预测中,如何用离散小波变换提取高频特征概念 为您简单地绘制一些示意图来描述离散小波变换的基本概念。但请注意,这只是一个简化的示意图,可能不能完全捕捉到所有的细节和特性。 首先,我将为您绘制一个简单的小波函数和尺度函数的图像。然后,我会提供一…

链表oj题1(Leetcode)——移除链表元素,反转链表,链表的中间节点,

链表OJ 一&#xff0c;移除链表元素1.1分析1.2代码 二&#xff0c;找到链表的中间节点2.1分析2.2代码 三&#xff0c;反转链表3.1分析3.2代码 四&#xff0c;找到链表中倒数第k个节点4.1分析4.2代码 一&#xff0c;移除链表元素 移除链表元素 1.1分析 这里的删除要分成两种…

Android Jetpack组件架构 :LiveData的使用和原理

Android Jetpack组件架构&#xff1a; LiveDate的使用和原理 导言 继Lifecycle组件之后我们接下来要介绍的就是LiveDate组件&#xff0c;所谓LiveDate字面意思上就是有声明的数据&#xff0c;当数据有改动时该组件可以感知到这个操作并将该事件通知到其观察者&#xff0c;这样…

屏幕分辨率dpi解析(adb 调试查看)

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 ro.sf.lcd_density属性指定了这个机型使用的dpi是多少&#xff0c;dpi全称是dots per inch&#xff0c;对角线每英寸的像素点的个数。 密度 ldpi mdpi hdpi xhdpi xxhdpi 分辨率 240x320 320x480 480x800 7…

2023-9-25 耍杂技的牛

题目链接&#xff1a;耍杂技的牛 #include <iostream> #include <algorithm>using namespace std;typedef pair<int, int> PII;const int N 50010;int n; PII cow[N];int main() {cin >> n;for(int i 0; i < n; i ){int w, s;cin >> w >…

Android 10.0 系统开启和关闭黑白模式主题功能实现

1. 概述 在10.0的rom系统开发定制化中,在系统SystemUI的下拉状态栏中,产品开发功能需求要求添加黑白模式功能开关的功能,就是打开黑白模式,系统颜色就会变成黑白颜色, 关闭黑白模式开关系统就会变成彩色模式,所以就需要了解下系统是怎么设置黑白模式和彩色模式的,然后添…

接口自动化测试之Mock

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 1.Mock实现原理和实现机制 在某些时候&#xff0c;后端在开发接口的时候&#xff0c;处理逻辑非常复杂&a…

Android跨进程通信:Binder机制原理

目录 1. Binder到底是什么&#xff1f; 2. 知识储备 2.1 进程空间划分 2.2 进程隔离 & 跨进程通信&#xff08; IPC &#xff09; 2.3 内存映射 2.3.1 作用 2.3.2 实现过程 2.3.3 特点 2.3.4 应用场景 2.3.5 实例讲解 ① 文件读 / 写操作 ② 跨进程通信 3. Bi…

C#中的(++)和(--)运算符

目录 背景: 的前加 效果展示:​ 的后加 效果展示 :​ 总结: 背景: 自增和自减运算符存在于C/C/C#/Java等高级语言中&#xff0c;它的作用是在运算结束前(前置自增自减运算符 )或后(后置自增自减运算符 )将 变量的值加(或减)1。 在C#中&#xff0c;和--是自增和自减运…

基于springboot小区疫情防控系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

前端Vue3+element-plus表单输入框实现Cron表达式校验

页面如下&#xff1a; 本来想手写正则表达式校验&#xff0c;结果发现很麻烦&#xff0c;cron表达式组成如下&#xff1a; 开发使用框架为vue3element-plus&#xff0c;于是选择cron-validator依赖。使用步骤如下&#xff1a; 1、通过npm install cron-validator命令安装&…

面经分享 | 某康安全开发工程师

本文由掌控安全学院 - sbhglqy 投稿 一、反射型XSS跟DOM型XSS的最大区别 DOM型xss和别的xss最大的区别就是它不经过服务器&#xff0c;仅仅是通过网页本身的JavaScript进行渲染触发的。 二、Oracle数据库了解多吗 平常用的多的是MySQL数据库&#xff0c;像Oracle数据库也有…

想要精通算法和SQL的成长之路 - 最长回文子序列

想要精通算法和SQL的成长之路 - 最长回文子序列 前言一. 最长回文子序列 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 最长回文子序列 原题链接 首先&#xff0c;我们看下动态规划方程的定义&#xff0c;我们用dp[i][j] 来代表&#xff1a;字符串s在下标区间为[i,j]之间…