首页 文章

释放未被更改的“写时复制”内存

提问于
浏览
5

我得到了写拷贝背后的想法 . 当我fork时,堆被标记为CoW,当任何进程尝试更改它时,会生成一个副本 . 问题是:我是否必须在孩子的过程中释放它?假设一个父有一个动态char *数组,然后它就是forks . 子进程打印一些const char,然后退出 . 子进程根本没有改变堆 . 会有内存泄漏吗?

编辑:我的子进程在堆上打印数组,但不修改它 . Valgrind说如果我不释放那个阵列就会有泄漏 . 我释放它时没有泄漏/内存错误 .

2 回答

  • 4

    CoW只是一种懒惰的优化 . 你可以自由地认为 fork() 总是制作完整的过程副本(至少在内存方面),没有任何延迟 . 但…

    如果你准备动态数据块以“传递”给fork的子进程,那么在fork之后你有两个进程有两个动态数据块:父进程和子进程(都是副本) . 当孩子退出时,它的内存副本被回收,但是父母应该在分叉后自己释放那个块 .

    更清楚,这是一个例子:

    char *buf = malloc(123456);
    // … fill buf for child …
    
    int res = fork();
    
    if (res == -1) {
        fprintf(stderr, "fork failed\n");
        exit(EXIT_FAILURE);
    }
    
    if (res == 0) {
        // this is child process
        // … do work with buf …
        _Exit(EXIT_SUCCESS); // child reclaims buf by means of exit
    }
    
    // this is parent process
    free(buf); // we don't need it in parent
    
    // … other parent tasks here …
    

    CoW在fork-exec技术中也是非常有用的优化,其中child除了 exec 之外什么都不做 . exec 用指定的可执行映像替换当前进程,保留开放描述符和其他内容(更多内容在 man 2 execve 中) . 在这样的fork之后复制的唯一页面只是当前的堆栈帧,使得fork-exec非常有效 .

    有些系统还提供 vfork ,这是非常严格的不公平版本的fork,但是在没有CoW的系统上,这是有效运行vfork-exec的唯一方法 .

  • 5

    First the logical (process centered) view:

    分叉进程时,整个地址空间将按原样复制到新进程中 . 您的堆在两个进程中基本上都是重复的,并且两个进程都可以继续使用它,就像从未调用 fork() 时的一个进程一样 . 两个进程都可以释放在 fork() 之前完成的分配,如果他们想重用与分配相关的地址范围,则必须这样做 . CoW映射只是一种不会改变这些语义的优化 .


    Now the physical (system centered) view:

    您的系统内核不知道您使用 malloc() 分配的数据范围,它只知道它在 malloc() 请求时分配给进程的内存页面 . 当您调用 fork() 时,它会将所有这些页面标记为CoW,并从两个进程中引用它们 . 如果两个进程中的任何一个写入任何CoW页面而另一个进程仍然存在,则它将陷入复制整个页面的系统 . 如果其中一个进程退出,它将至少降低这些页面的引用计数,这样就不必再复制它们了 .

    那么,在退出之前在孩子中调用 free() 会发生什么?
    好吧, free() 函数很可能会写入包含内存分配的页面,告诉 malloc() 该块可以再次使用 . 这将陷入系统并复制页面,期望此操作需要一微秒或两秒 . 如果您的父进程在子进程尚未运行时调用 free() ,则会发生同样的情况 . 但是,如果您的孩子没有释放页面并退出,内核将知道它不再需要执行CoW . 如果父进程释放并重新使用内存区域,则不需要进行复制 .


    我假设,您孩子所做的只是检查一些错误情况,如果符合则立即退出 . 在这种情况下,最谨慎的方法是忘记在孩子中调用 free() ,让系统完成它的工作 .

相关问题