菜鸟学习Nginx之启动流程(1)

对于C语言编写的程序来说,main函数就是入口函数,把main函数研究清楚对于理解软件架构、功能会有事半功倍的效果。好在Nginx的main函数并不是很复杂,这里会把启动流程分成两篇来介绍,希望能够描述清楚。

我把启动流程划分成两部分:cycle核心结构体初始化、master/worker进程启动。本篇介绍cycle核心结构体初始化。

一、初始化流程

在Nginx中有一个结构体伴随Nginx进程整个生命周期,那就是ngx_cycle。在Nginx中有且只有一个对象ngx_cycle_t。先来看一下Nginx启动流程图,有一些无关紧要的功能并没有在图中体现:

 

特别说明:

  1. 继承socket,此部分功能主要用于平滑升级功能并且继承的是监听socket,为了保证服务不中断。
  2. 初始化cycle,核心结构体初始化是Nginx源码中最长的函数,里面涉及的内容非常多,本篇就是详细分析该方法。
  3. 启动后台进程,一般情况下我们是通过终端,直接运行Nginx进程启动服务。这种场景属于前台进程,为了脱离终端进程,必须要以后台方式运行Nginx方式。
  4. Nginx生存模式,一般由两种,单机模式和master/woker模式。在真正服务部署时,均采用master/worker模式。

二、结构体

typedef struct ngx_cycle_s           ngx_cycle_t;

struct ngx_cycle_s
{
     /**
      * 指向一个指针数组,该数组元素又指向了一个指针数组,因此时4级指针.
      * 第一层数组下标,是ngx_module_t.index
      * 第二层数组下标,是ngx_module_t.ctx_index
      * 返回配置结构体,每个模块都一个配置模块结构体(自定义). 
      * 由create_conf回调创建出来的配置结构体
      * 注: conf_ctx是一个数组,大小与ngx_modules一样 初始化在函数ngx_init_cycle
      */
    void ****conf_ctx;
    ngx_pool_t *pool; /* 进程级内存池 */

    ngx_log_t *log;
    ngx_log_t new_log;

    ngx_uint_t log_use_stderr; /* unsigned  log_use_stderr:1; */

    ngx_connection_t **files;
    ngx_connection_t *free_connections; /* 空闲连接 */
    ngx_uint_t free_connection_n; /* 空闲连接数 */

    ngx_module_t **modules; /* 实际指向ngx_modules.c中ngx_modules */
    ngx_uint_t modules_n;
    ngx_uint_t modules_used; /* unsigned  modules_used:1; */

    ngx_queue_t reusable_connections_queue; /* 双向链表 空闲connections 可重复使用 */
    ngx_uint_t reusable_connections_n;

    ngx_array_t listening; /* 动态数组 监听socket */
    ngx_array_t paths;

    ngx_array_t config_dump;
    ngx_rbtree_t config_dump_rbtree;
    ngx_rbtree_node_t config_dump_sentinel;

    ngx_list_t open_files;
    ngx_list_t shared_memory;

    ngx_uint_t connection_n; /* 当前活跃连接数 */
    ngx_uint_t files_n;

    /* 三者对应关系是 按照数组下标对应 */
    ngx_connection_t *connections; /* 连接池 数组 默认1024 */
    ngx_event_t *read_events; /* 数组 每一个连接至少对应一个读事件 默认1024 */
    ngx_event_t *write_events;/* 数组 每一个连接至少对应一个写事件 默认1024 */

    ngx_cycle_t *old_cycle;

    ngx_str_t conf_file; /* 默认/usr/local/nginx/conf/nginx.conf */
    ngx_str_t conf_param;
    ngx_str_t conf_prefix; /* 默认/usr/local/nginx/conf/ */
    ngx_str_t prefix; /* /usr/local/nginx/ */
    ngx_str_t lock_file;
    ngx_str_t hostname;
};

我相信所有人第一次看到该结构体,第一反应绝对是:我靠,4级指针,什么鬼!不错,我就是这个反应,当初看到了这个成员突然有种不想继续想法。这里我想说的是:当遇到奇葩的数据结构或者定义的时候,我们需要冷静下来,慢慢分析,一定能分析出个所以然。如果还不行就是百度/谷歌,我们相信自己绝对不是第一个吃螃蟹的人。

这个4级指针conf_ctx,在注释中已经介绍的详细了,如果还是比较模糊,在下面还会具体介绍。

三、详细说明

函数ngx_init_cycle主要功能就是初始化ngx_cycle_t数据结构中各个成员,该函数大概有900行代码,应该是Nginx中最长的代码。具体如下:

/**
 * 创建ngx_cycle_t核心结构
 * @param old_cycle 旧的核心结构
 * @return 返回新的ngx_cycle_t结构
 */
