ngx accept_mutex

尝试获取锁,如果获取了锁,那么还要将当前监听端口全部注册到当前worker进程的epoll当中去  获取失败就需要确保此时ls-fd 没有被 epoll 监听

ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
    if (ngx_shmtx_trylock(&ngx_accept_mutex)) {//

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "accept mutex locked");

        //如果本来已经获得锁,则直接返回Ok   
        if (ngx_accept_mutex_held && ngx_accept_events == 0) {
            return NGX_OK;
        }

   //到达这里,说明重新获得锁成功,因此需要打开被关闭的listening句柄,调用ngx_enable_accept_events函数,将监听端口注册到当前worker进程的epoll当中去   
        if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
            ngx_shmtx_unlock(&ngx_accept_mutex);
            return NGX_ERROR;
        }

        ngx_accept_events = 0;
        ngx_accept_mutex_held = 1; ////表示当前获取了锁   

        return NGX_OK;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "accept mutex lock failed: %ui", ngx_accept_mutex_held);

//这里表示的是以前曾经获取过,但是这次却获取失败了,那么需要将监听端口从当前的worker进程的epoll当中移除,调用的是ngx_disable_accept_events函数   
-----当前进程没有获取到锁,证明是由别的进程获取到了。如果ngx_accept_mutex_held的值为1,证明该锁原来是由
    //本进程持有,即监听socket原先是加入到了本进程的事件驱动机制当中的。因此这里在进入下一次事件驱动机制(select/
    // poll/eploll)之前,我们需要先disable掉
    if (ngx_accept_mutex_held) {
        if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }

        ngx_accept_mutex_held = 0;
    }

    return NGX_OK;
}


static ngx_int_t
ngx_enable_accept_events(ngx_cycle_t *cycle)
{
    ngx_uint_t         i;
    ngx_listening_t   *ls;
    ngx_connection_t  *c;

    ls = cycle->listening.elts;

    /*本进程ngx_enable_accept_events把所有listen加入本进程epoll中后,本进程获取到ngx_accept_mutex锁后,在执行accept事件的
    过程中如果如果其他进程也开始ngx_trylock_accept_mutex,如果之前已经获取到锁,并把所有的listen添加到了epoll中,这时会因为没法获取到
    accept锁,而把之前加入到本进程,但没有accept过的时间全部清除。和ngx_disable_accept_events配合使用
    最终只有一个进程能accept到同一个客户端连接
     */
    for (i = 0; i < cycle->listening.nelts; i++) { 

        c = ls[i].connection;

        //后面的ngx_add_event->ngx_epoll_add_event中把listening中的c->read->active置1, ngx_epoll_del_event中把listening中置read->active置0
        if (c == NULL || c->read->active) { //之前本进程已经添加过,不用再加入epoll事件中,避免重复
            continue;
        }

        char tmpbuf[256];
        
        snprintf(tmpbuf, sizeof(tmpbuf), "<%25s, %5d> epoll NGX_READ_EVENT(et) read add", NGX_FUNC_LINE);
        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, tmpbuf);
        if (ngx_add_event(c->read, NGX_READ_EVENT, 0) == NGX_ERROR) { //ngx_epoll_add_event
            return NGX_ERROR;
        }
    }

    return NGX_OK;
}

1

/addr为共享内存ngx_shm_alloc开辟的空间中的一个128字节首地址 --cache 长度
ngx_int_t
ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name)
{
    mtx->lock = &addr->lock;    //直接执行共享内存空间addr中的lock区间中

    if (mtx->spin == (ngx_uint_t) -1) { //注意,当spin值为-1时,表示不能使用信号量,这时直接返回成功
        return NGX_OK;
    }
mtx->lock 原子操作数,在加锁时,作为判断条件值。
mtx->spin 用于判断mtx->lock的次数,nginx的锁不是盲目的轮询判断或者判断只判断一次,它是选取了一个spin的循环判断次数,超过次数,让出cpu或者等待信号处理。
    mtx->spin = 2048; //spin值默认为2048

//同时使用信号量
#if (NGX_HAVE_POSIX_SEM)
    mtx->wait = &addr->wait;

    /*
    int  sem init (sem_t  sem,  int pshared,  unsigned int value) ,
    其中,参数sem即为我们定义的信号量,而参数pshared将指明sem信号量是用于进程间同步还是用于线程间同步,当pshared为0时表示线程间同步,
    而pshared为1时表示进程间同步。由于Nginx的每个进程都是单线程的,因此将参数pshared设为1即可。参数value表示信号量sem的初始值。
     */
    //以多进程使用的方式初始化sem信号量,sem初始值为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; //在信号量初始化成功后,设置semaphore标志位为1
    }

