nginx源码分析(10)-启动过程分析

来源:互联网 发布:centos7删除ssh22端口 编辑:程序博客网 时间:2024/05/01 05:43

nginx有两个重要头文件:ngx_config.h和ngx_core.h。

src/core/ngx_config.h文件中包含的是和操作系统相关的宏定义和头文件,其中又会包含objs/ngx_auto_headers.h和src/os/unix/ngx_<os name>_config.h,前面提到过,这个头文件是自动脚本检验操作系统后生成的,这个头文件中包含了一些宏定义,这些宏定义说明了存在哪些与特定操作系统有关的头文件,并依此判断出操作系统的种类(linux、freebsd、solaris、darwin等),根据判断出的操作系统种类,ngx_config.h包含具体的与操作系统有关的头文件src/os/unix/ngx_<os name>_config.h,这个头文件中包含几乎所有需要的系统头文件,并包含了objs/ngx_auto_config.h,这是自动脚本生成的另一个头文件,其中包含一些宏定义,这些宏定义说明当前操作系统中存在的特性,根据这些特性又判断出可以支持的nginx内嵌模块情况,比如rewrite模块需要pcre的支持等。

src/core/ngx_core.h文件中包含的是nginx中几乎所有实现代码的头文件,包含这个头文件,就可以访问nginx中的所有数据结构和函数接口。

几乎所有的模块都包含了ngx_config.h和ngx_core.h。所以,在nginx的任何实现代码中,可以直接使用很多操作系统的接口和nginx实现的接口。

纵观整个nginx的代码,可以大致分为三块,一块是一些重要的数据结构及其操作接口,代码主要在src/core和src/os/unix目录下;第二块是模块的实现,四个种类几十个模块的实现分散在src/core、src/event、src/http、src/mail目录下;第三块是启动过程的代码,上一篇也大致列了一下启动调用的函数序列,其代码分布在src/core和src/os/unix目录下。

0.8.9版本nginx的核心类模块有7个,event类模块有10个,http类模块有47个,mail类模块有7个。另外还有一个模块是没有上下文的,是conf模块,所以准确的说nginx的模块有五种:

core/ngx_conf_file.h:#define NGX_CORE_MODULE      0x45524F43  /* "CORE" */
core/ngx_conf_file.h:#define NGX_CONF_MODULE      0x464E4F43  /* "CONF" */
event/ngx_event.h:#define NGX_EVENT_MODULE      0x544E5645  /* "EVNT" */
http/ngx_http_config.h:#define NGX_HTTP_MODULE           0x50545448   /* "HTTP" */
mail/ngx_mail.h:#define NGX_MAIL_MODULE         0x4C49414D     /* "MAIL" */

上一篇简单的走了一遍nginx的启动流程,列了一下函数调用序列,流程中最重要的是三个函数:main(src/core/nginx.c)->ngx_init_cycle(src/core/ngx_cycle.c)->ngx_master_process_cycle(src/os/unix/ngx_process_cycle.c)。

下面会更深入细致的分析一下启动流程,重点围绕上述三个函数进行。

nginx的启动过程是从设置一个变量开始的:ngx_cycle_t *ngx_cycle(src/core/ngx_cycle.c),ngx_cycle_t是一个重要的数据结构,它是一个很重要的容器,保存了一些全局性质的数据,在整个系统内都会被使用到。

struct ngx_cycle_s {
    void                  ****conf_ctx;
    ngx_pool_t               *pool;

    ngx_log_t                *log;
    ngx_log_t                 new_log;

    ngx_connection_t        **files;
    ngx_connection_t         *free_connections;
    ngx_uint_t                free_connection_n;

    ngx_array_t               listening;
    ngx_array_t               pathes;
    ngx_list_t                open_files;
    ngx_list_t                shared_memory;

    ngx_uint_t                connection_n;
    ngx_uint_t                files_n;

    ngx_connection_t         *connections;
    ngx_event_t              *read_events;
    ngx_event_t              *write_events;

    ngx_cycle_t              *old_cycle;

