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

Nginx启动初始化过程(二)

淘宝数据平台团队 2010-12-02 22:32:44 累计浏览 3,642 次
本机暂存

在Nginx启动初始化过程(一)中提到main函数会调用ngx_init_cycle()初始化一个全局cycle变量,本文就来看看这个ngx_init_cycle()函数究竟做了哪些初始化工作。ngx_cycle_t结构类型被定义在src/core/ngx_cycle.h文件中,多达23个成员变量(nginx-0.7.67),由于ngx_init_cycle()函数的代码多达近800行,绝对算大函数了(当然,我也相信还有更加变态的函数,将整个世界都写到一个函数中的情况也是有可能的),在此就挑一些相对关键的代码来看吧。

    ngx_timezone_update();
    /* force localtime update with a new timezone */
    tp = ngx_timeofday();
    tp->sec = 0;
    ngx_time_update();

这几个函数都是来自于Nginx的时间管理,对时区和时间进行一次更新操作。一个高性能服务器需要合理的调用gettimeofday(),减少gettimeofday()的调用次数有利于提高服务器的性能。Nginx对时间的管理也是值得我们去学习的,这部分代码就放后续分析吧。

    /*创建一个大小为NGX_CYCLE_POOL_SIZE的内存池*/
    pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
    if (pool == NULL) {
        return NULL;
    }
    pool->log = log;
    /*在内存池上分配一个ngx_cycle_t对象*/
    cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t));
    if (cycle == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }

这几行简简单单的代码在整个Nginx源码中是随处可见,它首先创建了一个内存池,然后在这个内存池上为cycle变量分配了一块存储空间。后续的所有初始化工作都为了填写这个cycle变量的各个成员字段。Nginx的内存池实现是相当的简单,但绝对不简陋;在web server的应用场景中是非常的有效。内存池的实现在src/core/ngx_palloc.h和src/core/ngx_palloc.c中。

     /*初始化一个链表*/
     if (ngx_list_init(&cycle->open_files, pool, n,
                      sizeof(ngx_open_file_t)) != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }
    if (ngx_list_init(&cycle->shared_memory, pool, n,
            sizeof(ngx_shm_zone_t)) != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

此段代码被我刻意简化了参数n值的求解过程,但我们能够清楚的看出Nginx使用了链表来维护需要打开的文件以及共享内存,也就是每个打开的文件都会放到cycle中的open_files链表中,每个共享内存段都会放到shared_memory链表中。此处仅仅是完成这两个链表空间的初始化,为后面存放相应的对象做好准备。

    /*初始化listening数组*/
    cycle->listening.elts = ngx_pcalloc(pool, n * sizeof(ngx_listening_t));
    if (cycle->listening.elts == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
    cycle->listening.nelts = 0;
    cycle->listening.size = sizeof(ngx_listening_t);
    cycle->listening.nalloc = n;
    cycle->listening.pool = pool;

cycle中的listening字段是一个数组,在这里完成此数组的初始化,为该数组分配起n个存储ngx_listening_t元素的单元。这个数组将用于存储监听套接字。Nginx原本就提供数组初始化的接口函数ngx_array_init,此处却没有使用数组初始化函数来完成初始化,诡异。

    cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));/*ngx_max_module为模块总数*/
    if (cycle->conf_ctx == NULL) {
        ngx_destroy_pool(pool);
        return NULL;
    }
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_CORE_MODULE) {
            continue; /* 非核心模块跳过,此处只关心核心模块*/
        }
        module = ngx_modules[i]->ctx;
        if (module->create_conf) {
            rv = module->create_conf(cycle);
            if (rv == NULL) {
                ngx_destroy_pool(pool);
                return NULL;
            }
            cycle->conf_ctx[ngx_modules[i]->index] = rv;
        }
    }

