如不做特殊说明,本博客所使用的 nginx 源码版本是 1.0.14,[] 中是代码所在的文件!

上一个博客中我们将 main 函数执行流程分析完,到最后一步调用 ngx_master_process_cycle(),现在我们就来分析该函数的执行流程,写贴部分代码:

[os/unix/ngx_process_cycle.c]

   /* master 进程:监控进程 */
void
ngx_master_process_cycle(ngx_cycle_t *cycle)
{
char *title;
u_char *p;
size_t size;
ngx_int_t i;
ngx_uint_t n, sigio;
sigset_t set;
struct itimerval itv;
ngx_uint_t live;
ngx_msec_t delay;
ngx_listening_t *ls;
ngx_core_conf_t *ccf; /* master 进程是靠信号来控制的,所以以下可以看着是对信号的一些设置 */
/* sigemptyset() 用来将参数信号集初始化并清空 */
sigemptyset(&set);
/* sigaddset()用来将参数signum 代表的信号加入至参数set 信号集里。*/
sigaddset(&set, SIGCHLD); /* 加入 SIGCHILD 信号 */
sigaddset(&set, SIGALRM);
sigaddset(&set, SIGIO);
sigaddset(&set, SIGINT);
sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
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) == -) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"sigprocmask() failed");
} sigemptyset(&set);

  1.由于 master 进程是控制进程,他不做实际的工作,只是实现对 worker 进程的控制。所以 master 会一直阻塞起来,唯一驱动 master 进程进行工作的是信号! 101 - 12 行相当于把想要关心的信号都加入到 set 中。

     size = sizeof(master_process);

      for (i = ; i < ngx_argc; i++) {
size += ngx_strlen(ngx_argv[i]) + ;
} title = ngx_pnalloc(cycle->pool, size); p = ngx_cpymem(title, master_process, sizeof(master_process) - );
for (i = ; i < ngx_argc; i++) {
*p++ = ' ';
p = ngx_cpystrn(p, (u_char *) ngx_argv[i], size);
} ngx_setproctitle(title); /* 获取配置信息 */
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); /* 创建工作进程:worker_process ,这是重要的一步 */
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
/* 运行 Cache 进程 :该函数中还可能会创建另一个进程 */
ngx_start_cache_manager_processes(cycle, ); /* 全部是旗标 */
ngx_new_binary = ; /* 新的二进制文件 */
delay = ; /* 是否延时 */
sigio = ; /* IO信号 */
live = ; /* 是否活着 */

  2. 12-137 是为了设置进程的名称,该进程的名称格式是 "nginx: master process "+ 你启动 nginx 所输入的命令。例如我输入 sudo ./nginx -g aaa 启动 nginx,那么 master 进程名就是 : nginx: master process ./nginx -g -aaa

  3. 140 行获取配置信息

  4. 143 行很重要,调用 ngx_start_process () 函数创建 worker 进程,真正的工作也就从这里开始了,下一节我们将分析该函数。146 行调用 ngx_start_cache_manager_processes() 函数创建管理进程!这个函数我们以后也会进行相应的分析。

  5. 149-152 行初始化一些全局变量 ngx_new_binary 是否执行新的二进制文件、delay 是否时延、sigio 是否有 IO 信号、live 是否存活

      /* master 进程开始了,这里是个死循环 */