    ngx_str_t                 conf_file;
    ngx_str_t                 conf_param;
    ngx_str_t                 conf_prefix;
    ngx_str_t                 prefix;
    ngx_str_t                 lock_file;
    ngx_str_t                 hostname;
};(见src/core/ngx_cycle.h)

下面从main开始详细分析启动流程中处理过程,在遇到设置ngx_cycle_t字段的时候再详细解释这个字段。

main函数的处理过程可以分为以下步骤:

1、从控制台获取参数并处理:ngx_get_options(argc, argv);

2、简单初始化,初始化一些数据结构和模块:ngx_debug_init(),ngx_time_init(),ngx_regex_init(),ngx_log_init(),ngx_ssl_init();

3、初始化局部的ngx_cycle_t init_cycle结构体变量:
    ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
    init_cycle.log = log;
    ngx_cycle = &init_cycle;

    init_cycle.pool = ngx_create_pool(1024, log);
    if (init_cycle.pool == NULL) {
        return 1;
    }    

4、保存参数,设置几个全局变量:ngx_argc,ngx_os_argv,ngx_argv,ngx_os_environ;

5、调用ngx_process_options,设置init_cycle的一些字段,这些字段是从控制台的命令中取得的:conf_prefix(config prefix path)、prefix(prefix path:-p prefix)、conf_file(配置文件路径:-c filenname)、conf_param(-g directives),另外还把init_cycle.log.log_level设置为NGX_LOG_INFO;

6、调用ngx_os_init,这个调用会设置一些全局变量,这些全局变量和操作系统相关,比如:ngx_pagesize,ngx_cacheline_size,ngx_ncpu,ngx_cpuinfo(),ngx_max_sockets等;

7、调用初始化函数ngx_crc32_table_init();

8、调用ngx_set_inherited_sockets(&init_cycle),初始化init_cycle.listening,这是一个ngx_listening_t的结构数组,其socket_fd是从环境变量NGINX中读取的;

9、对系统所有模块点一下数,然后进入ngx_init_cycle作主要的模块相关的初始化,init_cycle作为旧的全局设置传进去,这个函数会创建一下新的ngx_cycle_t变量,并返回其指针:
    ngx_max_module = 0;
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = ngx_max_module++;
    }    

    cycle = ngx_init_cycle(&init_cycle);

10、与信号量相关的一些操作代码;

11、多进程的情况下,调用ngx_master_process_cycle(cycle),单进程情况下调用ngx_single_process_cycle完成最后的启动工作。

ngx_init_cycle函数的处理过程分为以下步骤:

1、调用ngx_timezone_update()、ngx_timeofday()和ngx_time_update(0, 0)做时间校准,nginx的时间以及计数器相关的内容以后作专题介绍吧;

2、创建一个新的ngx_cycle_t变量cycle,并且初始化其大部分的成员字段,有一些是从传入的old_cycle直接拷贝过来的,这些字段包括:log,conf_prefix,prefix,conf_file,conf_param;还有一些字段会判断一下old_cycle中是否存在,如果存在,则取得这些字段的占用空间,在cycle中申请等大的空间,并初始化(不拷贝),否则就申请默认大小的空间,这些字段有:pathes,open_files,share_memory,listening;还有一些字段是重新创建或者第一次赋值的:pool,new_log.log_level(=NGX_LOG_ERR),old_cycle(=old_cycle),hostname(gethostname);
最重要的一个字段是conf_ctx,它被初始化为ngx_max_module个void *指针,这预示着conf_ctx是所有模块的配置结构的指针数组;

3、调用所有核心类模块的钩子create_conf,并把返回的配置结构指针放到conf_ctx数组中,偏移位置为ngx_module_t.index;

4、从命令行和配置文件中把所有配置更新到cycle的conf_ctx中,首先调用ngx_conf_param把命令行中的指令(-g directives)转换为配置结构并把指针加入到cycle.conf_ctx中,接着调用ngx_conf_parse(..,filename)把配置文件中的指令转换为配置结构并把指针加入到cycle.conf_ctx中。