ngx_cycle_t *
ngx_init_cycle(ngx_cycle_t *old_cycle)
{
    void *rv;
    char **senv;
    ngx_uint_t i, n;
    ngx_log_t *log;
    ngx_time_t *tp;
    ngx_conf_t conf;
    ngx_pool_t *pool;
    ngx_cycle_t *cycle, **old;
    ngx_shm_zone_t *shm_zone, *oshm_zone;
    ngx_list_part_t *part, *opart;
    ngx_open_file_t *file;
    ngx_listening_t *ls, *nls;
    ngx_core_conf_t *ccf, *old_ccf;
    ngx_core_module_t *module;
    char hostname[NGX_MAXHOSTNAMELEN];

    ngx_timezone_update();

    /* force localtime update with a new timezone */

    tp = ngx_timeofday();
    tp->sec = 0;

    ngx_time_update();

    log = old_cycle->log;
    /* 创建内存池 */
    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;
    }

    cycle->pool = pool;
    cycle->log = log;
    cycle->old_cycle = old_cycle;
    /* 配置文件相关 */
    cycle->conf_prefix.len = old_cycle->conf_prefix.len;
    cycle->conf_prefix.data = ngx_pstrdup(pool, &old_cycle->conf_prefix);
    if (cycle->conf_prefix.data == NULL)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    cycle->prefix.len = old_cycle->prefix.len;
    cycle->prefix.data = ngx_pstrdup(pool, &old_cycle->prefix);
    if (cycle->prefix.data == NULL)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    cycle->conf_file.len = old_cycle->conf_file.len;
    cycle->conf_file.data = ngx_pnalloc(pool, old_cycle->conf_file.len + 1);
    if (cycle->conf_file.data == NULL)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }
    ngx_cpystrn(cycle->conf_file.data, old_cycle->conf_file.data,
                old_cycle->conf_file.len + 1);

    cycle->conf_param.len = old_cycle->conf_param.len;
    cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param);
    if (cycle->conf_param.data == NULL)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    n = old_cycle->paths.nelts ? old_cycle->paths.nelts : 10;

    if (ngx_array_init(&cycle->paths, pool, n, sizeof(ngx_path_t *)) != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    ngx_memzero(cycle->paths.elts, n * sizeof(ngx_path_t *));

    if (ngx_array_init(&cycle->config_dump, pool, 1, sizeof(ngx_conf_dump_t)) != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    ngx_rbtree_init(&cycle->config_dump_rbtree, &cycle->config_dump_sentinel,
                    ngx_str_rbtree_insert_value);

    if (old_cycle->open_files.part.nelts)
    {
        n = old_cycle->open_files.part.nelts;
        for (part = old_cycle->open_files.part.next; part; part = part->next)
        {
            n += part->nelts;
        }
    }
    else
    {
        n = 20;
    }

    if (ngx_list_init(&cycle->open_files, pool, n, sizeof(ngx_open_file_t)) != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    if (old_cycle->shared_memory.part.nelts)
    {
        n = old_cycle->shared_memory.part.nelts;
        for (part = old_cycle->shared_memory.part.next; part; part = part->next)
        {
            n += part->nelts;
        }
    }
    else
    {
        n = 1;
    }

    if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t)) != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

这段代码逻辑比较简单,只是单纯的调用内部api对各个成员 进行初始化,例如内存池,共享内存,配置文件等。这里需要特别说明ngx_cycle_t中内存池pool。在开篇的时候就已经说了ngx_cycle_t生命周期是和进程一样的,那么ngx_cycle_t中的pool也是一样,在这里我称呼它为进程级内存池。后续所有内存的申请以及子内存池(连接级内存池、请求级内存池)均来自此池。

    /* 分配listening数组动态数组 如果第一次启动old_cycle->listening为0 */
    n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;

    if (ngx_array_init(&cycle->listening, pool, n, sizeof(ngx_listening_t)) != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    ngx_memzero(cycle->listening.elts, n * sizeof(ngx_listening_t));

初始化listening结构,该结构主要用于保存监听socket相关信息,例如:监听80端口的socket。为什么listening是动态数组呢?对于Tcp监听来说,我们可以指定多个端口同时提供服务,例如:http默认端口80,https默认端口是443。这个时候就需要有多个listening保存。 

    ngx_queue_init(&cycle->reusable_connections_queue);
    /* 创建大小为ngx_max_module,数组元素类型为void* 其实创建的指针数组 */
    cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));
    if (cycle->conf_ctx == NULL)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

 注意conf_ctx初始化工作,通过内存池申请内存,其大小为ngx_max_module * sizeof(void *)。实质内容是,创建了一个指针数组,等价于void* conf_ctx[ngx_max_module],数组每一个项保存的是一个指针。个人认为这样解释应该比较清晰。数组与ngx_modules定义顺序是保持一致的。例如:conf_ctx[0]是模块ngx_core_module上下文,conf_ctx[4]是模块ngx_events_module上下文,下面代码可验证

    /* 从全局变量ngx_modules拷贝到cycle->modules*/
    if (ngx_cycle_modules(cycle) != NGX_OK)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }
    /* 初始化核心模块即类型为NGX_CORE_MODULE */
    for (i = 0; cycle->modules[i]; i++)
    {
        if (cycle->modules[i]->type != NGX_CORE_MODULE)
        {
            continue;
        }
            
        module = cycle->modules[i]->ctx;/* 定义模块时赋值 */

        if (module->create_conf)
        {
            /**
             * 目前定义create_conf回调方法 只有ngx_core_module和ngx_regex_module
             * ngx_event_module没有定义create_conf,只定义了init_conf,可知
             * ngx_event_module对应的conf_ctx是NULL, 但是在经过ngx_conf_parse后
             * conf_ctx不为NULL. 
             */
            rv = module->create_conf(cycle);
            if (rv == NULL)
            {
                ngx_destroy_pool(pool);
                return NULL;
            }
            cycle->conf_ctx[cycle->modules[i]->index] = rv;//给指针数组赋值
        }
    }

