再谈fork()
进程调用fork,当控制转移到内核中的fork代码后,内核做:
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表当中
fork返回(当return的时候),开始调度器调度
创建子进程,给子进程分配对应的内核结构,这个结构必须子进程自己独有,因为具有独立性,理论上子进程也要有自己的代码和数据,可是一般而言,我们没有加载的过程,也就是说子进程没有自己的代码和数据。所以,子进程只能使用父进程的代码和数据
代码:都是不可被写的,只能读取,父子共享没问题
数据:可能被修改,所以,必须分离。
对于数据而言创建进程的时候,就直接拷贝分,这种情况可能会拷贝一些不会用到的空间,即使用到了,也只能读取
编译器编译程序的时候,尚且知道节省空间。创建子进程,不需要将不会被访问的,或者只会读取的数据,拷贝一份。将来被父或子进程写入得数据会被拷贝。一般而言即便是OS,也无法提前知道哪些空间可能会被写入。所以操作系统选择了,写时拷贝技术,将父子进程的数据分离。
fork之后代码共享,共享的是所有的代码
我们的代码汇编之后,会有很多行代码,而且每行代码加载到内存之后,都有对应的地址
当进程没执行完时候,可能被切换,下次回来,还会从之前切换的位置继续运行,所以CPU要记住这个位置,CPU中有对应的寄存器数据来记录这个当前位置
pc指针存的是当前正在执行代码的下一行代码的地址,寄存器在CPU内只有一份,但寄存器内的数据(进程的上下文数据)可以有多份
父子进程可以各自调度上下文数据,各自会修改EIP,但是已经不重要了,子进程认为自己的EIP起始值,就是fork之后的代码,子进程从after开始跑,不代表fork之前的代码它看不到
fork常规用法和调用失败原因
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数
系统中有太多的进程
实际用户的进程数超过了限制
进程终止
1. 进程终止,操作系统都做了什么?
释放进程申请的相关内核数据结构和对应的数据和代码,本质就是释放系统资源
2.进程终止常见的方式有哪些?
a.代码跑完,结果正确 b.代码跑完,结果不正确 c.代码没有跑完,程序崩溃了(下面例子)
程序崩溃
main函数返回值
main函数的返回值并不总是0,return 后面跟的数字是进程退出码,进程退出码表示进程是否正确返回,如果返回0则运行结果正确,如果非0,则结果不正确
查看退出码:echo $?
我们把进程退出码修改为10,第一次查看退出码结果是10,之后查看结果就会是0,这是因为echo $?获取的是最近一个进程执行完毕后的退出码
main函数返回值的意义,返回给上一级进程(如父进程),用来评判该进程执行结果用的,可以忽略。
我们可以通过退出码判断出运行错误的原因,不同的退出码代表着不同的出错信息
strerror可把一个整数转换成字符串
查看退出码
我们可以看到0代表成功
我们可以自己使用这些退出码和含义,我们也可以自己定义退出码和推出含义,如return 10,我们可认为10是File exists
程序崩溃的时候,退出码无意义,一般而言程序还没执行到return就会崩溃,只有main函数里的return是进程退出,其它函数return起返回作用
exit和_exit
相同点
exit
_exit
测试exit
退出码 111,说明exit在代码的任何地方调用都是直接终止进程
测试_eixt
说明exit和_exit共同之处,一执行就退出
不同点
正常运行
去掉\n,由于没了\n,就无法刷新缓冲区,也就是说要打印的内容此时存在了输出缓冲区当中,但exit此时也正常运行
我们换成_exit
此时没有打印出来,也就是_exit不会刷新缓冲区
加上\n之后,正常运行
总结
exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作
1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit
printf不加\n数据保存在“缓冲区”当中,这个缓冲区一定不在操作系统内部,如果是操作系统维护的,缓冲区_eixt也能刷新,该缓冲区只能在库函数,如C标准库