nginx源码分析(1)——启动过程

http://blog.csdn.net/chosen0ne/article/details/7728294

1. 重要的数据结构

         1. ngx_module_t
        nginx中所有模块的类型都是ngx_module_t类型的,定义了模块的一些属性。nginx是完全模块化的,所有的组件都是模块,从而实现了nginx的高度松耦合。同时,我们在进行nginx模块开发时,也离不开这个数据结构。
[cpp]  view plain copy print ?
  1. struct ngx_module_s {  
  2.     /** 
  3.      * 在具体类型模块(http、event等)的全局配置结构数组的下标。以http module模块为例, 
  4.      * nginx把所有的http module的config信息存放在ngx_http_conf_ctx_t类型的变量中, 
  5.      * 这个变量只有3个属性,分别是所有http module的main、srv、loc的config信息的数组。 
  6.      * 如果该模块是http module,则ctx_index是该模块的config信息(main、srv、loc) 
  7.      * 在ngx_http_conf_ctx_t中的下标。 
  8.      */  
  9.     ngx_uint_t            ctx_index;  
  10.   
  11.     /** 
  12.      * nginx把所有模块(ngx_module_t)存放到ngx_modules数组中,这个数组在nginx源码路 
  13.      * 径的objs/ngx_modules.c中,是在运行configure脚本后生成的。index属性就是该模块 
  14.      * 在ngx_modules数组中的下标。同时nginx把所有的core module的配置结构存放到ngx_cycle的 
  15.      * conf_ctx数组中,index也是该模块的配置结构在ngx_cycle->conf_ctx数组中的下标。 
  16.      */  
  17.     ngx_uint_t            index;  
  18.   
  19.     ……  
  20.   
  21.     /** 
  22.      * 模块的上下文属性,同一类型的模块的属性是相同的,比如core module的ctx是ngx_core_module_t类型。 
  23.      * 而http module的ctx是ngx_http_moduel_t类型,event module的ctx是ngx_event_module_t类型等等。 
  24.      * 相应类型的模块由分开处理的,比如所有的http module由ngx_http_module解析处理,而所有的event module 
  25.      * 由ngx_events_module解析处理。 
  26.      */  
  27.     void                 *ctx;  
  28.   
  29.     /** 
  30.      * 该模块支持的指令的数组,最后以一个空指令结尾。ngx_commond_t的分析见下文。 
  31.      */  
  32.     ngx_command_t        *commands;  
  33.   
  34.     /** 
  35.      * 模块的类型,nginx所有的模块类型: 
  36.      *      NGX_CORE_MODULE 
  37.      *      NGX_CONF_MODULE 
  38.      *      NGX_HTTP_MODULE 
  39.      *      NGX_EVENT_MODULE 
  40.      *      NGX_MAIL_MODULE 
  41.      * 这些不同的类型也指定了不同的ctx。 
  42.      */  
  43.     ngx_uint_t            type;  
  44.   
  45.     /* 接下来都是一些回调函数,在nginx初始化过程的特定时间点调用 */  
  46.     ngx_int_t           (*init_master)(ngx_log_t *log);  
  47.   
  48.     /* 初始化完所有模块后调用,在ngx_int_cycle函数(ngx_cycle.c)中 */  
  49.     ngx_int_t           (*init_module)(ngx_cycle_t *cycle);  
  50.   
  51.     /* 初始化完worker进程后调用,在ngx_worker_process_init函数(ngx_process_cycle.c)中 */  
  52.     ngx_int_t           (*init_process)(ngx_cycle_t *cycle);  
  53.     ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);  
  54.     void                (*exit_thread)(ngx_cycle_t *cycle);  
  55.     void                (*exit_process)(ngx_cycle_t *cycle);  
  56.   
  57.     void                (*exit_master)(ngx_cycle_t *cycle);  
  58.     ……  
  59. };  
        下面的表格是type与ctx的对应关系:
