I began to review the source of nginx again. Here is the source of ngx_init_cycle(). It is so important to understand the initialization process of nginx.
Nginx (三) ngx_init_cycle()
ngx_init_cycle()这个函数比较长,涉及到nginx诸多方面,代码到不是很复杂。这里是其代码理解的笔记,用以参考。
ngx_init_cycle()函数,这是个比较重要的函数,被main, ngx_master_process_cycle,ngx_single_process_cycle 调用, 其中后两者是在reconfigure的时候被调用的。它主要工作是,初始化cycle是基于旧有的cycle进行的,比如这里的 init_cycle,会继承old cycle的很多属性, 比如log等, 但是同时会对很多资源重新分配,比如pool, shared mem, file handler, listening socket 等,同时清除旧有的cycle的资源。
注:ngx_master/single_process_cycle里面会对init_process进行调用,并且循环调用 ngx_process_events_and_timers,其中里面会调用ngx_process_events(cycle, timer, flags); 对事件循环进行polling 时间一般默认为500 ms。
下面是ngx_init_cycle()函数的主要工作:
a) 更新时间,时区;
b) 用NGX_CYCLE_POOL_SIZE(16384)定义的大小新建一个内存池,并在这个内存池中新建一个cycle对象;
c) 执行下面操作:
cycle->pool = pool;
cycle->log = log;
cycle->new_log.log_level = NGX_LOG_ERR;
cycle->old_cycle = old_cycle;
d) 从old_cycle中将conf_prefix复制到新cycle的conf_prefix中;
e) 从old_cycle中将prefix复制到新cycle的prefix中;
f) 从old_cycle中将conf_file复制到新cycle的conf_file中;
g) 从old_cycle中将conf_param复制到新cycle的conf_param中;
h) 在新cycle中创建pathes对象;
i) 计算old_cycle中open_files队列中打开文件的数目,若old_cycle中open_files是空的,则假定打开文件数目为20;然后在新cycle中新建open_files列表;
j) 根据old_cycle在新cycle中创建shared_memory对象的方式与上面类似;
k) 接着是在新cycle中创建listening对象列表;
l) 根据模块数创建conf_ctx对象,也就是说conf_ctx是个存放配置信息的数组,每个模块对应conf_ctx中的一项;
m) 取得本机的主机名,保存在新cycle的hostname中;
n) 遍历加载的模块,仅调用核心模块中的create_conf钩子函数(参看后面的核心模块结构),并将返回的非空结果保存在conf_ctx数组中对应位置(以ngx_modules[i]->index来定位。说明每个模块有个编号。);
o) 创建一个conf对象,包括其中的args数组,temp_pool,还有下面相关属性设置:
conf.ctx = cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type = NGX_CORE_MODULE;
conf.cmd_type = NGX_MAIN_CONF;
p) 调用ngx_conf_param()函数;(ngx_conf_param()函数检查cf->cycle->conf_param的长度,如果为0,立即返回;否则,调用ngx_conf_parse(cf, NULL)解析配置文件。)
q) 接着调用ngx_conf_parse()函数把配置文件中的指令转换为配置结构并把指针加入到cycle.conf_ctx中;
ngx_conf_parse()函数有两个参数,一个是ngx_conf_t对象的指针,另一个是字符串表示配置文件的文件名。如果配置文件文件名没有指定,从代码中推断表示已经打开一个配置文件正在解析一个参数或配置块。所以ngx_conf_param()中调用ngx_conf_parse(cf, NULL),表示解析参数或配置块。ngx_conf_param()之所以先于ngx_conf_parse()调用,以防如果前一个进程打开了配置文件没有解析完成。
配置的更新主要是在ngx_conf_parse中进行的,这个函数中有一个for循环,每次循环调用ngx_conf_read_token取得一个配置指令(以;结尾),然后调用ngx_conf_handler处理这条指令,ngx_conf_handler每次都会遍历所有模块的指令集,查找这条配置指令并分析其合法性,如果指令正确,则会创建配置结构并把指针加入到 cycle.conf_ctx中,配置结构的赋值是调用该指令的钩子set完成的。
r) 南再次遍历加载的模块,仅调用核心模块中的init_conf钩子函数;
s) 如果ngx_process是NGX_PROCESS_SIGNALLER模式,函数返加新cycle;
t) 接下来的代码,检查ngx_test_config,为真则创建pid文件;(注:pid文件中只记录后来管理进程的pid,不是启动最初那个进程。在代码为数不多的注解中有下面的原话:
/*
* we do not create the pid file in the first ngx_init_cycle() call
* because we need to write the demonized process pid
*/)
u) 如果old_cycle中conf_ctx不为NULL时,比较old_cycle中的pid文件名与cycle中的pid文件名是不是相同,不同的话,创建新的pid文件,删除老的pid文件。
v) ngx_test_lockfile函数,检测lockfile是否存在,不存在则失败跳出;如果存在该函数会删除这个文件。(lock_file在ngx_core_module_init_conf()函数中获取,默认的宏是NGX_LOCK_PATH)
w) ngx_create_pathes()函数,主要用来创建目录,cycle中的pathes建成对应目录,设置用户读、写、执行权限;
x) ngx_conf_open_file()函数,先在打开的文件名表中找error log文件,找到了则返回打开的error log文件结构;如果还没有打开error log文件,将error log文件名添加到打开的文件列表中,若error log文件名不存在,则将stderr作为error log文件,否则打开文件的描述符为-1;最后error log文件的缓冲设为NULL;
y) 遍历打开文件列表,打开列表中的文件,如果有失败则跳到fail:处,非Windows环境下,调用fcntl()将每个打开的文件设为FD_CLOEXEC;(FD_CLOEXEC用来设置文件的close-on-exec状态标准。在exec()调用后,close-on-exec标志为0的情况,此文件不被关闭。非零则在exec()后被关闭。默认close-on-exec状态为0,需要通过FD_CLOEXEC设置。)
z) 设定新log文件;
cycle->log = &cycle->new_log;
pool->log = &cycle->new_log;
aa) 遍历共享内存块列表,将old_cycle中共享内存块列表中数据迁移到新cycle中的共享内存列表的对应内存块对象中,old_cycle中的共享内存块关闭;
bb) old_cycle->listening.nelts不为0,则将old_cycle->listening中的监听中的socket迁移到cycle->listening数组中;否则,将cycle->listening数组中元素的open标志置为1,另外有SO_ACCEPTFILTER或TCP_DEFER_ACCEPT宏对accept_filter进行设置。
cc) 调用ngx_open_listening_sockets();(ngx_open_listening_sockets()函数代码不复杂容易理解,主要是打开一些socket,不是全部socket,将打开的socket与相关地址bind(),然后listen(),设置listen标志为1,将监听socket存入listening数组对应元素中。如果支持异步IO事件,则将监听socket置于nonblocking状态。)
dd) ngx_test_config为0时,调用ngx_configure_listening_sockets()函数。ngx_configure_listening_sockets()函数是对监听的socket进行了socket配置。注意:如果socket是old_cycle中迁移来的,则socket选项信息也是从old_cycle中获取的。
ee) !ngx_use_stderr && cycle->log->file->fd != ngx_stderr时,使用ngx_set_stderr()置stderr到cycle->log->file->fd。
ff) 循环调用所有模块的init_module钩子函数;
gg) 循环检查old_cycle中的共享内存区列表,如果cycle中有与old_cycle中同名的共享内存区则保留下来,其他的old_cycle中的共享内存区释放掉。
hh) 关闭old_cycle->listening中没用的监听socket;
ii) 关闭old_cycle->open_files中打开的文件;
jj) 销毁配置对象中的temp_pool;
kk) 进程是NGX_PROCESS_MASTER模式时且ngx_is_init_cycle(old_cycle)检测为真,销毁old_cycle中的内存池,返回新cycle;
ll) ngx_temp_pool对象不存在就创建一个;同时在ngx_temp_pool中创建一个ngx_old_cycles数组和ngx_cleaner_event事件对象,ngx_cleaner_event.handler设为ngx_clean_old_cycles();
ngx_cleaner_event.handler = ngx_clean_old_cycles;
ngx_cleaner_event.log = cycle->log;
ngx_cleaner_event.data = &dumb;
dumb.fd = (ngx_socket_t) -1;
mm) old_cycle保存在ngx_old_cycles数组中;
nn) ngx_cleaner_event对象没有设定定时器时,设置一个定时器;返回cycle。
oo)最后是failed:分支,如果old_cycle->conf_ctx不为NULL时,获取ngx_core_module自己的配置项,如果其中有environment则将其保存在environ中;接着关闭打开的文件、监听的socket;销毁conf对象中的内存池和临时内存池;最后返回NULL。