Nginx源码分析 - Nginx启动以及IOCP模型
来源:互联网 发布:发票数据导出软件 编辑:程序博客网 时间:2024/06/06 02:57
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 directives]Options: -?,-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); } 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
函数中创建的:
static ngx_int_tngx_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); return NGX_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
函数,同样在这个文件里:
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 < ccf->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_last_process; 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, "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 < WAIT_OBJECT_0 + nev) { ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0, "reap worker"); 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() 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 < ngx_last_process; s++) { if (ngx_processes[s].handle == NULL) { break; } } if (s == NGX_MAX_PROCESSES) { ngx_log_error(NGX_LOG_ALERT, cycle->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; } file[n] = '\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_argv[0]); 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); events[0] = CreateEvent(NULL, 1, 0, wtevn); if (events[0] == 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); events[1] = CreateEvent(NULL, 1, 0, wqevn); if (events[1] == 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); events[2] = CreateEvent(NULL, 1, 0, wroevn); if (events[2] == 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(events[0]) == 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(events[2]) == 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"); } events[1] = wtid; events[2] = 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; } events[1] = events[2]; nev = 2; continue; } if (ev == WAIT_OBJECT_0 + 2) { nev = 2; continue; } if (ev == WAIT_FAILED) { ngx_log_error(NGX_LOG_ALERT, log, err, "WaitForMultipleObjects() failed"); break; } } ngx_close_handle(ngx_cache_manager_event); ngx_close_handle(events[0]); ngx_close_handle(events[1]); ngx_close_handle(events[2]); ngx_close_handle(mev); ngx_worker_process_exit(cycle);failed: exit(2);}
这部分代码是在处理自己的死亡,先发送信息让缓存管理线程死亡。将终止(终止和退出的含义是有区别的,终止是很暴力的概念,而退
出就平稳了很多)事件和工作者线程和缓存管理线程的id都放入WaitForMultipleObjects
的列表中,等待自己开启的线程死亡。当然
里面做了一个小小的处理,使得WaitForMultipleObjects
收到某个线程的消息后,不会再去等另外一个,而且等到两个都结束后才会
跳出等待,执行会面自己的死亡处理。但是这里有个疑问,为什么不等待缓存加载线程呢?于是看看缓存加载线程干了什么:
static ngx_thread_value_t __stdcallngx_cache_loader_thread(void *data){ ngx_uint_t i; ngx_path_t **path; ngx_cycle_t *cycle; ngx_msleep(60000); cycle = (ngx_cycle_t *) ngx_cycle; path = cycle->paths.elts; for (i = 0; i < cycle->paths.nelts; i++) { if (ngx_terminate || ngx_quit || ngx_exiting) { break; } if (path[i]->loader) { path[i]->loader(path[i]->data); ngx_time_update(); } } return 0;}
很短的一点代码,对每个路径调用它的缓存加载函数(在后面的http模块中看到了),如果这个过程中遇到了终止或者结束标志就直接退出。
缓存管理的代码如下:
static voidngx_cache_manager_process_handler(void){ u_long ev; ngx_uint_t i; ngx_msec_t next, n; ngx_path_t **path; next = 60 * 60 * 1000; path = ngx_cycle->paths.elts; for (i = 0; i < ngx_cycle->paths.nelts; i++) { if (path[i]->manager) { n = path[i]->manager(path[i]->data); next = (n <= next) ? n : next; ngx_time_update(); } } if (next == 0) { next = 1; } ev = WaitForSingleObject(ngx_cache_manager_event, (u_long) next); if (ev != WAIT_TIMEOUT) { ngx_time_update(); ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "cache manager WaitForSingleObject: %ul", ev); }}
这个线程代码也类似,调用所有path的缓存管理函数,之后会去等待让他死亡的信号。关于缓存这部分不在本篇文章中涉及,后续文章中
可能会专门去讲。于是我们关注一下比较关键的工作者线程。
工作者线程
工作者线程的代码如下:
static ngx_thread_value_t __stdcallngx_worker_thread(void *data){ ngx_int_t n; ngx_time_t *tp; ngx_cycle_t *cycle; tp = ngx_timeofday(); srand((ngx_pid << 16) ^ (unsigned) tp->sec ^ tp->msec); cycle = (ngx_cycle_t *) ngx_cycle; // 加载所有模块的初始化函数 for (n = 0; cycle->modules[n]; n++) { if (cycle->modules[n]->init_process) { if (cycle->modules[n]->init_process(cycle) == NGX_ERROR) { /* fatal */ exit(2); } } } while (!ngx_quit) { if (ngx_exiting) { // 退出的处理 ngx_event_cancel_timers(); if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel) { break; } } ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0, "worker cycle"); // 处理IO事件 ngx_process_events_and_timers(cycle); if (ngx_terminate) { // 暴力退出 return 0; } if (ngx_quit) { ngx_quit = 0; if (!ngx_exiting) { ngx_exiting = 1; ngx_close_listening_sockets(cycle); ngx_close_idle_connections(cycle); } } if (ngx_reopen) { ngx_reopen = 0; ngx_reopen_files(cycle, -1); } } ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); return 0;}
工作者线程显示加载所有模块的初始化函数,然后循环调用IO事件的处理函数,由于工作者线程对于性能要求极高,所以该线程函数里面完全
没有系统调用,退出等等的处理都是通过进程共享的标识变量来处理的,避免了频繁内核态和用户态的切换引起的开销。由于标识变量的操作
都是在主线程中处理的,所以也不需要加锁。因为IO事件处理函数只会处理一个IO事件,所以这个for循环的频率非常高。于是我们看看IO事件
处理函数(位于/src/event/nginx_event.c):
voidngx_process_events_and_timers(ngx_cycle_t *cycle){ ngx_uint_t flags; ngx_msec_t timer, delta; if (ngx_timer_resolution) { timer = NGX_TIMER_INFINITE; flags = 0; } else { timer = ngx_event_find_timer(); flags = NGX_UPDATE_TIME;#if (NGX_WIN32) /* handle signals from master in case of network inactivity */ if (timer == NGX_TIMER_INFINITE || timer > 500) { timer = 500; }#endif } if (ngx_use_accept_mutex) { if (ngx_accept_disabled > 0) { ngx_accept_disabled--; } else { if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) { return; } if (ngx_accept_mutex_held) { flags |= NGX_POST_EVENTS; } else { if (timer == NGX_TIMER_INFINITE || timer > ngx_accept_mutex_delay) { timer = ngx_accept_mutex_delay; } } } } delta = ngx_current_msec; (void) ngx_process_events(cycle, timer, flags); delta = ngx_current_msec - delta; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "timer delta: %M", delta); ngx_event_process_posted(cycle, &ngx_posted_accept_events); if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); } if (delta) { ngx_event_expire_timers(); } ngx_event_process_posted(cycle, &ngx_posted_events);}
IO事件处理函数先是做了一些时间的处理,暂时略过,然后看一下事件处理函数,(void) ngx_process_events(cycle, timer, flags);
这条语句调用的实际是已经加载的IO模块的处理函数。nginx
是支持很多种IO模型的,多路复用,信号驱动IO等等,但是这篇主要介绍一个 Nginx 1.11
之后的版本才开始考虑加入的超高性能IO模型,该模型表现非常优异,也是著名的node.js
项目在windows下的基础IO模型。
IOCP模块
Linux下没有完美的异步IO模型,read
,select
,poll
,epoll
,pselect
,kqueue
实质都是应用程序同步轮询,即使内核告诉你已经
有设备就绪,也要应用程序自己去循环读取文件描述符状态,另外也需要自己去从内核态把缓冲区复制出来,而且对多线程不友好,以至于很多linux
网络库使用线程间通讯来模拟异步的IO事件(如Glibc的AIO只是将IO操作分到了多个线程上)。而IOCP则是几乎完美的解决方案,内核托管线程
池去处理IO,复制缓冲区也是内核处理,直到数据到达,应用程序线程才会被唤醒。
相比循环判断文件描述符,IOCP省去了大量循环时间;相比应用程序自己去内核复制缓冲区的系统调用,IOCP省去了大量系统调用的时间;IOCP自己
本身就可以把IO分配到多个进程,这是在内核里面做的,相比应用程序用线程间通信模拟,节省了大量系统调用和锁机制/信号机制导致的开销,nginx
中新加入的IOCP模块中处理IO事件的方法如下(位于/src/event/modules/ngx_iocp_module.c):
staticngx_int_t ngx_iocp_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags){ int rc; u_int key; u_long bytes; ngx_err_t err; ngx_msec_t delta; ngx_event_t *ev; ngx_event_ovlp_t *ovlp; if (timer == NGX_TIMER_INFINITE) { timer = INFINITE; } ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "iocp timer: %M", timer); // 获取一个完成的端口 rc = GetQueuedCompletionStatus(iocp, &bytes, (PULONG_PTR) &key, (LPOVERLAPPED *) &ovlp, (u_long) timer); if (rc == 0) { err = ngx_errno; } else { err = 0; } delta = ngx_current_msec; if (flags & NGX_UPDATE_TIME) { ngx_time_update(); } ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "iocp: %d b:%d k:%d ov:%p", rc, bytes, key, ovlp); if (timer != INFINITE) { delta = ngx_current_msec - delta; ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "iocp timer: %M, delta: %M", timer, delta); } if (err) { if (ovlp == NULL) { if (err != WAIT_TIMEOUT) { ngx_log_error(NGX_LOG_ALERT, cycle->log, err, "GetQueuedCompletionStatus() failed"); return NGX_ERROR; } return NGX_OK; } ovlp->error = err; } if (ovlp == NULL) { ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "GetQueuedCompletionStatus() returned no operation"); return NGX_ERROR; } // 这个event是AcceptEx函数注册上去的 ev = ovlp->event; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err, "iocp event:%p", ev); if (err == ERROR_NETNAME_DELETED /* the socket was closed */ || err == ERROR_OPERATION_ABORTED /* the operation was canceled */) { /* * the WSA_OPERATION_ABORTED completion notification * for a file descriptor that was closed */ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err, "iocp: aborted event %p", ev); return NGX_OK; } if (err) { ngx_log_error(NGX_LOG_ALERT, cycle->log, err, "GetQueuedCompletionStatus() returned operation error"); } switch (key) { case NGX_IOCP_ACCEPT: if (bytes) { ev->ready = 1; } break; case NGX_IOCP_IO: ev->complete = 1; ev->ready = 1; break; case NGX_IOCP_CONNECT: ev->ready = 1; } ev->available = bytes; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "iocp event handler: %p", ev->handler); ev->handler(ev); return NGX_OK;}
在这个事件处理函数中,IOCP模块先调用GetQueuedCompletionStatus
来获取一个完成的端口,并设置了超时时间,在win32下,
超时时间被设置为最多500.然后判断完成的端口,如果是空,那么返回,如果出现一些错误,就进行错误处理。然后判断是哪种IO
完成,并对其作出分别的处理,对ev
的标识进行标记,那么这个ev
是哪里来的呢,ev = ovlp->event;
,而ovelapeed
重叠
IO的结构,是可以自己定义的,最后的语句,也是调用了这个自己定义的ev
的handler
函数去处理这个异步IO。而注册新的IO
端口,是在IOCP模块的添加事件接口中:
static ngx_int_tngx_iocp_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t key){ ngx_connection_t *c; c = (ngx_connection_t *) ev->data; c->read->active = 1; c->write->active = 1; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0, "iocp add: fd:%d k:%ui ov:%p", c->fd, key, &ev->ovlp); if (CreateIoCompletionPort((HANDLE) c->fd, iocp, key, 0) == NULL) { ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, "CreateIoCompletionPort() failed"); return NGX_ERROR; } return NGX_OK;}
但是要了解ev
的handler
等等到底是哪里定义的,还得看看event
进程的初始化函数(位于/src/ngx_event.c):
static ngx_int_tngx_event_process_init(ngx_cycle_t *cycle){ ... ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); ecf = ngx_event_get_conf(cycle->conf_ctx, ngx_event_core_module); // 如果有多个用户进程就考虑使用Accept锁 if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) { ngx_use_accept_mutex = 1; ngx_accept_mutex_held = 0; ngx_accept_mutex_delay = ecf->accept_mutex_delay; } else { ngx_use_accept_mutex = 0; }#if (NGX_WIN32) /* * disable accept mutex on win32 as it may cause deadlock if * grabbed by a process which can't accept connections */ ngx_use_accept_mutex = 0;#endif // 初始化投递事件队列 ngx_queue_init(&ngx_posted_accept_events); ngx_queue_init(&ngx_posted_events); if (ngx_event_timer_init(cycle->log) == NGX_ERROR) { return NGX_ERROR; } // 寻找加载的event模块并调用其初始化函数 for (m = 0; cycle->modules[m]; m++) { if (cycle->modules[m]->type != NGX_EVENT_MODULE) { continue; } if (cycle->modules[m]->ctx_index != ecf->use) { continue; } module = cycle->modules[m]->ctx; if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) { /* fatal */ exit(2); } break; } ... // 为所有连接分配空间 cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log); if (cycle->connections == NULL) { return NGX_ERROR; } c = cycle->connections; // 为读事件分配空间 cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log); if (cycle->read_events == NULL) { return NGX_ERROR; } // 初始化读事件 rev = cycle->read_events; for (i = 0; i < cycle->connection_n; i++) { rev[i].closed = 1; rev[i].instance = 1; } // 为写事件分配空间 cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log); if (cycle->write_events == NULL) { return NGX_ERROR; } // 初始化写事件 wev = cycle->write_events; for (i = 0; i < cycle->connection_n; i++) { wev[i].closed = 1; } // 初始化所有连接 i = cycle->connection_n; next = NULL; do { i--; c[i].data = next; c[i].read = &cycle->read_events[i]; c[i].write = &cycle->write_events[i]; c[i].fd = (ngx_socket_t) -1; next = &c[i]; } while (i); // 连接复用 cycle->free_connections = next; cycle->free_connection_n = cycle->connection_n; // 初始化侦听的套接字 /* for each listening socket */ ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) {#if (NGX_HAVE_REUSEPORT) if (ls[i].reuseport && ls[i].worker != ngx_worker) { continue; }#endif c = ngx_get_connection(ls[i].fd, cycle->log); if (c == NULL) { return NGX_ERROR; } c->type = ls[i].type; c->log = &ls[i].log; c->listening = &ls[i]; ls[i].connection = c; rev = c->read; rev->log = c->log; rev->accept = 1;#if (NGX_HAVE_DEFERRED_ACCEPT) rev->deferred_accept = ls[i].deferred_accept;#endif if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) { if (ls[i].previous) { /* * delete the old accept events that were bound to * the old cycle read events array */ old = ls[i].previous->connection; if (ngx_del_event(old->read, NGX_READ_EVENT, NGX_CLOSE_EVENT) == NGX_ERROR) { return NGX_ERROR; } old->fd = (ngx_socket_t) -1; } }#if (NGX_WIN32) if (ngx_event_flags & NGX_USE_IOCP_EVENT) { // 关键的IOCP处理代码 ngx_iocp_conf_t *iocpcf; rev->handler = ngx_event_acceptex; if (ngx_use_accept_mutex) { continue; } // 侦听端口继续放入accept事件 if (ngx_add_event(rev, 0, NGX_IOCP_ACCEPT) == NGX_ERROR) { return NGX_ERROR; } ls[i].log.handler = ngx_acceptex_log_error; iocpcf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module); if (ngx_event_post_acceptex(&ls[i], iocpcf->post_acceptex) == NGX_ERROR) { return NGX_ERROR; } } else { ... }#else ...#endif if (ngx_use_accept_mutex) { continue; } ... if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; }#endif } return NGX_OK;}这个函数先是调用了`event`模块的初始化,然后为投递事件跌了、连接、读写事件分配了空间,然后我们分析下和IOCP最相关的部分,初始化函数为每个侦听的连接添加了`NGX_IOCP_ACCEPT`事件,并设置事件处理函数为`ngx_event_acceptex`这样当ACCEPT事件发生,侦听端口会继续去侦听,该函数定义如下:```cvoidngx_event_acceptex(ngx_event_t *rev){ // 对新连入的socket进行一些设置 ... // 给侦听端口投递下一次accept ngx_event_post_acceptex(ls, 1); // 原子操作+1 c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); // 处理新连入的连接,该函数由上层模块定义,如http,mail等 // handler还会给新连入的connection加上读写的handler // 之后IO事件循环将会调用 ls->handler(c); return;}<div class="se-preview-section-delimiter"></div>
大家都知道tcp侦听后,是会返回一个新的套接字,该函数对连接上面的套接字使用了原子加一操作,该函数在不同架构下有不同实现,在
amd64平台下的实现为:
static ngx_inline ngx_atomic_int_tngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add){ __asm__ volatile ( NGX_SMP_LOCK " xaddq %0, %1; " : "+r" (add) : "m" (*value) : "cc", "memory"); return add;}<div class="se-preview-section-delimiter"></div>
纯汇编指令实现,性能非常高效。然后看看ngx_event_post_acceptex
函数:
ngx_int_tngx_event_post_acceptex(ngx_listening_t *ls, ngx_uint_t n){ ... for (i = 0; i < n; i++) { /* TODO: look up reused sockets */ s = ngx_socket(ls->sockaddr->sa_family, ls->type, 0); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &ls->log, 0, ngx_socket_n " s:%d", s); if (s == (ngx_socket_t) -1) { ngx_log_error(NGX_LOG_ALERT, &ls->log, ngx_socket_errno, ngx_socket_n " failed"); return NGX_ERROR; } // 复用一个连接 c = ngx_get_connection(s, &ls->log); if (c == NULL) { return NGX_ERROR; } // 为新的连接分配空间 c->pool = ngx_create_pool(ls->pool_size, &ls->log); if (c->pool == NULL) { ngx_close_posted_connection(c); return NGX_ERROR; } log = ngx_palloc(c->pool, sizeof(ngx_log_t)); if (log == NULL) { ngx_close_posted_connection(c); return NGX_ERROR; } c->buffer = ngx_create_temp_buf(c->pool, ls->post_accept_buffer_size + 2 * (ls->socklen + 16)); if (c->buffer == NULL) { ngx_close_posted_connection(c); return NGX_ERROR; } c->local_sockaddr = ngx_palloc(c->pool, ls->socklen); if (c->local_sockaddr == NULL) { ngx_close_posted_connection(c); return NGX_ERROR; } c->sockaddr = ngx_palloc(c->pool, ls->socklen); if (c->sockaddr == NULL) { ngx_close_posted_connection(c); return NGX_ERROR; } *log = ls->log; c->log = log; c->recv = ngx_recv; c->send = ngx_send; c->recv_chain = ngx_recv_chain; c->send_chain = ngx_send_chain; c->listening = ls; rev = c->read; wev = c->write; // 设置这个连接的overlapped结构体中的事件 rev->ovlp.event = rev; wev->ovlp.event = wev; rev->handler = ngx_event_acceptex; rev->ready = 1; wev->ready = 1; rev->log = c->log; wev->log = c->log; // 新增到IOCP的IO事件中 if (ngx_add_event(rev, 0, NGX_IOCP_IO) == NGX_ERROR) { ngx_close_posted_connection(c); return NGX_ERROR; } // 调用AcceptEx来接受连接 if (ngx_acceptex(ls->fd, s, c->buffer->pos, ls->post_accept_buffer_size, ls->socklen + 16, ls->socklen + 16, &rcvd, (LPOVERLAPPED) &rev->ovlp) == 0) { err = ngx_socket_errno; if (err != WSA_IO_PENDING) { ngx_log_error(NGX_LOG_ALERT, &ls->log, err, "AcceptEx() %V failed", &ls->addr_text); ngx_close_posted_connection(c); return NGX_ERROR; } } } return NGX_OK;}<div class="se-preview-section-delimiter"></div>
ngx_acceptex
函数用已经分配好的空间,让内核去accept
一个连接,并把相关信息放入该结构体中。这就有了IOCP
事件处理循环中的处理。
switch (key) { case NGX_IOCP_ACCEPT: if (bytes) { ev->ready = 1; } break; case NGX_IOCP_IO: ev->complete = 1; ev->ready = 1; break; case NGX_IOCP_CONNECT: ev->ready = 1; } ev->available = bytes; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "iocp event handler: %p", ev->handler); ev->handler(ev); return NGX_OK;<div class="se-preview-section-delimiter"></div>
ev就是acceptex
中传入的overlapped
结构中的event
,也就是nginx
事件模型中定义的事件结构。这个结构也被其他高层
模块所使用(比如http
)。在调用相应处理函数处理了IO事件后返回。最后回到event
事件循环中的处理:
(void) ngx_process_events(cycle, timer, flags); delta = ngx_current_msec - delta; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "timer delta: %M", delta); ngx_event_process_posted(cycle, &ngx_posted_accept_events); if (ngx_accept_mutex_held) { ngx_shmtx_unlock(&ngx_accept_mutex); } if (delta) { ngx_event_expire_timers(); } ngx_event_process_posted(cycle, &ngx_posted_events);}
在调用IO事件处理函数之后,会调用ngx_event_process_posted
函数,这个函数只是简单的将传入的队列里面的所有事件调用一遍相应
的回调函数。然后就会进入下一次IO事件循环。
写了这么多,也只是在nginx
启动和IOCP
模型中做了一些窥探,实际使用event和iocp都是非常复杂的事情,需要处理大量细节,尤其使用
c之类需要手动管理内存的语言,需要加上大量监控标记等等,来防止内存泄露。nginx
作为世界知名的项目,结构和架构的复杂度,也是远远
超过我所窥探的部分,要想深入研究理解,还得多加努力。最后附两张图,帮助理解:
(IOCP流程图转载自:http://blog.csdn.net/piggyxp/article/details/6922277)
- Nginx源码分析 - Nginx启动以及IOCP模型
- nginx 源码分析 进程模型
- Nginx源码分析 ——Nginx的进程模型
- Nginx源码分析---Nginx启动初始化过程(一)
- Nginx源码分析---Nginx启动初始化过程(二)
- Nginx源码分析 - 主流程篇 - Nginx的启动流程
- nginx源码分析—启动流程
- nginx源码分析—启动流程
- nginx源码分析—启动流程
- nginx源码分析—启动流程
- Nginx源码启动过程分析(图)
- nginx模型分析
- nginx源码分析(10)-启动过程分析
- nginx源码分析(11)-进程启动分析(1)
- nginx源码分析(12)-进程启动分析(2)
- nginx源码分析(10)-启动过程分析
- nginx源码分析(11)-进程启动分析(1)
- nginx源码分析(12)-进程启动分析(2)
- Docker
- Jump Game
- 谈谈fresco的bitmap内存分配
- 水题 模板
- sed命令详解
- Nginx源码分析 - Nginx启动以及IOCP模型
- linux shell
- bzoj4383
- 【Linux】将Oracle安装目录从根目录下迁移到逻辑卷
- 2016新财富最佳分析师榜单全揭晓
- The need for a POP POP RET instruction sequence
- OPNET14.5 Compilation errors and warnings Enter Exit 反了 相反
- Vmware Vsphere web client无法跳转https://(WebClient-hostname)(port)/vsphere-client/
- Scrapy爬取链家网房源 高德地图展示