模块类型(type) 上下文属性类型(ctx)
NGX_CORE_MODULE ngx_core_module_t(ngx_conf_file.h)
NGX_CONF_MODULE NULL
NGX_HTTP_MODULE ngx_http_module_t(http/ngx_http_config.h)
NGX_EVENT_MODULE ngx_event_module_t(event/ngx_event.h)
NGX_MAIL_MODULE ngx_mail_module_t(mail/ngx_mail.h)

         2. ngx_commond_t
        ngx_commond_t描述的是模块的配置指令,也就是出现在配置文件的指令。nginx模块支持多个配置指令,所以是以ngx_commond_t数组形式存储的。这个结构在配置文件解析和模块的配置结构信息初始化时会用到。
[cpp]  view plain copy print ?
  1. struct ngx_command_s {  
  2.     /** 
  3.      * 指令名,与配置文件中一致 
  4.      */  
  5.     ngx_str_t             name;  
  6.   
  7.     /** 
  8.      * 指令的类型,以及参数的个数。这个属性有两个作用: 
  9.      *  1. 实现只解析某个类型的指令,比如当前这个指令是event module类型的,而正在解析的是 
  10.      *     http module,所以会跳过所有不是http module类型的指令。 
  11.      *  2. 实现指令参数个数的校验。 
  12.      */  
  13.     ngx_uint_t            type;  
  14.   
  15.     /* 
  16.      * 回调函数,在解析配置文件时,遇到这个指令时调用。 
  17.      * cf: 包括配置参数信息cf->args(ngx_array_t类型),以及指令对应的模块上下文cf->ctx 
  18.      *      在解析不同模块的指令时,这个上下文信息不同。比如在解析core module时,cf->ctx 
  19.      *      是ngx_cycle->conf_ctx也就是所有core module的配置结构数组,而在解析http module 
  20.      *      时cf->ctx是ngx_http_conf_ctx_t类型的,其中包含所有http module的main、srv、loc 
  21.      *      的配置结构数组。 
  22.      * cmd: 指令对应的ngx_command_t结构。 
  23.      * conf:指令对应的模块的配置信息。 
  24.      */  
  25.     char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);  
  26.   
  27.     /** 
  28.      * 对http module有效,http module的配置结构信息(main、srv、loc)都存放在ngx_http_conf_ctx_t 
  29.      * 中对应的数组,conf属性指示这个指令的配置结构是main、srv还是loc。 
  30.      */  
  31.     ngx_uint_t            conf;  
  32.   
  33.     /** 
  34.      * 指令对应属性在模块配置结构中的偏移量。 
  35.      */  
  36.     ngx_uint_t            offset;  
  37.       
  38.     /** 
  39.      * 一般是函数指针,在set回调函数中调用。 
  40.      */  
  41.     void                 *post;  
  42. };  
        
         3. ngx_cycle_t
        ngx_cycle_t是nginx中最重要的数据结构,包含了全局的配置信息、所有监听的套接字、连接池、读写事件等。ngx_cycle_t相当于nginx的一个生命周期,从nginx启动后直到向nginx发送stop或者reload信号。nginx中有一个全局变量ngx_cycle指向当前的cycle。
