上一篇分析了master进程的初始化流程,接着来分析work进程的初始化流程。work进程初始化流程包括:
1、work进程创建;
2、ngx_worker_process_init初始化过程
3、master-work进程通信
4、work进程的事件机制
5、work进程负载均衡实现
一、work进程的创建
在master进程的ngx_start_worker_processes函数中,会调用ngx_spawn_process函数开始创建work进程。创建完成后master进程、work进程同时工作,分别执行不同的业务逻辑。work进程用来处理来自客户端的读写事件,或者将客户端的请求反向代理到上游服务器等。函数ngx_spawn_process用来创建一个work进程,而work进程的处理函数为ngx_worker_process_cycle.
ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
char *name, ngx_int_t respawn)
{
//创建work进程
pid = fork();
switch (pid)
{
case -1:
ngx_close_channel(ngx_processes[s].channel, cycle->log);
return NGX_INVALID_PID;
case 0:
//子进程处理逻辑
ngx_pid = ngx_getpid();
proc(cycle, data);
break;
default:
break;
}
return pid;
}
二、ngx_worker_process_init初始化过程
ngx_worker_process_cycle函数为work进程的入口函数,在该函数内,会调用ngx_worker_process_init函数,对work进程进行初始化操作。
初始化操作主要是为work进程的运行提供必要的环境。例如设置work进程能够打开socket的最大个数;预先分配连接池空间,当有客户端请求时,直接从连接池中获取一个空闲连接;创建epoll对象,使得能够监听客户端的读写事件等。下面以一张思维导图来概括ngx_worker_process_init初始化过程。
1、函数会对work进程的一些基本环境进行初始化操作。例如:
(1)设置work进程的优先级;
(2)设置work进程最大能打开多少文件;
(3)设置work进程core文件的最大值;
(4)设置work进程绑定到特定的cpu
//设置work进程的优先级
if (priority && ccf->priority != 0)
{
if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setpriority(%d) failed", ccf->priority);
}
}
而像ccf->priority这些要设置的值,都是解析nginx.conf配置后得到,保存到core核心模块的上下文中。
2、接下来work进程将调用各个模块实现的init_process方法,用来初始化所有的模块
//调用所有模块的init_process方法,目前只有ngx_event_core_module模块实现了方法。
//方法名为ngx_event_process_init
for (i = 0; ngx_modules[i]; i++)
{
if (ngx_modules[i]->init_process)
{
if (ngx_modules[i]->init_process(cycle) == NGX_ERROR)
{
/* fatal */
exit(2);
}
}
}
目前只有ngx_event_core_module模块实现init_process方法,方法名为ngx_event_process_init。函数内部主要创建work事件模型所需要的epoll对象;以及使用红黑树数实现定时器,用来管理超时事件;注册SIGALRM信号,在信号回调中获取系统时间;预先开辟连接池空间,注册客户端连接事件的回调等等。
2.1、创建红黑树实现的定时器
nginx内部使用红黑树实现定时器功能,用来管理所有超时事件。函数ngx_event_timer_init就是用来初始化一颗红黑树。
//初始化红黑树定时器
if (ngx_event_timer_init(cycle->log) == NGX_ERROR)
{
return NGX_ERROR;
}
初始化好红黑树定时器后,就可以调用ngx_event_add_timer函数往红黑树中插入一个事件,待事件超时后,就会触发超时回调。
如果不想使用超时事件了,则可以调用ngx_event_del_timer函数将超时事件从红黑树中删除,删除后相应的超时回调就不会被触发。
2.2、work进程会对所有事件模块进行初始化操作。
在编译nginx时,系统会优先使用一种事件模型进行编译,一般linux下事件模型为epoll。在每一个平台只会有一个事件模块编译,例如:epoll, select中的一个。
各个事件模块实现的init方法,就是为了创建相应的事件对象。以epoll为例,init函数将创建epoll对象,同时指定epoll每次最多可以从内核中返回多少个事件。
for (m = 0; ngx_modules[m]; m++)
{
if (ngx_modules[m]->type != NGX_EVENT_MODULE)
{
continue;
}
//使用use配置项指定的事件驱动模块,linux下为epoll
if (ngx_modules[m]->ctx_index != ecf->use)
{
continue;
}
module = ngx_modules[m]->ctx;
//调用epoll事件模块的init方法,创建epoll对象与epoll事件方法。
//因为目前只有ngx_epoll_module模块的上下文实现了actions方法,init方法为ngx_epoll_init
if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK)
{
exit(2);
}
break;
}
epoll事件模块实现的init方法为ngx_epoll_init。函数内部会创建epoll对象,同时指定epoll每次最多可以从内核中返回多少个事件。
//epoll模块初始化,创建epoll句柄,同时创建epoll队列
static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
{
//创建epoll对象
if (ep == -1)
{
ep = epoll_create(cycle->connection_n / 2);
if (ep == -1)
{
return NGX_ERROR;
}
}
//创建epoll返回队列,也就是epoll每次从epoll_wait返回时,最多可以返回多少个事件
if (nevents < epcf->events)
{
event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,cycle->log);
}
//返回队列元素个数
nevents = epcf->events;
//保存epoll事件的10个方法
ngx_event_actions = ngx_epoll_module_ctx.actions;
return NGX_OK;
}
有了epoll对象后,就可以将读写事件注册到epoll中,当客户端连接时将触发读事件回调。
2.3 设置定时器,在定时时间到后,会触发SIGALRM信号。这个定时器是用来避免每次事件循环返回都调用gettimeofday函数,而是定时调用,减少调用系统调用频率
itv.it_value.tv_sec = ngx_timer_resolution / 1000;
itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
setitimer(ITIMER_REAL, &itv, NULL);
定时器的回调为ngx_timer_signal_handler,函数内部只是给变量ngx_event_timer_alarm打上标记
//信号处理函数,用于更新标记。目的是为了减少频繁的系统调用更新时间,而采用信号定时器方式获取
//ngx_event_timer_alarm更新后,表示需要进行一次gettimeofday系统调用,获取时间
void ngx_timer_signal_handler(int signo)
{
ngx_event_timer_alarm = 1;
}
这样每次epoll返回时,会根据这个变量决定是否需要进行一次系统调用,从而获取系统时间。ngx_epoll_process_events为ngx_epoll_module模块上下文结构实现的process_events方法
ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
//等待事件返回
events = epoll_wait(ep, event_list, (int) nevents, timer);
//更新缓存时间
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm)
{
ngx_time_update();
}
}
2.4 接下来将会创建连接池空间。每一个连接对象ngx_connection_t,都会关联一个读事件,一个写事件ngx_event_t结构。也就是说,nginx服务器对于每一个客户端连接,都可以处理该客户端的读事件,写事件。nginx通过预先分配一个连接池空间,当有客户端连接时,直接从连接池中获取一个空闲连接。
//预分配连接池
cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
//预分配读事件池
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
//预先分配写事件池
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,cycle->log);
//将连接池,读事件池,写事件池一一关联起来
do
{
i--;
c[i].data = next; //data成员组成一个空闲连接池链表
c[i].read = &cycle->read_events[i];
c[i].write = &cycle->write_events[i];
c[i].fd = (ngx_socket_t) -1;
next = &c[i];
} while (i);
nginx使用数组方式实现了一个连接池链表,使用data成员将各个连接对象ngx_connection_t给连接起来。这样从连接池中获取一个连接后,也相应得到连接对应的读事件,写事件。最后使用cycle->free_connections指针指向空闲连接池链表头;
cycle->free_connections = next; //空闲连接池链表头
以下是可能存在的连接池、读事件池、写事件池的内存布局图
在创建好连接池后,如果有客户端连接,就可以调用ngx_get_connection函数,从连接池中获取一个连接。
2.5 为所有监听socket,从空间连接池中获取一个连接。同时将注册监听socket读事件回调设置为ngx_event_accept。当有客户端连接时,该函数会被调用,用于建立与客户端的连接。
//对于每一个监听端口,从连接池中取出一个连接对象(也将从读时间,写事件池取出对象,
//使得连接,读、写保持一一对应关系),负责监听来自客户端的连接
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++)
{
c = ngx_get_connection(ls[i].fd, cycle->log);
c->listening = &ls[i];
ls[i].connection = c;
rev = c->read;
//设置连接回调,当有客户端连接时,将触发回调
//这里不需要注册写事件回调,应该在没有建立连接时,nginx不可能向客户端写入数据
rev->handler = ngx_event_accept;
//如果work进程之间使用枷锁,则暂时不把读事件加入epoll中,在别处加入。这种方式下,只会有一个work进程将所有的监听socket加入到epoll事件管理器中
if (ngx_use_accept_mutex)
{
continue;
}
//如果work进程之间没有使用枷锁,则把读事件加入epoll中。这种情况下各个work进程将该socket的读连接事件。
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR)
{
return NGX_ERROR;
}
}
如果work进程之间不使用同步,则各个work进程都可以将所有监听socket加入到epoll中。这样当某个监听socket有客户端连接时,各个work将采用抢占方式,最终只会有一个work进程获取该socket的连接权限,处理客户端的连接请求。这容易造成"惊群"现象。在这种方式下,上面函数会将监听socket加入到epoll中。ngx_add_event是一个宏,添加事件的方法为ngx_epoll_add_event。
static ngx_int_t
ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
//data指的是该事件对于的是哪个连接
c = ev->data;
//活跃事件,则说明之前已经注册过事件,现在则应是修改操作
if (e->active)
{
op = EPOLL_CTL_MOD;
events |= prev;
}
else
{
op = EPOLL_CTL_ADD;
}
//注册或者修改事件
epoll_ctl(ep, op, c->fd, &ee);
return NGX_OK;
}
如果work进程之前使用同步方式,则只会有一个work进程能够将所有监听socket加入到epoll中。其它work进程因为获取不到监听socket,从而不会处理来自客户端的连接。因此,同一时刻只会有一个work进程在监听客户端的连接事件。当然如果已经连接的请求,则不影响,各个work进程正常接收客户端的读写事件。在使用同步方式时,上面函数并不会将监听socket加入到epoll中。那什么时候会将监听socket加入到epoll中呢? 在ngx_enable_accept_events函数中,会将监听socket加入到epoll中。
ngx_worker_process_cycle
---->ngx_process_events_and_timers
---->ngx_trylock_accept_mutex
---->ngx_enable_accept_events
static ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle)
{
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++)
{
c = ls[i].connection;
ngx_add_event(c->read, NGX_READ_EVENT, 0);
}
return NGX_OK;
}
3、work进程继承了master进程的进程表。进程表中存放了各个work进程的信息。work进程使用自己的channe[1]管道读取master进程或者其它work进程发来的数据。因此就不需要其它work进程的channel[1]读管道,因此可以关闭。
for (n = 0; n < ngx_last_process; n++)
{
//本work进程只会从自己的channel[1]读取来自master进程或者其它work进程的数据,因此可以关闭其它
//work进程的读端
close(ngx_processes[n].channel[1]);
}
work进程自己不需要channel[0]写管道,因此可以关闭写管道。
//work进程关闭写端,也就是说work进程从channel[1]读取来自master进程或者其它work进程写入管道的数据
//work进程不会写数据给master进程,但work进程会写数据给其它的work进程,则可以在进程表中找到相应work进程,
//并向channel[0]写入数据
if (close(ngx_processes[ngx_process_slot].channel[0]) == -1)
{
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"close() channel failed");
}
4、work进程间通信、以及master进程与work进程通信,使用管道来通信。在上面我们已经创建了epoll对象,同时也预先分配了连接池空间。现在可以将管道加入到epoll中监听。这样就将管道与epoll事件关联起来了。 ngx_add_channel_event函数将从连接池中获取一个连接,并加事件加入到epoll中。
//将管道加入到epoll事件
ngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,
ngx_event_handler_pt handler)
{
ngx_event_t *ev, *rev, *wev;
ngx_connection_t *c;
//从空闲连接队列中获取一个连接对象
c = ngx_get_connection(fd, cycle->log);
c->pool = cycle->pool;
//关联连接对应的读写事件
rev = c->read;
wev = c->write;
rev->channel = 1;
wev->channel = 1;
ev = (event == NGX_READ_EVENT) ? rev : wev;
//设置事件回调
ev->handler = handler;
//将事件添加到epoll中
ngx_add_event(ev, event, 0);
return NGX_OK;
}
将读管道注册到epoll中,读管道的回调为ngx_channel_handler。在master进程向管道写入数据时,epoll被触发,从而可以ngx_channel_handler函数被调用。在该函数内可以读取到数据。下面以master-work进程通信的例子来分析ngx_channel_handler函数的使用。
三、master-work进程通信下面以重新打开文件为例,说明master-work进程的通信过程。当执行./nginx -s reopen时。这个新执行的程序会查找master进程所在的pid文件,进而向master进程发送USR1信号,也就是reopen信号。master进程中,信号函数ngx_signal_handler会被调用,给ngx_reopen打上标记。
void ngx_signal_handler(int signo)
{
//master进程中执行
switch (signo)
{
case ngx_signal_value(NGX_REOPEN_SIGNAL): //用户信号,用来reopen
ngx_reopen = 1; //打上标记
action = ", reopening logs";
break;
}
}
之后master进程的事件循环函数ngx_master_process_cycle,会循环检测是否有reopen标记。如果有,master进程首先将所有已经打开文件缓存中的数据保存到文件中。然后在重新打开所有文件。接着向channe[1]写管道,向work进程写入数据,通知work进程重新打开所有文件。
//master进程工程流程
void ngx_master_process_cycle(ngx_cycle_t *cycle)
{
for ( ;; )
{
//收到重新打开所有文件信号
if (ngx_reopen)
{
ngx_reopen = 0;
ngx_reopen_files(cycle, ccf->user);
ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_REOPEN_SIGNAL));
}
}
}
void ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
{
ch.command = NGX_CMD_REOPEN;
ngx_write_channel(ngx_processes[i].channel[0],&ch, sizeof(ngx_channel_t), cycle->log)
}
在上面已经将work进程的读管道channel[1]已经注册到epoll中。这样master进程通过channel[0]向管道写入数据时,epoll会被触发,进而调用读管道的回调ngx_channel_handler。该函数会从管道中读取数据,并将ngx_reopen打上标记。
static void ngx_channel_handler(ngx_event_t *ev)
{
for ( ;; )
{
//work进程从读管道channel[1]中读取数据,数据内容为ngx_channel_t
n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
switch (ch.command)
{
case NGX_CMD_REOPEN:
ngx_reopen = 1; //work进程给该变量打上标记
break;
}
}
}
work进程的事件循环函数ngx_worker_process_cycle会检测ngx_reopen标记是否为1。如果是,则work进程将所有已经打开文件的缓存写入到文件中保存。然后重新打开文件。
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
for ( ;; )
{
//重新打开所有文件
if (ngx_reopen)
{
ngx_reopen = 0;
ngx_reopen_files(cycle, -1);
}
}
}
四、work进程的事件机制
nginx采用epoll来管理来自客户端的读写事件。work进程的事件循环函数中会调用ngx_process_events_and_timers函数,处理来自客户端的网络事件。
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
for ( ;; )
{
ngx_process_events_and_timers(cycle); //处理epoll网络事件
}
}
在上面2.4小节中,如果work进程需要使用互斥锁来进行同步,那时候并没有将所有监听socket加入到epoll中。使用互斥所情况下ngx_use_accept_mutex这个标记将为1,这个时候应该将所有监听socket加入到epoll中。当前work进程这个时候如果获取到了锁,则将所有监听socket加入到epoll中。如果work进程没有获取到锁,那应该将所有监听socket从epoll中移除。这样就保证了在同一时候,只会有一个work进程监听来自客户端的连接。函数ngx_trylock_accept_mutex就是来实现这个功能。
//获取互斥锁
ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
//获取到锁
if (ngx_shmtx_trylock(&ngx_accept_mutex))
{
//如果进程已经获取到锁了,则直接返回,不需要把监听fd加入到事件中
//什么时候会走入这样逻辑? 进程第一次获取锁成功,并在epoll_wait返回后释放了锁,但没有将
//ngx_accept_mutex_held标记清0。这样所有子进程都可以抢占锁,如果还是本进程先获取到锁,会进入
//这个逻辑?
if (ngx_accept_mutex_held
&& ngx_accept_events == 0
&& !(ngx_event_flags & NGX_USE_RTSIG_EVENT))
{
return NGX_OK;
}
//将所有监听fd加入监听事件中
ngx_enable_accept_events(cycle);
//标示进程拥有锁
ngx_accept_events = 0;
ngx_accept_mutex_held = 1;
return NGX_OK;
}
//未获取到锁,但进程拥有了锁,则是不正确的。应该把所有监听连接都关闭,并标示进程没有拥有锁。
//什么时候会走入这样逻辑? 进程第一次获取锁成功,并在epoll_wait返回后释放了锁,但没有将
//ngx_accept_mutex_held标记清0。这样所有子进程都可以抢占锁,在其他子进程先获取到情况下,会进入
//这个逻辑。
if (ngx_accept_mutex_held)
{
if (ngx_disable_accept_events(cycle) == NGX_ERROR)
{
return NGX_ERROR;
}
ngx_accept_mutex_held = 0;
}
return NGX_OK;
}
ngx_enable_accept_events负责将所有监听socket加入到epoll, ngx_disable_accept_events则将所有监听socket从epoll中移除。
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle,
ngx_msec_t timer,
ngx_uint_t flags)
{
//等待事件返回
events = epoll_wait(ep, event_list, (int) nevents, timer);
//更新缓存时间
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm)
{
ngx_time_update();
}
//遍历所有已经发生的事件
for (i = 0; i < events; i++)
{
c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
rev = c->read;
revents = event_list[i].events;
//如果是读事件且事件时活跃的
if ((revents & EPOLLIN) && rev->active)
{
//标示这个事件要延后处理
if (flags & NGX_POST_EVENTS)
{
//如果要在post队列中延后处理事件,首先要判断它是新连接还是普通事件,从而决定要把
//事件放到哪个队列。连接队列:存放来自客户端的连接。
//普通队列: 存放已经建立连接的客户端,进行读写事件通信
queue = (ngx_event_t **) (rev->accept ?
&ngx_posted_accept_events : &ngx_posted_events);
//将事件放入相应的延后队列
ngx_locked_post_event(rev, queue);
}
else
{
//不是延迟事件,则立即调用读事件回调
rev->handler(rev);
}
}
wev = c->write;
//取出写事件
if ((revents & EPOLLOUT) && wev->active)
{
//将事件添加到延迟队列中
if (flags & NGX_POST_EVENTS)
{
ngx_locked_post_event(wev, &ngx_posted_events);
}
else
{
//立即调用写事件的回调
wev->handler(wev);
}
}
}
return NGX_OK;
}
work进程阻塞在epoll_wait系统调用中。如果有事件发生,则work进程被唤醒。进而判断事件是否需要延迟处理。如果不需要延迟处理,则立即调用事件的处理函数。如果需要延迟处理,则将事件加入到队列中,稍后再对队列中的事件进行处理。nginx使用了两个队列,一个连接队列,存放新客户端的连接。另一个普通队列,存放所有已经建立连接后的客户端。这两个队列中,连接队列的优先级更高。也就是说在同一个时刻,有新客户端的连接,也有老客户的读写事件请求。则优先处理新客户端的连接事件。
//调用epoll_wait等待事件
(void) ngx_process_events(cycle, timer, flags);
//计算处理事件消耗多长时间
delta = ngx_current_msec - delta;
//优先处理连接队列中的所有连接
if (ngx_posted_accept_events)
{
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
}
//处理完连接队列后,要立即释放互斥锁,避免长事件占用锁,而不能在普通读写事件处理完后才释放锁。
//因为有可能普通事件处理的时间很长,导致锁没释放,其它子进程无法处理新的连接
if (ngx_accept_mutex_held)
{
//此处最好也调用ngx_disable_accept_events清除监听socket,而不需要等待循环把监听socket清除
//因为释放锁后,其它子进程有可能马上获取到了锁,并把监听socket加入到epoll。
//这样导致多个work进程都在监听同一个socket
ngx_shmtx_unlock(&ngx_accept_mutex);
}
//消耗了时间,有可能红黑树中有超时事件,则处理红黑树中所有超时事件,并把超时事件从红黑树中删除
if (delta)
{
ngx_event_expire_timers();
}
//最后才处理普通事件队列中的读写事件
if (ngx_posted_events)
{
ngx_event_process_posted(cycle, &ngx_posted_events);
}
而ngx_event_process_posted实际上只是调用事件的处理函数。
//事件post事件队列中的事件,处理完后并将事件从队列中删除
//注意:删除事件并没有真正把ngx_event_t删除,而是将prev,next指针从post队列中断开
void ngx_event_process_posted(ngx_cycle_t *cycle,
ngx_thread_volatile ngx_event_t **posted)
{
ngx_event_t *ev;
for ( ;; )
{
ev = (ngx_event_t *) *posted;
ngx_delete_posted_event(ev);
ev->handler(ev);
}
}
五、work进程间负载均衡
每一个work进程都可以监听来自客户的连接,使用互斥锁方式实现work进程间的同步。在某些情况下,有可能某个work进程处理着大量的客户端连接,处于高负载工作状态。而其他work进程可能只处理着少量的客户端连接,这样就造成负载不均,影响整体性能。那nginx是如何处理work进程之间的负载均衡呢?
在2.4中将所有监听socket加入到epoll时,注册了读事件的回调为ngx_event_accept;在函数中会对ngx_accept_disabled变量进行统计。如果work进程处理的客户端连接数达到了work进程连接的7/8时,说明work进程处于高负载运行状态,此时ngx_accept_disabled将大于0,不能在处理新客户端的连接事件了。
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
c = ngx_get_connection(s, ev->log);
那work进程根据这个ngx_accept_disabled这个变量,什么时候确定是否还能允许新客户端连接呢? 在函数ngx_process_events_and_timers中会对这个变量值进行判断。如果该变量值大于0,说明work进程处于高负载运行状态,此时不能处理新客户端连接。函数中当值大于0时,只是把变量值减1操作。并不会尝试去获取锁,也就不会将所有监听socket加入到epoll中。因为没有把监听socket加入到epoll中,因此该work进程也就不会处理新客户端的连接。这样本work进程不参数互斥锁的抢占,其它的work进程可以获取到锁后,处理新客户端的连接,从而实现work进程之间的负载均衡。当然,在变量值递减到0以下时,本work进程还是会再次尝试获取锁操作。
void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
//nginx.conf配置了accept_mutex on, 标示使用锁
if (ngx_use_accept_mutex)
{
//当值为正数时,本work进程不处理新连接事件,而是将值递减1。
//此处怀疑有bug,最好调用ngx_disable_accept_events清除监听socket
if (ngx_accept_disabled > 0)
{
ngx_accept_disabled--;
}
else
{
//尝试获取锁
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR)
{
return;
}
}
}
}
到此work进程的初始化操作就全部分析完成了。