一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
假定父进程malloc的指针指向0x12345678, fork 后,子进程中的指针也是指向0x12345678,但是这两个地址都是虚拟内存地址,经过内存地址转换后所对应的 物理地址是不一样的。所以两个进程中的这两个地址相互之间没有任何关系。
父子进程的地址空间是相互独立的!
fork后,这两个虚拟地址实际上指向相同的物理地址(内存页);只有任何一个进程试图修改这个虚拟地址里的内容前,
两个虚拟地址才会指向不同的物理地址,新的物理地址的内容从原物理地址中复制得到,这就是Linux的copy-on-write技术。
fork的返回值比较特殊,在父进程和子进程中返回值不相同,如果创建子进程成功,那么
在父进程中,返回子进程pid,
在子进程中,返回0,
如果出现错误,fork返回一个负值。
举个例子说明:
int ForkWork(int count);
int main(int argc, char const *argv[])
{
int count = 4;
ForkWork(count);
return 0;
}
int ForkWork(int count)
{
pid_t pid = 0;
for (int i = 0; i < count; ++i)
{
pid = fork();
if (pid == 0 || pid < 0)
{
break;
}
}
if (pid < 0)
{
perror("fail to fork!\n");
exit(1);
}
else if (pid == 0)
{
int a = 10;
count = 5;
printf("child-pid:%u, count=%d, count_ptr:%p, a_ptr:%p\n", getpid(), count, &count, &a);
exit(0);
}
else if(pid > 0)
{
count = 3;
printf("father-pid:%u, count=%d, count_ptr:%p\n", getpid(), count, &count);
exit(0);
}
return 0;
}
打印结果:
father-pid:26313, count=3, count_ptr:0x7fff81741c9c
child-pid:26317, count=5, count_ptr:0x7fff81741c9c, a_ptr:0x7fff81741ca4
child-pid:26315, count=5, count_ptr:0x7fff81741c9c, a_ptr:0x7fff81741ca4
child-pid:26314, count=5, count_ptr:0x7fff81741c9c, a_ptr:0x7fff81741ca4
child-pid:26316, count=5, count_ptr:0x7fff81741c9c, a_ptr:0x7fff81741ca4
可以看出父子进程的变量的指针地址是相同的,但是实际的物理地址不同的,值也不相同。
用pid == 0 || pid < 0的条件判断是否break,是为了让子进程跳出循环,父进程继续执行循环。
因为子进程返回0,可以break;
父进程返回的pid>0,则继续执行循环。