嵌入式Linux应用开发-驱动大全-同步与互斥①

嵌入式Linux应用开发-驱动大全-同步与互斥①

  • 第一章 同步与互斥①
    • 1.1 内联汇编
      • 1.1.1 C语言实现加法
      • 1.1.2 使用汇编函数实现加法
      • 1.1.3 内联汇编语法
      • 1.1.4 编写内联汇编实现加法
      • 1.1.5 earlyclobber的例子
    • 1.2 同步与互斥的失败例子
      • 1.2.1 失败例子1
      • 1.2.2 失败例子2
      • 1.2.3 失败例子3

第一章 同步与互斥①

在这里插入图片描述

1.1 内联汇编

要深入理解 Linux内核中的同步与互斥的实现,需要先了解一下内联汇编:在 C函数中使用汇编代码。
现代编译器已经足够优秀,大部分的 C代码转成汇编码后,效率都很高。但是有些特殊的算法需要我们手工优化,这时就需要手写汇编代码;或是有时需要调用特殊的汇编指令(比如使用 ldrex/strex实现互斥访问),这都涉及内联汇编。
实际上你完全可以不使用内联汇编,单独写一个遵守 ATPCS规则的汇编函数,让 C函数去调用它。但是在 C函数中写汇编代码,可以不用另外新建一个汇编文件,比较方便。
内联汇编的完整语法比较复杂,可以参考这 3篇文章:
① GNU C扩展汇编 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
② ARM GCC 内嵌(inline)汇编手册 http://blog.chinaunix.net/uid-20543672-id-3194385.html
③ C内联汇编 https://akaedu.github.io/book/ch19s05.html
这 3章文章写得细致而深入,也有些难以理解。你跟着我们的视频或文档,就可以掌握到足够的知识。 下面举 3个例子说明汇编函数、用 C函数中使用内联汇编的方法。

1.1.1 C语言实现加法

使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\01_c_code\main.c”:

01 #include <stdio.h> 
02 #include <stdlib.h> 
03 
04 int add(int a, int b) 
05 { 
06      return a+b; 
07 } 
08 
09 int main(int argc, char **argv) 
10 { 
11      int a; 
12      int b; 
13 
14      if (argc != 3) 
15      { 
16              printf("Usage: %s <val1> <val2>\n", argv[0]); 
17              return -1; 
18      } 
19 
20      a = (int)strtol(argv[1], NULL, 0); 
21      b = (int)strtol(argv[2], NULL, 0); 
22 
23      printf("%d + %d = %d\n", a, b, add(a, b)); 
24      return 0; 
25 } 

上面的 add函数代码最简单,但是对应的汇编也挺复杂:需要入栈、出栈等操作,效率不算高。看看test.dis:

