0
点赞
收藏
分享

微信扫一扫

Nginx原子操作及自旋锁实现

Nginx原子操作

​执行原子操作的变量只有整形​

Nginx原子操作及自旋锁实现_原子操作

这两种整型都使用了volatile关键字告诉C编译器不要做优化

​nginx原子操作提供的2个方法​

  • ngx_atomic_cmp_set

入参:
ngx_auomic_t*lock,
ngx_atomic_unit_t old,
ngx_atomic_unit set

Nginx原子操作及自旋锁实现_nginx_02

compare & set

  • ngx_atomic_fetch_add

入参:
ngx_atomic_t*value,
ngx_atomic_int_t add

Nginx原子操作及自旋锁实现_nginx_03

x86的SMP多核架构下的原子操作

当无法实现原子操作时
就只能用volatile 关键字在C语言级别上模拟原子操作
目前绝大多数体系架构都是支持原子操作的

Nginx原子操作及自旋锁实现_寄存器_04

Nginx在源代码中实现对整型的原子操作
需通过内联汇编语言直接操作硬件才能做到

​使用GCC编译器在C语言中嵌入汇编语言的方式使用__asm__关键字​

__asm__volatile(汇编语句部分)
:输出部分
:输入部分
:破坏描述部分

volatile关键字用于限制GCC编译器对这段代码做优化

GCC如何内联汇编语言

汇编语句

引号中所包含的汇编语句可以直接用占位符%来引用C语言中的变量 
(最多10个,%0~%9)

介绍个汇编语句

  • cmpxchgl r,[m]

Nginx原子操作及自旋锁实现_原子操作_05

首先会用m比较eax寄存器中的值
如果相等 则把m的值设为r,zf标志位设为1
否则将zf标志位设为0,寄存器中的值设为m

输出部分

将寄存器中的值设置为C语言变量中

输入部分

将C语言变量设置到寄存器中

破坏描述部分

通知编译器使用了哪些寄存器、内存

ngx_atomic_cmp_set

Nginx原子操作及自旋锁实现_原子操作_06

从内存中获取lock变量(不使用寄存器)
把old变量写入eax寄存器
把set变量写入通用寄存器

首先锁住总线防止多核的并发执行
接着判断原子变量old和old值是否相等
若相等 则把lock值设为set 同时res为1
若不相等 则设res为0

自旋锁

每当内核调度到这个进程执行时
就持续检查是否可以获得到锁
在拿不到锁时
这个进程的代码将会一直在自旋锁代码处执行
直到其他进程释放了锁且当前进程获得到锁
代码才会继续向下执行

​适用场景​

自旋锁主要为多处理器操作系统而设置
解决的共享资源保护场景是进程使用锁的时间非常短
(如果太久 会占用大量的CPU资源)

如果使用锁的进程不希望自己进入睡眠状态
特别它处理的是非常核心的事件时
这时应该使用自旋锁
大部分情况下Nginx的worker进程最好都不要进入睡眠状态
因为它非常繁忙
这个进程的epoll上可能会有十万甚至百万的TCP连接等待着处理
进程一旦睡眠后必须等待其他时间的唤醒
这中间极其频繁的进程间切换带来的负载消耗可能无法让用户接受

​注意​

自选锁对于单处理器操作系统来说一样有效
不进入睡眠状态并不意味着其他可执行状态的进程得不到执行
Linux内核中对于每个处理器都有一个运行队列
自选锁可以仅仅调整当前进程在运行队列中的顺序
或者调整进程的时间片
这都会为当前处理器上的其他进程提供被调度的机会
以使得锁被其他进程释放

​Nginx基于原子操作的自旋锁ngx_spinlock的实现​

入参:

a、

lock是原子变量表达的锁
值为0表示锁是被释放的
值不为0表示锁已经被某个进程持有了

b、value参数表示希望当锁没有被任何进程持有时
(也就是lock值为0)把lock值设为value表示当前进程持有了

c、spin参数表示在多处理器系统内
当ngx_spinlock方法没有拿到锁时
当前进程在内核的一次调度中
该方法等待其他处理器释放锁的时间

​ngx_spinlock实现逻辑​

Nginx原子操作及自旋锁实现_nginx_07

​不要立刻让出CPU​

在多处理器下
更好的做法是当前进程不要立刻让出正在使用的CPU处理器
而是等待一段时间
看看其他处理器上的进程是否会释放锁
这会减少进程间切换的次数

​检查lock是否释放的频率越来越小​

随着等待的次数越来越多
实际去检查lock是否释放的频率会越来越小
因为检查lock值会更消耗CPU
而执行ngx_cpu_pause对于CPU能耗很节省的

​nginx_cpu_pause​

nginx_cpu_pause是在许多架构体系中专门为了自选锁而提供的命令
它会告诉CPU现在处于自选锁等待状态
通常一些CPU会将自己置于节能状态 降低功耗
"注意"在执行ngx_cpu_pause后
当前进程没有让出正使用的处理器

​ngx_sched_yield​

当前进程仍然处于可执行状态
但暂时让出处理器
使得处理器优先调度其他可执行状态的进程
在进程被内核再次调度时
在for循环代码中可以期望其他进程释放锁


举报

相关推荐

0 条评论