所谓原子操作,就是不可中断的一个或一系列操作。 硬件级的原子操作:在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作都可以认为是原子操作,因为中断只发生在指令边缘。在多处理器结构中(SymmetricMultiProcessor)就不同了,由于系统中有多个处理器独立运行,即使能在单条指令中完成的操作也有可能受到干扰。在X86平台生,CPU提供了在指令执行期间对总线加锁的手段。CPU上有一根引线HLOCKpin连到北桥,如果汇编语言的程序中在一条指令前面加上前缀LOCK,经过汇编以后的机器代码就使CPU在执行这条指令的时候把HLOCKpin的电位拉低,持续到这条指令结束时放开,从而把总线锁住,这样同一总线上别的CPU就暂时不能通过总线访问内存了,保证了这条指令在多处理器环境中的原子性。对于其他平台的CPU,实现各不相同,有的是通过关中断来实现原子操作(sparc),有的通过CMPXCHG系列的指令来实现原子操作(IA64)。本文主要探讨X86平台下原子操作的实现。 一。整型原子操作 详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。 原子操作通常针对int或bit类型的数据,但是Linux并不能直接对int进行原子操作,而只能通过atomict的数据结构来进行。目前了解到的原因有两个。定义于include分为定义,获取,加减,测试,返回。voidatomicset(atomictv,inti);设置原子变量v的值为iatomictvATOMICINIT(0);定义原子变量v,并初始化为0;atomicread(atomictv);返回原子变量v的值;voidatomicadd(inti,atomictv);原子变量v增加i;voidatomicsub(inti,atomictv);voidatomicinc(atomictv);原子变量增加1;voidatomicdec(atomictv);intatomicincandtest(atomictv);先自增1,然后测试其值是否为0,若为0,则返回true,否则返回false;intatomicdecandtest(atomictv);intatomicsubandtest(inti,atomictv);先减i,然后测试其值是否为0,若为0,则返回true,否则返回false;注意:只有自加,没有加操作intatomicaddreturn(inti,atomictv);v的值加i后返回新的值;intatomicsubreturn(inti,atomictv);intatomicincreturn(atomictv);v的值自增1后返回新的值;intatomicdecreturn(atomictv); 二。原子位操作的实现 详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。定义于include分为设置,清除,改变,测试voidsetbit(intnr,volatilevoidaddr);设置地址addr的第nr位,所谓设置位,就是把位写为1;voidclearbit(intnr,volatilevoidaddr);清除地址addr的第nr位,所谓清除位,就是把位写为0;voidchangebit(intnr,volatilevoidaddr);把地址addr的第nr位反转;inttestbit(intnr,volatilevoidaddr);返回地址addr的第nr位;inttestandsetbit(intnr,volatilevoidaddr);测试并设置位;若addr的第nr位非0,则返回true;若addr的第nr位为0,则返回false;inttestandclearbit(intnr,volatilevoidaddr);测试并清除位;inttestandchangebit(intnr,volatilevoidaddr);测试并反转位;上述操作等同于先执行testbit(nr,voidaddr)然后在执行xxxbit(nr,voidaddr)举个简单例子: 详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。 为了实现设备只能被一个进程打开,从而避免竞态的出现staticatomictscullavailableATOMICINIT(1);initatomic在scullopen函数和scullclose函数中:intscullopen(structinodeinode,structfilefilp){structsculldevdev;deviceinformationdevcontainerof(inodeicdev,structsculldev,cdev);filpprivatedatadev;forothermethodsif(!atomicdecandtest(scullavailable)){atomicinc(scullavailable);returnEBUSY;}return0;success}intscullrelease(structinodeinode,structfilefilp){atomicinc(scullavailable);return0;} 假设原子变量的底层实现是由一个汇编指令实现的,这个原子性必然有保障。但是如果原子变量的实现是由多条指令组合而成的,那么对于SMP和中断的介入会不会有什么影响呢?我在看ARM的原子变量操作实现的时候,发现其是由多条汇编指令(ldrexstrex)实现的。在参考了别的书籍和资料后,发现大部分书中对这两条指令的描诉都是说他们是支持在SMP系统中实现多核共享内存的互斥访问。但在UP系统中使用,如果ldrexstrex和之间发生了中断,并在中断中也用ldrexstrex操作了同一个原子变量会不会有问题呢?就这个问题,我认真看了一下内核的ARM原子变量源码和ARM官方对于ldrexstrex的功能解释,总结如下 一、ARM构架的原子变量实现结构 对于ARM构架的原子变量实现源码位于:archarmincludeasmatomic。h 其主要的实现代码分为ARMv6以上(含v6)构架的实现和ARMv6版本以下的实现。 该文件的主要结构如下: 详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。ifLINUXARMARCH6。。。。。。(通过ldrexstrex指令的汇编实现)elseARMARCH6ifdefCONFIGSMPerrorSMPnotsupportedonpreARMv6CPUsendif。。。。。。(通过关闭CPU中断的C语言实现)endifLINUXARMARCH。。。。。。ifndefCONFIGGENERICATOMIC64。。。。。。(通过ldrexdstrexd指令的汇编实现的64bit原子变量的访问)else!CONFIGGENERICATOMIC64includeendifinclude 这样的安排是依据ARM核心指令集版本的实现来做的: (1)在ARMv6以上(含v6)构架有了多核的CPU,为了在多核之间同步数据和控制并发,ARM在内存访问上增加了独占监测(Exclusivemonitors)机制(一种简单的状态机),并增加了相关的ldrexstrex指令。请先阅读以下参考资料(关键在于理解localmonitor和Globalmonitor): 1。2。2。Exclusivemonitors 4。2。12。LDREX和STREX 2)对于ARMv6以前的构架不可能有多核CPU,所以对于变量的原子访问只需要关闭本CPU中断即可保证原子性。对于(2),非常好理解。 但是(1)情况,我还是要通过源码的分析才认同这种代码,以下我仅仅分析最具有代表性的atomicadd源码,其他的API原理都一样。如果读者还不熟悉C内嵌汇编的格式,请参考CCLinux服务器开发后台架构师【零声教育】学习视频教程腾讯课堂 二、内核对于ARM构架的atomicadd源码分析 详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。ARMv6UP和SMP安全原子操作。我们是用独占载入和独占存储来保证这些操作的原子性。我们可能会通过循环来保证成功更新变量。staticinlinevoidatomicadd(inti,atomictv){unsignedlongtmp;intresult;asmvolatile(atomicadd1:ldrex0,〔3〕add0,0,4strex1,0,〔3〕teq1,0bne1b:r(result),r(tmp),Qo(vcounter):r(vcounter),Ir(i):cc);} 源码分析: 注意:根据内联汇编的语法,result、tmp、vcounter对应的数据都放在了寄存器中操作。如果出现上下文切换,切换机制会做寄存器上下文保护。(1)ldrex0,〔3〕 意思是将vcounter指向的数据放入result中,并且(分别在Localmonitor和Globalmonitor中)设置独占标志。(2)add0,0,4resultresulti(3)strex1,0,〔3〕 意思是将result保存到vcounter指向的内存中,此时Exclusivemonitors会发挥作用,将保存是否成功的标志放入tmp中。(4)teq1,0 测试strex是否成功(tmp0??)(5)bne1b 如果发现strex失败,从(1)再次执行。 通过上面的分析,可知关键在于strex的操作是否成功的判断上。而这个就归功于ARM的Exclusivemonitors和ldrexstrex指令的机制。以下通过可能的情况分析ldrexstrex指令机制。(请阅读时参考4。2。12。LDREX和STREX) 1、UP系统或SMP系统中变量为非CPU间共享访问的情况 详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。 此情况下,仅有一个CPU可能访问变量,此时仅有Localmonitor需要关注。 假设CPU执行到(2)的时候,来了一个中断,并在中断里使用ldrexstrex操作了同一个原子变量。则情况如下图所示: A:处理器标记一个物理地址,但访问尚未完毕B:再次标记此物理地址访问尚未完毕(与A重复)C:进行存储操作,清除以上标记,返回0(操作成功)D:不会进行存储操作,并返回1(操作失败) 也就是说,中断例程里的操作会成功,被中断的操作会失败重试。 2、SMP系统中变量为CPU间共享访问的情况 详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。 此情况下,需要两个CPU间的互斥访问,此时ldrexstrex指令会同时关注Localmonitor和Globalmonitor。 (i)两个CPU同时访问同个原子变量(ldrexstrex指令会关注Globalmonitor。) 详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。 A:将该物理地址标记为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。B:标记此物理地址为CPU1独占访问,并清除CPU1对其他任何物理地址的任何独占访问标记。C:没有标记为CPU0独占访问,不会进行存储,并返回1(操作失败)。D:已被标记为CPU1独占访问,进行存储并清除独占访问标记,并返回0(操作成功)。 也就是说,后执行ldrex操作的CPU会成功。 (ii)同一个CPU因为中断,嵌套访问同个原子变量(ldrexstrex指令会关注Localmonito)详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。 A:将该物理地址标记为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。B:再次标记此物理地址为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。C:已被标记为CPU0独占访问,进行存储并清除独占访问标记,并返回0(操作成功)。D:没有标记为CPU0独占访问,不会进行存储,并返回1(操作失败)。 也就是说,中断例程里的操作会成功,被中断的操作会失败重试。 (iii)两个CPU同时访问同个原子变量,并同时有CPU因中断嵌套访问改原子变量(ldrexstrex指令会同时关注Localmonitor和Globalmonitor)详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。 虽然对于人来说,这种情况比较BT。但是在飞速运行的CPU来说,BT的事情随时都可能发生。A:将该物理地址标记为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。B:标记此物理地址为CPU1独占访问,并清除CPU1对其他任何物理地址的任何独占访问标记。C:再次标记此物理地址为CPU0独占访问,并清除CPU0对其他任何物理地址的任何独占访问标记。D:已被标记为CPU0独占访问,进行存储并清除独占访问标记,并返回0(操作成功)。E:没有标记为CPU1独占访问,不会进行存储,并返回1(操作失败)。F:没有标记为CPU0独占访问,不会进行存储,并返回1(操作失败)。总结: 当然还有其他许多复杂的可能,也可以通过ldrexstrex指令的机制分析出来。从上面列举的分析中,我们可以看出:ldrexstrex可以保证在任何情况下(包括被中断)的访问原子性。所以内核中ARM构架中的原子操作是可以信任的。 详细教程资料课件关注后台私信;资料;两个字可以免费视频领取文档各大厂面试题资料内容包括:CC,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCPIP,协程,DPDK,嵌入式等。