文章目录
- nginx运行模式与进程模式
- 进程模式流程图
- 默认初始化运行模式与进程模式(宏展开)
- cpu_affinity多CPU绑定合理性判定
- Nginx的daemon创建(os/unix/ngx_daemon.c)
- 运行模式、进程模式启动
- 多进程模式下master处理流程
- 设置进程信号、初始化信号掩码、屏蔽相关信号
- 调用ngx_start_worker_processes函数派生Worker进程
- 创建cache进程,用于缓存管理
- 进入主循环,通过sigsuspend使进程等待信号
- 调用ngx_start_worker_processes函数派生Worker进程
- ngx_pass_open_channel函数
- ngx_write_channel函数
- 讨论
nginx运行模式与进程模式
nginx有守护(daemon)模式与非守护(非daemon)模式两种模式,两种模式在运行后的区别在于:
- 守护模式:nginx启动后直接回到命令提示符界面,不接受任何的输入和输出,其父进程为Linux的init进程;启动nginx进程后,会使用fork创建子进程进行后续操作,启动进程会自动结束。
- 非守护模式:nginx启动后没有回到命令提示符界面,当nginx有输出时,会直接在终端输出,其父进程为bash。
进程模式分为单进程模式和多进程模式
进程模式流程图
默认初始化运行模式与进程模式(宏展开)
static char *ngx_core_module_init_conf(ngx_cycle_t *cycle, void *conf)//函数中(core/nginx.c文件中):
{ ………
ngx_conf_init_value(ccf->daemon, 1); //守护进程,daemon守护方式
ngx_conf_init_value(ccf->master, 1); //多进程模式
………
ngx_conf_init_value(ccf->worker_processes, 1); //默认一个worker
………//worker进程绑定CPU设置合理性判断,用户名,文件锁等初始化
return NGX_CONF_OK;
}
//宏定义展开如下
#define NGX_CONF_UNSET -1
………
#define ngx_conf_init_value(conf, default) \
if (conf == NGX_CONF_UNSET) { \
conf = default; \
}
………
static char *ngx_core_module_init_conf(ngx_cycle_t *cycle, void *conf)//函数中(core/nginx.c文件中):
{
………
//ngx_conf_init_value(ccf->daemon, 1)按宏展开;
if (ccf->daemon == -1) {//守护进程,daemon守护方式
ccf->daemon = 1;
}
//以下ngx_conf_init_value宏类似展开
………
return NGX_CONF_OK;
}
cpu_affinity多CPU绑定合理性判定
进行绑定时,必须cpu数目与worker进程数目相等,否则不好绑定
#if (NGX_HAVE_CPU_AFFINITY) //支持多CPU绑定
if (!ccf->cpu_affinity_auto //非自动
&& ccf->cpu_affinity_n //非0
&& ccf->cpu_affinity_n != 1 //非1
&& ccf->cpu_affinity_n != (ngx_uint_t) ccf->worker_processes) //与工作进程数相等
{
//错误处理及记录;
}
#endif
Nginx的daemon创建(os/unix/ngx_daemon.c)
- 创建一个子进程,自己(父进程)关闭结束;.
- 创建新的会话;
- 设置进程的umask为0;
- daemon模式下,没有输入输出;
- 返回
//创建新的会话
………
switch (fork())
{
case -1:
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
return NGX_ERROR;
case 0:
break; //子进程
default:
exit(0); //父进程退出,结束
}
………
//在守护模式下没有输出,nginx采用重定向的方式对输出进行重定向
………
fd = open("/dev/null", O_RDWR);
if (fd == -1) {
//错误记录处理,
return NGX_ERROR;
}
if (dup2(fd, STDIN_FILENO) == -1) {
//错误记录处理,
return NGX_ERROR; } //将标准输入重定位到fd,即null设备
if (dup2(fd, STDOUT_FILENO) == -1) {
//错误记录处理,
return NGX_ERROR; } //将标准输出重定位到fd,即null设备
………
ngx_int_t ngx_daemon(ngx_log_t *log) //汇总与总结
{
//定义变量;
//创建一个子进程,自己(父进程)关闭结束;
ngx_parent = ngx_pid; //ngx_pid为全局变量
ngx_pid = ngx_getpid();
if (setsid() == -1) { //创建新的会话
//错误处理与记录后返回;
}
umask(0);//设置掩码为0
//daemon模式下,关闭标准输入与输出;
}
运行模式、进程模式启动
在Nginx的main函数最后,一切准备就绪后,根据ngx_process 值的指示,判定单/多进程模式
int ngx_cdecl main(int argc, char *const *argv)
{
………//前期内存池等各种准备
if (ngx_process == NGX_PROCESS_SINGLE) { //为0
ngx_single_process_cycle(cycle); //单进程模式
}
else //不为0
{
ngx_master_process_cycle(cycle); //多进程模式
}
return 0;
}
多进程模式下master处理流程
- 设置进程信号、初始化信号掩码屏蔽相关信号。
- 调用ngx_start_worker_processes函数派生Worker进程;
- ngx_start_cache_manager_processes函数派生cache manager进程以及cache loader进程; cache进程用于缓存管理;
- 进入主循环,通过sigsuspend使进程等待信号;
- 待收到信号后进入ngx_signal_handler进行信号处理
设置进程信号、初始化信号掩码、屏蔽相关信号
//Nginx定义的几个常见信号
#define NGX_SHUTDOWN_SIGNAL QUIT //终端退出
#define NGX_TERMINATE_SIGNAL TERM //软件终止
#define NGX_NOACCEPT_SIGNAL WINCH //窗口大小改变
#define NGX_RECONFIGURE_SIGNAL HUP //终端挂起或终端控制进程结束
- nginx信号解析
#define ngx_signal_helper(n) SIG##n // 表示前后两个字符串合并(宏)
#define ngx_signal_value(n) ngx_signal_helper(n) //程序中使用的宏
ngx_signal_value (NGX_RECONFIGURE_SIGNAL)//,经过宏展开后
1. ngx_signal_value ( HUP ),经过宏展开后
2. ngx_signal_helper( HUP ),再经过宏展开后
3. SIGHUP 这正是Linux的信号
………//定义局部临时变量
sigemptyset(&set);
sigaddset(&set, SIGCHLD); //Linux信号标准名字
sigaddset(&set, SIGALRM); //Linux信号标准名字
sigaddset(&set, SIGIO); //Linux信号标准名字
sigaddset(&set, SIGINT); //Linux信号标准名字
sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL)); //Linux信号别名
sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) { //若新增屏蔽(搁置)信号集合set 失败
//错误处理并记录
}
sigemptyset(&set);//清空信号集,以便后面使用
………
调用ngx_start_worker_processes函数派生Worker进程
………
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN);
………
创建cache进程,用于缓存管理
………
ngx_start_cache_manager_processes(cycle, 0);
………
进入主循环,通过sigsuspend使进程等待信号
………
for(;;){
………//设置时钟
sigsuspend(&set);
ngx_time_update();//更新时钟
………
}
调用ngx_start_worker_processes函数派生Worker进程
多进程模式下Worker进程的作用(os/unix/ ngx_process_cycle.c)
- 读取请求、解析请求、处理请求,产生结果后,返回给客户,最后断开连接。
- 一个请求在一个且仅在该worker内处理完成;
- ngx_start_worker_processes函数调用ngx_spawn_process函数派生worker进程.
ngx_start_worker_processes函数的主要功能及其过程如下:
- 局部临时变量定义。如创建一个ngx_channel_t 变量ch,并将ch的command置为NGX_CMD_OPEN_CHANNEL;表示新建一个进程;
- 循环创建n个worker进程;
- 调用ngx_spawn_process创建一个worker进程,设置ngx_process_slot 值;
- 为了ngx_processes数组的值同步,设置好ch的其它字段的值,
- 调用ngx_pass_open_channel广播,使得以前创建的进程,更新ngx_processes数据;
- 重复,3~5,直至n个worker创建完成。
//第三个参数type
#define NGX_PROCESS_NORESPAWN -1 //子进程退出,父进程不重启
#define NGX_PROCESS_JUST_SPAWN -2 //区分刚建的子进程与其它子进程
#define NGX_PROCESS_RESPAWN -3//子进程退出,父进程重启
#define NGX_PROCESS_JUST_RESPAWN -4//区分刚建的子进程与其它子进程,子进程退出,父进程重启
#define NGX_PROCESS_DETACHED -5 //新派生进程与父进程脱离关系
//第二个参数n :创建worker进程的总数
static voidngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
struct ngx_cycle_s {
void ****conf_ctx; //保持所有模块的配置结构体
ngx_pool_t *pool; //内存池
ngx_log_t *log; //日志信息
ngx_log_t new_log;
ngx_uint_t log_use_stderr; /* unsigned log_use_stderr:1; */
ngx_connection_t **files; //文件句柄
ngx_connection_t *free_connections; //可用连接池
ngx_uint_t free_connection_n;//可用连接池总数
ngx_module_t **modules; //模块信息
ngx_uint_t modules_n;
ngx_uint_t modules_used; /* unsigned modules_used:1; */
ngx_queue_t reusable_connections_queue; //再利用连接队列
ngx_uint_t reusable_connections_n; //再利用连接数
ngx_array_t listening; //被监听端口
ngx_array_t paths; //操作目录
ngx_array_t config_dump;
ngx_rbtree_t config_dump_rbtree;
ngx_rbtree_node_t config_dump_sentinel;
ngx_list_t open_files; //打开文件
ngx_list_t shared_memory; //共享文件
ngx_uint_t connection_n; //当前进程中所有连接对象的总数
ngx_uint_t files_n;//代开文件个数
ngx_connection_t *connections; //指向当前进程中的所有连接对象
ngx_event_t *read_events; //读事件
ngx_event_t *write_events; //写事件
ngx_cycle_t *old_cycle; //old cycle指针
ngx_str_t conf_file; //配置文件
ngx_str_t conf_param; //配置参数
ngx_str_t conf_prefix; //配置前缀
ngx_str_t prefix; //前缀
ngx_str_t lock_file; //用于进程间同步的文件锁
ngx_str_t hostname;//主机名
};
ngx_channel_t结构体
//ngx_channel_t 的command
#define NGX_CMD_OPEN_CHANNEL 1 //新建
#define NGX_CMD_CLOSE_CHANNEL 2 //关闭
#define NGX_CMD_QUIT 3 //退出
#define NGX_CMD_TERMINATE 4 //终止
#define NGX_CMD_REOPEN 5 //重新打开(重启)
typedef struct {
ngx_uint_t command; //命令
ngx_pid_t pid; //进程号
ngx_int_t slot; // ngx_processes 数组中worker进程下标
ngx_fd_t fd;//保存用于socketpair全双工的socket
} ngx_channel_t;
ngx_processes数组
作用有以下几个方面:
(1)保存创建进程的有关信息;
(2)便于worker进程间通信;
(3)便于worker进程与master进程间通信;
ngx_process_t ngx_processes[NGX_MAX_PROCESSES];
typedef struct { ngx_pid_t
pid; //worker进程的PID
int status; //状态
ngx_socket_t channel[2]; //worker与master之间通信的socketpair
ngx_spawn_proc_pt proc; //回调函数
void *data; // proc回调函数的参数
char *name; //worker子进程的名字
unsigned respawn:1; //是否重启
unsigned just_spawn:1; //是否刚创建
unsigned detached:1; //是否脱离
unsigned exiting:1;
unsigned exited:1;
} ngx_process_t;
static voidngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type){
………// 创建并初始化ngx_channel_t结构体类型ch;
ngx_channel_t ch;
ch.command = NGX_CMD_OPEN_CHANNEL;
for (i = 0; i < n; i++) { //依次创建n个worker进程
ngx_spawn_process(cycle, ngx_worker_process_cycle, (void *) (intptr_t) i, "worker process", type);
//调用 ngx_spawn_process函数,创建worker进程,其信息ngx_processes数组ngx_process_slot 元素的中;
ch.pid = ngx_processes[ngx_process_slot].pid;
ch.slot = ngx_process_slot;
ch.fd = ngx_processes[ngx_process_slot].channel[0]; //刚创建进程的写端给其它进程,便于后续通信
ngx_pass_open_channel(cycle, &ch); //调用ngx_pass_open_channel广播(这是有linux的进程机制决定的,fork时父子进程会共享内存,当进程对内存内容进行修改时,内核会复制一份数据到另一片内存时,这时前后的内容会不一致,所以才需要广播)
}
}
ngx_spawn_process函数
- 在ngx_processes数组中找到一个保存worker进程信息的元素下标s;①如果respawn大于0, respawn就是s;②如果不大于0,从ngx_processes数组中找到其元素的pid成员为-1的(空闲)元素,该元素就是的数组下标就是s;但该s不能超过最大worker范围。
if (respawn >= 0)
{
s = respawn; // ①
}
else
{ // ②,遍历ngx_processes数组,确定s
for (s = 0; s < ngx_last_process; s++)
{
if (ngx_processes[s].pid == -1) { break; }
}
if (s == NGX_MAX_PROCESSES) { //最大为1024
//超过最大worker数限制,错误处理并记录
return NGX_INVALID_PID;
}
}
- 如果respawn不是NGX_PROCESS_DETACHED,即派生子进程与父进程不是非脱离关系,①创建通信socketpair, ②以非阻塞方式设置读写端, ③将写端设置为异步写, ④并将写端的SIGIO以及SIGURG信号的属主设为父进程; ⑤设置Master、worker进程执行exec()函数后,关闭socket。
①创建通信socketpair,
if (respawn != NGX_PROCESS_DETACHED) {//2,如果非关系成立
if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
// ①
{
//错误处理并记录;
return NGX_INVALID_PID;
}
②以非阻塞方式设置读写端,
if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) { //设置写端
//错误处理并记录;
return NGX_INVALID_PID;
}
③将写端设置为异步写,
on = 1;
if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) { // ③
// 错误处理并记录;
return NGX_INVALID_PID;
}
④并将写端的SIGIO以及SIGURG信号的属主设为ngx_pid(父进程);
if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) { // ④
// 错误处理并记录;
return NGX_INVALID_PID;
}
⑤设置Master、worker进程执行exec()函数后,关闭socket。
if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) { // ⑤
// 错误处理并记录;
return NGX_INVALID_PID;
}//以下设置当前子进程的socket,Master进程用于监听
ngx_channel = ngx_processes[s].channel[1];//给全局变量ngx_channel 赋值
}
- 如果respawn是NGX_PROCESS_DETACHED,设置该worker的通信通道都为-1;
else//是2的if的else分支,否则,则是NGX_PROCESS_DETACHED脱离关系
{
ngx_processes[s].channel[0] = -1; //创建的是master,通信pair置为-1
ngx_processes[s].channel[1] = -1;
}
- fork派生worker进程;
//为ngx_start_worker_processes函数记下ngx_process_slot 值ngx_process_slot = s; //ngx_process_slots是全部变量
pid = fork(); //派生子进程
- worker子进程执行回调proc;
switch (pid) {
case -1:
错误处理并记录;
return NGX_INVALID_PID;
case 0: //5,子进程执行回调函数proc
ngx_parent = ngx_pid;
ngx_pid = ngx_getpid();
proc(cycle, data);
break;
default: break; //父进程往后执行
}
- 父进程填充worker子进程的信息到ngx_processes数组s元素中;
ngx_processes[s].pid = pid; //
ngx_processes[s].exited = 0;
f (respawn >= 0) { //如果是重启子进程,返回
return pid;
}
ngx_spawn_process(cycle, ngx_worker_process_cycle, (void *) (intptr_t) i, "worker process", type);
1.第一个参数: cycle, ngx_start_worker_processes函数
2.第二个参数:回调函数ngx_worker_process_cycle,该函数在os/unix/ ngx_process_cycle.c中定义;
3.第三个参数:i,即第几个worker
4.第四个参数:worker进程名字
5.第五个参数:类型,来自ngx_start_worker_processes函数的形参
ngx_pass_open_channel函数
遍历ngx_processes数组广播,对每个worker子进程发送信息。
static void ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch):
- 对每个非刚创建、不存在进程以及父进程socket关闭的子进程;
- 调用ngx_write_channel发送消息内容;
typedef struct {
ngx_uint_t command; //命令,见下页
ngx_pid_t pid; //进程号
ngx_int_t slot; // ngx_processes 数组中worker进程下标
ngx_fd_t fd;//保存用于socketpair全双工的socket
} ngx_channel_t;
static void ngx_pass_open_channel(ngx_cycle_t *cycle, ngx_channel_t *ch)
{
for (i = 0; i < ngx_last_process; i++)
{ //过滤刚建的子进程、不存在的子进程以及关闭socket的子进程
if (i == ngx_process_slot || //刚刚创建的
ngx_processes[i].pid == -1 || //没有进程数据的数组元素
ngx_processes[i].channel[0] == -1) //无法通信了
{ continue; }
..........
//给每个子进程的父进程发送刚建进程的worker信息
ngx_write_channel(ngx_processes[i].channel[0], ch, sizeof(ngx_channel_t), cycle->log);
}
}
ngx_write_channel函数
- 构造struct msghdr 类型msg;//Linux系统层次接口
- 调用sendmsg发送msg;//Linux系统层次接口
ngx_int_tngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
{
ssize_t n; //声明所需变量
ngx_err_t err;
struct iovec iov[1];
struct msghdr msg;
……
iov[0].iov_base = (char *) ch; //构造msg
iov[0].iov_len = size;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
n = sendmsg(s, &msg, 0);//发送msg,Linux编程接口
……//错误处理等
return NGX_OK;
}
讨论
- 为什么nginx使用的是进程不是线程
是因为 Nginx 要保证高可用性,多线程之间会共享地址空间,当某一个第三方模块引发了一个段错误时,就会导致整个 Nginx 进程挂掉。而采用多进程模型不会出现这个问题