8 nginx锁(spinlock,ngx_shmtx_t)

本文详细介绍了原子操作的实现方式,包括使用LOCK指令前缀和特定的汇编指令(如xadd和cmpxchg),并深入探讨了自旋锁及进程锁的实现原理。同时,文章还展示了如何在多处理器环境中利用这些技术来确保并发操作的一致性和安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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,表示解锁,然后唤醒等待
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值