[cpp]  view plain copy print ?
  1. struct ngx_cycle_s {  
  2.     /** 
  3.      * 所有core module的配置结构信息的数组,大小是ngx_max_module。配置结构信息由 
  4.      * ngx_core_module_t的create_config回调函数生成,并有init_config回调函数初始化。 
  5.      * module的index属性指示了该模块的配置结构信息在conf_ctx数组中的下标。 
  6.      */  
  7.     void                  ****conf_ctx;  
  8.   
  9.     /** 
  10.      * 随ngx_cycle_t同生命周期的内存池,是生命周期最长的,除非nginx关闭或重启。内存块大小 
  11.      * 由NGX_CYCLE_POOL_SIZE定义,是16KB。 
  12.      */  
  13.     ngx_pool_t               *pool;  
  14.   
  15.     ngx_log_t                *log;                      // 日志  
  16.     ngx_log_t                 new_log;  
  17.   
  18.     ngx_connection_t        **files;                        // 链接文件  
  19.     ngx_connection_t         *free_connections;                     // 空闲链接数组  
  20.     ngx_uint_t                free_connection_n;                    // 空闲链接个数  
  21.   
  22.     ngx_queue_t               reusable_connections_queue;   // 重用链接队列  
  23.   
  24.     /** 
  25.      * 监听套接字的数组,类型是ngx_listening_t,ngx_create_listening函数会向这个数组 
  26.      * 添加新的监听句柄。 
  27.      */  
  28.     ngx_array_t               listening;  
  29.     ngx_array_t               pathes;                       // 路径数组,ngx_path_t  
  30.   
  31.     /** 
  32.      * 所有打开的文件描述符的链表,ngx_open_file_t类型。ngx_conf_file.c中的 
  33.      *      ngx_conf_open_file(ngx_cycyle_t *cycle, ngx_str_t *name) 
  34.      * 可以返回名字为name的打开的文件描述符,如果对应的描述符不存在,则打开并存入open_files中。 
  35.      * 比如:logs/error.log的文件描述符就在其中。 
  36.      */  
  37.     ngx_list_t                open_files;  
  38.   
  39.     /** 
  40.      * 共享内存的链表,ngx_shm_zone_t类型。nginx会把需要进程共享的数据放在共享内存中。 
  41.      * 比如,accept锁就放在这里。worker进程只有获取这个锁之后,才能accept到新的连接。 
  42.      * 防止惊群。 
  43.      */  
  44.     ngx_list_t                shared_memory;  
  45.   
  46.     ngx_uint_t                connection_n;                 // 链接个数  
  47.     ngx_uint_t                files_n;                      // 打开文件个数  
  48.   
  49.     /** 
  50.      * 连接池,nginx把连接池组织成综合数组和链表特性的一种数据结构,后文会详细介绍。 
  51.      */  
  52.     ngx_connection_t         *connections;  
  53.     ngx_event_t              *read_events;                  // 读事件  
  54.     ngx_event_t              *write_events;                 // 写事件  
  55.   
  56.     ngx_cycle_t              *old_cycle;  
  57.   
  58.     ngx_str_t                 conf_file;                    // 配置文件名  
  59.     ngx_str_t                 conf_param;                   // 由命令行-g提供配置参数  
  60.     ngx_str_t                 conf_prefix;                  // 配置前缀  
  61.     ngx_str_t                 prefix;                       // nginx所在路径  
  62.     ngx_str_t                 lock_file;  
  63.     ngx_str_t                 hostname;                     // 主机名  
  64. };  


2. nginx启动过程

        nginx启动显然是由main函数驱动的,在core/nginx.c文件中,整个启动过程主要是初始化ngx_cycle_t类型的全局变量ngx_cycle。下面分析按照nginx的主要执行流程讲述的,首先从main函数开始。

        (1) main函数

[cpp]  view plain copy print ?
  1. if (ngx_strerror_init() != NGX_OK) {  
  2.     return 1;  
  3. }  
        初始化错误提示列表,以errno为下标,元素就是对应的错误提示信息。

[cpp]  view plain copy print ?
  1. if (ngx_get_options(argc, argv) != NGX_OK) {  
  2.     return 1;  
  3. }  
        获取命令行参数,比如-t、-c、g等。这里没有使用linux的getopt,主要是为了跨平台。

[cpp]  view plain copy print ?
  1. ngx_time_init();  
  2.   
  3. (NGX_PCRE)  
  4. ngx_regex_init();  
  5. if  
  6.   
  7. ngx_pid = ngx_getpid();  
  8.   
  9. // 初始化log  
  10. log = ngx_log_init(ngx_prefix);  
  11. if (log == NULL) {  
  12.     return 1;  
  13. }  
        上面几行代码主要完成时间、正则表达式和log的初始化。

[cpp]  view plain copy print ?
  1. /* 
  2.  * init_cycle->log is required for signal handlers and 
  3.  * ngx_process_options() 
  4.  */  
  5.   
  6. ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));  
  7. init_cycle.log = log;  
  8. ngx_cycle = &init_cycle;  
  9.   
  10. // cycle的内存池以1024大小的内存块  
  11. init_cycle.pool = ngx_create_pool(1024, log);  
  12. if (init_cycle.pool == NULL) {  
  13.     return 1;  
  14. }  
  15.   
  16. if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {  
  17.     return 1;  
  18. }  
        初始化cycle结构,并创建内存块大小为1024的内存池,ngx_save_argv会将命令行参数保存到ngx_os_argv,ngx_argc和ngx_argv,以便于之后master进程完成热代码替换。

