nginx-1.24.0 源码分析-main函数

发布时间:2026/5/24 6:49:54

nginx-1.24.0 源码分析-main函数 1 定义nginx-1.24.0 源码的 main 函数 定义在 ./nginx-1.24.0/src/core/nginx.cintngx_cdeclmain(intargc,char*const*argv){ngx_buf_t*b;ngx_log_t*log;ngx_uint_ti;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){return1;}if(ngx_get_options(argc,argv)!NGX_OK){return1;}if(ngx_show_version){ngx_show_version_info();if(!ngx_test_config){return0;}}/* TODO */ngx_max_sockets-1;ngx_time_init();#if(NGX_PCRE)ngx_regex_init();#endifngx_pidngx_getpid();ngx_parentngx_getppid();logngx_log_init(ngx_prefix,ngx_error_log);if(logNULL){return1;}/* 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.loglog;ngx_cycleinit_cycle;init_cycle.poolngx_create_pool(1024,log);if(init_cycle.poolNULL){return1;}if(ngx_save_argv(init_cycle,argc,argv)!NGX_OK){return1;}if(ngx_process_options(init_cycle)!NGX_OK){return1;}if(ngx_os_init(log)!NGX_OK){return1;}/* * ngx_crc32_table_init() requires ngx_cacheline_size set in ngx_os_init() */if(ngx_crc32_table_init()!NGX_OK){return1;}/* * ngx_slab_sizes_init() requires ngx_pagesize set in ngx_os_init() */ngx_slab_sizes_init();if(ngx_add_inherited_sockets(init_cycle)!NGX_OK){return1;}if(ngx_preinit_modules()!NGX_OK){return1;}cyclengx_init_cycle(init_cycle);if(cycleNULL){if(ngx_test_config){ngx_log_stderr(0,configuration file %s test failed,init_cycle.conf_file.data);}return1;}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){cdcycle-config_dump.elts;for(i0;icycle-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);bcd[i].buffer;(void)ngx_write_fd(ngx_stdout,b-pos,b-last-b-pos);ngx_write_stdout(NGX_LINEFEED);}}return0;}if(ngx_signal){returnngx_signal_process(cycle,ngx_signal);}ngx_os_status(cycle-log);ngx_cyclecycle;ccf(ngx_core_conf_t*)ngx_get_conf(cycle-conf_ctx,ngx_core_module);if(ccf-masterngx_processNGX_PROCESS_SINGLE){ngx_processNGX_PROCESS_MASTER;}#if!(NGX_WIN32)if(ngx_init_signals(cycle-log)!NGX_OK){return1;}if(!ngx_inheritedccf-daemon){if(ngx_daemon(cycle-log)!NGX_OK){return1;}ngx_daemonized1;}if(ngx_inherited){ngx_daemonized1;}#endifif(ngx_create_pidfile(ccf-pid,cycle-log)!NGX_OK){return1;}if(ngx_log_redirect_stderr(cycle)!NGX_OK){return1;}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_stderr0;if(ngx_processNGX_PROCESS_SINGLE){ngx_single_process_cycle(cycle);}else{ngx_master_process_cycle(cycle);}return0;}nginx 1.24.0 的主函数 main 位于 src/core/nginx.c 文件中。 它是整个 nginx 服务器的入口点 负责初始化各种子系统、解析命令行参数、读取配置文件、 启动工作进程或单进程模式最终进入主循环。2 详解1 函数签名intngx_cdeclmain(intargc,char*const*argv)调用约定 ngx_cdecl 什么是调用约定 调用约定规定了 函数参数的传递顺序从左到右还是从右到左 由调用者还是被调用者负责清理堆栈 在 C 语言中默认的调用约定通常是由编译器决定的例如 GCC 默认使用 cdecl 但在跨平台或混合编程如与汇编、Windows 系统调用时可能需要显式指定。ngx_cdeclchar *const *argv argv 是一个数组在参数中退化为指针数组的每个元素是一个 char *字符串。 中间的 const 修饰的是数组里的指针本身。 约束力这意味着在 main 函数内部你不能修改 argv 数组中的指针指向。 const 修饰 *argv *argv 也是 argv[0], 所以 const 放在中间的含义是约束数组中的每一个元素不够修改数组中的元素的值 const 锁住的是 argv 数组中存储的“地址”,而不是锁住了“字符串内容”2 逻辑流程1 局部变量{ngx_buf_t*b;ngx_log_t*log;ngx_uint_ti;ngx_cycle_t*cycle,init_cycle;ngx_conf_dump_t*cd;ngx_core_conf_t*ccf;2 调试初始化ngx_debug_init();在Linux上不需要特殊的初始化所以定义为空。 在FreeBSD或其他BSD系统上可能有实际内容例如初始化某些内核级别的调试选项ngx_debug_init3 初始化错误码到错误字符串的映射机制if(ngx_strerror_init()!NGX_OK){return1;}ngx_strerror_initngx_strerror_init() 负责建立系统错误码到错误描述字符串的映射。 在 Unix/Linux 系统中当系统调用失败时errno 会被设置为一个错误码如 EACCES、ENOENT。 nginx 需要能够将这些错误码转换成人类可读的错误信息用于日志记录和错误输出。 在 Linux/Unix 环境下 实现直接返回成功 原因Unix 系统标准的 strerror_r 或 strerror 函数通常足够可靠且线程安全或 Nginx 有自己的线程安全封装 不需要额外的初始化步骤。 虽然什么都不做但保留了调用接口保证代码流程统一。 在 Windows 环境下 实现可能包含实际的初始化逻辑。 Windows 的错误码机制GetLastError与 Unix 的 errno 不同4 解析命令行参数if(ngx_get_options(argc,argv)!NGX_OK){return1;}解析用户启动时 在命令行输入的参数 它决定了程序将执行哪种模式测试、发信号、正常启动ngx_get_options5 版本信息if(ngx_show_version){ngx_show_version_info();if(!ngx_test_config){return0;}}负责将 nginx 的版本信息输出到标准输出 ngx_show_version 全局标志由 ngx_get_options() 在解析命令行时设置。 当用户指定 -v显示版本或 -V显示版本及编译参数时该标志被设为 1。 ngx_test_config 全局标志由 ngx_get_options() 在解析到 -t 或 -T 时设置为 1表示仅测试配置文件而不实际运行服务。场景 A 仅查看版本 (nginx -v 或 nginx -V) ngx_show_version 1 (真)。 ngx_test_config 0 (假)。 进入外层 if执行 ngx_show_version_info()打印版本。 进入内层 if (!0)条件成立。 执行 return 0;。 结果程序立即退出速度极快无资源消耗。场景 B仅测试配置 (nginx -t) ngx_show_version 0 (假)。 ngx_test_config 1 (真)。 外层 if 条件不成立跳过整个代码块。 结果程序继续向下执行进行日志初始化、配置解析等直到后面的 if (ngx_test_config) 块处理测试逻辑。场景 C查看版本 且 测试配置 (nginx -V -t) 这是一个组合命令的场景也是内层 if 存在的意义。 ngx_show_version 1 (真)因为用了 -V。 ngx_test_config 1 (真)因为用了 -t。 进入外层 if执行 ngx_show_version_info()打印版本。 进入内层 if (!1)条件不成立。 不执行 return 0;。 结果程序打印完版本后继续向下执行进行后续的初始化以便完成配置测试的任务。6 初始化最大 socket 文件描述符数量/* TODO */ngx_max_sockets-1;ngx_max_sockets 这是一个 Nginx 的全局变量。 它用于记录 Nginx 当前进程或整个系统允许打开的最大 Socket套接字数量 -1将其赋值为 -1。 -1 通常是一个哨兵值Sentinel Value表示“未初始化”、“未知”或“无效”。 它明确表示“此时我们还不知道最大 Socket 数是多少稍后再计算。”7 时间系统初始化ngx_time_init();ngx_time_init8 初始化正则表达式引擎#if(NGX_PCRE)ngx_regex_init();#endif在编译时启用了 PCRE 支持的前提下 初始化正则表达式引擎。 #if (NGX_PCRE) 这是一个预处理器指令Preprocessor Directive。 它在编译阶段而非运行阶段起作用。 如果 NGX_PCRE 宏被定义为非零值表示编译时开启了 PCRE 支持则包含其中的代码 否则编译器会直接忽略这部分代码。 ngx_regex_init() 调用 Nginx 的正则表达式初始化函数。 负责初始化 PCRE 库的内部状态、内存池、缓存等。 #endif 结束条件编译块。9 获取进程 idngx_pidngx_getpid();ngx_parentngx_getppid();ngx_getpid()Nginx 封装的系统调用 getpid()。 ngx_getppid()Nginx 封装的系统调用 getppid()。 全局变量ngx_pid 和 ngx_parent 是 Nginx 的全局变量整个程序的任何地方都可以访问为什么需要保存 PID/PPID ? Nginx 采用 Master-Worker 多进程模型 进程身份管理至关重要 进程身份识别 Master 进程需要知道自己是 Master负责管理 Worker。 Worker 进程需要知道自己是 Worker负责处理请求。 信号处理需要进程的 PID (向 哪个进程发送信号10 日志系统初始化logngx_log_init(ngx_prefix,ngx_error_log);if(logNULL){return1;}11 SSL/TLS 加密库初始化/* STUB */#if(NGX_OPENSSL)ngx_ssl_init(log);#endif它负责在编译时启用了 OpenSSL 支持的前提下 初始化 SSL 环境为后续的 HTTPS 连接提供加密基础12 创建 init_cycle/* * init_cycle-log is required for signal handlers and * ngx_process_options() */ngx_memzero(init_cycle,sizeof(ngx_cycle_t));init_cycle.loglog;ngx_cycleinit_cycle;init_cycle.poolngx_create_pool(1024,log);if(init_cycle.poolNULL){return1;}创建一个临时的 ngx_cycle_t 结构体init_cycle 用于承载早期初始化阶段所需的环境#1 清空临时结构体 #2 设置日志对象 #3 将全局 ngx_cycle 指向临时结构 #4 创建内存池, 大小 1024 字节 如果内存池创建失败通常是系统内存不足main 函数返回 1程序退出init_cycle 与 cycle 的关系 cycle 是 Nginx 架构中最核心的数据结构之一它是整个 Nginx 服务器的运行时上下文和生命线 本质cycle 代表 Nginx 的一个完整运行周期包含了服务器运行时所需的所有核心数据 生命周期从 ngx_init_cycle() 创建开始直到 Nginx 退出时销毁 init_cycle 是临时周期对象,作为初始化阶段的临时载体 用于在真正运行时的 cycle 对象创建之前承载所有必需的基础资源和配置信息。 为什么需要两个 cycle 对象 init_cycle: 临时的、简化的周期对象只包含初始化必需的最少信息 cycle: 最终的、完整的运行时周期对象包含所有模块、配置、连接等完整信息 这种设计解决了鸡生蛋蛋生鸡的问题 要创建完整的 cycle 需要很多基础资源但这些资源本身又需要一个 cycle 来管理。 所以 init_cycle 是用来收集和管理 创建完整的 cycle 需要的资源的ngx_create_pool13 保存命令行参数if(ngx_save_argv(init_cycle,argc,argv)!NGX_OK){return1;}将命令行参数从栈上复制到临时 cycle 的内存池中 使其持久化并纳入 nginx 的内存管理体系ngx_save_argv14 处理命令行选项if(ngx_process_options(init_cycle)!NGX_OK){return1;}应用之前解析出来的命令行参数ngx_process_options15 系统相关初始化if(ngx_os_init(log)!NGX_OK){return1;}ngx_os_init16 初始化 CRC32 查找表/* * ngx_crc32_table_init() requires ngx_cacheline_size set in ngx_os_init() */if(ngx_crc32_table_init()!NGX_OK){return1;}17 slab 内存分配器初始化/* * ngx_slab_sizes_init() requires ngx_pagesize set in ngx_os_init() */ngx_slab_sizes_init();nginx 使用 slab 分配器 来管理共享内存 ngx_slab_sizes_init 计算并设置 Slab 分配器的内存块大小规则 为后续创建共享内存池奠定基础。 为什么要初始化“尺寸” Slab 分配器为了提高性能和减少碎片 不支持任意大小的内存分配。 它将内存划分为不同大小的等级Size Classes。 根据操作系统页面大小计算 Slab 池的内存块尺寸等级 为后续限流、状态统计等模块创建跨进程共享内存池提供准确的分配规则 是 Nginx 实现多进程状态共享的底层基石。ngx_slab_sizes_init18 继承监听套接字if(ngx_add_inherited_sockets(init_cycle)!NGX_OK){return1;}在 Nginx 平滑升级场景中 新进程会从旧进程继承监听 Socket而不是重新创建。19 模块预初始化if(ngx_preinit_modules()!NGX_OK){return1;}预初始化所有 nginx 模块 在配置解析之前注册所有模块 确保后续 ngx_init_cycle() 解析 配置文件 nginx.conf 时能够识别所有配置指令 是 Nginx 从代码转变为可配置服务的关键桥梁—— 没有模块预初始化配置文件中的任何指令都无法被识别和处理20 创建完整的运行时周期对象cyclengx_init_cycle(init_cycle);if(cycleNULL){if(ngx_test_config){ngx_log_stderr(0,configuration file %s test failed,init_cycle.conf_file.data);}return1;}基于临时的 init_cycle 创建完整的运行时周期 cycle 完成了从 准备环境 到 可用服务 的转变 如果失败根据是否为测试模式决定错误输出方式 是 Nginx 能否成功启动的决定性步骤—— 在此之前都是准备工作 在此之后 Nginx 才真正成为一个可处理请求的 Web 服务器。ngx_init_cycle21if(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){cdcycle-config_dump.elts;for(i0;icycle-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);bcd[i].buffer;(void)ngx_write_fd(ngx_stdout,b-pos,b-last-b-pos);ngx_write_stdout(NGX_LINEFEED);}}return0;}if(ngx_signal){returnngx_signal_process(cycle,ngx_signal);}ngx_os_status(cycle-log);ngx_cyclecycle;ccf(ngx_core_conf_t*)ngx_get_conf(cycle-conf_ctx,ngx_core_module);if(ccf-masterngx_processNGX_PROCESS_SINGLE){ngx_processNGX_PROCESS_MASTER;}#if!(NGX_WIN32)if(ngx_init_signals(cycle-log)!NGX_OK){return1;}if(!ngx_inheritedccf-daemon){if(ngx_daemon(cycle-log)!NGX_OK){return1;}ngx_daemonized1;}if(ngx_inherited){ngx_daemonized1;}#endifif(ngx_create_pidfile(ccf-pid,cycle-log)!NGX_OK){return1;}if(ngx_log_redirect_stderr(cycle)!NGX_OK){return1;}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_stderr0;if(ngx_processNGX_PROCESS_SINGLE){ngx_single_process_cycle(cycle);}else{ngx_master_process_cycle(cycle);}return0;}

相关新闻