具体来说,fork()如何处理Linux中malloc()动态分配的内存?

时间:2011-01-04 20:06:15

标签: c linux malloc heap fork

我有一个包含父进程和子进程的程序。在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()手册页,但没有特别提到堆上动态分配的内存。

谢谢

4 个答案:

答案 0 :(得分:34)

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

实际上,它不是在开始时复制,而是设置为Copy-on-Write,这意味着一旦其中一个进程(父进程或子进程)尝试修改它被复制的页面,这样它们就不会损坏一个 - 另一个,仍然可以访问fork()的所有数据。

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

有更多信息herehere

答案 1 :(得分:5)

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

至于文档:我注意到文档通常会说明所有都被复制了,除了之外的等等,等等等等。

答案 2 :(得分:2)

简短的回答是“写得很脏” - 答案越长......时间越长。

但是对于所有的目的和目的 - 在C级安全假设的工作模型就是在fork()之后两个进程完全相同 - 即孩子得到100%的精确拷贝 - (但是对于fork()的返回值周围的一点点 - 然后开始发散,因为每一方修改其内存,堆栈和堆。

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

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

DW传递。

答案 3 :(得分:0)

该示例不起作用,因为父级没有被子级更新。这是一个解决方案:

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