[cpp]  view plain copy print ?
  1. if (ngx_process_options(&init_cycle) != NGX_OK) {  
  2.     return 1;  
  3. }  
        处理配置文件相关信息,初始化cycle的conf_prefix(配置文件所在路径的前缀)、prefix(nginx可执行文件所在路径)、conf_file(配置文件名)和conf_param(通过命令行-g选项指定的全局配置信息)。

[cpp]  view plain copy print ?
  1. if (ngx_os_init(log) != NGX_OK) {  
  2.     return 1;  
  3. }  
        处理操作系统的相关信息,比如页大小ngx_pagesize等。

[cpp]  view plain copy print ?
  1. if (ngx_crc32_table_init() != NGX_OK) {  
  2.     return 1;  
  3. }  
        初始化循环冗余检验表。

[cpp]  view plain copy print ?
  1. if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {  
  2.     return 1;  
  3. }  
        通过环境变量NGINX完成socket的继承,这些继承来的socket会放到init_cycyle的listening数组中。

[cpp]  view plain copy print ?
  1. ngx_max_module = 0;  
  2. for (i = 0; ngx_modules[i]; i++) {  
  3.     ngx_modules[i]->index = ngx_max_module++;  
  4. }  
        这段代码是对所有nginx的模块计数,同时更新模块的index属性。ngx_max_module就是所有模块的总数。关于index的解释见上文对ngx_module_t的解释。

[cpp]  view plain copy print ?
  1. cycle = ngx_init_cycle(&init_cycle);  
        这行代码是nginx启动过程中最重要的一个步骤,用于初始化ngx_cycle。下面就进入ngx_init_cycle看看具体cycle的初始化过程。ngx_init_cycle代码有800多行,这里只讲解大概的流程,细节不在展开。

        (2) ngx_init_cycle函数

        在ngx_cycle_t结构中包含各种ngx_list_t、ngx_array_t等类型的属性,ngx_init_cycle的前面200多行代码都是对这些数据结构进行初始化。主要有
                1. 更新时区和时间。
                2. 创建内存池。
                3. 根据old_cycle初始化cycle中的conf_file、conf_prefix、prefix和conf_param。
                4. 初始化pathes(ngx_array_t)。
                5. 初始化openfiles(ngx_list_t)。
                6. 初始化shared_memory(ngx_list_t)。
                7. 初始化listening(ngx_array_t)。
                8. 初始化conf_ctx(void ****)数组,大小是ngx_max_module,用于存储所有core module的配置结构信息。
                9. 调用系统调用gethostname获取主机名,初始化hostname。
        介绍完ngx_init_cycle前面的大部分初始化工作后,下面看一下比较重要的初始化步骤。

[cpp]  view plain copy print ?
  1. for (i = 0; ngx_modules[i]; i++) {  
  2.     if (ngx_modules[i]->type != NGX_CORE_MODULE) {  
  3.         continue;  
  4.     }  
  5.   
  6.     module = ngx_modules[i]->ctx;  
  7.   
  8.     if (module->create_conf) {  
  9.         rv = module->create_conf(cycle);  
  10.         if (rv == NULL) {  
  11.             ngx_destroy_pool(pool);  
  12.             return NULL;  
  13.         }  
  14.         cycle->conf_ctx[ngx_modules[i]->index] = rv;  
  15.     }  
  16. }  
        这段代码的逻辑是调用所有core module的create_conf回调函数创建该core module的配置信息结构,并且更新cycle->conf_ctx数组。nginx的core module主要有:
                ngx_core_module(core/nginx.c)
                ngx_http_module(http/ngx_http.c)
                ngx_events_module(event/ngx_event.c)
                ngx_errlog_module(core/ngx_log.c)
                ngx_mail_module(mail/ngx_mail.c)
                ngx_openssl_module(event/ngx_event_openssl.c)
                ngx_google_perftools_module(misc/ngx_google_perftools_module.c)
        只有ngx_core_module和ngx_google_perftools_module两个模块有定义create_conf,而ngx_google_perftools_module仅用于性能测试,所以真正使用时只有ngx_core_module有create_conf回调函数。这个会调用函数会创建ngx_core_conf_t结构,用于存储整个配置文件main scope范围内的信息,比如worker_processes,worker_cpu_affinity等。

