求教:内核中 truncate 和 write 可以并发执行么?结果是怎样的?

发布于 2022-10-15 06:20:44 字数 1964 浏览 24 评论 0

假设文件 A 初始长度为 100 MB,文件所有字节全部非 0;
线程 1 执行 truncate,把文件长度截短为 20 MB;
线程 2 执行 write 操作,从 99 MB + 1023 KB + 100 B 处开始写 10 字节的 1;
问题是,当两个操作都完成之后,文件内容会是怎样的?

可以分三种情况讨论:

1、write 发生在 truncate 之前;
   -> 文件被截断,长度为 20 MB

2、write 发生在 truncate 之后;
   -> 文件先被截断到 20 MB,然后增长到 99 MB + 1023 KB + 110 B
   -> 并且文件从 20 MB 到 99 MB + 1023 KB + 100 B 全为 0
   -> 文件从 99 MB + 1023 KB + 100 B 开始全为 1

3、write 发生在 truncate 之中。
   这种情况比较复杂。

   查看了内核代码,truncate 是在 mm/truncate.c 中 truncate_inode_pages_range 函数中完成的
   这个函数先把所有非脏的页 truncate 掉,然后等脏页写入完成之后,把他们也 truncate 掉
   假设在 truncate 之前,有人在 99 MB + 1023 KB + 50 B 处写入了 10 字节的 2
   使得 500 MB 起始的这个 page 变为 dirty
   truncate_inode_pages_range 第一遍检查的时候,发现该页为脏页,没有 truncate 该页
   在第二遍检查之前,另一个线程写入了那 10 字节的 1,并且之后设备驱动才把这个页写到磁盘
   那么第二遍检查脏页并等待写入成功的时候,实际等到的是把那 10 个字节的 2 和 10 个字节的 1 全部写入磁盘
   这时才 truncate 该页
   write 10 字节 1 完成之后,第二个线程修改了文件长度为 99 MB + 1023 KB + 110 B
   最终结果是
     -> 文件先被截断到 20 MB,然后增长到 99 MB + 1023 KB + 110 B
     -> 并且文件从 20 MB 到 99 MB + 1023 KB + 50 B 全为 0
     -> 并且文件从 99 MB + 1023 KB + 50 B 到 99 MB + 1023 KB + 60 B 全为 2
     -> 并且文件从 99 MB + 1023 KB + 60 B 到 99 MB + 1023 KB + 100 B 全为 0
     -> 文件从 99 MB + 1023 KB + 100 B 开始全为 1
   这种结果不是很奇怪么?
   是不是内核中对于同一个文件的 truncate 操作和 write 操作不能允许并发执行呢?
   可是也没有见到 write 操作一定要锁 inode->i_mutex 锁阿?
   (例如 vfs_write,如果最终调用 generic_file_aio_write_nolock 的话?)
   内核怎么能保证 truncate 的时候,没有 write 操作在执行呢?

困惑中...请高手指教...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

时光礼记 2022-10-22 06:20:44

忘了说了。。内核版本是 2.6.28

搜索了一下 mm、fs 和 fs/* 下所有调用 generic_file_aio_write_nolock 的地方

---- generic_file_aio_write_nolock Matches (3 in 2 files) ----
block_dev.c (fs):          .aio_write        = generic_file_aio_write_nolock,     //块设备,不需要管
file.c (fs\ntfs): * ntfs_file_aio_write_nolock() instead of __generic_file_aio_write_nolock(). //注释
file.c (fs\ocfs2):                written = generic_file_aio_write_nolock(iocb, iov, nr_segs,    // ocfs2_file_aio_write,加了 inode->i_mutex 锁
---- generic_file_aio_write_nolock Matches (5 in 1 files) ----
filemap.c (mm):__generic_file_aio_write_nolock(struct kiocb *iocb, const struct iovec *iov, // 另外一个函数
filemap.c (mm):ssize_t generic_file_aio_write_nolock(struct kiocb *iocb, // 定义
filemap.c (mm):        ret = __generic_file_aio_write_nolock(iocb, iov, nr_segs, // generic_file_aio_write_nolock 中调用另外一个函数
filemap.c (mm):EXPORT_SYMBOL(generic_file_aio_write_nolock); // 定义
filemap.c (mm):        ret = __generic_file_aio_write_nolock(iocb, iov, nr_segs, // generic_file_aio_write,加了 inode->i_mutex 锁

这样看来所有调用 generic_file_aio_write_nolock 函数的地方似乎都加了 inode->i_mutex 锁

??

薄荷梦 2022-10-22 06:20:44

v2.4.0

        down(&inode->i_sem);

肯定有互斥操作

如梦 2022-10-22 06:20:44

文件操作都要mutex_lock(inode->i_mutex)

我一向站在原地 2022-10-22 06:20:44

不是太明白,这样的话不是并发性会下降了么?

如果有一个读写锁,读/写加读锁,truncate 加写锁。
即 truncate 的时候不允许发生读/写,而读/写操作之间可以并发。
相对于 truncate,读/写是很频繁的。
这样可以增加读/写的效率。
不然对于同一个文件的每个读/写都必须顺序执行,严重影响效率吧?

爱给你人给你 2022-10-22 06:20:44

呵呵,抱歉,是我搞错了,对文件的读操作应该没有加锁,而对文件的修改操作是要加锁的,更改包括写,截短,修改其用户和组等操作
我的问题是如果读不加锁,是否可能导致数据完整性的问题? 还是把这个保证完整行交给用户来处理,比如读写双方约定操作前用flock来同步?

云雾 2022-10-22 06:20:44

内核是锁定页之后,看如果已经超越文件长度(如果被截断?),就给用户返回读到空数据
否则,把页拷贝给用户

数据完整性应该是用户需要处理的吧。。

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文