• 1. Nginx源码浅析 朱庆昌 2013.05.13
  • 2. 1. 源码概览www.founderbn.comPage2源码包解压之后,根目录下有几个子目录和几个文件,最重要的子目录是auto和src,最重要的文件是configure脚本,不同于绝大多数的开源代码,nginx的configure脚本是作者手工编写的,没有使用autoconf之类的工具去自动生成,configure脚本会引用auto目录下面的脚本文件来干活。 运行configure脚本完成后,会生成三个重要的文件ngx_auto_config.h,ngx_auto_headers.h,ngx_modules.c src是源码存放的目录,configure创建的objs/src目录是用来存放生成的.o文件的,注意区分一下。 src按照功能特性划分为几个部分,对应着是几个不同的子目录。 src/core存放着主干部分、基础数据结构和基础设施的源码,main函数在src/core/nginx.c中,这是分析源码的一个很好的起点。 src/event存放着事件驱动模型和相关模块的源码。 src/http存放着http server和相关模块的源码。 src/os存放着依赖于操作系统实现的源码,nginx启动过程中最重要的master和workers创建代码就在这个目录下。
  • 3. 1.源码概览nginx是一个master主进程+多个worker子进程的工作模式 (详细会在分析事件处理的时候解释),nginx主进程启动的过程中会按照初始化master、初始化模块、初始化工作进程、(初始化线程、退出线程)、退出工作进程、退出master顺序进行,而在这些子过程内部和子过程之间,又会有读取配置、创建配置、初始化配置、合并配置、http解析、http过滤、http输出、http代理等过程,在这些过程开始前后、过程中、结束前后等时机,nginx调用合适的模块接口完成特定的任务。 Main函数中主要入口函数: ngx_init_cycle,ngx_single_process_cycle, ngx_master_process_cycle www.founderbn.comPage3
  • 4. 1.源码概览Nginx是高度模块化的,nginx的核心类模块有7个,event类模块有10个,http类模块有47个,mail类模块有7个 参见ngx_modules.c www.founderbn.comPage4
  • 5. 2.子进程宕机监测和命令行控制机制两种机制主要是通过进程通信来实现的 查看代码ngx_spawn_process函数 1、找到一个空闲的ngx_processes,respawn重启一个子进程时会用到 2、用socketpair创建UNIX套接字channel 3、fork出子进程 4、将进程信息记录在master进程ngx_processes中 5、master进程调用ngx_pass_open_channel将command为NGX_CMD_OPEN_CHANNEL的信息发送给各个子进程 www.founderbn.comPage5
  • 6. 2.子进程宕机监测和命令行控制机制ngx_worker_process_cycle函数调用ngx_worker_process_init 在ngx_worker_process_init的操作: 1、 关闭进程池中其他子进程的 channel[1]; 2、 关闭自身的 channel[0]; 3、 将自身的 channel[1] 注册event读事件: ngx_channel_handler(). ngx_channel_handler中case为NGX_CMD_OPEN_CHANNEL分支 case NGX_CMD_OPEN_CHANNEL: ngx_log_debug3(NGX_LOG_DEBUG_CORE, ev->log, 0, "get channel s:%i pid:%P fd:%d", ch.slot, ch.pid, ch.fd); ngx_processes[ch.slot].pid = ch.pid; //记录新生成的进程的pid ngx_processes[ch.slot].channel[0] = ch.fd; //记录新与新生成的进程通信的fd break; www.founderbn.comPage6
  • 7. 2.子进程宕机监测和命令行控制机制以上机制实现的功能 : 自身可以通过 进程池中其他子进程的 channel[0] 收发其他子进程 的信息. 自身可以通过 自身的 channel[1] 收发 master进程 的信息. 执行nginx -s reload时,代码流程如下: 1、Main函数执行ngx_signal_process,从nginx.pid文件中解析出当前的 master 进程PID, 向其发送 reload 信号 2、master接收到 reload 信号, 信号处理函数 ngx_signal_handler 中将 ngx_reconfigure=1; 3、 ngx_master_process_cycle中 调用 ngx_init_cycle() 初始化环境,重新加载配置文件, 重新创建 worker进程, cache管理进程 4、 调用ngx_signal_worker_processes向各个子进程发送SIGQUIT信号 5、子进程退出,调用ngx_process_get_status回收子进程( waitpid )www.founderbn.comPage7
  • 8. 2.子进程宕机监测和命令行控制机制有子进程异常退出处理过程 1、master收到内核发送来的 SIGCHLD 信号, 信号处理函数 ngx_signal_handler() 将 ngx_reap=1, 调用 ngx_process_get_status() 回收子进程, 2、ngx_process_get_status() 回收子进程, 将 exited=1, 此时 exiting=0; 3、进入 ngx_reap 状态(ngx_master_process_cycle中), ngx_reap_children(), 通知其他子进程该子进程已经退出,请关闭它的channel[0], 重新创建异常退出的worker进程. 重新创建 老的可再生的子进程的条件是: (ngx_processes[i].exited && ngx_processes[i].respawn && !ngx_processes[i].exiting && !ngx_terminate && !ngx_quit) 这里除了 cache_loader 子进程(respawn=0)外, worker, cache_manager子进程(respawn=1)异常退出时都会被创建. www.founderbn.comPage8
  • 9. 2.子进程宕机监测和命令行控制机制有子进程异常退出处理过程 1、master收到内核发送来的 SIGCHLD 信号, 信号处理函数 ngx_signal_handler() 将 ngx_reap=1, 调用 ngx_process_get_status() 回收子进程, 2、ngx_process_get_status() 回收子进程, 将 exited=1, 此时 exiting=0; 3、进入 ngx_reap 状态(ngx_master_process_cycle中), ngx_reap_children(), 通知其他子进程该子进程已经退出,请关闭它的channel[0], 重新创建异常退出的worker进程. 重新创建 老的可再生的子进程的条件是: (ngx_processes[i].exited && ngx_processes[i].respawn && !ngx_processes[i].exiting && !ngx_terminate && !ngx_quit) 这里除了 cache_loader 子进程(respawn=0)外, worker, cache_manager子进程(respawn=1)异常退出时都会被创建. www.founderbn.comPage9
  • 10. 3.初识nginx配置文件www.founderbn.comPage10
  • 11. 4.配置文件读取配置文件读取过程 1、ngx_init_cycle中rv = module->create_conf(cycle); 2、保存此指针cycle->conf_ctx[ngx_modules[i]->index] = rv; 3、ngx_conf_read_token函数中,将每一行配置参数保存在cf->args数组中。 4、调用rc = ngx_conf_handler(cf, rc)函数对每行参数进行处理 5、比较cmd = ngx_modules[i]->commands数组每个关键字,对参数名字、个数等信息进行匹配。 6、匹配成功后,找到需要保存的位置conf = &(((void **) cf->ctx)[ngx_modules[i]->index]); 7、调用rv = cmd->set(cf, cmd, conf)函数将配置转化并放到特定位置保存 大家可以重点看一下ngx_http_block函数www.founderbn.comPage11
  • 12. 4.配置文件读取 set 是一个函数指针,这个函数主要是从配置文件中把该指令的参数(存放在ngx_conf_t中)转换为合适的数据类型并将转换后的值保存到模块的配置结构体中(void *conf),这个配置结构体又是用void *指向的,应该可以料到这说明每个模块的配置结构体是不同的,这些结构体命名格式为:ngx_*_conf_t,至于要把转换后的值放到配置结构体的什么位置,就要依靠offset了,offset是调用了offsetof函数计算出的结构体中某个成员的偏移位置。 并不是所有的模块都要定义一个配置结构体,因为set也可能是一个简单的操作函数,它可能只是从配置中(ngx_conf_t)读取一些数据进行简单的操作,比如errlog模块的“error_log”指令就是调用ngx_error_log写一条日志,并不需要存储什么配置数据。 www.founderbn.comPage12
  • 13. 4.配置文件读取配置文件读取过程 #define NGX_CONF_NOARGS 0×00000001 (没有参数) #define NGX_CONF_TAKE1 0×00000002 (有1个参数) #define NGX_CONF_TAKE2 0×00000004 (有2个参数) #define NGX_CONF_TAKE3 0×00000008 (有3个参数) #define NGX_CONF_TAKE4 0×00000010 (有4个参数) #define NGX_CONF_TAKE5 0×00000020 (有5个参数) #define NGX_CONF_TAKE6 0×00000040 (有6个参数) #define NGX_CONF_TAKE7 0×00000080 (有7个参数) #define NGX_CONF_MAX_ARGS 8 #define NGX_CONF_TAKE12 (NGX_CONF_TAKE1|NGX_CONF_TAKE2) (有1个或者有2个参数) #define NGX_CONF_TAKE13 (NGX_CONF_TAKE1|NGX_CONF_TAKE3) #define NGX_CONF_TAKE23 (NGX_CONF_TAKE2|NGX_CONF_TAKE3) #define NGX_CONF_TAKE123 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3) #define NGX_CONF_TAKE1234 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3|NGX_CONF_TAKE4) #define NGX_CONF_ARGS_NUMBER 0×000000ff #define NGX_CONF_BLOCK 0×00000100 #define NGX_CONF_FLAG 0×00000200 (有一个布尔型参数) #define NGX_CONF_ANY 0×00000400 #define NGX_CONF_1MORE 0×00000800 (至多有1个参数) #define NGX_CONF_2MORE 0×00001000 (至多有2个参数) www.founderbn.comPage13
  • 14. 5. Nginx epoll工作机制每个worker子进程都会创建 一个epoll fd,此fd首先对所有正在监听的服务套接字进行事件监听 ngx_worker_process_cycle ngx_worker_process_init(cycle, worker); if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) { // ngx_event_core_module . ngx_event_process_init module = ngx_modules[m]->ctx; // ngx_epoll_module . ngx_epoll_module_ctx if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) { // ngx_epoll_init ep = epoll_create(cycle->connection_n / 2); ngx_event_actions = ngx_epoll_module_ctx.actions; c = ngx_get_connection(ls[i].fd, cycle->log); rev->handler = ngx_event_accept; if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { //ngx_event_actions.add--->ngx_epoll_add_event op = EPOLL_CTL_ADD; if (epoll_ctl(ep, op, c->fd, &ee) == -1) { www.founderbn.comPage14
  • 15. 5. Nginx epoll工作机制整个nginx worker子进程核心是靠事件(event)驱动,由定时器(timer)和epoll event构成,nginx先将由后端得到的http文件缓存到一个temp目录,待下载完成时,将temp文件重命名为最终缓存文件 ngx_worker_process_cycle ngx_process_events_and_timers(cycle); (void) ngx_process_events(cycle, timer, flags); // ngx_event_actions.process_events--->ngx_epoll_process_events events = epoll_wait(ep, event_list, (int) nevents, timer); rev->handler(rev); // ngx_event_accept s = accept4(lc->fd, (struct sockaddr *) sa, &socklen, c = ngx_get_connection(s, ev->log); c->recv = ngx_recv; c->send = ngx_send; c->recv_chain = ngx_recv_chain; c->send_chain = ngx_send_chain; ls->handler(c); // ngx_http_init_connection rev->handler = ngx_http_wait_request_handler; if (ngx_handle_read_event(rev, 0) != NGX_OK) { if (ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT) www.founderbn.comPage15
  • 16. 6. Cache工作机制Nginx中cache文件名是由cache key经过MD5加密得到的,长度为32位,cache key由配置文件中得到 ngx_http_upstream_init_request(r); rc = ngx_http_upstream_cache(r, u); if (ngx_http_file_cache_new(r) != NGX_OK) { if (u->create_key(r) != NGX_OK) { // ngx_http_proxy_create_key ngx_http_file_cache_create_key(r); rc = ngx_http_file_cache_open(r); rc = ngx_http_file_cache_exists(cache, c); ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node); if (ngx_http_file_cache_name(r, cache->path) != NGX_OK) { ////将16位key转化为32位cache文件名 p = ngx_hex_dump(p, c->key, NGX_HTTP_CACHE_KEY_LEN); ngx_create_hashed_filename(path, c->file.name.data, c->file.name.len); www.founderbn.comPage16
  • 17. 5. Cache工作机制缓存文件由红黑树和队列共同进行管理 Nginx在启动时会由master进程创建两个负责cache管理的子进程: cache manager process和cache loader process cache loader process再将缓存文件加载到nginx系统中之后就是消亡 对于http缓存而言, loader进程对每个缓存路径调用ngx_http_file_cache_loader,ngx_http_file_cache_manage_file函数将各个 缓存文件插入到红黑树中 缓存文件 的淘汰机制是由cache manager process来完成的,每秒中来检测一次是否有文件需要淘汰 淘汰文件分为强制淘汰和过期淘汰 在缓存总大小超过了配置文件中配置的max_size大小时,会执行强制淘汰 在缓存文件在长时间不被访问时,执行过期淘汰,时间由inactive配置 缓存文件每被访问一次,都会被重新加到inactive queue的队头,因此所有淘汰的文件永远是在队尾的 www.founderbn.comPage17
  • 18. 7. http头解析过程Nginx会将每行以键值对形式保存在r->headers_in.headers链表中 ngx_http_init_connection hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); c->data = hc; rev->handler = ngx_http_wait_request_handler; rev->handler(rev); // ngx_http_wait_request_handler; b = ngx_create_temp_buf(c->pool, size); c->buffer = b; n = c->recv(c, b->last, size); c->data = ngx_http_create_request(c); r = ngx_pcalloc(pool, sizeof(ngx_http_request_t)); r->header_in = hc->nbusy ? hc->busy[0] : c->buffer; ngx_http_process_request_line(rev); rc = ngx_http_parse_request_line(r, r->header_in); if (ngx_http_process_request_uri(r) != NGX_OK) { ngx_http_process_request_headers(rev); rc = ngx_http_parse_header_line(r, r->header_in, h = ngx_list_push(&r->headers_in.headers); www.founderbn.comPage18
  • 19. 8. http头解析过程Nginx会将每行以键值对形式保存在r->headers_in.headers链表中 ngx_http_init_connection hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); c->data = hc; rev->handler = ngx_http_wait_request_handler; rev->handler(rev); // ngx_http_wait_request_handler; b = ngx_create_temp_buf(c->pool, size); c->buffer = b; n = c->recv(c, b->last, size); c->data = ngx_http_create_request(c); r = ngx_pcalloc(pool, sizeof(ngx_http_request_t)); r->header_in = hc->nbusy ? hc->busy[0] : c->buffer; ngx_http_process_request_line(rev); rc = ngx_http_parse_request_line(r, r->header_in); if (ngx_http_process_request_uri(r) != NGX_OK) { ngx_http_process_request_headers(rev); rc = ngx_http_parse_header_line(r, r->header_in, h = ngx_list_push(&r->headers_in.headers); www.founderbn.comPage19
  • 20. 9. nginx中红黑树的用法a binary tree有用的结构成员如下: typedef struct { ngx_rbtree_node_t node; ngx_queue_t queue; u_char key[NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t)]; ... } ngx_http_file_cache_node_t; 1.树和队列的初始化 ngx_rbtree_init(&cache->sh->rbtree, &cache->sh->sentinel, ngx_http_file_cache_rbtree_insert_value); ngx_rbtree_init(&r->name_rbtree, &r->name_sentinel, ngx_resolver_rbtree_insert_value); ngx_rbtree_init(&r->addr_rbtree, &r->addr_sentinel, ngx_rbtree_insert_value); 三个红色函数实现了相似的功能,类似C++中的面向对象思想 ngx_queue_init(&cache->sh->queue); 2.查找节点的方法 ngx_http_file_cache_lookup(cache, c->key); c->key是结过md5加密之后长16字节的数组 www.founderbn.comPage20
  • 21. 9. nginx中红黑树的用法a binary tree3.插入节点方法 a.先设置好key ngx_memcpy((u_char *) &fcn->node.key, c->key, sizeof(ngx_rbtree_key_t)); ngx_memcpy(fcn->key, &c->key[sizeof(ngx_rbtree_key_t)], NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t)); b.调用ngx_rbtree_insert(&cache->sh->rbtree, &fcn->node); tree->insert(*root, node, sentinel);会调用ngx_http_file_cache_rbtree_insert_value函数 如果是首次调用ngx_rbtree_insert,也就是说是红黑树的第一个节点,ngx_rbtree_insert会执行以下代码,而不会执行tree->insert函数 if (*root == sentinel) { node->parent = NULL; node->left = sentinel; node->right = sentinel; ngx_rbt_black(node); *root = node; return; } 4.删除节点的一种用法 q = ngx_queue_last(&cache->expire_queue); file = ngx_queue_data(q, ngx_cached_open_file_t, queue); ngx_queue_remove(q); ngx_rbtree_delete(&cache->rbtree, &file->node); Page21
  • 22. (本页无文本内容)