[cpp]  view plain copy print ?
  1. ngx_memzero(&conf, sizeof(ngx_conf_t));  
  2. /* STUB: init array ? */  
  3. conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t));  // args是指令后带的参数的数组  
  4. if (conf.args == NULL) {  
  5.     ngx_destroy_pool(pool);  
  6.     return NULL;  
  7. }  
  8.   
  9. conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);  
  10. if (conf.temp_pool == NULL) {  
  11.     ngx_destroy_pool(pool);  
  12.     return NULL;  
  13. }  
  14.   
  15. /* 解析core module对应的指令,此时conf的上下文是cycle->conf_ctx,就是所有core module的config信息 */  
  16. conf.ctx = cycle->conf_ctx;  
  17. conf.cycle = cycle;  
  18. conf.pool = pool;  
  19. conf.log = log;  
  20. conf.module_type = NGX_CORE_MODULE;     // conf.module_type指示将要解析这个类型模块的指令。  
  21. conf.cmd_type = NGX_MAIN_CONF;      // conf.cmd_type指示将要解析的指令的类型。  
        这段代码是为解析配置文件做准备。初始化ngx_conf_t,用于解析配置文件并保存解析出来的信息。args是配置文件中指令的信息的数组,args[0]是指令名,args[1] - args[n]是指令的参数,参数个数需要根据ngx_commond_t的type属性做校验。ngx_conf_t中的module_type和cmd_type用于控制解析什么类型的指令,module_type表示只解析该类型模块包含的指令,cmd_type表示将要解析的指令的类型,也就是说只有符合module_type和cmd_type的指令才会被解析。比如module_type取NGX_HTTP_MODULE,而cmd_type取NGX_HTTP_SRV_CONF,那么在一次配置文件解析中,只会对http module的server块的指令进行解析。

[plain]  view plain copy print ?
  1. if (ngx_conf_param(&conf) != NGX_CONF_OK) {  
  2.     environ = senv;  
  3.     ngx_destroy_cycle_pools(&conf);  
  4.     return NULL;  
  5. }  
        对通过nginx -g xxx 设置的全局配置指令初始化、解析。

[cpp]  view plain copy print ?
  1. if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {  
  2.     environ = senv;  
  3.     ngx_destroy_cycle_pools(&conf);  
  4.     return NULL;  
  5. }  
        解析配置文件。配置文件的解析类似一棵树的遍历,nginx中的指令分为块指令和普通指令,每个块指令对应一棵子树,比如http块和event块。由这些块指令负责调用ngx_conf_parse函数解析块内部的指令。配置文件的具体分析会另开一片文章,这里忽略这些细节。在ngx_conf_parse函数返回后,整个配置文件解析完毕,所有模块的指令已经初始化,也就意味着所有模块基本上都初始化完,实际上ngx_conf_parse函数后面隐藏了大量的信息,包括http模块的初始化和事件模块的初始化。关于http的初始化我们后面再详细描述,这里接着讲述ngx_init_cycle。

[cpp]  view plain copy print ?
  1. for (i = 0; ngx_modules[i]; i++) {  
  2.     if (ngx_modules[i]->type != NGX_CORE_MODULE) {  
  3.         continue;  
  4.     }  
  5.   
  6.     module = ngx_modules[i]->ctx;  
  7.   
  8.     if (module->init_conf) {  
  9.         if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index])  
  10.             == NGX_CONF_ERROR)  
  11.         {  
  12.             environ = senv;  
  13.             ngx_destroy_cycle_pools(&conf);  
  14.             return NULL;  
  15.         }  
  16.     }  
  17. }  
        初始化所有core module模块的config结构调用ngx_core_module_t的init_conf 。在所有core module中,只有ngx_core_module有init_conf回调,用于对ngx_core_conf_t中没有配置的字段设置默认值。

