一、父进程和子进程
当前的一个进程在fork的时候可以复制当前的进程产生一个进程,这时产生出来的这个进程就是子进程,被复制的进程叫做父进程。子进程会将环境变量从父进程继承过来,或者说被拷贝过来。父进程也会有它的父进程,一层一层往上走,最顶头的父进程是读取了一些文件才得到了这些环境变量的值,这样的话一个进程就有了,这个进程有了之后,就会把这些环境变量的值传给它后边的进程,它后边的进程一直往后传。
在运行一个程序的时候,这个程序处在内存中,给这个程序分配内存空间,fork产生的子进程也要给它分配内存空间,原来的父进程需要多少内存空间,复制出来的子进程就需要多少内存空间,系统原来描述父进程有一个PCB,现在产生的子进程也需要有一个PCB,也就是所有父进程的资源都要拷贝一份给子进程,父进程和子进程一模一样,两个进程一起执行。但是子进程不是从头开始执行,是从fork复制的位置开始执行。
二、 fork 方法
语法形式:
pid_t fork(void);
函数返回类型pid_t实质是int类型。这个pid_t
实际上就是进程的一个id号,所以它实际上就是一个整型值。
这个返回值比较复杂,因为fork之后会变成两个进程,一个父进程,一个子进程,fork 函数新生成的一个进程就是子进程。调用fork函数的进程为父进程,新生成的进程为子进程。 这个fork会在父进程中返回一个值,这个值是子进程的pid,即子进程的id号;在子进程中也返回一个值,子进程中返回的是0,如果失败则返回-1。也就是说,在父进程中返回子进程的pid,在子进程中返回0,失败返回-1。
fork执行之后会有父进程和子进程两个进程,两个进程都执行同一套代码,区分到底是父进程在执行,还是子进程在执行,就看fork的返回值。父进程中fork的返回值是大于0的,子进程中fork的返回值等于0。
1.当前进程复制产生一个子进程的过程
代码如下图所示:
以返回值作为判断是父进程在执行还是子进程在执行的条件,然后循环输出父进程和子进程的执行结果。
编译并执行以上代码:
从结果可以看出子进程和父进程两个进程并发运行,无论是先执行父进程还是先执行子进程都是可以的。
2.当前进程复制产生一个子进程之后子进程和父进程当前的pid是怎样的?
通过getpid()
得到当前进程的pid。
编译并运行以上代码:
根据运行结果可以看出子进程比父进程的pid大,这是因为子进程是父进程复制而来的,子进程肯定比父进程晚一些。
当前进程复制产生一个子进程的过程分析:
当程序执行到fork之后,就把当前的进程复制了一份,产生子进程,即子进程和父进程,两个进程分别执行,两个进程具有相同的代码,假设如上述代码当前父进程的pid为264088,新产生出来的子进程的pid为264089。
当fork执行完毕之后,接下来就从fork返回的地方开始继续执行,在当前父进程中fork的返回值就是子进程的pid,即264089,这时pid的值既不等于-1也不等于0,所以就是父进程在执行,跳转到代码中的
n=7;
s="parent";
这一部分,然后再去循环输出结果,输出的时候获取的pid是当前进程的pid,也就是父进程的pid,即264088。这时输出的s就是"parent"。
接下来到了子进程,子进程并不是从头开始执行,而是也从fork返回值得地方开始执行,子进程中fork的返回值是固定值0,所以这时子进程在执行,跳转到代码中的
n=3;
s="child";
这一部分,然后再去循环输出结果,输出的时候获取的pid是当前进程的pid,也就是子进程的pid,即264089。这时输出的s就是"child"。
仅仅由于fork的返回值不同,代码执行的分支不一样。
fork产生一个子进程之后fork就结束了,产生的子进程也不会无限fork下去,因为子进程是从fork带回返回值的位置开始执行。fork不会多次执行。同样在父进程中也不会多次执行fork,也是从fork返回值位置开始执行。
3.父进程和子进程的内存空间
代码如下图所示:
编译并运行的结果如下:
运行结果:n=3和n=7时,n的值不同,但是地址的值却是一样的。
分析:在物理内存中父进程和子进程用的不是同一块内存空间而是分开的,父进程用的是父进程的内存空间,子进程用的是子进程的内存空间。之所以运行结果中的地址是相同的,是因为那是逻辑地址,逻辑地址也就是距离起始位置的偏移量,逻辑地址相同是因为举例起始位置的偏移量相同,所以父进程和子进程的逻辑地址是相同的。打印的地址一般都是逻辑地址,而不是物理地址,物理地址一般看不到,物理地址要经过页表进行转换才能看到物理地址。正常情况下,程序中无法直接打印物理地址。
因此,父进程和子进程的逻辑地址是相同的,但是物理地址不相同。子进程的相对地址还是和父进程中一样的。
三、写时拷贝
假如父进程和子进程两个进程的内容暂时是一样的,现在只修改父进程中其中两个物理内存的值,其它物理内存不做任何修改,那么就把父进程中这两个物理内存的值各复制了一份给了子进程,子进程重新分配两个物理内存用来存放对应的值,接下来对于父进程中不做修改的物理内存,子进程中就不分配物理内存来存放这块物理内存中的值了,而是让子进程直接去使用父进程中没有被修改物理内存中的值,也就是没有被修改的物理内存就不会被复制一份给子进程,而是让父进程和子进程共享同一个物理内存。等到父进程或者子进程要修改被共享的物理内存时,父子进程就不能共享了,因为修改了之后,父进程和子进程对应的物理内存就不一样了,这时就只能把父进程被修改的物理内存的值复制一份给子进程,子进程重新分配物理内存来存放对应的值。
这个共享的过程就是写时拷贝,写时拷贝的目的是为了提高fork复制的性能。在写时拷贝的时候,系统记录这个内存正在被两个进程所引用,一旦其中一个进程结束之后,这块物理内存空间不会被释放掉。
写时拷贝对于用户来说时透明的。