可以在mmapped空间的中间处理插入内存页面吗?

时间:2016-07-07 17:44:09

标签: c linux unix mmap virtual-memory

蒸馏场景:

用户空间程序需要数百万页大小的结构(即大多数Linux系统为4k)。它还需要快速随机访问结构。有时程序需要在数组中间插入新结构。订单很重要。

struct { char data[PAGE_SIZE]; } page_sized_t;
size_t N = 1 * 1000 * 1000;
size_t X = INSERT_INDEX;

程序可以通过堆分配数组来实现,该数组包含指向堆分配结构的指针。可以使用realloc和memmove实现插入。

struct page_sized_t **array = malloc( sizeof(array[0]) * N );
...
array = realloc( array, sizeof(array[0]) * (N+1) );
memmove( &array[X+1], &array[X], N-X );
array[X] = malloc( sizeof(array[X][0]) );
...

现在我的问题是这个。在具有一个大的mmapped内存区域方面实现这样的程序是否可行。每个结构都放在单​​页中。然后插入可以这样实现:程序可以要求内核在其他人之间插入新页面。基本上是内核完成上一段所述的工作。

struct page_sized_t *array = mmap( 0, sizeof(array[0]) * N,
                                   PROT_READ|PROT_WRITE, MAP_ANONYMOUS, -1, 0 );
...
// imaginary syscall: m_insert_map(old_address, old_size, insert_address, insert_size)
array = m_insert_map( array, sizeof(array[0]) * N, sizeof(array[0]) * X, sizeof(array[0]) );
...

我认为使用当前的系统调用是不可能的。人们只能mremap - 所以在某种程度上只能在最后插入页面。

总结:可以在Linux内核中实现内存页面的插入吗?使用这样的界面而不是用户空间实现是否可行?是否有一个系统实现了这个?

1 个答案:

答案 0 :(得分:0)

  

程序可以通过堆分配数组来实现,该数组包含指向堆分配结构的指针。可以使用realloc和memmove实现插入。

如果你已经有一个指向结构的指针数组,你为什么要在内存中移动结构呢?只需更新指针即可。在内存中连续修改一百万个条目总是比修改一百万个页表条目更有效。

始终通过数组中的索引引用结构,而不是通过指针引用结构。这样,即使结构在内存中不连续,也可以按顺序遍历数组。

  

在具有一个大的mmapped内存区域方面实现这样的程序是否可行。

没有。按照您自己的意思,每个结构都有一个页面。要在中间插入页面,需要更新其余页面表条目。那将是缓慢的。

如果你通过间接指针定位每个结构,即你有

struct page_sized_t **array;

然后没有真正的理由移动内容;只是更新指针。是的,这意味着要将项目j移动到索引i,使用i < j,您需要

struct page_sized_t *temp = array[j];
memmove(array + i + 1, array + 1, (j - i) * sizeof array[0]);
array[i] = temp;

请注意,array[j]的类型为struct page_sized_t *,因此会移动指针,而不是内容。修改指针总是比修改多个页表条目更快。 (即使使用了大页面,根据需要将它们合并/分割成普通页面所需的逻辑几乎肯定是不切实际的。你可以设计一个微基准,它表现得比简单的memmove更好(尽管如果你这样做,我和# 39;令我感到惊讶的是,但在所有现实生活中,这样的页表恶作剧只会增加开销。

  

可以在Linux内核中实现内存页面的插入吗?使用这样的界面而不是用户空间实现是否可行?

您已经可以通过mremap()完成此操作。

使用mremap(array + index, pagesize * (size - index), pagesize * (size - index + 1), MREMAP_FIXED, array + index + 1)重新映射从插入点开始的区域。然后,使用mremap(array, pagesize * index, pagesize * (index + 1), 0)增长初始部分以覆盖洞,或mmap(array + index, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, -1, 0)插入洞。

这与你使用memmove()做同样事情非常相似,真的。

但是,您必须确保在两次调用期间没有其他线程会进行新的内存分配(通过mmap()),否则内核可能会提供&#34; hole&#34;对于其他内存分配调用,打破方案。这完全是一个用户空间问题,在单线程应用程序中实现应该是微不足道的(因为在信号处理程序中使用任何内存分配函数不是异步信号安全的),但对于多线程程序,它可能很难甚至是不可能 - 甚至一些C库函数也进行隐式/内部动态内存分配。

总结:

无论你在做什么,看起来你没有使用最有效的数据结构,因此,正在寻找错误位置的加速。特别是,需要直接/随机访问内容并不意味着您应该使用线性数组。

由于您还没有提供足够的信息来提出手动建议,我只是指出,拥有页面大小的结构本身就是一个不好的迹象。数据库使用索引(其中对应于单个键/字段的值是连续的(至少在某种意义上)是为了更快地访问(和整理)。因此,除非您对每个结构的访问确实通常需要结构中的所有数据,否则可能会把它拆分成单独的数组做得更好。