检查文件内容是否已实际写入磁盘 - 未在磁盘控制器的缓冲区中排队
Check if file content has been actually written to disk - not being queued in disk controller's buffer
我编写了一个程序,可以将两个小文件压缩成一个更大的文件。我首先从输入文件中读取数据,合并数据,然后将输出写入临时文件。完成后,我将临时文件重命名为所需的文件名(位于磁盘上的同一分区中)。这是伪代码:
FILE* fp_1 = fopen("file_1.dat", "r+b");
FILE* fp_2 = fopen("file_2.dat", "r+b");
FILE* fp_out = fopen("file_tmp.dat", "w+b");
// 1. Read data for the key in two files
const char* data_1 = ...;
const char* data_2 = ...;
// 2. Merge data, store in an allocated buffer
// 3. Write merged buffer to temp file
fwrite(temp_buff, estimated_size, 1, fp_out);
fflush(fp_out);
fclose(fp_1);
fclose(fp_2);
fclose(fp_out);
// Now rename temp file to desired file name
if(std::rename("file_tmp.dat", "file_out.dat") == 0)
{
std::remove("file_1.dat");
std::remove("file_2.dat");
}
我用两个每个 5 MB 的输入文件反复测试了程序。有一次我拔掉电源线突然关闭了系统。重启系统后我检查了数据,发现输入文件被删除,file_out.dat
被全零填充。这让我相信系统在删除 2 个输入文件后立即崩溃,并且输出数据仍在磁盘控制器缓冲区中的某个位置。如果这是真的,那么有什么方法可以检查数据是否已实际写入磁盘?
一般情况下不会。即使您告诉 OS 等到数据写入(使用 sync
API 系列),一些磁盘也会对 OS 撒谎,声称写入已完成实际上只是在硬盘驱动器的板载 RAM 缓存中排队,在突然断电时会丢失。
你能做的最好的事情就是在你执行完 fflush
之后明确地要求 OS 告诉磁盘“真的,真的同步所有东西并阻止它直到它完成”(这只是告诉 stdio 库将所有用户模式缓冲数据发送到 OS,它通常将其保存在内核缓冲区中并稍后在后台将内核缓冲区同步到磁盘),或者使用 fsync
or using something like sync
or syncfs
限制范围(前者同步所有文件系统,后者将范围限制在单个文件描述符对应的文件系统)。
为了最大程度的安全,您需要:
- 在最后的
fflush
之后但在 rename
之前做一个有针对性的 fsync
(所以新文件在替换旧文件之前在磁盘上是完整的),并且
- 在
rename
之后但在 remove
调用之前进行更广泛的 sync
/syncfs
(因此来自 rename
的元数据更新在之前完成你删除了源文件)
如果您不介意在输入数据仍然存在的情况下损坏输出数据,则可以省略第 1 步。
当然,就像我说的,这是尽力而为;如果磁盘控制器对 OS 撒谎,您只能为磁盘编写新的固件和驱动程序,这可能太过分了。
我编写了一个程序,可以将两个小文件压缩成一个更大的文件。我首先从输入文件中读取数据,合并数据,然后将输出写入临时文件。完成后,我将临时文件重命名为所需的文件名(位于磁盘上的同一分区中)。这是伪代码:
FILE* fp_1 = fopen("file_1.dat", "r+b");
FILE* fp_2 = fopen("file_2.dat", "r+b");
FILE* fp_out = fopen("file_tmp.dat", "w+b");
// 1. Read data for the key in two files
const char* data_1 = ...;
const char* data_2 = ...;
// 2. Merge data, store in an allocated buffer
// 3. Write merged buffer to temp file
fwrite(temp_buff, estimated_size, 1, fp_out);
fflush(fp_out);
fclose(fp_1);
fclose(fp_2);
fclose(fp_out);
// Now rename temp file to desired file name
if(std::rename("file_tmp.dat", "file_out.dat") == 0)
{
std::remove("file_1.dat");
std::remove("file_2.dat");
}
我用两个每个 5 MB 的输入文件反复测试了程序。有一次我拔掉电源线突然关闭了系统。重启系统后我检查了数据,发现输入文件被删除,file_out.dat
被全零填充。这让我相信系统在删除 2 个输入文件后立即崩溃,并且输出数据仍在磁盘控制器缓冲区中的某个位置。如果这是真的,那么有什么方法可以检查数据是否已实际写入磁盘?
一般情况下不会。即使您告诉 OS 等到数据写入(使用 sync
API 系列),一些磁盘也会对 OS 撒谎,声称写入已完成实际上只是在硬盘驱动器的板载 RAM 缓存中排队,在突然断电时会丢失。
你能做的最好的事情就是在你执行完 fflush
之后明确地要求 OS 告诉磁盘“真的,真的同步所有东西并阻止它直到它完成”(这只是告诉 stdio 库将所有用户模式缓冲数据发送到 OS,它通常将其保存在内核缓冲区中并稍后在后台将内核缓冲区同步到磁盘),或者使用 fsync
or using something like sync
or syncfs
限制范围(前者同步所有文件系统,后者将范围限制在单个文件描述符对应的文件系统)。
为了最大程度的安全,您需要:
- 在最后的
fflush
之后但在rename
之前做一个有针对性的fsync
(所以新文件在替换旧文件之前在磁盘上是完整的),并且 - 在
rename
之后但在remove
调用之前进行更广泛的sync
/syncfs
(因此来自rename
的元数据更新在之前完成你删除了源文件)
如果您不介意在输入数据仍然存在的情况下损坏输出数据,则可以省略第 1 步。
当然,就像我说的,这是尽力而为;如果磁盘控制器对 OS 撒谎,您只能为磁盘编写新的固件和驱动程序,这可能太过分了。