【问题标题】:Specifically, how does fork() handle dynamically allocated memory from malloc() in Linux?具体来说,fork() 如何处理 Linux 中 malloc() 动态分配的内存?
【发布时间】:2011-06-03 15:07:42
【问题描述】:

我有一个包含父进程和子进程的程序。在 fork() 之前,父进程调用 malloc() 并用一些数据填充了一个数组。在 fork() 之后,孩子需要该数据。我知道我可以使用管道,但以下代码似乎可以工作:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main( int argc, char *argv[] ) {
    char *array;
    array = malloc( 20 );
    strcpy( array, "Hello" );
    switch( fork() ) {
    case 0:
        printf( "Child array: %s\n", array );
        strcpy( array, "Goodbye" );
        printf( "Child array: %s\n", array );
        free( array );
        break;
    case -1:
        printf( "Error with fork()\n" );
        break;
    default:
        printf( "Parent array: %s\n", array );
        sleep(1);
        printf( "Parent array: %s\n", array );
        free( array );
    }
    return 0;
}

输出是:

Parent array: Hello
Child array: Hello
Child array: Goodbye
Parent array: Hello

我知道在堆栈上分配的数据在子进程中可用,但在堆上分配的数据似乎也对子进程可用。同样,子进程不能修改父进程栈上的数据,子进程也不能修改父进程堆上的数据。所以我假设孩子获得了自己的堆栈和堆数据副本。

在 Linux 中总是这样吗?如果是这样,支持这一点的文档在哪里?我查看了 fork() 手册页,但没有特别提到堆上动态分配的内存。

【问题讨论】:

    标签: c linux malloc fork heap-memory


    【解决方案1】:

    为进程分配的每个页面(无论是上面有堆栈的虚拟内存页面还是堆)都会被复制,以便分叉的进程能够访问它。

    实际上,它并没有在一开始就被复制,它被设置为 Copy-on-Write,这意味着一旦某个进程(父进程或子进程)尝试修改一个页面,它就会被复制,这样它们就不会伤害一个进程-another,并且仍然可以访问来自 fork() 点的所有数据。

    例如,代码页,即实际可执行文件映射到内存中的代码页,通常是只读的,因此可以在所有分叉的进程中重用——它们不会被再次复制,因为没有人在那里写,只有读,因此永远不需要写时复制。

    更多信息请见here 和here。

    【讨论】:

    • 明确一点,OP 的代码中没有 no 竞争条件,对吗?
    • 不可能 - 进程无法再使用此内存进行通信。
    • 吹毛求疵:考虑mmap(MAP_SHARED) 和shmat。这超出了 C 的“堆栈”和“堆”的范围,但您确实说过“每一页”......
    • @ephemient - 好吧,总是有一个挑剔的。你是对的,但我不想开始记录内核代码边缘情况:)
    • 谢谢!这对我来说很有意义。就挑剔而言,共享内存是一种特殊情况,因为它必须可以跨多个进程进行读写访问。在我的程序中,我只需要读取权限,这就是为什么常规 malloc 对我有用,即使我正在从复制的页面中读取。
    【解决方案2】:

    在 fork 之后,子节点完全独立于父节点,但可以继承某些父节点的副本。在堆的情况下,子节点在分叉时从概念上将拥有父堆的副本。但是,修改子地址空间中的头部只会修改子副本(例如通过写时复制)。

    至于文档:我注意到文档通常会声明所有内容都是复制的,除了等等等等。

    【讨论】:

      【解决方案3】:

      简短的回答是“写的很脏” - 较长的答案是......更长。

      但是出于所有意图和目的-在 C 级别可以安全假设的工作模型是,在 fork() 之后,两个进程是绝对相同的 -- 即子进程获得 100% 精确的副本 --(但是在 fork()) 的返回值附近有一点点 - 然后随着每一方修改其内存、堆栈和堆而开始发散。

      所以你的结论有点偏离 - 孩子开始时将与父母相同的数据复制到自己的空间中 - 然后对其进行修改 - 并将其视为已修改 - 而父母继续使用自己的副本。

      实际上事情要复杂一些——因为它试图通过做一些肮脏的事情来避免完整的副本;避免复制,直到必须复制。

      Dw.

      【讨论】:

        【解决方案4】:

        该示例不起作用,因为父项未由子项更新。这是一个解决方案:

        #include <sys/types.h>
        #include <sys/ipc.h>
        #include <sys/shm.h>
        #include <sys/stat.h>
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        #include <unistd.h>
        #include <sys/wait.h>
        
        typedef struct
        {
          int id;
          size_t size;
        } shm_t;
        
        shm_t *shm_new(size_t size)
        {
          shm_t *shm = calloc(1, sizeof *shm);
          shm->size = size;
        
          if ((shm->id = shmget(IPC_PRIVATE, size, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR)) < 0)
          {
            perror("shmget");
            free(shm);
            return NULL;
          }
        
          return shm;
        }
        
        void shm_write(shm_t *shm, void *data)
        {
          void *shm_data;
        
          if ((shm_data = shmat(shm->id, NULL, 0)) == (void *) -1)
          {
            perror("write");
            return;
          }
        
          memcpy(shm_data, data, shm->size);
          shmdt(shm_data);
        }
        
        void shm_read(void *data, shm_t *shm)
        {
          void *shm_data;
        
          if ((shm_data = shmat(shm->id, NULL, 0)) == (void *) -1)
          {
            perror("read");
            return;
          }
          memcpy(data, shm_data, shm->size);
          shmdt(shm_data);
        }
        
        void shm_del(shm_t *shm)
        {
          shmctl(shm->id, IPC_RMID, 0);
          free(shm);
        }
        
        int main()
        {
          void *array = malloc(20);
          strcpy((char *) array, "Hello");
          printf("parent: %s\n", (char *) array);
          shm_t *shm = shm_new(sizeof array);
        
          int pid;
          if ((pid = fork()) == 0)
          { /* child */
            strcpy((char *) array, "Goodbye");
            shm_write(shm, array);
            printf("child: %s\n", (char *) array);
            return 0;
          }
          /* Wait for child to return */
          int status;
          while (wait(&status) != pid);
          /* */
          shm_read(array, shm);
          /* Parent is updated by child */
          printf("parent: %s\n", (char *) array);
          shm_del(shm);
          free(array);
          return 0;
        }
        

        【讨论】:

          猜你喜欢
          • 2022-11-10
          • 2018-01-29
          • 1970-01-01
          • 2020-11-23
          • 2013-11-20
          • 2011-01-15
          • 2019-09-13
          • 2013-10-13
          • 2018-10-16
          相关资源
          最近更新 更多