求教:内核中 truncate 和 write 可以并发执行么?结果是怎样的?
假设文件 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
忘了说了。。内核版本是 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 锁
??
v2.4.0
down(&inode->i_sem);
肯定有互斥操作
文件操作都要mutex_lock(inode->i_mutex)
不是太明白,这样的话不是并发性会下降了么?
如果有一个读写锁,读/写加读锁,truncate 加写锁。
即 truncate 的时候不允许发生读/写,而读/写操作之间可以并发。
相对于 truncate,读/写是很频繁的。
这样可以增加读/写的效率。
不然对于同一个文件的每个读/写都必须顺序执行,严重影响效率吧?
呵呵,抱歉,是我搞错了,对文件的读操作应该没有加锁,而对文件的修改操作是要加锁的,更改包括写,截短,修改其用户和组等操作
我的问题是如果读不加锁,是否可能导致数据完整性的问题? 还是把这个保证完整行交给用户来处理,比如读写双方约定操作前用flock来同步?
内核是锁定页之后,看如果已经超越文件长度(如果被截断?),就给用户返回读到空数据
否则,把页拷贝给用户
数据完整性应该是用户需要处理的吧。。