1 定义
ngx_output_chain_align_file_buf 函数 定义在 src/core/ngx_output_chain.cstaticngx_int_tngx_output_chain_align_file_buf(ngx_output_chain_ctx_t*ctx,off_tbsize){size_tsize;ngx_buf_t*in;in=ctx->in->buf;if(in->file==NULL||!in->file->directio){returnNGX_DECLINED;}ctx->directio=1;size=(size_t)(in->file_pos-(in->file_pos&~(ctx->alignment-1)));if(size==0){if(bsize>=(off_t)ctx->bufs.size){returnNGX_DECLINED;}size=(size_t)bsize;}else{size=(size_t)ctx->alignment-size;if((off_t)size>bsize){size=(size_t)bsize;}}ctx->buf=ngx_create_temp_buf(ctx->pool,size);if(ctx->buf==NULL){returnNGX_ERROR;}/* * we do not set ctx->buf->tag, because we do not want * to reuse the buf via ctx->free list */#if(NGX_HAVE_ALIGNED_DIRECTIO)ctx->unaligned=1;#endifreturnNGX_OK;}2 目的
1 设计意图
ngx_output_chain_align_file_buf的核心职责是:
为 DirectIO 场景下可能存在的文件偏移未对齐问题,
分配一个小的临时缓冲区来读取未对齐的头部数据,
使后续读取的起始位置对齐到磁盘扇区边界。
该函数属于 Nginx输出过滤链的缓冲区分配阶段。
Nginx 的ngx_output_chain(定义于同一文件)是输出数据处理的核心引擎,
负责将上游传递的数据缓冲区(ctx->in链)经过必要的拷贝/变换后,
通过output_filter传递给下游过滤器。
当ngx_output_chain发现当前没有可用的输出缓冲区(ctx->buf == NULL)时,
会首先调用本函数尝试对齐分配。
DirectIO(直接 I/O)允许应用程序绕过操作系统的页缓存,直接与磁盘设备交互,
从而消除双重缓冲的开销。
其代价是:在部分操作系统(尤其是 Linux)上,
DirectIO 要求读写缓冲区的内存地址和文件偏移都对齐到
文件系统扇区大小(通常 512 字节,XFS 文件系统可达 4096 字节)。
如果文件偏移未对齐,Linux 会返回EINVAL错误而非自动回退到普通读取。
本函数的引入正是为了解决这一问题:
当检测到使用了 DirectIO 且当前文件偏移未对齐时,
它计算需要补齐多少字节才能到达下一个对齐边界,
分配一个恰好大小的临时缓冲区用于"吃掉"这些非对齐数据,
之后的读取位置即自然对齐。
这一设计使得 Nginx 可以在享受 DirectIO 性能优势的同时,
无需上游模块保证数据边界恰好对齐。
3 详解
1 函数签名
staticngx_int_tngx_output_chain_align_file_buf(ngx_output_chain_ctx_t*ctx,off_tbsize)1 返回值:ngx_int_t
| 返回值 | 值 | 含义 |
|---|---|---|
NGX_DECLINED | -5 | 无需对齐处理或无 DirectIO,调用方应走普通缓冲区分配路径 |
NGX_ERROR | -1 | 内存分配失败,调用方应中止当前请求 |
NGX_OK | 0 | 成功分配了对齐用的临时缓冲区,ctx->buf已指向该缓冲区,且ctx->directio = 1 |
2 函数名:ngx_output_chain_align_file_buf
| 词段 | 含义 |
|---|---|
ngx | Nginx 标准前缀 |
output_chain | 所属模块:输出链处理逻辑 |
align | 核心操作:对齐(alignment) |
file | 适用场景:文件类型的缓冲区(区别于内存/mmap 等) |
buf | 产出物:对齐用的临时缓冲区 |
3 参数列表
| 参数名 | 类型 | 含义 | 来源 | 约束 |
|---|---|---|---|---|
ctx | ngx_output_chain_ctx_t* | 输出链上下文,持有所有累积状态 | 调用方(ngx_output_chain) | 非空;ctx->in必须非空 |
bsize | off_t | 当前输入缓冲区的数据大小(字节) | ngx_output_chain内部通过ngx_buf_size计算 | bsize >= 0 |
2 逻辑流程
ngx_output_chain_align_file_buf(ctx, bsize) ├─ [1] 非 DirectIO 或非文件缓冲区 │ └─ in->file == NULL || !in->file->directio → return NGX_DECLINED ├─ [2] 标记 DirectIO 模式 │ └─ ctx->directio = 1 ├─ [3] 计算文件偏移的未对齐量 │ └─ size = file_pos - (file_pos & ~(alignment - 1)) ├─ [4] 已对齐路径 │ └─ size == 0 │ ├─ [4.1] 数据量充足 │ │ └─ bsize >= bufs.size → return NGX_DECLINED(直接读入大缓冲区) │ └─ [4.2] 数据量不足一个完整缓冲区 │ └─ size = bsize → 分配小临时缓冲区 → return NGX_OK └─ [5] 未对齐路径 └─ size > 0 └─ size = min(alignment - size, bsize) → 分配对齐补齐缓冲区 → return NGX_OK{size_tsize;ngx_buf_t*in;}局部变量声明。
size:
分配给临时缓冲区的大小(字节),后续语义根据分支变化:
未对齐路径下为"补齐到对齐边界的字节数";
已对齐路径下为bsize。in:来自ctx->in->buf的快捷引用
1 非 DirectIO 或非文件缓冲区
in=ctx->in->buf;if(in->file==NULL||!in->file->directio){returnNGX_DECLINED;}进入条件:函数入口,无条件执行。
处理逻辑:
先从输入缓冲区获取对ngx_file_t的引用。in->file == NULL表示该缓冲区不关联任何文件
(例如纯内存缓冲区、临时内存缓冲区等),in->file->directio是ngx_file_t中的位字段(定义于src/core/ngx_file.h:):
unsigneddirectio:1;该标记由ngx_http_copy_filter_module(或其他调用方)在配置阶段根据用户指令设置,
表示本次响应输出打开了 DirectIO。
任一条件不满足,直接返回NGX_DECLINED。
这个返回值的语义是"本函数不适用当前场景,请走普通缓冲区分配逻辑"。
调用方ngx_output_chain(src/core/ngx_output_chain.c)收到NGX_DECLINED后,
会跳过ctx->buf(此时为 NULL,未被本函数设置),
转而尝试从ctx->free链表复用已有缓冲区,
或调用ngx_output_chain_get_buf分配新的输出缓冲区。
设计意图:
本函数只在 DirectIO 的文件缓冲区场景下才有用武之地。
非 DirectIO 场景下操作系统的块设备层会自动处理对齐问题,无需额外干预。
将是否需要对齐的判断前置于此,使函数的主体逻辑专注于纯粹的"对齐计算 + 分配"语义。
2 标记 DirectIO 模式
ctx->directio=1;进入条件:通过了 [1] 的检查,即确定当前处于 DirectIO + 文件缓冲区场景。
处理逻辑:
ctx->directio是ngx_output_chain_ctx_t中的位字段(定义于src/core/ngx_buf.h:):
unsigneddirectio:1;将其设置为1,向下游声明"当前输出链处于 DirectIO 模式"。此标记有两个下游影响:
- 影响后续缓冲区分配策略:
- 影响缓冲区复用策略:
3 计算文件偏移的未对齐量
size=(size_t)(in->file_pos-(in->file_pos&~(ctx->alignment-1)));进入条件:DirectIO 已确认开启。
处理逻辑:
这是整个函数最关键的位运算。先拆解右侧子表达式:
ctx->alignment:
对齐粒度,来自 Nginx 配置的directio_alignment指令,
默认值NGX_CONF_UNSET(即 0),
实际运行时会与上级配置合并为有效值512
(src/http/ngx_http_core_module.c,通过ngx_conf_merge_off_value合并)。
通常设置为磁盘扇区大小(512 字节)。ctx->alignment - 1:
构造对齐掩码。例如 alignment = 512 时,得到 511(二进制0000000111111111)。~(ctx->alignment - 1):
逐位取反。例如 alignment = 512 时,得到1111111111100000000(高位全 1,低 9 位全 0)。in->file_pos & ~(ctx->alignment - 1):
将当前文件偏移的低 N 位清零,
即将file_pos向下对齐到最近的 alignment 整数倍边界。in->file_pos - (...):
原偏移量减去已对齐的偏移量,得到未对齐的尾部字节数,
即当前读取位置偏离上一个对齐边界的距离。
以具体数值为例:假设ctx->alignment = 512,in->file_pos = 1000:
file_pos = 1000 (二进制: ...0001111101000) ~(alignment - 1) = ~511 (二进制: ...1110000000000) file_pos & ~(alignment -1) = 512 (向下对齐到 512) 未对齐量 = 1000 - 512 = 488这个值表示当前文件读取位置距离上一个 512 字节对齐边界有 488 字节——
也就是说,要使下一次读取从 1024(下一个对齐边界)开始,
需要先读取 512 - 488 = 24 字节的"补齐数据"。
4 已对齐路径
if(size==0){if(bsize>=(off_t)ctx->bufs.size){returnNGX_DECLINED;}size=(size_t)bsize;进入条件:size == 0,即当前file_pos已对齐到alignment边界。
ngx_bufs_t定义(src/core/ngx_buf.h):
typedefstruct{ngx_int_tnum;size_tsize;}ngx_bufs_t;其中ctx->bufs.size是输出缓冲区的标准大小,
由output_buffers指令配置,表示一组输出缓冲区的单个大小。
4.1 数据量充足
if(bsize>=(off_t)ctx->bufs.size){returnNGX_DECLINED;}进入条件:文件偏移已对齐,且待处理的数据量bsize≥ 标准缓冲区大小。
处理逻辑:数据量足够填满一个标准缓冲区,无需分配"补齐用"的小缓冲区。
返回NGX_DECLINED,调用方将转而分配一个标准大小的缓冲区
(通过ngx_output_chain_get_buf),直接读取数据。
设计意图:
当前已对齐 + 数据量 >= 标准缓冲区 → 直接使用大缓冲区读取,保证 I/O 效率。
分配小缓冲区会浪费一次 I/O 系统调用。
4.2 数据量不足一个完整缓冲区
size=(size_t)bsize;进入条件:文件偏移已对齐,且bsize < ctx->bufs.size(数据量不足填满一个标准缓冲区)。
处理逻辑:将size设为bsize,即只需分配一个恰好容纳剩余数据的小缓冲区。后续步骤中会用这个 size 调用ngx_create_temp_buf分配临时缓冲区。
5 未对齐路径
}else{size=(size_t)ctx->alignment-size;if((off_t)size>bsize){size=(size_t)bsize;}}进入条件:size > 0,即当前file_pos未对齐到alignment边界。
处理逻辑:
第一步:size = alignment - size。
此时变量size的语义发生变化——
从"未对齐偏移量"变为"需要读取多少字节才能到达下一个对齐边界"。
沿用前文的例子(alignment=512, file_pos=1000, 偏移量=488):
需要对齐补齐量 = 512 - 488 = 24这意味着只需读取 24 字节的"填补数据",
文件偏移就从 1000 变为 1024,下一次读取就对齐了。
第二步:if (size > bsize)。
如果对齐补齐量大于实际可用数据量,则以实际数据量为准。
这对应数据尾部场景:剩余数据不足到达下一个对齐边界,
且这是最后一批数据,不存在"下一次读取"。
例如:file_pos = 1000,bsize = 20。
此时需要 24 字节才能对齐到 1024,
但只剩下 20 字节数据。
最终size = 20,仅读取实际剩余数据。
6 分配对齐用临时缓冲区
ctx->buf=ngx_create_temp_buf(ctx->pool,size);if(ctx->buf==NULL){returnNGX_ERROR;}进入条件:
对已对齐路径的 [4.2] 或未对齐路径的 [5],size已被确定为临时缓冲区的分配大小。
ngx_create_temp_buf函数概述
定义在src/core/ngx_buf.c。
该函数从指定内存池分配一个ngx_buf_t结构体及其数据区。
处理逻辑:
将分配结果赋给ctx->buf。ctx->buf是ngx_output_chain_ctx_t的输出缓冲区指针(定义于src/core/ngx_buf.h:79)。
之后的ngx_output_chain_copy_buf函数会使用ctx->buf作为目标缓冲区,
将文件数据从ctx->in->buf拷贝到此缓冲区中。
若NULL,返回NGX_ERROR,调用方ngx_output_chain会立即中止处理并向上传播错误。
7 标记未对齐状态(平台相关)
/* * we do not set ctx->buf->tag, because we do not want * to reuse the buf via ctx->free list */源码注释解释了为何不设置ctx->buf->tag:
ctx->tag是缓冲区复用机制的关键标识。
同一个 tag 值的缓冲区在完成使用后会被归还到ctx->free链表中供后续复用。
本函数分配的临时缓冲区是一个"一次性"的补齐缓冲区——
它仅用于读取 DirectIO 下未对齐头部的那几个字节,读完后并不会再次被需要。
不设置 tag 意味着该缓冲区不会被加入 free 链表,
避免了内存池中堆积大量尺寸特殊的小缓冲区
(这些缓冲区的大小可能远小于标准缓冲区大小,复用价值极低)。
#if(NGX_HAVE_ALIGNED_DIRECTIO)ctx->unaligned=1;#endifNGX_HAVE_ALIGNED_DIRECTIO是编译期特性检测宏,
仅在 Linux 平台上定义为1(见auto/unix)。
原因在于:Linux 的 DirectIO 是"硬"对齐——
如果参数未对齐,read()/write()直接返回EINVAL,
必须由应用层自行处理。
而 FreeBSD、Solaris、macOS 的 DirectIO 是"软"对齐——
参数未对齐时操作系统会透明回退到普通读取,无需应用层额外处理
NGX_HAVE_ALIGNED_DIRECTIO宏定义
(auto/unix→auto/have,编译期写入ngx_auto_config.h):
#ifndefNGX_HAVE_ALIGNED_DIRECTIO#defineNGX_HAVE_ALIGNED_DIRECTIO1#endif仅当系统为 Linux 时定义(auto/unix:208-209:if [ $ngx_found = yes -a "$NGX_SYSTEM" = "Linux" ]; then have=NGX_HAVE_ALIGNED_DIRECTIO . auto/have; fi)。
ctx->unaligned位字段设置后,实际生效位置有两处(同文件):
读取后(line 553-561,在
ngx_output_chain_copy_buf的"从文件读取数据到内存缓冲区"路径中):读取完成后,如果ctx->unaligned为真,调用ngx_directio_off(in->file->fd)暂时关闭DirectIO。因为后续的读取(已经对齐)可能仍然是 DirectIO 模式,但本次"对齐补齐"读取小量数据时,关闭 DirectIO 可以让内核走缓存路径,避免 DirectIO 的最小传输粒度限制。恢复 DirectIO(line 596-609):数据读取完成后,调用
ngx_directio_on(in->file->fd)重新开启 DirectIO,并恢复此前的errno值(因为ngx_directio_on的fcntl可能会覆盖errno,需用ngx_set_errno恢复,确保上层能正确获取实际 I/O 操作的错误状态)。
8 成功返回
returnNGX_OK;进入条件:临时缓冲区分配成功。此时函数已完成了以下工作:
ctx->directio = 1:声明 DirectIO 模式启用;ctx->buf:指向分配的临时缓冲区,尺寸为"补齐对齐所需的字节数或实际数据量";ctx->unaligned = 1(如果平台需要):标记当前读取为非对齐读取。
调用方ngx_output_chain在收到NGX_OK后,流程继续——ctx->buf已就绪,接着调用ngx_output_chain_copy_buf(ctx)将输入缓冲区的数据
(文件内容)拷贝到此临时缓冲区中,然后通过output_filter传递给下游过滤器。
整体设计意图总结:
| 视角 | 说明 |
|---|---|
| 安全 | 避免了在不支持软对齐的 Linux 系统上因 DirectIO 对齐不满足而导致的EINVAL错误。如果跳过此函数直接在 Linux 上以未对齐偏移执行 DirectIO 读取,内核会拒绝该操作,导致请求失败。 |
| 性能 | 只分配最小必要的缓冲区(通常几十到几百字节),而非完整大小缓冲区,节省内存。补齐小缓冲区只使用一次即丢弃(不进入 free 链表复用),避免内存池中堆积尺寸特异的碎片。在非 Linux 平台上直接编译消除,不产生任何开销。 |
| 架构 | 将对齐处理的细节封装在模块内部,上游模块(如ngx_http_copy_filter_module)只需设置ctx->alignment和打开 DirectIO 即可,无需关心底层对齐逻辑。本函数与ngx_output_chain_get_buf(负责标准缓冲区分配)和ngx_output_chain_copy_buf(负责实际数据拷贝)构成了完整的"分配-对齐-拷贝"三角协作关系。 |