gpt4 book ai didi

c++ - munmap() 当进程共享文件描述符表而不是虚拟内存时

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:34:31 25 4
gpt4 key购买 nike

我有通过 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);

我的问题是——如果在 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_count65530 .

  • 如果两个进程都发出 munmap() , 一切顺利,似乎没有内存泄漏(尽管看到内存被释放有明显的延迟;而且程序很慢,因为 mmap()/munmap() 做繁重的事情。运行时间大约 12 秒。
  • 如果只有 child 发出 munmap() ,程序在命中 65530 后进行核心转储mmaps,这意味着它没有被取消映射。程序运行越来越慢(初始 1000 mmaps 不到 1ms;最后 1000 mmaps 需要 34 秒)
  • 如果只有 parent 发出munmap() ,程序正常执行,运行时间也约为 12 秒。 child 退出后自动取消映射内存。

我使用的代码:

#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()递减这个计数器。一旦计数器达到零,内存最终将被内核回收。

程序运行时间几乎与分配大小无关。指定 4B 的 ALLOC_SIZE 只需要不到 12 秒,而分配 1MB 只需要 13 秒多一点。

指定变量分配大小 1ul<<30 - 4096 * i1ul<<30 + 4096 * i结果分别为 12.9/13.0 秒的执行时间(在误差范围内)。

一些结论是:

  • mmap()独立于分配区域花费(大约?)相同的时间
  • mmap()需要更长的时间,具体取决于已经存在的映射的数量。前 1000 个 mmap 大约需要 0.05秒;在使用 64000 mmaps 之后 1000 mmaps 34秒。
  • munmap()需要在映射相同区域的所有进程上发出,以便内核回收它。

最佳答案

使用下面的程序,我可以根据经验得出一些结论(尽管我不能保证它们是正确的):

  • 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;
}

关于c++ - munmap() 当进程共享文件描述符表而不是虚拟内存时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51744350/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com