目录
1.进程等待
1.1进程等待必要性
·子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
·父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
·父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
1.2进程等待的方法
wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
wait是一个阻塞接口,功能是等待当前调用者的任意一个子进程退出(如果已经有退出的直接处理),获取返回值,释放资源
返回值:成功返回被等待进程pid,失败返回-1
status参数:是一个int空间的地址,用于向指定空间中存放子进程的退出返回值
waitpid方法
pid_t waitpid(pid_t pid,int* status,int options);
waitpid接口既可以等待任意一个子进程退出,也可以等待指定的子进程退出
pid参数:>0则表示等待指定pid的子进程退出;
-1表示等待任意一个子进程的退出
waitpid接口既可以阻塞等待,也可以使用非阻塞等待
options参数:0-表示默认阻塞等待;
WNOHANG-设置为非阻塞(当前没有子进程退出则会返回0)
返回值:成功则返回处理的退出子进程的pid,若没有子进程退出则返回0;出错则返回-1
阻塞接口:
为了完成一个功能发起了一个调用,但是这个调用完成条件不具备,则接口一直等待不返回。
操作流程简单,但是对资源利用率较低。
非阻塞接口:
为了完成一个功能发起了一个调用,但是这个调用完成条件不具备,则立即报错返回。
操作流程稍微复杂一些(通常需要循环操作),但是对资源的利用率较高。
1.3获取子进程status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
通过wait获取的返回值,有多个信息,其中进程的退出码只是其中一部分,并且只用了1个字节保存(如果exit给的退出码过大,就会截断)
coredump翻译过来就是核心转储,当程序异常退出的时候,保存程序运行信息,便于能够事后调试,默认处于关闭状态(隐私&安全,占据磁盘空间)
异常信号值--作用就是保存退出异常退出原因,为0则表示程序是正常退出,非0则表示异常退出
因此 要获取一个进程的退出码,首先得确定这个进程是否是正常退出,如果是,才有意义
代码中关心的问题:
如何判断进程是否是正常退出:取出status中的低7位--status & 0x7f
如何从status中取出退出码:取出status中的低16位中的高8位--(status>>8) & 0xff
获取status实例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>//wait&waitpid
int main()
{
pid_t cpid=fork();
if(cpid < 0)
{
perror("fork error");
}else if(cpid==0)
{
printf("i am child process!\n");
sleep(5);
exit(99);
}
int status,ret;
while((ret=waitpid(-1,&status,WNOHANG))==0){
printf("现在子进程还没退出...\n");
sleep(2);
}
if(ret<0){
perror("fork error");
return -1;
}
if((status & 0x7f)== 0){
printf("%d 子进程退出了! 返回值是:%d\n",ret,(status>>8)&0xff);
}else{
printf("子进程是异常退出的!\n");
}
sleep(10000000);
return 0;
}
异常退出实例:
2.程序替换
2.1替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
其实就是加载一个新的程序到内存中,将指定进程的pcb页表映射信息进行修改,让其调度管理新的程序运行。
2.2替换函数
其实有六种以exec开头的函数,统称exec函数:
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...); //int execlp("ls","ls","-l",NULL)
int execle(const char *path, const char *arg, ...,char *const env[]);//int execle("/bin/ls","ls","-l",NULL,env);
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 env[]);
返回值:
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值。
2.3execl使用实例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc,char *argv[],char *env[])
{
printf("本来运行程序的起始~\n");
execlp("ls","ps",NULL);
printf("本来程序结束\n");
}
原程序在运行到execlp函数时发生了替换,执行了ls程序,原程序不再执行。
2.4 minishell实现
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
int main()
{
while(1)
{
printf("【user@host ~】$ ");
fflush(stdout);//刷新标准输出缓冲区
char cmd[1024]={0};
fgets(cmd,1023,stdin);//获取键盘输入的一行数据
cmd[strlen(cmd)-1]='\0';//取出行尾的换行字符
int argc=0;
char *argv[32]={NULL};
char *ptr=cmd;
argv[argc++]=strtok(cmd," ");//字符串分割函数,以指定字符作为间隔符对字符串进行分割
while((argv[argc]=strtok(NULL," "))!=NULL)
{
argc++;
}
if(strcmp(argv[0],"cd")==0)
{
chdir(argv[1]); //改变工作路径, 工作路径改到哪里,哪里就是当前所在目录
continue;
}
pid_t child_pid=fork();
if(child_pid<0)
{
perror("fork error");
continue;
}else if(child_pid==0){
//子进程进行程序替换,执行指令程序
execvp(argv[0],argv);
perror("execvp error");
exit(-1);
}
wait(NULL);//每一个子进程运行完毕后,才能开始捕捉下一个输入进行操作
}
return 0;
}