nginx-0.8.38源码探秘先推荐几个研究nginx源码的好网址:/kenbinzhang/category/603177.aspx/p/nginxsrp/wiki/NginxCodeReview/langwan/blog/category/%D4%B4%C2%EB%B7%D6%CE%F6网上分析nginx源码的文章很多,但感觉分析的不够具体和完整,而且都是比较老的nginx版本。
本源码分析基于nginx-0.8.38版本,力求做到更具体和更完整,这是一种自我学习,希望和对此有兴趣的朋友一起探讨,有不正确的地方,也请各位指正。
那么一切从main开始吧!ngx_get_options函数是main调用的第一个函数,比较简单,它负责分析命令行参数,将相应的值赋给对应的全局变量,其中:1.ngx_prefix表示nginx的路径前缀,默认为/usr/local/nginx;2.ngx_conf_file表示nginx配置文件的路径,默认为/usr/local/nginx/conf/nginx.conf;3.ngx_test_config表示是否开启测试配置文件,如配置文件的语法是否正确,配置文件是否可正确打开。
ngx_time_init函数格式化nginx的日志时间,包括ngx_cached_err_log_time,ngx_cached_http_time,ngx_cached_http_log_time,ngx_cached_time。
主要操作在ngx_time_update 内,先获取系统当前时间,与之前保存的时间比较(注意slot),如果已经过时,则将时间重新更新,ngx_cached_time总是指向当前时间的cached_time。
最后还使用了内存屏障ngx_memory_barrier,确保读写顺序。
ngx_log_init函数初始日志结构,主要是对ngx_log变量操作。
初始log 级别为NGX_LOG_NOTICE。
接着分析error_log日志路径,获得完整的日志路径(默认为:/usr/local/nginx/logs/error.log)后,打开日志,获取对应的fd。
接下来碰到了ngx_cycle,这是一个非常重要的变量,初始指向init_cycle,然后创建了1024大小的内存池,在后面将会使用到。
ngx_save_argv函数将命令行参数浅拷贝一份到ngx_os_argv,深拷贝一份到ngx_argv(为什么要复制两份?)。
并将环境变量浅拷贝一份到ngx_os_environ。
ngx_process_options获取配置文件nginx.conf的绝对路径。
默认下,ngx_cycle的如下成员分别为:1. conf_prefix =/usr/local/nginx/conf;2. prefix = /usr/local/nginx/;3. conf_file = /usr/local/nginx/conf/nginx.conf。
ngx_os_init函数获取OS名称和版本号,CPU个数,单个进程能打开的最大文件数,修改ps命令显示的nginx进程名称。
获取OS信息是通过ngx_os_specific_init实现的,其内调用了uname,最重要的语句是ngx_os_io = ngx_linux_io,看看这个结构的组成:ngx_os_io_t ngx_os_io = {ngx_unix_recv,ngx_readv_chain,ngx_udp_unix_recv,ngx_unix_send,ngx_writev_chain,};static ngx_os_io_t ngx_linux_io = {ngx_unix_recv,ngx_readv_chain,ngx_udp_unix_recv,ngx_unix_send,#if (NGX_HAVE_SENDFILE)ngx_linux_sendfile_chain,NGX_IO_SENDFILE#elsengx_writev_chain,#endif};typedef struct {ngx_recv_pt recv;ngx_recv_chain_pt recv_chain;ngx_recv_pt udp_recv;ngx_send_pt send;ngx_send_chain_pt send_chain;ngx_uint_t flags;} ngx_os_io_t;更改了ngx_os_io 的默认配置,注册了linux系统的钩子函数,唯一不同的是ngx_linux_sendfile_chain,里面用系统函数sendfile实现两个文件之间的数据传输,它直接在内核空间拷贝数据,非常高效。
ngx_init_setproctitle实现更改进程名称,因为argv[]和environ[]是相续存储,先遍历完argv,这时ngx_os_argv_last=environ[0],并将environ保存在新分配的内存p中,最后ngx_os_argv_last=argv[0],此函数之前有详细注解,请仔细理解。
ngx_cpuinfo函数获取cpu信息,主要是得到缓存行大小,保存在ngx_cacheline_size中,也可以通过/proc/cpuinfo获取。
在linux中,ngx_inherited_nonblocking=0。
另外,ngx_ncpu最多为1?ngx_add_inherited_sockets函数打开上次保存在环境变量NGINX里的socket,linux一般不设置这个变量,直接返回。
接着遇到了ngx_modules变量,这是nginx模块化思想的实现核心。
每个模块都会注册自己需要的钩子函数。
for (i = 0; ngx_modules[i]; i++) {ngx_modules[i]->index = ngx_max_module++;}以上代码主要是对每个模块建立索引,ngx_max_module保存总的模块数。
ngx_init_cycle函数是个庞然大物,嗯......那就把它留到下章讲解了。
可以看到,nginx大量使用全局变量,这是一个令人头疼的问题!继续分析ngx_init_cycle函数,该函数以init_cycle作为实参,而ngx_cycle是指向它的。
ngx_init_cycle一上来就是更新时区和时间,why?必要吗?紧跟着创建一个NGX_CYCLE_POOL_SIZE大小的内存池,并在该内存池上创建了新的cycle(类型为ngx_cycle_t),然后初始化成员pool、log、new_log、conf_prefix、prefix、conf_file、conf_param、pathes、open_files、shared_memory、listening,值得一提的是cycle->conf_ctx =ngx_pcalloc(pool, ngx_max_module * sizeof(void *)),这个成员在以后索引相应模块的context配置信息非常重要,很快就会看到它的用处。
下面的代码遍历类型为NGX_CORE_MODULE的各个模块,调用模块context 里注册的create_conf函数,该钩子函数基本是初始化配置信息,并将返回的配置信息保存在cycle->conf_ctx中。
for (i = 0; ngx_modules[i]; i++) {if (ngx_modules[i]->type != NGX_CORE_MODULE) {continue;}module = ngx_modules[i]->ctx;if (module->create_conf) {rv = module->create_conf(cycle);if (rv == NULL) {ngx_destroy_pool(pool);return NULL;}cycle->conf_ctx[ngx_modules[i]->index] = rv;}}开始深入到各NGX_CORE_MODULE模块里的create_conf分析:1.ngx_core_module模块,对应的钩子函数是ngx_core_module_create_conf,主要工作就是创建ngx_core_conf_t结构,该结构成员表示的意思可以查看网址/NginxChsHttpMainModule,thanks wiki;2.ngx_errlog_module模块,对应的钩子函数是NULL,让人省事的NULL;3.ngx_events_module模块,又见到可爱的NULL;4.ngx_http_module模块,多来些NULL吧。
这么看来,上面的代码好像也没做啥事情。
配置信息,嗯,到了初始化conf(类型为ngx_conf_t),注意conf.ctx = cycle->conf_ctx,conf.module_type = NGX_CORE_MODULE,conf.cmd_type = NGX_MAIN_CONF。
进入ngx_conf_param函数。
如果启动nginx时,使用了-g选项,那么这个函数就是用来分析后面所带的参数,否则,直接退出。
看看ngx_conf_parse,它的解析分三种类型:parse_file(如果形参带的filename有效,那就打开文件,建立缓冲区),parse_block(这个是文件的内容已经装到缓冲区了,分析{}里面的内容),parse_param(这个就是处理-g选项所带的参数或者是解析出来的参数)。
ngx_conf_read_token函数就是读取配置文件nginx.conf里的内容,取得name-value对。
cf->handler当前为NULL。
ngx_conf_handler函数遍历类型为NGX_CORE_MODULE或NGX_CONF_MODULE的模块,调用这些模块commands里的set钩子,取得相应的value。
如果commands 的type=NGX_CONF_BLOCK,那么则需last=NGX_CONF_BLOCK_START,否则,则需last=NGX_OK。
然后取得本地conf的地址,这个地址就是cycle->conf_ctx对应的模块索引(还记得conf.ctx = cycle->conf_ctx?)。
然后是各模块的set钩子分析:1.ngx_core_module模块,commands注册为ngx_core_commands,它里面注册的set钩子很多,但都比较简单,就是取得对应name的value,需要关注的是name=worker_processes,这是启动工作者进程的个数;2.ngx_errlog_module模块,commands注册为ngx_errlog_commands,它里面只注册了一个set钩子-----ngx_error_log。