1.进程的终止分3种情况
1)代码执行完,结果正确(结果看退出码,结果正确退出码为0)
2)代码执行完,结果不正确(退出码非0)
3)代码没执行完,进程异常终止。
2.正常退出的3种方式
1)exit :执行的3个步骤 :执行用户通过atexit或on_exit定义的清理函数--->刷新输出缓存--->调用_exit
2)main函数的return
3)_exit
3.异常退出的方式
1)ctrl+c 2)abort()函数
4.exit和_exit函数
exit:
#include
#include
#include
int main(void){
printf("exit");
exit(1);
}
运行结果:
_exit:
#include
#include
#include
int main(void){
printf("_exit");
_exit(2);
}
运行结果:
echo $?命令:上一次执行程序的退出码(只能是0~255)
可以看出调用exit和_exit之间的不同,exit刷新了缓冲区,_exit没有。
再来看一个实例:
#include
#include
#include
void fun1(){
printf("This is fun1\n");
}
void fun2(int status,void * arg){
printf("This is on_exit:%d\n",status);
printf("arg=%s\n",(char *)arg);
}
int main(void){
char *str="on_exit";
atexit(fun1);
on_exit(fun2,(void *)str);
printf("This is exit\n");
exit(2);
}
运行结果:
在调用exit的第一步,调用了退出处理函数atexit和on_exit
简单介绍一下on_exit
函数原型: int on_exit(void (*function)(int , void *), void *arg);
on_exit()用来设置一个程序正常结束前调用的函数。当程序通过调用exit()或从main中返回时,参数function所指定的函数会先被调用,然后才真正由exit()结束程序。参数arg指针会传给参数function函数。
同样的代码,调用_exit退出之后的结果
显而易见,在调用_exit时,并没有调用退出处理函数,也没有刷新缓存,此时能打印出main函数结果,是因为用了\n才刷新了缓冲区,若没有,结果与上面实例结果相同。
1.僵尸进程
僵尸进程的成因:
僵尸状态是一种比较特殊的状态,当子进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程。僵尸进程会以终止状态保存在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,并且父进程没有读取子进程的退出代码,子进程会进入Z状态。
僵尸进程的危害:
进程的退出状态会一直维持下去,如果父进程一直不读取,子进程会一直维持在Z状态,所以PCB也一直要维护,也会造成内存泄漏。而且,一旦进入僵尸状态,无法进行回收,kill -9也杀不死此进程。同时,父进程也读取不到子进程的进度状态,无法知道结果对错。
解决方案:父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息
2.进程等待的方法
两种方式:wait和waitpid
1)三种情况:
1>如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获取子进程退出信息。
2>如果在任何时候调用wait/waitpid时,子进程存在且在正常运行,父进程可能阻塞在wait处。
3>如果不存在子进程,报错并返回。
2)wait函数
1>调用之前,要加#include
2>函数原型 pid_t wait(int *status)
3>返回值:成功返回被等待进程的pid,失败返回-1
4>参数:输出型参数,获取子进程的退出状态(最低字节表示进程是否正常终止,倒数第二低字节表示进程的退出码)
5>注意:wait的调用次数要和子进程数匹配
写一个简单的实例
#include
#include
#include
#include
#include
int main (void){
pid_t pid;
pid = fork();
if(pid==-1) perror("fork"),exit(1);
else if (pid==0){ //子进程
printf("child will die!%d \n",getpid());
sleep(20);
exit(5);
}
else { //父进程
int s;
pid_t id=wait(&s);
if( id ==-1) perror("wait"),exit(1);
if(id>0&&(s&0Xff)==0){ //正常退出
printf("child:%d exit code is %d\n",id,(s>>8)&0xff);
}
else { //异常退出
printf("%d:child exit error,sig code %d \n",getpid(),(s&0x7f));
}
}
}
运行结果:
解释一下:通过fork创建出子进程,先执行子进程,子进程通过exit正常退出,但父进程未读取子进程的退出状态,子进程成了僵尸进程,父进程通过wait回收子进程,wait的参数是输出型参数,最低字节表示进程是否正常终止,倒数第二低字节表示进程的退出码,可以看出父进程的状态,获取退出码。
3)waitpid
1>调用之前,要先加#include
2>函数原型:pid_t waitpid(pid_t pid, int *status, int options);
3>返回值:正常返回waitpid回收的子进程id,如果设置了WNOHANG,而调用waitpid发现没有进程可回收,返回0,如果调用出错,返回-1
4>参数:pid : 1}pid > 0 , 等待指定子进程死亡
2}pid = 0 ,等待本进程组的任何一个子进程死亡
3}pid = -1 , 等待本进程的任何一个子进程死亡
4}pid <-1 , 等待|pid|进程组的任何一个子进程死亡
status:输出型参数,介绍几个宏
1}WIFEXITED(status):返回真表示子进程正常退出
2}WEXITSTATUS(status):若非零,提取子进程的退出码
3}WIFSIGNALED(status):返回真则被信号打断
4}WTERMSIG(status):返回具体的退出信号
options:若为WNOHANG,为非阻塞式等待,若pid指定的子进程没有结束,则waitpid函数返回0,不予以等待。若正常结束,则返回该子进程的id。若为0,表示阻塞式等待,等待子进程的退出。
举个例子:
#include
#include
#include
#include
#include
int main(void){
pid_t pid;
int i=0;
pid =fork();
if(pid ==-1) perror("fork"),exit(1);
else if (pid ==0){ //子进程
for( i=0;i<5;i++){
printf("c");
fflush(stdout);
}
sleep(5);
exit(-1);
}
else { //父进程
int s;
pid_t p = waitpid(-1,&s,WNOHANG);//非阻塞式等待
if ( p==-1) perror("waitpid"),exit(1);
printf("p=%d \n",p);
if(WIFEXITED(s)){ //表示子进程正常退出
printf("exit code %d \n",WEXITSTATUS(s));//获取退出码
}
else if(WIFSIGNALED(s)){ //表示子进程被信号打断
printf("exit sig = %u\n",WTERMSIG(s)); //打印退出信号
}
while(1){
printf("Y");
fflush(stdout);
sleep(1);
}
}
}
运行结果:
解释一下:fork出子进程之后,父子进程交替执行,非阻塞式等待,指定的子进程并未结束,父进程不予以等待,返回0,子进程被信号打断结束。
4)wait与waitpid的区别
用法基本相同,但waitpid能够只针对某个子进程进行等待,同时支持非阻塞式等待。