#endif

    return NGX_OK;
}
/*
首先是判断mtx的lock域是否等于0,如果不等于,那么就直接返回false好了,如果等于的话,那么就要调用原子操作ngx_atomic_cmp_set了,
它用于比较mtx的lock域,如果等于零,那么设置为当前进程的进程id号,否则返回false。
*/ 
ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
    return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}

使用ngx_pid 对lock赋值,ngx_pid用于区分进程,不可能重复

接下来是一个for循环判断,判断多次无果,才进行下一步休眠操作或等待操作,

接下来是一个选择,在自旋锁直接使用ngx_sched_yield函数,让出cpu,等待下次判断,在共享内存锁多了一个信号量的选择。

等待sem_post唤醒,此时阻塞。

sem_wait和ngx_sched_yield的对比,sem_wait是一个等待通知的机制,sched_yield是一个循环遍历的机制,在自旋锁使用sched_yield是因为自旋时间断,快速循环遍历,在共享内存锁中,可能需要等待时间长,使用sem机制,避免cpu浪费,

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 ( ;; ) {
 //lock值是当前的锁状态。注意,lock一般是在共享内存中的,它可能会时刻变化,而val是当前进程的栈中变量,下面代码的执行中它可能与lock值不一致
        if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
            return;
        }
        //仅在多处理器状态下spin值才有意义,否则PAUSE指令是不会执行的
        if (ngx_ncpu > 1) {
            //循环执行PAUSE,检查锁是否已经释放
            for (n = 1; n < mtx->spin; n <<= 1) {
                //随着长时间没有获得到锁,将会执行更多次PAUSE才会检查锁
                for (i = 0; i < n; i++) {
                    ngx_cpu_pause();
                }

                //再次由共享内存中获得lock原子变量的值
                if (*mtx->lock == 0// 使用ngx_pid 对lock赋值,ngx_pid用于区分进程,不可能重复,非常巧妙
                    && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid))
                {
                    return;
                }
            }
        }

#if (NGX_HAVE_POSIX_SEM) //支持信号量时才继续执行

        if (mtx->semaphore) {//semaphore标志位为1才使用信号量
            (void) ngx_atomic_fetch_add(mtx->wait, 1);

            //重新获取一次可能虚共享内存中的lock原子变量
            if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) {
                (void) ngx_atomic_fetch_add(mtx->wait, -1);
                return;
            }

            ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,
                           "shmtx wait %uA", *mtx->wait);

            //如果没有拿到锁,这时Nginx进程将会睡眠,直到其他进程释放了锁
            /*
                检查信号量sem的值,如果sem值为正数,则sem值减1,表示拿到了信号量互斥锁,同时sem wait方法返回o。如果sem值为0或
                者负数,则当前进程进入睡眠状态,等待其他进程使用ngx_shmtx_unlock方法释放锁(等待sem信号量变为正数),到时Linux内核
                会重新调度当前进程,继续检查sem值是否为正,重复以上流程
               */
            while (sem_wait(&mtx->sem) == -1) {
                ngx_err_t  err;

                err = ngx_errno;

                if (err != NGX_EINTR) {//当EINTR信号出现时,表示sem wait只是被打断,并不是出错
                    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; //循环检查lock锁的值,注意,当使用信号量后不会调用sched_yield
        }

#endif

        ngx_sched_yield(); //在不使用信号量时,调用sched_yield将会使当前进程暂时“让出”处理器
    }
}
//判断锁的lock域与当前进程的进程id是否相等,如果相等的话,那么就将lock设置为0,然后就相当于释放了锁。
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");
    }
//ngx_atomic_cmp_set 将lock设置0,执行ngx_shmtx_wakeup。如果使用ngx_sched_yield休眠,ngx_shmtx_wakeup函数无意义,ngx_shmtx_wakeup主要唤醒sem。
    if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {
        ngx_shmtx_wakeup(mtx);
    }
}
原文地址:https://www.cnblogs.com/codestack/p/13467142.html