Nginx源码分析-启动初始化过程(二)

转自http://blog.csdn.net/marcky/article/details/5993471

在Nginx启动初始化过程(一)中提到main函数会调用ngx_init_cycle()初始化一个全局cycle变量,本文就来看看这个ngx_init_cycle()函数究竟做了哪些初始化工作。ngx_cycle_t结构类型被定义在src/core/ngx_cycle.h文件中,多达23个成员变量(nginx-0.7.67),初次目睹这个结构类型的时候,最让我震惊的是成员变量void ****conf_ctx,想必大家都知道我为何而震惊了吧,也许仅仅只是我见识太少吧,呵呵。由于ngx_init_cycle()函数的代码多达近800行,绝对算大函数了(当然,我也相信还有更加变态的函数,将整个世界都写到一个函数中的情况也是有可能的),在此就挑一些相对关键的代码来看吧。


  1. ngx_timezone_update();  
  2. /* force localtime update with a new timezone */  
  3. tp = ngx_timeofday();  
  4. tp->sec = 0;  
  5. ngx_time_update();  

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


  1. pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log);  
  2. if (pool == NULL) {  
  3.     return NULL;  
  4. }  
  5. pool->log = log;  
  6. cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t));  
  7. if (cycle == NULL) {  
  8.     ngx_destroy_pool(pool);  
  9.     return NULL;  
  10. }  

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

 


 

 [cpp] view plaincopyprint?

  1. if (ngx_list_init(&cycle->open_files, pool, n, sizeof(ngx_open_file_t))  
  2.     != NGX_OK)  
  3. {  
  4.     ngx_destroy_pool(pool);  
  5.     return NULL;  
  6. }  
  7. if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t))  
  8.     != NGX_OK)  
  9. {  
  10.     ngx_destroy_pool(pool);  
  11.     return NULL;  
  12. }  

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


  1. cycle->listening.elts = ngx_pcalloc(pool, n * sizeof(ngx_listening_t));  
  2. if (cycle->listening.elts == NULL) {  
  3.     ngx_destroy_pool(pool);  
  4.     return NULL;  
  5. }  
  6. cycle->listening.nelts = 0;  
  7. cycle->listening.size = sizeof(ngx_listening_t);  
  8. cycle->listening.nalloc = n;  
  9. cycle->listening.pool = pool;  

 cycle中的listening字段是一个数组,在这里完成此数组的初始化,为该数组分配起n个存储ngx_listening_t元素的单元。这个数组将用于存储监听套接字。


 [cpp] view plaincopyprint?

  1. cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *));  
  2. if (cycle->conf_ctx == NULL) {  
  3.     ngx_destroy_pool(pool);  
  4.     return NULL;  
  5. }  
  6. for (i = 0; ngx_modules[i]; i++) {  
  7.     if (ngx_modules[i]->type != NGX_CORE_MODULE) {  
  8.         continue;  
  9.     }  
  10.     module = ngx_modules[i]->ctx;  
  11.     if (module->create_conf) {  
  12.         rv = module->create_conf(cycle);  
  13.         if (rv == NULL) {  
  14.             ngx_destroy_pool(pool);  
  15.             return NULL;  
  16.         }  
  17.         cycle->conf_ctx[ngx_modules[i]->index] = rv;  
  18.     }  
  19. }  

  首先将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()。


  1. if (ngx_conf_param(&conf) != NGX_CONF_OK) {  
  2.     environ = senv;  
  3.     ngx_destroy_cycle_pools(&conf);  
  4.     return NULL;  
  5. }  
  6. if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {  
  7.     environ = senv;  
  8.     ngx_destroy_cycle_pools(&conf);  
  9.     return NULL;  
  10. }  

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


  1. for (i = 0; ngx_modules[i]; i++) {  
  2.     if (ngx_modules[i]->type != NGX_CORE_MODULE) {  
  3.         continue;  
  4.     }  
  5.     module = ngx_modules[i]->ctx;  
  6.     if (module->init_conf) {  
  7.         if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index])  
  8.             == NGX_CONF_ERROR)  
  9.         {  
  10.             environ = senv;  
  11.             ngx_destroy_cycle_pools(&conf);  
  12.             return NULL;  
  13.         }  
  14.     }  
  15. }  

 前面完成了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回调函数。


  1.     /* open the new files */  
  2.     part = &cycle->open_files.part;  
  3.     file = part->elts;  
  4.     for (i = 0; /* void */ ; i++) {  
  5.         if (i >= part->nelts) {  
  6.             if (part->next == NULL) {  
  7.                 break;  
  8.             }  
  9.             part = part->next;  
  10.             file = part->elts;  
  11.             i = 0;  
  12.         }  
  13.         if (file[i].name.len == 0) {  
  14.             continue;  
  15.         }  
  16.         file[i].fd = ngx_open_file(file[i].name.data,  
  17.                                    NGX_FILE_APPEND,  
  18.                                    NGX_FILE_CREATE_OR_OPEN,  
  19.                                    NGX_FILE_DEFAULT_ACCESS);  
  20.         ngx_log_debug3(NGX_LOG_DEBUG_CORE, log, 0,  
  21.                        "log: %p %d /"%s/"",  
  22.                        &file[i], file[i].fd, file[i].name.data);  
  23.         if (file[i].fd == NGX_INVALID_FILE) {  
  24.             ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,  
  25.                           ngx_open_file_n " /"%s/" failed",  
  26.                           file[i].name.data);  
  27.             goto failed;  
  28.         }  
  29. #if !(NGX_WIN32)   
  30.         if (fcntl(file[i].fd, F_SETFD, FD_CLOEXEC) == -1) {  
  31.             ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,  
  32.                           "fcntl(FD_CLOEXEC) /"%s/" failed",  
  33.                           file[i].name.data);  
  34.             goto failed;  
  35.         }  
  36. #endif   
  37.     }  

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


  1. /* create shared memory */  
  2. part = &cycle->shared_memory.part;  
  3. shm_zone = part->elts;  
  4. for (i = 0; /* void */ ; i++) {  
  5.     if (i >= part->nelts) {  
  6.         if (part->next == NULL) {  
  7.             break;  
  8.         }  
  9.         part = part->next;  
  10.         shm_zone = part->elts;  
  11.         i = 0;  
  12.     }  
  13.     if (shm_zone[i].shm.size == 0) {  
  14.         ngx_log_error(NGX_LOG_EMERG, log, 0,  
  15.                       "zero size shared memory zone /"%V/"",  
  16.                       &shm_zone[i].shm.name);  
  17.         goto failed;  
  18.     }  
  19.     if (shm_zone[i].init == NULL) {  
  20.         /* unused shared zone */  
  21.         continue;  
  22.     }  
  23.     shm_zone[i].shm.log = cycle->log;  
  24.     opart = &old_cycle->shared_memory.part;  
  25.     oshm_zone = opart->elts;  
  26.     for (n = 0; /* void */ ; n++) {  
  27.         if (n >= opart->nelts) {  
  28.             if (opart->next == NULL) {  
  29.                 break;  
  30.             }  
  31.             opart = opart->next;  
  32.             oshm_zone = opart->elts;  
  33.             n = 0;  
  34.         }  
  35.         if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) {  
  36.             continue;  
  37.         }  
  38.         if (ngx_strncmp(shm_zone[i].shm.name.data,  
  39.                         oshm_zone[n].shm.name.data,  
  40.                         shm_zone[i].shm.name.len)  
  41.             != 0)  
  42.         {  
  43.             continue;  
  44.         }  
  45.         if (shm_zone[i].shm.size == oshm_zone[n].shm.size) {  
  46.             shm_zone[i].shm.addr = oshm_zone[n].shm.addr;  
  47.             if (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data)  
  48.                 != NGX_OK)  
  49.             {  
  50.                 goto failed;  
  51.             }  
  52.             goto shm_zone_found;  
  53.         }  
  54.         ngx_shm_free(&oshm_zone[n].shm);  
  55.         break;  
  56.     }  
  57.     if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) {  
  58.         goto failed;  
  59.     }  
  60.     if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) {  
  61.         goto failed;  
  62.     }  
  63.     if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) {  
  64.         goto failed;  
  65.     }  
  66. shm_zone_found:  
  67.     continue;  
  68. }  

 这部分复杂的代码就是为完成所有共享内存的创建, 既然复杂就不多说了,以后慢慢分析。


  1. if (ngx_open_listening_sockets(cycle) != NGX_OK) {  
  2.     goto failed;  
  3. }  
  4. if (!ngx_test_config) {  
  5.     ngx_configure_listening_sockets(cycle);  
  6. }  

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


  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_CORE_MODULE类型的模块在内的绝大多数模块都没有这个init回调函数。究竟哪些模块才使用这个回调接口呢?动用搜索功能,终于找到了一个模块使用了这个回调接口,它就是ngx_event_core_module。在此,就不纠结这个独特的初始化函数了,到分析事件驱动的时候,再回头看看。

posted @ 2013-03-04 14:53  only_eVonne  阅读(1299)  评论(0编辑  收藏  举报