目录
传统艺能😎
小编是双非本科大一菜鸟不赘述,欢迎大佬指点江山,QQ - 1319365055
🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,打码路上一路向北,彼岸之前皆是疾苦
一个人的单打独斗不如一群人的砥砺前行
诚邀各位有志之士加入!!
直达: 社区链接点我
pc 指针🤔
我们说进程调用 fork ,当控制转移到内核的 fork 代码后,内核会:
- 将父进程部分数据拷贝给子进程;
- 分配新的内存块和数据结构给子进程;
- 子进程添加到进程列表;
- fork 返回,开始调度
因此 fork 之前父进程独立执行,fork 后父子分别执行,但是谁先谁后完全取决于调度器。
fork 之后,共享的只有 fork 后的代码吗?一般情况下,是父子共享所有代码,虽然子进程执行后续代码,它是相当于共享了所有代码但是子进程只能从某个地方开始执行!什么原理呢?很简单,在 CPU 里面有一种寄存器能保存当前的执行进度,它叫 eip,普遍喜欢叫它程序计数器或者 pc 指针,通过保存当前正在执行指令的下一条指令,拷贝给子进程。
为什么要写时拷贝?🤔
我们创建子进程的时候,直接给父子进程单独拷一份把数据分开,这样不香吗?
首先,这种方法是肯定可行的,但是不是最优的。父进程的数据,子进程不一定全用,即使全用也不一定全部写入,这样会有浪费空间的嫌疑。那为什么不把要用的数据拷一份?先不说技术上有没有人能完美实现,就算我拷贝了要用的但是不进行写入或修改的话绕一圈还是在浪费嘛。第三如果 fork 时无脑给子进程拷贝还会增加 fork 的成本,不管是内存还是时间。
所以采取写时拷贝,只会拷贝父子修改的就是拷贝的最小成本,但是拷贝的成本是不可消除的,这时一种延迟拷贝策略,他最大的意义就是你想要但是不立马使用的会先给别人提高了使用效率。
退出码🤔
我们知道程序结束会 return 0,但是大部分情况我们都是在无脑返回 0,程序最终返回 0 说明得到了我想要的结果,不为 0 就是失败了,那么非 0 返回值就是在标识失败的原因:return x ,这个 x 就是进程退出码。
我们可以查看当前程序的退出码:
echo $?
这个 $? 就是 bash 中最近一次执行完毕时对应的进程退出码。普通的 C++ 代码可以无脑搞个 return 0,但是系统编程时强烈建议搞清楚当前程序的退出码,一般来讲失败的这个非 0 值是可以自定义的,主要是不同返回值方便我们定位不同问题。
exit 与 _exit🤔
main 函数中 return 代表进程终止,但是其他函数为什么不能 return 呢?不代表,return 仅仅代表当前函数调用结束!但是在自己代码中任意位置调用 exit ,就可以直接终止进程,exit 是C库函数,exit(x)中这个 x 就是自义定的退出码。
exit 的底层原理就是利用 _exit 函数 ,功能上二者无异,exit 调用是终止进程并且刷新缓冲区关闭文件流,而 _exit 是直接终止进程,不会有任何其他动作。
slab 分配器🤔
创建对象的过程包括了开辟空间和初始化,对象创建了像 task_struct,mm_struct 这种内核数据结构可能并不会被系统释放,那么系统会维护一个废弃的链表,不要的数据结构就放在这里,所以系统不是去释放而是放在这里使其无效化,当我们再度开辟时,就进行迭代即可,这样节约了申请时间直接跳到初始化,这种结构叫做内核数据结构缓冲池,也叫他 slab 分配器。
进程等待🤔
这个概念是根据僵尸进程提出的,为什么进程结束后不直接终止(X 状态)而是要先进入僵尸进程(Z 状态)?
子进程退出后如果父进程不管就会造成所谓的僵尸进程,从而造成内存泄漏,另外一旦变成僵尸进程连 kill 指令也束手无策,因为逻辑上你无法杀死一个已经死去的进程。其次,父进程有义务知道子进程的完成情况且结果是否正确,我们说到的退出码这里也是这样,确认子进程的退出状态就是子进程的退出码需要被父进程所读取。
所以进程等待实际上是父进程回收子进程资源,获取子进程退出信息的重要渠道!
进程等待方法🤔
wait😎
函数原型以及所需头文件:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
返回值:等待成功则返回等待进程的PID,等待失败,返回-1;参数为输出型参数,获取子进程退出状态,不关心则可以设置成为 NULL 即可。
因此我们不妨用 wait 来验证一下僵尸进程和进程等待机制,通过 fork 创建子进程实现打印,然后终止掉子进程,子进程便处于僵尸状态,此时父进程是处于等待状态的,在10秒后开始对子进程进行处理(也就是获取子进程的pid),处理结束子进程退出,父进程此时再次等待10秒后退出 :
#include