我有一个未命名的进程间共享内存区域,该区域是通过mmap
创建的。通过clone
系统调用创建进程。进程共享文件描述符表(CLONE_FILES
)和文件系统信息(CLONE_FS
)。进程不共享内存空间(先前映射到clone
调用的区域除外)
mmap(NULL, sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
syscall(SYS_clone, CLONE_FS | CLONE_FILES | SIGCHLD, nullptr);
我的问题是-如果在派生之后一个(或两个)进程调用munmap()
,会发生什么?
我的理解是munmap()
将做两件事:
我假设MAP_ANONYMOUS
创建了某种由内核处理的虚拟文件(可能位于/proc
?),该文件在munmap()
上自动关闭。 / p>
因此...其他进程会将未打开但可能不再存在的文件映射到内存中吗?
这让我非常困惑,因为我找不到任何合理的解释。
在此测试中,这两个进程都可以发出一个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
mmap之后转储,这表示它没有被映射。程序运行的越来越慢(最初的1000 mmap耗时不到1ms;最后的1000 mmap耗时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个mmap大约需要0.05
秒; 64000 mmap之后需要1000 mmap,需要34
秒。munmap()
必须在映射相同区域的 ALL 进程中发出,以供内核回收。答案 0 :(得分:1)
使用下面的程序,我可以凭经验得出一些结论(即使我不能保证它们是正确的):
mmap()
大约花费相同的时间,而与分配区域无关(这是由于linux内核进行有效的内存管理。映射的内存除非写入,否则不会占用空间)。mmap()
花费的时间更长,具体取决于已存在的映射的数量。前1000个mmap大约需要0.05秒;在进行64000次映射后,1000 mmap大约需要34秒。我没有检查Linux内核,但是在某些结构中可能在索引中插入映射区域需要O(n)
而不是可行的O(1)
。可能有内核补丁;但这对除我以外的任何人来说不是问题:-)munmap()
必须在映射同一MAP_ANONYMOUS
区域的 ALL 进程中发出,以使其被内核回收。这样可以正确释放共享内存区域。#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;
}