Nginx源码分析 - Nginx启动以及IOCP模型.doc_第1页
Nginx源码分析 - Nginx启动以及IOCP模型.doc_第2页
Nginx源码分析 - Nginx启动以及IOCP模型.doc_第3页
Nginx源码分析 - Nginx启动以及IOCP模型.doc_第4页
Nginx源码分析 - Nginx启动以及IOCP模型.doc_第5页
已阅读5页,还剩31页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Nginx源码分析 - Nginx启动以及IOCP模型本文档针对Nginx1.11.7版本,分析Windows下的相关代码,虽然服务器可能用Linux更多,但是windows平台下的代码也基本相似 ,另外windows的IOCP完成端口,异步IO模型非常优秀,很值得一看。Nginx启动曾经有朋友问我,面对一个大项目的源代码,应该从何读起呢?我给他举了一个例子,我们学校大一大二是在紫金港校区,到了 大三搬到玉泉校区,但是大一的时候也会有时候有事情要去玉泉办。偶尔会去玉泉,但是玉泉校区不熟悉,于是跟着百度地图或者 跟着学长走。因为是办事情,所以一般也就是局部走走,比如在学院办公楼里面走走。等到大三刚来到玉泉,会发现,即使是自己 以前来过几次,也觉得这个校区完全陌生,甚至以前来过的地方,也显得格外生疏。但是当我们真正在玉泉校区开始学习生活了, 每天从寝室走到教室大多就是一条路,教超就是另一条路,这两条主要的路走几遍之后,有时候顺路去旁边的小路看看,于是慢慢 也熟悉了这个新的校区。源代码的阅读又何尝不是这样呢,如果没有一条主要的路线,总是局部看看,浅尝辄止不说,还不容易把握整体的结构。各模块之间 的依赖也容易理不清。如果有一条比较主干的线路,去读源代码,整体结构和思路也会变得明晰起来。当然我也是持这种看法:博客、 文章的作者,写文章的思路作者自己是清楚的,读者却不一定能看得到;而且大家写东西都难免会有疏漏。看别人写的源码分析指引 等等,用一种比较极端的话来说,是一种自我满足,觉得自己很快学到了很多源码级别的知识,但是其实想想,学习乎,更重要的是 学习能力的锻炼,通过源码的学习,学习过程中自己结合自己情况的思考,甚至结合社会哲学的思考,以及读源码之后带来的收益, 自己在平时使用框架、库的时候,出了问题的解决思路,翻阅别人源码来找到bug的能力。如果只是单单看别人写的源码分析,与写 代码的时候只去抄抄现成的代码,某种程度上是有一定相似性的。我自己是使用Go为主的,之前对于一流的nginx中间件也没有太多了解,也是第一次去看,水平不足之处,还望海涵。回归正题, Nginx的源代码分析,也是要找一条主要的路线,对于很多程序来说,启动过程就是一条很不错的路线,找找nginx的入口函数main ,发现在/src/core/nginx.c中,代码大概如下:int ngx_cdecl main(int argc, 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) return ngx_signal_process(cycle, ngx_signal); . if (ngx_create_pidfile(&ccf-pid, cycle-log) != NGX_OK) return 1; . if (ngx_process = NGX_PROCESS_SINGLE) ngx_single_process_cycle(cycle); else ngx_master_process_cycle(cycle); return 0这段代码大致看上去,先是做了一些初始化的事情,包括pool看起来应该是内存池之类的变量的分配,获取系统信息,初始化日志系统等等,因为还没有进入相应函数去仔细看,所以先放着。用过nginx的同学应该了解,nginx命令行 运行./nginx后,他直接就运行服务了,很静默,然后即使用Ctrl+C也关不掉。但是再开一个console,运行 ./nginx -h就会看到:nginx version: nginx/1.11.7Usage: nginx -?hvVtTq -s signal -c filename -p prefix -g directivesOptions: -?,-h : this help -v : show version and exit -V : show version and configure options then exit -t : test configuration and exit -T : test configuration, dump it and exit -q : suppress non-error messages during configuration testing -s signal : send signal to a master process: stop, quit, reopen, reload -p prefix : set prefix path (default: NONE) -c filename : set configuration file (default: conf/nginx.conf) -g directives : set global directives out of configuration file这是nginx的命令行参数介绍,要退出nginx需要用nginx -s stop给已经打开的nginx进程发送信号,让其退出。 而且nginx还支持平滑的重启,这种重启在更改nginx配置时非常有用,重启服务器的过程,实际上是nginx自己内部 的一种处理,重新载入新的配置,但是却不影响已经有的一些连接,所以称之为平滑重启。gracefully stop nginx而main函数在初始化之后,做的就是命令行参数的解析,如果是显示版本,那么显示一个版本信息,就退出;如果是设置 配置文件,那么去调用设置配置文件的相应处理;如果是发送控制信号,那么return ngx_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每平滑重启一次,就是一次循环。代码 分为几个部分来看:void ngx_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); events0 = ngx_stop_event; events1 = ngx_quit_event; events2 = ngx_reopen_event; events3 = 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 函数中创建的:static ngx_int_tngx_create_signal_events(ngx_cycle_t *cycle) ngx_sprintf(u_char *) ngx_stop_event_name, Globalngx_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); return NGX_ERROR; ngx_sprintf(u_char *) ngx_quit_event_name, Globalngx_quit_%s%Z, ngx_unique);.之后,主进程调用ngx_close_listening_sockets(cycle)关闭正在侦听的套接字,这样之后的连接就不会进来了, 因为主进程循环肯定是重启或者初始化的时候被调用的。之后调用ngx_start_worker_processes函数去启动工作者 线程。我们看看ngx_start_worker_process函数,同样在这个文件里:static ngx_int_tngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t type) ngx_int_t n; ngx_core_conf_t *ccf; ngx_log_error(NGX_LOG_NOTICE, cycle-log, 0, start worker processes); ccf = (ngx_core_conf_t *) ngx_get_conf(cycle-conf_ctx, ngx_core_module); for (n = 0; n worker_processes; n+) if (ngx_spawn_process(cycle, worker, type) = NGX_INVALID_PID) break; return n;这个函数先是读取了本次循环的配置,根据配置中的worker_process的设置来启动相应数量的工作者进程,配置文件 在/conf/nginx.conf中:#user nobody;worker_processes 8;#error_log logs/error.log;#error_log logs/error.log notice;#error_log logs/error.log info;#pid logs/nginx.pid;events worker_connections 65536;.当然如果配置文件中没有设置,以及新创建的配置文件中如何设置默认值,这些都在/src/core/nginx.c中,但是不是 非常重要,所以暂时略过。回归ngx_master_process_cycle函数,该函数在创建了事件之后,会进入一个死循环: for ( ; ) nev = 4; for (n = 0; n 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, master WaitForMultipleObjects: %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, shutting down); 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 log, 0, reap worker); live = ngx_reap_worker(cycle, eventsev); 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() returned unexpected value %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_t ngx_spawn_process(ngx_cycle_t *cycle, char *name, ngx_int_t respawn) . / 变量定义 / 第一次主循环传入的是NGX_PROCESS_JUST_RESPAWN=-3 if (respawn = 0) s = respawn; else for (s = 0; s log, 0, no more than %d processes can be spawned, NGX_MAX_PROCESSES); return NGX_INVALID_PID; / 得到Nginx的文件路径 n = GetModuleFileName(NULL, file, MAX_PATH); if (n = 0) ngx_log_error(NGX_LOG_ALERT, cycle-log, ngx_errno, GetModuleFileName() failed); return NGX_INVALID_PID; filen = 0; . ctx.path = file; . pid = ngx_execute(cycle, &ctx); / 创建新进程 .这部分是先找到ngx_process中的索引,然后放入一个新的进程,那么我们看看ngx_execute函数是怎么执行的:ngx_pid_t ngx_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_argv0); return 0; 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 %s process %P, ctx-name, pi.dwProcessId); return pi.dwProcessId;这个函数通过调用ngx_execute开启新的进程,并把进程句柄存入了context中,返回pid。创建系统进程之后,会调用 WaitForMultipleObjects等待两个事件,一个是ngx_master_process_event,这个事件在主进程循环中定义,另一 个是新开的进程死亡。如果主进程事件触发,那么会使用OpenEvent设置新进程的事件为以前创建的事件。但是可能是因为 我手头的版本还在开发中,我没有在代码里面找到关于这个手动事件触发的语句。另一个事件是新进程的死亡,如果该事件 被触发,就会执行一些清理代码(杀进程等等)。但是我们发现,CreateProcess里面只是新启动了一个nginx,那么这个新启动的nginx进程为什么会成为工作者进程呢? 还记得main函数中有做针对操作系统的初始化os_init,这个函数在win32的实现中有一部分代码如下:ngx_int_tngx_os_init(ngx_log_t *log) . if (GetEnvironmentVariable(ngx_unique, ngx_unique, NGX_INT32_LEN + 1) != 0) ngx_process = NGX_PROCESS_WORKER; .而ngx_process正是决定了一个主进程循环变更为工作者进程的条件:void ngx_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主进程已经运行那么我们来看看这个工作者进程的主循环:static voidngx_worker_process_cycle(ngx_cycle_t *cycle, char *mevn) . / 变量定义 log = cycle-log; ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, worker started); ngx_sprintf(u_char *) wtevn, ngx_worker_term_%P%Z, ngx_pid); events0 = CreateEvent(NULL, 1, 0, wtevn); if (events0 = NULL) ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, CreateEvent(%s) failed, wtevn); goto failed; ngx_sprintf(u_char *) wqevn, ngx_worker_quit_%P%Z, ngx_pid); events1 = CreateEvent(NULL, 1, 0, wqevn); if (events1 = NULL) ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, CreateEvent(%s) failed, wqevn); goto failed; ngx_sprintf(u_char *) wroevn, ngx_worker_reopen_%P%Z, ngx_pid); events2 = CreateEvent(NULL, 1, 0, wroevn); if (events2 = NULL) ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, CreateEvent(%s) failed, wroevn); goto failed; mev = OpenEvent(EVENT_MODIFY_STATE, 0, mevn); if (mev = NULL) ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, OpenEvent(%s) failed, mevn); goto failed; if (SetEvent(mev) = 0) ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, SetEvent(%s) failed, mevn); goto failed; ngx_sprintf(u_char *) ngx_cache_manager_mutex_name, ngx_cache_manager_mutex_%s%Z, ngx_unique); ngx_cache_manager_mutex = OpenMutex(SYNCHRONIZE, 0, ngx_cache_manager_mutex_name); if (ngx_cache_manager_mutex = NULL) ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, OpenMutex(%s) failed, ngx_cache_manager_mutex_name); goto failed; ngx_cache_manager_event = CreateEvent(NULL, 1, 0, NULL); if (ngx_cache_manager_event = NULL) ngx_log_error(NGX_LOG_ALERT, cycle-log, ngx_errno, CreateEvent(ngx_cache_manager_event) failed); goto failed; .最开始仍然是创建了一些事件,目前可以看到是有一些是通知工作者进程重启或者关闭的,还有一个是用来通知事件 修改状态的,而且马上激活了这个事件。然后拿到cache_manage的互斥锁的句柄,创建了ngx_cache_manager_event 事件,这个事件是命令缓存管理线程退出的,后面函数体中会讲到。之后,工作者进程启动了3个主要线程,分别是 工作者线程、缓存管理线程、缓存加载线程: . if (ngx_create_thread(&wtid, ngx_worker_thread, NULL, log) != 0) goto failed; if (ngx_create_thread(&cmtid, ngx_cache_manager_thread, NULL, log) != 0) goto failed; if (ngx_create_thread(&cltid, ngx_cache_loader_thread, NULL, log) != 0) goto failed; .启动后工作进程的主线程会进入一个事件处理循环: . for ( ; ) ev = WaitForMultipleObjects(3, events, 0, INFINITE); err = ngx_errno; ngx_time_update(); ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, worker WaitForMultipleObjects: %ul, ev); if (ev = WAIT_OBJECT_0) ngx_terminate = 1; ngx_log_error(NGX_LOG_NOTICE, log, 0, exiting); if (ResetEvent(events0) = 0) ngx_log_error(NGX_LOG_ALERT, log, 0, ResetEvent(%s) failed, wtevn); break; if (ev = WAIT_OBJECT_0 + 1) ngx_quit = 1; ngx_log_error(NGX_LOG_NOTICE, log, 0, gracefully shutting down); break; if (ev = WAIT_OBJECT_0 + 2) ngx_reopen = 1; ngx_log_error(NGX_LOG_NOTICE, log, 0, reopening logs); if (ResetEvent(events2) = 0) ngx_log_error(NGX_LOG_ALERT, log, 0, ResetEvent(%s) failed, wroevn); continue; if (ev = WAIT_FAILED) ngx_log_error(NGX_LOG_ALERT, log, err, WaitForMultipleObjects() failed); goto failed; .这个事件循环会处理以下3个事件,如果是重新开启会设置重启位置(可能之后会有处理)拿掉消息,并继续循环如果是终止或者退出就会跳出循环,则会设置标志后跳出循环,如果调用失败会进入失败处理: . /* wait threads */ if (SetEvent(ngx_cache_manager_event) = 0) ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, SetEvent(ngx_cache_manager_event) failed); events1 = wtid; events2 = cmtid; nev = 3; for ( ; ) ev = WaitForMultipleObjects(nev, events, 0, INFINITE); err = ngx_errno; ngx_time_update(); ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, worker exit WaitForMultipleObjects: %ul, ev); if (ev = WAIT_OBJECT_0) break; if (ev = WAIT_OBJECT_0 + 1) if (nev = 2) break; events1 = events2; nev = 2; continue; if (ev = WAIT_OBJECT_0 + 2) nev = 2; continue; if (ev = W

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论