[cpp]  view plain copy print ?
  1. if (ngx_test_config) {  
  2.     if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {  
  3.         goto failed;  
  4.     }  
  5. else if (!ngx_is_init_cycle(old_cycle)) {  
  6.     old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx,  
  7.                                                ngx_core_module);  
  8.     if (ccf->pid.len != old_ccf->pid.len  
  9.         || ngx_strcmp(ccf->pid.data, old_ccf->pid.data) != 0)  
  10.     {  
  11.         if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) {  
  12.             goto failed;  
  13.         }  
  14.         ngx_delete_pidfile(old_cycle);  
  15.     }  
  16. }  
        这段代码很简单就是创建nginx的pid文件。随后的很长一段代码用于创建所有的文件路径、打开文件描述符以及创建共享内存,这里就不贴代码了。接下来的一段代码是用于处理监听socket的,如果监听地址相同的话,则把新、旧cycle的监听socket合并。

[cpp]  view plain copy print ?
  1. if (ngx_open_listening_sockets(cycle) != NGX_OK) {  
  2.     goto failed;  
  3. }  
         ngx_open_listening_sockets函数的功能就同它名字一样,用于打开所有的监听socket,具体过程和用socket编程时是一样的,调用socket创建套接字 -> 调用setsockopt设置成可重用socket -> 设置成非阻塞socket -> 调用bind绑定要监听的socket地址 -> 调用listen转化成监听socket。

[cpp]  view plain copy print ?
  1. if (!ngx_test_config) {  
  2.     ngx_configure_listening_sockets(cycle);  
  3. }  
        根据cycle配置所有的监听socket,包括设置监听socket的接收缓冲区大小、发送缓冲区大小以及accept filter等。

[cpp]  view plain copy print ?
  1. for (i = 0; ngx_modules[i]; i++) {  
  2.     if (ngx_modules[i]->init_module) {  
  3.         if (ngx_modules[i]->init_module(cycle) != NGX_OK) {  
  4.             /* fatal */  
  5.             exit(1);  
  6.         }  
  7.     }  
  8. }  
        调用所有模块的init_module回调函数,进行模块的初始化动作。ngx_init_cycle最后部分代码主要就是释放多余的资源,包括关闭共享内存、监听socket已经打开的文件等,然后ngx_init_cycle正常返回,接下来继续看main函数。

        (3) main函数

[cpp]  view plain copy print ?
  1. if (ngx_init_signals(cycle->log) != NGX_OK) {  
  2.     return 1;  
  3. }  
        注册信号,这些信号是定义在os/unix/ngx_process.c中的signals数组,元素的类型是ngx_signal_t,这个结构定义了信号的值、名字已经信号处理函数。

[cpp]  view plain copy print ?
  1. if (!ngx_inherited && ccf->daemon) {  
  2.     if (ngx_daemon(cycle->log) != NGX_OK) {  
  3.         return 1;  
  4.     }  
  5.   
  6.     ngx_daemonized = 1;  
  7. }  
        这段代码很明显是实现守护进程的。

[cpp]  view plain copy print ?
  1. if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {  
  2.     return 1;  
  3. }  
        接下来是创建nginx的pid文件。main函数的最后是关于进程模型的初始化的。

[cpp]  view plain copy print ?
  1. if (ngx_process == NGX_PROCESS_SINGLE) {  
  2.     ngx_single_process_cycle(cycle);  
  3.   
  4. else {  
  5.     ngx_master_process_cycle(cycle);  
  6. }  
        可以看出,这里分为单进程和多进程模式,大部分情况我们都采用多进程模式。这部分内容会在另一篇专门讲述nginx进程模型的文章中再详细介绍。介绍完整个nginx的启动过程,可以总结出启动过程就是一堆的初始化工作,其中最重要的是配置文件解析的过程,在一个ngx_conf_parse函数背后隐藏了大量的秘密,下一篇源码分析就会介绍http模块的启动流程,这样大家就可以了解一个具体模块是怎么初始化的的。

你可能感兴趣的:(nginx源码分析(1)——启动过程)