进程调用fork,当控制转移到内核中的fork代码后,内核做:
1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝至子进程
3.添加子进程到系统进程列表当中
4.fork返回,开始调度器调度
内核在软件和硬件之间进行打交道
fork的运行规则:
1.以父进程为模板,将父进程的Pcb拷贝一份,
2.会把父进程的虚拟地址空间拷贝一份作为子进程的地址空间,但是此时的拷贝只是浅拷贝,为的就是节省时间提高
效率,等到对子进程的虚拟地址空间中的内容要去修改的时候,此时就会触发写时拷贝。通常代码是在代码段,只会
被读,所以一般就算是写时拷贝也不会去修改代码段,但是代码段也不是百分之百不会被下修改。因为病毒软件就是
修改代码段,此时也就会触发写时拷贝了。
3.fork()返回会在父子进程中分别返回,父子进程中返回资金的pid,子进程返回0,在fork()后面继续往下执行
4.父子进程的执行顺序没有先后关系,全靠调度器来实现
写时拷贝可以理解为,父子进程共用一份代码,各有一份数据
由于大部分内存空间可能被拷贝,所以创建一个进程的开销比较高(和线程相比)。
1 #include<stdio.h> #include<stdio.h>
2 #include<unistd.h> #include<unistd.h>
3 int main() int main()
4 { {
5 int i = 0; int i = 0;
6 for(i = 0;i < 2;++i) for(i = 0;i < 2;++i)
7 { {
8 fork(); fork();
9 printf("="); printf("=");
10 } fflush(stdout);
11 return 0; }
12 } return 0;
}
上面两段代码充分说明了fork()的继承关系,第一段代码会打印8个 ‘=’,第二段代码会打印6个‘=’原因就是是否刷新缓冲区问题。 而第二段代码,再刷新缓冲区之后, = 就不会被继承
fork()输出系统调用,比较接近底层。
进程的终止有几种情况:
1.代码运行完毕结果正确
2.代码运行完毕结果不正确
3.代码未执行完,异常终止
1.代码执行完的情况
main函数返回,返回值叫做进程的退出码。通过这个码表示运行结果是否正确
退出码为0表示结果正确
退出码非0表示结果不正确
在Linux中输入echo $?按下回车就会打印出上一个进程的退出码,
$?只是bash的特殊变量,也许你换一个shell结果就不一样了。0表示正确非0表示不正确。
标准库函数指的是,只要他是一个相同语言的编译器,那么在他包含头文件之后就可以使用的函数。
exit(退出码)和return相比,exit更方便一些,return的话,只有从main函数返回之后才退出进程。而exit只要
你调用,这个函数,无论你是在main函数中还是在main函数调用的其他的函数之中,都会直接让进程退出,并且返回
你所给的推出码。可以使用echo $?.
但是不推荐使用,因为代码量大的话拍错的话就不太好排查,可以用但是最好不要滥用
exit和_exit的区别?
2.exit进程退出(库函数),exit本质上通过调用_exit来完成退出。
exit虽然说是调用_exit但是人家是库函数,是经过封装的,做的事情比较多,
在使用exit退出的时候会关闭流并且刷新缓冲区。_exit就不行。
而且还会给我们多第哦啊用一个结束函数
3._exit进程退出(系统调用)
总之就是:把建立空间的声明成为“定义”,把不需要建立存储空间的成为“声明”。
2.代码未执行完的情况:
申请内存就和开小旅馆差不多,你要申请到才能用,申请不到不能用,还有一点就是你这次住的小旅馆的房间和下一次住的小旅馆的房间不一样,就和你这次运行代码申请的内存空间,和下一次运行申请的内存空间不是同一块内存空间
空指针(NULL)是0号内存,这块空间你永远也不能用,就属于非法内存的典型代表
段错误就叫做访问内存出错,在Linux下就是进程异常退出,我们访问的一般就是虚拟内存地址,当我们在虚拟内存地
址中,访问的内存在页表上无法对对应物理内存上的地址,那么就是访问非法内存
MMU这个硬件设备就是专门在虚拟内存和物理内存之间进行翻译和转换,所以就是MMU发现了你访问了非法内存,然后
上报操作系统,操作系统将出错的进程强行干掉。或者也可以捕获异常再自己处理。
两句经典语录:
三思而后行 : 先判断,再访问
获得原谅:假如发生错误,进行try catch异常处理
进程等待:
父进程对子进程进行等待,等待是为了读取子进程的运行结果。
在C++中 指针和引用在底层实现上也不是完全一样,因为引用是一个const类型的指针,不是一个随便的指针
初始进程等待
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/wait.h>
4 #include<unistd.h>
5 int main ()
6 {
7 pid_t res = fork();
8 if(res > 0)
9 {
10 printf("我是父亲%d\n",getpid());
11 int status = 0;//传入传出参数
12 pid_t result = wait (&status);//默认阻塞等待
13 printf("我等待的儿子是%d\n",result);
14 }
15 else if(res == 0)
16 {
17 int count = 3;
18 while(count > 0)
19 {
20 sleep(1);
21 --count;
22 printf("%d\n",getpid());
23 }
24 }
25 return 0;
26 }
打印结果:
我是父亲7056
7057
7057
7057
我等待的儿子是7057
wait()参数是输出型参数,返回值是子进程的pid,阻塞式等待,直到子进程退出才会继续运行。
关于如何获取退出码:
我们通过一个输入输出参数,status进行获取,但是由上图可以看出并不是简简单单将参数看作是一个整数,
而是只研究低2个字节
假如是正常退出,那么最低一个字节是0,那么倒数第二个字节就是进程的退出码(退出状态0-255)。
假如是非正常(信号)退出那么最后一个自己的低七位存的是终止的信号,core dump标志指的是
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/wait.h>
4 #include<unistd.h>
5 int main ()
6 {
7 pid_t res = fork();
8 if(res > 0)
9 {
10 printf("我是父亲%d\n",getpid());
11 int status = 0;//传入传出参数
12 pid_t result = wait (&status);//默认阻塞等待
13 if(status & 0xff)
14 {
15 //异常终止
16 printf("异常终止,信号为%d\n",status & 0x7f);//异常终止的话那就拿到core dump标志后面的七个位表示终止的信号
17 }
18 else
19 {
20 //正常终止
21 printf("正常终止,退出码为%d\n",(status>>8) & 0xff);
22 }
23 printf("我等待的儿子是%d\n",result);
24 }
25 else if(res == 0)
26 {
27 int count = 3;
28 while(count > 0)
29 {
30 sleep(1);
31 --count;
32 printf("%d\n",getpid());
33 }
34 }
35 return 0;
36 }
打印结果:
我是父亲7551
7552
7552
7552
正常终止,退出码为0
我等待的儿子是7552
假如在进程正在运行的时候直接将进程终止掉,那么就会给进程一个信号,让进程退出,此时就是异常终止,打印信号码
子进程退出之后,无论是return还是exit退出,都会将自己的 return和exit后面跟的书籍返回个status,而status中只有一个无符号字节来表示退出码,所以只会是0-255,对于上面的代码无论是父进程还是子进程都要从main函数中的return 处返回。返回值都是0,然后父进程等待子进程退出后自己再退出,在shall中使用echo $?得到的父进程的退出码也是0.