目录
一、进程创建
之前的博客已经讲解过fork如何创建进程了,以及如何理解fork创建进程, 详见博客
进程概念与进程状态-CSDN博客
fork创建进程工作
进程调用fork,当控制转移到内核中的fork代码后,内核做:
1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝至子进程
3.添加子进程到系统进程列表当中
4.fork返回,开始调度器调度
fork创建多进程
fork创建的多进程谁先运行完全取决于调度算法!!!
fork调用失败原因
1.系统中有太多的进程
2.实际用户的进程数超过了限制
写时拷贝
上篇博客讲解进程地址空间的时候已经提到过写时拷贝了,本质就是父子进程的代码和数据本来都是共享的,而当父子进程任意一方对数据尝试写入时,为了保证父子进程的独立性,系统会在内存中重新申请一块空间,将要修改的数据重新拷贝一份,然后将重新申请的空间的物理地址填写到 要对数据写入的那个进程对应的页表里面,然后重新映射,重新申请空间并且拷贝数据的机制就是写时拷贝机制!写时拷贝就可以让父子进程实现"惰性分离", 能共享的时候就共享,等到需要写入的时候再重新申请空间!
问题: 当父进程形成子进程之后,子进程要对数据写入时会发生写时拷贝,而写时拷贝需要OS重新申请空间,进行拷贝,修改页表, 而此时子进程即将对数据写入了, 如何让触发OS去做这些工作?
事实就是当父进程创建子进程的时候首先将自己数据段的读写权限改为只读,然后再创建子进程!
但是用户并不知道,所以用户可能对某一批数据进行写入,页表转换就会因为权限问题而出错,此时OS就可以介入了!
出错有两种情况:
1.真正出错了! 比如代码段本身是只读的,而你尝试写入!
2.没有真正出错!比如数据段本身是读写的,但是创建子进程时改成了只读,所以触发OS写时拷贝机制, 然后再将要写入数据的进程的数据段权限改为读写,进程就可以继续执行了!
Q: 当子进程尝试进行写入时,按理只需要重新申请一块空间即可,为啥要拷贝数据呢??
A: 因为子进程修改数据不一定是修改全部数据,比如想修改一个字符串中的1个字符,所以就整体拷贝过来,把那个字符进行修改即可!
二、进程终止
进程退出
类比现实生活中做事情,无外乎三种情况,做完了结果很好,做完了结果不好以及没做完;同样的,进程退出的情况也就三种:
1.代码运行完毕,结果正确
2.代码运行完毕,结果不正确
3.代码异常终止
父进程创建子进程的目的就是为了让子进程执行某种任务,所以任务执行的怎么样了,父进程必然要知道,如何知道?? 就是根据子进程的返回值,而main函数的返回值也叫作进程的退出码!
1.代码运行完毕,结果正确
学习C语言时,我们一直return 0就是表明进程就正常把代码运行完毕了,并且结果正确, 所以命令行上启动的进程,返回0是返回给了bash, 表明bash的子进程正常执行完了代码,结果正确
echo $? 查看进程的退出码
2.代码运行完毕,结果不正确
子进程代码运行完了,但是结果不正确,就会返回非0数字,表明错误信息,但是纯数字不利于人看,所以C语言提供了strerror函数将纯数字表示的错误信息转化成字符串!
第一次查看退出码是10,因为子进程返回值是10, 而后续查看发现都是0,因为指令运行起来后也是进程,所以echo指令正常执行完后就返回了0!
退出码 vs 错误码
我们C语言还学过错误码, 当函数调用失败时,errno会被设置
所以错误码是我们一个函数/系统调用返回的结果,而退出码是一个进程的返回结果!
3.代码异常终止
进程异常了,OS就要杀死进程,而OS检测到进程异常是通过发信号杀死进程的!
所以一个正常的进程,如果我们人为发送异常信号,照样可以复现上述的报错信息, 杀死进程!
return 与 exit 与 _exit
在main函数中return终止进程
在main函数中exit终止进程
在普通函数中return, 进程不会终止,后续操作照常执行
在普通函数中exit, 进程直也会接终止,后续操作不会执行
exit是一个C语言库函数,而_exit是一个系统调用,两者在上述的代码中并无二异
exit:
_exit:
带\n与不带\n的区别我在 Linux环境基础开发工具使用篇(二) gcc/g++ 与 make/makefile-CSDN博客 这篇博客中已经讲解过了,exit是C语言库函数,_exit是系统调用,而在 库与系统调用 已经讲过库和系统调用的关系了;
由上述对比可以得到的结论是exit在终止进程时会刷新缓冲区,而_exit在终止进程时不会刷新缓冲区; 会进一步得到的结论时,缓冲区一定不在OS内部,只能在C标准库里!!!
因为终止进程就是要删除pcb,释放代码和数据等等,所以必定要访问内核,而exit是库函数,里面封装的是_exit接口,所以如果缓冲区在OS内部,那么exit在终止进程时刷新了缓冲区,_exit也必定要刷新缓冲区!