复制进程(共享资源)
1.复制进程的概念
fork创建一个进程时,子进程只是完全复制父进程的资源,复制出来的子进程有自己的task_struct结构和pid,同时复制父进程其它资源(用户空间、文件描述符集)。
2.复制进程的实现方式
当我们需要复制一个进程时,我们就去调用fork()这这个函数,fork()这个函数就会去陷入内核去多复制一个pid出来,此时这个pid下的进程执行的程序就是子进程的。
3.写实拷贝
fork()是一个开销十分大的系统调用,这些开销并不是所有的情况下都是必须的,比如某进程fork()出一个子进程后,其子进程仅仅是为了调用exec执行另一个可执行文件,那么在fork()过程中对于虚存空间的复制将是一个多余的过程。但由于现在Linux中是采取了copy-on-write(COW写时复制)技术,为了降低开销,fork()最初并不会真的产生两个不同的拷贝,因为在那个时候,大量的数据其实完全是一样的。写时复制是在推迟真正的数据拷贝。若后来确实发生了写入,那意味着parent和child的数据不一致了,于是产生复制动作,每个进程拿到属于自己的那一份,这样就可以降低系统调用的开销。所以有了写时复制。
4.fork()的返回值
fork()执行完成之后会有两个返回值,其中父进程返回父进程的pid号,对于子进程,它会返回一个零,这就是通过fork()来执行两个进程的关键。
5.共享代码段
在fork之后,子进程和父进程都会继续执行fork()调用之后的指令。子进程是父进程的副本。它将获得父进程的数据空间,堆和栈的副本,这些都是副本,父子进程并不共享这部分的内存。也就是说,子进程对父进程中的同名变量进行修改并不会影响其在父进程中的值。但是父子进程又共享一些东西,简单说来就是程序的正文段。正文段存放着由cpu执行的机器指令,通常是read-only的。
6.fork()复制进程的程序验证
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
char *s =NULL;
int n = 0;
pid_t pid = fork();
assert (pid != -1);
if(pid == 0)
{
s = "child";
n = 3;
}
else
{
int val=0;
pid_t child_id =wait(&val);
s = "parent";
n = 8;
}
int i = 0;
for(;i<n;i++)
{
printf("pid=%d,s=%s,ppid=%d\n",getpid(),s,getppid());
sleep(1);
}
exit(3);
}