我们在学习进程的时候不得不提到一个函数fork(),fork还有一个名字叫做分叉函数,我们可以通过fork函数来创建一个和我们当前进程一样的新进程,并且我们通常把新创建的进程叫做子进程,把之前就存在的进程叫做父进程,并且子进程继承了父进程的整个地址空间,包括了程序上下文,包括堆栈体制,甚至我们上次刚刚说过的PCB他也是直接复制了过来。不过既然这是两个不同的进程那两个进程的进程id是不同的。
我们在Linux可以通过man指令来查看我们的fork函数
![](https://i-blog.csdnimg.cn/blog_migrate/4e74c5d953a2d57ce10ff37ed1441055.png)
fork函数就是来创建一个子进程,如果这个子进程创建成功的话对于父进程来说,就是返回这个子进程的ID,但是对于子进程来说就是返回0。如果返回的是-1,那就代表着进程创建失败了。我们通过程序来看一下fork。
1 #include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 pid_t pid= fork();
6 if(pid<0)
7 {
8 printf("failed!!\n");
9 return -1;
10 }
11 else
12 {if(pid==0)
13 {
14 printf("this is chilld!!! %d \n",getpid());
15 }
16 else
17 {printf("this is parent!!! %d \n",getpid());
18 }
19 }
20 while(1)
21 {
22 printf("pid=%d leihoua!!\n",getpid());
23 sleep(1);
24 }
25 return 0;
26 }
27
我们通过pid来获取fork的返回值,然后让子程序和父程序进入不同的条件语句,输出不同的语句之后我们编译运行这个程序
![](https://i-blog.csdnimg.cn/blog_migrate/e785596c4e713ddcb65e7ebba407487f.png)
会发现父进程和子进程两个都在执行着自己的语句。在pid=fork(),只有一个进程来执行这段代码,但是在这句话之后,就变成了两个程序来执行这段代码这两个进程几乎相同,之后要执行的下一条指令都是去进入if判断语句。但是为什么我们两个进程的id不同,这就和我们fork函数的特性有关,fork的特点就是调用一次,但是却能够返回两次,并且由三种不同的返回值。我们可以通过fork的返回值来判断到底是子进程还是父进程。Fork返回错误值的可能性有两种,第一种就是我们的系统进程数已经到达了系统的上限,另一种就是我们的系统资源没有了。
#include<unistd.h>
3 int main()
4 {
5 int val=100;
6 pid_t pid= fork();
7 if(pid<0)
8 {
9 printf("failed!!\n");
10 return -1;
11 }
12 else
13 {if(pid==0)
14 {
15 val=200;
16 printf("this is chilld!!! %d \n",getpid());
17 }
18 else
19 {printf("this is parent!!! %d \n",getpid());
20 }
21 }
22 while(1)
23 {
24 printf("pid=%d leihoua!! %d\n",getpid(),val);
25 sleep(1);
26 }
27 return 0;
28 }
29
在这次的代码里边我们加入了一个变量,val在初始的时候我们的变量值是100,之后我们在子进程的那部分将变量的值修改为200,我们再去运行程序,看一下结果。
![](https://i-blog.csdnimg.cn/blog_migrate/583551fb0bd35b0f6ee00268c74bbc7b.png)
我们发现在子进程下值变成了200,但是父进程下午我们val的值依然没有变仍然是100.我们再对程序做个简单修改,这里我们再输出一下val的地址。
24 printf("pid=%d leihoua!! %d %p\n",getpid(),val,&val);
![](https://i-blog.csdnimg.cn/blog_migrate/c44327de37261061d29bcdaed181a430.png)
这时候就会发现一个很奇怪的问题,我们打印出来的两个变量的地址竟然是相同的,但是我们都知道我们同一个地址怎么可能存储两个不同的值,这里就设计到了我们的虚拟地址也就是逻辑地址和物理地址的区别,我们简单来说就是我们在创建子进程的时候并没有说给他分配一定的物理地址空间,而是给他拷贝了一份虚拟的地址空间,只有等到他真正的要去使用我们的物理空间的时候,才会给他分配一份物理的地址。这就是我们的写时拷贝,我们之前在学习c++类的时候就提到过这种方法。之后我们会对物理地址和虚拟地址的关系再做讲解。
![](https://i-blog.csdnimg.cn/blog_migrate/5d0e688c143c9a80607ded4e61667458.png)
这里我们在fork函数前边加入一个输出语句
![](https://i-blog.csdnimg.cn/blog_migrate/b9ff9b64c94980a3ab721183456cf2ac.png)
这里我们就是为了来看一下这个输出语句,我们上边也说过了fork是来创建一个和父进程一样的子进程,但是看我们的输出语句,只是输出了一个输出语句,我们上边也说过了子进程是完全复制了父进程,为什么只有一个进程输出了呢?这里不用想就是我们的父进程输出的,这里我想说的是,我们的子进程在复制父进程的时候是复制的我们的PCB,PCB我们之前也介绍过PCB的其中有一个是我们的程序计数器就是我们下一条指令,这里我们在fork函数的时候printf输出函数已经执行过了, 所以复制的时候复制的是直接是下一个指令地址。所以fork函数之前的指令是不会复制过来的。