• NGINX 启动流程概览


    Nginx启动流程概览

    一、Init Cycle

    1 ngx_cycle结构:

      • ngx_cycle是nginx全局配置,类型为ngx_cycle_t,其结构如下(已精简):
    struct ngx_cycle_s {                                                                                                       
        void                  ****conf_ctx;   //全局配置项
        ngx_pool_t               *pool;                                                                
        ngx_log_t                *log;                                                                                                  
            
        ngx_connection_t        **files;
        ngx_connection_t         *free_connections;
            
        ngx_module_t            **modules;    //module数组                                                                                                          
        ngx_queue_t               reusable_connections_queue;  //重复使用的socket存放队列
        ngx_uint_t                reusable_connections_n;                                                                                                                                                                                             
        ngx_array_t               listening;                                                                                            
            
        ngx_rbtree_t              config_dump_rbtree;
        ngx_rbtree_node_t         config_dump_sentinel;
            
        ngx_connection_t         *connections;
        ngx_event_t              *read_events;
        ngx_event_t              *write_events;
        ... ...
    };
    

    1.1 看到ngx_cycle的conf_ctx的指针的层次可知,这个结构将会很复杂,不过复杂性都体现在HTTP类型module中,以下用HTTP类型模块说明:

    • 第一层,指针类型为ngx_http_conf_ctx_t*,包含main、Svr、Loc三种作用域的conf;
    typedef struct {
        void        **main_conf;
        void        **srv_conf;
        void        **loc_conf;
    } ngx_http_conf_ctx_t;
    
    • 第二层,因http_module有多个,所以为三种作用域的conf数组;
    • 第三层指针指向具体某个http_module不同作用域的配置,类型分别为:
      • 1 ngx_http_core_main_conf_t,Main作用域的配置是全局的,包含监听的sever以及http阶段解析相关配置;
    typedef struct {                     
        ngx_array_t                servers;         /* ngx_http_core_srv_conf_t */
        ngx_http_phase_engine_t    phase_engine;
        ... ...
        ngx_array_t               *ports;
        ngx_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];
    } ngx_http_core_main_conf_t;
    
      • 2 ngx_http_core_srv_conf_t,Svr作用域的配置对应于conf文件的sever块,server可能有多个,每个server下location也可以有多个,所以看到当前结构内部有指向Location的两级指针;
    typedef struct {
        /* array of the ngx_http_server_name_t, "server_name" directive */
        ngx_array_t                 server_names;
    
        /* server ctx */                  
        ngx_http_conf_ctx_t        *ctx; 
        ... ...
        ngx_http_core_loc_conf_t  **named_locations;
    } ngx_http_core_srv_conf_t;
    
      • 3 ngx_http_core_loc_conf_t location配置结构太过复杂,以下只列举几项;
    struct ngx_http_core_loc_conf_s {
        ngx_str_t     name;          /* location name */
        ... ...
        /* pointer to the modules' loc_conf */
        void        **loc_conf;
        ngx_http_handler_pt  handler;
        ... ...
    };
    
    • 最后一层指针便能访问具体的特定Http_module模块指定作用域下的特定配置项。

    1.2 不过实际使用中没那么麻烦,有专门的宏,只需指定模块指针便能取得需要的各种配置:

    ```c
    #define ngx_http_conf_get_module_main_conf(cf, module)                                            
        ((ngx_http_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
    #define ngx_http_conf_get_module_srv_conf(cf, module)                                             
        ((ngx_http_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
    #define ngx_http_conf_get_module_loc_conf(cf, module)                                             
        ((ngx_http_conf_ctx_t *) cf->ctx)->loc_conf[module.ctx_index] 
    ```
    

    2 ngx_cycle Initial

    2.1 配置解析

    • 由于ngx_cycle结构很复杂,其初始化过程也很冗长,其复杂性主要体现在配置解析阶段,在该阶段从ngx_conf_parse函数进入,但在解析配置文件过程中会多次递归调用该函数;举例说明:
    worker_processes  1;            
    events {
        worker_connections  1024;  
    }   
        
    http {
        include       mime.types;  
        default_type  application/octet-stream;
        ... ...
    
    • 1 文件解析到worker_processes时对各模块的commands扫描一遍,找到该命令,调用对应的set函数,当前配置项,只需设置一项全局数值;
    • 2 当解析到events 遇到“{”符号,会触发对events对应set函数的调用;调用ngx_events_block函数,因为该命令为event core module的,所以其起到所有event module的代理作用,将会触发所有event module的create_conf函数和init_conf函数,大致流程如下:
    for (i = 0; cf->cycle->modules[i]; i++) {
        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {                                     
            continue;                                                                              
        }                                                                                          
        (*ctx)[cf->cycle->modules[i]->ctx_index] = m->create_conf(cf->cycle);                                            
    }                                                                                              
        
    rv = ngx_conf_parse(cf, NULL); 
    
    for (i = 0; cf->cycle->modules[i]; i++) {
        if (cf->cycle->modules[i]->type != NGX_EVENT_MODULE) {                                     
            continue;                                                                              
        }                                                                                          
        rv = m->init_conf(cf->cycle, (*ctx)[cf->cycle->modules[i]->ctx_index]);                                
    }                                                                                          
    
    • 3 由上流程中也可以看出,ngx_conf_parse将会被调用数次。因epollselectpoll等模块也是event module,所以epoll的创建等参数设置也会在这一阶段完成
    • 4 解析到http块时,先找到http命令对应的set,即调用ngx_http_block,在该函数中会完成所有模块各个作用域的http配置;大致流程如下:
    for (m = 0; cf->cycle->modules[m]; m++) {   
        // 为各个模块创建配置
        ctx->main_conf[mi] = module->create_main_conf(cf); //create_main_conf
        ctx->srv_conf[mi] = module->create_srv_conf(cf); //create_srv_conf
        ctx->loc_conf[mi] = module->create_loc_conf(cf); //create_loc_conf
        module->preconfiguration(cf)
    
        rv = module->init_main_conf(cf, ctx->main_conf[mi]) //init_main_conf
        // 关联 或作用域冲突的配置项合并
        rv = ngx_http_merge_servers(cf, cmcf, module, mi); //merge_servers
    }
    /* create location trees */
    ngx_http_init_locations(cf, cscfp[s], clcf)
    ngx_http_init_static_location_trees(cf, clcf)
    
    //初始化http解析过程中的11阶段,每个阶段都有个handler数组,为其分配空间
    ngx_http_init_phases(cf, cmcf)
    
    for (module:modules) {
        // 遍历所有http模块向各个phase的handlers中添加handler
        module->postconfiguration(cf)
    }
    // 为各phase设置公共checker,设置handler调用链
    ngx_http_init_phase_handlers(cf, cmcf)
    
    // 优化服务器端口对,初始化监听队列即配置,设置ngx_listening_t的handler
    // handler = ngx_http_init_connection 此handler将会在accept后被调用,用来初始化已接受的tcp链接
    ngx_http_optimize_servers(cf, cmcf, cmcf->ports)
    }
    
    • 5 其余配置项都要经历类似的解析设置经历,最终nginx.conf个文件将会变成极其复杂的cycle结构体。

    2.2 全局配置初始化

    • *在init_cycle流程中篇幅被众多的数组、链表、树等配置结构的初始化流程充斥着,还有配置继承等内容,看起来很直接,很好懂,此处不再花篇幅。

    2.3 ngx_init_modules

    • 各模块静态配置初始化之后,依次调用其init module函数完成配置阶段最后的初始化,该阶段大多数模块都相关函数,少数模块做了设置回调等操作。

    二、Init Process

    1 进程初始化

    if (ngx_process == NGX_PROCESS_SINGLE) {
        ngx_single_process_cycle(cycle);
    } else {
        ngx_master_process_cycle(cycle);
    }
    
    • 进程的初始化流程会因配置不同,一般情况下都需要Main + N Worker的模式,所以以下内容以ngx_master_process_cycle展开。
    • 1 main 进程流程,处理控制台及操作系统的信号,管理worker进程的生命周期;
    ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN);
    for ( ;; ) {
        if (ngx_reap) {}
        if (ngx_terminate) {} //结束进程
        if (ngx_quit) {} //退出
        if (ngx_reconfigure) {} //刷新配置
        if (ngx_restart) {} //重启
        ...
    }
    
    • 2 fork子进程,完成每个子进程的初始化,调用各module的process init;设置与主进程的通信管道;
    for (i = 0; i < n; i++) {
        ngx_spawn_process(cycle, ngx_worker_process_cycle, (void *) (intptr_t) i, "worker process", type);
        ch.pid = ngx_processes[ngx_process_slot].pid;
        ch.slot = ngx_process_slot;
        ch.fd = ngx_processes[ngx_process_slot].channel[0];
        ngx_pass_open_channel(cycle, &ch);
    }
    
      • ngx_spawn_process完成对各子进程的fork操作,设置好与父进程(即Main)的全双工通信管道后调用fork,再调用ngx_worker_process_cycle启动各子进程的主流程;
    ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
        char *name, ngx_int_t respawn)
    {
        // 用socketPair 充当父子进程间通信的,可以使用各种socket io选项
        socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel)
        ngx_nonblocking(ngx_processes[s].channel[0]) == -1)
        ngx_nonblocking(ngx_processes[s].channel[1]) == -1)
        ioctl(ngx_processes[s].channel[0], FIOASYNC, &on)
        fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid)
        fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC)
        fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC)
        // 创建子进程
        ret = fork()
        if ret==0 {
            proc(cycle, data); //ngx_worker_process_cycle
        }
    }
    

    三、Start Worker 和 Event Handle

    • 子进程由于都是fork而来,继承了主进程在配置阶段配置完的cycle结构,所以每个子进程都是一个单独的server;在这一阶段需要调用各子模块的init_process来完成众多模块的进程级别初始化,之后进入自己的循环,处理各种事件。
    static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
    {
        ngx_worker_process_init(cycle, worker);
        for ( ;; ) {
            // 处理信号
            if (ngx_exiting)
    
            // 检查事件驱动模块和定时器
            ngx_process_events_and_timers(cycle);
    
            if (ngx_terminate)
            if (ngx_quit)
            if (ngx_reopen)
    }
    
    • 1 ngx_worker_process_init
    • 在这一阶段遍历module执行init_process;大多数模块的init_process都是NULL,重点关注event_core_module,其init_process为ngx_event_process_init
    for (m = 0; cycle->modules[m]; m++) {
            if (cycle->modules[m]->type != NGX_EVENT_MODULE) {
                continue;
            }    
            module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) //ngx_epoll_init
    }
    //设置读事件的handler
    rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept : ngx_event_recvmsg; 
    
      • 该函数中将会遍历event类型的模块,执行其actions.init函数,实际上,linux环境默认事件模块是epoll,event模块的非核心模块只有ngx_epoll_module,所以此次会完成epoll的读写事件初始化函数ngx_epoll_init,以下为函数体:
    static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer)
    {
        ep = epoll_create(cycle->connection_n / 2); //事件创建
        event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
        nevents = epcf->events;
        ngx_event_actions = ngx_epoll_module_ctx.actions;
    }
    
    • 2 worker进程主逻辑 ngx_process_events_and_timers
    void ngx_process_events_and_timers(ngx_cycle_t *cycle)
    {
        (void) ngx_process_events(cycle, timer, flags); //事件处理
        ngx_event_process_posted(cycle, &ngx_posted_accept_events); 请求响应
    }
    
      • 事件模块使用epoll时,此处的ngx_process_events实际上是ngx_epoll_process_events;完成epoll_wait和事件检测,调用可读或可写事件的handler;
    static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
    {
        events = epoll_wait(ep, event_list, (int) nevents, timer);
        rev->handler(rev); // ngx_event_accept
    }
    
      • 来新的请求后,事件变成可读,将会调用ngx_event_accept,会调用操作系统提供的accept,之后创建ngx_connection结构,设置socket属性后调用ngx_http_init_connection初始化新创建的链接,准备接受数据;
    void ngx_event_accept(ngx_event_t *ev)                                                                
    {
        s = accept(lc->fd, &sa.sockaddr, &socklen);
        c = ngx_get_connection(s, ev->log);
        ls->handler(c); // ngx_http_init_connection
    }
    
  • 相关阅读:
    Faster R-CNN
    Ubuntu软件安装
    Ubuntu16.04 caffe安装记录
    Unity Editor 工具开发 (三)——数据存储工具 -1
    #Unity Editor 工具开发 (二)
    Unity Editor 工具开发 (一)
    c# 多线程入门记录
    Unity 获取按键
    常用排序算法
    My “Hello World” For Blog
  • 原文地址:https://www.cnblogs.com/zhangyi-studio/p/12657179.html
Copyright © 2020-2023  润新知