1

目标

为旧版C++11 程序提供无缓冲 I/O禁用内核页面缓存。此功能必须按需提供(通过可执行参数)。这个想法是为了减少 I/O 操作的内存开销,而不管性能如何。我不确定这是实现这一目标的正确方法......

我的尝试

代码库非常大,大量使用std::ifstreamstd::ofstream分布在不同的二进制文件/库中,我的目标是实现一个派生自std::filebuf依赖 CI/O 特性的类(FILE *因此open()我可以传递O_DIRECT标志等),并通过使用继承的方法将它传递给一个std::ifstream对象(目前仅输入)std::basic_streambuf<CharT,Traits>* std::basic_ios<CharT,Traits>::rdbuf(std::basic_streambuf<CharT,Traits>*)

问题

问题是该std::ifstream对象实际上似乎有两个内部缓冲区。查看代码以了解我的实验(可能仍然存在一些明显的错误)。

我的文件缓冲区

// FileBuf.h
class FileBuf : public std::filebuf {
public:
    FileBuf();
    virtual ~FileBuf();
    virtual std::filebuf* open(const char* filename,  std::ios_base::openmode mode);
    virtual std::filebuf* open(const std::string filename, std::ios_base::openmode mode);
    virtual bool is_open() const;
    virtual std::filebuf* close();
    virtual std::streambuf* setbuf(char_type* s, std::streamsize n);
    virtual int_type overflow(int c = traits_type::eof());
    virtual FileBuf::int_type underflow();
    virtual int sync();
private:
    int _fd;
    FILE * _fp;
    char _buff[1]; // minimal size
};
// FileBuf.cpp
FileBuf::FileBuf()
: std::filebuf(), _fd(0), _fp(NULL)
{}

FileBuf::~FileBuf() {
  close(); // RAII
}

std::filebuf* FileBuf::open(const char* filename,  std::ios_base::openmode mode) {
  std::cout << "open(const char*, ..): filename=" << filename << ", mode=" << mode << std::endl;
  // not finished, need to handle all modes
  int flags = O_RDONLY;
  mode_t fmode = S_IRUSR;
  std::string smode = "r";
  _fd = ::open(filename, flags, fmode);
  _fp = ::fdopen(_fd, smode.c_str());
  return _fp != NULL ? this : nullptr;
}

std::filebuf* FileBuf::open(const std::string filename, std::ios_base::openmode mode) {
  std::cout << "open(const std::string, ..): filename=" << filename << ", mode=" << mode << std::endl;
  return open(filename.c_str(), mode);
}

std::streambuf* FileBuf::setbuf(char_type* s, std::streamsize n) {
  return this;
}

bool FileBuf::is_open() const {
  return (_fp != NULL);
}

std::filebuf* FileBuf::close() {
  std::cout << "close()" << std::endl;
  if (_fp) {
    if (std::fclose(_fp) == 0) {
      return this;
    }
  }
  return nullptr;
}

FileBuf::int_type FileBuf::overflow(int_type c) {
  std::cout << "overflow()" << std::endl;
  if (traits_type::eq_int_type(c, traits_type::eof())) {
    return (sync() == 0) ? traits_type::not_eof(c) : traits_type::eof();
  } else {
    return ((std::fputc(c, _fp) != EOF) ? traits_type::not_eof(c) : traits_type::eof());
  }
}

FileBuf::int_type FileBuf::underflow()
{
  std::cout << "underflow(): _fp=" << _fp << std::endl;
  if (gptr() == NULL || gptr() >= egptr()) {
    int gotted = fgetc(_fp);
    if (gotted == EOF) {
      return traits_type::eof();
    } else {
      *_buff = gotted;
      setg(_buff, _buff, _buff + 1);
      return traits_type::to_int_type(*_buff);
    }
  } else {
    return traits_type::to_int_type(*_buff);
  }
}

int FileBuf::sync()
{
  std::cout << "sync()" << std::endl;
  return (std::fflush(_fp) == 0) ? 0 : -1;
}

客户端代码

std::string buff(1024, '\0');
std::ifstream ifs;
FileBuf fileBuf;

ifs.std::istream::rdbuf(&fileBuf); // file buf passed here

std::cout << "rdbuf()=" << static_cast<void*>(ifs.rdbuf()) << ", istream.rdbuf()=" << static_cast<void*>(ifs.std::istream::rdbuf()) << ", &fileBuf=" << static_cast<void*>(&fileBuf) << std::endl;
ifs.open("data/test1/delta");
ifs.read(&buff[0], 1024);

输出