for ( ;; ) {
if (delay) {
if (ngx_sigalrm) {
sigio = ;
delay *= ;
ngx_sigalrm = ;
} ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, ,
"termination cycle: %d", delay); itv.it_interval.tv_sec = ;
itv.it_interval.tv_usec = ;
itv.it_value.tv_sec = delay / ;
itv.it_value.tv_usec = (delay % ) * ; //ÉèÖöšÊ±Æ÷
if (setitimer(ITIMER_REAL, &itv, NULL) == -) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setitimer() failed");
}
} ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, , "sigsuspend"); /* 通常情况下,master 进程挂起,等待信号来临 */
sigsuspend(&set);
/* 信号来了,往下执行 */
ngx_time_update(); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, ,
"wake up, sigio %i", sigio);
/*
188 以下变量 ngx_reap、ngx_terminate、ngx_quit、ngx_reopen、ngx_restart、ngx_reconfigure、ngx_change_binary、ngx_noaccept
189 都是通过信号量控制的
190 */
/* 有子进程退出吗? */
if (ngx_reap) {
ngx_reap = ;
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, , "reap children"); /* */
live = ngx_reap_children(cycle);
} /* live 表示是不是所有子进程正常退出 */
if (!live && (ngx_terminate || ngx_quit)) {
ngx_master_process_exit(cycle);
} /* 进程退出或终止---简单而粗暴 */
if (ngx_terminate) { if (delay == ) {
delay = ;
} if (sigio) {
sigio--;
continue;
} sigio = ccf->worker_processes + /* cache processes */; if (delay > ) { ngx_signal_worker_processes(cycle, SIGKILL);
} else { ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_TERMINATE_SIGNAL));
} continue;
} /* 进程退出或终止---优雅的退出 */
if (ngx_quit) { ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); ls = cycle->listening.elts;
for (n = ; n < cycle->listening.nelts; n++) {
if (ngx_close_socket(ls[n].fd) == -) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
ngx_close_socket_n " %V failed",
&ls[n].addr_text);
}
}
cycle->listening.nelts = ; continue;
} /* 重新加载配置文件 */
if (ngx_reconfigure) {
ngx_reconfigure = ; if (ngx_new_binary) {
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, );
ngx_noaccepting = ; continue;
}
} ngx_log_error(NGX_LOG_NOTICE, cycle->log, , "reconfiguring"); cycle = ngx_init_cycle(cycle);
if (cycle == NULL) {
cycle = (ngx_cycle_t *) ngx_cycle;
continue;
} ngx_cycle = cycle;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
ngx_core_module);
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_JUST_RESPAWN);
ngx_start_cache_manager_processes(cycle, );
live = ;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
} /* 重新启动 */
if (ngx_restart) {
ngx_restart = ;
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
ngx_start_cache_manager_processes(cycle, );
live = ;
} /* 重新打开 */
if (ngx_reopen) {
ngx_reopen = ;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, , "reopening logs");
ngx_reopen_files(cycle, ccf->user);
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_REOPEN_SIGNAL));
} /* 改变执行的二进制文件 */
if (ngx_change_binary) {
ngx_change_binary = ;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, , "changing binary");
//œøÐÐÈÈŽúÂëÌæ»»£¬ÕâÀïÊǵ÷ÓÃexecveÀŽÖŽÐÐеĎúÂë
ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
} /* 不接受 */
if (ngx_noaccept) {
ngx_noaccept = ;
ngx_noaccepting = ;
ngx_signal_worker_processes(cycle,
ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
}
}
}

  6. 以上全部代码是一个死循环,也就是说 master 进程就在这个循环中不停的执行着。

  7. 156-176 行,如果设置了时延,就用系统函数 setittimer 设置定时。

  8.  看 181 行用 sigsuspend 函数将整个进程挂起,进程交出 cpu ,进程进入休眠状态

  9.  只有在进程收到信号之后才会往下执行,183 行更新当前时间当前时间(当然要更新时间了,等进程收到信号继续往下进行的时候都不知道过了多久了)!

  10. 当进程收到信号时会执行相应的回调函数设置一些全局变量的值。我们知道,在(一)的分析中有 ngx_init_signals() 初始化了信号,而这个函数所做的工作就是设置了每种信号的回调函数。这些信号的回调函数要么是空,要么是系统函数,例如 SIG_IGN(忽略信号),要么就是作者自己定义的函数 void ngx_signal_handler(int signo)[os/unix/ngx_proces.c] 该函数代码较长,这里不贴出,大家自行查看。该函数的唯一一个参数就是接受到的信号值,根据信号值设置一些全局变量值。

  11. 192 行判断是否有子进程退出,如果有的话,通过调用 ngx_reap_children() 来设置 live 的值。如果 live = 1 表示子进程不是正常退出,否则表示正常退出!

  12. 201 行如果 ngx_ternubate 或者 ngx_quit  为 1,并且 live 为 0 则调用 ngx_master_process_exit() 函数退出 master 进程!该退出函数所做的事有

    a. 删除之前生成的 pid 文件

    b. 执行所有模块的 exit_master 函数回调

    c. 关闭所有监听套接字

    d. 销毁内存池

  14. 206 行判断进程是否终止,这是一种简单而又粗暴的退出,232 行判断进程是否终止,这中方法很优雅,做一些关闭前的准备工作。

  15. 251 行判断是否重新加载配置文件,如果是的话,就重新加载配置文件,重新设置全局变量 cycle,重新创建 worker 进程和 cache manager 进程(调用 ngx_start_worker_processes() 和 ngx_start_cache_manager_processes() )。

  16. 283 行判断是否重启程序,如果是就重新创建 worker 进程和 cache manager 进程(调用 ngx_start_worker_processes() 和 ngx_start_cache_manager_processes() )

  17. 292 行判断是否重新打开日志,如果是的话,调用 ngx_reopen_files 打开日志文件,并且将该信号发送给 worker 进程

  18. 301 行判断是否改变执行的二进制文件,如果是就调用ngx_exec_new_binary() 函数执行新的二进制文件

  19. 311 行判断是否接受 connections(连接),如果是的话调用  ngx_signal_worker_processes() 将shutdown 信号传给 worker 进程。

  20. 至此  ngx_master_process_cycle() 分析完毕!在上面的 for 循环里面多次调用了 ngx_signal_worker_processes() 函数,该函数如则将 master 进程收到的信号发送给 worker 进程( master 进程的子进程). 该函数的代码如下:

  

  /* 传递信号给 worker 进程 */
