handler原子锁_Linux的原子操作与同步机制

Linux的原子操作与同步机制

并发问题

现代操作系统支持多任务的并发,并发在提高计算资源利用率的同时也带来了资源竞争的问题。例如C语言语句“count++;”在未经编译器优化时生成的汇编代码为。

当操作系统内存在多个进程同时执行这段代码时,就可能带来并发问题。

假设count变量初始值为0。进程1执行完“mov eax, [count]”后,寄存器eax内保存了count的值0。此时,进程2被调度执行,抢占了进程1的CPU的控制权。进程2执行“count++;”的汇编代码,将累加后的count值1写回到内存。然后,进程1再次被调度执行,CPU控制权回到进程1。进程1接着执行,计算count的累加值仍为1,写回到内存。虽然进程1和进程2执行了两次“count++;”操作,但是count实际的内存值为1,而不是2!

单处理器原子操作

解决这个问题的方法是,将“count++;”语句翻译为单指令操作。

Intel x86指令集支持内存操作数的inc操作,这样“count++;”操作可以在一条指令内完成。因为进程的上下文切换是在总是在一条指令执行完成后,所以不会出现上述的并发问题。对于单处理器来说,一条处理器指令就是一个原子操作。

多处理器原子操作

但是在多处理器的环境下,例如SMP架构,这个结论不再成立。我们知道“inc [count]”指令的执行过程分为三步:

1)从内存将count的数据读取到cpu。

2)累加读取的值。

3)将修改的值写回count内存。

这又回到前面并发问题类似的情况,只不过此时并发的主题不再是进程,而是处理器。

Intel x86指令集提供了指令前缀lock用于锁定前端串行总线(FSB),保证了指令执行时不会受到其他处理器的干扰。

使用lock指令前缀后,处理器间对count内存的并发访问(读/写)被禁止,从而保证了指令的原子性。

x86原子操作实现

Linux的源码中x86体系结构原子操作的定义文件为。

linux2.6/include/asm-i386/atomic.h

文件内定义了原子类型atomic_t,其仅有一个字段counter,用于保存32位的数据。

typedef struct { volatile int counter; } atomic_t;

其中原子操作函数atomic_inc完成自加原子操作。

/**

* atomic_inc - increment atomic variable

* @v: pointer of type atomic_t

*

* Atomically increments @v by 1.

*/

static __inline__ void atomic_inc(atomic_t *v)

{

__asm__ __volatile__(

LOCK "incl %0"

:"=m" (v->counter)

:"m" (v->counter));

}

其中LOCK宏的定义为。

#ifdef CONFIG_SMP

#define LOCK "lock ; "

#else

#define LOCK ""

#endif

可见,在对称多处理器架构的情况下,LOCK被解释为指令前缀lock。而对于单处理器架构,LOCK不包含任何内容。

arm原子操作实现

在arm的指令集中,不存在指令前缀lock,那如何完成原子操作呢?

Linux的源码中arm体系结构原子操作的定义文件为。

linux2.6/include/asm-arm/atomic.h

其中自加原子操作由函数atomic_add_return实现。

static inline int atomic_add_return(int i, atomic_t *v)

{

unsigned long tmp;

int result;

__asm__ __volatile__("@ atomic_add_return\n"

"1:     ldrex   %0, [%2]\n"

"       add     %0, %0, %3\n"

"       strex   %1, %0, [%2]\n"

"       teq     %1, #0\n"

"       bne     1b"

: "=&r" (result), "=&r" (tmp)

: "r" (&v->counter), "Ir" (i)

: "cc");

return result;

}

上述嵌入式汇编的实际形式为。

1:

ldrex  [result], [v->counter]

add    [result], [result], [i]

strex  [temp], [result], [v->counter]

teq    [temp], #0

bne    1b

ldrex指令将v->counter的值传送到result,并设置全局标记“Exclusive”。

add指令完成“result+i”的操作,并将加法结果保存到result。

strex指令首先检测全局标记“Exclusive”是否存在,如果存在,则将result的值写回counter->v,并将temp置为0,清除“Exclusive”标记,否则直接将temp置为1结束。

teq指令测试temp值是否为0。

bne指令temp不等于0时跳转到标号1,其中字符b表示向后跳转。

整体看来,上述汇编代码一直尝试完成“v->counter+=i”的操作,直到temp为0时结束。

