尝试获取锁,如果获取了锁,那么还要将当前监听端口全部注册到当前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);
}
}

最新文章

  1. SuiteScript &gt; RecordType internalID采集步骤与结果
  2. 《JavaScript权威指南 第六版 中文版》(一)
  3. 【GoLang】golang底层数据类型实现原理
  4. UVA 11754 (暴力+中国剩余定理)
  5. xcode注释
  6. :first // :last
  7. Android Launcher 详解
  8. Func系列4:其他功能
  9. 创建透明的UIToolbar
  10. USB Type-C 应用面临安全性考验,USB-IF 将推动新认证机制
  11. 【POJ2406】 Power Strings (KMP)
  12. 使用libCurl实现断点下载
  13. Hardwood floor - SGU 131(状态压缩)
  14. 对ExtJS4应用 性能优化的几点建议
  15. 自定义View视图
  16. Git添加远程库和从远程库中获取
  17. mongodb将时间作为条件查询
  18. bzoj1271 秦腾与教学评估
  19. HTTPS IP直连问题小结
  20. 如何同时修改SharePoint帐号和AD帐号的密码 - 批量修改SharePoint Managed Account

热门文章

  1. Pycharm开发环境配置与调试
  2. Tomcat配置Gizp 客户端使用okHttp3
  3. 解决:npm install ERR! Unexpected end of JSON input
  4. MeteoInfoLab脚本示例:加载地图图层
  5. 50种编程语言,一句 “Hello, World”!展现编程语言七十年发展!
  6. 【Flutter 混合开发】嵌入原生View-iOS
  7. Java Map转成xml标签字符串
  8. npm install 几种不同后缀安装模式的区别
  9. 【Azure媒体服务 Azure Media Service】Azure Media Service中Stream Endpoint 说明 (流式处理终结点)
  10. MongoDB 监控 --- MongoDB基础用法(八)