如何在ofstream上进行fsync?

问题描述 投票:0回答:5

我想确保 ofstream 已写入磁盘设备。执行此操作的可移植方式(在 POSIX 系统上可移植)是什么?

如果我在

只读
追加模式下单独 open 文件以获取文件描述符并用它调用
fsync
,这是否可以解决问题?像这样:

ofstream out(filename);
/* ...
   write content into out
   ...
*/
out.close();

int fd = open(filename, O_APPEND);
fsync(fd);
close(fd);
c++ ofstream fsync
5个回答
13
投票

如果您能够使用 Boost,请尝试基于 file_descriptor_sink 的流,例如:

#include <boost/filesystem.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <unistd.h>

boost::filesystem::path filePath("some-file");
boost::iostreams::stream<boost::iostreams::file_descriptor_sink> file(filePath);

//  Write some stuff to file.

//  Ensure the buffer's written to the OS ...
file.flush();

//  Tell the OS to sync it with the real device.
//
::fdatasync(file->handle());

7
投票

不幸的是,纵观标准,

basic_filebuf
或任何
basic_[io]?fstream
类模板都没有提供任何内容来允许您提取底层操作系统文件描述符(就像
fileno()
对C stdio I/O所做的那样) .

也没有

open()
方法或构造函数将此类文件描述符作为参数(这将允许您使用不同的机制打开文件并记录文件句柄)。

basic_ostream::flush()
,但是我怀疑这实际上并没有调用
fsync()
——我希望像stdio中的
fflush()
一样,它只确保刷新用户空间运行时库缓冲区,这意味着操作系统可能仍在缓冲数据。

简而言之,似乎没有办法可移植地做到这一点。 :(

该怎么办? 我的建议是子类化

basic_filebuf<C, T>
:

template <typename charT, typename traits = std::char_traits<charT> >
class my_basic_filebuf : public basic_filebuf<charT, traits> {
    ....

public:
    int fileno() { ... }
    ....
};

typedef my_basic_filebuf<char> my_filebuf;

要使用它,您可以使用默认构造函数构造一个

ofstream
,然后使用
rdbuf()
分配新缓冲区:

my_filebuf buf;
buf.open("somefile.txt");

ofstream ofs;
ofs.rdbuf(&buf);

ofs << "Writing to somefile.txt..." << endl;
int fd = static_cast<my_filebuf*>(ofs.rdbuf())->fileno();

当然你也可以从

basic_ostream
派生一个新类,使打开文件和检索文件描述符的过程更加方便。


6
投票

std::filebuf
内部可能有一个文件描述符,但要获取它需要可怕的特定于实现的 hack。

这是 libstdc++ 的一个如此可怕的 hack。

#include <fstream>
#include <unistd.h>

int GetFileDescriptor(std::filebuf& filebuf)
{
  class my_filebuf : public std::filebuf
  {
  public:
    int handle() { return _M_file.fd(); }
  };

  return static_cast<my_filebuf&>(filebuf).handle();
}

int main()
{
  std::ofstream out("test");

  out << "Hello, world!";
  out.flush();

  fsync(GetFileDescriptor(*out.rdbuf()));
}

2
投票

您根本无法在打开供读取的文件描述符上执行

fsync()
操作。在 Linux 中,如果描述符不处于写入模式,
fsync()
记录为生成 EBADF。


0
投票
对我来说,打电话给

sync()

比打电话给
fsync(...)
更好,除非有人能告诉我为什么我应该害怕。对于性能要求不高的用例来说,额外的成本并不高。并且 
fsync(...)
 还要求您 
fsync(...)
 父目录(及其父目录,如果您在创建文件之前调用了 
std::filesystem::create_directories()
),以便安全、完整地将文件提交到存储。与其让自己负担代码来执行 
std::filesystem::directories
 的同步版本,并捕获打开文件的路径以便您可以 
fsync(...)
 父目录,不如使用 
sync()
 来代替,这似乎要轻松得多。

我的实现:

ofstream_synced.hpp


#pragma once #include <fstream> namespace pipedal { void filesystem_sync(); // for when you're doing a batch of files. class ofstream_synced : public std::ofstream { public: ofstream_synced() {} explicit ofstream_synced( const std::string &filename, ios_base::openmode mode = ios_base::out ) : std::ofstream(filename, mode) { } explicit ofstream_synced( const char *filename, ios_base::openmode mode = ios_base::out ) : std::ofstream(filename, mode) { } void close(); ~ofstream_synced(); }; }

ofstream_synced.cpp


#include "ofstream_synced.hpp" #include "unistd.h" using namespace pipedal; void pipedal::filesystem_sync() { ::sync(); } void ofstream_synced:close() { if (is_open()) { super::close(); ::sync(); } } ofstream_synced::~ofstream_synced() { ofstream_synced::close(); }
.cpp 文件的唯一目的是将 

unistd.h

 声明保留在全局命名空间之外。如果您不关心的话,您可以内联 .cpp 函数。

© www.soinside.com 2019 - 2024. All rights reserved.