nginx过滤链本质是静态函数指针在初始化阶段的覆盖式串联,由ngx_http_top_header_filter和ngx_http_top_body_filter两个全局指针锚定,各模块通过保存旧值、更新全局指针实现头插法单向链表构建,执行顺序由编译链接顺序决定。

Nginx 没有“内核字节码”这一概念。它不基于字节码解释执行,而是用 C 语言编译为原生机器码运行。所谓“过滤模块的链式挂载机制”,本质是静态函数指针在初始化阶段的覆盖式串联,发生在内存数据段(.data 或 .bss),而非运行时动态生成或字节码解析。
要理清这个机制,关键不是反编译字节码,而是看清三个层次的内存行为:
过滤链的两个锚点指针在全局数据区
Nginx 定义了两个全局函数指针:
-
ngx_http_top_header_filter -
ngx_http_top_body_filter
它们位于 Nginx 核心的数据段中,初始值为默认处理函数(如 ngx_http_header_filter、ngx_http_write_filter)。每个 HTTP 过滤模块在 postconfiguration 阶段,会直接修改这两个变量的值——把它们指向自己的处理函数。这种写操作是对全局变量的一次性赋值,不涉及指令解码或字节码分析。
每个模块私有的 next 指针保存调用链路
模块代码里通常声明:
static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
这两个 static 变量存储在模块自身的数据段中,初始化时被赋值为当前 ngx_http_top_*_filter 的旧值。随后,模块把自己的函数地址写入全局指针。
这就形成一条靠地址跳转维持的单向链:ngx_http_top_header_filter → 自定义模块A → 自定义模块B → ... → 默认header_filter
模块注册顺序决定内存覆盖顺序,而非运行时调度
链表结构在 nginx -t 或启动时的 init_modules 阶段就已固化。顺序由以下因素决定:
-
./configure --add-module=xxx的传入顺序 - Makefile 中
HTTP_FILTER_MODULES变量拼接顺序 - 各模块
ngx_http_module_t结构体中postconfiguration回调的执行先后
越晚链接进二进制的模块,其 postconfiguration 越晚执行,也就越早“抢占”ngx_http_top_*_filter,从而在链首位置。
如何验证这个内存机制
不需要字节码工具,可用以下方法观察:
- 用
nm -D nginx | grep "top_header\|top_body"查看全局符号地址 - 用
gdb nginx启动后,在ngx_http_block或ngx_http_init_phase_handlers处设断点,打印&ngx_http_top_header_filter和它的值变化 - 编译时加
-Wl,--print-map输出链接映射,确认各模块.o文件的加载顺序
链式调用本身只是函数指针连续跳转,每次调用都通过 next(...) 跳到下一个模块的函数入口——这完全是 C 语言层面的地址调用,没有中间表示层或虚拟机参与。











