从 mian 函数开始一步一步分析 nginx 执行流程(二)
如不做特殊说明,本博客所使用的 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 执行流程(二)的更多相关文章
- 从 mian 函数开始一步一步分析 nginx 执行流程(四)
如不做特殊说明,本博客所使用的 nginx 源码版本是 1.0.14,[] 中是代码所在的文件! 这一节我们分析ngx_worker_process_cycle(),该函数代码比较少,因为它通过调用函 ...
- 从 mian 函数开始一步一步分析 nginx 执行流程(三)
如不做特殊说明,本博客所使用的 nginx 源码版本是 1.0.14,[] 中是代码所在的文件! 这一节我们分析ngx_start_worker_processes(),该函数代码比较少,因为它通过调 ...
- 从 mian 函数开始一步一步分析 nginx 执行流程(一)
如不做特殊说明,本博客所使用的 nginx 源码版本是 1.0.14,[] 中是代码所在的文件! 我们先贴出 main 函数的部分代码: [core/nginx.c] int ngx_cdecl ma ...
- javascript 函数 add(1)(2)(3)(4)实现无限极累加 —— 一步一步原理解析
问题:我们有一个需求,用js 实现一个无限极累加的函数, 形如 add(1) //=> 1; add(1)(2) //=> 2; add(1)(2)(3) //=> 6; add ...
- 一步一步开发Game服务器(四)地图线程
时隔这么久 才再一次的回归正题继续讲解游戏服务器开发. 开始讲解前有一个问题需要修正.之前讲的线程和定时器线程的时候是分开的. 但是真正地图线程与之前的线程模型是有区别的. 为什么会有区别呢?一个地图 ...
- 一步一步学ROP之linux_x64篇
一步一步学ROP之linux_x64篇 一.序 **ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防 ...
- 一步一步学ROP之linux_x86篇
一步一步学ROP之linux_x86篇 作者:蒸米@阿里聚安全 一.序 ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过 ...
- 大流量网站性能优化:一步一步打造一个适合自己的BigRender插件
BigRender 当一个网站越来越庞大,加载速度越来越慢的时候,开发者们不得不对其进行优化,谁愿意访问一个需要等待 10 秒,20 秒才能出现的网页呢? 常见的也是相对简单易行的一个优化方案是 图片 ...
- 使用Python一步一步地来进行数据分析总结
原文链接:Step by step approach to perform data analysis using Python译文链接:使用Python一步一步地来进行数据分析--By Michae ...
随机推荐
- PHP表单验证内容是否为空
内容为空效果图为: 填写内容效果图: 下面是验证程序的代码: <!doctype html> <html> <head> <meta http-equiv=& ...
- linux tar 压缩解压缩
解压 .tar.bz tar zxvf file.tar.gz .tar.gz2 tar jxvf file.tar.bz2 .bz gzip -d file.bz .gz2 bzip2 -d fil ...
- Windows Python 2.7 安装 Numpy
为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/4846093. ...
- asp.net服务器控件开发系列一
最近想写写博客记录下自己学习开发服务器控件. 第一步:搭建环境. 1.新建一个项目类库,用于保存控件: 2.新建一个Web工程,用于调用控件: 如图: 第二步:在控件类库下,新建一个服务器控件类Tex ...
- Js--AJAX的小知识(一):ajax的五种状态
一.ajax的五种状态(readyState ) 0 - (未初始化)还没有调用send()方法 1 - (载入)已调用send()方法,正在发送请求 2 - (载入完成)send()方法执行完成,已 ...
- sql server获取当前日期
SqlServer中得到当前日期(convert函数,getdate函数)函数GETDATE()的返回值在显示时只显示到秒.实际上,SQL Sever内部时间可以精确到毫秒级(确切地说,可以精确到3. ...
- (转) c# ExecuteNonQuery() 返回值 -1
这是之前我遇到问题,在网上找解决方法时找到的,当时复制到txt文档了,今天整理笔记又看到了,贴出来,便于以后查阅.原文的作者没记住~~ 查询某个表中是否有数据的时候,如果用ExecuteNonQuer ...
- 转: angularjs 指令中动态编译的方法(适用于有异步请求的情况) 内嵌指令无效
angular的坑很多 例子: 在directive的link中有一个$http请求,当请求完成后根据返回的值动态做element.append('......');这个操作, 能显示没问题,可问题是 ...
- [Introduction to programming in Java 笔记] 1.3.8 Gambler's ruin simulation 赌徒破产模拟
赌徒赢得机会有多大? public class Gambler { public static void main(String[] args) { // Run T experiments that ...
- 完全背包的变形POJ1252
话说今天做背包做到有点累了,题目是英文的--而且还很长,我看了好久(弱爆了). 题目大概的意思就是,有六种硬币,之后,求用这六种硬币最小数目支付1到100美分的平均值,以及最小数目中的最大值. 很容易 ...