前言
本小节主要是整理一下前几节分析的nginx的核心模块的ngx_events_module
以及事件模块,关于事件模块什么时候初始化以及事件的处理等,因此不会涉及到太多具体的代码,主要是把握事件模块的整体。
配置项结构体的创建及赋值
在使用一个模块的功能之前,我们需要先根据配置文件来定制该模块的功能,对于事件模块来说,有选取哪种事件模型,以及单个进程的最大连接数等配置指令。
在ngx_init_cycle
中第一次调用ngx_conf_parse
,遇到了event {}
配置项时,会回调ngx_events_module
中ngx_commands_t
结构体的set
成员指向的函数。而ngx_events_module
就会通过set
函数指针调用ngx_events_block
函数,它所做的工作大致如下:
- 申请存储配置项结构体指针的数组以及指向该数组的指针的空间
- 调用所有事件模块的
create_conf
方法(它们会各自申请存储配置项的结构体的空间),将指向这些结构体的指针收集起来 - 解析
nginx.conf
文件,让所有事件模块解析自己感兴趣的配置项并存储到结构体中(回调ngx_commands_t
结构体的set
成员指向的函数) - 最后调用所有事件模块的
init_conf
方法,init_conf
方法的主要工作就是将没有设置初值的配置项结构体成员设置为默认值
对于ngx_event_core_module
来说,它关注的配置有连接池的大小(worker_connections、connections)、选择哪一种事件驱动机制(use)、是否一次性尽可能的接收连接(multi_accept)、是否使用负载均衡锁(accept_mutex)、用负载均衡锁后,延迟多少毫秒后再试图处理新连接(accept_mutex_delay)等
对于ngx_epoll_module
来说,它关注的配置有监听的事件个数,即传入epoll_wait
的第三个参数(epoll_events)、初始分配的异步I/O事件个数 worker_aio_requests
初始化工作
经过了以上步骤,ngx_init_cycle
接着遍历ngx_modules
数组,调用各模块的init_module
方法,根据配置项结构体中的成员做一些初始化工作。对于ngx_event_core_module
模块来说,该函数会根据其配置项结构体的内容初始化一些用于统计变量(与事件模块关系不是很大),而ngx_epoll_module
则没有实现该方法。
接下来的工作,就可以开始分不同进程进行了。还记得nginx通过什么样的步骤来开启的子进程的吗?首先配置文件中设置的worker_processes
配置指令如果为1,则nginx以单进程模式运行,若大与1,则以多进程模式运行。
多进程模式下,nginx会调用ngx_master_process_cycle
函数,即master
进程的工作循环。在master
进程的工作循环中,它会先设置一些信号的捕捉函数,别忘了master
进程靠各种标志位来选择进行什么样的工作。接着就会调用ngx_start_worker_processes
函数启动worker_processes
个子进程。在ngx_start_worker_processes
中,调用的核心函数是ngx_spawn_process
,其中fork
了进程之后,会调用ngx_worker_process_cycle
,即子进程的工作循环。
在子进程的工作循环中,第一件事就是调用ngx_worker_process_init
,它会调用所有模块的init_process
函数。对于事件模块中的ngx_event_core_module
模块来说,它对应的init_process
主要做了以下工作:
- 初始化定时器
- 调用配置中指定的事件驱动模块的
init
方法 - 若设置了
timer_resolution
,则定时调用ngx_timer_signal_handler
,控制时间精度 - 预分配连接池(
cycle->connections
) - 预分配读事件(
cycle->read_events
) - 预分配写事件(
cycle->write_events
) - 将对应的读、写事件放置到对应的
connections
连接中 - 初始化连接池,当前所有连接均为空闲连接,因此
cycle->free_connections
指向连接池首部 - 设置监听套接字上读事件的处理方法为
ngx_event_accept
,即建立新连接 - 将监听套接字的读事件添加到事件驱动模块中(若打开了
accept_mutex
锁不会执行这一步)
处理事件
接着子进程正式开始工作,而其工作的核心函数就是ngx_process_events_and_timers
,若开启了accept_mutex
并且当前进程的负载还没有达到总连接的7/8,会让进程去获取accept_mutex
锁,获取到该锁的进程可以处理新连接,没有获取到的只能调用epoll_wait
处理已有的连接的事件,不过过若在epoll_wait
上阻塞了accept_mutex_delay
毫秒之后,又可以再次尝试去获取锁。调用epoll_wait
并处理事件的函数是ngx_process_events
,对应epoll模块中是ngx_epoll_process_events
。
获取到了accept_mutex
锁的进程会将就绪的事件放入对应的post事件队列中延后处理,因为如果还是在ngx_process_events
函数中调用处理函数handler
,会导致进程长时间占有accept_mutex
锁得不到释放,导致其他进程获取不到该锁。因此ngx_process_events
还负责将设置了NGX_POST_EVENTS
标志位的事件加入post事件队列,注意post事件队列有两个,一个是建立新连接的事件的队列,另一个是其他普通的读/写事件的队列。在ngx_process_events_and_timers
中先将post事件队列中建立新连接的事件处理了,然后释放锁,接着再处理post事件队列的其他事件。
小结
本小节梳理了一下从main
函数开始再到事件模块处理事件的大致过程,这对把握nginx整个框架很有帮助,毕竟http、mail这些模块其实都属于事件消费模块,需要围绕着事件模块展开。