static void
ngx_signal_worker_processes(ngx_cycle_t *cycle, int signo)
{
ngx_int_t i;
ngx_err_t err;
ngx_channel_t ch; #if (NGX_BROKEN_SCM_RIGHTS) ch.command = ; #else switch (signo) { case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
ch.command = NGX_CMD_QUIT;
break; case ngx_signal_value(NGX_TERMINATE_SIGNAL):
ch.command = NGX_CMD_TERMINATE;
break; case ngx_signal_value(NGX_REOPEN_SIGNAL):
ch.command = NGX_CMD_REOPEN;
break; default:
ch.command = ;
} #endif ch.fd = -; for (i = ; i < ngx_last_process; i++) { ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, ,
"child: %d %P e:%d t:%d d:%d r:%d j:%d",
i,
ngx_processes[i].pid,
ngx_processes[i].exiting,
ngx_processes[i].exited,
ngx_processes[i].detached,
ngx_processes[i].respawn,
ngx_processes[i].just_spawn); if (ngx_processes[i].detached || ngx_processes[i].pid == -) {
continue;
} if (ngx_processes[i].just_spawn) {
ngx_processes[i].just_spawn = ;
continue;
} if (ngx_processes[i].exiting
&& signo == ngx_signal_value(NGX_SHUTDOWN_SIGNAL))
{
continue;
} if (ch.command) {
if (ngx_write_channel(ngx_processes[i].channel[],
&ch, sizeof(ngx_channel_t), cycle->log)
== NGX_OK)
{
if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
ngx_processes[i].exiting = ;
} continue;
}
} ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, ,
"kill (%P, %d)" , ngx_processes[i].pid, signo); /* 发送信号给进程 */
if (kill(ngx_processes[i].pid, signo) == -) {
err = ngx_errno;
ngx_log_error(NGX_LOG_ALERT, cycle->log, err,
"kill(%P, %d) failed", ngx_processes[i].pid, signo); if (err == NGX_ESRCH) {
ngx_processes[i].exited = ;
ngx_processes[i].exiting = ;
ngx_reap = ;
} continue;
} if (signo != ngx_signal_value(NGX_REOPEN_SIGNAL)) {
ngx_processes[i].exiting = ;
}
}
}

  1. 该函数的 500-522 行都是根据接受到的信号 (signo) 设置ngx_channerl_t 结构体的 command 的值。该结构体是一个类似于管道的父子进程间进行通信的通道!

  2. 529-590(函数结束) 通过一个 for 循环遍历所有 worker 进程,然后根据每个进程所处的状态,给他们发送或不发送信号!例如. 程序的 541 行,如果遍历到的这个进程已经与父进程分离(detached变量为 1 ) 或者该进程的 pid=-1( 有问题的进程),则忽视这次操作,进入下一个循环。程序的 545 行、550 行都是进行相应的判断。程序的 556-567行 和 573-585 行都是将相应的命令通过对 channel 的操作 或者 kill 函数发送给子进程。

  从下一节开始我们将分析 ngx_start_worker_processes() 函数,该函数创建真正干活的 worker 进程! nginx 几乎所有的功能都是由 worker 进程来完成的!所以这个函数非常重要!

