nginx源码分析——event模块
|
select
|
poll
|
epoll
|
fd数量
|
用fd_set类型来存储fd,每个fd占一位,fd_set在linux系统中默认1024位,所以select最多支持1024个fd(可以通过修改FD_SETSIZE来增加)
|
采用pollfd结构体来存储fd,因此支持的最大fd数目跟系统内存有关(支持存储多少个)
|
采用epoll_event存储,因此也只与内存有关
|
通知方式
|
需要遍历fd_set集合中所有的fd,然后一个个检查确定是哪些fd上有event发生
|
与select类似
|
fd上有事件发生时,epoll函数会直接返回,且只会返回有事件发生的fd,不需要遍历
|
消息传递
|
将fd_set拷贝到内核空间,有event发生时再从内核空间拷贝到用户空间
|
与select类似
|
mmap申请一块共享内存来存放消息,省去内存拷贝开销
|
/////////// nginx/src/event/ngx_event.c ////////
//这里各个模块并不像phase_handler或者filter那样用某种结构来存储,而是直接外部声明(方便直接调用)
extern ngx_module_t ngx_kqueue_module;
extern ngx_module_t ngx_eventport_module;
extern ngx_module_t ngx_devpoll_module;
extern ngx_module_t ngx_epoll_module;
extern ngx_module_t ngx_select_module;
/////////// nginx/src/core/ngx_cycle.c ////////
//在nginx主进程启动worker之前调用,用于初始化相关的配置
ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle)
{
.......
//对类型为NGX_CORE_MODULE的模块调用对应的create_conf函数,进行申请空间之类的操作
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}
.....
if (module->create_conf) {
rv = module->create_conf(cycle);
.......
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;
.......
//根据上面给conf的赋值语句,ngx_conf_parse主要对配置文件NGX_MAIN_CONF这一层级的
//配置进行解析,并只对NGX_CORE_MODULE类型的模块中的cmd进行匹配,然后执行匹配cmd对
//应的函数
if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
......
//对类型为NGX_CORE_MODULE的模块调用对应的int_conf函数,对相关的conf结构进行初始化
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->type != NGX_CORE_MODULE) {
continue;
}
......
if (module->init_conf(cycle, cycle->conf_ctx[cycle->modules[i]->index])
......
//调用每个模块注册的init_module函数初始化相关参数(因为这个操作在fork worker进程之前,
//所以初始化的参数都会被继承)
if (ngx_init_modules(cycle) != NGX_OK) {
/* fatal */
exit(1);
}
......
}
/////////// nginx/src/os/unix/ngx_process_cycle.c ////////
//worker进程启动初始化
static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{
......
//调用每个模块注册的init_process函数(这个操作是在每个worker进程中的,所以申请一些进程
//自用的一些资源,如内存、变量等)
for (i = 0; cycle->modules[i]; i++) {
if (cycle->modules[i]->init_process) {
if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
/* fatal */
exit(2);
}
}
}
......
}
///////// nginx/src/event/ngx_event.c ///////// //ngx_event_core_create_conf函数主要是申请空间的,比较简单这里就不讲了 //在执行ngx_conf_parse函数时,配置项events属于NGX_MAIN_CONF类型,且events模块类型为
//NGX_CORE_MODULE,所以events配置对应的ngx_events_block就会被调用 //ngx_event_block这个函数与上面的ngx_init_cycle以及ngx_http_block函数的结构类似,都是
//先调用特定模块的create_conf函数,然后解析对应level的配置项(调用配置注册的函数),接着
//调用init_conf函数实现完成模块对应配置的初始化
static char * ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
......
//执行NGX_EVENT_MODULE类型的模块注册的create_conf函数
for (i = 0; cf->cycle->modules[i]; i++) {
if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
.....
if (m->create_conf) {
(*ctx)[cf->cycle->modules[i]->ctx_index] = m->create_conf(cf->cycle);
......
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;
//解析配置文件中涉及NGX_EVENT_MODULE模块并执行相关配置注册的相关函数
rv = ngx_conf_parse(cf, NULL);
......
//执行NGX_EVENT_MODULE类型的模块注册的init_conf函数完成模块conf的初始化
//在event_core_module模块的init_conf阶段,即ngx_event_core_init_conf函数
//中完成了IO复用模型的选择
for (i = 0; cf->cycle->modules[i]; i++) {
if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
......
rv = m->init_conf(cf->cycle, (*ctx)[cf->cycle->modules[i]->ctx_index]);
......
} //该函数在init_cycle中的init_module函数中被调用。调用时worker还没有启动
static ngx_int_t ngx_event_module_init(ngx_cycle_t *cycle)
{
......
shm.size = size;
shm.name.len = sizeof("nginx_shared_zone") - 1;
shm.name.data = (u_char *) "nginx_shared_zone";
shm.log = cycle->log;
//内部调用mmap申请一块共享内存,这样fork出来的worker也能继承这块共享内存的地址
//worker进程可以通过继承的共享内存地址来实现与master以及其他worker的通信
if (ngx_shm_alloc(&shm) != NGX_OK) {
return NGX_ERROR;
} shared = shm.addr;
ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
ngx_accept_mutex.spin = (ngx_uint_t) -1;
//将accept锁放到共享内存中,这样就实现了对多个进程的互斥、同步操作
if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
cycle->lock_file.data)
!= NGX_OK)
{
return NGX_ERROR;
} //将连接计数器以及其他的一些原子类型的变量也存放到共享内存,这样可以很方便统计所有整个nginx
//的连接总数
ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
.....
} //该函数ngx_worker_process_init中的init_process函数中被调用,因为执行阶段位于
//worker进程初始化时,所以这里申请的资源都属于各个worker独有,不用担心因被继承而导致的资源浪费
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
.....
//创建保存event的队列用于保存accept、普通事件
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;
}
.....
//创建连接池
cycle->connections =
ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
.....
}
//////////// nginx/src/event/ngx_event.c /////////////
//在这个函数中完成了对从master进程继承的listen fd的初始化工作
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
......
//遍历所有的listen fd,然后从连接池中获取一个连接并初始化为listen fd的连接
/* for each listening socket */
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) { #if (NGX_HAVE_REUSEPORT)
//如果支持REUSEPORT选项,那么socket只能在一个worker上工作
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;
.....
}
//////////// nginx/src/os/unix/ngx_process_cycle.c /////////////
//worker进程处理连接、请求的主函数
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
.....
//初始化worker进程
ngx_worker_process_init(cycle, worker);
ngx_setproctitle("worker process"); for ( ;; ) {
.....
//处理新连接以及已有连接上的新数据
ngx_process_events_and_timers(cycle);
.....
}
} //////////// nginx/src/event/ngx_event.c /////////////
//检查是否有新连接,并处理事件队列中的事件
void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
.....
//检查accept锁是否启用
if (ngx_use_accept_mutex) {
//如果accept_disable大于0,说明当前进程负载偏高或者接受新连接异常
//该值-1是为了尽快让该进程参与accept锁的竞争,防止一直空闲
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
//尝试获取accept,如果获取成功ngx_accept_mutex_held会被置1,
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
} if (ngx_accept_mutex_held) {
//设置NGX_POST_EVENTS标志是为了在处理新连接时,将新的event放到
//事件队列异步处理,而不是立刻处理(防止阻塞)
flags |= NGX_POST_EVENTS; } else {
//没获取到accept锁
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
delta = ngx_current_msec;
//这里是一个函数指针,指向当前使用IO模块中的事件处理函数,等待事件到来的超时时间是timer
(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); //处理accept event队列中事件
ngx_event_process_posted(cycle, &ngx_posted_accept_events); //如果持有accept锁,就释放,避免一个进程长期持有该锁。在大都是长连接的情况下,这回导致
//各个worker负载的严重不均匀
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
} //检查是否有超时(过期)的事件,如果有进行处理
if (delta) {
ngx_event_expire_timers();
} //处理普通event队列中的事件
ngx_event_process_posted(cycle, &ngx_posted_events);
} //////////// nginx/src/event/ngx_event_accept.c /////////////
//接受新的连接
void ngx_event_accept(ngx_event_t *ev)
{
......
//如果剩余的空闲连接数小于连接池总数的1/8,下次执行ngx_process_events_and_timers时
//就不再竞争accept锁
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
......
//从连接池中取出一个连接并进行初始化
c = ngx_get_connection(s, ev->log);
......
//将新建的连接通过ngx_add_conn函数指针加入到IO复用函数的等待队列中
if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
if (ngx_add_conn(c) == NGX_ERROR) {
ngx_close_accepted_connection(c);
return;
}
} log->data = NULL;
log->handler = NULL;
//ls对应的handler是在ngx_http_add_listening函数中将ngx_http_init_connection
//函数赋值给了handler
ls->handler(c);
......
} //////////// nginx/src/event/modules/ngx_epoll_module.c /////////////
//epoll模块事件处理函数
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
......
//等待新连接(最多阻塞timer时长)
events = epoll_wait(ep, event_list, (int) nevents, timer); ......
//如果设置了NGX_POST_EVENTS就说明当前持有accept锁,应当将新事件放到队列中,尽快返回
if (flags & NGX_POST_EVENTS) {
queue = rev->accept ? &ngx_posted_accept_events
: &ngx_posted_events;
ngx_post_event(rev, queue);
} else {
//没有持有accept锁,可以同步执行事件处理函数
//rev->handler在ngx_http_init_connection函数中被赋值为ngx_http_wait_request_handler
rev->handler(rev);
}
.......
}
nginx源码分析——event模块的更多相关文章
- nginx源码分析之模块初始化
在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...
- Zepto源码分析-event模块
源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...
- nginx源码分析——http模块
源码:nginx 1.12.0 一.nginx http模块简介 由于nginx的性能优势,现在已经有越来越多的单位.个人采用nginx或者openresty. ...
- zepto源码分析·event模块
准备知识 事件的本质就是发布/订阅模式,dom事件也不例外:先简单说明下发布/订阅模式,dom事件api和兼容性 发布/订阅模式 所谓发布/订阅模式,用一个形象的比喻就是买房的人订阅楼房消息,售楼处发 ...
- jQuery源码分析--Event模块(2)
接下来就是触发事件了.事件触发后的处理函数的分发主要靠两个函数,一个jQuery.event.dispatch,一个是jQuery.event.handlers.这个dispatch会调用handle ...
- jQuery源码分析--Event模块(1)
jQuery的Event模块提供了强大的功能:事件代理,自定义事件,自定义数据等.今天记录一下它实现的原理. 我们都知道,在js的原生事件中,有事件对象和回调函数这两样东西.但是事件对象是只读的,所以 ...
- Nginx源码分析--epoll模块
Nginx采用epoll模块实现高并发的网络编程,现在对Nginx的epoll模块进行分析. 定义在src/event/modules/ngx_epoll_module.c中 1. epoll_cre ...
- jQuery源码分析--Event模块(3)
最后剩下了事件的手动触发了.jQuery提供了两个函数trigger和triggerHandler来手动触发事件,可以触发原生事件和自定义的事件.这个触发不单只会触发有jQuery绑定事件,而且也会触 ...
- 读Zepto源码之Event模块
Event 模块是 Zepto 必备的模块之一,由于对 Event Api 不太熟,Event 对象也比较复杂,所以乍一看 Event 模块的源码,有点懵,细看下去,其实也不太复杂. 读Zepto源码 ...
随机推荐
- 很污的机器学习:从xhamster网站找到喜欢的片子
前言 最近迷上了看黄片(大雾).每次总是去搜索想看的片子,什么asian porn anal pussy 什么的我都不知道. 搜索着搜索着我手也累了,而且我喜欢的片子也是有一定的特征的,我不想把所有的 ...
- yaourt 之 Curl 错误
最近执行 yaourt 更新时总是出现以下错误: curl error: Couldn't connect to server 无法进行更新.把配置中的下载工具更换了成 axel 等其它下载工具,还是 ...
- PMS5003ST+Arduino Nano 串口读取数据
先上代码: 库文件是在guihub上的大神写的https://github.com/jbanaszczyk,我拿来小改下用以支持5003ST #include <Arduino.h> #i ...
- webpack引入handlebars报错'You must pass a string or Handlebars AST to Handlebars.compile'
背景: webpack作为一个部分替代打包工具和模块化工具的优秀选择出现,作为尝试,也为了构建自己习惯的前端开发方式,我尝试了将webpack和自己常用handlebars模板引擎结合.整体项目背景为 ...
- Linux系统date命令的参数及获取时间戳的方法
date指令相关用法示例 date 用法: date [OPTION]... [+FORMAT]date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]] ...
- iOS 应用关于弥补安全优化问题
1.log输出会被中奖者截获,暴露信息,影响app得性能 在工程里面的pch文件加入以下代码 // 调试状态 #define LMLog(...) NSLog(__VA_ARGS__) #else / ...
- Python之路-shell&计划任务
开发脚本自动部署及监控1.编写脚本自动部署反向代理.web.nfs:要求: I.部署nginx反向代理三个web服务,调度算法使用加权轮询: II.所有web服务使用共享存储nfs ...
- Python之路-计算机基础
一·计算机的组成 一套完整的计算机系统分为:计算机硬件,操作系统,软件. 硬件系统:运算器,控制器和存储器 ,输入设备,输出设备. 1.运算器:负责算数运算和逻辑运算,与控制器一起组成CPU. 2 ...
- js随机模块颜色
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content ...
- Centos安装MySql、Java、Tomcat
一.安装MySql 安装MySql yum install -y mysql-server mysql mysql-devel 启动MySql服务 service mysqld start 为root ...