问题描述
目标
为旧版 C++11 程序无缓冲 I/O 和禁用内核页面缓存。此功能必须按需(通过可执行参数)。这个想法是减少 I/O 操作的内存开销,而不管性能如何。我不确定这是实现这一目标的正确方法......
我的尝试
代码库相当大,std::ifstream
和 std::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,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
返回的内部缓冲区对象,而不是来自 std::basic_ios<CharT,Traits>::rdbuf
的对象,但我仍然不知道如何获得我想要的行为。
我想不惜一切代价避免用它的自定义实现替换所有 std::ifstream
引用,因为这意味着替换所有当前声明中的类型。
注意:我正在使用 gcc 和 libstdc++ 进行编译。
解决方法
const enableBanner = () => setShow(true);
总是 将与它自己的 std::ifstream
一起使用。该 std::filebuf
与 std::filebuf
拥有的 std::basic_ios
分开。设置 is 无效,因为它从未使用过,而 std::ifstream
总是使用他自己的。另见Difference between "internal" vs "associated" stream buffer。
你可以做的是,你可以用你自己的实现覆盖 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
您必须将 fopen
替换为 open+fdopen
并同时替换 __basic_file::close()
,因此它也会执行 close(fileno(..))
。