总体流程
以下代码删掉了部分内容,也就是启动时指定-v -V -t -T
选项时用于显示版本ngx_show_version
,测试配置ngx_test_config
,显示配置ngx_dump_config
的相关代码,因为这里主要分析正常启动的流程,所以不关心这些参数
int ngx_cdecl
main(int argc, char *const *argv)
{
ngx_buf_t *b;
ngx_log_t *log;
ngx_uint_t i;
ngx_cycle_t *cycle, init_cycle;
ngx_core_conf_t *ccf;
ngx_debug_init(); // 位于 os/unix/ngx_linux_config.h
if (ngx_strerror_init() != NGX_OK) { // 位于 os/unix/ngx_errno.c
return 1;
}
if (ngx_get_options(argc, argv) != NGX_OK) {
return 1;
}
/* TODO */ ngx_max_sockets = -1;
ngx_time_init(); // 位于 core/ngx_times.c
#if (NGX_PCRE)
ngx_regex_init();
#endif
ngx_pid = ngx_getpid();
log = ngx_log_init(ngx_prefix); // 位于 core/ngx_log.c
if (log == NULL) {
return 1;
}
/* STUB */
#if (NGX_OPENSSL)
ngx_ssl_init(log);
#endif
ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
init_cycle.log = log;
ngx_cycle = &init_cycle;
init_cycle.pool = ngx_create_pool(1024, log);
if (init_cycle.pool == NULL) {
return 1;
}
if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
return 1;
}
if (ngx_process_options(&init_cycle) != NGX_OK) {
return 1;
}
if (ngx_os_init(log) != NGX_OK) { // 位于 os/unix/ngx_posix_init.c
return 1;
}
if (ngx_crc32_table_init() != NGX_OK) {
return 1;
}
if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
return 1;
}
if (ngx_preinit_modules() != NGX_OK) {
return 1;
}
cycle = ngx_init_cycle(&init_cycle);
if (cycle == NULL) {
return 1;
}
if (ngx_signal) {
return ngx_signal_process(cycle, ngx_signal);
}
ngx_os_status(cycle->log);
ngx_cycle = cycle;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) {
ngx_process = NGX_PROCESS_MASTER;
}
#if !(NGX_WIN32)
if (ngx_init_signals(cycle->log) != NGX_OK) { // 位于 os/unix/ngx_process.c
return 1;
}
if (!ngx_inherited && ccf->daemon) {
if (ngx_daemon(cycle->log) != NGX_OK) { // 位于 os/unix/ngx_daemon.c
return 1;
}
ngx_daemonized = 1;
}
if (ngx_inherited) {
ngx_daemonized = 1;
}
#endif
if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
return 1;
}
if (ngx_log_redirect_stderr(cycle) != NGX_OK) { // 位于 core/ngx_log.c
return 1;
}
if (log->file->fd != ngx_stderr) {
if (ngx_close_file(log->file->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
ngx_close_file_n " built-in log failed");
}
}
ngx_use_stderr = 0;
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);
} else {
ngx_master_process_cycle(cycle); // 位于 os/unix/ngx_process_cycle.c
}
return 0;
}
- 调用
ngx_debug_init
,因为这个函数是个宏定义,而且为空,所以不知道什么作用,忽略吧 - 调用
ngx_strerror_init
,这个函数比较简单,主要就是将系统中 errno 的描述字符串,存储到一个ngx_str_t
数组中,这么做的原因是strerror
和strerror_r
不是信号安全的 - 调用
ngx_get_options
,根据argv
中的参数,设置相关的变量,除了一开始说的测试相关的,还有若含有了p
选项则设置ngx_prefix
表示路径前缀,含有c
选项则设置ngx_conf_file
表示配置文件相对路径,含有g
选项设置ngx_conf_params
表示额外配置项(语法与配置文件相同),这里的重点应该是s
选项,用于向master
进程发送信号,后面跟着stop quit reopen reload
的其中之一,具体后面会讲 - 调用
ngx_time_init
,设置了描述时间的字符串长度,并调用ngx_time_update
更新初始时间 - 调用
ngx_regex_init
,看起来是初始化了正则模块,这个模块我没用过,详情不太了解 - 调用
ngx_getpid
,设置表示进程 pid 的变量ngx_pid
- 调用
ngx_log_init
,初始化了log
对象用于输出错误日志(仅用于初始化),函数大概看了一下,就是根据前缀ngx_prefix
打开错误日志文件,默认路径是NGX_ERROR_LOG_PATH
,这个宏定义由configure
脚本生成,位于objs/ngx_auto_config.h
,如果不存在这个宏定义,则输出到stderr
- 调用
ngx_ssl_init
,看起来是初始化openssl
这个库 - 设置
init_cycle
变量,这个变量的类型是ngx_cycle_t
,这个类型之后会介绍,现在只知道设置了之前初始化的log
用于输出错误,创建了内存池pool
用于分配内存 - 调用
ngx_save_argv
,主要是将argv
拷贝到了ngx_argv
,原因应该是之后需要修改argv[0]
的值以修改进程名称(可能会覆盖argv[1]的值),所以需要备份参数到ngx_argv
- 调用
ngx_process_options
,设置init_cycle
中的几个参数,分别为conf_file
配置文件绝对路径(ngx_conf_file
指定的是相对路径),conf_param
额外参数(与ngx_conf_params
相同),conf_prefix
配置文件前缀,prefix
路径前缀,一般情况下,最后两个变量值相同,但是当配置头文件中的NGX_PREFIX
和NGX_CONF_PREFIX
存在且没使用c
选项指定ngx_prefix
时,两者可能不同 - 调用
ngx_os_init
,初始化系统相关信息,例如例如 cpu 数量,缓存行大小,页大小,文件描述符最大数量 - 调用
ngx_crc32_table_init
,如果设置了缓存行大小,则将crc32
算法需要用到的ngx_crc32_table16
数组的首地址对齐到缓存行大小 - 调用
ngx_add_inherited_sockets
,继承文件描述符,关于这个函数的细节写在后面 - 调用
ngx_preinit_modules
,设置ngx_modules
数组中各个模块的index
和name
- 调用
ngx_init_cycle
,这个函数涉及的东西很多,打算之后再说,目前知道解析了配置文件就行了 - 根据
ngx_signal
的值判断是否以选项s
启动,是则调用ngx_signal_process
发送信号,这个函数从指定文件(默认是nginx.pid
,这个文件会在后面创建)中读取master
进程的 pid,然后向其发送指定信号,如果以这个选项运行则到这里就结束了 - 调用
ngx_os_status
,将系统信息写入日志文件 - 设置
ngx_cycle
并根据解析完配置文件得到的信息判断是否以master
形式启动 - 调用
ngx_init_signals
,初始化所有信号处理函数,具体有哪些信号之后再说 - 如果不是平滑升级的情况,并且需要以 daemon 的形式启动,则调用
ngx_daemon
将进程变为守护进程(具体细节参考可 APUE 这类书籍)并将ngx_daemonized
设置为 1,如果是平滑升级的情况则直接将ngx_daemonized
设置为 1(这里不懂为什么) - 调用
ngx_create_pidfile
,创建记录进程 pid 的文件,用于向本进程发送信号 - 调用
ngx_log_redirect_stderr
,如果不使用stderr
则将其重定向到cycle->log
中指定的日志文件 - 如果
log
(由ngx_log_init
函数返回的临时log
,与cycle->log
不同,仅用于初始化)的文件描述符不等于ngx_stderr
则关闭它 - 将
ngx_use_stderr
设置为 0,这个变量感觉有点奇怪,初始化时为 1,到这里设置为 0,从代码里可以看出,有一个作用是判断是否需要在ngx_init_cycle
里调用ngx_log_redirect_stderr
,启动时为 1 所以不调用,后面每次 reload 时都会调用,还有一个作用就是ngx_log_error_core
函数中,在初始化时将错误输出到控制台,总之不是很懂 - 根据
ngx_process
判断是以master
形式还是single
形式启动
继承文件描述符
static ngx_int_t
ngx_add_inherited_sockets(ngx_cycle_t *cycle)
{
u_char *p, *v, *inherited;
ngx_int_t s;
ngx_listening_t *ls;
inherited = (u_char *) getenv(NGINX_VAR);
if (inherited == NULL) {
return NGX_OK;
}
if (ngx_array_init(&cycle->listening, cycle->pool, 10,
sizeof(ngx_listening_t))
!= NGX_OK)
{
return NGX_ERROR;
}
for (p = inherited, v = p; *p; p++) {
if (*p == ':' || *p == ';') {
s = ngx_atoi(v, p - v);
if (s == NGX_ERROR) {
break;
}
v = p + 1;
ls = ngx_array_push(&cycle->listening);
if (ls == NULL) {
return NGX_ERROR;
}
ngx_memzero(ls, sizeof(ngx_listening_t));
ls->fd = (ngx_socket_t) s;
}
}
if (v != p) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"invalid socket number \"%s\" in " NGINX_VAR
" environment variable, ignoring", v);
}
ngx_inherited = 1;
return ngx_set_inherited_sockets(cycle);
}
这个函数整体比较好看,一开始从环境变量NGINX_VAR
中获取旧进程打开的监听文件描述符,这个环境变量的值是以冒号隔开的数字,这些数字代表旧进程的文件描述符,在旧进程调用 exec 系函数时传入,问题是这些数字与当前进程有什么关系呢?由于这些文件描述符没有设置FD_CLOEXEC
,所以在 fork 并调用 exec 执行新程序时不会被关闭,也就是和旧进程的文件描述符一致,所以只要知道值就可以拿来用
然后初始化了cycle->listening
数组,数量与旧进程传入的文件描述符相同,这是一个监听对象ngx_listening_t
数组,可以理解为每个监听对象对应一个监听端口,这里将旧进程的fd
直接赋值给监听对象的fd
最后,将ngx_inherited
设置为 1,并调用ngx_set_inherited_sockets
,这个函数太长了,就不贴上来了,大概就是设置了各个监听对象的 socket 地址,socket 类型,接收缓冲区大小,发送缓冲区大小还有一些端口重用等选项,需要注意的是,如果出错会将ignore
的值设置为 1,ngx_cycle_init
中需要使用这个值