一、进程的终止方式
-
五种正常终止的方式:
从main返回 | 在main函数内执行return语句 |
调用exit | 调用exit函数。此函数由ISO C定义,其操作包括调用各终止处理程序(终止处理程序再调用atexit函数时登记),然后关闭所有标准I/O流等。因为ISO C并不处理文件描述符、 多进程(父、子进程)以及作业控制,所以这一定义对UNIX系统而言是不完整的 |
调用_exit或_Exit | |
最后一个线程从其启动例程返回 | |
从最后一个线程调用pthread_exit |
-
异常终止的3种方式:
调用abort | 调用abort。它产生SIGABRT信号,所以是下一种异常终止的一种特 |
接到一个信号 | 当进程接收到某个信号时。信号可由进程本身(如调用abort函数)、其他进程和内核产生。例如,进程引用地址空间之外的存储单元,或者除以0,内核就会为该进程产生相应的信号 |
最后一个线程对取消请求做出响应 |
- 不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等等
进程终止状态
- 对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于exit、_exit、_Exit,实现这一点的方法是,将其退出状态作为参数传送给函数。在异常终止情况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态( termination status)
- 在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态
子进程在父进程之前终止,父进程如何获取子进程的终止状态?
- 内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到有关信息。这种信息至少包括进程ID、该进程的终止状态、以反该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储器,关闭其所有打开文件
二、exit、_exit、_Exit
#include <stdlib.h>
void exit(int status);
void _Exit(int status);
#include <unistd.h>
void _exit(int status);
- 3个函数都用于正常终止一个程序
- _exit和_Exit立即进入内核。exit则先执行一些清理处理(例如关闭所有由内核打开的描述符),再返回内核
参数可取得值
- 0:表示进程正常结束
- 1~255:表示进程出错结束
C标准指定了两个常量也可以传递给exit作为参数使用:
- EXIT_SUCCESS:成功退出
- EXIT_FAILURE:出错退出
//stdio.h中 #define EXIT_SUCCESS 0 #define EXIT_FAILURE 1
三、进程终止状态
- exit、_exit、_Exit这三个函数都带一个整型参数,称为终止状态(或退出状态)
未定义的进程终止状态
- 如果程序符合下面的某一条件,那么就说这个进程的终止状态是未定义的:
- ①若调用这些函数时不带终止状态
- ②main执行了一个无返回值的return语句
- ③main没有声明返回类型为整型
- 案例:执行下面没有返回值的main函数,其终止码是随机的
#include <stdio.h> main() { printf("hello, world\n"); }
- 如果使用gcc的1999 ISO C标准,就会提示警告信息
- 如果main的返回类型为整型,并且main执行到最后一条语句时返回(隐式返回),那么该进程的终止状态是0
- main函数(仅限main函数)调用return和调用exit是等价的。例如,下面都返回0
exit(0); //等价于return(0);
四、return和exit区别
概念一:
- 如果main函数返回一个整型以及用exit代替return,对某些C编译程序和UNIX lint(1)程序而言会产生不必要的警告信息,因为这些编译程序并不了解main中的exit与return语句的作用相同
概念二:
- 避开这种警告信息的一种方法是:在main中使用return语句而不是exit。但是这样做的结果是不能用UNIX 的grep实用程序来找出程序中所有的exit调用
概念三:
- 另外一个解决方法是将main说明为返回void而不是int,然后仍旧调用exit。这也避开了编译程序的警告,但从程序设计角度看却 并不正确,而且会产生其他的编译编辑警告,因为main的返回类型应当是带符号的整型
- ISO C和POSIX.1定义main返回整型
五、atexit()函数
#include <stdlib.h>
int atexit(void (*func)(void));
//参数:函数指针
//返回值:成功返回0,失败返回非0
- 功能与特点:
- 这个函数用于登记终止处理程序
- atexit先登记的终止函数后执行,后登记的先执行
- 一个函数可以被登记多次
- 什么是终止处理程序?
- 程序结束时,main函数结束之后还可以调用登记函数
- ISO C规定,一个进程可以登记多至32个函数
- atexit()与exit()的关系:
- atexit()来登记终止处理程序
- 然后exit()自动调用atexit()登记的这些终止处理程序
- 根据ANSI C和POSIX.1,exit首先调用各终止处理程序,然后关闭(通过fclose)所有的打开流。POSIX.1扩展ISO C标准,它说明,如若程序调用exec函数族中的任一函数,则将清除所有已安装的终止处理程序
- 注意,内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式或隐式地(调用exit)调用_exit。进程也可非自愿地由一个信号使其终止
进程的开启与终止流程图
演示案例
#include <stdio.h> #include <stdlib.h> static void my_exit1(void); static void my_exit2(void); int main(void) { if (atexit(my_exit2) != 0) perror("can’t register my_exit2"); if (atexit(my_exit1) != 0) perror("can’t register my_exit1"); if (atexit(my_exit1) != 0) perror("can’t register my_exit1"); printf("main is done\n"); return(0); } static void my_exit1(void){ printf("first exit handler\n"); } static void my_exit2(void){ printf("second exit handler\n"); }
六、on_exit()函数
#include <stdlib.h>
int on_exit(void (*function)(int , void *), void *arg);
//返回值:成功返回0;否则返回非零值
- on_exit()与atexit()函数相同,都是用于登记终止处理程序
- 特点也与atexit()函数相同
参数:
- 参数1:进程终止处理函数(参数1为调用on_exit()函数进程的退出码,参数2为附加信息,为void*类型)
- 参数2:传递给on_exit()参数1所绑定的函数的第2个参数
演示案例
- 子进程绑定两个on_exit函数。父进程等待子进程结束
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> void On_exit1(int status,void *arg) { printf("%s:child exit,exit code is %d\n",(char*)arg,status); } void On_exit2(int status,void *arg) { printf("%s:child exit,exit code is %d\n",(char*)arg,status); } int main() { pid_t pid; int status; if((pid=fork())==-1){ perror("fork"); exit(1); }else if(pid==0){ printf("I am child,pid=%d\n",getpid()); sleep(1); char buff1[]="On_exit1 function"; char buff2[]="On_exit2 function"; on_exit(On_exit1,buff1); on_exit(On_exit2,buff2); exit(2); }else{ printf("I am father,pid=%d\n",getpid()); } if(wait(&status)<=0){ perror("wait"); exit(2); } printf("wait success,status code :%d\n",WEXITSTATUS(status)); exit(0); }
七、僵死进程案例
演示案例
- 子进程先结束,但是父进程处于sleep状态,因此子进程就变为僵死进程了
#include<stdio.h> #include<unistd.h> #include<stdlib.h> int main() { pid_t pid; if((pid=fork())==-1) perror("fork"); else if(pid==0) { printf("child_pid pid=%d\n",getpid()); exit(0); } sleep(3); //子进程已经结束了,但是父进程没有对子进程进行任何处理,wait、waitpid之类的 system("ps"); exit(0); }
- 更改上面的案例,在父进程调用wait之前和调用wait之后都执行一次system("ps")。可以看到父进程之后wait处理子进程之后,子进程的僵死状态消失了
#include<stdio.h> #include<unistd.h> #include<stdlib.h> int main() { pid_t pid; if((pid=fork())==-1) perror("fork"); else if(pid==0) { printf("child_pid pid=%d\n",getpid()); exit(0); } sleep(3); printf("****before wait:\n"); system("ps"); wait(NULL); printf("****after wait:\n"); system("ps"); exit(0); }
八、父进程在子进程终止之前终止案例
- 让子进程sleep(2),父进程sleep(1),使父进程先结束。子进程之后就被init进程领养了
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
if((pid=fork())<0){
perror("frok");
exit(1);
}else if(pid==0){
printf("I am child,myPid=%d,ppid=%d\n",getpid(),getppid());
sleep(2);
printf("I am child,myPid=%d,Ppid=%d\n",getpid(),getppid());
exit(0);
}else{
printf("I am father,myPid=%d\n",getpid());
sleep(1);
exit(0);
}
}