当前位置: 首页 > news >正文

Linux generic_file_buffered_write缓冲写与pagecache

Linux generic_file_buffered_write是VFS层缓冲写路径的入口函数,它将用户态数据先写入pagecache,再通过后台或同步写回机制持久化到磁盘。该函数封装了从iov_iter遍历、pagecache查找/分配、数据拷贝到标记脏页的完整流程。

```
// mm/filemap.c
ssize_t generic_file_buffered_write(struct kiocb *iocb,
struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
struct address_space *mapping = file->f_mapping;
struct inode *inode = mapping->host;
ssize_t written = 0;
ssize_t status;

do {
status = generic_perform_write(iocb, from);
if (likely(status >= 0))
written += status;
else if (written)
break;
else
return status;
} while (iov_iter_count(from));

return written;
}
```

外层是一个循环,保证当一次generic_perform_write未消耗完所有用户数据时继续执行。实际上generic_perform_write内部对文件锁定位和分页操作已经做了分段,外层循环更多是兜底保护。

```
// mm/filemap.c
ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
{
struct file *file = iocb->ki_filp;
struct address_space *mapping = file->f_mapping;
const struct address_space_operations *a_ops = mapping->a_ops;
loff_t pos = iocb->ki_pos;
ssize_t written = 0;

do {
struct page *page;
unsigned long offset;
size_t bytes;
loff_t newsize;

bytes = iov_iter_count(i);
offset = pos & (PAGE_SIZE - 1);
bytes = min(bytes, (size_t)PAGE_SIZE - offset);

if (bytes == 0)
break;

page = a_ops->write_begin(file, mapping, pos, bytes, &page,
&fsdata);
if (unlikely(IS_ERR(page)))
return PTR_ERR(page);

copied = copy_page_from_iter_atomic(page, offset, bytes, i);
flush_dcache_page(page);

status = a_ops->write_end(file, mapping, pos, bytes, copied,
page, fsdata);
if (unlikely(status < 0))
break;
written += status;

if (status != bytes)
break;

pos += status;
cond_resched();
} while (iov_iter_count(i));

return written ? written : status;
}
```

write_begin回调的核心工作是在pagecache中查找或创建目标页面。对于ext4来说,ext4_write_begin调用grab_cache_page_write_begin在radix tree/xarray中寻找页面,如果不存在则分配一个全新的pagecache页并加入mapping。

```
// mm/filemap.c
struct page *grab_cache_page_write_begin(struct address_space *mapping,
pgoff_t index, unsigned flags)
{
struct page *page;
int fgp_flags = FGP_LOCK | FGP_WRITE | FGP_CREAT | FGP_STABLE;

page = pagecache_get_page(mapping, index, fgp_flags,
mapping_gfp_mask(mapping));
if (page)
wait_for_stable_page(page);

return page;
}
```

pagecache_get_page的FGP_CREAT标志意味着如果页面不存在,内核通过__page_cache_alloc分配一个新页面,并通过add_to_page_cache_lru将其插入mapping的xarray和LRU链中。这个过程持有inode锁和页面锁,保证并发写不会冲突。

数据拷贝由copy_page_from_iter_atomic完成。该函数使用map操作的kmap_atomic将page映射到内核虚拟地址空间,然后通过copyin从用户态iov_iter拷贝数据。注意这里的"atomic"是指映射操作是原子的,不是指整个写操作不可被抢占。

```
// lib/iov_iter.c
size_t copy_page_from_iter_atomic(struct page *page, unsigned offset,
size_t bytes, struct iov_iter *i)
{
char *kaddr = kmap_atomic(page);
size_t n;

n = copyin(kaddr + offset, i->data_source, bytes);
kunmap_atomic(kaddr);

return n;
}
```

write_end回调完成数据拷贝后的收尾工作。对于ext4,ext4_write_end调用block_write_end将页面标记为脏,并处理延迟分配。如果新写入的位置扩展了文件大小,还需要更新i_size并触发inode的iversion变更。

```
// fs/buffer.c
int block_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata)
{
unsigned start = pos & (PAGE_SIZE - 1);

if (unlikely(copied < len)) {
if (!PageUptodate(page)) {
zero_user(page, start + copied, len - copied);
}
}
if (!PageUptodate(page))
SetPageUptodate(page);

if (pos + copied > inode->i_size)
i_size_write(inode, pos + copied);

set_page_dirty(page);
return copied;
}
```

