菜鸟nginx源码剖析 框架篇(一) 从main函数看nginx启动流程
-
Author:Echo Chen(陈斌)
-
Email:chenb19870707@gmail.com
-
Date:Nov 9th, 2014
俗话说的好,牵牛要牵牛鼻子 驾车顶牛,处理复杂的东西,只要抓住重点,才能理清脉络,不至于深陷其中,不能自拔。对复杂的nginx而言,main函数就是“牛之鼻”,只要能理清main函数,就一定能理解其中的奥秘,下面我们就一起来研究一下nginx的main函数。
1.nginx的main函数解读
nginx启动显然是由main函数驱动的,main函数在在core/nginx.c文件中,其源代码解析如下,涉及到的数据结构在本节仅指出其作用,将在第二节中详细解释。
-
nginx main函数的流程图如下:
需要说明的:
-
1) 初始化错误提示列表,以errno为下标,元素就是对应的错误提示信息。
-
1: if (ngx_strerror_init() != NGX_OK) {
2: return 1;
3: }
2)获取命令行参数,保存在全局变量中,可以设置的命令行参数如下表所示:
-
1: if (ngx_get_options(argc, argv) != NGX_OK) {
2: return 1;
3: }
命令行参数 作用 -h或-? 显示版本信息和help信息 -v 显示版本信息 -V 显示nginx版本信息、编译器版本和配置选项信息 -t 测试配置文件信息是否OK,即检测配置文件语法的正确性,并尝试打开配置文件中所引用到的文件 -q 在测试配置文件的时候,屏蔽无错误信息,即quiet模式 -s signal 发送信号到master进程(如stop、quit、reopen、reload) -p prefix 设置前缀路径(默认为当前目录) -c filename 设置配置文件(默认为conf/nginx.conf) -g directives 在配置文件外设置全局的指令 3)时间、正则表达式和log的初始化。
1: ngx_time_init();
2:
3: (NGX_PCRE)
4: ngx_regex_init();
5: if
6:
7: ngx_pid = ngx_getpid();
8:
9: log = ngx_log_init(ngx_prefix);
10: if (log == NULL) {
11: return 1;
12: }
1: ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
2: init_cycle.log = log;
3: ngx_cycle = &init_cycle;
4:
5: init_cycle.pool = ngx_create_pool(1024, log);
6: if (init_cycle.pool == NULL) {
7: return 1;
8: }
5) 将命令行参数保存到ngx_os_argv、ngx_argc以及ngx_argv这几个全局的变量中。这算是一个备份存储,方便以后master进程做热代码替换之用。1: if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
2: return 1;
3: }
6)用命令行参数得来的全局变量初始化cycle的conf_prefix(配置文件所在路径的前缀)、prefix(nginx可执行文件所在路径)、conf_file(配置文件名)和conf_param(通过命令行-g选项指定的全局配置信息)。1: if (ngx_process_options(&init_cycle) != NGX_OK) {
2: return 1;
3: }
7) 根据操作系统确定一些参数,信息会被保存到一些全局变量中,如页大小ngx_pagesize, CPU cacheline1: if (ngx_os_init(log) != NGX_OK) {
2: return 1;
3: }
8) 初始化一个做循环冗余校验的表,由此可以看出后续的循环冗余校验将采用高效的查表法1: if (ngx_crc32_table_init() != NGX_OK) {
2: return 1;
3: }
9)通过环境变量NGINX完成socket的继承,继承来的socket将会放到init_cycle的listening数组中。同时可以读取master进程传递的平滑升级信息等等1: if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
2: return 1;
3: }
10)初始化所有模块的index信息,即对所有模块进行编号,ngx_modules数却是在自动编译的时候生成的,位于objs/ngx_modules.c文件中
1: ngx_max_module = 0;
2: for (i = 0; ngx_modules[i]; i++) {
3: ngx_modules[i]->index = ngx_max_module++;
4: }
11) 用上面收集的init_cycle信息初始化ngx_cycle,这行代码是nginx启动过程中最重要的一个步骤,在第3节将详细展开。
1: cycle = ngx_init_cycle(&init_cycle);
2: if (cycle == NULL) {
3: if (ngx_test_config) {
4: ngx_log_stderr(0, "configuration file %s test failed",
5: init_cycle.conf_file.data);
6: }
7:
8: return 1;
9: }
12)ccf 为ngx_core_conf_t 将在第2节给出详细定义,这个地方需要解释下,ccf->master是从配置文件中解析master_process配置项所得的值,初始化为NGX_CONF_UNSET(-1),在配置项中,如果flag类型的配置项master_process被设置为on,则其值为1,如果为off,则其值为0,ngx_process为全局变量,用于记录要采用的工作模式,未被初始化,因此初始值是0(uint型全局变量会被系统默认初始化为0),相关宏定义如下:
- #define NGX_PROCESS_SINGLE 0
- #define NGX_PROCESS_MASTER 1
- #define NGX_PROCESS_SIGNALLER 2
- #define NGX_PROCESS_WORKER 3
- #define NGX_PROCESS_HELPER 4
因此,下面的if判断语句的含义就是:用来处理一种特殊情况,即如果在配置项中未设置master_process配置项或者是设置为打开,ngx_process未被设置,采用默认值0,这个时候要采用master工作模式。因为master_process优先级高,且nginx默认采用master模式如果在配置项中设置master_process为off,那么if依据不会执行。最终nginx工作模式取决于ngx_proces的初值0,即采用单进程模式。
1: ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
2:
3: if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) {
4: ngx_process = NGX_PROCESS_MASTER;
5: }
13)初始化信号;主要完成信号处理程序的注册
1: if (ngx_init_signals(cycle->log) != NGX_OK) {
2: return 1;
3: }
14)若无继承sockets,且设置了守护进程表示,则创建守护进程
1: if (!ngx_inherited && ccf->daemon) {
2: if (ngx_daemon(cycle->log) != NGX_OK) {
3: return 1;
4: }
5:
6: ngx_daemonized = 1;
7: }
8:
9: if (ngx_inherited) {
10: ngx_daemonized = 1;
11: }
15) 创建进程记录文件;(非NGX_PROCESS_MASTER=1进程,不创建该文件)
1: if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
2: return 1;
3: }
16) 进入进程主循环,根据ngx_process确定启动单进程模式还是多进程模式。
1: if (ngx_process == NGX_PROCESS_SINGLE) {
2: ngx_single_process_cycle(cycle);
3:
4: } else {
5: ngx_master_process_cycle(cycle);
6: }
-
2.相关结构体
2.1. ngx_module_t
nginx中所有模块的类型都是ngx_module_t类型的,定义了模块的一些属性。nginx是完全模块化的,所有的组件都是模块,从而实现了nginx的高度松耦合。同时,我们在进行nginx模块开发时,也离不开这个数据结构。在上面初始化过程中的第10步就是初始化这个结构。
1: struct ngx_module_s {
2: /**
3: * 在具体类型模块(http、event等)的全局配置结构数组的下标。以http module模块为例,
4: * nginx把所有的http module的config信息存放在ngx_http_conf_ctx_t类型的变量中,
5: * 这个变量只有3个属性,分别是所有http module的main、srv、loc的config信息的数组。
6: * 如果该模块是http module,则ctx_index是该模块的config信息(main、srv、loc)
7: * 在ngx_http_conf_ctx_t中的下标。
8: */
9: ngx_uint_t ctx_index;
10:
11: /**
12: * nginx把所有模块(ngx_module_t)存放到ngx_modules数组中,这个数组在nginx源码路
13: * 径的objs/ngx_modules.c中,是在运行configure脚本后生成的。index属性就是该模块
14: * 在ngx_modules数组中的下标。同时nginx把所有的core module的配置结构存放到ngx_cycle的
15: * conf_ctx数组中,index也是该模块的配置结构在ngx_cycle->conf_ctx数组中的下标。
16: */
17: ngx_uint_t index;
18:
19: ……
20:
21: /**
22: * 模块的上下文属性,同一类型的模块的属性是相同的,比如core module的ctx是ngx_core_module_t类型。
23: * 而http module的ctx是ngx_http_moduel_t类型,event module的ctx是ngx_event_module_t类型等等。
24: * 相应类型的模块由分开处理的,比如所有的http module由ngx_http_module解析处理,而所有的event module
25: * 由ngx_events_module解析处理。
26: */
27: void *ctx;
28:
29: /**
30: * 该模块支持的指令的数组,最后以一个空指令结尾。ngx_commond_t的分析见下文。
31: */
32: ngx_command_t *commands;
33:
34: /**
35: * 模块的类型,nginx所有的模块类型:
36: * NGX_CORE_MODULE
37: * NGX_CONF_MODULE
38: * NGX_HTTP_MODULE
39: * NGX_EVENT_MODULE
40: * NGX_MAIL_MODULE
41: * 这些不同的类型也指定了不同的ctx。
42: */
43: ngx_uint_t type;