Nginx(八) 工作进程
ngx_event_find_timer()函数。
nginx中的timer用红黑树的结构排序。ngx_event_timer_rbtree就是nginx中timer的红黑树。Ngx_event_timer_rbtree的结构如下:
typedef struct ngx_rbtree_s ngx_rbtree_t;
typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
struct ngx_rbtree_s {
ngx_rbtree_node_t *root;
ngx_rbtree_node_t *sentinel;
ngx_rbtree_insert_pt insert;
};
树中节点的结构:
typedef ngx_uint_t ngx_rbtree_key_t;
typedef ngx_int_t ngx_rbtree_key_int_t;
typedef struct ngx_rbtree_node_s ngx_rbtree_node_t;
struct ngx_rbtree_node_s {
ngx_rbtree_key_t key;
ngx_rbtree_node_t *left;
ngx_rbtree_node_t *right;
ngx_rbtree_node_t *parent;
u_char color;
u_char data;
};
ngx_event_find_timer()逻辑如下:
1. ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel时,返回NGX_TIMER_INFINITE(即-1);
2.否则,对ngx_event_timer_mutex上锁;
3.调用ngx_rbtree_min(root, sentinel)取得最小值的timer节点;
4.解锁ngx_event_timer_mutex;
5.用最小值的timer节点的值减去ngx_current_msec值;
6.步骤5中计算的值大于0时,返回这个值,否则返回0。
Nginx里面,当前所有可能被触发的定时器被保存在红黑树这种数据结构中,通过红黑树,你可以很快的得到距离当前最快发生的定时器事件的时间差。将这个时间差作为select/poll/epoll等函数的参数,也就是,最多等待这么长时间就返回。当函数返回时,得到函数调用总共花费了多少时间,根据这个时间取出红黑树的根节点比较查看是否应该触发该定时器时间,如果可以则将该定时器从红黑树中删除,然后继续查看新的成为树根的定时器节点,这个过程一直进行下去直到没有定时器满足被触发的条件,也就是还没有到被触发的事件。
Nginx里面,新接收了一个连接,会保存这个连接上来的时间,并且以这个时间来加入红黑树定时器中。
可以看到,因为引入了红黑树这个数据结构,所有的定时器都可以按照顺序来依次取出,这样不用轮询所有事件来查看是否超时了;而以距离当前最快发生的定时器事件时间差作为轮询的定时,又可以不用使用alarm信号来触发定时,一举两得。
ngx_process_events_and_timers()函数。
1. ngx_timer_resolution不为0时,timer值设为NGX_TIMER_INFINITE,flag为0;
否则,使用ngx_event_find_timer()去计算timer值,flags为NGX_UPDATE_TIME。
如果线程支持时,timer == NGX_TIMER_INFINITE || timer > 500情况下,timer设为500。
(注:ngx_timer_resolution值来自配置文件中的timer_resolution项。
语法: timer_resolution t
缺省值: none Example: timer_resolution 100ms; The directive allows to decrease number gettimeofday() syscalls.By缺省值 gettimeofday() is called after each return from kevent(), epoll, /dev/poll, select(), poll(). But if you need an exact time in logs when logging $upstream_response_time, or $msec variables, then you should use timer_resolution。)
2. ngx_use_accept_mutex为1时,
a) ngx_accept_disabled > 0时,ngx_accept_disabled--;
b)否则调用ngx_trylock_accept_mutex()试着给cycle上锁;
c)上锁成功,如果ngx_accept_mutex_held不为0,flags |= NGX_POST_EVENTS;
否则查看timer是否是NGX_TIMER_INFINITE或者timer > ngx_accept_mutex_delay,为真时,ngx_accept_mutex_delay设为timer;
d) 调用ngx_process_events();(ngx_process_events是个宏,实际是ngx_event_actions.process_events。在不同系统中具体调用不同的函数。)
注:accept_mutex这个值来自哪能里?有什么用?下面是从wiki.nginx.org网站上找到的。
accept_mutex
Syntax: accept_mutex [ on | off ]
Default: on
nginx uses accept mutex to serialize accept() syscalls.
accept_mutex_delay
Syntax: accept_mutex_delay Nms;
Default: 500ms
If a worker process does not have accept mutex it will try to aquire it at least after this delay. By default delay is 500ms.
先看ngx_process_events这个宏,明显它才是关键部分。
ngx_process_events():
1. 找到它有定义 #define ngx_process_events ngx_event_actions.process_events;
2. 继续找ngx_event_actions,它的结构体如下定义:
typedef struct {
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*add_conn)(ngx_connection_t *c);
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
ngx_int_t (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags);
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
1. 在nginx源代码中搜索ngx_event_action关键字。发现如下结果:
Ngx_aio_module.c (src/event/modules): ngx_event_actions = ngx_aio_module_ctx.actions;
Ngx_devpoll_module.c (src/event/modules): ngx_event_actions = ngx_devpoll_module_ctx.actions;
Ngx_epoll_module.c (src/event/modules): ngx_event_actions = ngx_epoll_module_ctx.actions;
Ngx_event.c (src/event):ngx_event_actions_t ngx_event_actions;
…
前面三行表示,不同event模块对象中的actions就是ngx_event_ations_t对象,而ngx_event_actions在第四行定义成一个全局变量,明显是用来作统一接口用的。那么,就一个event模块看看具体它负责什么工作。就epoll了。
2. 先找到ngx_event_module_t的结构体。
typedef struct {
ngx_str_t *name;
void *(*create_conf)(ngx_cycle_t *cycle);
char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
ngx_event_actions_t actions;
} ngx_event_module_t;
name就不说了,当然是模块名称了。create_conf钩子调函数主要是创建一块内存用来保存配置信息;init_conf钩子函数当然是初始化用的。重要的是actions成员。看epoll模块的上下文变量定义。
ngx_event_module_t ngx_epoll_module_ctx = {
&epoll_name,
ngx_epoll_create_conf, /* create configuration */
ngx_epoll_init_conf, /* init configuration */
{
ngx_epoll_add_event, /* add an event */
ngx_epoll_del_event, /* delete an event */
ngx_epoll_add_event, /* enable an event */
ngx_epoll_del_event, /* disable an event */
ngx_epoll_add_connection, /* add an connection */
ngx_epoll_del_connection, /* delete an connection */
NULL, /* process the changes */
ngx_epoll_process_events, /* process the events */
ngx_epoll_init, /* init the events */
ngx_epoll_done, /* done the events */
}
};
难得有点注释呀!
ngx_epoll_process_events()这个函数就是我们要找的,调用ngx_process_events()实际上就是调用它(我是指linux环境下且支持epoll时)。累呀,找个函数都这么繁烦!
3. ngx_epoll_process_events()函数。这个函数大约150多行。一开始就调用epoll_wait()。终于见到亲人了。
events = epoll_wait(ep, event_list, (int) nevents, timer);
7.ngx_epoll_process_events()函数的主要逻辑。
epoll_wait();
检查有没有出错;
If 有出错 then
出错处理;
End if
If 没有任何事件发生 then
If timer不是无限超时 then
返回ok;
End if
返回Error;
End if
ngx_posted_events_mutex上锁;(ngx_posted_events_mutex只有在NGX_THREAD宏定义有效时才有效。)
foreach 发生的epoll事件对象 do
从发生的epoll事件对象中取得ngx_connection_t对象;
ngx_connection_t对象中取出读事件;
一些查错工作;
If 读事件且ready状态 then
If (flags & NGX_POST_THREAD_EVENTS) && !rev->accept then
该读事件设为posted_ready状态;
Else
该读事件设为ready状态;
End if
If flags & NGX_POST_EVENTS then
/* 表示不立即处理,要事件排队. */
If 该读事件为accept状态 then
将其放入ngx_posted_accept_events事件队列中;
Else
将其放入ngx_posted_events事件队列中;
End if
Else
调用这个读事件的处理函数;(通常就是读取数据了)
End if
End if
ngx_connection_t对象中取出写事件;
If 写事件且ready状态 then
If flags & NGX_POST_THREAD_EVENTS then
该写事件设为posted_ready状态;
Else
该写事件设为ready状态;
End if
If flags & NGX_POST_EVENTS then
/* 表示不立即处理,要事件排队. */
将其放入ngx_posted_events事件队列中;
Else
调用这个写事件的处理函数;(通常就是写入数据了)
End if
End if
End foreach;
ngx_posted_events_mutex解锁;
其中用到了ngx_locked_post_event()这个宏,它把事件放到事件队列的头部。