调用核心模块(类型为NGX_CORE_MODULE)中定义的create_conf回调函数。该函数主要用于创建配置文件上下文,用于赋值给conf_ctx。在模块声明时只有ngx_core_module和ngx_regex_module定义了create_conf,看到这里不知道是否和我有一样的疑问:那其他模块是在什么时候生成conf_ctx呢?继续往下看

    senv = environ;//保存环境变量

    ngx_memzero(&conf, sizeof(ngx_conf_t));
    /* STUB: init array ? */
    conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));
    if (conf.args == NULL)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);
    if (conf.temp_pool == NULL)
    {
        ngx_destroy_pool(pool);
        return NULL;
    }

    conf.ctx = cycle->conf_ctx;
    conf.cycle = cycle;
    conf.pool = pool;
    conf.log = log;
    conf.module_type = NGX_CORE_MODULE;
    conf.cmd_type = NGX_MAIN_CONF;

#if 0
    log->log_level = NGX_LOG_DEBUG_ALL;
#endif

    if (ngx_conf_param(&conf) != NGX_CONF_OK)
    {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }
    /**
     * 解析配置文件 
     * 注1: 经过此方法之后 核心模块ngx_event_module对应的conf_ctx有数据了 
     * 在执行ngx_conf_parse函数时,会解析nginx.conf配置文件,当遇到event标签,会调用
     * ngx_events_block回调方法 该方法会设置conf_ctx
     * 注2: 经过此方法cycle->listening中会保存真正数据
     */
    if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK)
    {
        environ = senv;
        ngx_destroy_cycle_pools(&conf);
        return NULL;
    }

通过执行ngx_conf_parse函数,解析nginx.conf配置文件,在解析过程中遇到标签就会调用对应的解析函数,在解析函数中会对conf_ctx进行赋值。例如:events标签,会调用ngx_events_block。对于其他modules都这样操作的。

for (i = 0; cycle->modules[i]; i++)
    {
        if (cycle->modules[i]->type != NGX_CORE_MODULE)
        {
            continue;
        }

        module = cycle->modules[i]->ctx;

        if (module->init_conf)
        {
            if (module->init_conf(cycle,
                                  cycle->conf_ctx[cycle->modules[i]->index]) == NGX_CONF_ERROR)
            {
                environ = senv;
                ngx_destroy_cycle_pools(&conf);
                return NULL;
            }
        }
    }

调用init_conf回调函数,大部分模块都没有定义该回调方法。

在接下来,为了节约篇幅,这里忽略一些逻辑简单的初始化流程。

    /* handle the listening sockets */

    if (old_cycle->listening.nelts)
    {
        /* 只有在平滑升级才会进入此分支,第一次启动服务不会进入。 针对平滑升级的会有独立一篇,介时会详细说明 */
    }
    else
    {
        /**
         * listening赋值是在执行ngx_conf_parse 即解析配置文件时,
         * 入口ngx_http_block,最终会ngx_create_listening
         */
        ls = cycle->listening.elts;
        for (i = 0; i < cycle->listening.nelts; i++)
        {
            ls[i].open = 1;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
            if (ls[i].accept_filter)
            {
                ls[i].add_deferred = 1;
            }
#endif
#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
            if (ls[i].deferred_accept)
            {
                ls[i].add_deferred = 1;
            }
#endif
        }
    }
    /* open listening socket */
    if (ngx_open_listening_sockets(cycle) != NGX_OK)
    {
        goto failed;
    }

    if (!ngx_test_config)
    {//对监听套接字进行配置 主要是socket选项
        ngx_configure_listening_sockets(cycle);
    }

    /* commit the new cycle configuration */

    if (!ngx_use_stderr)
    {
        (void)ngx_log_redirect_stderr(cycle);
    }

    pool->log = cycle->log;
    /* initialize all modules 调用所有模块init_module方法*/
    if (ngx_init_modules(cycle) != NGX_OK)
    {
        /* fatal */
        exit(1);
    }

