首页 Nginx源码研究_self

Nginx源码研究_self

举报
开通vip

Nginx源码研究_selfNginx源码研究 3概貌 5内存池 5内存分配相关函数 5内存池结构 6相关函数 8小结 9Array 9结构 9相关函数 10Queue 10结构 11相关函数 11Hash table 11结构 13相关函数 14List 14结构 14相关函数 15Nginx启动处理 18ngx_init_cycle() 19ngx_single_process_cycle() 19ngx_master_process_cycle() ...

Nginx源码研究_self
Nginx源码研究 3概貌 5内存池 5内存分配相关函数 5内存池结构 6相关函数 8小结 9Array 9结构 9相关函数 10Queue 10结构 11相关函数 11Hash table 11结构 13相关函数 14List 14结构 14相关函数 15Nginx启动处理 18ngx_init_cycle() 19ngx_single_process_cycle() 19ngx_master_process_cycle() 20信号对照 关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf 21ngx_reap_children 21ngx_start_worker_processes() 22Work进程逻辑(ngx_worker_process_cycle()函数) 231.进程部份 23ngx_worker_process_init() 242.线程部份 24ngx_worker_thread_cycle() 243.回到进程 25Cycle 26Connection 26connection的内存分布 26connection的分配与回收 27Event 27结构 31相关函数 31ngx_events_block() 32ngx_event_process_init() 33ngx_event_accept() 34ngx_process_events_and_timers() 34ngx_handle_read/write_event 34事件处理流程 36Connection 36结构 39相关函数 40Connection与Event 40HTTP流程 42ngx_http_block() 43ngx_http_init_request 43ngx_http_phases 44ngx_http_finalize_request 44Bufs 46Upstream 47ngx_http_upstream_add 47Location的构造 47ngx_http_request 47ngx_http_subrequest 48ngx_http_post_request 49模块笔记 Nginx的源码是0.8.16版本。不是最新版本,但是与网上其他人研究nginx的源码有所修改。阅读时注意参照对比。 概貌 Nginx可以开启多个进程,每个进程拥有最大上限128个子线程以及一定的可用连接数。如果你希望使用线程可以在配置文件中设置worker_threads这个参数,但这个参数在Nginx官方手册上没有。只有通过阅读源代码才看到。最大客户端连接数等于进程数与连接数的乘积,连接是在主进程中初始化的,一开始所有连接处于空闲状态。 每一个客户端请求进来以后会通过事件处理机制,在Linux是Epoll,在FreeBSD下是KQueue放到空闲的连接里。 如果设置了线程数,那么被填充的连接会在子线程中处理,否则会在主线程中依次处理。 nginx由以下几个元素组成: 1. worker(进程) 2. thread(线程) 3. connection(连接) 4. event(事件) 5. module(模块) 6. pool(内存池) 7. cycle(全局设置) 8. log(日志) 整个程序从main()开始算     ngx_max_module = 0;     for (i = 0; ngx_modules[i]; i++) {         ngx_modules[i]->index = ngx_max_module++;     } 这几句比较关键,对加载的模块点一下数,看有多少个。ngx_modules并不是在原代码中被赋值的,你先执行一下./configure命令生成用于编译的make环境。在根目录会多出来一个文件夹objs,找到ngx_modules.c文件,默认情况下nginx会加载大约30个模块,的确不少,如果你不需要那个模块尽量还是去掉好一些。 接下来比较重要的函数是 ngx_init_cycle(),这个函数初始化系统的配置以及网络连接等,如果是多进程方式加载的会继续调用ngx_master_process_cycle(),这是main函数中调用的最关键的两个函数。 ngx_init_cycle()实际上是个复杂的初始化函数,首先是加载各子模块的配置信息、并初始化各组成模块。 任何模块都有两个重要接口组成,一个是create_conf,一个是init_conf。分别是创建配置和初始化配置信息。 模块按照先后顺序依次初始化,大概是这样的: 内核模块(ngx_core_module), 错误日志(ngx_errlog_module), 配置模块(ngx_conf_module), 事件模块(ngx_events_module), 事件内核模块(ngx_event_core_module), EPOLL模块(ngx_epoll_module), http模块(ngx_http_module), http内核模块(ngx_http_core_module), http日志模块(ngx_http_log_module), … … epoll是比较关键的核心模块之一,nginx兼容多种IO控制模型。 内存池 内存分配相关函数 ngx_alloc.c中包括所有nginx内存申请的相关函数。 ngx_alloc() 包装了malloc(),仅添加了内存分配失败时的,log输出和debug时的log输出。 ngx_calloc() 调用上面的函数,成功分配后,将内存清零。 ngx_memalign() 也是向操作系统申请内存,只不过采用内存对齐方式。估计是为了减少内存碎片。如果操作系统支持posix_memalign()就采用它,如果支持memalign()则用memalign()。在0.8.19版本中,作者不再使用ngx_alloc(),而全部改用ngx_memalign()。注:在nginx的main()函数中,通过将ngx_pagesize 设置为1024来指定内存分配按1024bytes对齐。这是不是意味着你虽指示分配10 bytes的内存,实际上nginx也向操作系统申请至少1024bytes的内存。 内存池结构 Nginx的内存池类型是ngx_pool_t。这个类型定义在ngx_core.h中。 内存池管理结点: typedef struct { u_char *last; /* 指向所使用内存的最后的地址 */ u_char *end; /* 指向所申请到内存块的最后的地址 */ ngx_pool_t *next; /* 指向下一个ngx_pool_t */ ngx_uint_t failed; /* 这个 */ } ngx_pool_data_t; 内存池管理队列的头结点: struct ngx_pool_s { ngx_pool_data_t d; size_t max; /* 内存池块中空闲空间的大小 */ ngx_pool_t *current; /* 指向当前块自身 */ ngx_chain_t *chain; ngx_pool_large_t *large; /* 用来指向大块内存 */ ngx_pool_cleanup_t *cleanup; /* 释放内存时调用的清除函数队列 */ ngx_log_t *log; /* 指向log的指针 */ }; 清除函数的指针如下: typedef void (*ngx_pool_cleanup_pt)(void *data); struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; /* 清除函数 */ void *data; /* 清除函数所用的参数 */ ngx_pool_cleanup_t *next; /* 下一个结点的指针 */ }; struct ngx_pool_large_s { ngx_pool_large_t *next; /* 指向下一个大块内存。*/ /* 大块内存也是队列管理。*/ void *alloc; /* 指向申请的大块数据 */ }; 相关函数 ngx_create_pool() 函数用来创建内存池。 第一步,调用ngx_alloc()申请内存; 第二步,设置ngx_pool_t中的成员d中的各个变量; … p->d.last = (u_char *) p + sizeof(ngx_pool_t); p->d.end = (u_char *) p + size; … 从代码看出,d.end指向内存块的结尾处,而d.last则指向所占用的内存的结尾处。刚申请的内存中占用ngx_pool_t结构作为管理单元。所以,此时d.last指向(u_char *) p + sizeof(ngx_pool_t)处。 第三步,设置其他成员。注意:在计算max时,max中存放的数指所申请内存块中空闲的大小。因此,在计算max之前先减去了管理结点本身的大小。 ngx_destroy_pool() 用来释放内存池,一共分三步: 第一步、在释放前先对业务逻辑进行释放前的处理     for (c = pool->cleanup; c; c = c->next) {         if (c->handler) {             ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,                            "run cleanup: %p", c);             c->handler(c->data);         }     } 第二步、释放large占用的内存     for (l = pool->large; l; l = l->next) {         ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);         if (l->alloc) {             ngx_free(l->alloc);         }     } 第三步、释放所有的池子 for (p = pool, n = pool->next; /* void */; p = n, n = n->next) {         ngx_free(p);         if (n == NULL) {             break;         }   } ngx_palloc_large() 函数专用来申请大块内存。 第一步,申请的大块内存,在ngx_pool_t中大块他队列中寻找空闲的ngx_pool_larger结点。如果找到,将大块内存挂在该结点上。 ngx_pool_larger队列中查找空闲结点数不会超过三次。超过三个结点没找到空闲结点就放弃。 创建一个新的结点,将申请到地大块内存挂在这个新结点上。将这个结点插入队列头部。 ngx_palloc() 函数用来申请内存块。首先要说明的是内存池中可能是由多块内存块组成的队列。其中每块内存都有一个ngx_pool_t管理结点用来连成管理队列。 1. 如果申请的内存大小超过了当前的内存池结点中空闲空间,nginx直接采用ngx_palloc_large()函数进行大块内存申请; 2. 在内存池管理队列中寻找能够满足申请大小的管理结点。如果找到了,先按32位对齐方式计算申请内存的指针,再将last指向申请内存块的尾部。 3. 找不到合适的内存池,用ngx_palloc_block()函数。 ngx_pnalloc() 函数与ngx_palloc()函数唯一不同之处,就是在计算申请内存的指针的方式未按32位对齐方式计算。 ngx_palloc_block() 函数用来分配新的内存池块,形成一个队列。 这个函数中申请到新的内存池块后,在该块中分配完ngx_pool_data_t结点后,将这个结点挂在内存池队列的结尾处。 这个函数中有两个要注意的地方: 1. 在内存池块中保留ngx_pool_data_t时,不仅是按ngx_pool_data_t大小计算而且是按32位对齐。 2. ngx_pool_data_t结构中的failed的妙用。单从字面上不是太好理角这个成员变量的作用。实际上是用来计数用的。 for (p = current; p->d.next; p = p->d.next) { if (p->d.failed++ > 4) { current = p->d.next; } } 从上面这段代码是寻找内存池队列的尾部。当队列较长,由于内存池管理队列是单向队列所以每次从头到尾搜索是很费时的。每次搜寻失败的结点(非尾部结点)的failed加1。failed指出了该结点经历多少次查寻,目前版本中超过4次时,将内存池的current指针指向其后续的结点。这样,下次再做类似查询时,可以跳过若干不必要的结点加快查询速度。 ngx_pmemalign() 函数采用内存对齐的方式申请大内存块。 ngx_pfree() 函数用来释放大内存块。成功返回NGX_OK,失败返回NGX_DECLINED。 ngx_pcalloc() 函数使用ngx_palloc()函数申请内存后,将申请的内存清零。 ngx_pool_cleanup_add() 函数只是用来添加内存池的cleanup队。 由ngx_pool_cleanup_t类型的结点组成了内存池的清除函数处理队列。后加入队列的函数先调用。Nginx中预定义了两个cleanup函数。 void ngx_pool_cleanup_file(void *data) 用来关闭打开的文件。 void ngx_pool_delete_file(void *data) 用来删除文件并且试图关闭文件。 小结 下面是nginx内存池的概貌图。 Array 结构 struct ngx_array_s { void *elts; /* 指向数组元素的起始地址 */ ngx_uint_t nelts; /* 现使用的元素的数目 */ size_t size; /* 元素的大小 */ ngx_uint_t nalloc; /* 分配数组中元素的数目 */ ngx_pool_t *pool; /* 指向所在的内存池的指针 */ }; 注:nelts应该小于等于nalloc。例:分配了5个元素,使用了3个元素;则nalloc = 5, nelts = 3。 相关函数 ngx_array_init() 函数,在指定的array对象上分配n个元素,元素大小为size。 ngx_array_create()函数,函数参数中没有array对象的指针。这个函数在内存池中创建一个array对象,并且分配n个元素,元素大小为size。 ngx_array_push()函数,将array对象当作堆栈,作压栈处理。如果当前内存池没有空闲空间可用,就会申请新的内存池并且创建一个是原来array对象两倍大小的新array,原array对象中的元素复制到新array中。 ngx_array_push_n()函数,与ngx_array_push()函数功能类似。ngx_array_push_n()是压n个元素,ngx_array_push()压入一个元素。 Queue 结构 typedef struct ngx_queue_s  ngx_queue_t; struct ngx_queue_s {     ngx_queue_t  *prev;     ngx_queue_t  *next; }; 注意nginx的队列操作和结构只进行指针的操作,不负责节点内容空间的分配和保存,所以在定义自己的队列节点的时候,需要自己定义数据结构以及分配空间, 并包含一个ngx_queue_t类型的成员, 需要获得原始的数据节点的时候需要使用ngx_queue_data宏 #define ngx_queue_data(q, type, link) (type *) ((u_char *) q - offsetof(type, link)) 另外,整个queue结构中包含一个 sentinel(哨兵) 节点, 他指向队列的头和尾 相关函数 ngx_queue_init(()函数,初始化队列; ngx_queue_insert_head()函数,在队列头部插入一个元素; ngx_queue_sentinel()函数,取得队列的哨兵; ngx_queue_prev()函数,取得前一个元素; ngx_queue_last()函数,取得队列最后一个元素; ngx_queue_sort()函数,对队列中的元素进行排序; ngx_destroy_pool()函数,销毁队列。 Hash table 结构 这个在模块配置解析里经常被用到。该 hash 结构是只读的,即仅在初始创建时可以给出保存在其中的 key-val 对,其后就只能查询而不能进行增删改操作了。 typedef struct { void *value; u_char len; u_char name[1]; } ngx_hash_elt_t; typedef struct { ngx_hash_elt_t **buckets; ngx_uint_t size; } ngx_hash_t; typedef struct { ngx_hash_t hash; void *value; } ngx_hash_wildcard_t; typedef struct { ngx_str_t key; ngx_uint_t key_hash; void *value; } ngx_hash_key_t; typedef ngx_uint_t (*ngx_hash_key_pt) (u_char *data, size_t len); typedef struct { ngx_hash_t hash; ngx_hash_wildcard_t *wc_head; ngx_hash_wildcard_t *wc_tail; } ngx_hash_combined_t; typedef struct { ngx_hash_t *hash; ngx_hash_key_pt key; ngx_uint_t max_size; ngx_uint_t bucket_size; char *name; ngx_pool_t *pool; ngx_pool_t *temp_pool; } ngx_hash_init_t; 相关函数 虽然代码理解起来比较混乱,但是使用还是比较简单的,常用的有创建 hash 和在 hash 中进行查找两个操作,对于创建hash的操作,过程一般为: a) 构造一个 ngx_hash_key_t 为成员的数组, 包含 key, value 和 使用key计算出的一个hash值 b) 构建一个 ngx_hash_init_t结构体的变量, 其中包含了ngx_hash_t 的成员, 为hash的结构体, 还包括一些其他初始设置,如bucket的大小,内存池等 c) 调用 ngx_hash_init 传入 ngx_hash_init_t 结构, ngx_hash_key_t 的数组,和数组的长度, 进行初始化,这样 ngx_hash_init_t的hash成员就是我们要的hash结构 查找的过程很简单 a) 计算 key 的hash值; b) 使用ngx_hash_find进行查找,需要同时传入hash值和key,返回的就是value的指针。 List 结构 struct ngx_list_part_s { void *elts; /* list中元素 */ ngx_uint_t nelts; /* 使用data多少字节,初值为0 */ ngx_list_part_t *next; /* 指向下一个list part结点 */ }; typedef struct { ngx_list_part_t *last; /* 指向最后一个list part结点 */ ngx_list_part_t part; /* list的头结点中list part部份 */ size_t size; /* 元素大小 */ ngx_uint_t nalloc; /* list part结点中申请的元素的容量大小 */ ngx_pool_t *pool; /* 指向内存池 */ } ngx_list_t; 相关函数 ngx_list_init() 函数创建一个新的list对象设置ngx_list_t指针,内存池中申请的头结点大小为n * size。 1. ngx_list_create() 函数中没有指定list指针,创建一个新的list对象,头结点大小为n*size。 2. ngx_list_push() 函数,将list当作堆栈将元素压栈,如果当前的list结点中没有空间,则在内存池中申请一个新的list结点,且作为堆栈的顶部。 3. 遍历一个list的方法 part = &list.part;  data = part->elts;     for (i = 0 ;; i++) {    if (i >= part->nelts) {       if (part->next == NULL) {          break;       }         part = part->next;       data = part->elts;       i = 0;    }       ...  data[i] ...    } 这段示例代码中,part->nelts表示占用的数据区中的字节数,每到一个节点将i设为0;所以i>=part->nelts满足时表示当前list节点中数据处理完了,于是跳到下一个节点,在对下一个节点的数据进行处理之前,再将i设为0。 Nginx启动处理 1. 从main()函数开始,进行了一系列的初始化处理工作。下面将分别介绍,对于不是很重要或是很好理解力的部分可能不作详细说明。 a) 首先是从命令行获取参数,打印参数的用法说明。 b) ngx_time_init()函数。三个全局ngx_str_t变量err_log、http、http_log时间格式。 c) ngx_time_update()函数,取得当前的毫秒数,获取年月日时分秒以及时区转换。 2. 用ngx_getpid()获取nginx的pid。 3. 如果宏NGX_OPENSSL定义了,在ngx_ssl_init()函数中载入open ssl的库。 4. 随后在全局的ngx_cycle变量中,创建内存池,大小为1024。 5. ngx_log_init()函数 6. ngx_save_argv()函数则是将从main()函数传入的参数表保存到nginx的全局变量ngx_argc,ngx_argv[],ngx_os_argv,ngx_os_environ中。 7. ngx_process_options()函数,做下面工作: a) 确定前缀路径 b) 接着用ngx_conf_full_name()处理conf路径 c) ngx_conf_params存在时,将其保存到当前的cycle(即init_cycle)中; d) ngx_test_config(测试conf后退出)1是,cycle中log_level设为NGX_LOG_INFO。 8. ngx_os_init()函数,在NGX_HAVE_OS_SPECIFIC_INIT宏时,调用ngx_os_specific_init()进行特定操作系统初始化。linux主要是获取当前操作系统发行版的全称,内核名称,类型,及操作最信号的限制等; 调用ngx_init_setproctitle()函数的逻辑如下: a) 计算environ[]的大小,然后在内存池中分配足够大的空间; b) 将ngx_os_argv_last定位到ngx_os_argv[]尾部(即)enivorn[]起始处); c) 循环地将enivorn[]中环境变量复制到内存池已分配的空间中; d) ngx_os_argv_last最后定位到最后的环境变量。 e) 调用getpagesize()函数取得pagesize大小;用NGX_CPU_CACHE_LINE(编译前由configure脚本程序确定)设定ngx_cacheline_size。 f) for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ } g) ngx_cpuinfo:ngx_ncpu为0,则设为1;接着调用ngx_cpuinfo()函数,取得cpu类型,制造商,所支持的cacheline的大小。 h) 调用getrlimit(RLIMIT_NOFILE, &rlmt)函数,取得进程所能打开最大文件数 i) 初始化随机种子(srandom(ngx_time()))。 9. ngx_crc32_table_init()函数中初始化用于crc32循环冗余校验的数据表。 10. ngx_add_inherited_sockets()函数中,继承父进程中的socket。 a) 在内存池中预分配保存地址结构的空间; b) 调用getsockname()函数获取与socket相关连的地址,如果出错设定ignore为1; c) 根据socket地址的类型,设置地址文本格式的最大长度;如果不是IPv4和IPv6类型设定ignore为1; d) 地址文本格式的最大长度加上端口的文本格式的最大长度; e) 使用ngx_sock_ntop()函数,将socket绑定的地址转换为文本格式(IPv4和IPv6的不相同); f) 设置每个监听的socket的backlog(NGX_LISTEN_BACKLOG定义为511); g) 获取SO_RCVBUF和SO_SNDBUF选项的大小,保存与当前socket对应的ngx_listening_t结构中; h) 支持accept filter时,通过SO_ACCEPTFILTER选项取得socket的accept_filter表,保存在对应项的accept_filter中; SO_ACCEPTFILTER places an accept_filter on the socket, which will filter incoming connections on a listening stream socket before being presented for accept. Once more, listen must be called on the socket before trying to install the filter on it, or else the setsockopt system call will fail. struct accept_filter_arg { char af_name[16]; char af_arg[256-16]; }; i) 如果当前所在操作系统TCP层支持TCP DEFER ACCEPT功能,则试图获取TCP_DEFER_ACCEPT的timeout值。Timeout大于0时,则将socket对应deferred_accept标志设为1。 TCP_DEFER_ACCEPT 我 们首先考虑的第1个选项是TCP_DEFER_ACCEPT(这是Linux系统上的叫法,其他一些操作系统上也有同样的选项但使用不同的名字)。为了理 解TCP_DEFER_ACCEPT选项的具体思想,我们有必要大致阐述一下典型的HTTP客户/服务器交互过程。请回想下TCP是如何与传输数据的目标 建立连接的。在网络上,在分离的单元之间传输的信息称为IP包(或IP 数据报)。一个包总有一个携带服务信息的包头,包头用于内部协议的处理,并且它也可以携带数据负载。服务信息的典型例子就是一套所谓的标志,它把包标记代 表TCP/IP协议栈内的特殊含义,例如收到包的成功确认等等。通常,在经过“标记”的包里携带负载是完全可能的,但有时,内部逻辑迫使TCP/IP协议 栈发出只有包头的IP包。这些包经常会引发讨厌的网络延迟而且还增加了系统的负载,结果导致网络性能在整体上降低。 现在服务器创建了一个套接 字同时等待连接。TCP/IP式的连接过程就是所谓“3次握手”。首先,客户程序发送一个设置SYN标志而且不带数据负载的TCP包(一个SYN包)。服 务器则以发出带SYN/ACK标志的数据包(一个SYN/ACK包)作为刚才收到包的确认响应。客户随后发送一个ACK包确认收到了第2个包从而结束连接 过程。在收到客户发来的这个SYN/ACK包之后,服务器会唤醒一个接收进程等待数据到达。当3次握手完成后,客户程序即开始把“有用的”的数据发送给服 务器。通常,一个HTTP请求的量是很小的而且完全可以装到一个包里。但是,在以上的情况下,至少有4个包将用来进行双向传输,这样就增加了可观的延迟时 间。此外,你还得注意到,在“有用的”数据被发送之前,接收方已经开始在等待信息了。 为了减轻这些问题所带来的影响,Linux(以及其他的 一些操作系统)在其TCP实现中包括了TCP_DEFER_ACCEPT选项。它们设置在侦听套接字的服务器方,该选项命令内核不等待最后的ACK包而且 在第1个真正有数据的包到达才初始化侦听进程。在发送SYN/ACK包之后,服务器就会等待客户程序发送含数据的IP包。现在,只需要在网络上传送3个包 了,而且还显著降低了连接建立的延迟,对HTTP通信而言尤其如此。 对于那些支持deffered accept的操作系统,nginx会设置这个参数来增强功能,设置了这个参数,在 accept的时候,只有当实际收到了数据,才唤醒在accept等待的进程,可以减少一些无聊的上下文切换,如下: val = 5; setsockopt(socket_fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val)); kernel 在 val 秒之内还没有收到数据,不会继续唤醒进程,而是直接丢弃连接,如果connect之后立刻收到数据,kernel才创建数据套接口并唤醒在accept上等待的进程。 11. ngx_init_cycle()函数,这是个比较重要的函数,它主要工作是,初始化cycle是基于旧有的cycle进行的,比如这里的 init_cycle,会继承old cycle的很多属性, 比如log等, 但是同时会对很多资源重新分配,比如pool, shared mem, file handler, listening socket 等,同时清除旧有的cycle的资源。 12. ngx_os_specific_status()函数中,仅仅是在特殊操作系统情况下 记录 混凝土 养护记录下载土方回填监理旁站记录免费下载集备记录下载集备记录下载集备记录下载 操作系统名称以及相关信息。 13. 计算nginx的模块数,同时设置每个模块的index值。 14. 如果nginx的命令行参数中设置了signal值,则进行ngx_signal_process()处理。 ngx_signal_process()函数先取得自己的pid。(可能是从/proc目录下nginx的pid文件中读取的pid值。仅是猜测,目前还不清楚是不是这样。)再调用ngx_os_signal_process() 函数,来向pid代表的进程发送信号。 15. ngx_os_status()函数则是对应不同的操作系统,将不操作系统的内核版本,发布版本等写出到log文件中。不同系统输出内容不一样。 16. ngx_cycle = cycle ; …… 17. 随后ngx_init_signals()函数中,针对singals表中的信号安装信号处理函数。 Nginx中的信号处理函数是ngx_signal_handler()。 在ngx_signal_handler()函数中,会检查收到的信号是不是nginx有效的信号,取得当前系统的时间。 接着根据不同的进程模式——NGX_PROCESS_MASTER,NGX_PROCESS_SINGLE,NGX_PROCESS_WORKER,对应不同信号进行相应处理。 NGX_PROCESS_MASTER与NGX_PROCESS_SINGLE模式下信号处理方式一致。其中NGX_CHANGEBIN_SIGNAL信号需要特别在意。 收到这个信号表示要进程切换,ngx_change_binary置为1。如果收到该信号的进程是下列两种情况,就忽略信号。 a) 新启动的进程,其父进程不是init; b) 调用ngx_daemon()将进程置为daemon运行模式。 18. ngx_create_pidfile()函数将pid写入文件。 19. 将标准错误输出重定向到当前打开的log文件。 20. ngx_use_stderr = 0; 21. 根据进程模式是NGX_PROCESS_SINGLE还是NGX_PROCESS_MASTER分别调用ngx_single_process_cycle()或ngx_master_process_cycle()。他们启动工作进程干活、处理信号量,处理的过程中会杀死或者创建新的进程。 1. ngx_reap_children()函数,这个函数清理要退出的进程。ngx_processes数组中每个项保存对应一个进程的信号,有pid,status,channel等。如果有子进程还没有退出exiting或! detached,则live设为1。 2. ngx_master_process_exit()函数,这个函数删除pid文件,接着调用模块中的exit_master钩子函数,销毁内存池。 ngx_init_cycle() · 调用ngx_timezone_update()更新时区,调用ngx_time_update()更新时间 · 创建大小为NGX_CYCLE_POOL_SIZE=16384B内存池,并分配ngx_cycle_t结构 · 简单初始化,如记录pool指针、log指针 · 初始化配置前缀、前缀、配置文件、配置参数等字符串 · 初始化pathes数组 · 初始化open_files链表 · 初始化shared_memory链表 · 初始化listening数组 · 初始化resuable_connections_queue队列 · 从pool为conf_ctx分配空间 · 初始化hostname字符串 · 调用core模块的create_conf() · 配置文件解析: · 调用ngx_conf_param()函数,把命令行中的指令(-g directives)转换为配置结构并把指针加入到cycle.conf_ctx中; · 接着调用ngx_conf_parse()函数把配置文件中的指令转换为配置结构并把指针加入到cycle.conf_ctx中; 配置的更新主要是在ngx_conf_parse中进行的,这个函数中有一个for循环,每次循环调用ngx_conf_read_token取得一个配置指令(以;结尾),然后调用ngx_conf_handler处理这条指令,ngx_conf_handler每次都会遍历所有模块的指令集,查找这条配置指令并分析其合法性,如果指令正确,则会创建配置结构并把指针加入到 cycle.conf_ctx中,配置结构的赋值是调用该指令的钩子set完成的。 · 调用core模块的init_conf() · 遍历open_files链表中的每一个文件并打开 · 创建共享内存并初始化(新旧shared_memory链表的比较,相同的共享内存保留,旧的不同的共享内存被释放,新的被创建) · (尝试5遍)遍历listening数组并打开所有侦听sockets(socket()->setsockopt()->bind()->listen()) · 提交新的cycle配置,并调用所有模块的init_module(实际上只有ngx_event_core_module模块定义了该callback,即只有ngx_event_module_init()被调用) · 关闭或删除残留在old_cycle中的资源 · 释放多余的共享内存 · 关闭多余的侦听sockets · 关闭多余的打开文件 ngx_single_process_cycle() a) 遍历所有模块的,如果模块有init钩子函数,调用之; b) 接下来进入一个循环,先调用ngx_process_events_and_timers(),猜测是进行nginx事件处理,后面再分析。 c) 随后,如果是终结或是退出状态,先调用各模块中的exit_process钩子函数,接着调用ngx_master_process_exit()函数退出程序; d) ngx_reconfigure标志为1,重新创建并初始化ngx_cycle对象; e) ngx_reopen标志为1,重新打开log文件。 f) ngx_master_process_exit() 函数先删除保存pid的文件,再调用各个模块中exit_master钩子函数,最后销毁内存池对象。 ngx_master_process_cycle() 函数,这个函数会启动工作进程干活,并且会处理信号量,处理的过程中会杀死或者创建新的进程。 a) 阻塞所有nginx关心的信号; b) 设置进程的title(有点类似命令行形式的一个字符串,应该不是很重要。); c) 启动一个work process进程;ngx_start_worker_processes()按指定数目n,以ngx_worker_process_cycle()函数为参数调用ngx_spawn_process()创建work进程并初始化相关资源和属性;执行子进程的执行函数ngx_worker_process_cycle;向之前已经创建的所有worker广播当前创建的worker进程的信息;每个进程打开一个通道(ngx_pass_open_channel())。 d) 启动一个缓冲管理进程;ngx_start_cache_manager_processes()函数,这个函数在ngx_cycle全局对象的path数组中,检查是否有manager和loader函数。有manage函数,创建缓冲管理进程,打开相应的通道,负责管理工作;有loader函数,创建缓冲管理进程,打开相应通道,负责载入工作 e) 初始化几个标志:ngx_new_binary = 0; delay = 0; live = 1; f) 循环对不同的状态(收到的不同信号)进行不同处理: a) delay不为0: SIGALRM:则将delay时间乘以2;设置一个实时计时器; b) 挂起当前进程信号,更新当前时间; c) ngx_reap为1(SIGCHLD),调用ngx_reap_children()回收子进程; d) (!live)且收到SHUTDOWN或TERMINATE信号,进行退出处ngx_master_process_exit();退出处理先删除pid文件,然后将调用所有模块的进程退出钩子,销毁内存池对象; e) ngx_terminate为1,delay为0,就设成50;如果delay>1000,向work进程发送SIGKILL信号,否则向work进程发送TERMINATE f) ngx_quit为1,向work进程发送SHUTDOWN信号,然后将所有全局listening中的socket全关闭;continue; g) ngx_reconfigure为1: · ngx_new_binary为1,启动work进程,启动缓冲管理进程,然后将ngx_noaccepting设为0;continue; · 重新创建并初始化ngx_cycle,重新读取config文件;启动work,启动cache_manager,将live设为1,向worker发送SHUTDOWN; h) ngx_restart为1(当ngx_noaccepting=1的时候会把ngx_restart设为1,重启worker),启动work进程,启动缓冲管理进程,live设为1; i) ngx_reopen为1:则重新找开log文件,发送REOPEN; j) ngx_change_binary为1:调用ngx_exec_new_binary()执行新进程; k) ngx_noaccept为1:设ngx_noaccepting为1,发送SHUTDOWN。 信号对照表 SIGALRM, "SIGALRM" SIGINT, "SIGINT" SIGIO, "SIGIO" SIGCHLD, "SIGCHLD" SIGSYS, "SIGSYS, SIG_IGN" SIGPIPE, "SIGPIPE,SIG_IGN" NGX_SHUTDOWN_SIGNAL QUIT NGX_TERMINATE_SIGNAL TERM NGX_NOACCEPT_SIGNAL WINCH NGX_RECONFIGURE_SIGNAL HUP #if (NGX_LINUXTHREADS) NGX_REOPEN_SIGNAL INFO NGX_CHANGEBIN_SIGNAL XCPU #else NGX_REOPEN_SIGNAL USR1 NGX_CHANGEBIN_SIGNAL USR2 ngx_reap_children 对ngx_processes表中每项发送CLOSE_CHANEL: Exited而!detached:设置相应ngx_channel_t,然后发送至每个ngx_processes项 Exited而respawn&& ! exiting&& !ngx_terminate&& !ngx_quit:ngx_spawn_process创建线程(进程),OPEN_CHANNEL;continue; Exited而pid == ngx_new_binary:ngx_get_conf,ngx_rename_file,如果ngx_noaccepting则设置 ngx_restart = 1; Exited而如果是最后一个项,则将ngx_last_process减一,否则pid设置为 -1; (!exited)&&(exiting || !detached):live = 1; 返回live ngx_start_worker_processes() 调用ngx_spawn_process,建立N个ngx_worker_process_cycle,再ngx_pass_open_channel ngx_spawn_process函数的主要逻辑如下: a) 首先要说明的是参数respawn有两种含义:type 即创建新进程式的方式,如NGX_PROCESS_RESPAWN, NGX_PROCESS_JUST_RESPAWN…,是负值;另一种表示进程信息表的下标,此时是非负; b) 查找ngx_processes[]进程信息表中一个空项; c) 若不是分离的子进程(respawn != NGX_PROCESS_DETACHED), i) 创建一对已经连接的无名socket; ii) 设置socket(channel[0]和channel[1]))为非阻塞模式; iii) 开启channel[0]的消息驱动IO; iv) 设置channel[0]的属主,控制channel[0]的SIGIO信号只发给这个进程; v) 设置channel[0]和channel[1]的FD_CLOEXEC属性(进程执行了exec后关闭socket); vi) 取得用于监听可读事件的socket(即ngx_channel = ngx_processes[s].channel[1];); d) 分离的子进程时,ngx_processes[s].channel[0] = -1;         ngx_processes[s].channel[1] = -1; e) 设置当前子进程的进程表项索引;ngx_process_slot = s;  f) 创建子进程; g) 子进程式中,设置当前子进程的进程id,运行执行函数; h) respawn >= 0时,直接返回pid; i) 设置其他的进程表项字段; ngx_processes[s].proc = proc;    ngx_processes[s].data = data;    ngx_processes[s].name = name;    ngx_processes[s].exiting = 0; j) 根据respawn表示的类型,按不同方式设置表项。 创建socketpair用于进程间通信,master进程为每个worker创建一对socket, master进程空间打开所有socketpair的channel[0],channel[1]两端句柄。 当创建一个worker的时候,这个worker会继承master当前已经创建并打开的所有socketpair,这个worker初始化的时候(调用ngx_worker_process_init)会关闭掉本进程对应socketpair的channel[0]和其他worker对应的channel[1],保持打开本进程对应socketpair的channel[1]和其他worker对应的channel[0],并监听本进程对应socketpair的channel[1]的可读事件
本文档为【Nginx源码研究_self】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_264154
暂无简介~
格式:doc
大小:1MB
软件:Word
页数:52
分类:互联网
上传时间:2013-02-05
浏览量:40