nginx作为高效的http服务器和反向代理服务器,值得我们深入了解。
我们带着几个问题,深入了解下nginx的工作原理。首先是开篇:nginx是如何启动的?
nginx是用c写的软件,github地址: https://github.com/nginx/nginx
其目录结构如下,我们主要关注 src 目录下的文件。
nginx.c 是main函数入口,我们也是通过这里进行启动流程分析的。
零、启动流程时序图
我们先通过一个时序图进行全局观察nginx是如何跑起来的,然后后续再稍微深入了解些细节。
简要步骤说明:
1. 初始化调试信息;
2. 解析命令配置参数信息;
3. 初始化环境信息,时间、pid、ssl...;
4. 初始化 init_cycle 变量, 将各种配置信息放入其中;
5. 处理继承NGINX环境变量中指定的socket;
6. 给各处理模块编号;
7. 初始化全局变量 ngx_cycle, 将init_cycle信息转移过来,并处理许多其他必要信息;
8. 初始化信号控制处理器 signals;
9. 开启worker子进程循环服务,开启master主进程循环服务;
一、main函数启动处理实现
通过main函数呢,也就完全理解了整个过程了,整个运行框架都在这里了。细节看需要进行深入。
// src/core/nginx.c 入口 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_conf_dump_t *cd; ngx_core_conf_t *ccf; ngx_debug_init(); // 初始化错误信息容器 if (ngx_strerror_init() != NGX_OK) { return 1; } // 解析命令行参数,有限参数 if (ngx_get_options(argc, argv) != NGX_OK) { return 1; } if (ngx_show_version) { ngx_show_version_info(); if (!ngx_test_config) { return 0; } } /* TODO */ ngx_max_sockets = -1; ngx_time_init(); #if (NGX_PCRE) ngx_regex_init(); #endif ngx_pid = ngx_getpid(); ngx_parent = ngx_getppid(); // 初始化日志文件实例 log = ngx_log_init(ngx_prefix); if (log == NULL) { return 1; } /* STUB */ #if (NGX_OPENSSL) ngx_ssl_init(log); #endif /* * init_cycle->log is required for signal handlers and * ngx_process_options() */ ngx_memzero(&init_cycle, sizeof(ngx_cycle_t)); init_cycle.log = log; // 将 ngx_cycle 和 init_cycle 指向同一块内存,以下对 init_cycle 的操作,也就是对 ngx_cycle的操作 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; } // 初始化操作系统相关的参数, 如 cpu 核数, 进程标题,页缓存大小,随机数等 if (ngx_os_init(log) != NGX_OK) { return 1; } /* * ngx_crc32_table_init() requires ngx_cacheline_size set in ngx_os_init() */ // crc32 表初始化,内存分配 if (ngx_crc32_table_init() != NGX_OK) { return 1; } /* * ngx_slab_sizes_init() requires ngx_pagesize set in ngx_os_init() */ // slat 大小设置初始化 ngx_slab_sizes_init(); // 添加继承过来的socket, 用于无中断重启 if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) { return 1; } // 设置 ngx_module 的索引值及名称 if (ngx_preinit_modules() != NGX_OK) { return 1; } // 初始化 cycle 相关必须信息,如初始化各模块(重量级方法) cycle = ngx_init_cycle(&init_cycle); if (cycle == NULL) { if (ngx_test_config) { ngx_log_stderr(0, "configuration file %s test failed", init_cycle.conf_file.data); } return 1; } // 测试结束 if (ngx_test_config) { if (!ngx_quiet_mode) { ngx_log_stderr(0, "configuration file %s test is successful", cycle->conf_file.data); } if (ngx_dump_config) { cd = cycle->config_dump.elts; for (i = 0; i < cycle->config_dump.nelts; i++) { ngx_write_stdout("# configuration file "); (void) ngx_write_fd(ngx_stdout, cd[i].name.data, cd[i].name.len); ngx_write_stdout(":" NGX_LINEFEED); b = cd[i].buffer; (void) ngx_write_fd(ngx_stdout, b->pos, b->last - b->pos); ngx_write_stdout(NGX_LINEFEED); } } return 0; } // 如果是进行启停控制,则处理信号即可 if (ngx_signal) { return ngx_signal_process(cycle, ngx_signal); } // 记录操作系统信息 // 日志级别先后: error > warn > notice > info > debug 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) // 初始化信号处理方法,针对 signals[] 中的方法进行调用注册 if (ngx_init_signals(cycle->log) != NGX_OK) { return 1; } if (!ngx_inherited && ccf->daemon) { // 如果使用后台进程运行,则 fork() 当前进程后退出 if (ngx_daemon(cycle->log) != NGX_OK) { return 1; } ngx_daemonized = 1; } if (ngx_inherited) { ngx_daemonized = 1; } #endif // 创建进程pid文件,写入 ngx_pid if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) { return 1; } if (ngx_log_redirect_stderr(cycle) != NGX_OK) { 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); } return 0; }
二、命令行参数解析
解析命令行参数,让用户可以方便更改配置和控制nginx。没有啥复杂的,就是纯粹地按分割符将参数解析出来,放入全局的变量里,备后续的代码使用。简单看看即可。
// 解析命令行参数, -? -h -v -V -t -T -q -p -s static ngx_int_t ngx_get_options(int argc, char *const *argv) { u_char *p; ngx_int_t i; for (i = 1; i < argc; i++) { p = (u_char *) argv[i]; if (*p++ != '-') { ngx_log_stderr(0, "invalid option: "%s"", argv[i]); return NGX_ERROR; } while (*p) { switch (*p++) { // -h -v -V -t -T -q 后面无参数 case '?': case 'h': ngx_show_version = 1; ngx_show_help = 1; break; case 'v': ngx_show_version = 1; break; case 'V': ngx_show_version = 1; ngx_show_configure = 1; break; case 't': ngx_test_config = 1; break; case 'T': ngx_test_config = 1; ngx_dump_config = 1; break; case 'q': ngx_quiet_mode = 1; break; // -p -c -g -s 后面必带参数 case 'p': if (*p) { ngx_prefix = p; goto next; } if (argv[++i]) { ngx_prefix = (u_char *) argv[i]; goto next; } ngx_log_stderr(0, "option "-p" requires directory name"); return NGX_ERROR; // -c 指定配置文件 case 'c': if (*p) { ngx_conf_file = p; goto next; } if (argv[++i]) { ngx_conf_file = (u_char *) argv[i]; goto next; } ngx_log_stderr(0, "option "-c" requires file name"); return NGX_ERROR; case 'g': if (*p) { ngx_conf_params = p; goto next; } if (argv[++i]) { ngx_conf_params = (u_char *) argv[i]; goto next; } ngx_log_stderr(0, "option "-g" requires parameter"); return NGX_ERROR; // -s 服务启停控制 case 's': if (*p) { ngx_signal = (char *) p; } else if (argv[++i]) { ngx_signal = argv[i]; } else { ngx_log_stderr(0, "option "-s" requires parameter"); return NGX_ERROR; } if (ngx_strcmp(ngx_signal, "stop") == 0 || ngx_strcmp(ngx_signal, "quit") == 0 || ngx_strcmp(ngx_signal, "reopen") == 0 || ngx_strcmp(ngx_signal, "reload") == 0) { ngx_process = NGX_PROCESS_SIGNALLER; goto next; } ngx_log_stderr(0, "invalid option: "-s %s"", ngx_signal); return NGX_ERROR; default: ngx_log_stderr(0, "invalid option: "%c"", *(p - 1)); return NGX_ERROR; } } next: continue; } return NGX_OK; }
三、继承socket信息
通过 NGINX 这个环境变量,可以获取到原来的nginx监听的socket信息,如果要进行优雅重启,那么把这些socket接管过来,继续处理即可实现无中断重启服务作用。
// nginx.c, 继承之前的socket信息,无中断式重启 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; } ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "using inherited sockets from "%s"", inherited); 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) { ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "invalid socket number "%s" in " NGINX_VAR " environment variable, ignoring the rest" " of the variable", v); break; } v = p + 1; // 添加到nginx的监听列表中 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; // 设置每个socket的详细配置信息,比如忽略无效的socket等等 return ngx_set_inherited_sockets(cycle); }
// core/ngx_connection.c ngx_int_t ngx_set_inherited_sockets(ngx_cycle_t *cycle) { size_t len; ngx_uint_t i; ngx_listening_t *ls; socklen_t olen; #if (NGX_HAVE_DEFERRED_ACCEPT || NGX_HAVE_TCP_FASTOPEN) ngx_err_t err; #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) struct accept_filter_arg af; #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) int timeout; #endif #if (NGX_HAVE_REUSEPORT) int reuseport; #endif ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { ls[i].sockaddr = ngx_palloc(cycle->pool, sizeof(ngx_sockaddr_t)); if (ls[i].sockaddr == NULL) { return NGX_ERROR; } ls[i].socklen = sizeof(ngx_sockaddr_t); // 忽略无效的监听 if (getsockname(ls[i].fd, ls[i].sockaddr, &ls[i].socklen) == -1) { ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, "getsockname() of the inherited " "socket #%d failed", ls[i].fd); ls[i].ignore = 1; continue; } if (ls[i].socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { ls[i].socklen = sizeof(ngx_sockaddr_t); } switch (ls[i].sockaddr->sa_family) { #if (NGX_HAVE_INET6) case AF_INET6: ls[i].addr_text_max_len = NGX_INET6_ADDRSTRLEN; len = NGX_INET6_ADDRSTRLEN + sizeof("[]:65535") - 1; break; #endif #if (NGX_HAVE_UNIX_DOMAIN) case AF_UNIX: ls[i].addr_text_max_len = NGX_UNIX_ADDRSTRLEN; len = NGX_UNIX_ADDRSTRLEN; break; #endif case AF_INET: ls[i].addr_text_max_len = NGX_INET_ADDRSTRLEN; len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1; break; default: ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, "the inherited socket #%d has " "an unsupported protocol family", ls[i].fd); ls[i].ignore = 1; continue; } ls[i].addr_text.data = ngx_pnalloc(cycle->pool, len); if (ls[i].addr_text.data == NULL) { return NGX_ERROR; } len = ngx_sock_ntop(ls[i].sockaddr, ls[i].socklen, ls[i].addr_text.data, len, 1); if (len == 0) { return NGX_ERROR; } ls[i].addr_text.len = len; ls[i].backlog = NGX_LISTEN_BACKLOG; olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_TYPE, (void *) &ls[i].type, &olen) == -1) { ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, "getsockopt(SO_TYPE) %V failed", &ls[i].addr_text); ls[i].ignore = 1; continue; } olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_RCVBUF, (void *) &ls[i].rcvbuf, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_RCVBUF) %V failed, ignored", &ls[i].addr_text); ls[i].rcvbuf = -1; } olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_SNDBUF, (void *) &ls[i].sndbuf, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_SNDBUF) %V failed, ignored", &ls[i].addr_text); ls[i].sndbuf = -1; } #if 0 /* SO_SETFIB is currently a set only option */ #if (NGX_HAVE_SETFIB) olen = sizeof(int); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_SETFIB, (void *) &ls[i].setfib, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_SETFIB) %V failed, ignored", &ls[i].addr_text); ls[i].setfib = -1; } #endif #endif #if (NGX_HAVE_REUSEPORT) reuseport = 0; olen = sizeof(int); #ifdef SO_REUSEPORT_LB if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT_LB, (void *) &reuseport, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_REUSEPORT_LB) %V failed, ignored", &ls[i].addr_text); } else { ls[i].reuseport = reuseport ? 1 : 0; } #else if (getsockopt(ls[i].fd, SOL_SOCKET, SO_REUSEPORT, (void *) &reuseport, &olen) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, "getsockopt(SO_REUSEPORT) %V failed, ignored", &ls[i].addr_text); } else { ls[i].reuseport = reuseport ? 1 : 0; } #endif #endif if (ls[i].type != SOCK_STREAM) { continue; } #if (NGX_HAVE_TCP_FASTOPEN) olen = sizeof(int); if (getsockopt(ls[i].fd, IPPROTO_TCP, TCP_FASTOPEN, (void *) &ls[i].fastopen, &olen) == -1) { err = ngx_socket_errno; if (err != NGX_EOPNOTSUPP && err != NGX_ENOPROTOOPT && err != NGX_EINVAL) { ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, "getsockopt(TCP_FASTOPEN) %V failed, ignored", &ls[i].addr_text); } ls[i].fastopen = -1; } #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) ngx_memzero(&af, sizeof(struct accept_filter_arg)); olen = sizeof(struct accept_filter_arg); if (getsockopt(ls[i].fd, SOL_SOCKET, SO_ACCEPTFILTER, &af, &olen) == -1) { err = ngx_socket_errno; if (err == NGX_EINVAL) { continue; } ngx_log_error(NGX_LOG_NOTICE, cycle->log, err, "getsockopt(SO_ACCEPTFILTER) for %V failed, ignored", &ls[i].addr_text); continue; } if (olen < sizeof(struct accept_filter_arg) || af.af_name[0] == '