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研究》)。

posted @ 2013-01-11 18:17  Geek_Ma  阅读(2101)  评论(0编辑  收藏  举报