本文我们主要介绍一下与原子锁相关的部分:ngx_automic.h
1. os/unix/ngx_automic.h源文件
源文件内容如下:
2. 执行NGX_HAVE_GCC_ATOMIC代码
由于我们在执行configure时生成的头文件中定义了NGX_HAVE_GCC_ATOMIC
,因此这里我们执行如下:
如上,NGX_PTR_SIZE
在我们当前环境下为4,因此:
#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
在我们当前环境定义了__i386__
与__i386
,因此会执行如下:
#define ngx_cpu_pause() __asm__ ("pause")
下面我们介绍一下gcc中的一些内置原子函数:
2.1 gcc内置原子函数
参看:《Using the GNU Compiler Collection》 p462
gcc从4.1.2开始提供了__sync_*
系列的built-in函数,用于提供加减和逻辑运算的原子操作:
1)
上面这组built-in原子函数会根据其名称所示执行相应的操作,并且返回内存中更新之前的值。即:
{ tmp = *ptr; *ptr op= value; return tmp; }
{ tmp = *ptr; *ptr = ~(tmp & value); return tmp; } // nand
注意:GCC 4.4及之后的版本__sync_fetch_and_nand
的实现变为:*ptr = ~(tmp & value), 而不是 *ptr = ~tmp & value .
2)
上面这组built-in原子函数会根据其名称所示执行相应的操作,并且返回内存中更新之后的值。即:
{ *ptr op= value; return *ptr; }
{ *ptr = ~(*ptr & value); return *ptr; } // nand
注意:GCC 4.4及之后的版本__sync_nand_and_fetch
的实现变为:ptr =~(ptr & value), 而不是 ptr = ~ptr & value .
3)
上面这两个函数提供原子的比较和交换:如果 *ptr == oldval,就将 newval 写入 *ptr. 其中第一个函数在相等并写入的情况下返回true; 第二个函数返回操作之前的值。
说明:
上述 “__sync_*” 函数中type可以是1,2,4或8字节长度的 “整数” 类型或 “浮点” 类型:
int8_t / uint8_t
int16_t / uint16_t
int32_t / uint32_t
int64_t / uint64_t
后面的可扩展参数(...)用来指出哪些变量需要memory barrier,因为目前gcc实现的是full barrier
(类似于linux kernel中的mb(),表示这个操作之前的所有内存操作不会重排序到这个操作之后), 所以
可以忽略这个参数。
4)
函数发出一个full memory barrier。
关于memory barrier, cpu会对我们的指令进行排序,一般来说会提高程序的效率,但有时候可能造成我们不希望看到的结果。举一个例子,比如我们一个硬件设备,它有4个寄存器,当你发出一个操作指令的时候,一个寄存器存的是你的操作指令(比如READ),两个寄存器存的是参数(比如addr和size),最后一个寄存器是控制寄存器,在所有的参数都设置好之后向其发出指令,设备开始读取参数,执行命令。程序可能如下:
write1(dev.register_size,size);
write1(dev.register_addr,addr);
write1(dev.register_cmd,READ);
write1(dev.register_control,GO);
如果最后一条write1被换到了前几条语句之前,那么肯定不是我们所期望的,这时候我们可以在最后一条语句之前加入一个memory barrier,强制cpu执行完前面的写入以后再执行最后一条:
write1(dev.register_size,size);
write1(dev.register_addr,addr);
write1(dev.register_cmd,READ);
__sync_synchronize();
write1(dev.register_control,GO);
memory barrier有几种类型:
-
acquire barrier: 不允许将barrier之后的内存读取指令移到barrier之前(linux kernel中的wmb())
-
release barrier: 不允许将barrier之前的内存读取指令移到barrier之后(linux kernel中的rmb())
-
full barrier: 以上两种barrier的合集(linux kernel中的mb())
5)
上面第一个函数将*ptr
设为value,并返回*ptr
操作之前的值; 第二个函数将*ptr
置为0.
6)
示例:
编译运行:
[root@localhost test-src]# gcc -o test8 test8.c -lpthread
[root@localhost test-src]# ./test8
count: 400000
3. 其他
NGX_HAVE_ATOMIC_OPS
在上一节已经定义为1,因此这里:
#if !(NGX_HAVE_ATOMIC_OPS)
...
#endif
并不会被执行。
关于ngx_spinlock()我们会在后续进行讲解。
[参看]:
-
Nginx 源码完全剖析(11)ngx_spinlock
-
GCC内联汇编(1)Get started
-
GCC 提供的原子操作