概述
图一:nginx 启动及内存申请过程分析
任何程序都离不开启动和配置解析。ngx 的代码离不开 ngx_cycle_s 和 ngx_pool_s 这两个核心数据结构,所以我们在启动之前先来分析下。
内存申请过程分为 3 步
-
假如申请的内存小于当前块剩余的空间,则直接在当前块中分配。
-
假如当前块空间不足,则调用 ngx_palloc_block 分配一个新块然后把新块链接到 d.next 中,然后分配数据。
-
假如申请的大小大于当前块的最大值,则直接调用 ngx_palloc_large 分配一个大块,并且链接到 pool→large 链表中
内存分配过程图解如下
(图片来自网络)
为了更好理解上面的图,可以参看文末附 2 的几个数据结构:ngx_pool_s 及 ngx_cycle_s。
知道了这两个核心数据结构之后,我们正式进入 main 函数,main 函数执行过程如下
-
调用 ngx_get_options() 解析命令参数;
-
调用 ngx_time_init() 初始化并更新时间,如全局变量ngx_cached_time;
-
调用 ngx_log_init() 初始化日志,如初始化全局变量 ngx_prefix,打开日志文件 ngx_log_file.fd;
-
清零全局变量 ngx_cycle,并为 ngx_cycle.pool 创建大小为 1024B 的内存池;
-
调用 ngx_save_argv() 保存命令行参数至全局变量 ngx_os_argv、ngx_argc、ngx_argv 中;
-
调用 ngx_process_options() 初始化 ngx_cycle 的 prefix, conf_prefix, conf_file, conf_param 等字段;
-
调用 ngx_os_init() 初始化系统相关变量,如内存页面大小 ngx_pagesize , ngx_cacheline_size , 最大连接数 ngx_max_sockets 等;
-
调用 ngx_crc32_table_init() 初始化 CRC 表 ( 后续的 CRC 校验通过查表进行,效率高 );
-
调用 ngx_add_inherited_sockets() 继承 sockets:
-
解析环境变量 NGINX_VAR = "NGINX" 中的 sockets,并保存至 ngx_cycle.listening 数组;
-
设置 ngx_inherited = 1;
-
调用 ngx_set_inherited_sockets() 逐一对 ngx_cycle.listening 数组中的 sockets 进行设置;
-
-
初始化每个 module 的 index,并计算 ngx_max_module;
-
调用 ngx_init_cycle() 进行初始化;
-
该初始化主要对 ngx_cycle 结构进行;
-
-
若有信号,则进入 ngx_signal_process() 处理;
-
调用 ngx_init_signals() 初始化信号;主要完成信号处理程序的注册;
-
若无继承 sockets,且设置了守护进程标识,则调用 ngx_daemon() 创建守护进程;
-
调用 ngx_create_pidfile() 创建进程记录文件;( 非 NGX_PROCESS_MASTER = 1 进程,不创建该文件 )
-
进入进程主循环;
-
若为 NGX_PROCESS_SINGLE=1模式,则调用 ngx_single_process_cycle() 进入进程循环;
-
否则为 master-worker 模式,调用 ngx_master_process_cycle() 进入进程循环;
-
在 main 函数执行过程中,有一个非常重要的函数 ngx_init_cycle,这个阶段做了什么呢?下面分析 ngx_init_cycle,初始化过程:
-
更新 timezone 和 time
-
创建内存池
-
给 cycle 指针分配内存
-
保存安装路径,配置文件,启动参数等
-
初始化打开文件句柄
-
初始化共享内存
-
初始化连接队列
-
保存 hostname
-
调用各 NGX_CORE_MODULE 的 create_conf 方法
-
解析配置文件
-
调用各NGX_CORE_MODULE的init_conf方法
-
打开新的文件句柄
-
创建共享内存
-
处理监听socket
-
创建socket进行监听
-
调用各模块的init_module
图二:master 进程工作原理及工作工程
以下过程都在ngx_master_process_cycle 函数中进行,启动过程:
-
暂时阻塞所有 ngx 需要处理的信号
-
设置进程名称
-
启动工作进程
-
启动cache管理进程
-
进入循环开始处理相关信号
master 进程工作过程
-
设置 work 进程退出等待时间
-
挂起,等待新的信号来临
-
更新时间
-
如果有 worker 进程因为 SIGCHLD 信号退出了,则重启 worker 进程
-
master 进程退出。如果所有 worker 进程都退出了,并且收到 SIGTERM 信号或 SIGINT 信号或 SIGQUIT 信号等,master 进程开始处理退出
-
处理SIGTERM信号
-
处理SIGQUIT信号,并且关闭socket
-
处理SIGHUP信号
-
平滑升级,重启worker进程
-
不是平滑升级,需要重新读取配置
-
-
处理重启 10处理SIGUSR1信号 重新打开所有文件 11处理SIGUSR2信号 热代码替换,执行新的程序 12处理SIGWINCH信号,不再处理任何请求
图三:worker 进程工作原理
启动通过执行 ngx_start_worker_processes 函数:
-
先在 ngx_processes 数组中找坑位if (ngx_processes[s].pid == -1) {break;}
-
进程相关结构初始化工作
-
创建管道 ( socketpair )
-
设置管道为非阻塞模式
-
设置管道为异步模式
-
设置异步 I/O 的所有者
-
如果 exec 执行的时候本 fd 不传递给 exec 创建的进程
-
-
fork 创建子进程。创建成功后,子进程执行相关逻辑:proc(cycle, data)。
-
设置 ngx_processes[s] 相关属性
-
通知子进程新进程创建完毕 ngx_pass_open_channel(cycle, &ch);
接下来是 ngx_worker_process_cycle worker 进程逻辑
-
ngx_worker_process_init
-
初始化环境变量
-
设置进程优先级
-
设置文件句柄数量限制
-
设置 core_file 文件
-
用户组设置
-
cpu 亲和度设置
-
设定工作目录
-
设置随机种子数
-
初始化监听状态
-
调用各模块的init_process方法进行初始化
-
关闭别人的fd[1],保留别人的fd[1]用于互相通信。自己的fd[1]接收master进程的消息。
-
监听channel读事件
-
-
进程模式
-
处理管道信号。这个过程由 ngx_channel_handler 完成,这部分具体实现在管道事件中讲解。
-
-
线程模式
-
ngx_worker_thread_cycle 是一个线程的循环:死循环中除了处理退出信号。主要进行ngx_event_thread_process_posted工作,这块具体内容在后面讲事件模型的时候再展开。
-
-
处理相关信号
master 和 worker 通信原理为:
最后
以上就是彩色篮球为你收集整理的Nginx源码分析:3张图看懂启动及进程工作原理的全部内容,希望文章能够帮你解决Nginx源码分析:3张图看懂启动及进程工作原理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复