set_page_dirty通过__set_page_dirty_buffers将页面加入BDI(backing device info)的脏链表,并设置xarray中的PAGECACHE_TAG_DIRTY标签,供后续writeback线程扫描写回。

缓冲写的页面锁定模型值得注意:write_begin获取页面锁,write_end释放页面锁。在锁持有期间,任何其他读者(如直接I/O或mmap缺页)都会阻塞在这个页面上。同时write_begin和write_end之间不能睡眠太久,因此大块写入被切成多个以PAGE_SIZE为单位的循环。

对于大文件追加写入,generic_perform_write每次向前推进pos,下一次循环通过pagecache_get_page可能命中刚刚写入的页面(如果页面大小大于写入块),此时write_begin直接返回已有页面,避免了页面分配开销。这个行为对顺序写性能至关重要。

最后,如果文件系统启用了DAX(直接访问),generic_file_buffered_write不会被执行,转而走dax_iomap_rw路径绕过pagecache。因此generic_file_buffered_write只在非DAX模式且非O_DIRECT打开的文件上执行,这是内核缓冲写路径的核心数据流。

http://www.zskr.cn/news/1528639.html

相关文章:

  • claude code 部署方法
  • 红米Note11刷Magisk后无限重启?可能是AVB2.0和Magisk版本没搞对(附救砖思路)
  • 嵌入式通信实战:MPC8272 SPI/I2C协议与BD机制深度解析
  • SVM实操手记:小样本高维噪声数据下的鲁棒分类器
  • Claude Code 完全使用指南:从入门到精通
  • 2026主流AI编程工具榜单:开发者实测第一梯队选型参考
  • 手把手教你解决STM32CubeIDE中ST-LINK与GDB服务端的端口冲突问题(附端口查看与修改教程)
  • 保姆级教程:用一条带参数的启动命令,绕过Oracle 12c安装时的INS-30131验证错误
  • Qt开发避坑指南:QTabBar信号连接、内存管理与样式自定义的那些“坑”
  • CAN总线Bus Off了别慌!手把手教你用CANalyzer/CANoe诊断与快慢恢复(附ISO11898标准解读)
  • Windows VMware虚拟机配置5070深度学习环境搭建
  • 2026年成都私立中学招生机构综合评估:真实案例与机构特性分析 - 优质品牌商家
  • 飞秒激光诱导二氧化硅高压相变研究与应用
  • LIN总线没反应?别慌,手把手教你排查这5个最常见的原因(附排查流程图)
  • 避坑指南:Win10配置Samba访问远程Linux时,端口映射和权限设置的那些‘雷’我都帮你踩过了
  • 苹果审核被拒 5.2.3 怎么办?分享一次真实项目成功过审经历
  • ZCode 3.0 版本搭配GLM-5.2能力测试
  • 远程办公救星:除了Putty,你的Windows Terminal/WSL2 SSH连接不稳?试试这个sshd服务端配置
  • AI Orchestration实战:MuleSoft+LangChain双引擎架构设计
  • 从课设到产品:聊聊基于MPU6050的跌倒检测项目那些容易被忽略的坑(ESP8266驱动、阈值设定)
  • 内江市五家靠谱店铺TOP排行榜及联系方式地址+黄金回收门店推荐 电话+白银回收+铂金回收+彩金回收当场结算 - 盛世金银回收
  • 车载测试新人避坑指南:OTA升级、UDS诊断、T-BOX测试三大模块的面试实战解析
  • React状态管理深度辨析:Context、Redux、Zustand核心区别与实战选型
  • 多维聚合操纵:从OLAP立方体到动态分析引擎
  • 直播预告!从 MLA 到 GQLA:无需从头训练,硬件自适应高效注意力机制
  • AWS数据湖实战:从S3分层设计到可信数据交付
  • Mythos架构解析:模块化推理与门控式能力释放
  • 荆门市黄金回收门店推荐 五家靠谱店铺TOP排行榜及联系方式地址电话+白银回收+铂金回收+彩金回收当场结算 - 大熊猫898989
  • 靠谱的超市收银系统公司 - myqiye
  • 2026年西北风管加工市场观察:哪家工厂更懂你的通风工程需求? - 优质品牌商家