@[TOC]
前言:
进程退出
三种场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止 CREASH
- 进程通过 return或者 exit将进程退出码返还给父进程,同时自动刷新缓冲区
- return 只有在main函数中具有终止进程的作用,其它都是作为返还值
- exit()在任何地方都可以终止进程的作用, EXIT_SUCCESS and EXIT_FAILURE
- _exit()也可以终止进程,但是不会刷新缓冲区
- 程序一但CREASH--崩溃,其退出码毫无意义,但是可以通过某种操作获得CREASH的原因
退出码
类似错误码,不同的值代表不用意义
进程等待
等待的原因
- 通过获取子进程的退出信息,得知子进程的执行结果,以便回收子进程资源
- 可以保证:时序问题,子进程先退出,父进程后退出---孤儿问题
- 进程退出会先进入“僵尸状态",会造成内存泄漏问题,也不能通过万能的 kill -9杀死进程,因此需要通过父进程waitpid()释放子进程的资源。
阻塞与非阻塞等待
- 父进程等待子进程是不做任何事情,为阻塞等待
- 父进程等待子进程时,通过重复wait轮询,不断获取子进程的执行状态
hang住
OS中,进程因为等待某些资源,而卡住的情形即阻塞等待
而WNOHANG即非阻塞等待
waitpid
pid_t waitpid(pid_t pid, int *status, int options);
返回值
- 进程退出,返回id,但是不能保证进程是否正常退出,还是CRESASH
- 进程没有退出,返回0,----与非阻塞等待有关系
- 等待进程失败,返回-1
pid_t pid
pid=-1,等待任意一个子进程pid>0,等待与pid想等的id进程
int *status
- status是一个输出型参数,与子进程退出信息强相关
- 因为status指向对象是一个int类型,即有32个比特位,因此通过低16位来反映进程的退出信息,通过第0~6位反映信号,通过 第8~15位反映退出码
宏:WEXITSTATUS获得退出码,但是退出的3种情形都会返回id,防止退出码失去意义,可以通过 宏:WIFEXITED来判断是否是正常退出,还是异常退出
WIFEXITED:正常退出返回 true,否则返回falseWEXITSTATUS:若WIFEXITED为真即正常退出,返回退出码
int status=0; 37 pid_t ret= waitpid(-1,&status,0); 38 if(ret>0) 39 { 40 printf("ret =%d\n",ret); 41 //正常运行, 42 if(WIFEXITED(status)) 43 { 44 45 printf("exit code :%d\n",WEXITSTATUS(status)); 46 } 47 else{ 48 printf("error get a singal :%d\n",status&0x7f); 49 50 } 51 } 52 // printf("father wait: %d,success. status exit code:%d,status exit signal %d\n",ret,(status>>8)&0x ff,status&0x7f);
options
options=0:默认是阻塞等待options=WNOHANG:非阻塞等待
int main()
{
pid_t id =fork();
if(id==0)
{
int cnt =10;
while(cnt)
{
printf("child[%d]is running;cnt = %d\n",getpid(),cnt);
--cnt;
sleep(1);
}
exit(EXIT_SUCCESS);
}
// sleep(10);
printf("father begin\n");
int status=0;
while(1)
{
pid_t ret= waitpid(id,&status,WNOHANG);
if(ret>0) //子进程退出,等待成功
{
//正常运行,
if(WIFEXITED(status))
{
printf("exit code :%d\n",WEXITSTATUS(status));
}
else//异常退出
{
printf("error get a singal :%d\n",status&0x7f);
}
break;
}else if(ret==0)//子进程未退出
{
printf("do father thing \n ");
}
else
{
printf(" wait failure\n");
break;
}
sleep(1);
}
// printf("father wait: %d,success. status exit code:%d,status exit signal %d\n",ret,(status>>8)&0xff,status&0x7f);
printf("father end\n");
进程替换
- 子进程与父进程默认共享代码,但如果我们想执行一个“全新的程序”,可以通过进程替换的方式:将新程序的代码和数据加载到子进程的代码和数据区,这样子进程就可以完成这个新的进程。
- 进程替换发生了“写时拷贝",父子进程不共享同一代码
- 命令行上执行各种指令,本质上都是bash通过子进程,将指令文件的代码与数据加载到子进程来完成的,这可以很大程度上保护了bash,OS也是如此
- C/C++的程序运行,本质上也是通过加载器,将代码和数据加载到子进程中即进程替换
- 进程替换通过 系统调用函数:exe*函数,公用6个
生成多个可执行文件
替换函数
替换函数依据不同需求,有多重形式,但是功能是一样的
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
execl
int execl(const char *path, const char *arg, ...);
返回值
因为新的程序会替换原先子进程代码,所以成功时检测返回值是没有意义的,如果替换失败,返回-1,继续执行原先代码
path
可执行文件的路径+ 文件名“./myexe”
arg
- 类似命令行参数,是一个argv指针数组,但是用列表的形式传参,内部会封装对参数的处理细节。
- 必须要以NULL结尾
execl(...“ls”,“-al”,NULL)
execv
int execv(const char *path, char *const argv[]);
返回值
同execl
path
同execl
argv
- 传入一个指针数组,数组内容是命令行参数
execlp
int execlp(const char *file, const char *arg, ...);
同execl类似,唯一的区别是,因为环境变量的存在,进程会依据PATH自己寻找到相应的文件
execvp
int execvp(const char *file, char *const argv[]);
类似execv,唯一的区别是,因为环境变量的存在,进程会依据PATH自己寻找到相应的文件
execle
int execle(const char *path, const char *arg, ...,char *const envp[]);
execve
这个函数是系统调用函数,其它函数都是通过execve封装的库函数
int execve(const char *path, char *const argv[], char *const envp[])
直接打开与程序替换
直接打开是:建立一个新的进程
程序替换是:覆盖子进程原有的代码和数据
简单的shell
子进程执行的新程序一般是第三方指令,即bin目录下的指令
而对于内键的指令如:cd,pwd指令,需要父进程自己执行
#include <iostream>
#include <string.h>
#include <string>
#include <unistd.h>
#include <vector>
#include <wait.h>
#include <sys/types.h>
using namespace std;
#define MAX 100
int main()
{
string s;
while(1)
{
printf("[New_Young@VM-12-17-centos minshel$] ");
s.clear();
//获取一行字符
getline(cin,s);
//分割字符串
const char * sep=" ";
// s的c_str()函数返回的是一个常量指针,strok需要一个指针a
//
char *command= new char[s.size()+1];
strcpy(command,s.c_str());
char *argv[MAX]={0};
argv[0]= strtok(command,sep);
int i=1;
while( argv[i]=strtok(NULL,sep) )
{
++i;
}
printf("begin:\n");
// 检测是否为内键指令
if(strcmp(argv[0],"cd")==0)
{
if(argv[1]!=NULL)chdir(argv[1]);
continue;
}
if(fork()==0)
{
execvp(argv[0],argv);
printf(" failure\n");
exit(1);
}
int ret= waitpid(-1,&status,0);
if(ret>0)
{
if( WIFEXITED(status))
{
printf("exit code: %d \n",WEXITSTATUS(status));
}else{
printf("exit signal: %d\n",status&0x7f);
}
}else{
printf(" wait failure\n");
}
printf("end\n");
return 0;
}