天天看点

Nginx源码分析之启动过程

nginx的启动过程代码主要分布在src/core以及src/os/unix目录下。启动流程的函数调用序列:main(src/core/nginx.c)→ngx_init_cycle(src/core/ngx_cycle.c)→ngx_master_process_cycle(src/os/)。nginx的启动过程就是围绕着这三个函数进行的。

main函数的处理过程总体上可以概括如下:

简单初始化一些数据接结构和模块:ngx_debug_init、ngx_strerror_init、ngx_time_init、ngx_regex_init、ngx_log_init、ngx_ssl_init等

获取并处理命令参数:ngx_get_options。

初始化ngx_cycle_t结构体变量init_cycle,设置其log、pool字段等。

init_cycle.pool = ngx_create_pool(1024, log);

if (init_cycle.pool == NULL) {

return 1;

}

保存参数,设置全局变量:ngx_argc ngx_os_argv ngx_argv ngx_environ

处理控制台命令行参数,设置init_cycle的字段。这些字段包括:conf_prefix、prefix(-p prefix)、conf_file(-c filename)、conf_param(-g directives)。此外,还设置init_cyle.log.log_level=NGX_LOG_INFO。

调用ngx_os_init来设置一些和操作系统相关的全局变量:ngx_page_size、ngx_cacheline_size、ngx_ncpu、ngx_max_sockets、ngx_inherited_nonblocking。

调用ngx_crc32_table_init初始化ngx_crc32_table_short。用于后续做crc校验。

调用ngx_add_inherited_sockets(&init_cycle)→ngx_set_inherited_sockets,初始化init_cycle的listening字段(一个ngx_listening_t的数组)。

对所有模块进行计数

调用ngx_init_cycle进行模块的初始化,当解析配置文件错误时,退出程序。这个函数传入init_cycle然后返回一个新的ngx_cycle_t。

调用ngx_signal_process、ngx_init_signals处理信号。

在daemon模式下,调用ngx_daemon以守护进程的方式运行。这里可以在./configure的时候加入参数–with-debug,并在nginx.conf中配置:

可以取消守护进程模式以及master线程模型。

调用ngx_create_pidfile创建pid文件,把master进程的pid保存在里面。