使用ldrex和strex指令对是否可以保证add指令的原子性呢?假设两个进程并发执行“ldrex+add+strex”操作,当进程1执行ldrex后设定了全局标记“Exclusive”。此时切换到进程2,执行ldrex前全局标记“Exclusive”已经设定,ldrex执行后重复设定了该标记。然后执行add和strex指令,完成累加操作。再次切换回进程1,接着执行add指令,当执行strex指令时,由于“Exclusive”标记被进程2清除,因此不执行传送操作,将temp设置为1。后继teq指令测定temp不等于0,则跳转到起始位置重新执行,最终完成累加操作!可见ldrex和strex指令对可以保证进程间的同步。多处理器的情况与此相同,因为arm的原子操作只关心“Exclusive”标记,而不在乎前端串行总线是否加锁。

在ARMv6之前,swp指令就是通过锁定总线的方式完成原子的数据交换,但是影响系统性能。ARMv6之后,一般使用ldrex和strex指令对代替swp指令的功能。

自旋锁中的原子操作

Linux的源码中x86体系结构自旋锁的定义文件为。

linux2.6/include/asm-i386/spinlock.h

其中__raw_spin_lock完成自旋锁的加锁功能

#define __raw_spin_lock_string \

"\n1:\t" \

"lock ; decb %0\n\t" \

"jns 3f\n" \

"2:\t" \

"rep;nop\n\t" \

"cmpb $0,%0\n\t" \

"jle 2b\n\t" \

"jmp 1b\n" \

"3:\n\t"

static inline void __raw_spin_lock(raw_spinlock_t *lock)

{

__asm__ __volatile__(

__raw_spin_lock_string

:"=m" (lock->slock) : : "memory");

}

上述代码的实际汇编形式为。

1:

lock   decb [lock->slock]

jns    3

2:

rep    nop

cmpb   $0, [lock->slock]

jle    2

jmp    1

3:

其中lock->slock字段初始值为1,执行原子操作decb后值为0。符号位为0,执行jns指令跳转到3,完成自旋锁的加锁。

当再次申请自旋锁时,执行原子操作decb后lock->slock值为-1。符号位为1,不执行jns指令。进入标签2,执行一组nop指令后比较lock->slock是否小于等于0,如果小于等于0回到标签2进行循环(自旋)。否则跳转到标签1重新申请自旋锁,直到申请成功。

自旋锁释放时会将lock->slock设置为1,这样保证了其他进程可以获得自旋锁。

信号量中的原子操作

Linux的源码中x86体系结构信号量的定义文件为。

linux2.6/include/asm-i386/semaphore.h

信号量的申请操作由函数down实现。

/*

* This is ugly, but we want the default case to fall through.

* "__down_failed" is a special asm handler that calls the C

* routine that actually waits. See arch/i386/kernel/semaphore.c

*/

static inline void down(struct semaphore * sem)

{

might_sleep();

__asm__ __volatile__(

"# atomic down operation\n\t"

LOCK "decl %0\n\t"     /* --sem->count */

"js 2f\n"

"1:\n"

LOCK_SECTION_START("")

"2:\tlea %0,%%eax\n\t"

"call __down_failed\n\t"

"jmp 1b\n"

LOCK_SECTION_END

:"=m" (sem->count)

:

:"memory","ax");

}

实际的汇编代码形式为。

lock   decl [sem->count]

js 2

1:

<========== another section ==========>

2:

lea    [sem->count], eax

call   __down_failed

jmp 1

信号量的sem->count一般初始化为一个正整数,申请信号量时执行原子操作decl,将sem->count减1。如果该值减为负数(符号位为1)则跳转到另一个段内的标签2,否则申请信号量成功。

标签2被编译到另一个段内,进入标签2后,执行lea指令取出sem->count的地址,放到eax寄存器作为参数,然后调用函数__down_failed表示信号量申请失败,进程加入等待队列。最后跳回标签1结束信号量申请。

信号量的释放操作由函数up实现。

/*

* Note! This is subtle. We jump to wake people up only if

* the semaphore was negative (== somebody was waiting on it).

* The default case (no contention) will result in NO

* jumps for both down() and up().

*/

static inline void up(struct semaphore * sem)

{

__asm__ __volatile__(

"# atomic up operation\n\t"

LOCK "incl %0\n\t"     /* ++sem->count */

"jle 2f\n"

"1:\n"

LOCK_SECTION_START("")

"2:\tlea %0,%%eax\n\t"

"call __up_wakeup\n\t"

"jmp 1b\n"

LOCK_SECTION_END

".subsection 0\n"

:"=m" (sem->count)

:

:"memory","ax");

}

实际的汇编代码形式为。

lock   incl sem->count

jle     2

1:

<========== another section ==========>

2:

lea    [sem->count], eax

