Nginx:进程模型
Posted 看,未来
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx:进程模型相关的知识,希望对你有一定的参考价值。
文章目录
注:由于源码太长,于是我放另一篇里去了。
《Nginx(3):进程模型》篇 配套代码
进程模型图
main入口伪代码
我来用伪代码简化一下这个入口哈,看看nginx启动的时候都干了些什么。
int ngx_cdecl main(int argc, char *const *argv)
{
//1、声明一波对象,打印一些基本信息,初始化一些东西
//2、通过 ngx_save_argv(&init_cycle, argc, argv),将 argv 参数拷贝到一份内存中
if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
return 1;
}
/*
argv 参数是什么?就是管理者想nginx传入的信号,如 nginx -s reload之类的
在后面还能找到信号捕捉函数,我已经把源码也铺开了,后面再看
*/
//然后继续init
//3、呐,信号捕捉,再说
if (ngx_signal) {
return ngx_signal_process(cycle, ngx_signal);
}
//继续配置
#if !(NGX_WIN32)
if (ngx_init_signals(cycle->log) != NGX_OK) {
return 1;
}
if (!ngx_inherited && ccf->daemon) {
// 4、创建守护进程,源码也铺开了,后面再说
if (ngx_daemon(cycle->log) != NGX_OK) {
return 1;
}
ngx_daemonized = 1;
}
if (ngx_inherited) {
ngx_daemonized = 1;
}
#endif
//继续配置呗
//5、启动进程循环咯
if (ngx_process == NGX_PROCESS_SINGLE) {
ngx_single_process_cycle(cycle);
} else {
//源码已铺开,同上。
//修改进程名,启动worker并管理
ngx_master_process_cycle(cycle);
}
return 0;
}
怎么样,现在局势就很明朗了吧。要想看的那么细比方说初始化了些啥,可以自己去那篇源码里看。
ngx_signal_process 信号捕捉
ngx_save_argv 没啥好看的哈,就一波空间配置并内容拷贝。
Nginx定义ngx_signal_t,用于描述接收到信号时的行为:
typedef struct {
//信号值
int signo;
//信号名称
char *signame;
//nginx对应的名称,例如reload,reopen,stop等
char *name;
//nginx自定义信号处理函数
void (*handler)(int signo, siginfo_t *siginfo, void *ucontext);
} ngx_signal_t;
1、在main函数中调用ngx_init_signals,初始化所有需要自定义处理的信号,代码功能很直接,就不放出来了,隔壁有。
2、在master守护进程将所有信号添加到信号集里面。(ngx_master_process_cycle(ngx_cycle_t *cycle))。
3、worker进程将所有信号添加到信号集里面。(ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker))。
4、master向worker发送信号(ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo))。
ngx_daemon 创建守护进程
这里面东西就多了哈。
这个函数学着点,怎么创建一个守护进程。
这段代码不做删减,只做注释,好东西我还是认得出来的。
ngx_int_t ngx_daemon(ngx_log_t *log)
{
int fd;
//要成为守护进程,首先要成为孤儿,进孤儿院
/*
调用fork函数创建子进程后,使父进程立即退出。产生的子进程将被init进程接管,
同时,所产生的新进程将变为在后台运行。
*/
switch (fork()) {
case -1:
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
return NGX_ERROR;
case 0:
break;
default:
exit(0);
}
ngx_pid = ngx_getpid();
/*
孤儿调用setsid()函数脱离控制终端和进程组,使该进程成为会话组长,并与原来的登录会话和进程组脱离。
此时孤儿进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。
为了避免这种情况,可以通过使进程不再成为会话组长来禁止进程重新打开控制终端,
父进程(会话组长)退出,子进程继续执行,并不再拥有打开控制终端的能力。
在正在执行的进程中调用INIT_DAEMON后,进程将成为守护进程,脱离控制终端进入后台执行。
*/
if (setsid() == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
return NGX_ERROR;
}
/*
很多情况下,守护进程会创建一些临时文件。出于安全性的考虑,往往不希望这些文件被别的用户查看。
这时,可以使用umask函数修改文件权限,创建掩码的取值,以满足守护进程的要求。
*/
umask(0);
/*
新产生的进程从父进程继承了某些打开的文件描述符,如果不使用这些文件描述符,则需要关闭它们。
守护进程是运行在系统后台的,不应该在终端有任何的输出信息。
可以使用dup函数将标准输入、输出和错误输出重定向到/dev/null设备上
(/dev/null是一个空设备,向其写入数据不会有任何输出)。
*/
fd = open("/dev/null", O_RDWR); //难怪在好多地方有看到这么个写法,当时就不知道是干嘛的
if (fd == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"open(\\"/dev/null\\") failed");
return NGX_ERROR;
}
if (dup2(fd, STDIN_FILENO) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
return NGX_ERROR;
}
if (dup2(fd, STDOUT_FILENO) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
return NGX_ERROR;
}
#if 0
if (dup2(fd, STDERR_FILENO) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
return NGX_ERROR;
}
#endif
if (fd > STDERR_FILENO) {
if (close(fd) == -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
return NGX_ERROR;
}
}
/*
改变当前工作目录(nginx没有做)
使用fork函数产生的子进程将继承父进程的当前工作目录。当进程没有结束时,其工作目录是不能被卸载的。
为了防止这种问题发生,守护进程一般会将其工作目录更改到根目录下(/目录)。
*/
return NGX_OK;
}
ngx_master_process_cycle 启动master循环
那个孤单进程循环就不说了,人都单身了看个代码还要single吗。。。
void ngx_master_process_cycle(ngx_cycle_t *cycle)
{
//声明一大堆对象
//设置一堆的信号
//然后统统屏蔽了,因为员工还没来上班
//然后给进程起个名字
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
//启动worker进程(再说咯)
ngx_start_worker_processes(cycle, ccf->worker_processes,NGX_PROCESS_RESPAWN);
//启动文件cache管理进程
ngx_start_cache_manager_processes(cycle, 0);
//初始化一些变量咯
ngx_new_binary = 0;
delay = 0;
sigio = 0;
live = 1;
//开始循环处理信号咯
for ( ;; ) {
//delay用来设置等待worker进程退出的时间,master接受退出信号后,
//首先发送退出信号给worker,而worker退出需要一些时间
if (delay) {
if (ngx_sigalrm) {
sigio = 0;
delay *= 2;
ngx_sigalrm = 0;
}
//日志
//设置定时器,以系统真实时间来计算,送出SIGALRM信号
}
}
//···
//每次处理完一个信号,master进程会被挂起,直到有新的信号到来
sigsuspend(&set);
//···
/*
子进程退出后,作为父进程的master进程会收到SIGCHLD信号
发现信号是SIGCHLD后执行ngx_process_get_status函数判断worker子进程是正常退出,还是异常退出
如果发现worker子进程如果是正常退出的,会将exited置为1
master进程接收到信号,从挂起状态恢复,继续执行
以上这些,在signal_handle中
*/
/*
发现ngx_reap=1后,ngx_reap_children函数判断是否需要重启worker进程
如果worker是因为收到了quit信号正常退出的,所有worker进程退出时,live=0
live=0 并且收到了ngx_quit信号 通过ngx_master_process_exit关闭master进程
*/
if (ngx_reap) {
ngx_reap = 0;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
live = ngx_reap_children(cycle); //顺便说一下,它会查看所有子进程,因为它也不知道是谁走了
//在这个函数的第65行,有重启函数
}
if (!live && (ngx_terminate || ngx_quit)) {
ngx_master_process_exit(cycle);
}
//如果收到SIGTERM信号或SIGINT信号退出信号,设置推出延迟时间
//···
/*
如果收到SIGQUIT信号
给所有worker进程发送SIGQUIT信号
关闭所有监听套接字socket
*/
//如果收到SIGHUP信号,平滑升级就按平滑升级处理,不然就重新配置
//and底下一堆的信号处理方案
}
}
流程图收尾
以上是关于Nginx:进程模型的主要内容,如果未能解决你的问题,请参考以下文章