当前位置: 首页 > 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.jsqmd.com/news/1016817/

相关文章:

  • 2026年成都监控品牌怎么选?行业视角下的弱电工程服务商实力解析 - 优质品牌商家
  • claude code 部署方法
  • 告别玄学调参:手把手教你用ENVI Deep Learning 1.2优化遥感影像分类效果(附样本ROI绘制技巧)
  • 多维聚合实战:从SQL到Doris的OLAP数据操作心法
  • 红米Note11刷Magisk后无限重启?可能是AVB2.0和Magisk版本没搞对(附救砖思路)
  • 别再被网站识别成机器人了!用Chromedp + Go 实现‘隐身’爬虫的完整配置清单
  • 通话清晰蓝牙耳机技术选型与实测:从ENC降噪原理到旗舰方案对比(2026版)
  • Win10下Cadence OrCAD卡死?别急着重装,先试试关掉这个隐藏设置
  • 别再只记错误码了!用Python+OPC UA Client库,自动解析并处理这些状态码(附完整脚本)
  • 嵌入式通信实战:MPC8272 SPI/I2C协议与BD机制深度解析
  • TLE5012B寄存器配置避坑指南:从CRC校验失败到自动校准,我的调试笔记
  • 国民技术N32G030K8L7内部FLASH读写避坑指南:从解锁到校验的完整流程
  • 从生成式AI到智能代理:AI正在进入“第二阶段”
  • LabVIEW NIPM安装报错别慌!手把手教你定位C盘隐藏日志文件(附MSI/cURL日志开启命令)
  • 从‘矩阵求逆失败’到排查指南:盘点NumPy、PyTorch中判断矩阵可逆性的实战技巧与常见坑
  • SIT2515与MCP2515引脚兼容吗?国产替代实战中的那些‘坑’与解决方案
  • 测试用例自动生成助手-Dify API 部署到飞书
  • OpenCode可视化使用方式
  • NDB分数:量化GAN模式坍缩的无预训练评估方法
  • Rancher v2.7.5集群导入翻车实录:cattle-system卡在Terminating,我是如何一步步救回来的
  • 2026主流AI编程工具榜单:开发者实测第一梯队选型参考
  • 避坑指南:Oracle 19c DataGuard配置中那些容易踩的“雷”(归档、网络、密码文件)
  • Claude Code 完全使用指南:从入门到精通
  • SVM实操手记:小样本高维噪声数据下的鲁棒分类器
  • ENVI Deep Learning 1.2实战踩坑记:从TensorBoard白屏到模型分类效果差,我的避坑全记录
  • 别慌!MCU死机后,用Ozone和Keil这招非侵入式调试,5分钟定位HardFault
  • Qt5.15 + QWebEngine网页加载慢到超时?一个抓包对比Chrome的实战排查记录
  • 2026年论文党必备:盘点2026年碾压级的一键生成论文工具
  • 2026年靠谱无油空压机工厂哪家强
  • 手把手教你解决STM32CubeIDE中ST-LINK与GDB服务端的端口冲突问题(附端口查看与修改教程)