1. 原子变量
1.1 c语言嵌入汇编的写法
asm("汇编语句"
:输出寄存器
:输入寄存器
:会被修改的寄存器);
1.2 与原子变量实现相关的汇编指令
1.2.1 lock前缀指令
LOCK指令前缀会设置处理器的LOCK信号,直到使用LOCK前缀的指令执行结束,这会使这条指令的执行变为原子操作。在多处理器环境下,设置LOCK信号能保证某个处理器对共享内存的独占使用。
只能操作下列指令:
ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B,CMPXCHG16B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, XCHG
1.2.2 xadd指令
nginx中的解释
"xaddl r, [m]":
temp = [m];
[m] += r;
r = temp;
左操作数保存右操作数的值
右操作数+=左操作数
1.2.3 cmpxchgl指令
nginx中的解释
"cmpxchgl r, [m]":
if (eax == [m]) {
zf = 1;
[m] = r;
} else {
zf = 0;
eax = [m];
}
1.2.4 sete指令
如果设置了零标志(zf),则sete
指令(及其等价物, setz
)将其参数设置为1
,否则设置为0
1.3 原子操作实现
1.3.1 cmp_set比较赋值
#define NGX_SMP_LOCK "lock;"
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 //lock;
" cmpxchgl %3, %1; "
" sete %0; "
: "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");
return res;
}
- 如果*lock和old相等就把 *lock设置为set 返回1,否者返回0
汇编部分解析
-
输出部分:’=a’(res)表示输出在eax寄存器,然后eax赋值给res
-
输入部分:‘m’(*lock)表示 *lock的值保存在内存中,“a”(old) 把old的值给eax寄存器(用于cmpxchgl比较),“r”(set)表示把set的值任意给一个寄存器
-
破坏部分:"cc"表示会改变eflags寄存器,"memory"表示会改变内存的值
-
%0 ~ %4表示从输出部分开始从左到右算
套入解析
"cmpxchgl %3, %1;"
eax = old //"a" (old)
[m] = *lock //"m" (*lock)
if (eax == [m]) {
zf = 1;
[m] = set;
} else {
zf = 0;
eax = [m];
}
"sete %0;"
eax = zf
res = eax
1.3.2 add操作
#define NGX_SMP_LOCK "lock;"
static ngx_inline ngx_atomic_int_t
ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
{
__asm__ volatile (
NGX_SMP_LOCK //lock;
" xaddl %0, %1; "
: "+r" (add) : "m" (*value) : "cc", "memory");
return add;
}
"+"表示输入输出
2. 自旋锁的实现
2.1 加锁
void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
{
ngx_uint_t i, n;
for ( ;; ) {
//如果lock == 0 表示没有其他线程获取到锁
//尝试加锁
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;
}
}
}
//让出cpu
ngx_sched_yield();
}
}
-
*lock = 0 表示没有加锁,非零表示加锁
-
如果加锁不成功并且只有一个核那么该线程会直接让出cpu,让cpu调度其他
-
如果加锁不成功并且是多核,那么将进入循环加锁的过程,随着加锁失败次数增加空转会越来越久(每次乘2增加),直到n > spin还没有加锁成功则直接让出cpu
2.2 解锁
ngx_atomic_cmp_set(lock, *lock, 0);
只要把lock的值设置为0就可以
3. nginx进程锁
3.1 nginx锁定义
typedef struct {
ngx_atomic_t lock;
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_t wait;
#endif
} ngx_shmtx_sh_t;
typedef struct {
#if (NGX_HAVE_ATOMIC_OPS)
ngx_atomic_t *lock; //自旋锁
#if (NGX_HAVE_POSIX_SEM)//支持信号量
ngx_atomic_t *wait; //信号量的数量 和 sem同步的值
ngx_uint_t semaphore; //信号量使用的标志
sem_t sem; //信号量
#endif
#else//不支持原子操作 使用文件锁
ngx_fd_t fd; //文件锁
u_char *name;
#endif
ngx_uint_t spin; //和自旋锁等待相关的变量,一次轮完之后没有加锁成功就进入休眠
} ngx_shmtx_t;
3.2 操作方法
3.2.1 创建和销毁
ngx_int_t ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
{
mtx->lock = &addr->lock;
//如果spin = (ngx_uint_t) -1) 不使用信号量
if (mtx->spin == (ngx_uint_t) -1) {
return NGX_OK;
}
mtx->spin = 2048;
#if (NGX_HAVE_POSIX_SEM)
mtx->wait = &addr->wait;
//1表示进程同步 0是线程同步
if (sem_init(&mtx->sem, 1, 0) == -1) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
"sem_init() failed");
} else {
mtx->semaphore = 1; //信号量使用的标志
}
#endif
return NGX_OK;
}
void ngx_shmtx_destroy(ngx_shmtx_t *mtx)
{
#if (NGX_HAVE_POSIX_SEM)
//使用信号量
if (mtx->semaphore) {
if (sem_destroy(&mtx->sem) == -1) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
"sem_destroy() failed");
}
}
#endif
}
-
锁的创建,主要初始化ngx_shmtx_t。要注意的是如果mtx->spin == (ngx_uint_t) -1 则不会使用信号量
-
锁的销毁,主要销毁信号量
3.2.2 加锁
ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}
//加锁
void
ngx_shmtx_lock(ngx_shmtx_t *mtx)
{
ngx_uint_t i, n;
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx lock");
for ( ;; ) {
//使用自旋锁
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
return;
}
if (ngx_ncpu > 1) {
for (n = 1; n < mtx->spin; n <<= 1) {
for (i = 0; i < n; i++) {
ngx_cpu_pause();
}
if (*mtx->lock == 0
&& ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid))
{
return;
}
}
}
#if (NGX_HAVE_POSIX_SEM)
//自旋锁加锁失败 使用信号量
if (mtx->semaphore) {
(void) ngx_atomic_fetch_add(mtx->wait, 1); //等待+1
//最后一次尝试加锁
if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
(void) ngx_atomic_fetch_add(mtx->wait, -1);//加锁成功等待-1
return;
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"shmtx wait %uA", *mtx->wait);
//使用信号量进入休眠
while (sem_wait(&mtx->sem) == -1) {
ngx_err_t err;
err = ngx_errno;
if (err != NGX_EINTR) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err,
"sem_wait() failed while waiting on shmtx");
break;
}
}
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"shmtx awoke");
continue;
}
#endif
//让出cpu(没有使用信号量才会执行)
ngx_sched_yield();
}
}
- 如果没有使用信号量就和之前的自旋锁没有什么区别
- 如果使用了信号量,并且自旋锁一轮过来(spin消耗完全)都没有加锁成功,则会尝试最后一次加锁,如果加锁失败则使用信号量进行休眠等待唤醒。
3.2.3 解锁
static void ngx_shmtx_wakeup(ngx_shmtx_t *mtx)
{
#if (NGX_HAVE_POSIX_SEM)
ngx_atomic_uint_t wait;
//没用使用信号量
if (!mtx->semaphore) {
return;
}
for ( ;; ) {
wait = *mtx->wait;
if ((ngx_atomic_int_t) wait <= 0) {
return;
}
//等待-1
if (ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)) {
break;
}
}
ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
"shmtx wake %uA", wait);
//信号量-1,唤醒等待
if (sem_post(&mtx->sem) == -1) {
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno,
"sem_post() failed while wake shmtx");
}
#endif
}
void ngx_shmtx_unlock(ngx_shmtx_t *mtx)
{
if (mtx->spin != (ngx_uint_t) -1) {
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx unlock");
}
//自旋锁解锁 => mtx->lock = 0
if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {
ngx_shmtx_wakeup(mtx);//如果使用了信号量则唤醒其他进程
}
}
- 把 *locks设置为0,表示解锁,然后唤醒等待