rdbuf()=0x7fffffffdb10, istream.rdbuf()=0x7fffffffd9f0, &fileBuf=0x7fffffffd9f0
underflow(): _fp=0
// !! SEGFAULT !!

如输出所示,两种风格的rdbuf()不引用相同的内部缓冲区,并且FileBuf::open在它应该是的时候永远不会被调用,如std::basic_ifstream<CharT,Traits>::open中指定的那样:

有效调用 rdbuf()->open(filename, mode | ios_base::in)

我明白发生了什么:std::basic_ifstream::rdbuf正在调用返回的内部缓冲区对象而不是 from std::basic_ios<CharT,Traits>::rdbuf,但是,我仍然不知道如何获得我想要的行为。

我想避免 - 不惜一切代价 -std::ifstream用它的自定义实现替换所有引用,因为这意味着替换所有当前声明中的类型。

注意:我正在使用gcclibstdc++进行编译。

4

1 回答 1

1

std::ifstream总是与它自己的一起使用std::filebuf。那与拥有的那一个std::filebuf分开std::basic_ios的。设置是没有效果的,因为它从未使用过并且std::ifstream总是使用他自己的。另请参阅“内部”与“关联”流缓冲区之间的区别

相反,您可以用自己的实现覆盖 libstdc++ 中文件输入/输出操作的实现。从libstdc++/basic_file_stdio.c获取原始实现并使用自定义行为对其进行修补。动态链接器将更喜欢您的符号而不是共享的符号。例如:

#include <iostream>
#include <sstream>
#include <fstream>
#include <sys/fcntl.h>
#include <bits/basic_file.h>
#include <fcntl.h>
#include <errno.h>
#include <cstring>
#include <unistd.h>

// code copied from  libstdc++-v3/config/io/basic_file_stdio.cc
namespace {
  // Map ios_base::openmode flags to a string for use in fopen().
  // Table of valid combinations as given in [lib.filebuf.members]/2.
  static const char*
  fopen_mode(std::ios_base::openmode mode)
  {
    enum
      {
        in     = std::ios_base::in,
        out    = std::ios_base::out,
        trunc  = std::ios_base::trunc,
        app    = std::ios_base::app,
        binary = std::ios_base::binary
      };
    // _GLIBCXX_RESOLVE_LIB_DEFECTS
    // 596. 27.8.1.3 Table 112 omits "a+" and "a+b" modes.
    switch (mode & (in|out|trunc|app|binary))
      {
      case (   out                 ): return "w";
      case (   out      |app       ): return "a";
      case (             app       ): return "a";
      case (   out|trunc           ): return "w";
      case (in                     ): return "r";
      case (in|out                 ): return "r+";
      case (in|out|trunc           ): return "w+";
      case (in|out      |app       ): return "a+";
      case (in          |app       ): return "a+";
      case (   out          |binary): return "wb";
      case (   out      |app|binary): return "ab";
      case (             app|binary): return "ab";
      case (   out|trunc    |binary): return "wb";
      case (in              |binary): return "rb";
      case (in|out          |binary): return "r+b";
      case (in|out|trunc    |binary): return "w+b";
      case (in|out      |app|binary): return "a+b";
      case (in          |app|binary): return "a+b";
      default: return 0; // invalid
      }
  }
}

namespace std
{
  __basic_file<char>*
  __basic_file<char>::open(const char* __name, ios_base::openmode __mode,
                           int /*__prot*/)
  {
    __basic_file* __ret = NULL;
    const char* __c_mode = fopen_mode(__mode);
    if (__c_mode && !this->is_open())
      {
          // HERE I ADDED THIS LINE HERE I ADDED THIS LINE HERE I ADDED THIS LINE HERE I ADDED THIS LINE 
          const char *str = "TODO: set O_DIRECT here\n";
          write(STDOUT_FILENO, str, strlen(str));
#ifdef _GLIBCXX_USE_LFS
        if ((_M_cfile = fopen64(__name, __c_mode)))
#else
        if ((_M_cfile = fopen(__name, __c_mode)))
#endif
          {
            _M_cfile_created = true;
            __ret = this;
          }
      }
    return __ret;
  }
}

int main() {
    std::string buff(1024, '\0');
    std::ifstream ifs;
    ifs.open("/tmp/1.cpp");
    ifs.read(&buff[0], 1024);
}

程序在我的系统上编译输出(文件打开成功):

TODO: set O_DIRECT here

您将不得不替换fopenopen+fdopen并替换__basic_file::close(),所以它也可以close(fileno(..))

于 2021-04-28T18:18:44.550 回答