读完之前的学习笔记,相信已经对nginx的启动流程有了一定的认识,从这一节起我们想深入各个模块,学习各个模块的内的主要操作。
本文来自于:http://blog.csdn.net/lengzijian/article/details/7598996
今天我们就来学习下event模块,在之前的启动里多次提到了调用各个模块的钩子函数,我们先来回忆一下关于event模块钩子函数的执行,也是event模块启动的步骤:
1.创建conf(creat_conf):
ngx_event_create_conf()
该方法,主要是创建了一个ngx_event_conf_t结构体,并且分配内存空间。
2.读取配置文件:
例如读取到的文件有如下行:
events
{
use epoll;
worker_connections 10240;
}
这个地方的events是一个block指令,在大括号内可以配置很多指令,这些指令定义在src/event/ngx_event.c中
static ngx_command_t ngx_event_core_commands[] = {
{ ngx_string("worker_connections"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_connections,
0,
0,
NULL },
...(此处省略)
{ ngx_string("connections"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_connections,
0,
0,
NULL },
{ ngx_string("use"),
NGX_EVENT_CONF|NGX_CONF_TAKE1,
ngx_event_use,
0,
0,
NULL },
ngx_null_command
};
当解析到events是会回调如下函数:
src/event/ngx_event.c
static char *
ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
void ***ctx;
ngx_uint_t i;
ngx_conf_t pcf;
ngx_event_module_t *m;
/* count the number of the event modules and set up their indices */
//计算event模块数量,并且记录
ngx_event_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
ngx_modules[i]->ctx_index = ngx_event_max_module++;
}
ctx = ngx_pcalloc(cf->pool, sizeof(void *));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
//为每一个event模块分配空间,用来保存响应配置结构的地址
//共分配了ngx_event_max_module个空间
*ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
if (*ctx == NULL) {
return NGX_CONF_ERROR;
}
*(void **) conf = ctx;
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
m = ngx_modules[i]->ctx;
//循环调用每个模块的creat_conf钩子函数,用于创建配置结构
if (m->create_conf) {
(*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);
if ((*ctx)[ngx_modules[i]->ctx_index] == NULL) {
return NGX_CONF_ERROR;
}
}
}
pcf = *cf;
cf->ctx = ctx;
cf->module_type = NGX_EVENT_MODULE;
cf->cmd_type = NGX_EVENT_CONF;
//由于events是一个block指令,events域下还可以配置很多其他指令,
//比如之前提过的use等,现在开始解析events block中的指令,完成初始化工作。
rv = ngx_conf_parse(cf, NULL);
*cf = pcf;
if (rv != NGX_CONF_OK)
return rv;
for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
m = ngx_modules[i]->ctx;
//循环执行每个event模块的init_conf函数,初始化配置结构
if (m->init_conf) {
rv = m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]);
if (rv != NGX_CONF_OK) {
return rv;
}
}
}
return NGX_CONF_OK;
}
ngx_events_block()函数中最重要的一个过程就是调用ngx_conf_parse(cf, NULL),此处调用ngx_conf_parse()的作用就是完成配置文件中events{}这个block的解析,从而调用其下所有的配置指令的回调函数,完成解析配置文件的初始化工作。但是这里我个人有个问题,待问完前辈之后,在指明问题和答案******。
2.初始化conf(init_conf)
ngx_event_init_conf()
该方法,主要是初始化ngx_event_conf_t结构体。
3.ngx_event_module_init
从名字上看是模块的初始化操作,但是纵观各个模块源代码,发现很多模块都没有init回调函数。这里本人也在纠结为什么,希望在学完全部代码后,能够找到答案。
src/event/ngx_event.c
static ngx_int_t
ngx_event_module_init(ngx_cycle_t *cycle)
{
void ***cf;
u_char *shared;
size_t size, cl;
ngx_shm_t shm;
ngx_time_t *tp;
ngx_core_conf_t *ccf;
ngx_event_conf_t *ecf;
//判断ngx_events_module是否调用过初始化conf操作
cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module);
if (cf == NULL) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
"no \"events\" section in configuration");
return NGX_ERROR;
}
//获取ngx_event_core_module模块的配置结构
ecf = (*cf)[ngx_event_core_module.ctx_index];
//查看是否是event中的模块,例如use 。。。。
if (!ngx_test_config && ngx_process <= NGX_PROCESS_MASTER) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"using the \"%s\" event method", ecf->name);
}
//获取ngx_core_module模块的配置结构
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
//从ngx_core_module模块的配置结构中获取timer_resolution参数
ngx_timer_resolution = ccf->timer_resolution;
#if !(NGX_WIN32)
{
ngx_int_t limit;
struct rlimit rlmt;
//获取当前进程能够打开的最大文件数 man getrlimit
if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"getrlimit(RLIMIT_NOFILE) failed, ignored");
} else {
//如果ngx_event_core_module模块连接数大于当前(软)限制
//并且ngx_core_module最大连接数无限制
//或者ngx_event_core_module连接数大于ngx_core_module最大连接数
if (ecf->connections > (ngx_uint_t) rlmt.rlim_cur
&& (ccf->rlimit_nofile == NGX_CONF_UNSET
|| ecf->connections > (ngx_uint_t) ccf->rlimit_nofile))
{
limit = (ccf->rlimit_nofile == NGX_CONF_UNSET) ?
(ngx_int_t) rlmt.rlim_cur : ccf->rlimit_nofile;
ngx_log_error(NGX_LOG_WARN, cycle->log, 0,
"%ui worker_connections are more than "
"open file resource limit: %i",
ecf->connections, limit);
}
}
}
#endif /* !(NGX_WIN32) */
//如果关闭了master进程,就返回
//因为关闭了master进程就是单进程工作方式,
//之后的操作时创建共享内存实现锁等工作,单进程不需要。
if (ccf->master == 0) {
return NGX_OK;
}
//如果已经存在accept互斥体了,不需要再重复创建了
if (ngx_accept_mutex_ptr) {
return NGX_OK;
}
/* cl should be equal or bigger than cache line size */
cl = 128;
//这里创建size大小的共享内存,这块共享内存将被均分成三段
size = cl /* ngx_accept_mutex */
+ cl /* ngx_connection_counter */
+ cl; /* ngx_temp_number */
//准备共享内存,大小为size,命名nginx_shared_zone,
shm.size = size;
shm.name.len = sizeof("nginx_shared_zone");
shm.name.data = (u_char *) "nginx_shared_zone";
shm.log = cycle->log;
//创建共享内存,起始地址保存在shm.addr
if (ngx_shm_alloc(&shm) != NGX_OK) {
return NGX_ERROR;
}
//获取起始地址保存
shared = shm.addr;
//accept互斥体取得共享内存的第一段cl大小内存
ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
ngx_accept_mutex.spin = (ngx_uint_t) -1;
/*创建accept互斥体
accept互斥体的实现依赖是否支持原子操作,如果有相应的原子操作;
就是用取得的这段共享内存来实现accept互斥体;否则,将使用文件锁
来实现accept互斥体。
accept互斥体的作用是:避免惊群和实现worker进程的负载均衡。
*/
if (ngx_shmtx_create(&ngx_accept_mutex, shared, cycle->lock_file.data)
!= NGX_OK)
{
return NGX_ERROR;
}
//获取内存的第二段cl大小的地址
ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
(void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"counter: %p, %d",
ngx_connection_counter, *ngx_connection_counter);
//获取内存的第三段cl大小的地址
ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl);
tp = ngx_timeofday();
ngx_random_number = (tp->msec << 16) + ngx_pid;
return NGX_OK;
}
4.ngx_event_process_init
在之前的worker进程分析中有提到过,当创建了一个worker进程后,worker进程首先就会做进程的初始化工作,此时会调用ngx_event_process_init函数。
src/event/ngx_event.c
static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
ngx_uint_t m, i;
ngx_event_t *rev, *wev;
ngx_listening_t *ls;
ngx_connection_t *c, *next, *old;
ngx_core_conf_t *ccf;
ngx_event_conf_t *ecf;
ngx_event_module_t *module;
//和之前一样,获取响应模块的配置结构
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);
//master进程打开,worker进程大于1,已经创建了accept_mutex
//才打开accept互斥体
if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
ngx_use_accept_mutex = 1; //使用互斥体
ngx_accept_mutex_held = 0; //是否获得accept互斥体
ngx_accept_mutex_delay = ecf->accept_mutex_delay;//争抢互斥体失败后,等待下次争抢时间间隔
} else {
ngx_use_accept_mutex = 0;
}
#if (NGX_THREADS)
//线程先不讲
#endif
//初始化计数器,此处将会创建一颗红黑树,来维护计时器,之后会详细讲解
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
return NGX_ERROR;
}
for (m = 0; ngx_modules[m]; m++) {
//这里之前讲过,跳过非NGX_EVENT_MODULE模块
if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
continue;
}
//非use配置指令指定的模块跳过,linux默认epoll
if (ngx_modules[m]->ctx_index != ecf->use) {
continue;
}
module = ngx_modules[m]->ctx;
/*调用具体时间模块的init函数
由于nginx实现了很多事件模块,比如:epoll、poll、select、dqueue、aio
(这些模块位于src/event/modules目录中),所以nginx对时间模块进行了一层抽象,
方便了不同的系统使用不同的事件模型,也便于扩展新的时间模型,我们的重点应该
放在epoll上。
此处的init回调,其实就是调用了ngx_epoll_init函数。module->actions结构封装了
epoll的所有接口函数。nginx就是通过actions结构将epoll注册到事件抽象层中。
actions的类型是ngx_event_action_t,位于src/event/ngx_event.h
这些具体的内容会在下一节中重点讲解。
*/
if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
/* fatal */
exit(2);
}
break;
}
//此处省略部分内容
//创建全局的ngx_connection_t数组,保存所有的connection
//由于这个过程是在各个worker进程中执行的,所以每个worker都有自己的connection数组
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;
#if (NGX_THREADS)
rev[i].lock = &c[i].lock;
rev[i].own_lock = &c[i].lock;
#endif
}
//创建一个写事件数组
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;
#if (NGX_THREADS)
wev[i].lock = &c[i].lock;
wev[i].own_lock = &c[i].lock;
#endif
}
i = cycle->connection_n;
next = NULL;
//初始化整个connection数组
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];
#if (NGX_THREADS)
c[i].lock = 0;
#endif
} while (i);
cycle->free_connections = next;
cycle->free_connection_n = cycle->connection_n;
/* for each listening socket */
//为每个监听套接字从connection数组中分配一个连接,即一个slot
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
//从conneciton中取得一个新的连接solt
c = ngx_get_connection(ls[i].fd, cycle->log);
if (c == NULL) {
return NGX_ERROR;
}
c->log = &ls[i].log;
c->listening = &ls[i];
ls[i].connection = c;
rev = c->read;
rev->log = c->log;
rev->accept = 1; //读时间发生,调用accept
#if (NGX_HAVE_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;
}
}
//注册监听套接口毒事件的回调函数 ngx_event_accept
rev->handler = ngx_event_accept;
//使用了accept_mutex,暂时不将监听套接字放入epoll中,而是
//等到worker抢到accept互斥体后,再放入epoll,避免惊群的发生
if (ngx_use_accept_mutex) {
continue;
}
if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
if (ngx_add_conn(c) == NGX_ERROR) {
return NGX_ERROR;
}
} else {
//没有使用accept互斥体,那么就将此监听套接字放入epoll中。
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
}
#endif
}
return NGX_OK;
}
到现在为止,事件驱动的初始化已经完成。
分享到:
相关推荐
Nginx 源码分析笔记 自己的源码分析笔记,未完。
自己的nginx源码学习资料,包含《Nginx模块开发指南》和《深入理解Nginx》等,方便初学者了解nginx设计思想以及源码
nginx学习笔记.zip
nginx源码nginx源码nginx源码nginx源码nginx源码nginx源码nginx源码nginx源码nginx源码nginx源码
Nginx配置文件——一级域名、二级域名
mac无坑安装nginx(csdn)————程序
nginx源码说明;其他下载即可,没下载分了,大家互相帮忙。其他情况大概就这些吧,其他也没什么好说的了,看文档即可
nginx笔记nginx笔记nginx笔记nginx笔记nginx笔记nginx笔记nginx笔记nginx笔记
Nginx学习笔记
收集的 nginx 源码 解析 ,非常全 architecture.png Emiller的Nginx模块开发指南.docx Nginx(en).pdf nginx@taobao.pdf nginx_internals.pdf nginx核心讲解(0.2).doc nginx核心讲解(0.4).doc Nginx模块开发指南中文...
Nginx模块源码 nginx-notice-2 一只小麻雀。 通过Post的方式取文件 Nginx 0.7 可编译。
带有详细注释的nginx源码,能帮你有效地阅读和学习nginx源码
这是我自学nginx的学习笔记,上传只是为了更好的督促自己学习,如果你也有同感,加入一起学习吧。
在第二部分中,通过具体使用实例讲述了Nginx的模块(包括官方模块和第三方模块),并详细介绍了充分使用Nginx的方式方法。同时在这里使用了Heartbeat服务实现Nginx服务器的高可用。 本书的最后一部分是关于...
Nginx教程
本人在银行工作,基于生产环境搭建方法编写的Nginx源码安装手册,生产环境可参考此手册。
Windows下编译Nginx并添加模块,在项目中使用过的
压缩包内有四个版本的nginx,可根据自己的需求安装相应的软件包。使用方法:下载资源包--->将需要的版本导入到linux主机中--->tar解压得到nginx源码包--->源码编译安装
nginx开机自启动配置文件,在[CentOS 7.6 64位]系统下运行正常。 编译安装nginx后,将该配置文件放在服务器的/usr/lib/systemd/system/目录下,然后启动nginx服务。
nginx源码,1.0版本的,适合学习nginx的工作原理,欢迎码农朋友们下载学习。