从 mian 函数开始一步一步分析 nginx 执行流程(二)的更多相关文章

  1. 从 mian 函数开始一步一步分析 nginx 执行流程(四)

    如不做特殊说明,本博客所使用的 nginx 源码版本是 1.0.14,[] 中是代码所在的文件! 这一节我们分析ngx_worker_process_cycle(),该函数代码比较少,因为它通过调用函 ...

  2. 从 mian 函数开始一步一步分析 nginx 执行流程(三)

    如不做特殊说明,本博客所使用的 nginx 源码版本是 1.0.14,[] 中是代码所在的文件! 这一节我们分析ngx_start_worker_processes(),该函数代码比较少,因为它通过调 ...

  3. 从 mian 函数开始一步一步分析 nginx 执行流程(一)

    如不做特殊说明,本博客所使用的 nginx 源码版本是 1.0.14,[] 中是代码所在的文件! 我们先贴出 main 函数的部分代码: [core/nginx.c] int ngx_cdecl ma ...

  4. javascript 函数 add(1)(2)(3)(4)实现无限极累加 —— 一步一步原理解析

    问题:我们有一个需求,用js 实现一个无限极累加的函数, 形如 add(1) //=> 1; add(1)(2)  //=> 2; add(1)(2)(3) //=>  6; add ...

  5. 一步一步开发Game服务器(四)地图线程

    时隔这么久 才再一次的回归正题继续讲解游戏服务器开发. 开始讲解前有一个问题需要修正.之前讲的线程和定时器线程的时候是分开的. 但是真正地图线程与之前的线程模型是有区别的. 为什么会有区别呢?一个地图 ...

  6. 一步一步学ROP之linux_x64篇

    一步一步学ROP之linux_x64篇 一.序 **ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防 ...

  7. 一步一步学ROP之linux_x86篇

    一步一步学ROP之linux_x86篇 作者:蒸米@阿里聚安全 ​ 一.序 ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过 ...

  8. 大流量网站性能优化:一步一步打造一个适合自己的BigRender插件

    BigRender 当一个网站越来越庞大,加载速度越来越慢的时候,开发者们不得不对其进行优化,谁愿意访问一个需要等待 10 秒,20 秒才能出现的网页呢? 常见的也是相对简单易行的一个优化方案是 图片 ...

  9. 使用Python一步一步地来进行数据分析总结

    原文链接:Step by step approach to perform data analysis using Python译文链接:使用Python一步一步地来进行数据分析--By Michae ...

随机推荐

  1. Linux下Tomcat安装、配置

    /etc/profile./etc/profile.d和.bash_profile区别 /etc/profile和/etc/profile.d区别 .bash_profile 是存放用户的全局变量 / ...

  2. 使用Android Studio时so文件打包不到APK中

    1,需要在build中添加如下配置,这是必备的 Android {   sourceSets {       main {           jniLibs.srcDirs = ['libs']   ...

  3. jar打包通过exe4j转换成exe文件

    去年的时候有用过,最近写java的时候偶然用到,mark一下,方便以后看 下载链接后面附上 首先我们在eclipse上打包成jar文件,我这里只把简单的截图贴出来,详细的可以自行百度 打包jar文件: ...

  4. css height:100%失效

    有时做移动端页面时,需要用到height:100%来控制,但是设置完后会发现,用百分比的高是不生效的. 经过上网浏览等查阅资料得知,是否可以使用百分比是根据父级对象定义的.所以解决方法就是在body和 ...

  5. VS2010无法打开CSS问题

    安装了VS2010的SP1补丁后,发现打开css文件时出现下面问题: 一点击css文件就弹出:未能完成操作.未指定的错误.无法正常进入. [解决方法]安装最新Web Standards Update补 ...

  6. C++学习笔记-2-构造函数和析构函数

    问题2. 什么时候执行构造函数和析构函数  22:59:40 2015-07-22 做了一个实验: #include <iostream> class Object{ public: Ob ...

  7. 寒假的ACM训练三(PC110107/UVa10196)

    #include <iostream> #include <string.h> using namespace std; char qp[10][10]; int result ...

  8. Win32中GDI+应用(二)--初始化与清理

    GDI+提供了GdiplusStartup和 GdiplusShutdown 函数来进行初始化和完成清理工作.你必须在调用其他的GDI+函数之前,调用GdiplusStartup函数,在完成GDI+工 ...

  9. Strategy 模式

    可以看到 Strategy 模式和 Template 模式解决了类似的问题,也正如在 Template 模式中分析的,Strategy模式和 Template 模式实际是实现一个抽象接口的两种方式:继 ...

  10. JS字符串操作大全

    String对象属性 (1) length属性 length算是字符串中非常常用的一个属性了,它的功能是获取字符串的长度.当然需要注意的是js中的中文每个汉字也只代表一个字符,这里可能跟其他语言有些不 ...