nginx 进程模型

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)

  1. 创建一个子进程,自己(父进程)关闭结束;.
  2. 创建新的会话;
  3. 设置进程的umask为0;
  4. daemon模式下,没有输入输出;
  5. 返回
//创建新的会话
………
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处理流程

在这里插入图片描述

  1. 设置进程信号、初始化信号掩码屏蔽相关信号。
  2. 调用ngx_start_worker_processes函数派生Worker进程;
  3. ngx_start_cache_manager_processes函数派生cache manager进程以及cache loader进程; cache进程用于缓存管理;
  4. 进入主循环,通过sigsuspend使进程等待信号;
  5. 待收到信号后进入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)

  1. 读取请求、解析请求、处理请求,产生结果后,返回给客户,最后断开连接。
  2. 一个请求在一个且仅在该worker内处理完成;
  3. ngx_start_worker_processes函数调用ngx_spawn_process函数派生worker进程.

ngx_start_worker_processes函数的主要功能及其过程如下:

  1. 局部临时变量定义。如创建一个ngx_channel_t 变量ch,并将ch的command置为NGX_CMD_OPEN_CHANNEL;表示新建一个进程;
  2. 循环创建n个worker进程;
  3. 调用ngx_spawn_process创建一个worker进程,设置ngx_process_slot 值;
  4. 为了ngx_processes数组的值同步,设置好ch的其它字段的值,
  5. 调用ngx_pass_open_channel广播,使得以前创建的进程,更新ngx_processes数据;
  6. 重复,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函数

  1. 在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;     
   } 
}
  1. 如果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 赋值
}
  1. 如果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;  
  }
  1. fork派生worker进程;
//为ngx_start_worker_processes函数记下ngx_process_slot 值ngx_process_slot = s; //ngx_process_slots是全部变量
   
pid = fork();    //派生子进程
  1. 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;  //父进程往后执行
  }
  1. 父进程填充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):

  1. 对每个非刚创建、不存在进程以及父进程socket关闭的子进程;
  2. 调用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函数

  1. 构造struct msghdr 类型msg;//Linux系统层次接口
  2. 调用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 进程挂掉。而采用多进程模型不会出现这个问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值