目录
写实拷贝
为什么要写实拷贝?
fork函数
返回值
常规用法
调用失败的原因
进程终止
情况分类
a.代码正常执行完了
b.崩溃了(进程异常)
进程的退出码
c语言提供的系统的退出码
如何理解进程退出
操作都有哪些方式?
main函数return。
exit函数退出
进程等待
为什么要进程等待?
什么是进程等待?
进程等待的方法
获取子进程status
父进程是如何获取子进程的退出信息的?
父进程在wait的时候,如果子进程没退出,父进程在干什么?
进程程序替换
替换原理
替换函数
函数解释
命名理解
写实拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副 本。具体见下图:
为什么要写实拷贝?
操作系统不允许各种浪费或者不高效的行为存在
写实拷贝本质是一种按需申请资源的策略
fork函数
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程
进程调用fork,当控制转移到内核中的fork代码后,内核做:
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表当中
fork返回,开始调度器调度
返回值
子进程返回0, 父进程返回的是子进程的pid。
常规用法
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子 进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数
调用失败的原因
系统中有太多的进程
实际用户的进程数超过了限制
进程终止
情况分类
a.代码正常执行完了
b.崩溃了(进程异常)
(例如野指针,访问越界等等)——崩溃的本质:进程因为某些原因,导致进程收到了来自操作系统的信号(kill-9)
进程的退出码
例如现在可以理解为什么之前的c语言都有一个return 0,这个0就是main函数的退出码,指的是程序执行成功,没有错误正常退出
可以根据进程的退出码判定进程是否正常退出
用echo $?可以拿到当前进程的退出码
补充:$? 只会保存最近一次进程的退出码
c语言提供的系统的退出码
strerror()
退出码对应的描述
如何理解进程退出
OS内少了一个进程,OS就要释放进程对应的内核数据结构+代码和数据(如果有独立的)
操作都有哪些方式?
main函数return。
其它函数return呢? 仅仅代表函数返回-> 进程执行,本质是main执行流执行
exit函数退出
这里也说明了 exit的参数就是进程的退出码
这个exit在函数的任意地方调用,都可以退出
还有一个接口叫 _exit() 的功能跟exit类似
但是 exit()会刷新输出缓冲区,_exit()不会刷新输出缓冲区
exit()
1. 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit
进程等待
为什么要进程等待?
1.避免内存泄漏(目前一定要做)
2.获取子进程执行的结果(如果必要的话)
之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。
最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
什么是进程等待?
通过系统调用,获取子进程退出码或者退出信号的方式,顺便释放内存问题
进程等待的方法
wait方法
返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
waitpid方法
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码) options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进 程的ID
第二个参数
如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退
出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回
获取子进程status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status(32位)不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特 位):
次低八位保存当前进程退出状态,低七位保存当前进程退出信号
父进程是如何获取子进程的退出信息的?
进程pcb会有相关的退出信息,操作系统将其设置到waitpid/wait的第二个参数中
父进程在wait的时候,如果子进程没退出,父进程在干什么?
附近在没有退出的时候,一直在调用waitpid进行等待——阻塞等待
进程程序替换
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
替换函数
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
nt execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
-------------------------------------------------------------------------
以上都是对下面这个接口的封装,这个才是操作系统的替换函数
int execve(const char *path, char *const argv[], char *const envp[]);
函数解释
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1 所以exec函数只有出错的返回值而没有成功的返回值。
命名理解
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量