call   __up_wakeup

jmp    1

释放信号量时执行原子操作incl将sem->count加1,如果该值小于等于0,则说明等待队列有阻塞的进程需要唤醒,跳转到标签2,否则信号量释放成功。

标签2被编译到另一个段内,进入标签2后,执行lea指令取出sem->count的地址,放到eax寄存器作为参数,然后调用函数__up_wakeup唤醒等待队列的进程。最后跳回标签1结束信号量释放。

总结

本文通过对操作系统并发问题的讨论研究操作系统内的原子操作的实现原理,并讨论了不同体系结构下Linux原子操作的实现,最后描述了Linux操作系统如何利用原子操作实现常见的进程同步机制,希望对你有所帮助。

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

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

相关文章

ddr2和ddr3的区别

DDR3与DDR2的不同之处 1、逻辑Bank数量 DDR2 SDRAM中有4Bank和8Bank的设计&#xff0c;目的就是为了应对未来大容量芯片的需求。而DDR3很可能将从2Gb容量起步&#xff0c;因此起始的逻辑Bank就是8个&#xff0c;另外还为未来的16个逻辑Bank做好了准备。 2、封装&#xff08;Pac…

【直观理解】为什么梯度的负方向是局部下降最快的方向?

推荐阅读时间&#xff1a;8min~15min主要内容&#xff1a;为什么梯度的负方向是局部下降最快的方向&#xff1f;刚接触梯度下降这个概念的时候&#xff0c;是在学习机器学习算法的时候&#xff0c;很多训练算法用的就是梯度下降&#xff0c;然后资料和老师们也说朝着梯度的反方…

紫光物联linux登录账号,紫光展锐打造操作系统生态,赋能万物互联智能时代

本周&#xff0c;以“象由芯生科技服务人民”为主题的2020紫光展锐市场峰会重磅开启&#xff0c;广大生态合作伙伴共聚一堂&#xff0c;共话数字世界新未来。在今天举办的“操作系统OS研讨会”上&#xff0c;来自紫光展锐工程一线的架构师带来了一场整个操作系统领域的饕餮盛宴…

一行命令搭建内部的管道

在上一篇《边缘计算k8s集群之SuperEdge》文章中&#xff0c;笔者基于ECK搭建了边缘集群并添加了节点。通过边缘集群&#xff0c;我们可以很方便的管理各个地域的节点&#xff0c;本地、各云厂商的机房、客户所在地、海外的都可以。在本篇内容&#xff0c;我们将讲述如何使用ips…

推到 旋转矩阵公式_3D旋转矩阵的推导过程

3D旋转矩阵的推导过程包含平移的线性变换称作仿射变换&#xff0c;3D中的仿射变换不能用 3 x 3 矩阵表达&#xff0c;必须使用4 x 4矩阵。一般来说&#xff0c;变换物体相当于以相反的量变换描述这个物体的坐标系。当有多个变换时&#xff0c;则需要以相反的顺序变换相反的量。…

ArchiMate - 发布【企业架构语言ArchiMate v0.5.pdf】

在《年度总结和计划&#xff1a;去年4个1&#xff0c;今年5个1》中说过今年我准备在项目组引入1个架构语言&#xff08;ArchiMate&#xff09;&#xff0c;为了便于大家学习&#xff0c;我把一些内容集成一本电子书&#xff0c;目前发布0.5版本&#xff0c;后续还会不断更新&am…

那些有趣/用的 Python 库

图片处理pip install pillowfrom PIL import Imageimport numpy as npa np.array(Image.open(test.jpg))b [255,255,255] - aim Image.fromarray(b.astype(uint8))im.save(new.jpg)youtube-dl下载国外视频pip install youtube-dl #直接安装youtube-dlpip install -U youtube…

linux系统刷分辨率,Linux下设置其分辨率及刷新率

行频&#xff1a;行频又称为“水平扫描频率”&#xff0c;指电子枪每秒在荧光屏上扫过的水平线的数量&#xff0c;其值等于“场频 垂直分辨率1.04”&#xff0c;单位为KHz(千赫兹)。行频是一个综合分辨率和场频的参数&#xff0c;该值越大&#xff0c;显示器可以提供的分辨率越…

.NET 5 部署在docker上运行

1、创建站点创建一个ASP.NET Core Web应用程序&#xff0c;选中启用Docker支持。自动帮我们创建一个Dockerfile文件。2、编写Dockerfile文件dockerfile是一个文件格式的配置文件&#xff0c;用户可以使用dockerfile来快速构建自定义的镜像。由一行行命令语句组成&#xff0c;并…

