TL; DR:如果Linux内核丢失了缓冲的I / O写入,应用程序有什么办法可以找到它吗?
我知道您必须fsync()
文件(及其父目录)的持久性。问题是如果内核由于I / O错误而丢失了待处理写入的脏缓冲区,应用程序如何检测到这种情况并恢复或中止?
考虑数据库应用程序等,其中写入顺序和写入持久性至关重要。
Linux内核的阻止层在某些情况下丢失已由write()
,pwrite()
等成功提交的缓冲I / O请求错误如:
Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0
(见end_buffer_write_sync(...)
and end_buffer_async_write(...)
in fs/buffer.c
)。
On newer kernels the error will instead contain "lost async page write",如:
Buffer I/O error on dev dm-0, logical block 12345, lost async page write
由于应用程序write()
已经无错误地返回,因此似乎无法将错误报告给应用程序。
我不熟悉内核源代码,但我认为它在缓冲区上设置AS_EIO
,如果它没有被写出来的话做异步写:
set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);
但我不清楚应用程序在以后fsync()
文件确认它在磁盘上时是否或如何能够找到相关信息。
wait_on_page_writeback_range(...)
in mm/filemap.c
看起来do_sync_mapping_range(...)
in fs/sync.c
可能由sys_sync_file_range(...)
调用{{3}}。如果无法写入一个或多个缓冲区,则返回-EIO
。
如果正如我猜测的那样,这会传播到fsync()
的结果,那么如果应用程序因fsync()
出现I / O错误而发生恐慌并挽救并知道如何在重新启动时重新开始工作,那应该是足够的保障吗?
应用程序可能无法知道文件中哪些字节偏移对应于丢失的页面,因此如果它知道如何重写它们,但是如果应用程序重复所有自文件的上一次成功fsync()
以来它的待处理工作,并且重写与文件丢失写入相对应的任何脏内核缓冲区,应该清除丢失页面上的任何I / O错误标志并允许下一个{{ 1}}完成 - 对吧?
那么在fsync()
可能返回fsync()
的情况下还有其他任何其他无害的情况吗?拯救和重做工作会过于激烈吗?
当然这种错误不应该发生。在这种情况下,错误源于-EIO
驱动程序的默认值与SAN使用的感知代码之间的不幸交互,以报告分配精简配置存储的失败。但这并不是他们可能发生的唯一情况 - 我也看到过来自精简配置LVM的报告,例如libvirt,Docker等所使用的。像数据库这样的关键应用程序应该尝试应对这些错误,而不是盲目地继续进行,好像一切都很好。
如果内核认为可以丢失写入而不会因内核恐慌而死亡,应用程序必须找到应对的方法。
实际影响是我发现一个案例,即SAN的多路径问题导致丢失的写入导致数据库损坏,因为DBMS不知道其写入失败。不好玩。
答案 0 :(得分:83)
fsync()
返回-EIO
(注意:早期部分引用较旧的内核;下面更新以反映现代内核)
set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);
然后由wait_on_page_writeback_range(...)
调用的do_sync_mapping_range(...)
检测到sys_sync_file_range(...)
,由sys_sync_file_range2(...)
调用fsync()
调用,以实现C库调用168 * SYNC_FILE_RANGE_WAIT_BEFORE and SYNC_FILE_RANGE_WAIT_AFTER will detect any
169 * I/O errors or ENOSPC conditions and will return those to the caller, after
170 * clearing the EIO and ENOSPC flags in the address_space.
。< / p>
This comment on sys_sync_file_range
fsync()
建议当-EIO
返回-ENOSPC
或(在联机帮助页中未记录)fsync()
时,它将清除错误状态,以便随后{{1}即使页面从未写过,也会报告成功。
果然wait_on_page_writeback_range(...)
clears the error bits when it tests them:
301 /* Check for outstanding write errors */
302 if (test_and_clear_bit(AS_ENOSPC, &mapping->flags))
303 ret = -ENOSPC;
304 if (test_and_clear_bit(AS_EIO, &mapping->flags))
305 ret = -EIO;
因此,如果应用程序期望它可以重新尝试fsync()
,直到它成功并且相信数据是磁盘上的,那就非常错误了。
我很确定这是我在DBMS中发现的数据损坏的根源。它会重试fsync()
并认为一旦成功就会很好。
POSIX/SuS docs on fsync()
并未真正指明这一点:
如果fsync()函数失败,则无法保证完成未完成的I / O操作。
Linux's man-page for fsync()
只是没有说明失败后会发生什么。
所以似乎fsync()
错误的含义是&#34;不知道你的写作发生了什么,可能已经或不起作用,最好再试一次确保&#34;。
在页面上的4.9 end_buffer_async_write
集-EIO
,只需mapping_set_error
。
buffer_io_error(bh, ", lost async page write");
mapping_set_error(page->mapping, -EIO);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);
在同步方面,我认为它类似,但现在结构非常复杂。 filemap_check_errors
中的mm/filemap.c
现在可以:
if (test_bit(AS_EIO, &mapping->flags) &&
test_and_clear_bit(AS_EIO, &mapping->flags))
ret = -EIO;
具有相同的效果。错误检查似乎全部通过filemap_check_errors
进行测试和清除:
if (test_bit(AS_EIO, &mapping->flags) &&
test_and_clear_bit(AS_EIO, &mapping->flags))
ret = -EIO;
return ret;
我在笔记本电脑上使用btrfs
,但是当我在ext4
上创建一个/mnt/tmp
环回测试并在其上设置一个perf探测器时:
sudo dd if=/dev/zero of=/tmp/ext bs=1M count=100
sudo mke2fs -j -T ext4 /tmp/ext
sudo mount -o loop /tmp/ext /mnt/tmp
sudo perf probe filemap_check_errors
sudo perf record -g -e probe:end_buffer_async_write -e probe:filemap_check_errors dd if=/dev/zero of=/mnt/tmp/test bs=4k count=1 conv=fsync
我在perf report -T
中找到以下调用堆栈:
---__GI___libc_fsync
entry_SYSCALL_64_fastpath
sys_fsync
do_fsync
vfs_fsync_range
ext4_sync_file
filemap_write_and_wait_range
filemap_check_errors
直读表明,现代内核的行为相同。
这似乎意味着如果fsync()
(或大概是write()
或close()
)返回-EIO
,则文件在您上次成功{{}之间处于某种未定义状态1}} d或fsync()
d及其最近的close()
十州。
I've implemented a test case to demonstrate this behaviour
DBMS可以通过进入崩溃恢复来解决这个问题。一个普通的用户应用程序应该如何处理这个? write()
手册页没有发出任何警告,表示&#34; fsync-if-you-like-like-it&#34;我希望很多的应用程序不会很好地应对这种行为。
lwn.net touched on this in the article "Improved block-layer error handling"
答案 1 :(得分:22)
由于应用程序的write()已经没有错误地返回,因此似乎无法将错误报告给应用程序。
我不同意。如果写入只是排队,write
可以无错误地返回,但错误将在下一个需要在磁盘上实际写入的操作上报告,这意味着在下一个fsync
,可能在下面的写入如果系统决定刷新缓存,至少在最后一个文件关闭时。
这就是为什么应用程序必须测试close的返回值以检测可能的写错误。
如果您确实需要能够进行巧妙的错误处理,那么您必须假设自上次成功fsync
以来所写的所有内容都可能失败,并且至少在某些情况下失败了。
答案 2 :(得分:1)
打开文件时使用O_SYNC标志。它确保将数据写入磁盘。
如果这不能让你满意,那就没有了。
答案 3 :(得分:1)
write
(2)提供的比你想象的少。手册页对于成功write()
调用的语义非常开放:
从
write()
成功返回并不能保证 数据已提交到磁盘。事实上,在一些错误的实施, 它甚至不能保证已成功保留空间 对于数据。唯一可以确定的方法是在你之后拨打fsync
(2) 完成了所有数据的写作。
我们可以得出结论,成功的write()
仅仅意味着数据已经到达内核的缓冲设施。如果持久化缓冲区失败,则后续访问文件描述符将返回错误代码。作为最后的手段,可能是close()
。 close
(2)系统调用的手册页包含以下句子:
以前的
write
(2)操作中的错误很可能是 首先在最后close
()报告。
如果您的应用需要保留数据写入,则必须定期使用fsync
/ fsyncdata
:
fsync()
转移(&#34;刷新&#34;)所有已修改的核心内数据(即,已修改) 缓冲区缓存页面)文件描述符fd引用的文件 到磁盘设备(或其他永久存储设备)所以 即使在之后,所有已更改的信息都可以被检索到 系统崩溃或重新启动。这包括通过或写作 刷新磁盘缓存(如果存在)。呼叫阻塞直到 设备报告传输已完成。
答案 4 :(得分:-5)
检查关闭的返回值。关闭可能会失败,而缓冲写入似乎成功。