Ubuntu 下 nginx-1.24.0 源码分析 - ngx_atomic_cmp_set 函数
目录
修正
执行
./configure
命令时,输出:
checking for OS
+ Linux 6.8.0-52-generic x86_64
checking for C compiler ... found
+ using GNU C compiler
+ gcc version: 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
所以当前环境是 x86_64
于是在 src\os\unix\ngx_atomic.h 中
#include "ngx_gcc_atomic_x86.h"
被包含
src/os/unix/ngx_gcc_atomic_x86.h 中:
static ngx_inline ngx_atomic_uint_t ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old, ngx_atomic_uint_t set) { u_char res; __asm__ volatile ( NGX_SMP_LOCK " cmpxchgl %3, %1; " " sete %0; " : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory"); return res; }
函数功能:
原子地比较
*lock
的值是否等于old
:若相等:将
*lock
设置为set
,并返回1
(成功)若不相等:不修改
*lock
,返回0
(失败)
内联汇编实现
NGX_SMP_LOCK
宏定义,在多核(SMP)系统中扩展为
lock
前缀,确保指令的原子性;单核系统中可能为空
cmpxchgl %3, %1
x86 的原子比较并交换指令:
- 比较
%1
(即*lock
)与eax
寄存器(隐含使用,值为old
)- 若相等,将
%3
(即set
)写入*lock
;否则,将*lock
的值加载到eax
sete %0
根据
cmpxchgl
结果(ZF 标志位)设置res
为0
或1
输入输出约束
输出
"=a" (res)
:结果res
通过eax
寄存器返回输入
"m" (*lock), "a" (old), "r" (set)
:
*lock
作为内存操作数
old
存入eax
(隐含用于cmpxchgl
的比较)
set
存入通用寄存器
"cc", "memory"
:告知编译器条件寄存器和内存可能被修改
内联汇编语法
操作数占位符
%0, %1, %2, %3
:按操作数出现顺序编号:
%0
→"=a" (res)
(输出)%1
→"m" (*lock)
(输入)%2
→"a" (old)
(输入)%3
→"r" (set)
(输入)操作数约束(Constraints)
输出操作数
"=a" (res)
=
:表示只写(输出)。a
:使用eax
寄存器。res
:C变量,接收结果(0或1)
"m" (*lock)
m
:内存操作数,直接操作*lock
的内存地址
"a" (old)
a
:将old
的值存入eax
寄存器(cmpxchgl
隐式使用eax
进行比较)。
"r" (set)
r
:将set
的值存入任意通用寄存器(如ebx
、ecx
等)。Clobber列表
"cc"
:表示指令修改了标志寄存器(如 ZF、CF)。"memory"
:表示指令可能修改内存,强制编译器刷新内存缓存。cmpxchgl 源操作数, 目标操作数
比较
目标操作数
(即*lock
)与eax
的值(old
)。若相等:
将
源操作数
(即set
)写入目标操作数
(*lock
)。设置 ZF(Zero Flag)为 1。
若不相等:
将
目标操作数
(*lock
)的值加载到eax
。设置 ZF 为 0。
sete %0
sete
:若 ZF=1(即比较成功),将目标(%0
,即res
)设为 1,否则设为 0。- 由于
%0
约束为"=a"
,结果通过eax
写入res
。
执行流程
将
old
加载到eax
。原子比较
*lock
与eax
:相等 → 将
set
写入*lock
,ZF=1。不等 → 将
*lock
值加载到eax
,ZF=0。根据 ZF 设置
res
(1 或 0)。返回
res
。
意图
实现原子操作
通过cmpxchgl
指令和lock
前缀,确保在多核环境下的原子性,避免竞态条件。跨平台兼容性
使用
NGX_SMP_LOCK
宏适配不同平台(如单核无需lock
前缀)
NGX_SMP_LOCK
在 src\os\unix\ngx_gcc_atomic_x86.h
#if (NGX_SMP) #define NGX_SMP_LOCK "lock;" #else #define NGX_SMP_LOCK #endif
objs/ngx_auto_config.h 中
#ifndef NGX_SMP #define NGX_SMP 1 #endif
所以
NGX_SMP_LOCK 是 "lock;"
- 无
lock
:cmpxchgl
本身是原子的,但仅限单核环境。- 有
lock
:确保多核环境下的原子性,完整执行“比较-交换”操作。
修正
以上部分 可能是错误的
在 src\os\unix\ngx_atomic.h 中:
#define ngx_trylock(lock) (*(lock) == 0 && ngx_atomic_cmp_set(lock, 0, 1))
这样定义了 ngx_trylock
但对于 ngx_atomic_cmp_set 的定义可能判断错了
在 我的Ubuntu 环境中
objs/ngx_auto_config.h:9:#define NGX_HAVE_GCC_ATOMIC 1
也就是在 objs/ngx_auto_config.h 中
定义了
#ifndef NGX_HAVE_GCC_ATOMIC #define NGX_HAVE_GCC_ATOMIC 1 #endif
在 ngx_atomic.h 中
#if (NGX_HAVE_LIBATOMIC) 省略 #elif (NGX_HAVE_GCC_ATOMIC) 省略 #define ngx_atomic_cmp_set(lock, old, set) \ __sync_bool_compare_and_swap(lock, old, set)
#elif (NGX_HAVE_GCC_ATOMIC) 条件成立
所以 ngx_atomic_cmp_set 的定义应该是:
#define ngx_atomic_cmp_set(lock, old, set) \ __sync_bool_compare_and_swap(lock, old, set)
在Ubuntu的x86_64架构下,
NGX_HAVE_GCC_ATOMIC
会被定义ngx_atomic_cmp_set 在支持GCC原子内置函数的情况下,这个函数应该会被定义为上述情况
__sync_bool_compare_and_swap
是 GCC 提供的一个内置原子操作函数,用于实现多线程环境下的无锁同步。其作用是在原子操作中比较并交换(Compare-and-Swap, CAS)一个值,常用于实现线程安全的操作。
函数原型
bool __sync_bool_compare_and_swap(type *ptr, type oldval, type newval);
- 参数:
ptr
:指向需要操作的内存地址的指针。oldval
:期望的旧值。newval
:要设置的新值。- 返回值:
- 如果
*ptr
的当前值等于oldval
,则将*ptr
设置为newval
,并返回true
。- 否则不修改内存,返回
false
。底层原理
该函数依赖硬件级别的原子指令(如 x86 的
CMPXCHG
指令)实现,确保多线程环境下操作的原子性函数由 GCC 编译器直接提供,无需像标准库函数(如
printf
)那样通过#include
引入头文件。直接在代码中调用即可
gcc -E
鉴于有时预编译指令较多且嵌套,难以判断具体使用的哪一个定义
于是改用
gcc -E 的方法
- 作用:运行 GCC 的 预处理阶段,处理以下内容:
- 展开
#include
引入的头文件。- 替换
#define
定义的宏。- 处理
#ifdef
/#if
等条件编译指令。- 删除注释。
- 输出:预处理后的纯 C 代码(未编译)。
gcc -E src/core/ngx_times.c \ -I src/core \ -I src/event \ -I src/event/modules \ -I src/os/unix \ -I objs \ > ngx_times_preprocessed.c
-I 添加头文件搜索路径
> ngx_times_preprocessed.c
- 作用:将预处理结果重定向到文件
ngx_times_preprocessed.c
- 文件内容:展开后的完整代码
找到原本 ngx_times.c中
void ngx_time_update(void) { u_char *p0, *p1, *p2, *p3, *p4; ngx_tm_t tm, gmt; time_t sec; ngx_uint_t msec; ngx_time_t *tp; struct timeval tv; if (!ngx_trylock(&ngx_time_lock)) { return; }
ngx_time_update 函数中,struct timeval tv; 后,ngx_trylock 调用的地方
在
ngx_times_preprocessed.c 中的位置
if (!(*(&ngx_time_lock) == 0 && __sync_bool_compare_and_swap(&ngx_time_lock, 0, 1))) { return; }
这里是展开后的样子
所以 确认是 调用了
__sync_bool_compare_and_swap