文章出自云中楼阁,欢迎转载交流
原子操作在系统编程中属于基础工具。nginx与其他软件很不一样,它几乎把所有操作系统的功能都封装了一遍,估计是出于编程一致性和可移植性的考虑。对于原子操作,nginx提供一组接口。
这里我们只研究有GCC 4.1以上版本的情况,因为nginx对不同的情况有不同的实现,GCC应该还是比较普遍的吧
先看些基本类型
typedef unsigned long ngx_atomic_uint_t;
typedef volatile ngx_atomic_uint_t ngx_atomic_t;
#if (NGX_PTR_SIZE == 8)
#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
#else
#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
#endif
GCC中的long类型的长度始终是操作系统的位数,所以这一段代码是可以兼容64位系统的。 另外注意原子类型ngx_atomic_t一定要加上volatile标志,如果不加的话,原子变量可能被编译器优化到寄存器去了,就不可能利用原子操作去实现锁的功能了。
再看nginx提供了哪些原子操作
#define ngx_atomic_fetch_add(value, add) __sync_fetch_and_add(value, add)
#define ngx_memory_barrier() __sync_synchronize()
#define ngx_cpu_pause() __asm__("pause")
第一个接口是“如果lock指向的值等于old,那么把lock指向的内存单元设为set,并返回1”,其他情况则返回0;
第二个接口是“将value指向的值增加add,返回增加以前的值”;
第三个接口的功能也有点儿诡异,假如你在代码中放上这个函数,那么编译器就不会为了优化而打乱这个函数前后的指令,这也是为什么它取名叫barrier,相当于一个屏障,阻止编译器把后面的指令搬到barrier前面去;
第四个接口是一条汇编指令,暂停程序运行。
其实后面两个接口跟底层原子操作关系不是非常紧密,可能是作者觉得放在这里最合适吧。
前面说过原子操作是基础设施,我们有很多其他的功能要依赖它,比如实现锁
#define ngx_unlock(lock) *(lock) = 0
ngx_trylock顾名思义是尝试着去加锁,能加则加,不能则向调用者报告失败,1代表成功,0代表失败。不过我对这个实现有点儿疑问,直接使用ngx_atomic_cmp_set就可以了啊,为什么还要在前面加一个玩意儿,难道是为了性能?还有一点使用者要注意,lock一定要是ngx_atomic_t类型的,这是个宏,是不会做类型检查的。
ngx_unlock毋庸赘言了吧,就是解锁的。
nginx还为我们实现了自旋锁,它的声明同样放在os/unix/ngx_atomic.h中,实现确是core/ngx_spinlock.c
{
#if (NGX_HAVE_ATOMIC_OPS)
ngx_uint_t i, n;
for ( ;; ) {
if ( * lock == 0 && ngx_atomic_cmp_set( lock , 0 , value)) {
return ;
}
if (ngx_ncpu > 1 ) {
for (n = 1 ; n < spin; n <<= 1 ) {
for (i = 0 ; i < n; i ++ ) {
ngx_cpu_pause();
}
if ( * lock == 0 && ngx_atomic_cmp_set( lock , 0 , value)) {
return ;
}
}
}哦u
ngx_sched_yield();
}
#else
#if (NGX_THREADS)
#error ngx_spinlock() or ngx_atomic_cmp_set() are not defined !
#endif
#endif
}
我以前理解的自旋锁是进程会一直死循环,直到可以申请锁为止。这样就必须要求处理器是多核的,否则就你一个进程在CPU上死循环了。从这个实现可以看出,当CPU是单核时,自旋锁并不无休止地试探,而是申请不到锁时直接挂起让其他进程运行。并且即使CPU是多核的,也没有死循环,这里就有点儿看不懂了,如果哪位网友清楚的话请告诉我。
最后总结下ngx_atomic.h为我们提供了哪些功能吧
ngx_atomic_int_t, ngx_atomic_uint_t, ngx_atomic_t, NGX_ATOMIC_T_LEN 四种类型
int ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old, ngx_atomic_uint_t set);
ngx_atomic_uint_t ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add);
void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin);
void ngx_memory_barrier();
void ngx_cpu_pause();
这篇文章分析了nginx中os/unix/ngx_atomic.h和core/ngx_spinlock.c,希望能和网友多多交流。