IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

Nginx事件驱动的初始化

淘宝数据平台与产品部官方博客 tbdata.org 2010-12-08 22:10:44 累计浏览 1,977 次
本机暂存

Nginx的高性能应该算是事件驱动的功劳。Nginx事件处理的相关代码位于src/event目录中,事件驱动是Nginx的核心,所以代码量相对也比较大。事件驱动初始化的过程主要由下图中的三步组成。
原图已失效

第一步:解析配置文件的初始化

在Nginx的启动初始化过程中,将调用ngx_conf_parse()解析配置文件,此过程将遇到类似如下的配置项:
events {
worker_connections 20480;
}
此处的events是一个block指令,在它下面还可以配置很多其他的指令,比如这里的worker_connections等。events下面可以配置的指令定义在数组ngx_event_core_commands(位于src/event/ngx_event.c文件中)中。每个指令都有自己对应的回调函数,events指令的回调函数是ngx_events_block()(位于src/events/ngx_event.c文件中),即在解析配置文件到events指令的时候,将调用此回调函数。此函数的大概分析如下:

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 */
    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;
    }
	/*为每个事件模块分配一个指针,用以保存相应配置结构的地址。*/
    *ctx = ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
    if (*ctx == NULL) {
        return NGX_CONF_ERROR;
    }
    *(void **) conf = ctx;
	/* 循环调用每个事件模块的create_conf函数,创建配置结构*/
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }
        m = ngx_modules[i]->ctx;
        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;
	/*循环执行每个事件模块的init_conf函数,初始化配置结构*/
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_EVENT_MODULE) {
            continue;
        }
        m = ngx_modules[i]->ctx;
        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的解析,从而调用其下所有的配置指令的回调函数,完成解析配置文件的初始化工作。

第二步:ngx_event_module_init

在《Nginx启动初始化过程(二)》结尾的时候有这样的一段文字――“执行所有模块的init_module操作,看名字为对模块进行初始化。 浏览源码,发现包括几个NGX_CORE_MODULE类型的模块在内的绝大多数模块都没有这个init回调函数。究竟哪些模块才使用这个回调接口呢?动用搜索功能,终于找到了一个模块使用了这个回调接口,它就是ngx_event_core_module。在此,就不纠结这个独特的初始化函数了,到分析事件驱动的时候,再回头看看。”;此处的小标题ngx_event_module_init就是模块ngx_event_core_module的那个回调函数。ngx_event_module_init的代码分析如下:

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;

    cf = ngx_get_conf(cycle->conf_ctx, ngx_events_module);
    if (cf == NULL) {
        return NGX_ERROR;
    }
	/*取得ngx_event_core_module模块的配置结构*/
    ecf = (*cf)[ngx_event_core_module.ctx_index];
	/*取得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;
	。。。。。。。。。。。。。。。。。。。。
	/*如果关闭了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大小的共享内存,这块共享内存将被均分成三段,
	分别供ngx_accept_mutex、ngx_connection_counter、ngx_temp_number
	使用。
	*/
    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;

	/*创建accept互斥体。

	accept互斥体的实现依赖是否支持原子操作,如果有相应的原子操作;
	就是用取得的这段共享内存来实现accept互斥体;否则,将使用文件
	锁来实现accept互斥体。

	accept互斥体的作用是:避免惊群和实现worker进程的负载均衡。
	*/
    if (ngx_shmtx_create(&ngx_accept_mutex, shared, cycle->lock_file.data)
        != NGX_OK)
    {
        return NGX_ERROR;
    }
	/*ngx_connection_counter取得共享内存的第二段cl大小内存*/
    ngx_connection_counter = (ngx_atomic_t *) (shared + 1 * cl);
    (void) ngx_atomic_cmp_set(ngx_connection_counter, 0, 1);

	/*ngx_temp_number取得共享内存的第三段cl大小内存*/
    ngx_temp_number = (ngx_atomic_t *) (shared + 2 * cl);

	。。。。。。。。。。。。。。。。。。。。。。
    return NGX_OK;
}

