Nginx源码分析Nginx启动以及IOCP模型.docx
- 文档编号:29840667
- 上传时间:2023-07-27
- 格式:DOCX
- 页数:47
- 大小:152.08KB
Nginx源码分析Nginx启动以及IOCP模型.docx
《Nginx源码分析Nginx启动以及IOCP模型.docx》由会员分享,可在线阅读,更多相关《Nginx源码分析Nginx启动以及IOCP模型.docx(47页珍藏版)》请在冰豆网上搜索。
Nginx源码分析Nginx启动以及IOCP模型
Nginx源码分析-Nginx启动以及IOCP模型
本文档针对Nginx1.11.7版本,分析Windows下的相关代码,虽然服务器可能用Linux更多,但是windows平台下的代码也基本相似
,另外windows的IOCP完成端口,异步IO模型非常优秀,很值得一看。
Nginx启动
曾经有朋友问我,面对一个大项目的源代码,应该从何读起呢?
我给他举了一个例子,我们学校大一大二是在紫金港校区,到了
大三搬到玉泉校区,但是大一的时候也会有时候有事情要去玉泉办。
偶尔会去玉泉,但是玉泉校区不熟悉,于是跟着XX地图或者
跟着学长走。
因为是办事情,所以一般也就是局部走走,比如在学院办公楼里面走走。
等到大三刚来到玉泉,会发现,即使是自己
以前来过几次,也觉得这个校区完全陌生,甚至以前来过的地方,也显得格外生疏。
但是当我们真正在玉泉校区开始学习生活了,
每天从寝室走到教室大多就是一条路,教超就是另一条路,这两条主要的路走几遍之后,有时候顺路去旁边的小路看看,于是慢慢
也熟悉了这个新的校区。
源代码的阅读又何尝不是这样呢,如果没有一条主要的路线,总是局部看看,浅尝辄止不说,还不容易把握整体的结构。
各模块之间
的依赖也容易理不清。
如果有一条比较主干的线路,去读源代码,整体结构和思路也会变得明晰起来。
当然我也是持这种看法:
博客、
文章的作者,写文章的思路作者自己是清楚的,读者却不一定能看得到;而且大家写东西都难免会有疏漏。
看别人写的源码分析指引
等等,用一种比较极端的话来说,是一种自我满足,觉得自己很快学到了很多源码级别的知识,但是其实想想,学习乎,更重要的是
学习能力的锻炼,通过源码的学习,学习过程中自己结合自己情况的思考,甚至结合社会哲学的思考,以及读源码之后带来的收益,
自己在平时使用框架、库的时候,出了问题的解决思路,翻阅别人源码来找到bug的能力。
如果只是单单看别人写的源码分析,与写
代码的时候只去抄抄现成的代码,某种程度上是有一定相似性的。
我自己是使用Go为主的,之前对于一流的nginx中间件也没有太多了解,也是第一次去看,水平不足之处,还望海涵。
回归正题,
Nginx的源代码分析,也是要找一条主要的路线,对于很多程序来说,启动过程就是一条很不错的路线,找找nginx的入口函数main
,发现在/src/core/nginx.c中,代码大概如下:
intngx_cdeclmain(intargc,char*const*argv){
...//先是一些变量声明
ngx_debug_init();
...
ngx_pid=ngx_getpid();
...
init_cycle.pool=ngx_create_pool(1024,log);
...
cycle=ngx_init_cycle(&init_cycle);
...
if(ngx_signal){
returnngx_signal_process(cycle,ngx_signal);
}
...
if(ngx_create_pidfile(&ccf->pid,cycle->log)!
=NGX_OK){
return1;
}
...
if(ngx_process==NGX_PROCESS_SINGLE){
ngx_single_process_cycle(cycle);
}else{
ngx_master_process_cycle(cycle);
}
return0
}
这段代码大致看上去,先是做了一些初始化的事情,包括pool看起来应该是内存池之类的变量的分配,获取系统信息,
初始化日志系统等等,因为还没有进入相应函数去仔细看,所以先放着。
用过nginx的同学应该了解,nginx命令行
运行./nginx后,他直接就运行服务了,很静默,然后即使用Ctrl+C也关不掉。
但是再开一个console,运行
./nginx-h就会看到:
nginxversion:
nginx/1.11.7
Usage:
nginx[-?
hvVtTq][-ssignal][-cfilename][-pprefix][-gdirectives]
Options:
-?
-h:
thishelp
-v:
showversionandexit
-V:
showversionandconfigureoptionsthenexit
-t:
testconfigurationandexit
-T:
testconfiguration,dumpitandexit
-q:
suppressnon-errormessagesduringconfigurationtesting
-ssignal:
sendsignaltoamasterprocess:
stop,quit,reopen,reload
-pprefix:
setprefixpath(default:
NONE)
-cfilename:
setconfigurationfile(default:
conf/nginx.conf)
-gdirectives:
setglobaldirectivesoutofconfigurationfile
这是nginx的命令行参数介绍,要退出nginx需要用nginx-sstop给已经打开的nginx进程发送信号,让其退出。
而且nginx还支持平滑的重启,这种重启在更改nginx配置时非常有用,重启服务器的过程,实际上是nginx自己内部
的一种处理,重新载入新的配置,但是却不影响已经有的一些连接,所以称之为平滑重启。
gracefullystopnginx…
而main函数在初始化之后,做的就是命令行参数的解析,如果是显示版本,那么显示一个版本信息,就退出;如果是设置
配置文件,那么去调用设置配置文件的相应处理;如果是发送控制信号,那么returnngx_signal_process(cycle,ngx_signal);
处理信号等等。
这里还有个小trick,就是关于pid文件,程序把自己的pid写入一个文件,然后就可以防止启动多个进程,
这是一个比较常用的小技巧。
关于ngx_single_process_cycle(cycle)这应该是单进程的情况,一般而言现在的服务器
都是多核为主,所以我们去ngx_master_process_cycle(cycle)Master进程的主函数看一看。
主进程
ngx_master_process_cycle函数在/src/os/win32/ngx_process_cycle.c中,该函数接受一个参数,这个参数比较
复杂,但是可以看出,应该是和每次nginx循环的生命周期有关,这里认为nginx每平滑重启一次,就是一次循环。
代码
分为几个部分来看:
voidngx_master_process_cycle(ngx_cycle_t*cycle){
...
if(ngx_process==NGX_PROCESS_WORKER){
//ngx_process标识进程的身份,如果本进程应该是工作者进程,就去执行工作者应该做的
ngx_worker_process_cycle(cycle,ngx_master_process_event_name);
return;
}
...
SetEnvironmentVariable("ngx_unique",ngx_unique);//设置环境变量,表示nginx主进程已经运行
...
ngx_master_process_event=CreateEvent(NULL,1,0,ngx_master_process_event_name);
if(ngx_master_process_event==NULL){
ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno,
"CreateEvent(\"%s\")failed",
ngx_master_process_event_name);
exit
(2);
}
if(ngx_create_signal_events(cycle)!
=NGX_OK){
exit
(2);
}
ngx_sprintf((u_char*)ngx_cache_manager_mutex_name,
"ngx_cache_manager_mutex_%s%Z",ngx_unique);
ngx_cache_manager_mutex=CreateMutex(NULL,0,
ngx_cache_manager_mutex_name);
if(ngx_cache_manager_mutex==NULL){
ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno,
"CreateMutex(\"%s\")failed",ngx_cache_manager_mutex_name);
exit
(2);
}
events[0]=ngx_stop_event;
events[1]=ngx_quit_event;
events[2]=ngx_reopen_event;
events[3]=ngx_reload_event;
ngx_close_listening_sockets(cycle);
if(ngx_start_worker_processes(cycle,NGX_PROCESS_RESPAWN)==0){
exit
(2);
}
...
}
理解这段代码,需要了解Windows系统的一点点事件相关API,CreateEvent可以创建一个事件,之后可以通过一些方法
比如SetEvent可以使得这个事件被激活,进程或者线程也可以通过WaitForSingleObejct等API去等待一个事件的发
生。
这段代码就是创建了一些事件,包括stop,quit,reopen和reload,这些事件是在ngx_create_signal_events
函数中创建的:
staticngx_int_t
ngx_create_signal_events(ngx_cycle_t*cycle)
{
ngx_sprintf((u_char*)ngx_stop_event_name,
"Global\\ngx_stop_%s%Z",ngx_unique);
ngx_stop_event=CreateEvent(NULL,1,0,ngx_stop_event_name);
if(ngx_stop_event==NULL){
ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno,
"CreateEvent(\"%s\")failed",ngx_stop_event_name);
returnNGX_ERROR;
}
ngx_sprintf((u_char*)ngx_quit_event_name,
"Global\\ngx_quit_%s%Z",ngx_unique);
...
}
之后,主进程调用ngx_close_listening_sockets(cycle)关闭正在侦听的套接字,这样之后的连接就不会进来了,
因为主进程循环肯定是重启或者初始化的时候被调用的。
之后调用ngx_start_worker_processes函数去启动工作者
线程。
我们看看ngx_start_worker_process函数,同样在这个文件里:
staticngx_int_t
ngx_start_worker_processes(ngx_cycle_t*cycle,ngx_int_ttype)
{
ngx_int_tn;
ngx_core_conf_t*ccf;
ngx_log_error(NGX_LOG_NOTICE,cycle->log,0,"startworkerprocesses");
ccf=(ngx_core_conf_t*)ngx_get_conf(cycle->conf_ctx,ngx_core_module);
for(n=0;n
if(ngx_spawn_process(cycle,"worker",type)==NGX_INVALID_PID){
break;
}
}
returnn;
}
这个函数先是读取了本次循环的配置,根据配置中的worker_process的设置来启动相应数量的工作者进程,配置文件
在/conf/nginx.conf中:
#usernobody;
worker_processes8;
#error_loglogs/error.log;
#error_loglogs/error.lognotice;
#error_loglogs/error.loginfo;
#pidlogs/nginx.pid;
events{
worker_connections65536;
}
...
当然如果配置文件中没有设置,以及新创建的配置文件中如何设置默认值,这些都在/src/core/nginx.c中,但是不是
非常重要,所以暂时略过。
回归ngx_master_process_cycle函数,该函数在创建了事件之后,会进入一个死循环:
for(;;){
nev=4;
for(n=0;n if(ngx_processes[n].handle){ events[nev++]=ngx_processes[n].handle; } } if(timer){ timeout=timer>ngx_current_msec? timer-ngx_current_msec: 0; } ev=WaitForMultipleObjects(nev,events,0,timeout); err=ngx_errno; ngx_time_update(); ngx_log_debug1(NGX_LOG_DEBUG_CORE,cycle->log,0, "masterWaitForMultipleObjects: %ul",ev); if(ev==WAIT_OBJECT_0){ ngx_log_error(NGX_LOG_NOTICE,cycle->log,0,"exiting"); if(ResetEvent(ngx_stop_event)==0){ ngx_log_error(NGX_LOG_ALERT,cycle->log,0, "ResetEvent(\"%s\")failed",ngx_stop_event_name); } if(timer==0){ timer=ngx_current_msec+5000; } ngx_terminate=1; ngx_quit_worker_processes(cycle,0); continue; } if(ev==WAIT_OBJECT_0+1){ ngx_log_error(NGX_LOG_NOTICE,cycle->log,0,"shuttingdown"); if(ResetEvent(ngx_quit_event)==0){ ngx_log_error(NGX_LOG_ALERT,cycle->log,0, "ResetEvent(\"%s\")failed",ngx_quit_event_name); } ngx_quit=1; ngx_quit_worker_processes(cycle,0); continue; } ... if(ev>WAIT_OBJECT_0+3&&ev ngx_log_debug0(NGX_LOG_DEBUG_CORE,cycle->log,0,"reapworker"); live=ngx_reap_worker(cycle,events[ev]); if(! live&&(ngx_terminate||ngx_quit)){ ngx_master_process_exit(cycle); } continue; } if(ev==WAIT_TIMEOUT){ ngx_terminate_worker_processes(cycle); ngx_master_process_exit(cycle); } if(ev==WAIT_FAILED){ ngx_log_error(NGX_LOG_ALERT,cycle->log,err, "WaitForMultipleObjects()failed"); continue; } ngx_log_error(NGX_LOG_ALERT,cycle->log,0, WaitForMultipleObjects()returnedunexpectedvalue%ul",ev); } 首先介绍WaitForMultipleObjects,这个函数会等待多个内核对象,可以是事件,也可以是锁,进程等等。 这个循环中,每次 循环先添加了ngx_last_process个进程到了事件数组中,这个ngx_processes大概是上次循环中使用的进程组。 如果定义的 stop,quit,reload,reopen四种事件触发,分别调用相关函数去关闭或者重启工作者进程。 如果是上次循环中使用的进 程死亡,那么就去重启这个进程,调用ngx_reap_worker函数,这个函数在确认旧的进程已经死亡后,会调用ngx_spawn_process 去重启一个新的进程。 ngx_spawn_process会调用ngx_execute去开一个新的进程,这部分的细节,放入下一节再讲。 这样我们 了解了主进程在启动后,会进入事件处理循环来处理nginx-s发送的指令以及处理进程组死亡的重启。 那么我们看看工作者进程 是做什么的。 工作者进程 我们了解到,主进程调用ngx_start_worker_process函数根据配置文件启动多个工作者进程,这个函数中调用了ngx_spawn_process 来启动新的工作者进程,那么我们来看看ngx_spawn_process是如何启动一个新的进程。 以下是部分代码(位于/src/os/win32/ngx_process.c): ngx_pid_tngx_spawn_process(ngx_cycle_t*cycle,char*name,ngx_int_trespawn){ ...//变量定义 //第一次主循环传入的是NGX_PROCESS_JUST_RESPAWN==-3 if(respawn>=0){ s=respawn; }else{ for(s=0;s if(ngx_processes[s].handle==NULL){ break; } } if(s==NGX_MAX_PROCESSES){ ngx_log_error(NGX_LOG_ALERT,cycle->log,0, "nomorethan%dprocessescanbespawned", NGX_MAX_PROCESSES); returnNGX_INVALID_PID; } } //得到Nginx的文件路径 n=GetModuleFileName(NULL,file,MAX_PATH); if(n==0){ ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno, "GetModuleFileName()failed"); returnNGX_INVALID_PID; } file[n]='\0'; ... ctx.path=file; ... pid=ngx_execute(cycle,&ctx);//创建新进程 ... 这部分是先找到ngx_process中的索引,然后放入一个新的进程,那么我们看看ngx_execute函数是怎么执行的: ngx_pid_tngx_execute(ngx_cycle_t*cycle,ngx_exec_ctx_t*ctx) { ... if(CreateProcess(ctx->path,ctx->args, NULL,NULL,0,CREATE_NO_WINDOW,NULL,NULL,&si,&pi) ==0) { ngx_log_error(NGX_LOG_CRIT,cycle->log,ngx_errno, "CreateProcess(\"%s\")failed",ngx_argv[0]); return0; } ctx->child=pi.hProcess; if(CloseHandle(pi.hThread)==0){ ngx_log_error(NGX_LOG_ALERT,cycle->log,ngx_errno, "CloseHandle(pi.hThread)failed"); } ngx_log_error(NGX_LOG_NOTICE,cycle->log,0, "start%sprocess%P",ctx->name,pi.dwProcessId); returnpi.dwProcessId; } 这个函数通过调用ngx_execute开启新的进程,并把进程句柄存入了context中,返回pid。 创建系统进程之后,会调用 WaitForMultipleObjects等待两个事件,一个是ngx_master_process_event,这个事件在主进程循环中定义,另一 个是新开的进程死亡。 如果主进程事件触发,那么会使用OpenEvent设置新进程的事件为以前创建的事件。 但是可能是因为 我
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Nginx源码分析 Nginx启动以及IOCP模型 Nginx 源码 分析 启动 以及 IOCP 模型