C++ 进程共享文件描述符表但不共享虚拟内存时的munmap()
我有未命名的进程间共享内存区域,这些区域是通过C++ 进程共享文件描述符表但不共享虚拟内存时的munmap(),c++,linux,memory,linux-kernel,mmap,C++,Linux,Memory,Linux Kernel,Mmap,我有未命名的进程间共享内存区域,这些区域是通过mmap创建的。进程是通过克隆系统调用创建的。进程共享文件描述符表(CLONE\u FILES),文件系统信息(CLONE\u FS)。进程不共享内存空间(预先映射到克隆调用的区域除外): 我的问题是——如果在fork之后,一个(或两个)进程调用munmap(),会发生什么 我的理解是munmap()将做两件事: 取消映射内存区域(在我的情况下,它不会在进程之间传播) 如果是匿名映射,请关闭文件描述符(在我的例子中,它是在进程之间传播的) 我假设
mmap
创建的。进程是通过克隆
系统调用创建的。进程共享文件描述符表(CLONE\u FILES
),文件系统信息(CLONE\u FS
)。进程不共享内存空间(预先映射到克隆调用的区域除外):
我的问题是——如果在fork之后,一个(或两个)进程调用munmap()
,会发生什么
我的理解是munmap()
将做两件事:
- 取消映射内存区域(在我的情况下,它不会在进程之间传播)
- 如果是匿名映射,请关闭文件描述符(在我的例子中,它是在进程之间传播的)
我假设MAP\u 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");
}
}
内核似乎会保留一个匿名映射的引用计数器,munmap()
递减该计数器。一旦计数器达到零,内核将最终回收内存
程序运行时几乎与分配大小无关。指定4B的ALLOC_大小只需不到12秒,而1MB的分配只需13秒多一点
使用下面的程序指定变量分配大小1ul,我可以根据经验得出一些结论(尽管我不能保证它们是正确的):
mmap()
占用的时间与分配区域大致相同(这是由于linux内核的高效内存管理。映射内存不会占用空间,除非写入内存)
mmap()
所需时间更长,具体取决于已存在映射的数量。前1000个MMAP大约需要0.05秒;拥有64000个映射后的1000 mmaps大约需要34秒。我还没有检查linux内核,但是在索引中插入映射区域可能需要O(n)
而不是某些结构中可行的O(1)
。内核补丁可能;但可能除了我之外,这对任何人都不是问题:-)
munmap()。这将正确释放共享内存区域
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#定义NUM_迭代100000次
#定义ALLOC_SIZE 1您试过了吗?我试过了--我将在原始问题中添加详细信息。我能够成功地发布两个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");
}
}
#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;
}
#include <cassert>
#include <cinttypes>
#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 1ul<<30
#define CLOCK_TYPE CLOCK_PROCESS_CPUTIME_ID
#define NUM_ELEMS 1024*1024/4
struct timespec start_time;
int main() {
clock_gettime(CLOCK_TYPE, &start_time);
printf("iterations = %d\n", NUM_ITERATIONS);
printf("alloc size = %lu\n", ALLOC_SIZE);
assert(ALLOC_SIZE >= NUM_ELEMS * sizeof(int));
bool *written = (bool*) mmap(NULL, sizeof(bool), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
for(int i=0; i < NUM_ITERATIONS; i++) {
if(i % (NUM_ITERATIONS / 100) == 0) {
struct timespec now;
struct timespec elapsed;
printf("[%3d%%]", i / (NUM_ITERATIONS / 100));
clock_gettime(CLOCK_TYPE, &now);
if (now.tv_nsec < start_time.tv_nsec) {
elapsed.tv_sec = now.tv_sec - start_time.tv_sec - 1;
elapsed.tv_nsec = now.tv_nsec - start_time.tv_nsec + 1000000000;
} else {
elapsed.tv_sec = now.tv_sec - start_time.tv_sec;
elapsed.tv_nsec = now.tv_nsec - start_time.tv_nsec;
}
printf("%05" PRIdMAX ".%09ld\n", elapsed.tv_sec, elapsed.tv_nsec);
}
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 {
for(int j=0; j<NUM_ELEMS; j++)
value[j] = i;
*written = 1;
//munmap(value, ALLOC_SIZE);
return 0;
}
}
return 0;
}