第三步:ngx_event_process_init

在worker进程的分析中,有提到调用每个模块自定义的进程初始化函数。ngx_event_process_init回调函数就是ngx_event_core_commands模块自定义的进程初始化函数。因此,在master进程创建好了一个worker进程后,worker进程首先就会做进程的初始化工作,此时就会调用ngx_event_process_init函数。ngx_event_process_init的代码分析如下:

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,配置了accetp_mutex(默认使用)
	时,才使用accept互斥体。
	*/
    if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
        ngx_use_accept_mutex = 1; 	/* 1 使用accept互斥体, 0 不使用*/
        ngx_accept_mutex_held = 0;	/* ngx_accept_mutex_held代表是否获得accept互斥体*/
        ngx_accept_mutex_delay = ecf->accept_mutex_delay; /*抢互斥体失败后,下次再抢的间隔时间*/
    } else {
        ngx_use_accept_mutex = 0;
    }
	/*初始化计时器,此处将会创建起一颗红黑色,来维护计时器。*/
    if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
        return NGX_ERROR;
    }
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
            continue; /*非NGX_EVENT_MODULE跳过*/
        }
        if (ngx_modules[m]->ctx_index != ecf->use) {
            continue;/*非use配置指令指定的模块跳过,Linux默认epoll*/
        }
        module = ngx_modules[m]->ctx;
		/*调用具体事件模块的init函数。

		由于Nginx实现了很多的事件模块,比如:epoll,poll,select, kqueue,aio
		(这些模块位于src/event/modules目录中)等等,所以Nginx对事件模块进行
		了一层抽象,方便在不同的系统上使用不同的事件模型,也便于扩展新的事件
		模型。从此过后,将把注意力主要集中在epoll上。

		此处的init回调,其实就是调用了ngx_epoll_init函数。module->actions结构
		封装了epoll的所有接口函数。Nginx就是通过actions结构将epoll注册到事件
		抽象层中。actions的类型是ngx_event_actions_t,位于src/event/ngx_event.h
		*/
        if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
            /* fatal */
            exit(2);
        }
        break; 	/*跳出循环,只可能使用一个具体的事件模型*/
    }
	。。。。。。。。。。。。。。。。。。。。。。。。。
	/*创建一个connection数组,维护所有的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 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 connection_n; i++) {
        wev[i].closed = 1;
    }
    i = cycle->connection_n;
    next = NULL;

	/*初始化整个connection数组,connection数组使用得很是巧妙,
	能够快速的获取释放一个连接结构。下一篇画个图来详细看看
	这个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];
    } 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 listening.nelts; i++) {
		/*从connection中取得一个新的连接slot*/
        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_WIN32)
        。。。。。。。。。。。。。。。。。。。
#else
		/*注册监听套接口读事件的回调函数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;
}

至此,事件驱动的初始化三步曲就分析完了。

同分类推荐文章

  1. 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
  2. Go 实验特性详解 (2026-06-21 10:05:27)
  3. amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)

查看更多 后端 文章 →

建议继续学习

  1. 配置Nginx+uwsgi更方便地部署python应用 (累计阅读 107,164)
  2. 搜狐闪电邮箱的 Nginx/Postfix 使用模式 (累计阅读 33,895)
  3. 记录一个软中断问题 (累计阅读 16,955)
  4. 解析nginx负载均衡 (累计阅读 16,622)
  5. server日志的路径分析 (累计阅读 11,241)
  6. Nginx模块开发入门 (累计阅读 11,170)
  7. 检查nginx配置,重载配置以及重启的方法 (累计阅读 10,896)
  8. Cacti 添加 Nginx 监控 (累计阅读 10,644)
  9. fsockopen 异步处理 (累计阅读 10,345)
  10. 使用Squid缓存视频 (累计阅读 10,339)