如不做特殊说明,本博客所使用的 nginx 源码版本是 1.0.14
我们先贴出 main 函数的部分代码:
205 int ngx_cdecl 206 main(int argc, char *const *argv) 207 { 208 ngx_int_t i; 209 ngx_log_t *log; 210 ngx_cycle_t *cycle, init_cycle; 211 ngx_core_conf_t *ccf; 212 213 #if (NGX_FREEBSD) 214 ngx_debug_init(); 215 #endif 216 /* 错误信息字符串初始化 */ 217 if (ngx_strerror_init() != NGX_OK) { 218 return 1; 219 } 220 221 /* 解析命令参数 */ 222 if (ngx_get_options(argc, argv) != NGX_OK) { 223 return 1; 224 }
main 函数很简单全是函数的调用,所有功能都放在各个函数中实现!
1.我们先看 217 行的第一个函数 ngx_strerror_init()[os/unix/ngx_error.c],该函数比较简单,是将系统的每个错误信息保存到 nginx 自身的变量 ngx_sys_errlist 中,以便后面调用。其实 nginx 中很多变量都是这样,将系统提供的变量重新定义成 nginx 的名字。如果 ngx_strerror_init() 函数返回成功,main 函数继续,错误 nginx 终止运行。
2. 接下来第二个函数 ngx_get_options()[core/nginx.c],这个函数有两个参数,argc,argv:一个是系统调用 main函数传过来参数的个数,另一个就是参数值数组.该函数也比叫容易理解。我们知道在运行 Nginx 的时候可以附带一些命令,形式如 nginx -v 这就是在运行 ngxin 后,并显示 Nginx 版本号的命令。该函数就是对 nginx 命令之后的选项(这里的 -v) 进行解析,然后设置一些全局变量的值。 main 函数在根据这些全局变量的值做出相应的动作。
接着看 main() 函数代码:
226 /* 显示 nginx 的版本*/ 227 if (ngx_show_version) { 228 ngx_write_stderr("nginx version: " NGINX_VER NGX_LINEFEED); 229 /* 如果需要显示帮助 */ 230 if (ngx_show_help) { 231 ngx_write_stderr( 232 "Usage: nginx [-?hvVtq] [-s signal] [-c filename] " 233 "[-p prefix] [-g directives]" NGX_LINEFEED 234 NGX_LINEFEED 235 "Options:" NGX_LINEFEED 236 " -?,-h : this help" NGX_LINEFEED 237 " -v : show version and exit" NGX_LINEFEED 238 " -V : show version and configure options then exit" 239 NGX_LINEFEED 240 " -t : test configuration and exit" NGX_LINEFEE D 241 " -q : suppress non-error messages " 242 "during configuration testing" NGX_LINEF EED 243 " -s signal : send signal to a master process: " 244 "stop, quit, reopen, reload" NGX_LINEFEE D 245 #ifdef NGX_PREFIX 246 " -p prefix : set prefix path (default: " 247 NGX_PREFIX ")" NGX_LINEFEED 248 #else 249 " -p prefix : set prefix path (default: NONE)" NGX_LIN EFEED 250 #endif 251 " -c filename : set configuration file (default: " 252 NGX_CONF_PATH ")" NGX_LINEFEED 253 " -g directives : set global directives out of configurati on " 254 "file" NGX_LINEFEED NGX_LINEFEED 255 ); 256 } 257 /* 如果需要显示配置文件 */ 258 if (ngx_show_configure) { 259 ngx_write_stderr( 260 #ifdef NGX_COMPILER 261 "built by " NGX_COMPILER NGX_LINEFEED 262 #endif 263 #if (NGX_SSL) 264 #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME 265 "TLS SNI support enabled" NGX_LINEFEED 266 #else 267 "TLS SNI support disabled" NGX_LINEFEED 268 #endif 269 #endif 270 "configure arguments:" NGX_CONFIGURE NGX_LINEFEED); 271 } 272 273 if (!ngx_test_config) { 274 return 0; 275 } 276 }
3. 这段代码根据一些全局变量进行相应的操作,里的 ngx_show_version、ngx_show_help、ngx_show_configure、ngx_test_config 都是全局变量,其值在 ngx_get_options() 得到设置。
a. ngx_show_version 显示版本号
b. ngx_show_help 显示帮助
c. ngx_show_configure 显示配置文件
d. ngx_test_config 只是为了测试配置,如果设置了,nginx 程序到此为止
278 /* TODO */ ngx_max_sockets = -1; 279 280 /* 初始化并更新时间,如全局变量 ngx_cached_time,ngx_time_init() 函数定义在 ngx_time.h.c 文件中 */ 281 ngx_time_init(); 282 283 #if (NGX_PCRE) 284 /* 如果应用 pcre 库,正则表达式初始化 */ 285 ngx_regex_init(); 286 #endif 287 288 /* 获取进程 ID */ 289 ngx_pid = ngx_getpid(); 290 /* 日志初始化 ,如:初始化全局变量 ngx_prefix, 打开日志文件 ngx_log_file.fd */ 291 log = ngx_log_init(ngx_prefix); 292 if (log == NULL) { 293 return 1; 294 } 295 296 /* STUB */ 297 #if (NGX_OPENSSL) 298 ngx_ssl_init(log); 299 #endif
4. 281 行的 ngx_time_init() [core/nginx.h/nginx.c] 函数也很简单,将当前的时间保存到全局变量中。
5. 285 行的 ngx_regex_init() 函数要有 prce 库的支持才会被调用,它的作用是初始化正则表达式,方便以后的匹配操作
6. 291 行的 ngx_log_init()[core/ngx_log.h/ngx_log.c] 是初始化错误日志记录文件(你安装 nginx 文件夹下的 logs 文件中的 error.log 文件)。该函数所做的操作有打开错误日志记录文件,设置日志等级等等,并将文件描述符保存到结构体中。这里我们要关心的两个结构体有:
a.struct ngx_log_s 他的定义如下:
struct ngx_log_s { ngx_uint_t log_level; /* 日志等级 */ ngx_open_file_t *file; /* 打开的文件(这也是个结构体) */ ngx_atomic_uint_t connection; ngx_log_handler_pt handler; /* 回调函数 */ void *data; /* 数据指针 */ /* * we declare "action" as "char *" because the actions are usually * the static strings and in the "u_char *" case we have to override * their types all the time */ char *action; };
其中的日志等级有
* 日志标注错误 */
#define NGX_LOG_STDERR 0
/* 紧急 */
#define NGX_LOG_EMERG 1
/* 警告 */
#define NGX_LOG_ALERT 2
/* 中断 */
#define NGX_LOG_CRIT 3
/* 错误 */
#define NGX_LOG_ERR 4
/* 提醒 */
#define NGX_LOG_WARN 5
/* 通知 */
#define NGX_LOG_NOTICE 6
/* 信息 */
#define NGX_LOG_INFO 7
/* 调试 */
#define NGX_LOG_DEBUG 8
八个等级
b.而结构体变量 file 又是一个结构体,该结构体代码如下:
truct ngx_open_file_s { ngx_fd_t fd; ngx_str_t name; u_char *buffer; u_char *pos; u_char *last; #if 0 /* e.g. append mode, error_log */ ngx_uint_t flags; /* e.g. reopen db file */ ngx_uint_t (*handler)(void *data, ngx_open_file_t *file); void *data; #endif };
从该结构体我的定义我们可以看出,这是一个打开文件并将文件描述符保存到fd,同时将文件的内容缓冲到 *buf 的一个结构体
好了,日志的初始化完成了,我们接着往下看 main 函数代码:
301 /* 302 * init_cycle->log is required for signal handlers and 303 * ngx_process_options() 304 */ 305 306 /* 307 * 信号处理和 ngx_process_options() 函数会用到 init_cycle->log 308 * 309 */ 310 /* 清零 init_cycle */ 311 ngx_memzero(&init_cycle, sizeof(ngx_cycle_t)); 312 313 /* 挂接 log 到 init_cycle */ 314 init_cycle.log = log; 315 316 /* ngx_cycle 应该是个 ngx_cycle_t * 类型,此文件中未看到定义 */ 317 ngx_cycle = &init_cycle; 318 319 /* 为u init_cycle 创建内存池,大小为 1024B */ 320 init_cycle.pool = ngx_create_pool(1024, log); 321 if (init_cycle.pool == NULL) { 322 return 1; 323 } 324 325 /* 保存命令行参数至全局变量 ngx_os_argv、ngx_argc、ngx_argv 中 */ 326 if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) { 327 return 1; 328 } 329 330 /* 初始化 ngx_cycle 的 prefix,conf_prefie,conf_file,ngx_argv 中 */ 331 if (ngx_process_options(&init_cycle) != NGX_OK) { 332 return 1; 333 } 334 335 /* 初始化系统相关变量,如页面内存大小 ngx_pagesize,ngx_cacheline_size,最大连接数 ngx_max_sockets */ 336 if (ngx_os_init(log) != NGX_OK) { /* 日志参数是为了记录 */ 337 return 1; 338 }
7. init_cycle 是一个很重要的变量,他的结构体是 ngc_cycle_t. nginx 的所有操作都是围绕这个变量进行的。下面我们来看看对他的操作,第 311 行,对 init_cycle的 内存进行清零
8. 将全局变量 ngx_cycle 绑定到 init_cycle。320 行设置 init_cycle 的内存池大小为 1024B,调用 ngx_create_pool() 函数进行创建。nginx 的内存池是 nginx 作者自行设计的方便内存操作的结构体,网上有大牛分析的很透彻(http://blog.csdn.net/livelylittlefish/article/details/6586946),我这里不在讲解。你只要大致的知道,这是 nginx 是向内存申请一块较大的内存,以后的内存申请操作都不再向内存直接申请,而是在该分配的内存中直接获取进行了(除非该大块内存也被分配完).如果创建内存池失败则终止程序。
9. 326 行的 ngx_save_argv()[core/nginx.c] 很简单,就是将 argv 中保存的参数保存到全局变量 ngx_argv 中。两个参数分别是 init_cycle, argv。argv 是要被保存的,而在函数中的操作要用到 init_cycle 中的日志变量
10. 331 行中的函数所做的事情就是设置 init_cycle 结构体中的 conf_file、conf_param、conf_prefix、prefix 的值。ngx_cycle_t 结构体的定义已经有大牛分析的很详细,这里不再赘述!详见:http://blog.csdn.net/livelylittlefish/article/details/7247080
11. 336 行的 ngx_os_init 函数设置一些与操作系统有关的变量比如 ngx_pagesize(页面大小)、ngx_cacheline_size(缓存行的大小)、ngx_max_sockets(最大连接数)
继续贴 main 函数代码
/* 342 * ngx_crc32_table_init() requires ngx_cacheline_size set in ngx_os_init() 343 */ 344 /* 初始化 CRC 表---后续的 CRC 校验通过查表进行,效率高 */ 345 if (ngx_crc32_table_init() != NGX_OK) { 346 return 1; 347 } 348 349 /* 调用 ngx_add_inherited_sockets 继承 sockets; --- 将环境变量中的监听端口保存到 init_cycle->listening 数组中去 */ 350 if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) { 351 return 1; 352 } 353 354 /* 处理每个模块的 index 变量 ,并计算 ngx_max_moudle */ 355 ngx_max_module = 0; 356 for (i = 0; ngx_modules[i]; i++) { 357 ngx_modules[i]->index = ngx_max_module++; 358 } 359 360 /* 初始化 init_cycle 结构 --- 很重要的一个结构体变量,许多东西都保存在这个结构体中*/ 361 /* ngx_init_cycle() 在 ngx_cycle.h/c 文件中 */ 362 cycle = ngx_init_cycle(&init_cycle); 363 if (cycle == NULL) { 364 if (ngx_test_config) { 365 ngx_log_stderr(0, "configuration file %s test failed", 366 init_cycle.conf_file.data); 367 } 368 369 return 1; 370 } 371 /* 是否开启测试配置文件,开启则测试配置文件 */ 372 if (ngx_test_config) { 373 if (!ngx_quiet_mode) { 374 ngx_log_stderr(0, "configuration file %s test is successful", 375 cycle->conf_file.data); 376 } 377 378 return 0; 379 } 380 /* 若有信号,处理收到的信号 */ 381 if (ngx_signal) { 382 return ngx_signal_process(cycle, ngx_signal); 383 } 384 385 ngx_os_status(cycle->log); 386 387 ngx_cycle = cycle; 388 389 /* 读取 ngx_core_moudle 的配置文件,值得关注 */ 390 ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); 391 392 /* 有 master 线程并且 ngx_process 是单*/ 393 if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) { 394 ngx_process = NGX_PROCESS_MASTER; 395 } 396 397 #if !(NGX_WIN32) 398 /* 初始化信号 */ 399 if (ngx_init_signals(cycle->log) != NGX_OK) { 400 return 1; 401 } 402 /* 若无继承 sockets,且设置了守护进程标识,则调用 ngx_daemon() 创建守护进程 */ 403 if (!ngx_inherited && ccf->daemon) { 404 /* 405 if (ngx_daemon(cycle->log) != NGX_OK) { 406 return 1; 407 } 408 */ 409 ngx_daemonized = 1; 410 }
12. 344 行中的 ngx_crc32_table_init() 函数是初始化 CRC 标,后续的 CRC 校验直接通过查表进行,效率高!
13. 350 行中的 ngx_add_inherited_sockets()函数在 http://blog.csdn.net/livelylittlefish/article/details/7277607 中有分析!很详细,这里不再赘述!
14. 355-357 行是为了设置每个模块结构体变量中的 index 变量,同时统计模块的个数
15. 362 行调用 ngx_init_cycle() 根据 init_cycle 初始化 cycle 变量,具体分析见博客:http://blog.csdn.net/livelylittlefish/article/details/7247080 !很详细,也很重要,一定要认真看,搞懂,配置文件的解析并保存到全局变量都在此函数中进行,不再赘述!如果初始化失败,并且设置了 ngx_test_cnfig 为 1,则输出错误信息到标准错误,main 函数返回!
16. 381 行等待信号的来临,如果 master 进程(执行 main 函数的进程) 收到信号,那么该进程要作出相应的处理,由 ngx_singal_process()[core/ngx_cycle.c]函数执行!
17. 390 行获取 ngx_core_module 模块的配置信息,393-395 行:如果配置中 master 变量 > 1 并且 ngx_process 是 NGX_PROCESS_SINGLE 模式那么就将 ngx_process 设置成 NGX_PROCESS_MASTER
18. 399 行中的 ngx_init_signals() 函数初始化信号在博客 http://blog.csdn.net/livelylittlefish/article/details/7308100 分析的很详细,这里不再赘述!
19. 403-410 根据配置中的 daemon 变量和 是否 继承 sockets(ngx_inherited,会在 ngx_add_inherited_sockets() 函数中设置) 来设置 master 进程(执行 main 函数的进程)是否为守护进程!我这里为了方便调试,将设置成守护进程打代码注释了。
继续贴 main() 函数代码
440 /* 进入主循环 */ 441 /* ngx_single_process() 和 ngx_master_process_cycle() 定义在 OS/ngx_process_cycle.h/c 文件中*/ 442 if (ngx_process == NGX_PROCESS_SINGLE) { 443 ngx_single_process_cycle(cycle); /* 若为 NGX_PROCESS_SINGLE = 1 模式,则调用 ngx_single_process_cycle() 进入主循环*/ 444 445 } else { 446 ngx_master_process_cycle(cycle); /* 否则为 master-worker 模式,调用 ngx_master_process_cycle() 进入主循环*/ 447 } 448 449 return 0; 450 }
20.根据 ngx_process 使进程调用不同的处理函数进入主循环!
至此,main() 函数分析完毕,下一节我们将分析进入 ngx_master_process_cycle() 函数的代码!