首先将conf_ctx初始化了一段内存空间(可以看成一个普通的数组),这段空间能够存储ngx_max_module个void*指针;在启动初初始化过程(一)中,我们得知ngx_max_modules统计了模块总数,因此可以看出cycle中的conf_ctx将存储每个module的某些信息。接下来的for循环验证了我们的猜想。在for循环中,调用NGX_CORE_MODULE类型模块的ceate_conf回调,创建相应的配置结构存储空间,然后将这个配置结构存储空间的地址保存到conf_ctx数组的对应单元处。寻找正确的对应单元就是通过每个模块的index字段。通过对NGX_CORE_MODULE类型模块的处理,我们暂且猜测conf_ctx数组就是用来存储每个模块的配置结构;利用这个数组,我们能够获取每个模块相应的配置数据。以后的分析将会检验这里的猜测。

NGX_CORE_MODULE类型的模块有:ngx_core_module、ngx_errlog_module、ngx_events_module和ngx_http_module等。

ngx_core_module定义在src/core/nginx.c文件中,create_conf钩子对应的函数为ngx_core_module_create_conf(),此函数就是创建ngx_core_conf_t配置结构。

ngx_errlog_module定义在src/core/ngx_log.c中,create_conf钩子没有对应的回调函数,为NULL。

ngx_events_module定义在src/event/ngx_event.c中,create_conf钩子没有对应的回调函数,为NULL。

ngx_http_module定义在src/http/ngx_http.c中,create_conf钩子没有对应的回调函数,为NULL。

由此可以看出,此处的循环执行create_conf回调函数,其实就只调用了ngx_core_module_create_conf()。

    if (ngx_conf_param(&conf) != NGX_CONF_OK) {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }
    if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {/*解析配置文件*/
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }

这里将完成对配置文件的解析工作,做过Nginx模块开发的都清楚――解析配置文件的时候,将会完成每个指令的set回调,这个set回调函数一般都是用于将配置数据填写到配置结构中。 解析配置文件常用的手法之一就是利用状态机,但此处Nginx却没有明确的使用状态机来完成配置文件的解析工作。后面具体看看Nginx解析配置文件的流程。其次,Nginx通过解析配置文件,回调一系列的函数来完成各个模块之间的衔接,有效的将一个完全解耦的系统组合到了一块。

    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_CORE_MODULE) {
            continue;
        }
        module = ngx_modules[i]->ctx;
        if (module->init_conf) {
            if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index])
                == NGX_CONF_ERROR)
            {
                environ = senv;
                ngx_destroy_cycle_pools(&conf);
                return NULL;
            }
        }
    }

前面完成了NGX_CORE_MODULE类型模块的配置结构创建工作,这里调用所有NGX_CORE_MODULE类型模块的init_conf回调函数,完成配置结构的初始化工作。同ceate_conf,其实此处也只是调用了ngx_core_module的ngx_core_module_init_conf()回调函数。对ceate_conf创建的配置结构进行初始化。其他几个NGX_CORE_MODULE没有init_conf回调函数。

    /* open the new files */
    part = &cycle->open_files.part;
    file = part->elts;
    /*此处for循环是在遍历链表open_files*/
    for (i = 0; /* void */ ; i++) {
        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }
            part = part->next;
            file = part->elts;
            i = 0;
        }
        if (file[i].name.len == 0) {
            continue;
        }
        /*根据链表中存储的文件名来打开对应的文件*/
        file[i].fd = ngx_open_file(file[i].name.data,
                                   NGX_FILE_APPEND,
                                   NGX_FILE_CREATE_OR_OPEN,
                                   NGX_FILE_DEFAULT_ACCESS);

        if (file[i].fd == NGX_INVALID_FILE) {
            goto failed;
        }
#if !(NGX_WIN32)
        if (fcntl(file[i].fd, F_SETFD, FD_CLOEXEC) == -1) {
            goto failed;
        }
#endif
    }

