news 2026/8/27 18:11:01

OS85.【Linux】分析glibc-2.43 x86_64版本的自旋锁的底层原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OS85.【Linux】分析glibc-2.43 x86_64版本的自旋锁的底层原理

目录

1.知识回顾: glibc-2.43 i386版本的自旋锁

2.glibc-2.43 x86_64版本的自旋锁的参考代码

3.前置知识

几个调用约定

lock前缀、pthread_spinlock_t的类型

4.分析

pthread_spin_init.c

pthread_spin_unlock.S(x86_64)

pthread_spin_lock.S(x86_64)

pthread_spin_trylock.S(x86_64)

从汇编指令看pthread_spin_lock.S和pthread_spin_trylock.S的区别

5.知识点摘录: Raymond Chen The Intel 80386, part 10: Atomic operations and memory alignment


1.知识回顾: glibc-2.43 i386版本的自旋锁

参见OS85.【Linux】各种锁(重点分析glibc i386版本的自旋锁的底层原理)文章

2.glibc-2.43 x86_64版本的自旋锁的参考代码

glibc-2.43的以下文件:

/sysdeps/x86_64/nptl/pthread_spin_init.c

/sysdeps/x86_64/nptl/pthread_spin_lock.S

/sysdeps/x86_64/nptl/pthread_spin_trylock.S

/sysdeps/x86_64/nptl/thread_spin_unlock.S

3.前置知识

几个调用约定

参见127.【C语言】面试题:函数的调用约定文章复习x86_64(64位)和i386(32位)下的linux的函数调用约定

lock前缀、pthread_spinlock_t的类型

两个都参见OS85.【Linux】各种锁(重点分析glibc i386版本的自旋锁的底层原理)文章

4.分析

pthread_spin_init.c

x86_64版的pthread_spin_init.c和之前文章讲的i386版的一样

pthread_spin_init.c就一个include语句:

#include <sysdeps/i386/nptl/pthread_spin_init.c>

sysdeps/i386/nptl/pthread_spin_init.c中的pthread_spin_init函数又是i386版pthread_spin_unlock的别名

也就是说: x86_64和i386版的pthread_spin_init函数的实现都是i386版pthread_spin_unlock汇编版本

pthread_spin_unlock使用栈传递锁的指针,在x86_64下也是兼容i386的

pthread_spin_unlock.S(x86_64)

#include <sysdep.h> #include <shlib-compat.h> ENTRY(__pthread_spin_unlock) movl $1, (%rdi) xorl %eax, %eax retq END(__pthread_spin_unlock) versioned_symbol (libc, __pthread_spin_unlock, pthread_spin_unlock, GLIBC_2_34) /* The implementation of pthread_spin_init is identical. */ versioned_symbol (libc, __pthread_spin_unlock, pthread_spin_init, GLIBC_2_34) #if OTHER_SHLIB_COMPAT (libpthread, GLIBC_2_2, GLIBC_2_34) compat_symbol (libpthread, __pthread_spin_unlock, pthread_spin_unlock, GLIBC_2_2) compat_symbol (libpthread, __pthread_spin_unlock, pthread_spin_init, GLIBC_2_2) #endif

转换为intel语法:

mov dword ptr [rdi], 1 xor eax, eax ret

由于Linux上x86_64的调用约定和i386不一样,根据System V AMD64 ABI的调用约定,第一个整数参数使用rdi寄存器

和OS85.【Linux】各种锁(重点分析glibc i386版本的自旋锁的底层原理)文章的分析一样,锁在内存中的值为1,代表锁未被线程获取

pthread_spin_lock.S(x86_64)

#include <sysdep.h> #include <shlib-compat.h> ENTRY(__pthread_spin_lock) /* Always return zero. */ xor %eax, %eax LOCK decl 0(%rdi) jne 1f ret .align 16 1: /* `rep nop` == `pause`. */ rep nop cmpl %eax, 0(%rdi) jle 1b /* Just repeat the `lock decl` logic here. The code size save of jumping back to entry doesn't change how many 16-byte chunks (default function alignment) that the code fits in. */ LOCK decl 0(%rdi) jne 1b ret END(__pthread_spin_lock) versioned_symbol (libc, __pthread_spin_lock, pthread_spin_lock, GLIBC_2_34) #if OTHER_SHLIB_COMPAT (libpthread, GLIBC_2_2, GLIBC_2_34) compat_symbol (libpthread, __pthread_spin_lock, pthread_spin_lock, GLIBC_2_2) #endif

转换为intel语法:

xor eax,eax lock dec dword [rdi] jnz label ret nop dword [rax+rax+0x0] label: pause cmp [rdi],eax jng label lock dec dword [rdi] jnz label ret

逐条指令分析:
xor eax,eax: 表示pthread_spin_lock函数总是返回0

lock dec dword [rdi]: 锁值原子的-1

jnz label:

如果锁值变成0,说明线程成功申请锁,那么执行ret返回,nop dword [rax+rax+0x0]是空操作,用于对齐,不会执行

