使用内存映射文件进行持久化 - 是否需要 volatile?
Using a memory mapped file for persistence - is volatile required?
我需要 uint64_t
标记在重新启动后持久化。
为了实现这一点,我使用 boost::interprocess::mapped_region
来内存映射我在同一进程中创建的文件:
bip::file_mapping file(filename.c_str(), bip::read_write);
auto region = std::make_unique<bip::mapped_region>(file, bip::read_write);
然后我将地址转换为我的 uint64_t
类型
using Tag = uint64_t;
Tag& curr_ = *reinterpret_cast<Tag*>(region->get_address());
现在我可以 post-increment 标签,获取 "next tag",并且结果在重启后保持不变
Tag next = curr_++;
请注意,此文件写入,从读取仅。它的目的纯粹是为了提供持久性。
问题:
我的 Tag& curr_
是非易失性的,并且对内存映射区域执行 I/O 是未定义的行为吗?
正确地说,我的代码是否需要 volatile
关键字?
下面的完整工作示例:
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/file_mapping.hpp>
#include <sys/stat.h>
#include <fstream>
#include <cstdint>
#include <memory>
#include <iostream>
namespace bip = boost::interprocess;
using Tag = uint64_t;
Tag& map_tag(const std::string& filename,
std::unique_ptr<bip::mapped_region>& region)
{
struct stat buffer;
if (stat(filename.c_str(), &buffer) != 0)
{
std::filebuf fbuf;
fbuf.open(filename.c_str(), std::ios_base::in |
std::ios_base::out |
std::ios_base::trunc |
std::ios_base::binary);
Tag tag = 1;
fbuf.sputn((char*)&tag, sizeof(Tag));
}
bip::file_mapping file(filename.c_str(), bip::read_write);
// map the whole file with read-write permissions in this process
region = std::make_unique<bip::mapped_region>(file, bip::read_write);
return *reinterpret_cast<Tag*>(region->get_address());
}
class TagBroker
{
public:
TagBroker(const std::string& filename)
: curr_(map_tag(filename, region_))
{}
Tag next()
{
return curr_++;
}
private:
std::unique_ptr<bip::mapped_region> region_;
Tag& curr_;
};
int main()
{
TagBroker broker("/tmp/tags.bin");
Tag tag = broker.next();
std::cout << tag << '\n';
return 0;
}
输出:
在运行过程中,持久性得以保留。
$ ./a.out
1
$ ./a.out
2
$ ./a.out
3
$ ./a.out
4
我不知道这是否正确,因为我的进程是唯一一个读取 from/writing 到 Tag& curr_
的进程,或者它只是偶然工作,而且实际上是未定义的行为。
在这种情况下,没有。
在幕后,Boost 的 interprocess/mapped_region.hpp 正在使用 mmap
,它将 return 指向您的内存映射区域。
如果您怀疑另一个进程(或硬件)可能正在写入您的文件,则只需使用 volatile
。
(这将是您应该提供的最基本的同步,因为 volatile
强制在每次访问时从内存中读取。如果您可以控制进程,您可以尝试更高级的同步,如信号量。 )
我需要 uint64_t
标记在重新启动后持久化。
为了实现这一点,我使用 boost::interprocess::mapped_region
来内存映射我在同一进程中创建的文件:
bip::file_mapping file(filename.c_str(), bip::read_write);
auto region = std::make_unique<bip::mapped_region>(file, bip::read_write);
然后我将地址转换为我的 uint64_t
类型
using Tag = uint64_t;
Tag& curr_ = *reinterpret_cast<Tag*>(region->get_address());
现在我可以 post-increment 标签,获取 "next tag",并且结果在重启后保持不变
Tag next = curr_++;
请注意,此文件写入,从读取仅。它的目的纯粹是为了提供持久性。
问题:
我的 Tag& curr_
是非易失性的,并且对内存映射区域执行 I/O 是未定义的行为吗?
正确地说,我的代码是否需要 volatile
关键字?
下面的完整工作示例:
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/file_mapping.hpp>
#include <sys/stat.h>
#include <fstream>
#include <cstdint>
#include <memory>
#include <iostream>
namespace bip = boost::interprocess;
using Tag = uint64_t;
Tag& map_tag(const std::string& filename,
std::unique_ptr<bip::mapped_region>& region)
{
struct stat buffer;
if (stat(filename.c_str(), &buffer) != 0)
{
std::filebuf fbuf;
fbuf.open(filename.c_str(), std::ios_base::in |
std::ios_base::out |
std::ios_base::trunc |
std::ios_base::binary);
Tag tag = 1;
fbuf.sputn((char*)&tag, sizeof(Tag));
}
bip::file_mapping file(filename.c_str(), bip::read_write);
// map the whole file with read-write permissions in this process
region = std::make_unique<bip::mapped_region>(file, bip::read_write);
return *reinterpret_cast<Tag*>(region->get_address());
}
class TagBroker
{
public:
TagBroker(const std::string& filename)
: curr_(map_tag(filename, region_))
{}
Tag next()
{
return curr_++;
}
private:
std::unique_ptr<bip::mapped_region> region_;
Tag& curr_;
};
int main()
{
TagBroker broker("/tmp/tags.bin");
Tag tag = broker.next();
std::cout << tag << '\n';
return 0;
}
输出:
在运行过程中,持久性得以保留。
$ ./a.out
1
$ ./a.out
2
$ ./a.out
3
$ ./a.out
4
我不知道这是否正确,因为我的进程是唯一一个读取 from/writing 到 Tag& curr_
的进程,或者它只是偶然工作,而且实际上是未定义的行为。
在这种情况下,没有。
在幕后,Boost 的 interprocess/mapped_region.hpp 正在使用 mmap
,它将 return 指向您的内存映射区域。
如果您怀疑另一个进程(或硬件)可能正在写入您的文件,则只需使用 volatile
。
(这将是您应该提供的最基本的同步,因为 volatile
强制在每次访问时从内存中读取。如果您可以控制进程,您可以尝试更高级的同步,如信号量。 )