遍历open_files链表,打开所有文件(调用open)。起初,我们看到了对open_files链表的创建、初始化,却没有看到写需要打开的文件名数据到链表中;其实,填写文件名数据就是在解析配置文件的时候完成的。除此之外,很多很多的数据初始化都是在解析配置文件的时候完成。执行了打开文件操作后,open_files链表就不光保存了文件名了,还保存了文件描述符等信息,足够以后读写文件之用了。

    /* create shared memory */
    part = &cycle->shared_memory.part;
    shm_zone = part->elts;
    /*同上面的open_files,此处遍历链表shared_memory,初始化各个共享内存段*/
    for (i = 0; /* void */ ; i++) {
        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }
            part = part->next;
            shm_zone = part->elts;
            i = 0;
        }
        if (shm_zone[i].shm.size == 0) {
            goto failed;
        }
        if (shm_zone[i].init == NULL) {
            /* unused shared zone */
            continue;
        }
        shm_zone[i].shm.log = cycle->log;
        opart = &old_cycle->shared_memory.part;
        oshm_zone = opart->elts;
        for (n = 0; /* void */ ; n++) {
            if (n >= opart->nelts) {
                if (opart->next == NULL) {
                    break;
                }
                opart = opart->next;
                oshm_zone = opart->elts;
                n = 0;
            }
            if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) {
                continue;
            }
            if (ngx_strncmp(shm_zone[i].shm.name.data,
                            oshm_zone[n].shm.name.data,
                            shm_zone[i].shm.name.len)
                != 0)
            {
                continue;
            }
            if (shm_zone[i].shm.size == oshm_zone[n].shm.size) {
                shm_zone[i].shm.addr = oshm_zone[n].shm.addr;
                if (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data)
                    != NGX_OK)
                {
                    goto failed;
                }
                goto shm_zone_found;
            }
            ngx_shm_free(&oshm_zone[n].shm);
            break;
        }
        /*下面三步就完成共享内存的创建和初始化*/
        if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {
            goto failed;
        }
        if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {
            goto failed;
        }
        if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {
            goto failed;
        }
    shm_zone_found:
        continue;
    }

这部分复杂的代码就是为完成所有共享内存的创建及初始化,后面再专门分析Nginx对共享内存的管理及使用。

    if (ngx_open_listening_sockets(cycle) != NGX_OK) {
        goto failed;
    }
    if (!ngx_test_config) {
        ngx_configure_listening_sockets(cycle);
    }

遍历listening数组,打开所有的监听套接口(依次进行socket、bind、listen),同时设置一些socket选项及文件描述符属性,如非阻塞等。

    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->init_module) {
            if (ngx_modules[i]->init_module(cycle) != NGX_OK) {
                /* fatal */
                exit(1);
            }
        }
    }

执行所有模块的init_module操作,看名字为对模块进行初始化。 浏览源码,发现包括几个NGX_CORE_MODULE类型的模块在内的绝大多数模块都没有这个init回调函数。究竟哪些模块才使用这个回调接口呢?动用搜索功能,终于找到了一个模块使用了这个回调接口,它就是ngx_event_core_module。在此,就不纠结这个独特的初始化函数了,到分析事件驱动的时候,再回头看看。

ngx_init_cycle()就差不多了,不过还有很多清除多余资源的代码没有做介绍,比如:关闭无用的文件,监听套接字等资源。有兴趣的读者,自行研究吧。至此,初始化过程就暂且告一段落吧,下一篇开始进程管理。

同分类推荐文章

  1. Vibe新开源项目 - Vaala AI Gateway (2026-05-17 02:10:19)
  2. SmartPerfetto 架构文章 Q&A:8 个深度技术问答 (2026-04-10 11:00:00)
  3. 让 AI 把我的 PHP 博客重写成 Go (2026-03-27 18:33:54)

查看更多 后端 文章 →

建议继续学习

  1. 配置Nginx+uwsgi更方便地部署python应用 (累计阅读 106,963)
  2. 搜狐闪电邮箱的 Nginx/Postfix 使用模式 (累计阅读 33,822)
  3. Git log diff config高级进阶 (累计阅读 24,780)
  4. 记录一个软中断问题 (累计阅读 16,885)
  5. 解析nginx负载均衡 (累计阅读 16,503)
  6. server日志的路径分析 (累计阅读 11,181)
  7. Nginx模块开发入门 (累计阅读 11,101)
  8. 检查nginx配置,重载配置以及重启的方法 (累计阅读 10,782)
  9. Cacti 添加 Nginx 监控 (累计阅读 10,521)
  10. 使用Squid缓存视频 (累计阅读 10,280)