Nginx 源码分析 - Nginx启动以及IOCP模型
版本及平台信息
本文档针对Nginx1.11.7版本,分析Windows下的相关代码,虽然服务器可能用linux更多,但是windows平台下的代码也基本相似
,另外windows的IOCP完成端口,异步IO模型非常优秀,很值得一看。
Nginx启动
曾经有朋友问我,面对一个大项目的源代码,应该从何读起呢?我给他举了一个例子,我们学校大一大二是在紫金港校区,到了
大三搬到玉泉校区,但是大一的时候也会有时候有事情要去玉泉办。偶尔会去玉泉,但是玉泉校区不熟悉,于是跟着百度地图或者
跟着学长走。因为是办事情,所以一般也就是局部走走,比如在学院办公楼里面走走。等到大三刚来到玉泉,会发现,即使是自己
以前来过几次,也觉得这个校区完全陌生,甚至以前来过的地方,也显得格外生疏。但是当我们真正在玉泉校区开始学习生活了,
每天从寝室走到教室大多就是一条路,教超就是另一条路,这两条主要的路走几遍之后,有时候顺路去旁边的小路看看,于是慢慢
也熟悉了这个新的校区。
源代码的阅读又何尝不是这样呢,如果没有一条主要的路线,总是局部看看,浅尝辄止不说,还不容易把握整体的结构。各模块之间
的依赖也容易理不清。如果有一条比较主干的线路,去读源代码,整体结构和思路也会变得明晰起来。当然我也是持这种看法:博客、
文章的作者,写文章的思路作者自己是清楚的,读者却不一定能看得到;而且大家写东西都难免会有疏漏。看别人写的源码分析指引
等等,用一种比较极端的话来说,是一种自我满足,觉得自己很快学到了很多源码级别的知识,但是其实想想,学习乎,更重要的是
学习能力的锻炼,通过源码的学习,学习过程中自己结合自己情况的思考,甚至结合社会哲学的思考,以及读源码之后带来的收益,
自己在平时使用框架、库的时候,出了问题的解决思路,翻阅别人源码来找到bug的能力。如果只是单单看别人写的源码分析,与写
代码的时候只去抄抄现成的代码,某种程度上是有一定相似性的。
我自己是使用go
为主的,之前对于一流的nginx
中间件也没有太多了解,也是第一次去看,水平不足之处,还望海涵。回归正题,
Nginx的源代码分析,也是要找一条主要的路线,对于很多程序来说,启动过程就是一条很不错的路线,找找nginx的入口函数main
,发现在/src/core/nginx.c中,代码大概如下:
int ngx_cdecl main(int argc, char *const *argv) {
... // 先是一些变量声明
ngx_debug_init();
...
ngx_pid = ngx_getpid();
...
init_cycle.pool = ngx_create_pool(1024, log);
...
cycle = ngx_init_cycle(&init_cycle);
...
if (ngx_signal) {
return ngx_signal_process(cycle, ngx_signal);
}
...
if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
return 1;
}
...
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);
} else {
ngx_master_process_cycle(cycle);
}
return 0
}
这段代码大致看上去,先是做了一些初始化的事情,包括pool
看起来应该是内存池之类的变量的分配,获取系统信息,
初始化日志系统等等,因为还没有进入相应函数去仔细看,所以先放着。用过nginx
的同学应该了解,nginx
命令行
运行./nginx
后,他直接就运行服务了,很静默,然后即使用Ctrl+C
也关不掉。但是再开一个console
,运行
./nginx -h
就会看到:
nginx version: nginx/1.11.7
Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]
Options:
-?,-h : this help
-v : show version and exit
-V : show version and configure options then exit
-t : test configuration and exit
-T : test configuration, dump it and exit
-q : suppress non-error messages during configuration testing
-s signal : send signal to a master process: stop, quit, reopen, reload
-p prefix : set prefix path (default: NONE)
-c filename : set configuration file (default: conf/nginx.conf)
-g directives : set global directives out of configuration file
这是nginx
的命令行参数介绍,要退出nginx
需要用nginx -s stop
给已经打开的nginx
进程发送信号,让其退出。
而且nginx
还支持平滑的重启,这种重启在更改nginx
配置时非常有用,重启服务器的过程,实际上是nginx
自己内部
的一种处理,重新载入新的配置,但是却不影响已经有的一些连接,所以称之为平滑重启。
gracefully stop nginx…
而main
函数在初始化之后,做的就是命令行参数的解析,如果是显示版本,那么显示一个版本信息,就退出;如果是设置
配置文件,那么去调用设置配置文件的相应处理;如果是发送控制信号,那么return ngx_signal_process(cycle, ngx_signal);
处理信号等等。这里还有个小trick,就是关于pid文件,程序把自己的pid写入一个文件,然后就可以防止启动多个进程,
这是一个比较常用的小技巧。关于ngx_single_process_cycle(cycle)
这应该是单进程的情况,一般而言现在的服务器
都是多核为主,所以我们去ngx_master_process_cycle(cycle)
Master进程的主函数看一看。
主进程
ngx_master_process_cycle
函数在/src/os/win32/ngx_process_cycle.c中,该函数接受一个参数,这个参数比较
复杂,但是可以看出,应该是和每次nginx
循环的生命周期有关,这里认为nginx
每平滑重启一次,就是一次循环。代码
分为几个部分来看:
void ngx_master_process_cycle(ngx_cycle_t *cycle) {
...
if (ngx_process == NGX_PROCESS_WORKER) {
// ngx_process标识进程的身份,如果本进程应该是工作者进程,就去执行工作者应该做的
ngx_worker_process_cycle(cycle, ngx_master_process_event_name);
return;
}
...
SetEnvironmentVariable("ngx_unique", ngx_unique); // 设置环境变量,表示nginx主进程已经运行
...
ngx_master_process_event = CreateEvent(NULL, 1, 0, ngx_master_process_event_name);
if (ngx_master_process_event == NULL) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"CreateEvent(\"%s\") failed",
ngx_master_process_event_name);
exit(2);
}
if (ngx_create_signal_events(cycle) != NGX_OK) {
exit(2);
}
ngx_sprintf((u_char *) ngx_cache_manager_mutex_name,
"ngx_cache_manager_mutex_%s%Z", ngx_unique);
ngx_cache_manager_mutex = CreateMutex(NULL, 0,
ngx_cache_manager_mutex_name);
if (ngx_cache_manager_mutex == NULL) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"CreateMutex(\"%s\") failed", ngx_cache_manager_mutex_name);
exit(2);
}
events[0] = ngx_stop_event;
events[1] = ngx_quit_event;
events[2] = ngx_reopen_event;
events[3] = ngx_reload_event;
ngx_close_listening_sockets(cycle);
if (ngx_start_worker_processes(cycle, NGX_PROCESS_RESPAWN) == 0) {
exit(2);
}
...
}
理解这段代码,需要了解Windows系统的一点点事件相关API,CreateEvent
可以创建一个事件,之后可以通过一些方法
比如SetEvent
可以使得这个事件被激活,进程或者线程也可以通过WaitForSingleObejct
等API去等待一个事件的发
生。这段代码就是创建了一些事件,包括stop
,quit
,reopen
和reload
,这些事件是在ngx_create_signal_events
函数中创建的:
static ngx_int_t
ngx_create_signal_events(ngx_cycle_t *cycle)
{
ngx_sprintf((u_char *) ngx_stop_event_name,
"Global\\ngx_stop_%s%Z", ngx_unique);
ngx_stop_event = CreateEvent(NULL, 1, 0, ngx_stop_event_name);
if (ngx_stop_event == NULL) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"CreateEvent(\"%s\") failed", ngx_stop_event_name);
return NGX_ERROR;
}
ngx_sprintf((u_char *) ngx_quit_event_name,
"Global\\ngx_quit_%s%Z", ngx_unique);
...
}
之后,主进程调用ngx_close_listening_sockets(cycle)
关闭正在侦听的套接字,这样之后的连接就不会进来了,
因为主进程循环肯定是重启或者初始化的时候被调用的。之后调用ngx_start_worker_processes
函数去启动工作者
线程。我们看看ngx_start_worker_process
函数,同样在这个文件里:
static ngx_int_t
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t type)
{
ngx_int_t n;
ngx_core_conf_t *ccf;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
for (n = 0; n < ccf->worker_processes; n++) {
if (ngx_spawn_process(cycle, "worker", type) == NGX_INVALID_PID) {
break;
}
}
return n;
}
这个函数先是读取了本次循环的配置,根据配置中的worker_process
的设置来启动相应数量的工作者进程,配置文件
在/conf/nginx.conf
中:
#user nobody;
worker_processes 8;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 65536;
}
...
当然如果配置文件中没有设置,以及新创建的配置文件中如何设置默认值,这些都在/src/core/nginx.c
中,但是不是
非常重要,所以暂时略过。回归ngx_master_process_cycle
函数,该函数在创建了事件之后,会进入一个死循环:
for ( ;; ) {
nev = 4;
for (n = 0; n < ngx_last_process; n++) {
if (ngx_processes[n].handle) {
events[nev++] = ngx_processes[n].handle;
}
}
if (timer) {
timeout = timer > ngx_current_msec ? timer - ngx_current_msec : 0;
}
ev = WaitForMultipleObjects(nev, events, 0, timeout);
err = ngx_errno;
ngx_time_update();
ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0,
"master WaitForMultipleObjects: %ul", ev);
if (ev == WAIT_OBJECT_0) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
if (ResetEvent(ngx_stop_event) == 0) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"ResetEvent(\"%s\") failed", ngx_stop_event_name);
}
if (timer == 0)