目录
1.上节回顾
上次学习了这个页表,物理内存,虚拟地址的相关知识,页表充当了一个物理地址和虚拟地址映射的一个功能;
发生写实拷贝的时候,会发生缺页中断现象,先到这个物理地址上面开辟空间,再去这个对应的页表上面去进行完善;
2.对于fork函数的再谈
2.1调用fork做的事情
分配新的内核段和内核数据结构给子进程,然后给这个子进程拷贝一份父进程的数据,接着把这个子进程添加到我们系统的进程列表里面,最后是使用调度器进行调度;
2.2fork的返回值
子进程返回0
父进程返回子进程的pid,所以这个函数的调用的时候会有两个返回值;
2.3修改内容前后
接下来我们看一下这个下面的对于数据代码修改前后的这个变化的过程:
修改之前,五路我们的数据区里面的内容是什么属性的,系统都会判定其为只读的属性,就是给这个数据贴上一个只读的标签,父进程和这个子进程的内容是一样的,实际上就是父进程的拷贝;
但是当我们想要对于这个父子进程共享的数据进行修改的时候,这个肯定会触碰系统的权限要求,这个时候系统不会因为这个对于只读数据进行修改而对于这个当做异常值进行处理,而是把这个只读的标签去掉,然后把这个修改的数据在物理内存上面开辟新的空间,对应的这个子进程的页表上面的只读的标签也会被去掉;
2.4调度器的调度
当我们的一个父进程对应多个子进程的时候,这个子进程的执行顺序我们是没有办法进行控制的,这个和调度器的执行逻辑有关,调度器会根据这个子进程的位置之类的信息去进行调度,从而确定他们的执行的顺序,我们操作者无从得知;
3.进程的退出
3.1错误码介绍
三种情况:代码运行完成,输出正确结果
代码运行完成,输出错误结果;
程序遇到异常导致程序终止掉;
return 0这个是我们经常写的这个main函数的返回值,这个返回值的本质表示的就是这个进程运行结束之后输出的这个结果是不是正确的结果,如果不是的,可是使用不同的数字去表示这个错误的原因,0表示的就是这个程序正常运行,成功运行;
echo $?这个指令打印输出就是最后一次进程退出时候的退出码;
我们可以使用下面的这个strerror去查看所有的退出码对应的所有的情况,像这个0表示的就是success成功运行的意思,2表示的就是我们想要查看的这个文件不存在的意思等等;
当我们的程序遇到异常的时候,这个退出码不管用了,因为代码可能就没有跑完,我们只有在这个代码跑完的情况下才会去关注他的退出码,没有跑完我们不去关心他的退出码;
系统提供的这个错误码和对应的这个错误码的描述一一对应,进程出现异常,本质是我们的进程收到了对应的信号
3.2两点区别
_exit是我们的系统调用接口,这个可以直接把我们正在运行的程序加载到内核里面去;
exit这个函数要帮助我们用户进行这个数据的清理,刷新这个缓冲区,最后才把这个数据加载到内核里面;
因此这个缓冲区是在我们的用户空间里面,一定不再我们的内核区里面!!!
4.进程等待
4.1是什么
通过wait/waitpid,对于这个子进程进行功能的检测和回回收;
4.2为什么
我们僵尸进程无法被杀死,需要通过进程等待进而解决内存泄漏问题;
进程等待获得子进程的退出情况,布置给子进程的任务,完成的怎么样了;
4.3怎么样
wait在等待的时候,等待的就是我们的子进程,如果我们的父进程有多个子进程,wait是等待任意一个子进程退出,这个时候需要进行这个循环等待;
如果我们的父进程进行这个wait的时候,我们的子进程一直不退出,这个时候的父进程就是阻塞进程,一直不退出;
4.4具体说明
我们的父进程等待就是为了获取子进程的相关信息,具体是哪些信息:父进程的代码是不是异常,如果没有异常,这个执行的结果是否正确,获取这个退出码,知道这个原因,因为不同的退出码,显示的就是不同的出错原因;
我们想要知道这个子进程是不是异常的,这个时候需要借助这个status参数,这个参数的低7位表示的是这个程序是不是异常的,如果不是异常的,我们的这个低8位可以得到这个正常的信号,如果没有得到这个信号,就说明这个子进程是异常的,根本就没有正确的执行;
如果得到这个信号,子进程正常执行,但是这个结果是不是正确的,我们需要借助这个status参数的次8位进行判断,来我们根据这个次8位的退出码,确定这个子进程正常执行,但是结果不正确的原因;
其次我们应该清楚,虽然这个子进程和父进程的代码在一个里面,但是这个子进程和父进程有独立性,这个时候父进程想要得到子进程的信息,必须使用waitpid函数;
5.fork函数手册讲解以及实际案例
5.1手册概述
我们上面也说了这个fork函数就是创建一个新的进程,这个新的进程就是子进程,我们的pid_t就是fork函数的返回值,下面我们具体看一下这个fork函数的手册man函数调出来编程手册:
首先我们研读这个手册内容:
第一点就是使用这个fork函数需要包含这个上面的两个头文件,sys/types.h和unistd.h文件;
第二点就是这个函数的返回值是一个pid_t类型的,这个实际上就是一个整形,也就是返回的进程的id号码;
这个下面里面的第一句话的意思就是我们的这个fork函数通过拷贝去创建一个新的进程,dupllicating就是拷贝的意思,calling process就是调用的进程的意思,这个新的进程叫做子进程,调用fork函数的进程叫做父进程;
下面的这个意思是说:我们的这个父进程和子进程都有他们自己的空间,same content说明这个父进程和子进程的空间虽然是不一样的,但是这个里面运行的内容是一样的,这个其实就是我们前面说的这个子进程是拷贝的这个父进程;
5.2题目说明
下面两次调用这个fork函数,打印的结果应该是什么?
我们分析一下,我们首先是A这个父进程调用这个fork函数,这个就会产生一个子进程,再次调用的时候就是还会产生子进程,1到2,2到4的过程,因此这个打印的结果里面应该是有4个输出结果;
第1个结果,两个结果分别是2759,2760说明两次调用的时候都是作为父进程,因为父进程调用fork函数,返回这个子进程的pid,子进程的返回值是0,如果不是0,说明这个是父进程,我们对照发现这个只有A进程两次调用的时候都是作为父进程的,因此第一个结果是A进程的;
第3个结果,第一次作为子进程打印0,第二次作为父进程打印2761,我们对比发现这个应该是进程B,因为这个进程B第一次作为子进程,第二次作为父进程的;
第二个结果,2759和0,按照道理应该是第一次作为父进程,第二次作为子进程,但是这个时候我们发现并没有这个符合要求的进程,但是实际上这个是C进程,因为这个C进程第二次是作为子进程,打印0是没有问题的,但是第一次他是作为父进程的吗,虽然这个进程C没有参与这个第一次调用,但是这个C的父进程是A,这个A第一次是作为父进程的,我们的这个C进程实际上是拷贝的这个父进程的数据,因此这个打印的结果是2759,其实也就是进程A第一次的pid数值;
同理,我们就可以进行这个第四个输出结果的分析了,这个打印结果是两个0,实际上这个两次作为子进程的进程应该也是不存在的,但是这个D第二次作为子进程打印0,第一次实际上就是因为拷贝的是自己的父进程B的代码和数据,这个B进程第一次作为的是子进程,所以这个D打印结果的第一个0实际上就是拷贝的B进程的;
综上所述:实际上这个题目并不难,但是对于初学者并不简单,通过这个题目,我们应该深刻的了解这个子进程拷贝了这个父进程的pid,调用fork函数的时候,这个子进程返回0,父进程返回子进程的pid,当这个子进程没有的时候,会拷贝这个对应的父进程的代码和数据作为输出的结果;
5.3fork函数进一步认识
我们首先要通过下面的这个实例知道:父进程和子进程是并发运行的,这个打印结果不能正确的显示这个子进程和父进程的执行顺序,这个printf打印的先后顺序不能代表进程运行的先后顺序;
这个printf打印结果只是那个进程先调度这个函数,显示的结果只能说明这个调用的顺序,不能说明这个运行的顺序,实际上这个父进程先调用,因为这个子进程需要拷贝我们的父进程的数据,从这个角度我们父进程也是先运行的;
实际上这个打印结果,hello world和good morning的这个打印的顺序的先后不是确定的,这个不能正确的显示这个进程的运行先后顺序;
首先来看第一份代码:我们运行发现,这个父进程的count和子进程的count都是在自己的这个count上面进行的++操作的,这个互不干扰,主要父子进程拥有自己的独立的空间;
再看下一个实例,这个就是想要说明这个父子进程之间的这个运行状态不会相互影响,这个父进程结束了,子进程依然可以继续运行: