1.进程等待的必要性
- 子进程退出,父进程不管不顾,就会造成僵尸进程,进而造成内存泄漏
- 进程一旦变成僵尸状态,kill-9也无法杀死
- 父进程通过等待的方式,回收子进程资源,获取子进程的退出信息
2.进程等待的方法
- wait方法
- int wait(int *status)
- 是一个阻塞接口,处理退出的子进程,则会一直等待,直到有子进程退出才会返回
- 返回值:成功则返回处理的退出子进程的pid;失败则返回-1
- 阻塞:为了完成一个功能,发起一个调用,若不具备完成功能的条件,则调用一直等待
- 非阻塞:为了完成一个功能,发起一个调用,若不具备完成功能的条件,则调用会理解报错返回
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
//返回值:成功返回等待进程pid,失败返回-1.
//参数:输出型参数,获取最近称退出状态,不关心则可以设置为NULL
-
waitpid方法
-
int waitpid(int pid, int* status, int option);
-
wait和waitpid的不同之处
-
wait等待的是任意一个子进程的退出,(wait是一个父进程假设有很多的子进程,任意一个退出,都会处理后调用返回)
-
waitpid可以等待指定的子进程,也可以等待任意一个子进程,通过第一个参数确定(第一个参数pid==-1则表示等待任意)
-
wait是一个阻塞接口(wait如果没有子进程退出,则会一直等待)
-
waitpid可以默认阻塞,也可以设置为非阻塞,通过第三个参数确定(第三个option=0表示默认阻塞,option==WNOHANG则表示非阻塞)
-
返回值:成功则返回退出子进程的pid大于0;若子进程退返回0,;若出错返回-1
pid_t waitpid(pid_t pid, int* status,int optons);
// 返回值:当正常返回的时候waitpid返回收集到子进程的进程ID;
// 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
// 如果调用中出错,则返回-1,这是error会被设置成相应的值以指示错误所在;
// 参数:pid:pid =-1,等待任一个子进程,与wait等效
// pid > 0,等待其进程ID与pid相等的子进程
// status: WIFEITED(status):若为正常终止子进程返回的状态,则为真(查看进程是否正好吃那个退出)
// WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码(查看进程的退出码)
// options: WNOHANG:若pid执行的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该进程的ID
- 如果子进程已经退出,调用wait / waitpid 时,wait / waitpid会立即返回,并且释放资源,获取子进程退出信息。
- 如果在任意时刻调用wait / waitpid****,子进程存在且正常运行,则进程可能堵塞
- 如果不存在该子进程,则会立即出错
- 理解两个接口的区别:在合适的地方使用适合的接口(比如不想一直等待子进程退出,则使用非阻塞)
- 通过wait接口获取的子进程退出返回值,返回值只使用了一个自己进行保存,并且在返回的时候,并没有存储在status的低位
- 判断一个进程是否正常退出:取出低7位;status&0x7f==0正常退出
- 如果取出返回值—低16位的高八位;(status>>8)&0xff
3.获取子进程的status
-
wait / waitpid,都有一个status的参数,该参数是一个输出型参数,由操作系统填充。
-
如果传递NULL,表示不关心子进程退出状态信息
-
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程
-
status不能简单的当做整型来对待,可以当做位图来对待 (如下图)
-
额外介绍:系统调用接口出错后,如何获取错误原因
#include <errno.h>
#include <string.h>
char *strerror(errno); // 根据错误编号获取文字信息---错误都是在上一次系统调用接口使用错误的原因
perror(char *msg);// 直接打印上一次系统调用接口使用错误的原因
- core dump—核心转储
- 核心转储其实就是指程序异常退出时,将推出前的程序运行信息保存下来(默认是关闭的)
4.程序替换:替换一个进程正在调试运行的程序
- 程序是一堆死代码,运行时会被加载到内存中(依旧是一顿数据);如何动起来,是pcb干的事情
- 程序替换说白了就是,重新加载另一个程序到内核中,然后将现有的一个pcb的内存指针所指向的内存空间执行新的程序(更新页表映射信息),则这个现有的pcb跑去调度这个新的程序了;
-
fork创建一个子进程,代码共享数据独有—父子进程干的事情是一样的
-
通常情况下,我们创建一个子进程的目的并不是为了让子进程与自己干一样的事情,而是让子进程运行调度一个新的程序,让他干其他的事情,完成其他的任务,这时候就用到了程序替换。
5.程序替换的exec函数族
- 程序替换,给一个进程替换一个新的要调度运行的程序,并且因为这个进程调度的程序已经被替换,因此当运行完毕新的程序就会退出;原先的程序在程序替换以后的代码都不会被运行到(替换后相当于已经没有当前的代码了,只有新的程序)
- 函数族参数说明:第一个参数就是新的程序文件路径名称
#include <unistd.h>
extern char **environ;
// 加载一个新的程序到内核中,将当前的pcb的映射指向这个新的程序,调度这个新的程序的运行
int execl(const char*path,const char* arg,...); //path-带路径的程序文件名称;arg、...表示程序的运行参数,逐个赋值,最终以NULL结尾
int execlp(const char*file,const char*arg,...);//PATH环境变量指定了一些路径,execlp会去PATH环境变量指定的路径下查找程序文件
int execle(const char*path,const char*arg,...,char *const envp[]);
int execv(const char*path,char*const argv[]); // l和v的区别:程序运行参数的赋予方式不同
int execvp(const char*file, char *const argv[]); // 有无p的区别:新的程序文件的名称是否需要带路径
int execve(const char*file,char*const argv[],char*const envp[]); // 有没有e区别:是否自定义环境变量