根据进程模式来分别调用相应的函数

} else {

ngx_master_process_cycle(cycle);

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

整个启动过程中一个关键的变量init_cycle,其数据结构ngx_cycle_t如下所示:

它保存了一次启动过程需要的一些资源。

ngx_init_cycle函数的处理过程如下:

调用ngx_timezone_update()、ngx_timeofday、ngx_time_update()来更新时区、时间等,做时间校准,用来创建定时器等。

创建pool,赋给一个新的cycle(ngx_cycle_t)。这个新的cycle的一些字段从旧的cycle传递过来,比如:log,conf_prefix,prefix,conf_file,conf_param。

cycle->prefix.len = old_cycle->prefix.len;

cycle->prefix.data = ngx_pstrdup(pool, &old_cycle->prefix);

if (cycle->prefix.data == NULL) {

ngx_destroy_pool(pool);

return NULL;

cycle->conf_file.len = old_cycle->conf_file.len;

cycle->conf_file.data = ngx_pnalloc(pool, old_cycle->conf_file.len + 1);

if (cycle->conf_file.data == NULL) {

ngx_cpystrn(cycle->conf_file.data, old_cycle->conf_file.data,

old_cycle->conf_file.len + 1);

cycle->conf_param.len = old_cycle->conf_param.len;

cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param);

if (cycle->conf_param.data == NULL) {

还有一些字段会首先判断old_cycle中是否存在,如果存在,则申请同样大小的空间,并初始化。这些字段如下:

//paths

cycle->paths.elts = ngx_pcalloc(pool, n * sizeof(ngx_path_t *));

if (cycle->paths.elts == NULL) {

cycle->paths.nelts = 0;

cycle->paths.size = sizeof(ngx_path_t *);

cycle->paths.nalloc = n;

cycle->paths.pool = pool;

//open_files

if (old_cycle->open_files.part.nelts) {

n = old_cycle->open_files.part.nelts;

for (part = old_cycle->open_files.part.next; part; part = part->next) {

n += part->nelts;

n = 20;

//shared_memory

if (old_cycle->shared_memory.part.nelts) {

n = old_cycle->shared_memory.part.nelts;

for (part = old_cycle->shared_memory.part.next; part; part = part->next)

{

n = 1;

//listening

n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;

cycle->listening.elts = ngx_pcalloc(pool, n * sizeof(ngx_listening_t));

if (cycle->listening.elts == NULL) {

cycle->listening.nelts = 0;

cycle->listening.size = sizeof(ngx_listening_t);

cycle->listening.nalloc = n;

cycle->listening.pool = pool;

此外,new_log.log_level重新赋值的为NGX_LOG_ERR;old_cycle为传递进来的cycle;hostname为gethostname;初始化resuable_connection_queue。

这里有一个关键变量的初始化:conf_ctx。初始化为ngx_max_module个void *指针。说明其实所有模块的配置结构的指针。

调用所有模块的create_conf,返回的配置结构指针放到conf_ctx数组中,索引为ngx_modules[i]->index。

从命令行和配置文件读取配置更新到conf_ctx中。ngx_conf_param是读取命令行中的指令,ngx_conf_parse是把读取配置文件。ngx_conf_param最后也是通过调用ngx_cong_parse来读取配置的。ngx_conf_parse函数中有一个for循环,每次都调用ngx_conf_read_token取得一个配置指令,然后调用ngx_conf_handler来处理这条指令。ngx_conf_handler每次会遍历所有模块的指令集,查找这条配置指令并分析其合法性,如果正确则创建配置结构并把指针加入到cycle.conf_ctx中。 遍历指令集的过程首先是遍历所有的核心类模块,若是 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三类模块的钩子是配置中的指令驱动的。

调用core module的init_conf。

读取核心模块ngx_core_module的配置结构,调用ngx_create_pidfile创建pid文件。

这里代码中有一句注释:

当不是第一次初始化cycles时才会调用ngx_create_pidfile写入pid。

调用ngx_test_lockfile,ngx_create_paths并打开error_log文件复制给cycle->new_log.file。

遍历cycle的open_files.part.elts,打开每一个文件。open_files填充的文件数据是读取配置文件时写入的。

创建共享内存。这里和对open_files类似。先预分配空间,再填充数据。

处理listening sockets,遍历cycle->listening数组与old_cycle->listenning进行比较,设置cycle->listening的一些状态信息,调用ngx_open_listening_sockets启动所有监听socket,循环调用socket、bind、listen完成服务端监听监听socket的启动。并调用ngx_configure_listening_sockets配置监听socket,根据ngx_listening_t中的状态信息设置socket的读写缓存和TCP_DEFER_ACCEPT。

调用每个module的init_module。

关闭或者删除一些残留在old_cycle中的资源,首先释放不用的共性内存,关闭不使用的监听socket,再关闭不适用的打开文件。最后把old_cycle放入ngx_old_cycles。最后再设定一个定时器,定期回调ngx_cleaner_event清理ngx_old_cycles。周期设置为30000ms。

接下来是进程的启动,包括master和worker进程。main函数最后调用ngx_master_process_cycle来启动master进程模式(这里对单进程模式不做讲述)。

设置一些信号,如下:

调用ngx_setproctitle设置进程标题:”master process” + ngx_argv[0…]

启动worker进程,数量为ccf->worker_processes。

启动文件cache管理进程。

这里的cahche在一些模块中是需要的,如fastcgi模块等,这些模块会把文件cache路径添加到cycle->paths中,文件cache管理进程会定期调用这些模块的文件cache处理钩子处理一下文件cache。

master主循环,主要是循环处理信号量。在循环过程中,判断相应的条件然后进入相应的处理。这里的相关标志位基本都是在信号处理函数中赋值的。

信号处理函数是在main函数中进行的初始化:ngx_init_signals(cycle->log)。其中的signal handler代码如下所示:

创建worker子进程的函数是ngx_start_worker_processes。

这里ngx_pass_open_channel,即遍历所有worker进程,跳过自己和异常的worker,把消息发送给各个worker进程。worker进程的管道可读事件捕捉函数是ngx_channel_handler(ngx_event_t *ev),在这个函数中,会读取message,然后解析,并根据不同给的命令做不同的处理。

这里有一个关键的函数是ngx_pid_t ngx_spawn_process(ngx_cycle_t cycle, ngx_spawn_proc_pt proc, void data,char *name, ngx_int_t respawn)。proc是子进程的执行函数,data是其参数,name是进程名。

这个函数的任务:

有一个ngx_processes全局数组,包含了所有的子进程,这里会fork出子进程并放入相应的位置,并设置这个进程的相关属性。

创建socketpair,并设置相关属性

子啊子进程中执行传递进来的函数。

接下来创建一对socketpair句柄,然后初始化相关属性。

接下来就是fork子进程,并设置进程相关参数。

最后看一下,worker进程执行的函数static void ngx_worker_process_cycle(ngx_cycle_t cycle, void data)。

调用ngx_worker_process_init初始化;

设置ngx_process=NGX_PROCESS_WORKER

全局性的设置,包括执行环境、优先级、限制、setgid、setuid、信号初始化

调用所有模块的init_process钩子

关闭不使用的管道句柄,关闭当前的worker子进程的channel[0]句柄和继承来的其他进程的channel[1]句柄。使用其他进程的channel[0]句柄发送消息,使用本进程的channel[1]句柄监听事件。

进行线程相关的操作。(如果有线程模式)

主循环处理各种状态,类似master进程的主循环。

总结一下,nginx的启动过程可以划分为两个部分,

读取配置文件并设置全局的配置结构信息以及每个模块的配置结构信息,调用模块的 create_conf钩子和init_conf钩子。

创建进程和进程间通信机制,master进程负责管理各个worker子进程,通过 socketpair向子进程发送消息,各个worker子进程服务利用事件机制处理请求,通过socketpair与其他子进程通信(发送消息或者接收消息),进程启动的各个适当时机会调用模块的init_module钩子、init_process钩子、exit_process钩子和 exit_master钩子,init_master钩子没有被调用过。nginx的worker子进程继承了父进程的全局变量之后,子进程和父进程就会独立处理这些全局变量,有些全局量需要在父子进程之间同步就要通过通信方式了,比如 ngx_processes(进程表)的同步。

原文出处:后端技术杂谈

<a href="http://www.rowkey.me/blog/2014/09/24/nginx-bootstrap/" target="_blank">原文链接</a>

转载请与作者联系,同时请务必标明文章原始出处和原文链接及本声明。

继续阅读