我有未命名的进程间共享内存区域,通过
mmap
. 流程是通过
clone
系统调用。进程共享文件描述符表(
CLONE_FILES
),文件系统信息(
CLONE_FS
). 过程
不要
共享内存空间(除了先前映射到
克隆
呼叫):
mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
syscall(SYS_clone, CLONE_FS | CLONE_FILES | SIGCHLD, nullptr);
我的问题是——如果在fork之后,一个(或两个)进程调用
munmap()
?
我的理解是
munmap()
会做两件事:
-
取消映射内存区域(在我的例子中,它不在进程之间传播)
-
如果是匿名映射,请关闭文件描述符(在本例中是在进程之间传播的)
我想
MAP_ANONYMOUS
创造某种
事实上的
内核处理的文件(可能位于
/proc
?) 自动关闭的
munmap()
.
因此。。。另一个进程将映射到内存中一个不打开的文件,甚至可能不再存在。
这让我很困惑,因为我找不到任何合理的解释。
简单测试
在这个测试中,两个进程可以发出一个
munmap()
没有任何问题。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sched.h>
int main() {
int *value = (int*) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
*value = 0;
if (syscall(SYS_clone, CLONE_FS | CLONE_FILES | SIGCHLD, nullptr)) {
sleep(1);
printf("[parent] the value is %d\n", *value); // reads value just fine
munmap(value, sizeof(int));
// is the memory completely free'd now? if yes, why?
} else {
*value = 1234;
printf("[child] set to %d\n", *value);
munmap(value, sizeof(int));
// printf("[child] value after unmap is %d\n", *value); // SIGSEGV
printf("[child] exiting\n");
}
}
连续分配
在这个测试中,我们按顺序映射许多匿名区域。
在我的系统中
vm.max_map_count
是
65530
.
-
如果两个进程都发出
munmap()
一切顺利,似乎没有内存泄漏(虽然有明显的延迟看到内存被释放),程序也相当慢。
mmap()
/
munmap()
做重的事情。运行时间约为12秒。
-
如果只是孩子的问题
munmap()
,程序核心在点击
65530个
mmaps,意思是它没有被取消映射。程序运行得越来越慢(最初1000 mmaps需要的时间少于1 ms;最后1000 mmaps需要34秒)
-
如果只有父问题
munmap()
,程序正常执行,运行时间也约为12秒。孩子在退出后自动解压记忆。
我使用的代码:
#include <cassert>
#include <thread>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sched.h>
#define NUM_ITERATIONS 100000
#define ALLOC_SIZE 4ul<<0
int main() {
printf("iterations = %d\n", NUM_ITERATIONS);
printf("alloc size = %lu\n", ALLOC_SIZE);
assert(ALLOC_SIZE >= sizeof(int));
assert(ALLOC_SIZE >= sizeof(bool));
bool *written = (bool*) mmap(NULL, ALLOC_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
for(int i=0; i < NUM_ITERATIONS; i++) {
if(i % (NUM_ITERATIONS / 100) == 0) {
printf("%d%%\n", i / (NUM_ITERATIONS / 100));
}
int *value = (int*) mmap(NULL, ALLOC_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
*value = 0;
*written = 0;
if (int rv = syscall(SYS_clone, CLONE_FS | CLONE_FILES | SIGCHLD, nullptr)) {
while(*written == 0) std::this_thread::yield();
assert(*value == i);
munmap(value, ALLOC_SIZE);
waitpid(-1, NULL, 0);
} else {
*value = i;
*written = 1;
munmap(value, ALLOC_SIZE);
return 0;
}
}
return 0;
}
似乎内核会保留对匿名映射的引用计数器,并且
munmap()
减少此计数器。一旦计数器达到零,内存最终将被内核回收。
程序运行时几乎与分配大小无关。指定ALLOC_SIZE为4B只需不到12秒,而分配1MB只需略超过13秒。
指定变量分配大小
1ul<<30 - 4096 * i
或
1ul<<30 + 4096 * i
结果执行时间分别为12.9/13.0秒(在误差范围内)。
一些结论是:
-
mmap()
(大约?)同时独立于分配区域
-
mmap()
根据已经存在的映射的数量需要更长的时间。前1000毫米每秒
0.05
秒;64000毫微秒后为1000毫微秒
34
几秒钟。
-
munmap()
需要在
全部
进程映射同一区域,以便内核回收它。