nginx源码学习 主要的工作流程

运行流程分析(以下分析基于Linux,所以采用进程和子进程的说法,没有用windows的进程和线程的说法)
main函数启动:
1)初始化debug,ngx_debug_init
2)获取命令行参数,ngx_get_options
3)初始化时间相关,ngx_time_init
4)初始化日志相关,ngx_log_init
5)核心变量init_cycle,是一个ngx_cycle_t对象(不是指针)
6)用该变量处理命令行参数,ngx_process_options
7)初始化crc32校验表,ngx_crc32_table_init
8)初始化监听socket,ngx_add_inherited_sockets
9)预初始化模块,ngx_preinit_modules
10)初始化init_cycle,ngx_init_cycle ————重点
11)创建pid文件,ngx_create_pidfile
12)日志输出重定向到stderr,ngx_log_redirect_stderr
13)启动单进程或多进程处理,ngx_single_process_cycle,ngx_master_process_cycle ————核心处理部分
从源码来看,只有Linux下的核心处理,没有windows的。


现在看ngx_master_process_cycle 的实现:(ngx_process_cycle.c 73行)
1)初始化信号,包括:SIGCHLD,SIGALRM,SIGIO,SIGINT,NGX_RECONFIGURE_SIGNAL,NGX_REOPEN_SIGNAL,NGX_NOACCEPT_SIGNAL,NGX_TERMINATE_SIGNAL,NGX_SHUTDOWN_SIGNAL,NGX_CHANGEBIN_SIGNAL
2)启动工作者进程,ngx_start_worker_processes ————重点(130行)
3)启动缓冲管理进程,ngx_start_cache_manager_processes ————重点(132行)
4)进入for循环,直到某些条件触发后退出循环,返回main函数,退出程序
5)for循环内:
(a)如果delay(延时),则设置一个定时器setitimer(ITIMER_REAL, &itv, NULL
(b)更新缓冲的各个时间戳,ngx_time_update
(c)如果收到SIGCHLD,会把ngx_reap设置为1,此时启动子处理过程,ngx_reap_children
(d)如果没有活动子过程,并且nginx_terminate或者ngx_quit被置为1,则退出nginx,ngx_master_process_exit(内部调用exit(0)直接退出整个程序)
(e)如果ngx_terminate被置为1,则发出NGX_TERMINATE_SIGNAL信号,如果delay达到1000以上,发出SIGKILL
(f)如果ngx_quit被置为1,则发出NGX_SHUTDOWN_SIGNAL信号,关闭所有监听socket
(g)如果ngx_reconfigure被置为1,重新初始化ngx_cycle_t,重新启动工作进程和缓存管理进程,发出NGX_SHUTDOWN_SIGNAL信号停止本进程,如果是ngx_new_binary,说明是启动新实例,则再创建新的工作进程和缓存管理进程,和本进程并行
(h)如果ngx_restart置为1,则重启工作进程和缓存管理进程
(j)如果ngx_reopen置为1,则重新打开日志文件
(k)如果ngx_change_binary置为1,则调用exec启动新服务端实例
(l)如果ngx_noaccept置为1,则发出NGX_SHUTDOWN_SIGNAL信号


现在看看ngx_start_worker_processes都做了什么:(ngx_process_cycle.c 345行)
参数:ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type,第一个是核心对象ngx_cycle_t,第二个是要启动的进程数,默认是1,通过配置文件修改,第三个是进程类型,在ngx_master_process_cycle中,该参数是NGX_PROCESS_RESPAWN
在for循环中,创建n个进程,ngx_spawn_process,打开n个通道,ngx_pass_open_channel
进程和通道信息都保存在全局变量ngx_processes中。


现在看看ngx_spawn_process做了什么:(ngx_process.c 87行)
参数:ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn,第二个是真正的处理过程(这里传入的是ngx_worker_process_cycle),第三个是处理的数据(这里是上一步传入的循环变量i),第四个是进程名,第五个是创建类型,在ngx_start_worker_processes中传入NGX_PROCESS_RESPAWN
全局变量ngx_last_process保存了当前有多少进程被创建。
全局变量ngx_process_t    ngx_processes[NGX_MAX_PROCESSES];保存了所有的进程信息。
在for循环中检查每个ngx_processes元素,查找一个没被使用的元素用来创建进程。
常量NGX_MAX_PROCESSES 1024,代表最多开启1024个进程,如果上面的循环结束后s==它,说明没有进程可以创建了,直接返回NGX_INVALID_PID。
如果respawn!=NGX_PROCESS_DETACHED(现在就是这种情况),创建TCPsocket对(两个)保存到ngx_processes[s].channel,设置两个socket为非阻塞,对channel[0]设置标志:FIOASYNC:1,F_SETOWN:ngx_pid,F_SETFD:FD_CLOEXEC,对channel[1]设置标志:F_SETFD:FD_CLOEXEC
然后fork()子进程(实际的处理进程)。成功后根据respawn设置ngx_processes[s].的状态值。
在fork()出来的子进程会调用传入的proc,真正开启循环处理过程。


现在看看实际循环进程ngx_worker_process_cycle:(ngx_process_cycle.c 727行)
首先用data,即循环时的i,作为进程序号,初始化进程,ngx_worker_process_init(同文件,784行),该函数内部发出了第一个NGX_READ_EVENT消息。
然后在for循环中检查ngx_process_events_and_timers,处理消息。


实际处理函数ngx_process_events_and_timers:(ngx_event.c 194行)
查看该文件可以发现,该文件是事件处理的统一调用层,根据最初模块初始化时的不同,产生了同名消息吃处理函数的不同实现(使用不同的模块,包括epoll,kqueue,w32select等),该文件调用统一的接口去处理事件,接口内部调用名称相同、实际加载的处理函数宏,比如ngx_add_event(wev, NGX_WRITE_EVENT, 0),ngx_del_event(wev, NGX_WRITE_EVENT, 0),再比如,ngx_epoll_add_event对应的就是ngx_add_event宏。


到这里,基本上就可以明白,nginx的高效,来自于底层网络模块,比如epoll的异步非阻塞网络操作。所以熟悉epoll的用法,就可以自己实现一个简单高效的网络模块。libevent在Linux下的高效吞吐其实也是来自于epoll,libevent本身的事件机制也不复杂。不过libevent在windows下用的是IOCP,而nginx目前看是用的select,效率应该比libevent低。IOCP本身效率是极其高的,但是开发模式的复杂度也非常搞,会导致业务逻辑复杂,反倒降低了性能。

猜你喜欢

转载自blog.csdn.net/blwinner/article/details/55505969