ngx_conf_param最后也会调用ngx_conf_parse(..,NULL),所以配置的更新主要是在ngx_conf_parse中进行的,这个函数中有一个for循环,每次循环调用ngx_conf_read_token取得一个配置指令(以;结尾),然后调用ngx_conf_handler处理这条指令,ngx_conf_handler每次都会遍历所有模块的指令集,查找这条配置指令并分析其合法性,如果指令正确,则会创建配置结构并把指针加入到cycle.conf_ctx中,配置结构的赋值是调用该指令的钩子set完成的。

遍历指令集的过程首先是遍历所有的核心类模块,若是event类的指令,则会遍历到ngx_events_module,这个模块是属于核心类的,其钩子set又会嵌套调用ngx_conf_parse去遍历所有的event类模块,同样的,若是http类指令,则会遍历到ngx_http_module,该模块的钩子set进一步遍历所有的http类模块,mail类指令会遍历到ngx_mail_module,该模块的钩子进一步遍历到所有的mail类模块。要特别注意的是:这三个遍历过程中会在适当的时机调用event类模块、http类模块和mail类模块的创建配置和初始化配置的钩子。从这里可以看出,event、http、mail三类模块的钩子是配置中的指令驱动的;

5、调用所有核心类模块的钩子init_conf,把模块的配置结构作为一个参数传入:init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index);

6、获得核心模块ngx_core_dodule的配置结构,然后调用ngx_create_pidfile创建pid文件。获取配置结构的代码:ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module),这里的ngx_get_conf是一个宏定义:#define ngx_get_conf(conf_ctx, module)  conf_ctx[module.index];

7、调用ngx_test_lockfile(filename,log),ngx_create_pathes(cycle,user),接着打开errorlog文件并赋值给cycle->new_log.file:cycle->new_log.file = ngx_conf_open_file(cycle, &error_log);

8、打开新文件,在第2步的时候提到cycle->open_files这个链表是空的,只是给它预先分配了空间,并没有数据,这里之所以可能会有文件被打开,估计是前面读配置文件的时候,调用各个钩子的过程中,填充了这个链表,把ngx_open_file_t结构变量填充进来(结构体中包含要打开文件的路径信息),这是我猜测的,之后再验证:)接着修改一下cycle的成员:cycle->log = &cycle->new_log;pool->log = &cycle->new_log;

9、创建共享内存,和open_files类似,在第2步的时候cycle->share_memory也初始化为一个空的链表,也是预分配了空间,如果此时链表中已经被填充了ngx_shm_zone_t结构变量(其中包含需要共享内存的尺寸和标识等信息),那么这里就会分配共享内存,并且调用合适的初始化钩子初始化分配的共享内存,每块共享内存都会有name标识,这里也会做一些排重,已经分配的就不会再去分配,从对open_files和share_memory的处理过程可以看出,nginx在资源管理上是集中分配的,请求资源的时候分配说明性的结构变量,然后在恰当的时机才去真正分配资源;

10、处理listening sockets,cycle->listening是ngx_listening_t结构的数组,把cycle->listening于old_cycle->listening进行比较,设置cycle->listening的一些状态信息,接着调用ngx_open_listening_sockets(cycle)启动cycle->listening中的所有监听socket,循环调用socket,bind,listen完成服务端监听socket的启动。接着调用ngx_configure_listening_sockets(cycle)配置监听socket,会根据ngx_listening_t中的状态信息设置socket的读写缓存和TCP_DEFER_ACCEPT;

11、调用所有模块的钩子init_module;

12、关闭或者删除一些残留在old_cycle中的资源,首先释放不用的共享内存,接着关闭不使用的监听socket,再关闭不使用的打开文件,最后把old_cycle放入ngx_old_cycles中,这是一个ngx_cycle_t *的数组,最后设定一个定时器,定期回调ngx_cleaner_event清理ngx_old_cycles,这里设置了30000ms清理一次。

ngx_master_process_cycle的分析留待下一篇,这个函数会启动工作进程干活,并且会处理信号量,处理的过程中会杀死或者创建新的进程。

原创粉丝点击