这部分功能是创建监听socket并且对socket进行基本设置。 那么cycle->listening.elts是在什么地方设置的呢?其实仔细思考一下,我们监听端口配置是写在nginx.conf配置文件,那么肯定是在解析配置文件时对其赋值,所以应该ngx_conf_parse。至此,针对ngx_cycle_t初始化基本主要内容介绍完毕。

我们回过头在来看一下conf_ctx赋值这部分代码,上面介绍过conf_ctx有两种赋值方式,一个是调用回调函数create_conf,另外一个是解析配置文件标签信息。通过ngx_core_module和ngx_event_module来举例说明这两种方式:

/**
 * create_conf回调函数 用于生成conf_ctx上下文
 */
static void *
ngx_core_module_create_conf(ngx_cycle_t *cycle)
{
    ngx_core_conf_t  *ccf;

    ccf = ngx_pcalloc(cycle->pool, sizeof(ngx_core_conf_t));
    if (ccf == NULL) {
        return NULL;
    }

    /*
     * set by ngx_pcalloc()
     *
     *     ccf->pid = NULL;
     *     ccf->oldpid = NULL;
     *     ccf->priority = 0;
     *     ccf->cpu_affinity_auto = 0;
     *     ccf->cpu_affinity_n = 0;
     *     ccf->cpu_affinity = NULL;
     */

    ccf->daemon = NGX_CONF_UNSET;
    ccf->master = NGX_CONF_UNSET;
    ccf->timer_resolution = NGX_CONF_UNSET_MSEC;
    ccf->shutdown_timeout = NGX_CONF_UNSET_MSEC;

    ccf->worker_processes = NGX_CONF_UNSET;
    ccf->debug_points = NGX_CONF_UNSET;

    ccf->rlimit_nofile = NGX_CONF_UNSET;
    ccf->rlimit_core = NGX_CONF_UNSET;

    ccf->user = (ngx_uid_t) NGX_CONF_UNSET_UINT;
    ccf->group = (ngx_gid_t) NGX_CONF_UNSET_UINT;

    if (ngx_array_init(&ccf->env, cycle->pool, 1, sizeof(ngx_str_t))
        != NGX_OK)
    {
        return NULL;
    }

    return ccf;
}
/**
 * 解析events标签,调用此函数。用于创建conf_ctx上下文
 * @param conf是就是cycle中conf_ctx元素地址
 */
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;

    if (*(void **)conf)
    {//表示上下文已经存在 直接返回
        return "is duplicate";
    }

    /* count the number of the event modules and set up their indices */
    /* 获取所有event模块数量并且设置他们的索引值ctx_index             */
    ngx_event_max_module = ngx_count_modules(cf->cycle, NGX_EVENT_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;

    for (i = 0; cf->cycle->modules[i]; i++)
    {
        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE)
        {
            continue;
        }

        m = cf->cycle->modules[i]->ctx;

        if (m->create_conf)
        {//创建子模块conf_ctx上下文
            (*ctx)[cf->cycle->modules[i]->ctx_index] =
                m->create_conf(cf->cycle);//创建子类型上下文
            if ((*ctx)[cf->cycle->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;

    rv = ngx_conf_parse(cf, NULL);

    *cf = pcf;

    if (rv != NGX_CONF_OK)
    {
        return rv;
    }
    //初始化NGX_EVENT_MODULE模块
    for (i = 0; cf->cycle->modules[i]; i++)
    {
        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE)
        {
            continue;
        }

        m = cf->cycle->modules[i]->ctx;

        if (m->init_conf)
        {
            rv = m->init_conf(cf->cycle,
                              (*ctx)[cf->cycle->modules[i]->ctx_index]);
            if (rv != NGX_CONF_OK)
            {
                return rv;
            }
        }
    }

    return NGX_CONF_OK;
}

四、总结

至此Nginx启动流程,关于ngx_cycle_t初始化介绍完毕,有些地方可能介绍不到位,请大家多多指点。后面介绍Nginx启动流程中关于master/worker模式

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值