Linux 7.1/7.2 内核深度实战:当 NTFS 获得"重生"——从 iomap 延迟分配到 folio 内存管理、从 FRED 事件分发到三行代码撬动 5% IOPS 的存储性能革命(2026)
引言:Linus 说了个词——Resurrection
2026 年 4 月 17 日,一个 Git commit 出现在 kernel.org 上,提交信息平淡无奇。但 Linus Torvalds 在合并时用了一个意味深长的词:"ntfs resurrection"——NTFS 重生。
这不仅仅是一次驱动重写。这是 Linux 对 Windows 主流文件系统长达 20 年的"二等公民"待遇的终结,也是内核文件系统子系统现代化进程中的一个里程碑。新驱动由开发者 Namjae Jeon 历时四年重构,采用 iomap 框架和 folio 内存模型,实现了完整读写支持,在多线程写入场景下性能提升 35%-110%,挂载 4TB 大容量硬盘的速度更是提升至原来的 4 倍。
紧接着,Linux 7.2 开发版中,字节跳动工程师 Fengnan Chang 仅调整了两行代码的位置,就让 EXT4 和 XFS 在高 IOPS 场景下性能提升约 5%。
这两件事放在一起看,勾勒出了 2026 年 Linux 内核存储栈的全景图:从文件系统驱动的彻底重构,到 I/O 路径上两行代码的精密手术,Linux 正在经历一次从上到下的存储性能革命。
本文将从 NTFS 新驱动的源码架构、iomap 框架的设计哲学、folio 内存模型、FRED 事件分发机制、三行代码的性能魔法五个维度,对 Linux 7.1/7.2 的存储子系统进行全面拆解。
一、NTFS 的二十年困局:为什么"重生"如此重要
1.1 三代 NTFS 驱动的演进
NTFS 是 Windows 的核心文件系统,自 1993 年随 Windows NT 3.1 发布以来,一直是 PC 世界的存储基石。但在 Linux 世界里,NTFS 的支持一直是痛点:
时间线:Linux NTFS 支持的三代演进
第一代(2001-2022):只读驱动
├── 功能:仅支持读取 NTFS 分区
├── 限制:无写入支持,NTFS 特性覆盖不完整
├── 状态:2022 年后基本停维
└── 代码:约 8 万行,架构老旧
第二代(2020-2025):NTFS3(Paragon)
├── 功能:读写支持,但维护停滞
├── 问题:补丁审核缓慢,社区参与度低
├── 状态:2023 年后维护基本停滞
└── 争议:Paragon 的社区协作模式引发不满
第三代(2022-2026):新 NTFS 驱动(Namjae Jeon)
├── 功能:完整读写 + iomap + folio + 延迟分配
├── 性能:多线程写入提升 35%-110%
├── 状态:Linux 7.1 合入主线
└── 代码:架构清晰,社区积极维护
第一代只读驱动的问题是显而易见的——你只能读,不能写。对于双系统用户、数据恢复工程师、跨平台文件交换场景,这是致命的。
第二代 NTFS3 驱动由 Paragon Software 在 2020 年提交,虽然支持读写,但维护模式存在根本性问题:Paragon 是一家商业公司,其贡献驱动的主要目的是推广付费版本。当社区开发者提交 Bug 报告和补丁时,响应速度缓慢,代码审核流程不透明。2023 年后,NTFS3 的维护基本停滞,多个已知的损坏风险 Bug 无人修复。
1.2 新驱动的核心突破
Namjae Jeon 的新驱动从第一代只读驱动出发,进行了四年的全面重构。核心突破包括:
(1)完整写入支持
旧驱动只实现了读取路径,写入路径完全缺失。新驱动从零实现了 NTFS 的写入语义:
// 新 NTFS 驱动的写入路径核心 — fs/ntfs3/inode.c(简化)
// 基于 iomap 框架的写入实现
static int ntfs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len,
struct page **pagep, void **fsdata)
{
// 采用 iomap 框架替代旧的 buffer_head 方案
// iomap 提供了统一的块映射接口,支持延迟分配
return iomap_write_begin(mapping, pos, len, pagep,
&ntfs_iomap_ops);
}
static int ntfs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata)
{
// 写入完成后的元数据更新
// 包括 MFT 记录更新、目录项更新、时间戳更新
return iomap_write_end(mapping, pos, len, copied, page,
&ntfs_iomap_ops);
}
const struct address_space_operations ntfs_aops = {
.write_begin = ntfs_write_begin,
.write_end = ntfs_write_end,
.dirty_folio = filemap_dirty_folio, // folio 版本的脏页标记
.migrate_folio = buffer_migrate_folio,
.invalidate_folio = iomap_invalidate_folio,
.release_folio = iomap_release_folio,
.direct_IO = ntfs_direct_IO,
};
(2)延迟分配(Delayed Allocation)
这是新驱动性能大幅提升的关键。传统 NTFS 写入采用"立即分配"策略——数据写入时立即在磁盘上分配空间并更新元数据。延迟分配则将空间分配推迟到数据实际刷回磁盘时:
// 延迟分配的核心逻辑
static int ntfs_iomap_begin(struct inode *inode, loff_t offset,
loff_t length, unsigned flags,
struct iomap *iomap, struct iomap *srcmap)
{
struct ntfs_inode *ni = NTFS_I(inode);
if (flags & IOMAP_WRITE && (iomap_flags & IOMAP_F_DIRTY)) {
// 延迟分配:不立即分配物理块
// 而是标记为 DELAY,等待 writeback 时再分配
iomap->type = IOMAP_DELALLOC;
iomap->flags |= IOMAP_F_DIRTY;
iomap->addr = IOMAP_NULL_ADDR; // 尚无物理地址
return 0;
}
// 已分配的块:直接映射
iomap->type = IOMAP_MAPPED;
iomap->addr = ni->run.rb_mappings[offset >> inode->i_blkbits];
return 0;
}
// writeback 时分配物理块
static int ntfs_iomap_get_delayed_blks(struct inode *inode,
struct iomap *iomap)
{
// 在 writeback 阶段执行实际的空间分配
// 优势:可以批量分配连续块,减少碎片
struct ntfs_inode *ni = NTFS_I(inode);
CLST vcn = iomap->offset >> inode->i_blkbits;
CLST len = iomap->length >> inode->i_blkbits;
// 在 MFT 中分配簇
int err = ntfs_alloc_clusters(ni->mi.sbi, &vcn, &len);
if (err)
return err;
// 更新 NTFS 的 $DATA 运行列表
err = ntfs_update_run(ni, vcn, len, ALLOCATED);
return err;
}
延迟分配的好处有三:
- 减少元数据更新:不需要为每次写入都更新 MFT(Master File Table)记录
- 批量分配连续块:writeback 时可以根据全局视图选择最优物理位置
- 合并小写入:多个小写入可以在页缓存中合并,减少实际 I/O 次数
(3)iomap 与 folio 的深度整合
iomap 是 Linux 内核在 4.8 引入的文件系统 I/O 映射框架,folio 是 5.16 引入的多页内存描述符。新 NTFS 驱动是第一个同时深度整合这两个特性的 NTFS 实现:
// folio 版本的页操作 — 替代旧的 page-based API
static bool ntfs_release_folio(struct folio *folio, gfp_t gfp_flags)
{
// folio 替代 page:一个 folio 可以包含多个连续 page
// 减少 page->mapping 等指针的开销
if (folio_test_private(folio)) {
if (!iolock_trylock(folio))
return false;
iomap_release_folio(folio, gfp_flags);
iolock_unlock(folio);
}
return true;
}
static void ntfs_invalidate_folio(struct folio *folio, size_t offset,
size_t length)
{
// folio 级别的缓存失效处理
// 支持大 folio(order > 0)的部分失效
if (offset || length < folio_size(folio)) {
iomap_invalidate_folio(folio, offset, length);
} else {
// 整个 folio 失效
filemap_release_folio(folio, GFP_NOFS);
}
}
1.3 性能测试:35%-110% 提升背后的故事
Phoronix 的测试数据令人印象深刻,但需要深入理解这些数字的含义:
多线程写入 35%-110% 提升的原因分析:
// 旧 NTFS3 驱动的写入瓶颈
// 1. 每次写入都获取全局锁
static ssize_t ntfs3_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct inode *inode = file_inode(iocb->ki_filp);
// 全局 inode 锁 — 串行化所有写入操作
inode_lock(inode);
// 立即分配:每次写入都更新 MFT
// 多线程写入时,MFT 成为竞争热点
ret = ntfs3_write_begin(iocb->ki_filp, inode->i_mapping, ...);
inode_unlock(inode);
return ret;
}
// 新 NTFS 驱动的并发优化
// 1. 延迟分配消除了写入时的 MFT 竞争
// 数据写入时只修改页缓存,不获取 MFT 锁
// MFT 更新延迟到 writeback 阶段,此时可以批量合并
// 2. iomap 支持 DIO(Direct I/O)的无锁路径
static ssize_t ntfs_dio_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
// iomap 的 DIO 路径使用 rwsem 读写锁
// 多个读者可以并行,写者与读者互斥
return iomap_dio_rw(iocb, to, &ntfs_iomap_ops,
&ntfs_dio_read_ops, 0, 0);
}
挂载 4TB 硬盘速度提升 4 倍的原因:
旧驱动在挂载时需要逐个扫描 MFT 条目以构建内存索引。对于 4TB 硬盘,这可能需要数十秒。新驱动采用了两种优化:
- 延迟加载 MFT 索引:只在首次访问时加载对应目录的索引
- 并行初始化:利用内核的多线程基础设施并行初始化卷信息
// 新驱动的挂载优化
static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct ntfs_sb_info *sbi = ntfs_sb(sb);
// Phase 1: 只读取最基本的卷信息
err = ntfs_init_volume(sbi);
if (err)
goto out;
// Phase 2: 延迟加载 MFT 和目录索引
// 旧驱动在这里会扫描整个 MFT 表
// 新驱动只初始化根目录索引
err = ntfs_load_root(sbi);
if (err)
goto out;
// Phase 3: 启动后台线程异步加载元数据
// 用户可以立即开始使用文件系统
queue_work(system_long_wq, &sbi->meta_load_work);
return 0;
}
// 后台异步加载
static void ntfs_meta_load_work(struct work_struct *work)
{
struct ntfs_sb_info *sbi = container_of(work, struct ntfs_sb_info,
meta_load_work);
// 异步加载扩展属性、安全描述符等非关键元数据
ntfs_load_metadata_later(sbi);
}
二、iomap 框架:Linux 文件系统的现代化基石
2.1 从 buffer_head 到 iomap 的范式转移
Linux 内核的文件系统 I/O 路径经历了三代演进:
第一代(2.4-4.7):buffer_head
├── 每个 page 关联一组 buffer_head
├── 每个 buffer_head 描述一个磁盘块
├── 问题:大量小对象,内存开销大,锁粒度粗
└── 适用:小容量磁盘,块大小 512B/4KB
第二代(4.8-5.15):iomap
├── 统一的块映射接口
├── 支持大块映射(一次映射多个块)
├── 支持 DIO、DAX、writeback 统一路径
└── 适用:大容量磁盘,多 TB 文件
第三代(5.16+):iomap + folio
├── folio 替代 page 作为内存管理单元
├── 一个 folio 可以是多个连续 page
├── iomap 原生支持 folio 操作
└── 适用:大内存系统,减少 page 指针开销
iomap 的核心思想是声明式的块映射:文件系统不需要自己管理 buffer_head,只需要告诉 iomap "这个文件的 [offset, offset+len) 区域在磁盘上的位置",剩余的读、写、DIO、writeback 全部由 iomap 框架处理。
// iomap 的核心数据结构
struct iomap {
u64 offset; // 文件内偏移
u64 length; // 映射长度
u64 addr; // 磁盘物理地址(IOMAP_NULL_ADDR 表示未分配)
enum iomap_type type; // IOMAP_MAPPED / IOMAP_DELALLOC / IOMAP_UNWRITTEN / IOMAP_HOLE
u16 flags; // IOMAP_F_* 标志
struct block_device *bdev; // 块设备
struct dax_device *dax_dev; // DAX 设备(NVDIMM)
void *private; // 文件系统私有数据
const struct iomap_ops *ops; // 操作回调
};
// 文件系统只需实现 iomap_ops
struct iomap_ops {
int (*iomap_begin)(struct inode *inode, loff_t offset,
loff_t length, unsigned flags,
struct iomap *iomap, struct iomap *srcmap);
int (*iomap_end)(struct inode *inode, loff_t offset,
loff_t length, ssize_t written,
unsigned flags, struct iomap *iomap);
};
2.2 iomap 的四种映射类型
// iomap_type 枚举 — 理解这四种类型是理解 iomap 的关键
enum iomap_type {
IOMAP_HOLE, // 文件空洞:无数据,读取返回零
IOMAP_DELALLOC, // 延迟分配:数据在页缓存中,尚未分配磁盘块
IOMAP_MAPPED, // 已映射:数据在磁盘上,有物理地址
IOMAP_UNWRITTEN, // 已分配但未写入:磁盘空间已预留,数据尚未写入
};
/*
* 四种类型的状态转换:
*
* 写入开始 → IOMAP_DELALLOC(延迟分配)
* writeback → IOMAP_UNWRITTEN(分配了物理块但数据未写入)
* 写入完成 → IOMAP_MAPPED(数据已安全落盘)
* 截断文件 → IOMAP_HOLE(释放空间)
*
* 对于 NTFS 新驱动:
* - 读取路径:HOLE → 返回零页;MAPPED → 读取磁盘
* - 写入路径:先 DELALLOC,后 writeback 时 UNWRITTEN → MAPPED
* - DIO 路径:直接 MAPPED,跳过延迟分配
*/
2.3 iomap 的统一 I/O 路径
iomap 最大的架构优势是统一了所有 I/O 路径:缓冲写入、DIO、DAX、writeback 全部共享同一套块映射逻辑:
// iomap 统一 I/O 路径示意
// 1. 缓冲写入(Buffered Write)
static ssize_t iomap_write_iter(struct iomap_iter *iter,
struct iov_iter *i)
{
// iomap_begin → 获取块映射
// 如果是 DELALLOC → 在页缓存中分配 folio
// 如果是 MAPPED → 读取磁盘块到页缓存,然后写入
// 如果是 HOLE → 分配新 folio,写入后标记为 DELALLOC
}
// 2. 直接 I/O(Direct I/O)
static ssize_t iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter,
const struct iomap_ops *ops,
const struct iomap_dio_ops *dops,
unsigned int dio_flags, size_t done_before)
{
// iomap_begin → 获取块映射
// 必须是 MAPPED 类型(DIO 不支持延迟分配)
// 直接在用户缓冲区和磁盘之间传输数据
// 不经过页缓存
}
// 3. Writeback(脏页回写)
static int iomap_writepages(struct address_space *mapping,
struct writeback_control *wbc,
const struct iomap_writeback_ops *ops)
{
// 遍历脏 folio
// 对每个 DELALLOC folio → 先分配物理块(变为 UNWRITTEN)
// 提交 bio 写入磁盘
// 写入完成 → 标记为 MAPPED
}
// 4. DAX(直接访问 NVDIMM)
static loff_t iomap_dax_rw(struct kiocb *iocb, struct iov_iter *iter,
const struct iomap_ops *ops)
{
// iomap_begin → 获取块映射
// 必须是 MAPPED 类型
// 直接映射 NVDIMM 的物理地址到用户空间
// 无 I/O 操作,纯内存拷贝
}
2.4 新 NTFS 驱动的 iomap 实现
// fs/ntfs3/iomap.c — 新 NTFS 驱动的 iomap 操作(简化)
static int ntfs_iomap_begin(struct inode *inode, loff_t offset,
loff_t length, unsigned flags,
struct iomap *iomap, struct iomap *srcmap)
{
struct ntfs_inode *ni = NTFS_I(inode);
struct ntfs_sb_info *sbi = ni->mi.sbi;
CLST vcn = offset >> sbi->cluster_bits;
CLST len = (length + sbi->cluster_mask) >> sbi->cluster_bits;
struct runs_tree *run = &ni->file.run;
// 查找簇映射
CLST lcn;
bool found = run_get_entry(run, vcn, &lcn, &len);
if (!found || lcn == SPARSE_LCN) {
// 空洞或未分配
if (flags & IOMAP_WRITE) {
// 写入时创建延迟分配
iomap->type = IOMAP_DELALLOC;
iomap->flags |= IOMAP_F_DIRTY;
iomap->addr = IOMAP_NULL_ADDR;
} else {
// 读取时返回空洞
iomap->type = IOMAP_HOLE;
iomap->addr = IOMAP_NULL_ADDR;
}
} else {
// 已分配的簇
iomap->type = IOMAP_MAPPED;
iomap->addr = (u64)lcn << sbi->cluster_bits;
iomap->flags |= IOMAP_F_MERGED; // 连续簇,可合并
}
iomap->offset = offset;
iomap->length = (u64)len << sbi->cluster_bits;
iomap->bdev = sbi->sb->s_bdev;
return 0;
}
static int ntfs_iomap_end(struct inode *inode, loff_t offset,
loff_t length, ssize_t written,
unsigned flags, struct iomap *iomap)
{
// 写入结束后的处理
if (written <= 0)
return written;
// 更新 inode 的时间戳
if (flags & IOMAP_WRITE) {
inode->i_mtime = inode->i_ctime = current_time(inode);
mark_inode_dirty(inode);
}
return 0;
}
const struct iomap_ops ntfs_iomap_ops = {
.iomap_begin = ntfs_iomap_begin,
.iomap_end = ntfs_iomap_end,
};
对比旧 NTFS3 的 buffer_head 实现,iomap 方案的优势非常明显:
// 旧 NTFS3 的写入路径(buffer_head 版本)— 问题重重
static int ntfs3_write_begin(struct file *file,
struct address_space *mapping,
loff_t pos, unsigned len,
struct page **pagep, void **fsdata)
{
// 问题 1:每个 page 需要创建 buffer_head 链表
// 大文件写入时,buffer_head 对象数量爆炸
struct buffer_head *bh;
struct page *page = grab_cache_page_write_begin(mapping, ...);
// 问题 2:每个 buffer_head 需要单独的块映射查找
// 4KB page × 512B 块 = 8 次 block_read_full_page
create_empty_buffers(page, sb->s_blocksize, 0);
bh = page_buffers(page);
while (bh) {
// 逐块映射,效率低下
map_buffer_to_page(mapping, bh, ...);
bh = bh->b_this_page;
}
// 问题 3:无法支持延迟分配
// 必须立即分配物理块并更新 MFT
err = ntfs3_alloc_clusters(sbi, &lcn, &clen);
*pagep = page;
return 0;
}
三、folio 内存模型:比 page 更大的内存管理单元
3.1 folio 解决了什么问题
Linux 内核从 2.4 时代就使用 struct page 作为内存管理的基本单位,每个 page 固定 4KB。这在 2026 年看来有几个严重问题:
- 指针开销:每个 page 有 64 字节的
struct page,1TB 内存需要 16GB 仅仅存放 page 结构 - 操作放大:处理一个大文件(比如 4TB NTFS 分区)需要操作 10 亿个 page
- 锁粒度太细:每个 page 一个锁,导致大量细粒度竞争
folio 的核心思想:允许将多个连续 page 组合为一个 folio,用单个结构体和单个锁管理。
// struct folio — page 的替代品
struct folio {
// 这些字段与 struct page 的前几个字段兼容
// 使得 folio 可以安全地 cast 为 page
unsigned long flags; // PG_locked, PG_dirty, PG_writeback 等
struct address_space *mapping; // 所属的地址空间
pgoff_t index; // 在地址空间中的位置
union {
struct list_head lru; // LRU 链表
struct {
void *__pad;
unsigned long mlock_count;
};
};
// ... 更多字段
};
// folio 的关键 API
size_t folio_size(struct folio *folio); // folio 的大小(可以是 4KB, 16KB, ..., 2MB)
struct page *folio_page(struct folio *folio, unsigned n); // 获取 folio 中的第 n 个 page
unsigned folio_nr_pages(struct folio *folio); // folio 包含的 page 数量
bool folio_test_large(struct folio *folio); // 是否是大 folio
3.2 folio 在文件系统中的实际收益
对于 NTFS 新驱动来说,folio 的收益主要体现在三方面:
(1)减少 address_space 操作的调用次数
// 旧方案:逐 page 操作
// 写入 64KB 数据需要操作 16 个 page
for (i = 0; i < 16; i++) {
struct page *page = grab_cache_page(mapping, index + i);
// 每个 page 单独获取锁
lock_page(page);
// 每个 page 单独标记脏
set_page_dirty(page);
unlock_page(page);
put_page(page);
}
// 总共:16 次 lock/unlock + 16 次 dirty 标记
// 新方案:folio 操作
// 写入 64KB 数据只需要操作 1 个 order-4 folio
struct folio *folio = filemap_grab_folio(mapping, index);
// 一次锁覆盖整个 64KB
folio_lock(folio);
// 一次脏标记覆盖整个 64KB
folio_mark_dirty(folio);
folio_unlock(folio);
folio_put(folio);
// 总共:1 次 lock/unlock + 1 次 dirty 标记
(2)减少 writeback 的 bio 提交次数
// writeback 路径的 folio 优化
static int ntfs_submit_folio(struct folio *folio,
struct writeback_control *wbc)
{
struct bio *bio;
struct inode *inode = folio->mapping->host;
// 一个 order-4 folio(64KB)只需要一个 bio_vec
// 而不是 16 个 page 各需要一个 bio_vec
bio = bio_alloc(inode->i_sb->s_bdev,
folio_nr_pages(folio), // 一次分配足够的 bio_vec
REQ_OP_WRITE,
GFP_NOFS);
// 整个 folio 一次性加入 bio
bio_add_folio(bio, folio, folio_size(folio),
folio_pos(folio) - folio->mapping->host->i_ino);
// 一次提交,而不是 16 次
submit_bio(bio);
return 0;
}
(3)减少内存分配器压力
// folio 分配 vs page 分配
// 旧方案:分配 16 个 page
for (i = 0; i < 16; i++) {
pages[i] = alloc_page(GFP_NOFS);
// 16 次分配器调用,16 次 per-cpu 操作
}
// 新方案:分配 1 个 order-4 folio
struct folio *folio = filemap_alloc_folio(GFP_NOFS, 4);
// 1 次分配器调用,连续物理内存
// 更好的 TLB 局部性
3.3 NTFS 新驱动的 folio 适配
// NTFS 新驱动中 folio 相关的操作实现
const struct address_space_operations ntfs_aops = {
// folio 版本的脏页标记
.dirty_folio = filemap_dirty_folio,
// folio 版本的迁移(用于内存规整)
.migrate_folio = buffer_migrate_folio,
// folio 版本的缓存失效
.invalidate_folio = iomap_invalidate_folio,
// folio 版本的释放判断
.release_folio = iomap_release_folio,
// folio 版本的写入开始/结束
.write_begin = iomap_write_begin, // iomap 统一入口
.write_end = iomap_write_end,
// DIO 支持
.direct_IO = iomap_dio_rw,
// writeback 支持
.writepages = iomap_writepages,
};
四、FRED:Intel 的事件分发革命
4.1 FRED 是什么
Linux 7.1 引入了对 Intel FRED(Flexible Return and Event Delivery)的支持,这是 Intel 为 Panther Lake 及后续处理器设计的全新异常/中断/事件处理机制。
传统 x86 的中断和异常处理路径是从 8086 时代继承下来的,已经积累了 40 多年的技术债务:
传统 IDT(Interrupt Descriptor Table)路径:
CPU 触发异常/中断
→ 查找 IDT 表获取门描述符
→ 切换栈(如果特权级变化)
→ 保存部分寄存器(CPU 自动保存的 5 个)
→ 软件保存剩余寄存器(中断处理程序手动保存)
→ 执行中断处理程序
→ 软件恢复寄存器
→ iret 指令返回
问题:
1. iret 是非原子操作,NMI 嵌套时可能破坏栈
2. 栈切换逻辑复杂,依赖 TR(Task Register)
3. 寄存器保存/恢复路径不统一
4. 安全漏洞面大(Spectre/MDS 类攻击常利用中断路径)
FRED 重新设计了这条路径:
FRED 路径:
CPU 触发事件(异常/中断/NMI/#MC)
→ CPU 自动保存完整的寄存器状态到 FRED 栈帧
→ 切换到 FRED 专用栈
→ 一次性设置新的 CS:RIP
→ 执行统一的事件处理程序
→ ERETS 指令原子返回
改进:
1. ERETS 是原子操作,无 iret 的嵌套风险
2. CPU 自动保存所有寄存器,无软件保存需求
3. 统一的事件入口,消除了 IDT 的碎片化
4. 更小的攻击面,更安全
4.2 Linux 7.1 的 FRED 内核适配
// arch/x86/kernel/fred.c — FRED 事件入口(简化)
/*
* FRED 事件入口点
* 所有异常和中断通过统一的 FRED 入口进入内核
* 不再需要 IDT 中的多个入口点
*/
// FRED 栈帧布局 — CPU 自动保存
struct fred_stack {
u64 r15, r14, r13, r12, rbp, rbx;
u64 r11, r10, r9, r8, rax, rcx, rdx, rsi, rdi;
u64 orig_rax; // 事件向量号
u64 error_code; // 错误码(如果适用)
u64 rip;
u64 cs;
u64 rflags;
u64 rsp;
u64 ss;
};
// FRED 统一事件处理器
DEFINE_FRED_HANDLER(fred_event_entry)
{
struct fred_stack *frame = (struct fred_stack *)__fred_rsp();
unsigned int vector = frame->orig_rax;
// FRED 将所有事件路由到统一入口
// 根据向量号分发到具体处理程序
switch (vector) {
case X86_TRAP_DE: // 除零错误
return exc_divide_error(frame);
case X86_TRAP_PF: // 页错误
return exc_page_fault(frame);
case X86_TRAP_NMI: // NMI
return exc_nmi(frame);
case X86_TRAP_MC: // 机器检查
return exc_machine_check(frame);
default:
// 通用中断处理
if (vector >= FIRST_EXTERNAL_VECTOR) {
return fred_handle_irq(frame, vector);
}
return fred_handle_exception(frame, vector);
}
}
// ERETS — FRED 的原子返回指令
// 替代了 iret,消除了 iret 的嵌套风险
static inline void fred_return(struct fred_stack *frame)
{
// ERETS 从 FRED 栈帧恢复所有寄存器
// 这是一个原子操作,不会被 NMI 打断后破坏
asm volatile("ERETS");
}
4.3 FRED 对性能的影响
FRED 的性能收益主要体现在减少中断处理的开销:
// 传统 IDT 路径的寄存器保存(软件完成)
// entry_64.S — 传统中断入口
ENTRY(interrupt_entry)
// 手动保存寄存器 — 大约 20+ 条指令
pushq %rdi
pushq %rsi
pushq %rdx
pushq %rcx
pushq %rax
pushq %r8
pushq %r9
pushq %r10
pushq %r11
// ... 更多寄存器
// 还需要处理 SWAPGS、内核栈切换等
// 总计约 40-60 条指令才能进入中断处理
END(interrupt_entry)
// FRED 路径 — CPU 自动完成
// fred_event_entry 直接从完整栈帧开始
// 不需要手动保存任何寄存器
// 节省约 40-60 条指令的中断入口开销
// 在高频中断场景(网络、存储)下收益显著
对 NTFS 新驱动的影响:在高 IOPS 的存储场景下,中断处理的开销占比可观。FRED 减少了每次 I/O 完成中断的入口开销,对于 NVMe 设备的轮询模式和中断模式都有实际收益。
五、三行代码的魔法:Linux 7.2 iomap 优化深度分析
5.1 问题发现
2026 年 6 月,字节跳动工程师 Fengnan Chang 在审查 iomap 框架代码时发现了一个看似无害但实际上浪费巨大的模式:
// 优化前的 iomap_iter() 函数(Linux 7.1)
/*
* iomap_iter() — iomap 迭代器的核心循环
* 每次迭代处理一个块映射区间
*/
int iomap_iter(struct iomap_iter *iter, const struct iomap_ops *ops)
{
struct iomap *iomap = &iter->iomap;
int ret;
// 调用文件系统的 iomap_begin 获取块映射
ret = ops->iomap_begin(iter->inode, iter->pos, iter->len,
iter->flags, iomap, &iter->srcmap);
if (ret)
return ret;
// 执行实际的 I/O 操作(读/写)
ret = iomap_iter_handle(iter);
// 调用文件系统的 iomap_end 清理
if (ops->iomap_end) {
ret2 = ops->iomap_end(iter->inode, iter->pos, iter->len,
ret, iter->flags, iomap);
}
// *** 问题在这里 ***
// 每次迭代结束后,清空 iomap 结构体
// 但调用者在迭代结束后会直接丢弃迭代器!
// 这次 memset 是完全无用的
memset(iomap, 0, sizeof(*iomap)); // ← 浪费的内存写入
return ret;
}
5.2 优化的原理
// 优化后的 iomap_iter() 函数(Linux 7.2)
int iomap_iter(struct iomap_iter *iter, const struct iomap_ops *ops)
{
struct iomap *iomap = &iter->iomap;
int ret;
ret = ops->iomap_begin(iter->inode, iter->pos, iter->len,
iter->flags, iomap, &iter->srcmap);
if (ret)
return ret;
ret = iomap_iter_handle(iter);
if (ops->iomap_end) {
ret2 = ops->iomap_end(iter->inode, iter->pos, iter->len,
ret, iter->flags, iomap);
}
// 移除无用的 memset
// 调用者要么会重新初始化 iomap(下次迭代)
// 要么会丢弃整个迭代器
// 无论哪种情况,memset 都是浪费
return ret;
}
看起来只移除了一行 memset,但实际上是三行代码的位置调整——包括相关的条件判断和注释。
5.3 为什么两行代码能提升 5% IOPS
关键在于调用频率和缓存行效应:
// iomap_iter 的调用频率分析
// 在 fio 高 IOPS 测试中:
// - NVMe SSD 可以做到 100 万+ IOPS
// - 每个 I/O 操作至少调用一次 iomap_iter
// - 大文件操作可能多次调用(多次迭代)
// 每次 iomap_iter 调用中的 memset 开销:
// sizeof(struct iomap) ≈ 80-120 字节
// memset(iomap, 0, sizeof(*iomap)) ≈ 80-120 字节的内存写入
// 在高 IOPS 场景下的总开销:
// 1,000,000 IOPS × 100 字节 = 100MB/s 的无用内存写入
// 这 100MB/s 完全是浪费的——写入的数据从未被读取
// 移除后的收益:
// 1. 减少了 100MB/s 的内存带宽占用
// → 内存带宽可以用于实际数据传输
// 2. 减少了 iomap 结构体所在的缓存行失效
// → iomap 更可能留在 L1/L2 缓存中
// 3. 减少了 store buffer 的压力
// → CPU 的 store buffer 不再被无用写入填满
5.4 实测验证
# 使用 fio 验证 iomap memset 优化的效果
# 测试环境
# - CPU: AMD EPYC 9654 (96 cores)
# - NVMe: Samsung PM9A3 3.84TB
# - 内核: Linux 7.2-rc1 (含优化) vs Linux 7.1 (无优化)
# - 文件系统: XFS, ext4
# 4K 随机读 — 基准测试(不受 iomap 写入路径影响)
fio --name=rand-read --ioengine=io_uring --iodepth=128 \
--rw=randread --bs=4k --size=10G --numjobs=16 \
--time_based --runtime=60
# 4K 随机写 — 受 iomap 影响
fio --name=rand-write --ioengine=io_uring --iodepth=128 \
--rw=randwrite --bs=4k --size=10G --numjobs=16 \
--time_based --runtime=60
# 结果(示意):
# ext4 randread: 基本无变化(读路径不触发 iomap memset)
# ext4 randwrite: +4.8% IOPS
# xfs randread: 基本无变化
# xfs randwrite: +5.2% IOPS
#
# 在 io_uring + 高 iodepth 场景下收益最大
# 因为 io_uring 的批量提交放大了 iomap_iter 的调用频率
六、Linux 7.1/7.2 存储栈的全景图
把所有这些改进放在一起,我们可以看到 Linux 存储栈在 2026 年的整体演进:
Linux 7.1/7.2 存储栈演进全景
┌──────────────────────────────────────────────────────┐
│ 用户空间 (User Space) │
│ fio / io_uring / fsp / 直接 I/O 应用 │
├──────────────────────────────────────────────────────┤
│ VFS / 系统调用层 │
│ read/write → iomap_iter → writepages │
├──────────────────────────────────────────────────────┤
│ iomap 框架 (统一映射层) │
│ [7.2] 移除无用 memset → +5% IOPS │
│ 四种映射类型: HOLE / DELALLOC / MAPPED / UNWRITTEN │
├──────────────────────────────────────────────────────┤
│ 文件系统层 (File Systems) │
│ [7.1] 新 NTFS → iomap + folio + 延迟分配 │
│ [7.1] ext4, XFS → folio 优化持续进行 │
│ [7.1] Btrfs → 进一步 iomap 适配 │
├──────────────────────────────────────────────────────┤
│ 页缓存 / folio 层 │
│ [7.1] folio 全面替代 page │
│ 大 folio 减少锁竞争和操作放大 │
├──────────────────────────────────────────────────────┤
│ 块设备 / bio 层 │
│ bio → NVMe / SCSI / virtio │
├──────────────────────────────────────────────────────┤
│ 硬件中断层 │
│ [7.1] FRED → 统一事件入口,减少中断开销 │
│ 传统 IDT → 仍支持,逐步淘汰 │
└──────────────────────────────────────────────────────┘
七、实战:在 Linux 7.1 上编译和测试新 NTFS 驱动
7.1 编译启用新 NTFS 驱动的内核
# 获取 Linux 7.1 源码
git clone --depth 1 --branch v7.1 \
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
# 配置内核 — 启用新 NTFS 驱动
make menuconfig
# File systems → DOS/FAT/EXFAT/NT Filesystems
# <*> NTFS file system support (new driver) ← 启用新驱动
# < > NTFS 3.0 driver (Paragon) ← 禁用旧驱动
# < > NTFS read-only driver ← 禁用只读驱动
# 或者直接修改 .config
echo "CONFIG_NTFS_FS=y" >> .config
echo "# CONFIG_NTFS3_FS is not set" >> .config
echo "# CONFIG_NTFS_FS_OLD is not set" >> .config
# 编译
make -j$(nproc)
# 安装
sudo make modules_install
sudo make install
# 更新 GRUB
sudo update-grub
sudo reboot
7.2 验证新驱动已加载
# 检查 NTFS 模块
cat /proc/filesystems | grep ntfs
# 应该只看到一行 ntfs(新驱动)
# 检查模块信息
modinfo ntfs
# 应该看到 iomap 相关的依赖
# 挂载 NTFS 分区
sudo mount -t ntfs /dev/nvme0n1p3 /mnt/windows
# 验证延迟分配已启用
# 查看挂载选项
mount | grep ntfs
# 应该看到 delalloc 选项
# 基本性能测试
# 写入测试
dd if=/dev/zero of=/mnt/windows/testfile bs=1M count=1024 oflag=direct
# 读取测试
dd if=/mnt/windows/testfile of=/dev/null bs=1M iflag=direct
# 使用 fio 进行精确测试
fio --name=ntfs-write --filename=/mnt/windows/testfile \
--ioengine=libaio --iodepth=64 \
--rw=write --bs=1M --size=4G \
--numjobs=4 --group_reporting
7.3 对比新旧驱动的性能
# 测试脚本 — 对比 NTFS3 和新 NTFS 驱动
#!/bin/bash
# ntfs_benchmark.sh
MOUNT_POINT="/mnt/ntfs_test"
TEST_DEVICE="/dev/nvme0n1p3"
FIO_JOB="/tmp/ntfs_fio.job"
cat > $FIO_JOB << EOF
[global]
ioengine=io_uring
iodepth=128
size=2G
numjobs=4
group_reporting
time_based
runtime=30
[seq-write]
rw=write
bs=1M
[rand-write]
rw=randwrite
bs=4k
[seq-read]
rw=read
bs=1M
[rand-read]
rw=randread
bs=4k
EOF
# 测试新 NTFS 驱动
echo "=== Testing new NTFS driver ==="
sudo mount -t ntfs $TEST_DEVICE $MOUNT_POINT
fio $FIO_JOB --directory=$MOUNT_POINT --output=new_ntfs.json --output-format=json
sudo umount $MOUNT_POINT
# 测试 NTFS3 驱动(需要重启并切换内核配置)
echo "=== Switch to NTFS3 and reboot to test ==="
echo "After reboot with NTFS3 enabled, run:"
echo "fio $FIO_JOB --directory=$MOUNT_POINT --output=ntfs3.json --output-format=json"
八、Linux 7.1 的其他重要特性
8.1 移除 Intel 486 支持
Linux 7.1 正式移除了对 Intel 486 处理器的支持。这不是一个简单的清理——它允许内核使用 486 不支持的指令和特性:
// 移除 486 支持后的优化示例
// 旧代码需要兼容 486(无 cmpxchg8b)
static inline int atomic64_cmpxchg(atomic64_t *v, long long old, long long new)
{
// 486 上需要用 cmpxchg8b 指令模拟
// 或者回退到自旋锁
unsigned long flags;
long long val;
raw_spin_lock_irqsave(&atomic64_lock, flags);
val = v->counter;
if (val == old)
v->counter = new;
raw_spin_unlock_irqrestore(&atomic64_lock, flags);
return val;
}
// 新代码可以直接使用 64 位原子操作
static inline long long atomic64_cmpxchg(atomic64_t *v, long long old,
long long new)
{
// 586+ 有 cmpxchg8b,可以直接使用
// 更快,无锁竞争
return cmpxchg8b(&v->counter, old, new);
}
8.2 Apple Silicon 电池状态报告
Linux 7.1 新增了 Apple Silicon 设备的电池状态报告支持,这对在 M1/M2/M3 Mac 上运行 Linux 的用户来说是实用改进。
8.3 AMD CPU 电源管理优化
针对 AMD Ryzen/EPYC 处理器的电源管理状态切换进行了优化,减少了不必要的功耗。
8.4 Intel Arc Battlemage 图形支持
对 Intel Arc Battlemage 系列显卡的驱动支持进一步成熟。
九、Linux 7.2 前瞻:M3 启动支持与更多优化
Linux 7.2 的合并窗口已在 6 月中旬开启,已知的重大特性包括:
9.1 Apple M3 系列设备启动支持
开发者 Sven Peter 提交的补丁将使 Linux 7.2 主线内核首次支持苹果 M3 系列芯片设备启动,覆盖 iMac、MacBook Air 和 MacBook Pro。
9.2 移除上古驱动
继 Linux 7.1 移除 486 支持后,Linux 7.2 继续清理旧硬件驱动,包括超过 20 年未更新的 DoubleTalk ISA 语音合成卡驱动。
9.3 NVK 开源驱动获得 DLSS 支持
Mesa 26.2 合并了 NVK(NVIDIA 开源 Vulkan 驱动)的 DLSS 支持,Linux 上的 NVIDIA 游戏体验将显著改善。
十、对开发者的启示:从内核优化中学到什么
10.1 无用代码是最贵的代码
iomap 的 memset 优化告诉我们:即使是一行看似无害的初始化代码,在高频路径上也可能成为性能杀手。在你的项目中,检查以下高频路径是否有类似的"安全但无用"的操作:
// 常见的高频路径性能陷阱
// 陷阱 1:循环内的冗余初始化
for (int i = 0; i < N; i++) {
memset(&ctx, 0, sizeof(ctx)); // 每次循环都清零
ctx.handle_request(requests[i]);
// ctx 在下次循环开始时又被 memset 清零
// 但 handle_request 内部已经初始化了所有需要的字段
}
// 优化:移除冗余 memset,或确保只在必要时清零
// 陷阱 2:热路径上的日志
void process_request(Request *req) {
log_debug("Processing request %p", req); // debug 日志在生产环境可能关闭
// 但函数调用和参数评估的开销仍然存在
do_process(req);
}
// 优化:使用编译期条件或 unlikely 标注
void process_request(Request *req) {
if (unlikely(log_level >= LOG_DEBUG))
log_debug("Processing request %p", req);
do_process(req);
}
// 陷阱 3:过度防御性的错误检查
int read_data(int fd, void *buf, size_t len) {
if (fd < 0) return -EINVAL; // 在热路径上反复检查
if (!buf) return -EINVAL; // 调用者保证非 NULL
if (len == 0) return 0; // 调用者保证 len > 0
return do_read(fd, buf, len);
}
// 优化:在入口点检查一次,内部函数信任调用者
10.2 延迟分配思想的应用
NTFS 新驱动的延迟分配策略不仅适用于文件系统,在应用层同样有用:
# 应用层的"延迟分配"模式
class BatchProcessor:
"""延迟分配的应用层实现"""
def __init__(self):
self.pending_ops = [] # 延迟的操作队列
self.batch_size = 1000
def write(self, key: str, value: str):
"""写入操作不立即执行,而是缓存起来"""
self.pending_ops.append(("write", key, value))
# 达到批量阈值时一次性提交
if len(self.pending_ops) >= self.batch_size:
self.flush()
def flush(self):
"""批量提交所有延迟的操作"""
if not self.pending_ops:
return
# 一次性提交 → 减少网络往返和事务开销
# 类似于 NTFS 延迟分配的 writeback
batch = self.pending_ops
self.pending_ops = []
# 可以在 flush 时做优化:合并相同 key 的写入
optimized = self._merge_writes(batch)
self._submit_batch(optimized)
def _merge_writes(self, ops):
"""合并同一 key 的多次写入,只保留最后一次"""
latest = {}
for op_type, key, value in ops:
latest[key] = value
return [("write", k, v) for k, v in latest.items()]
10.3 folio 思想:批量操作的力量
folio 的核心思想——用更大的操作单元减少操作次数——在分布式系统中同样适用:
# folio 思想在分布式系统中的应用
class BatchRPC:
"""批量 RPC — folio 思想的网络版"""
def __init__(self, stub, max_batch_size=100, max_latency_ms=5):
self.stub = stub
self.max_batch_size = max_batch_size
self.max_latency_ms = max_latency_ms
self.pending = []
self.timer = None
async def call(self, request):
"""单个请求不立即发送,而是攒批"""
future = asyncio.get_event_loop().create_future()
self.pending.append((request, future))
if len(self.pending) >= self.max_batch_size:
await self._flush()
elif not self.timer:
self.timer = asyncio.ensure_future(
asyncio.sleep(self.max_latency_ms / 1000,
result=self._flush())
)
return await future
async def _flush(self):
"""批量发送 — 减少网络往返次数"""
if not self.pending:
return
batch = self.pending
self.pending = []
self.timer = None
requests = [r for r, _ in batch]
futures = [f for _, f in batch]
try:
# 一次 RPC 调用发送所有请求
responses = await self.stub.BatchCall(requests)
for future, response in zip(futures, responses):
future.set_result(response)
except Exception as e:
for future in futures:
future.set_exception(e)
总结:从"重生"到"革命"
Linux 7.1 的 NTFS 重生和 Linux 7.2 的两行代码优化,看似是两个不同层面的改进,但它们共享同一个底层逻辑:消除不必要的开销,让每一行代码都为实际工作服务。
NTFS 新驱动消除了 20 年来旧驱动的不必要限制(只读、无延迟分配、buffer_head 开销),而 iomap 的 memset 移除则消除了每次迭代的不必要内存写入。前者是架构级的重构,后者是微秒级的优化,但目标一致:让 I/O 路径上的每一个操作都是有意义的。
对 Linux 内核开发者来说,这是 2026 年最好的时代:iomap + folio 的现代化基础设施已经就绪,FRED 的事件分发机制为新处理器铺平了道路,NTFS 的重生让跨平台文件交换不再痛苦。
对应用开发者来说,这些内核级的优化提供了宝贵的思维模型:延迟分配、批量操作、消除无用代码——这些原则不分内核态和用户态,在任何高性能系统中都适用。
Linus Torvalds 用 "resurrection" 形容 NTFS 的重生。但在我看来,2026 年 Linux 内核存储栈的变革不仅仅是某个驱动的重生,而是一次从底层到顶层的系统性革命。从 FRED 的中断路径优化,到 iomap 的映射框架统一,到 folio 的内存管理现代化,再到文件系统驱动的全面重构——每一层都在向同一个方向演进:更快、更简洁、更少浪费。
这就是 Linux 的方式。不是推倒重来,而是一层一层地打磨,直到每个路径都无可优化。
参考资源:
- Linux 7.1 NTFS 合并提交:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=cdd4dc3aebeab43a72ce0bc2b5bab6f0a80b97a5
- iomap 框架文档:https://www.kernel.org/doc/html/latest/filesystems/iomap/index.html
- folio 设计文档:https://lwn.net/Articles/849538/
- FRED 规范:Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3, Chapter 6
- Linux 7.2 iomap 优化:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/?qt=fengnan+chang+iomap