Nginx源码阅读必须从src/core/nginx.c的main函数开始,核心调用链为:main→ngx_init_cycle→模块init_module回调→ngx_worker_process_cycle→ngx_process_events_and_timers。

直接看 main 函数,跳过所有“准备阶段”描述——Nginx 源码阅读必须从 src/core/nginx.c 的 main 入口开始,否则永远卡在配置加载或模块注册的黑盒里。
从 main 到 ngx_worker_process_cycle:只跟踪这四步调用链
源码不是线性文档,是调度逻辑。你不需要读懂全部,但必须清楚控制流怎么落到 worker 上处理请求:
-
main→ngx_init_cycle:只关注它初始化了cycle->conf_ctx和cycle->modules,其余字段(如connections)此时还是空指针 -
ngx_init_cycle→ 所有模块的init_module回调:重点观察ngx_event_core_module和ngx_http_core_module是否被调用(加日志即可验证) -
ngx_master_process_cycle或ngx_single_process_cycle:调试时强制设daemon off; master_process off;,直奔单进程模式,省去 fork 分支干扰 -
ngx_worker_process_cycle→ngx_process_events_and_timers:这就是事件循环真身,后续所有 epoll、HTTP 解析、handler 调用都从这里展开
调试 ngx_http_core_module 时,盯死三个回调时机
这个模块不是“干活的”,而是“搭架子的”。它的三个函数执行顺序决定了你能看到什么:
-
create_main_conf:在ngx_init_cycle早期就执行,分配ngx_http_conf_ctx_t *,此时所有 HTTP 模块的配置结构体都还没填充 -
postconfiguration:所有 HTTP 模块的set指令解析完才调用,location 树构建、handler 链组装、merge 逻辑都在这里——想查某个location /api最终用了谁的 handler,断点打在这里最准 -
init_main_conf:常被忽略,但它负责把各模块的srv_conf、loc_conf挂到ngx_http_conf_ctx_t对应字段上;若发现配置没生效,先确认这个函数是否被调用
用 GDB 跟一个 HTTP 请求,关键断点就这三个位置
别试图在 ngx_http_request.c 里大海捞针。真实请求路径极短,只靠三个断点就能串起来:
-
ngx_epoll_process_events:epoll_wait 返回后第一站,看rev->handler是什么函数(通常是ngx_http_wait_request_handler) -
ngx_http_wait_request_handler:连接刚建立、等待读取请求行时触发,r->header_in此时为空,下一步就是 parse -
ngx_http_process_request_line:真正开始解析GET /path HTTP/1.1,之后立刻进ngx_http_core_run_phases,进入 11 个 phase 阶段调度
注意:ngx_http_core_run_phases 不是递归调用,而是用 ph->handler(r) 直接跳转,没有中间分发层——这意味着你加断点时,看到的是 handler 地址,不是字符串名。
ngx_connection_t 和 ngx_event_t 的绑定关系最容易被误解
很多人以为“一个连接对应一个事件”,其实 Nginx 是复用结构体的:
-
ngx_connection_t里read和write字段都是ngx_event_t *,但它们共用同一个ngx_connection_t实例 -
rev->data和wev->data都指向同一块ngx_connection_t *内存,不是拷贝 - 连接池大小由
cycle->connection_n决定,预分配后全挂空闲链表;ngx_get_connection只是取节点,不 malloc;ngx_free_connection归还时也不 free - 如果你在
ngx_http_finalize_request里看到c->destroyed = 1,说明连接已被标记为可回收,但结构体还在内存里等着重用
这种复用机制让 Nginx 避开了频繁分配释放开销,但也意味着:你在调试时看到的 ngx_connection_t 地址,可能上一秒还在处理 /static/a.js,下一秒就用来收 /api/b.json ——别靠地址判断连接归属。


















