zoukankan      html  css  js  c++  java
  • nginx代码分析进程和模块初始化

    nginx事件处理流程

    1.    进程初始化

    nginx启动流程中已经对进程启动进行了简单介绍,现在对每种进程的初始化进行下介绍。

    (1)           总进程的初始化(这里还没有启动worker进程,所以称为总进程):

    作为整个进程的入口,很多公用的初始化都是在一开始完成的,先对debug(ngx_debug_init)和错误码进行初始化(ngx_strerror_init),然后解析nginx的启动命令参数,并根据参数进行进一步处理;时间相关参数初始化ngx_time_init(),如果支持正则表达式,则初始化regex相关参数ngx_regex_init,紧接着是日志相关参数(日志文件描述符等)初始化ngx_log_init,如果支持openssl,则对ssl相关参数初始化ngx_ssl_init。

    为init_cycle创建资源池pool,将ngin命令参数保存为全局变量,处理init_cycle的选项,初始化一些全局变量,最后根据系统初始化系统相关全局变量。

    初始化crc32表,然后调用ngx_add_inherited_sockets函数,这个函数有点复杂,一会单讲,继续向下,为模块编号,根据之前的init_cycle生成一个cycle指针保存所有的ngx_cycle_s数据, 初始化信号ngx_init_signals,将信号与处理函数连接起来,根据daemon配置参数启动daemon进程,创建进程号文件,最后根据ngx_process判断进入两种进程模式ngx_single_process_cycle和ngx_master_process_cycle。

    (2)           Master进程的初始化(启动worker进程后,原进程称为master进程):

    先初始化信号集,向信号集set中添加各种需要响应的信号,并通过sigprocmask(SIG_BLOCK, &set, NULL)将信号集set加入原有的进程阻塞信号集中,然后清空信号集set,设置进程title,获取core模块的配置信息,启动worker进程和cache管理进程,进入无限for(;;)循环。

    (3)           Worker进程的初始化:

    Worker进程的初始化主要在ngx_worker_process_init函数中完成,设置环境变量ngx_set_environment,获取core模块的配置参数ngx_get_conf,进程优先级设置setpriority,如果设置了worker_rlimit_nofile和worker_rlimit_core,则根据这两个值分别设置进程的RLIMIT_NOFILE和RLIMIT_CORE参数,并根据系统是否支持RLIMIT_SIGPENDING来配置用户可用的最大挂起信号数。然后根据当前的有效用户设置进程的userID和groupID,根据配置文件决定是否进行CPU绑定ngx_setaffinity,更改默认路径chdir,清空信号集set,通过sigprocmask清空进程的阻塞信号集,保证worker进程不会被信号中断。关闭该进程多余的channel。最后根据全局变量ngx_channel开启一个通道,该通道只处理读事件,处理函数为ngx_channel_handler,有点明显了,这个通道是用来与master进程进行通信的,因为只有跟master进程通信,worker才会只收不发。

    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->init_process) {
            if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
                /* fatal */
                exit(2);
            }
        }
    }

    代码4-1

      这段代码是对每个模块的处理初始化,遍历所有模块,对有处理初始化函数的模块进行处理,其中会调用ngx_event_process_init函数对事件进行初始化,将事件添加到epoll信号集中,代码如下:

    rev->handler = ngx_event_accept;
    if (ngx_use_accept_mutex) {
        continue;
    }
    if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
        if (ngx_add_conn(c) == NGX_ERROR) {
            return NGX_ERROR;
        }
    } else {
        if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }
    }

    代码4-2

      将在配置文件解析过程中的http模块初始化中建立的listen数组加入到epoll监控事件集中,并把处理函数handler挂载处理函数ngx_event_accept,结合下面的《模块初始化》可以体现整个的初始化过程。

    (4)           ngx_add_inherited_sockets函数:

    纠结了段时间,虽然能看懂代码逻辑,但实在搞不清实际用途,不过在查阅了很多网上资料后,终于明白了,原来适用于平滑升级的(感谢http://blog.csdn.net/dingyujie/article/details/7192144的一些提示)。

    该函数是将环境变量NGINX的值解析为sockets,将解析出的合法socket number加入listen数组,这些sockets以“:”或“;”分隔,并将全局变量ngx_inherited置为1,最后使用ngx_set_inherited_sockets对每个socket的参数进行赋值,原理简单,使用getsockname函数通过socket获取该连接的地址信息等,将结果放入listen的结构中,通过一系列的初始化,最终成为一组可用的listen数组。

    关于这个函数在整个代码中的用途,请看后面的《nginx平滑升级》。

    2.    模块初始化

    (1)           结构体介绍

    模块的结构体ngx_module_s定义如下:

    struct ngx_module_s {
        ngx_uint_t            ctx_index;     //分类模块计数器
        ngx_uint_t            index;         //模块计数器  
        ngx_uint_t            spare0;
        ngx_uint_t            spare1;
        ngx_uint_t            spare2;
        ngx_uint_t            spare3;
    
        ngx_uint_t            version;       //版本 
    
        void            *ctx;  //该模块的上下文,每个种类的模块有不同的上下文
        ngx_command_t  *commands; //该模块的命令集,指向一个ngx_command_t数组
        ngx_uint_t       type; //该模块的种类,为core/event/http/mail中的一种
    
        ngx_int_t           (*init_master)(ngx_log_t *log);  //初始化master
    
        ngx_int_t           (*init_module)(ngx_cycle_t *cycle); //初始化模块
    
        ngx_int_t           (*init_process)(ngx_cycle_t *cycle); //初始化工作进程
        ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);  //初始化线程 
        void                (*exit_thread)(ngx_cycle_t *cycle);   //退出线程
        void                (*exit_process)(ngx_cycle_t *cycle);  //退出工作进程
    
        void                (*exit_master)(ngx_cycle_t *cycle);  //退出master
    
        uintptr_t             spare_hook0;
        uintptr_t             spare_hook1;
        uintptr_t             spare_hook2;
        uintptr_t             spare_hook3;
        uintptr_t             spare_hook4;
        uintptr_t             spare_hook5;
        uintptr_t             spare_hook6;
        uintptr_t             spare_hook7;
    };

    代码4-3

    下面是对核心模块的结构体对象进行初始化,采用了两个宏来设置前7个字段和后8个字段。

    #define NGX_MODULE_V1    0, 0, 0, 0, 0, 0, 1    //该宏用来初始化前7个字段  
    #define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0 //该宏用来初始化最后8个字段
    static ngx_core_module_t  ngx_core_module_ctx = {
        ngx_string("core"),
        ngx_core_module_create_conf,
        ngx_core_module_init_conf
    };
    ngx_module_t  ngx_core_module = {
        NGX_MODULE_V1,
        &ngx_core_module_ctx,                  /* module context */
        ngx_core_commands,                     /* module directives */
        NGX_CORE_MODULE,                       /* module type */
        NULL,                                  /* init master */
        NULL,                                  /* init module */
        NULL,                                  /* init process */
        NULL,                                  /* init thread */
        NULL,                                  /* exit thread */
        NULL,                                  /* exit process */
        NULL,                                  /* exit master */
        NGX_MODULE_V1_PADDING
    };

    代码4-4

      

    (2)           初始化模块

    上面说明了两个结构体,下面开始对模块初始化流程进行介绍。在main函数中,对所有的模块进行排序。

    ngx_max_module = 0;
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = ngx_max_module++;
    }

    代码4-5

      紧接着就是ngx_init_cycle函数,该函数完成了对模块初始化的所有调用。该函数对时间、资源池、各种链表、数组和队列等进程初始化,如果指定了create_conf函数指针,就调用该函数。ngx_conf_parse是该函数中最为关键一个函数,它对模块进行了初始化操作(ngx_conf_param函数最终也会调用ngx_conf_parse函数进行配置解析)。

    if (filename) {
        /* open configuration file */
        fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0);
        if (fd == NGX_INVALID_FILE) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
                               ngx_open_file_n " \"%s\" failed",
                               filename->data);
            return NGX_CONF_ERROR;
        }
        prev = cf->conf_file;
        cf->conf_file = &conf_file;
    
        if (ngx_fd_info(fd, &cf->conf_file->file.info) == -1) {
            ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno,
                          ngx_fd_info_n " \"%s\" failed", filename->data);
        }
    
        cf->conf_file->buffer = &buf;
    
        buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log);
        if (buf.start == NULL) {
            goto failed;
        }
    
        buf.pos = buf.start;
        buf.last = buf.start;
        buf.end = buf.last + NGX_CONF_BUFFER;
        buf.temporary = 1;
    
        cf->conf_file->file.fd = fd;
        cf->conf_file->file.name.len = filename->len;
        cf->conf_file->file.name.data = filename->data;
        cf->conf_file->file.offset = 0;
        cf->conf_file->file.log = cf->log;
        cf->conf_file->line = 1;
    
        type = parse_file;
    } else if (cf->conf_file->file.fd != NGX_INVALID_FILE) {
        type = parse_block;
    } else {
        type = parse_param;
    }

    代码4-6

      这段代码主要是根据不同的解析方式,将变量type置为相应的类型。如果是解析配置文件,则打开文件,并将缓存区挂到ngx_conf_t结构体上。

      后面就会进入一个for循环,对数据和文件的解析一般都采用这种方式,在for循环中,先通过ngx_conf_read_token解析出记号,根据返回的结果是否为NGX_OK或NGX_CONF_BLOCK_START判断是否跳过执行handler函数。

      代码中有一段注释说明ngx_conf_read_token函数返回值的意义:

    /*   ngx_conf_read_token() may return
     *    NGX_ERROR             there is error
     *    NGX_OK                the token terminated by ";" was found
     *    NGX_CONF_BLOCK_START  the token terminated by "{" was found
     *    NGX_CONF_BLOCK_DONE   the "}" was found
     *    NGX_CONF_FILE_DONE    the configuration file is done
     */

    代码4-7

      由于没有对handler挂载函数,所以调用后面的ngx_conf_handler函数进行默认处理。

      Ngx_conf_handler函数中,会遍历所有的模块,而每个模块都有多个命令组成的命令数组,所以在遍历模块的循环中,还有一个遍历当前模块的命令数组的循环。针对于某个模块的某个命令,通过下面这段代码判断是否符合条件(主要对判断命令名、模块类型和命令类型进行判断),对符合条件的情况进行进一步处理:

    if (name->len != cmd->name.len) {
        continue;
    }
    if (ngx_strcmp(name->data, cmd->name.data) != 0) {
        continue;
    }
    found = 1;
    if (ngx_modules[i]->type != NGX_CONF_MODULE
        && ngx_modules[i]->type != cf->module_type)
    {
        continue;
    }
    /* is the directive's location right ? */
    if (!(cmd->type & cf->cmd_type)) {
        continue;
    }

    代码4-8

     

    图:配置文件解析流程

      然后就是判断指令的参数个数是否正确,如果不正确goto invalid;指定指令的配置环境,调用ngx_command_s结构中的set函数指针cmd->set(cf, cmd, conf)。该函数指针根据不同的命令指向不同的处理函数,而当前配置文件的模块类型module_type为NGX_CORE_MODULE,决定了当前的正在进行配置的模块为ngx_core_module,命令数组为ngx_core_commands。

      举个例子,比如在解析ngx_events_block函数时,里面还会调用ngx_conf_parse函数进行配置文件解析。这是由于在解析文件时,采用了递归的方法,当解析到某个关键值时,调用ngx_conf_parse函数,然后剩下的配置信息在该ngx_conf_parse调用的command->set函数指针中继续进行解析,直到解析完这个模块的配置,才会跳到最外层的循环,继续其它模块的配置文件解析。

      解析完成后,会根据配置的信息创建路径、打开文件、创建共享存储区、打开socket进行监听、设置socket选项、关闭不必要的文件、关闭不必要的socket、释放不必要的共享存储区等操作。

    (3)           http模块初始化

    图:http模块初始化流程

      由于nginx大多数是作为http服务器运行的,所以对http模块的初始化着重介绍一下。

      ngx_http_commands结构体的set函数指针挂载了函数ngx_http_block函数,该函数是http模块的配置初始化函数。

      和其它主要模块初始化一样,在开始处先对http模块中每个子模块进行编号,然后给配置上下文分配空间并初始化,解析http块,遍历所有的server块,初始化phases,最后两个函数及其重要,ngx_http_init_phase_handlers将配置文件对应的checker和handler挂载上对应的函数加入到配置上下文中,ngx_http_optimize_servers则会对所有配置的端口进行socket建立、绑定和监听,并将listen对应的结构体加入到listens数组中。

    1.    无限for()循环

    (1)           Master进程

    Master进程的主要工作是处理用户命令,管理worker进程。用户命令是通过信号传递给进程的,进程在接收到信号后会对信号进行处理。在main函数中,有对信号处理的初始化ngx_init_signals,将signals数组的信号及其处理函数通过sigaction连接起来。一旦用户执行命令,则会发出对应的信号,会触发ngx_signal_handler函数进行信号处理。

    与该进程信号有关的全局变量有如下几个:ngx_quit,ngx_terminate,ngx_noaccept,ngx_reconfigure,ngx_reopen,ngx_change_binary,ngx_sigalrm ,ngx_sigio和ngx_reap。

    nginx命令及与全局变量的对应关系:

    命令介绍

    命令

    全局变量

    备注

    从容停止Nginx

    kill -QUIT 主进程号

    nginx_quit

    快速停止Nginx

    kill -TERM 主进程号

    nginx_terminate

    平滑重启命令

    kill -HUP 主进程号或进程号文件路径或 /usr/nginx/sbin/nginx -s reload

    ngx_reconfigure

    平滑升级1:

    kill -USR2 旧版程序的主进程号或进程文件名

    ngx_change_binary

    旧的Nginx主进程将会把自己的进程文件改名为.oldbin,然后执行新版 Nginx。新旧Nginx会同市运行,共同处理请求。

    平滑升级2:

    kill -WINCH 旧版主进程号

    ngx_noaccept

    慢慢旧的工作进程就都会随着任务执行完毕而退出,新版的Nginx的工作进程会逐渐取代旧版 工作进程。

    重新打开日志文件

    kill –USR1进程号 或 nginx -s reopen

    ngx_reopen

    定时器超时信号

    SIGALRM

    ngx_sigalrm

    子进程关闭

    SIGCHLD

    ngx_reap

    子进程关闭向父进程发送该信号。

    表4-1:nginx命令介绍与全局变量关系

    通过表4-1了解了nginx支持的命令及信号,结合代码来进一步进行解释。

    if (delay) {
            if (ngx_sigalrm) {
                sigio = 0;
                delay *= 2;
                ngx_sigalrm = 0;
            }
    
            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                               "termination cycle: %d", delay);
    
            itv.it_interval.tv_sec = 0;
            itv.it_interval.tv_usec = 0;
            itv.it_value.tv_sec = delay / 1000;
            itv.it_value.tv_usec = (delay % 1000 ) * 1000;
    
            if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
                ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                              "setitimer() failed");
            }
    }
    sigsuspend(&set);
    ngx_time_update();

    代码4-9

      如果delay不为0,即需要延迟delay,如果是定时器超时(ngx_sigalrm为1),则delay翻倍,继续设置定时器setitimer。

      sigsuspend挂起,不阻塞任何信号,等待任何信号(因为set信号集为空)到达后恢复正常,由于有信号触发,所以先要执行ngx_signal_handler。

      ngx_time_update将更新当前时间的全局变量以及cache相关的变量。

    if (ngx_reap) {
        ngx_reap = 0;
        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
        live = ngx_reap_children(cycle);
    }
    if (!live && (ngx_terminate || ngx_quit)) {
        ngx_master_process_exit(cycle);
    }

    代码4-10

      ngx_reap为1,表示有子进程退出,会调用ngx_reap_children进行进一步处理,在该函数中,会根据用户命令(进程是否退出)决定是否重启关闭的worker进程。如果没有运行的worker进程了,则live为0。如果用户关闭进程,则在此处可以关闭master进程了。

    if (ngx_terminate) {
        if (delay == 0) {
            delay = 50;
        }
        if (sigio) {
            sigio--;
            continue;
        }
        sigio = ccf->worker_processes + 2 /* cache processes */;
        if (delay > 1000) {
             ngx_signal_worker_processes(cycle, SIGKILL);
        } else {
             ngx_signal_worker_processes(cycle,                  ngx_signal_value(NGX_TERMINATE_SIGNAL));
        }
        continue;
    }

    代码4-11

      ngx_terminate为1,通过delay设置延迟时间,当delay超过1000时,则强制关闭worker进程,否则,快速结束worker进程,与子进程的通信通过ngx_signal_worker_processes函数进行。

    if (ngx_quit) {
        ngx_signal_worker_processes(cycle,
             ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
        ls = cycle->listening.elts;
        for (n = 0; n < cycle->listening.nelts; n++) {
            if (ngx_close_socket(ls[n].fd) == -1) {
                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
                                ngx_close_socket_n " %V failed",
                                &ls[n].addr_text);
            }
         }
         cycle->listening.nelts = 0;
         continue;
    }

    代码4-12

      ngx_quit为1,则通知worker进程从容的关闭进程,并将listen监听数组中的每个链接关闭。

    if (ngx_reconfigure) {
         ngx_reconfigure = 0;
         if (ngx_new_binary) {
             ngx_start_worker_processes(cycle, ccf->worker_processes,
                                         NGX_PROCESS_RESPAWN);
             ngx_start_cache_manager_processes(cycle, 0);
             ngx_noaccepting = 0;
             continue;
         }
         ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
         cycle = ngx_init_cycle(cycle);
         if (cycle == NULL) {
             cycle = (ngx_cycle_t *) ngx_cycle;
             continue;
         }
         ngx_cycle = cycle;
         ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
                                           ngx_core_module);
         ngx_start_worker_processes(cycle, ccf->worker_processes,
                                      NGX_PROCESS_JUST_RESPAWN);
         ngx_start_cache_manager_processes(cycle, 1);
         /* allow new processes to start */
         ngx_msleep(100);
         live = 1;
         ngx_signal_worker_processes(cycle,
                    ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
    }

    代码4-13

      如果ngx_reconfigure为1,则表示要重新载入配置文件,这个过程其实是启动一个新的master进程,然后关闭旧的master进程。如果ngx_new_binary为1,表示为平滑升级过程,直接启动worker进程ngx_start_worker_processes和cache管理进程ngx_start_cache_manager_processes;否则就是普通的重载配置文件操作。会初始化cycle,启动worker进程和cache管理进程,通知worker进程关闭,这里对worker进程启动了又关闭,可能是启动了已经被关闭的worker进程,而始终活着的worker没有机会重载配置文件,所以最后会把worker都关闭,并通过SIGCHLD信号重启所有worker。

     

    if (ngx_restart) {
        ngx_restart = 0;
        ngx_start_worker_processes(cycle, ccf->worker_processes,
                                       NGX_PROCESS_RESPAWN);
        ngx_start_cache_manager_processes(cycle, 0);
        live = 1;
    }

    代码4-14

      ngx_reap为1,表示有子进程退出,会调用ngx_reap_children进行进一步处理,在该函数中,会根据用户命令(进程是否退出)决定是否重启关闭的worker进程。如果没有运行的worker进程了,则live为0。如果用户关闭进程,则在此处可以关闭master进程了。

    if (ngx_terminate) {
        if (delay == 0) {
            delay = 50;
        }
        if (sigio) {
            sigio--;
            continue;
        }
        sigio = ccf->worker_processes + 2 /* cache processes */;
        if (delay > 1000) {
             ngx_signal_worker_processes(cycle, SIGKILL);
        } else {
             ngx_signal_worker_processes(cycle,                  ngx_signal_value(NGX_TERMINATE_SIGNAL));
        }
        continue;
    }

    代码4-15

      ngx_terminate为1,通过delay设置延迟时间,当delay超过1000时,则强制关闭worker进程,否则,快速结束worker进程,与子进程的通信通过ngx_signal_worker_processes函数进行。

    if (ngx_quit) {
        ngx_signal_worker_processes(cycle,
             ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
        ls = cycle->listening.elts;
        for (n = 0; n < cycle->listening.nelts; n++) {
            if (ngx_close_socket(ls[n].fd) == -1) {
                ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
                                ngx_close_socket_n " %V failed",
                                &ls[n].addr_text);
            }
         }
         cycle->listening.nelts = 0;
         continue;
    }

    代码4-16

      ngx_quit为1,则通知worker进程从容的关闭进程,并将listen监听数组中的每个链接关闭。

    if (ngx_reconfigure) {
         ngx_reconfigure = 0;
         if (ngx_new_binary) {
             ngx_start_worker_processes(cycle, ccf->worker_processes,
                                         NGX_PROCESS_RESPAWN);
             ngx_start_cache_manager_processes(cycle, 0);
             ngx_noaccepting = 0;
             continue;
         }
         ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring");
         cycle = ngx_init_cycle(cycle);
         if (cycle == NULL) {
             cycle = (ngx_cycle_t *) ngx_cycle;
             continue;
         }
         ngx_cycle = cycle;
         ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
                                           ngx_core_module);
         ngx_start_worker_processes(cycle, ccf->worker_processes,
                                      NGX_PROCESS_JUST_RESPAWN);
         ngx_start_cache_manager_processes(cycle, 1);
         /* allow new processes to start */
         ngx_msleep(100);
         live = 1;
         ngx_signal_worker_processes(cycle,
                    ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
    }

    代码4-17

      如果ngx_reconfigure为1,则表示要重新载入配置文件,这个过程其实是启动一个新的master进程,然后关闭旧的master进程。如果ngx_new_binary为1,表示为平滑升级过程,直接启动worker进程ngx_start_worker_processes和cache管理进程ngx_start_cache_manager_processes;否则就是普通的重载配置文件操作。会初始化cycle,启动worker进程和cache管理进程,通知worker进程关闭,这里对worker进程启动了又关闭,可能是启动了已经被关闭的worker进程,而始终活着的worker没有机会重载配置文件,所以最后会把worker都关闭,并通过SIGCHLD信号重启所有worker。

    if (ngx_restart) {
        ngx_restart = 0;
        ngx_start_worker_processes(cycle, ccf->worker_processes,
                                       NGX_PROCESS_RESPAWN);
        ngx_start_cache_manager_processes(cycle, 0);
        live = 1;
    }

    代码4-18

      这段代码没有找到对应的命令,不知道在何时能够触发。

    if (ngx_reopen) {
        ngx_reopen = 0;
        ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
        ngx_reopen_files(cycle, ccf->user);
        ngx_signal_worker_processes(cycle,                                ngx_signal_value(NGX_REOPEN_SIGNAL));
    }

    代码4-19

      Ngx_reopen为1,是与重新打开日志文件有关。

    if (ngx_change_binary) {
         ngx_change_binary = 0;
         ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "changing binary");
         ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
    }
    
    if (ngx_noaccept) {
        ngx_noaccept = 0;
        ngx_noaccepting = 1;
        ngx_signal_worker_processes(cycle,    ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
    }

    代码4-20

    这段代码与平滑升级相关,在后面的《平滑升级》章节具体介绍。

    (2)           Worker进程

      Worker进程的for循环就简单多了,毕竟它不像master进程需要管理很多进程。从源码中可以看出,worker进程除了ngx_process_events_and_timers外只需要处理三种情况,完成四种操作。

    if (ngx_exiting) {
        c = cycle->connections;
        for (i = 0; i < cycle->connection_n; i++) {
            /* THREAD: lock */
            if (c[i].fd != -1 && c[i].idle) {
                c[i].close = 1;
                c[i].read->handler(c[i].read);
            }
        }
        if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
        {
             ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
             ngx_worker_process_exit(cycle);
        }
    }

    代码4-21

      ngx_exiting为1 表示进程正在退出,在该进程选择从容退出时,即ngx_quit为1后,才会进入该状态。该过程时将所有的connections连接关闭,当事件对应的红黑树中没有计时器时,可以快速退出该进程ngx_worker_process_exit。

    if (ngx_terminate) {
        ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
        ngx_worker_process_exit(cycle);
    }

    代码4-22

      ngx_terminate为1表示快速关闭进程,源码中有四个对该变量置为1的地方,其中一个是用于Master进程或Single进程的,一个适用于线程,其余两个用户Worker进程。当进程收到信号NGX_TERMINATE_SIGNAL和SIGINT时,以及通过channel接收到NGX_CMD_TERMINATE信号ngx_channel_handler。进程会快速的退出ngx_worker_process_exit。

    if (ngx_quit) {
        ngx_quit = 0;
        ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                      "gracefully shutting down");
        ngx_setproctitle("worker process is shutting down");
    
        if (!ngx_exiting) {
            ngx_close_listening_sockets(cycle);
            ngx_exiting = 1;
        }
    }

    代码4-23

      Ngx_quit为1表示进程从容退出,如果ngx_exiting为0,则关闭监听的socket,并将ngx_exiting置为1。

    if (ngx_reopen) {
        ngx_reopen = 0;
        ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
        ngx_reopen_files(cycle, -1);
    }

    代码4-24

      Ngx_reopen为1表示重新打开日志文件。

    3.    事件处理ngx_process_events_and_timers

    Ngx_process_events_and_timers函数只在worker进程中出现,因为只有worker进程才真正的处理用户请求。

    if (ngx_timer_resolution) {
        timer = NGX_TIMER_INFINITE;
        flags = 0;
    } else {
        timer = ngx_event_find_timer();
        flags = NGX_UPDATE_TIME;
    #if (NGX_THREADS)
        if (timer == NGX_TIMER_INFINITE || timer > 500) {
            timer = 500;
        }
    #endif
    }

    代码4-25

      该函数一开始就先进行ngx_timer_resolution判断,这个值在数据结构ngx_core_conf_t里介绍过,为了减少调用gettimeofdate的次数。如果配置了该值,则timer置为NGX_TIMER_INFINITE;如果没配置,通过ngx_event_find_timer函数将事件红黑树中最早超时的计时器时间与当前时间的差值赋给timer,并将flags的NGX_UPDATE_TIME置位。

      然后是关于进程间互斥和负载均衡的一段代码,将在后面的《nginx进程间的锁》中详细介绍,暂时先跳过去。

    delta = ngx_current_msec;
    (void) ngx_process_events(cycle, timer, flags);
    delta = ngx_current_msec - delta;

    代码4-26

      ngx_process_events函数会根据采用的不同事件模式来挂载不同的函数,由于目前linux/unix大都支持epoll,所以选择epoll模式。该函数会挂载ngx_epoll_process_events函数,用于处理epoll事件。

      Ngx_epoll_process_events函数wait所有的epoll事件event_list,并对发生的事件做相应的处理,EPOLLIN事件则响应读操作或者加入POST队列延迟处理,EPOLLOUT事件则响应写操作或加入POST队列延迟处理。

    static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
    {
        int                events;
        uint32_t           revents;
        ngx_int_t          instance, i;
        ngx_uint_t         level;
        ngx_err_t          err;
        ngx_event_t       *rev, *wev, **queue;
        ngx_connection_t  *c;
    
        /* NGX_TIMER_INFINITE == INFTIM */
    
        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "epoll timer: %M", timer);
    /*epoll_wait监听所有注册的epoll事件,timer则是代码4-13中计算出的超时时间,返回发生的事件集events*/
        events = epoll_wait(ep, event_list, (int) nevents, timer);
        err = (events == -1) ? ngx_errno : 0;
        if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
            ngx_time_update();
        }
        if (err) {
            if (err == NGX_EINTR) {
                if (ngx_event_timer_alarm) {
                    ngx_event_timer_alarm = 0;
                    return NGX_OK;
                }
                level = NGX_LOG_INFO;
            } else {
                level = NGX_LOG_ALERT;
            }
            ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
            return NGX_ERROR;
        }
    /*如果events为0,则没有事件发生*/
        if (events == 0) {
            if (timer != NGX_TIMER_INFINITE) {
                return NGX_OK;
            }
            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                          "epoll_wait() returned no events without timeout");
            return NGX_ERROR;
        }
        ngx_mutex_lock(ngx_posted_events_mutex);
    /*遍历发生的事件集,对每个事件做相对处理*/
        for (i = 0; i < events; i++) {
            c = event_list[i].data.ptr;
            instance = (uintptr_t) c & 1;
            c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
    /*rec事件为当前连接的读事件*/
            rev = c->read;
            if (c->fd == -1 || rev->instance != instance) {
    /*在这次迭代中刚刚关闭的文件描述符的旧事件*/
                /*
                 * the stale event from a file descriptor
                 * that was just closed in this iteration
                 */
                ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                               "epoll: stale event %p", c);
                continue;
            }
    /*revents为该事件的标识*/
            revents = event_list[i].events;
            ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll: fd:%d ev:%04XD d:%p",
                           c->fd, revents, event_list[i].data.ptr);
            if (revents & (EPOLLERR|EPOLLHUP)) {
                ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                               "epoll_wait() error on fd:%d ev:%04XD",
                               c->fd, revents);
            }
            if ((revents & (EPOLLERR|EPOLLHUP))
                 && (revents & (EPOLLIN|EPOLLOUT)) == 0)
            {
    /*如果错误事件没有EPOLLIN或EPOLLOUT标识,则将这两个标识加入到事件中,以保证至少有一个处理函数处理该事件*/
                revents |= EPOLLIN|EPOLLOUT;
            }
            if ((revents & EPOLLIN) && rev->active) {
    /*如果是EPOLLIN事件,并且是active的*/
                if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
                    rev->posted_ready = 1;
                } else {
                    rev->ready = 1;
                }
                if (flags & NGX_POST_EVENTS) {
    /*当前进程需要推迟事件处理,则将事件加入延迟队列中。如果是accept事件,则加入ngx_posted_accept_events 队列,否则加入ngx_posted_events队列 */
                    queue = (ngx_event_t **) (rev->accept ?
                          &ngx_posted_accept_events : ngx_posted_events);
                    ngx_locked_post_event(rev, queue);
                } else {
    /*如果不延迟处理,则马上调用对应的处理函数*/
                    rev->handler(rev);
                }
            }
    /*rec事件为当前连接的写事件*/
            wev = c->write;
            if ((revents & EPOLLOUT) && wev->active) {
    /*如果是EPOLLOUT事件,并且是active的*/
                if (c->fd == -1 || wev->instance != instance) {
                    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                                   "epoll: stale event %p", c);
                    continue;
                }
                if (flags & NGX_POST_THREAD_EVENTS) {
                    wev->posted_ready = 1;
                } else {
                    wev->ready = 1;
                }
                if (flags & NGX_POST_EVENTS) {
    /*当前进程需要推迟事件处理,则将事件加入延迟队列中。由于写操作不会有accept事件,所以没有ngx_posted_accept_events 队列的操作*/
                    ngx_locked_post_event(wev, &ngx_posted_events);
                } else {
    /*如果不延迟处理,则马上调用对应的处理函数*/
                    wev->handler(wev);
                }
            }
        }
        ngx_mutex_unlock(ngx_posted_events_mutex);
        return NGX_OK;
    }

    代码4-27

      到此,可以看出,剩下的处理就是对handler挂载函数的调用了。

      具体的信号处理在后面介绍,主要是用了epoll模式(关于epoll的介绍可以参看之前的《linux-epoll研究》)。

  • 相关阅读:
    Idea打包问题
    centos问题总结
    Linux CentOS7 系统目录详解
    centos下修改文件后如何保存退出
    利用windows上的VMware安装CentOS7
    VMware安装系统出现Operating System not found 解决方案
    mybatis 0 变成null问题
    Shiro权限前端调用302重定向
    java版本
    产品画原型工具放入到托管平台
  • 原文地址:https://www.cnblogs.com/geekma/p/2856759.html
Copyright © 2011-2022 走看看