具体来说,fork() 如何处理 Linux 中 malloc() 动态分配的内存?
Posted
技术标签:
【中文标题】具体来说,fork() 如何处理 Linux 中 malloc() 动态分配的内存?【英文标题】:Specifically, how does fork() handle dynamically allocated memory from malloc() in Linux? 【发布时间】: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() 手册页,但没有特别提到堆上动态分配的内存。
【问题讨论】:
【参考方案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;
【讨论】:
以上是关于具体来说,fork() 如何处理 Linux 中 malloc() 动态分配的内存?的主要内容,如果未能解决你的问题,请参考以下文章