mremap 不会扩展超过一页的大小

问题描述

我正在创建一个模板类,其中包含一个动态数组,其行为类似于 std::vector,但底层数组存储在共享内存中,以便可以在进程之间共享。目前,没有内置同步,只是努力让内存映射调整大小按预期工作,代码是一个最小的例子来显示错误来自哪里

我遇到的问题是,一旦我调整内存大小超过一个页面大小,如果我尝试访问第二个页面,我会收到 SIGBUS 错误。如果我使用 mmap 分配更大的页面大小,例如我已经尝试了 1 MB,它会很好地分配该数量,但是如果我尝试将该映射调整为更大的大小,我将再次获得 SIGBUS如果我尝试访问超过映射的原始边界,则会出错

template<typename T>
class DistributedVector {
private:
    T* create_shared_memory(size_t size) {
        int protection = PROT_READ | PROT_WRITE;
        int visibility = MAP_SHARED;

        m_shm_fd = memfd_create("shm",0);
        ftruncate(m_shm_fd,size);
        void* result = mmap(nullptr,size * sizeof(T),protection,visibility,m_shm_fd,0);
        if (result == MAP_FAILED){
            std::cerr << "Mapping failed!\n";
        }
        file_size = lseek(m_shm_fd,SEEK_END);
        return (T*)result;
    }

    void resize_shared_memory(T* addr,size_t size,size_t new_size){
        auto temp = create_shared_memory(new_size);
        if (temp == MAP_FAILED) {
            std::cerr << "Mapping failed!\n";
        }
        memcpy(temp,m_begin,size*sizeof(T));
        munmap(m_begin,size);
        m_begin = temp;
        m_end = m_begin + size;
        m_end_cap = m_begin + new_size;
    }

public:
    DistributedVector() {
        m_begin = create_shared_memory(INITIAL_VEC_CAPACITY);
        m_end_cap = m_begin + INITIAL_VEC_CAPACITY;
        m_end = m_begin;
    }

    size_t size() {
        return m_end - m_begin;
    }

    size_t capacity() {
        return m_end_cap - m_begin;
    }

    T* data(){
        return m_begin;
    }

    T at(size_t index){
        if (index < (m_end - m_begin)) {
            return *(m_begin+index);
        } else {
            return -1;
        }
    }

    T push_back(T ele){
        if (m_end == m_end_cap){
            resize_shared_memory(m_begin,capacity(),2*capacity());
        }
        *m_end = ele;
        m_end++;
        return *(m_end-1);
    }

private:
    T* m_begin;
    T* m_end;
    T* m_end_cap;
    int m_shm_fd;
    size_t file_size;

};

运行此测试:

std::vector<int> v;
dtl::DistributedVector<int> d;

assert(v.size() == d.size());

for(int i = 0; i < 20000000; ++i){
    v.push_back(i); 
    d.push_back(i);
    auto v_i = v.at(i);
    auto d_i = d.at(i);
    assert(v_i == d_i);
    if (i % 100 == 0)
        std::cout << v_i << ":" << d_i << '\n';
}
std::cout << '\n';

assert(v.size() == d.size());

在文档中没有看到关于 mremap 页大小限制的任何内容,如果我手动分配了一个带有 mmap 的新内存部分,复制数据,然后 munmap 旧的页面,它工作正常。 mremap 中是否有我忽略的东西?

edit:更新代码以使用 memfd_createftruncate,并通过 mmap 新内存区域和 munmap 旧区域调整大小,现在代码工作打算

解决方法

虽然我没有直接回答为什么您的代码无法重新映射内存(可能是 MAP_ANONYMOUS + MAP_SHARED + remap 不太有效),但您有一个更大的代码中的问题,当您解决此问题时,您的 mremap 也将得到解决。

最大的问题是 mremap 将执行内存区域的按位复制,留下旧数据。对于模板化向量,这是不可接受的。除非你的 T 既可以简单地复制又可以简单地破坏,否则你必须做法线向量所做的事情(从不使用 realloc)。

您需要分配一个全新的适当大小的共享内存段,将前一个段中的元素复制(或移动,如果没有抛出可移动)到另一个段,而不是销毁旧段中的元素。

说了这么多,为什么你甚至要自己做向量类也不是很明显。为什么不使用带有共享内存分配器的标准向量?

,

mremap 无法调整共享和匿名映射的大小。

每个虚拟地址区域都有一个与之关联的可选文件对象,用于获取新页面和写回旧的修改过的页面。由于匿名映射没有这样的文件,因此在缩小它时无处存储页面,在扩展时无处获取更多页面。当线程通过映射访问超出范围的文件时,内核会生成 SIGBUS - 当文件在映射后收缩时发生。

理论上内核可以做一些事情来使它工作,但到目前为止它没有,它仍然会导致这种映射的行为方式以及跟踪该映射使用部分的复杂性的问题。

您需要的是提供一个文件来支持您的映射。要调整它的大小,您必须首先调整文件的大小,然后重新映射映射。如果您不想在磁盘上保留文件,请使用 memfd_create 在内存中创建匿名文件 - 这样您也可以获得一个 fd,它可以通过 Unix 套接字发送到另一个进程。>

,

我认为不可能像您期望的那样完成这项工作。 假设您有两个进程共享初始内存映射。 当决定重新映射以将容量加倍时,如果 mremap() 必须移动 (MREMAP_MAYMOVE) 地址,因为之后没有足够的连续虚拟地址空间可用,那么决定此调整大小的进程操作为这个更大的映射获取新的虚拟地址。 但是将更大的映射移动到新地址的同样问题也可能出现在另一个进程中;没有任何保证在其一侧有足够的连续虚拟地址空间。 另一个进程怎么会知道它的旧映射失效而应该使用新映射?

我不知道为什么这不会立即失败(使用 MAP_FAILED)但是如果您希望共享容器在许多进程中增长,您当然需要一个应用协议(具有适当的同步)来通知每个进程必须考虑一些新映射的过程。 请注意,问题与 mmap()/copy/munmap() 相同。

顺便说一句,return *m_end; 末尾的 push_back() 是错误的,因为它是 past-the-end(甚至超过了内存区域的末尾,当我们就在调整大小之前)。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...