python字符串下标越界_Python_字符串

# str1"abcdef" #字符串 str型# int1 1 #整数 int型# float11.3 #浮点数&#xff0c;float型# str2 ABCDEF# #单引号和双引号都可以表示字符串# print(str1,str2)#在某一行按下Ctrl/&#xff0c;就可以进行注释&#xff0c;注释就是程序不执行该行代码&#xff0c;…

爱情三十一课,先信自己

我们每个人一生都在迎接两个问题的考验&#xff1a;其一&#xff0c;我是否值得被爱&#xff1b;其二&#xff0c;我是否可以成功。  如果某人在这两个问题上的自我认识是“值得”和“可以”&#xff0c;无论境遇多遭&#xff0c;都可以获得爱与幸福。如果某人内心里总是觉得…

c语言给坐标求多边形面积,多边形的面积问题

多边形的面积问题设构成多边形的坐标串为(xi,yi)(i1,2,……,n),求此多边形面积A。#include#define N 10float Area(float (*x)[2],int n){float sum0;int i0,j;for(;i{ji1;if(jn)j0;sum(x[j][0]x[i][0])*(x[j][1]-x[i][1]);}sum(1/2)*(fabs(sum));return sum;}main(){int n,i,j…

Nexus:一站式私有仓库管理(NuGet、Maven、npm、Docker)

我们在日常开发中经常需要使用到私有仓库&#xff0c;比如 dotNET 中的 NuGet、Java 中的 Maven、前端的 npm&#xff0c;还有 Docker 镜像&#xff0c;每一个私有仓库各自管理&#xff0c;维护起来比较麻烦&#xff0c;而 Nexus 可以将其统一起来。本文将介绍 Nexus 的安装以及…

python choice添加下拉框_自定义Django Form中choicefield下拉菜单选取数据库内容实例...

工作中遇到的问题,自定义了一个forms.form表单,某项需要作出下拉菜单,下拉菜单中的选项需要从数据库(objectForm models)中提取.form.py为:class objectForm(forms.Form):pre choicefield(lable "工作")最后的解决办法&#xff1a;1.定义一个函数def get_object(re…

Enum使用

http://www.java-cn.com/club/html/40/n-640.html1、 目的简单认为&#xff1a;满足一些需求2、 定义、使用public enum SexEnum {male(1),female(0); private final int value; private SexEnum(int value){this.value value;}public int getValue(){return this…

众里寻 Bug 千百度,蓦然回首,它却在隔壁老张处……

程序员与 Bug 是一对矛盾的存在&#xff0c;程序员既要在解决 Bug 中获得成就感&#xff0c;同时也讨厌 Bug 本身的存在。“程序不息&#xff0c;Bug 不止”&#xff0c;程序员在与 Bug 的斗争中&#xff0c;也有很多有趣的事情发生&#xff0c;我们整理了一些程序员在调试 Bug…

linux系统中常见的目录,Linux系统中常见的目录名称以及相应内容

LeetCode&colon; Largest Rectangle in Histogram&lpar;直方图最大面积&rpar;http://blog.csdn.net/abcbc/article/details/8943485 具体的题目描述为: Given n non-negative integers represent ...自己封装的一个无限滚动 mark 待传import url(http://i.cnblogs.…

Blazor WASM 实现人民币大写转换器

点击上方蓝字关注“汪宇杰博客”导语.NET 5 正式发布已经有一段时间了&#xff0c;其中 Blazor 技术是该版本的亮点之一。作为微软技术的被坑者&#xff0c;年少的我曾经以为 SilverLight 能血虐 Flash&#xff0c;Zune 能团灭 iPod&#xff0c;WP 能吊打 iPhone&#xff0c;UW…

vs2010中svn使用教程_VS2010中使用ankhSVN

给大家介绍一些SVN的入门知识&#xff01;希望对大家的学习起到作用&#xff01;关于SVN与CVS的相关知识&#xff0c;大家可以自己去google一下。一、准备SVN是一个开源的版本控制系统&#xff0c;它可以记录所有的文件修改版本。CVS也是一个版本控制系统&#xff0c;可是现在大…

金山安全实验室公布中国互联网六大类钓鱼网站

金山安全实验室公布中国互联网六大类钓鱼网站金山安全实验室研究人员对中国大陆钓鱼网站的普遍特征进行分析&#xff0c;发现以下六个领域最容易被钓鱼网站***&#xff1a;1.QQ十年庆典、QQ抽奖、腾讯活动&#xff1b;2.证券、股票分析、黑庄、理财专家等财经领域&#xff1b;3…