如果锁值没变成0(比如锁被其它线程抢走了,这个线程再执行锁值-1操作会导致锁值变成-1),说明线程未成功申请锁,执行pause

(pause的作用在 OS85.【Linux】各种锁(重点分析glibc i386版本的自旋锁的底层原理)文章讲过了)

cmp [rdi],eax,将锁值[rdi]和0比较(此时eax寄存器的值就是0)

jng label:

根据李忠老师的《x86汇编语言:从实模式到保护模式 第二版》的第7章
相同的功能,不同的代码 → 7.9 其他标志位和条件转移指令 → 7.9.5 条件转移指令 → 表7-1 各种比较结果和相应的条件转移指令:

jng的ng是not greater(不大于),jg适用于有符号数的比较,要求: ZF=1(两个数相同,相减的结果为0,或者SF≠OF(如果相减后溢出,则结果必须是正数,说明源操作数大;如果相减后未溢出,则结果必须是负数,同样表明源操作数大些)

如果锁变量0,则跳到label的pause,继续自旋等待

如果锁变量>0,则执行下一个lock dec dword [rdi],获取锁

jnz label,如果获取锁失败,则跳到label的pause,继续自旋等待,否则执行ret返回

jng label lock dec dword [rdi] jnz label

使用jng label和jnz label的原因: 意两条指令之间都可能发生线程的的切换,线程切换后,其它线程可能会抢走锁

代码充分说明pthread_spin_lock申请自旋锁失败会一直申请

pthread_spin_trylock.S(x86_64)

#include <sysdep.h> #include <errno.h> #include <shlib-compat.h> ENTRY(__pthread_spin_trylock) xorl %ecx, %ecx /* xchg has implicit LOCK prefix. */ xchgl %ecx, (%rdi) /* Branch on result. Expectation is the use of trylock will be branching on success/failure so this branch can be used to to predict the coming branch. It has the benefit of breaking the likely expensive memory dependency on (%rdi). */ cmpl $1, %ecx jnz 1f xorl %eax, %eax ret 1: movl $EBUSY, %eax ret END(__pthread_spin_trylock) versioned_symbol (libc, __pthread_spin_trylock, pthread_spin_trylock, GLIBC_2_34) #if OTHER_SHLIB_COMPAT (libpthread, GLIBC_2_2, GLIBC_2_34) compat_symbol (libpthread, __pthread_spin_trylock, pthread_spin_trylock, GLIBC_2_2) #endif

转换为intel语法:

xor ecx,ecx xchg ecx,[rdi] cmp ecx, 0x1 jnz label xor eax,eax ret label: mov eax,$EBUSY ret

逐条指令分析:

xor ecx,ecx: 清零ecx

xchg ecx,[rdi]: 交换ecx和[rdi]的值

cmp ecx, 0x1: 比较ecx和1的值,设置状态寄存器

jnz label: 根据cmp ecx, 0x1返回的结果,如果ecx值为1,说明取到锁了,继续执行下一条指令xor eax,eax,接着执行ret返回; 如果ecx值不为1,说明没有取到锁,跳到label编号的mov eax,$EBUSY,接着执行ret返回

从汇编指令看pthread_spin_lock.S和pthread_spin_trylock.S的区别

从以上分析可以看出: pthread_spin_lock有循环逻辑,因为需要等待锁,pthread_spin_trylock没有循环逻辑,不需要等待锁

5.知识点摘录: Raymond Chen The Intel 80386, part 10: Atomic operations and memory alignment

链接: The Intel 80386, part 10: Atomic operations and memory alignment - The Old New Thing

1.非对齐访问可能导致跨页访问,而操作系统可能不允许访问跨的页,跨页会触发缺页异常,如果缺页异常处理程序会判定该地址非法,会引发段错误

2."读、改、写"默认是非原子的,比如inc [value],如果需要改成原子操作,那么应该写成lock inc [value]

3.lock前缀也保证非对齐的内存访问也是原子的

4.执行交换指令会自动锁定总线,比如x86架构的XCHG指令

查xchg指令在上的描述:

从手册中了解到: xchg可以在寄存器之间、寄存器与内存之间交换数据

如果是在寄存器与内存之间交换数据,那么处理器会自动实施其锁定协议,无论xchg是否带lock前缀,也不管IOPL的值是多少("If a memory operand is referenced, the processor’s locking protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL. ")

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 18:08:00

蓝桥杯国赛必备:线段树与树状数组解决区间修改查询问题

1. 项目概述&#xff1a;从“暴力”到“优雅”的跨越如果你正在备战蓝桥杯国赛&#xff0c;或者刷力扣时被那些要求“区间修改、区间查询”的题目卡住&#xff0c;感觉自己的代码总是超时&#xff0c;那么这篇分享就是为你准备的。这类问题&#xff0c;比如给你一个数组&#x…

作者头像 李华
网站建设 2026/8/27 17:56:43

基于SpringBoot的社区健康体检信息系统毕业设计项目源码

联系博主 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 …

作者头像 李华