266 00010404 <add>: 
267    10404:   b480            push    {r7} 
268    10406:   b083            sub     sp, #12 
269    10408:   af00            add     r7, sp, #0 
270    1040a:   6078            str     r0, [r7, #4] 
271    1040c:   6039            str     r1, [r7, #0] 
272    1040e:   687a            ldr     r2, [r7, #4] 
273    10410:   683b            ldr     r3, [r7, #0] 
274    10412:   4413            add     r3, r2                // 真正实现加法的只有这条指令 275    10414:   4618            mov     r0, r3 
276    10416:   370c            adds    r7, #12 
277    10418:   46bd            mov     sp, r7 
278    1041a:   f85d 7b04       ldr.w   r7, [sp], #4 
279    1041e:   4770            bx      lr 
280 

1.1.2 使用汇编函数实现加法

使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\02_assembly\add.S”:

01 .text            // 放在代码段 
02 .global  add     // 实现全局函数 add 
03 .thumb           // 使用 thumb指令, main.c默认使用 thumb指令, 所以这里也使用 thumb指令 
04 
05 add: 
06      add r0, r0, r1 
07      bx lr 
08 

根据 ATPCS规则,main函数调用 add(a, b)时,会把第一个参数存入 r0寄存器,把第二个参数存入 r1寄存器。
在上面第 06行里,把 r0、r1累加后,结果存入 r0:根据 ATPCS规则,r0用来保存返回值。
可以看到,这个 add函数连栈都没有使用,非常高效。
这只是一个很简单的例子,我们工作中并不使用汇编来进行“加法优化”,在计算量非常大的地方可以考虑单独编写汇编函数实现优化。

1.1.3 内联汇编语法

从上面例子可以看到,我们完全可以新建一个汇编文件,在 ATPCS规则之下编写代码,这样 C函数就可以直接调用汇编函数。
但是,需要新建汇编文件,有点麻烦。
使用内联汇编,可以在 C代码中内嵌汇编代码。
先看看内联汇编的语法。
在这里插入图片描述

内联汇编语法:
① asm
也可以写作“asm”,表示这是一段内联汇编。
② asm-qualifiers
有 3个取值:volatile、inline、goto。
volatile的意思是易变的、不稳定的,用来告诉编译器不要随便优化这段代码,否则可能出问题。比如汇编指令“mov r0, r0”,它把 r0的值复制到 r0,并没有实际做什么事情,你的本意可能是用这条指令来延时。编译器看到这指令后,可能就把它去掉了。加上 volatile的话,编译器就不会擅自优化。
其他 2个取值我们不关心,也比较难以理解,不讲。
③ AssemblerTemplate
汇编指令,用双引号包含起来,每条指令用“\n”分开,比如:

“mov  %0, %1\n” 
“add  %0, %1, %2\n” 

④ OutputOperands
输出操作数,内联汇编执行时,输出的结果保存在哪里。
格式如下,当有多个变量时,用逗号隔开:
[ [asmSymbolicName] ] constraint (cvariablename)
asmSymbolicName是符号名,随便取,也可以不写。 constraint表示约束,有如下常用取值:
在这里插入图片描述

constraint前还可以加上一些修饰字符,比如“=r”、“+r”、“=&r”,含义如下:
在这里插入图片描述
variablename:C语言的变量名。

示例 1如下:
[result] “=r” (sum)
它的意思是汇编代码中会通过某个寄存器把结果写入 sum变量。在汇编代码中可以使用“%[result]”来引用它。

示例 2如下:
“=r” (sum)
在汇编代码中可以使用“%0”、“%1”等来引用它,这些数值怎么确定后面再说。

⑤ InputOperands
输入操作数,内联汇编执行前,输入的数据保存在哪里。
格式如下,当有多个变量时,用逗号隔开:
[ [asmSymbolicName] ] constraint (cexpression)

asmSymbolicName是符号名,随便取,也可以不写。
constraint表示约束,参考上一小节,跟 OutputOperands类似。

cexpression:C语言的表达式。

示例 1如下:
[a_val]“r”(a), [b_val]“r”(b)
它的意思变量 a、b的值会放入某些寄存器。在汇编代码中可以使用%[a_val]、%[b_val]使用它们。

示例 2如下:
“r”(a), “r”(b) 它的意思变量 a、b的值会放入某些寄存器。在汇编代码中可以使用%0、%1等使用它们,这些数值后面再说。

⑥ Clobbers 在汇编代码中,对于“OutputOperands”所涉及的寄存器、内存,肯定是做了修改。但是汇编代码中,也许要修改的寄存器、内存会更多。比如在计算过程中可能要用到 r3保存临时结果,我们必须在“Clobbers”中声明 r3会被修改。 下面是一个例子: : “r0”, “r1”, “r2”, “r3”, “r4”, “r5”, “memory”

我们常用的是有“cc”、“memory”,意义如下:
在这里插入图片描述

1.1.4 编写内联汇编实现加法

使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\03_inline_assembly\main.c”:

04 int add(int a, int b) 
05 { 
06      int sum; 
07      __asm__ volatile ( 
08              "add %0, %1, %2" 
09              :"=r"(sum) 
10              :"r"(a), "r"(b) 
11              :"cc" 
12      ); 
13      return sum; 

在这里插入图片描述

所以第 08行代码就是:把第 1、2个操作数相加,存入第 0个操作数。也就是把 a、b相加,存入 sum。
还可以使用另一种写法,在 Linux内核中这种用法比较少见。
使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\03_inline_assembly\main2.c”:
在这里插入图片描述

1.1.5 earlyclobber的例子

OutputOperands的约束中经常可以看到“=&r”,其中的“&”表示 earlyclobber,它是最难理解的。有
一些输出操作数在汇编代码中早早就被写入了新值 A,在这之后,汇编代码才去读取某个输入操作数,这个输出操作数就被称为 earlyclobber(早早就被改了)。
这可能会有问题:假设早早写入的新值 A,写到了 r0寄存器;后面读输入操作数时得到数值 B,也可能写入 r0寄存器,这新值 A就被破坏了。
核心原因就在于输出操作数、输入操作数都用了同一个 r0寄存器。为什么要用同一个?因为编译器不知道你是 earlyclobber的,它以为是先读入了所有输入操作数,都处理完了,才去写输出操作数的。按这流程,没人来覆盖新值 A。
所以,如果汇编代码中某个输出操作数是 earlyclobber的,它的 constraint就要加上“&”,这就是告诉编译器:给我分配一个单独的寄存器,别为了省事跟输入操作数用同一个寄存器。
使用 GIT下载后,源码在“07_驱动大全\source\01_inline_assembly\04_earlyclobber\main.c”:
在这里插入图片描述

上面的代码中,输出操作数%0对应的寄存器是 r3,输入操作数%1对应的寄存器也是 r3。
第 8行更新了%0的值后,第 9行修改%1的值,由于%0、%1是同一个寄存器,所以%0的值也被修改了。 最终返回的累加值是错的,增加了 1,如下图所示:
在这里插入图片描述

怎么修改?在第 11行加“&”就可以了,这是告诉编译器,对于%0操作数它是 earlyclobber的,不能跟其他操作数共用寄存器,如下:
在这里插入图片描述

从右边的反汇编码可以知道,%0跟%1、%2使用不一样的寄存器,所以后面第 9、10行无法影响到%0的值。
程序运行结果如下图所示:
在这里插入图片描述

1.2 同步与互斥的失败例子

注意:本节在 GIT上没有源码。
一句话理解同步与互斥:我等你用完厕所,我再用厕所。
什么叫同步?就是条件不允许,我要等等。
什么是互斥?你我早起都要用厕所,谁先抢到谁先用,中途不被打扰。
同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥”操作可以使用“同步”来实现。我“等”你用完厕所,我再用厕所。这不就是用“同步”来实现“互斥”吗?
有时候看代码更容易理解,伪代码如下:

01 void  抢厕所(void) 
02 { 
03    if (有人在用) 我眯一会; 
04    用厕所; 
05    喂,醒醒,有人要用厕所吗; 
06 } 

假设有 A、B两人早起抢厕所,A先行一步占用了;B慢了一步,于是就眯一会;当 A用完后叫醒 B,B也就愉快地上厕所了。
在这个过程中,A、B是互斥地访问“厕所”,“厕所”被称之为临界资源。我们使用了“休眠-唤醒”的同步机制实现了“临界资源”的“互斥访问”。
上面是一个有“味道”的例子,回到程序员的世界,一个驱动程序同时只能有一个 APP使用,怎么实现?

1.2.1 失败例子1

01 static int valid = 1; 
02 
03 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file) 04 { 
05      if (!valid) 
06      { 
07              return -EBUSY; 
08      } 
09      else 
10      { 
11              valid = 0; 
12      } 
13 
14      return 0; //成功 
15 } 
16 
17 static int gpio_key_drv_close (struct inode *node, struct file *file) 
18 { 
19      valid = 1; 
20      return 0; 21 } 
22 

看第 5行,我们使用一个全局变量 valid来实现互斥访问。这有问题吗?很大概率没问题,但是并非万无一失。
注意:编写驱动程序时,要有系统的概念,程序 A调用驱动程序时,它可能被程序 B打断,程序 B也去调用这个驱动程序。
下图是一个例子,程序 A在调用驱动程序的中途被程序 B抢占了 CPU资源:
在这里插入图片描述

程序 A执行到第 11行之前,被程序 B抢占了,这时 valid尚未被改成 0;
程序 B调用 gpio_key_drv_open时,发现 valid等于 1,所以成功返回 0;
当程序 A继续从第 11行执行时,它最终也成功返回 0;
这样程序 A、B都成功打开了驱动程序。
注意:在内核态,程序 A不是主动去休眠、主动放弃 CPU资源;而是被优先级更高的程序 B抢占了,这种行为被称为“preempt”(抢占)。

1.2.2 失败例子2

上面的例子是不是第 5行到第 11行的时间跨度大长了?再优化一下程序行不行?代码如下:

01 static int valid = 1; 
02 
03 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file) 
04 { 
05      if (--valid) 
06      { 
07              valid++; 
08              return -EBUSY; 
09      } 
10      return 0; 
11 } 
12 
13 static int gpio_key_drv_close (struct inode *node, struct file *file) 
14 { 
15      valid = 1; 
16      return 0; 
17 } 
18 

第 5行先减 1再判断,这样可以更大概率地避免问题,但是还是不能确保万无一失。对数据的修改分为 3步:读出来、修改、写进去。请看下图:
在这里插入图片描述

进程 A在读出 valid时发现它是 1,减 1后为 0,这时 if不成立;但是修改后的值尚未写回内存; 假设这时被程序 B抢占,程序 B读出 valid仍为 1,减 1后为 0,这时 if不成立,最后成功返回; 轮到 A继续执行,它把 0值写到 valid变量,最后也成功返回。
这样程序 A、B都成功打开了驱动程序。

1.2.3 失败例子3

前面 2个例子,都是在修改 valid的过程中被别的进程抢占了,那么在修改 valid的时候直接关中断不就可以了吗?

01 static int valid = 1; 
02 
03 static ssize_t gpio_key_drv_open (struct inode *node, struct file *file) 
04 { 
05       unsigned long flags; 
06       raw_local_irq_save(flags); // 关中断 
07      if (--valid) 
08      { 
09              valid++; 
10              raw_local_irq_restore(flags);  // 恢复之前的状态 
11              return -EBUSY; 
12      } 
13       raw_local_irq_restore(flags);          // 恢复之前的状态 
14      return 0; 
15 } 
16 
17 static int gpio_key_drv_close (struct inode *node, struct file *file) 
18 { 
19      valid = 1; 
20      return 0; 
21 } 

第 06行直接关中断,这样别的线程、中断都不能来打扰本线程了,在它读取、修改 valid变量的过程中无人打扰。
没有问题了?

对于单 CPU核的系统上述代码是没问题的;但是对于 SMP系统,你只能关闭当前 CPU核的中断,别的CPU核还可以运行程序,它们也可以来执行这个函数,同样导致问题,如下图:
在这里插入图片描述

假设 CPU0上进程 A、CPU1上进程 B同时运行到上图中读出 valid的地方,它们同时发现 valid都是 1,减减后都等于 0,在第 07行判断条件都不成立,所以在第 14行都可以返回 0,都可以成功打开驱动。

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

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

相关文章

使用CrawlSpider爬取全站数据。

CrawpSpider和Spider的区别 CrawlSpider使用基于规则的方式来定义如何跟踪链接和提取数据。它支持定义规则来自动跟踪链接&#xff0c;并可以根据链接的特征来确定如何爬取和提取数据。CrawlSpider可以对多个页面进行同样的操作&#xff0c;所以可以爬取全站的数据。CrawlSpid…

【2023年11月第四版教材】第17章《干系人管理》(合集篇)

第17章《干系人管理》&#xff08;合集篇&#xff09; 1 章节内容2 管理基础3 管理过程3.1 管理的过程★★★ &#xff08;22上44&#xff09;3.2 管理ITTO汇总★★★ 4 过程1-识别干系人4.1 数据收集★★★4.3数据分析4.4 权力利益方格4.5 数据表现&#xff1a;干系人映射分析…

springmvc中DispatcherServlet关键对象

以下代码为 spring boot 2.7.15 中自带的 spring 5.3.29 RequestMappingInfo 请求方法相关信息封装&#xff0c;对应的信息解析在 RequestMappingHandlerMapping 的 createRequestMappingInfo() 中实现。 对于 RequestMapping 赋值的相关信息进行解析 protected RequestMappi…

零基础Linux_11(进程)进程程序替换+实现简单的shell

目录 1. 进程程序替换 1.1 程序替换原理 1.2 execl 接口 1.3 execv execlp execvp 1.4 exec 调各种程序 1.5 execle 接口 2. 实现简单的shell 2.1 打印提示和获取输入 2.2 拆开输入的命令和选项 2.3 创建进程和程序替换执行命令 2.4 内建命令实现路径切换 2.5 my…

创建GCP service账号并管理权限

列出当前GCP项目的所有service account 我们可以用gcloud 命令 gcloud iam service-accounts list gcloud iam service-accounts list DISPLAY NAME EMAIL DISABLED terraform …

自动混剪多段视频、合并音频、添加文案的技巧分享

在如今的社交媒体时代&#xff0c;视频的重要性越来越被人们所重视。许多人喜欢记录生活中的美好瞬间&#xff0c;并将其制作成视频分享给朋友和家人。然而&#xff0c;对于那些拍摄了大量视频的人来说&#xff0c;一个一个地进行剪辑和合并可能是一项令人头痛的任务。但是&…

1.6 计算机网络的性能

思维导图&#xff1a; 1.6.1 计算机网络的性能指标 前言&#xff1a; 我的理解&#xff1a; 这段前言主要介绍了关于计算机网络性能的两个方面的讨论。首先&#xff0c;计算机网络的性能可以通过一些重要的性能指标来衡量。但除了这些指标之外&#xff0c;还有一些非性能特征…

【计算机网络】因特网中的电子邮件

文章目录 简单邮件传送协议SMTP邮件访问协议POP3IMAPHTTP 参考资料 电子邮件为异步通信媒介 因特网电子邮件系统 电子邮件系统的三个构件&#xff1a;用户代理、邮件服务器、邮件发送和读取协议 用户代理 User Agent 即UA 电子邮件客户端软件&#xff0c;用户与电子邮件系统的接…

代码随想录算法训练营第五十六天 | 动态规划 part 14 | 1143.最长公共子序列、1035.不相交的线、53. 最大子序和(dp)

目录 1143.最长公共子序列思路代码 1035.不相交的线思路代码 53. 最大子序和&#xff08;dp&#xff09;思路代码 1143.最长公共子序列 Leetcode 思路 本题和718. 最长重复子数组 区别在于这里不要求是连续的了&#xff0c;但要有相对顺序&#xff0c;即&#xff1a;“ace” …

Moonbeam Ignite强势回归

参与Moonbeam上最新的流动性计划 还记得新一轮的流动性激励计划吗&#xff1f;Moonbeam Ignite社区活动带着超过300万枚GLMR奖励来啦&#xff01;体验新项目&#xff0c;顺便薅一把GLMR羊毛。 本次Moonbeam Ignite活动的参与项目均为第二批Moonbeam生态系统Grant资助提案中获…

语义分割 Semantic Segmentation

之前了解过语义分割的内容&#xff0c;感觉可以做好多东西&#xff0c;然后就抽空学习了一下&#xff0c;这里记录一下方便以后查阅&#xff0c;这篇文章可能也会随着学习的深入不断更新。 语义分割 Semantic Segmentation 一些基本概念几种语义分割算法Fully Convolutional Ne…

【单片机】16-LCD1602和12864显示器

1.LCD显示器相关背景 1.LCD简介 &#xff08;1&#xff09;显示器&#xff0c;常见显示器&#xff1a;电视&#xff0c;电脑 &#xff08;2&#xff09;LCD&#xff08;Liquid Crystal Display&#xff09;&#xff0c;液晶显示器&#xff0c;原理介绍 &#xff08;3&#xff…

【分布式计算】二、架构 Architectures

1.中心化架构&#xff08;Centralized Architectures&#xff09; 1.1.经典C/S模型 服务器&#xff1a;一个或多个进程提供服务 客户端&#xff1a;一个或多个进程使用服务 客户端和服务器可以在不同的机器上 客户端遵循请求/回复模型 1.2.传统三层视图 用户界面层&#x…

JUC中的设计模式

文章目录 1. 终止模式之两阶段终止模式 1. 终止模式之两阶段终止模式 需求&#xff1a;用一个线程每两秒检测***状态&#xff0c;当不想检测时&#xff0c;用另一个线程将其停止 在一个线程 T1 中如何“优雅”终止线程 T2&#xff1f;这里的【优雅】指的是给 T2 一个料理后事…

910数据结构(2019年真题)

算法设计题 问题1 有一种排序算法叫做计数排序。这种排序算法对一个待排序的表&#xff08;采用顺序存储&#xff09;进行排序&#xff0c;并将排序结果存放到另一个新的表中。必须注意的是&#xff0c;表中所有待排序的关键字互不相同&#xff0c;计数排序算法针对表中的每个…

【AI处理器组合】python实现-附ChatGPT解析

1.题目 AI处理器组合 知识点数组 时间限制:1s 空间限制: 256MB 限定语言:不限 题目描述: 某公司研发了一款高性能AI处理器。每台物理设备具备8颗AI处理器,编号分别为0、1、2、3、4、5、6、7。编号0-3的处理器处于同一个链路中,编号4-7的处理器处于另外一个链路中,不通链路中…

10.03

代码 #include <iostream>using namespace std; class cz { private:int num1; //实部int num2; //虚部 public:cz(){}cz(int a,int b):num1(a),num2(b){}cz(const cz &other):num1(other.num1),num2(other.num2){}~cz(){}const cz operator(const cz &othe…

HTML5 跨屏前端框架 Amaze UI

Amaze UI采用国际最前沿的“组件式开发”以及“移动优先”的设计理念&#xff0c;基于其丰富的组件&#xff0c;开发者可通过简单拼装即可快速构建出HTML5网页应用&#xff0c;上线仅半年&#xff0c;Amaze UI就成为了国内最流行的前端框架&#xff0c;目前在Github上收获Star数…

Java开源工具库使用之Lombok

文章目录 前言一、常用注解1.1 AllArgsConstructor/NoArgsConstructor/RequiredArgsConstructor1.2 Builder1.3 Data1.4 EqualsAndHashCode1.5 Getter/Setter1.6 Slf4j/Log4j/Log4j2/Log1.7 ToString 二、踩坑2.1 Getter/Setter 方法名不一样2.2 Builder 不会生成无参构造方法2…

互联网Java工程师面试题·ZooKeeper 篇·第一弹

目录 1. ZooKeeper 面试题&#xff1f; 2. ZooKeeper 提供了什么&#xff1f; 3. Zookeeper 文件系统 4. ZAB 协议&#xff1f; 5. 四种类型的数据节点 Znode 6. Zookeeper Watcher 机制 -- 数据变更通知 7. 客户端注册 Watcher 实现 8. 服务端处理 Watcher 实现 9. 客…