Linux(编程-进程):01:---进程的终止(exit、atexit)

一、进程的终止方式

  • 五种正常终止的方式
从main返回 在main函数内执行return语句
调用exit 调用exit函数。此函数由ISO C定义,其操作包括调用各终止处理程序(终止处理程序再调用atexit函数时登记),然后关闭所有标准I/O流等。因为ISO C并不处理文件描述符、 多进程(父、子进程)以及作业控制,所以这一定义对UNIX系统而言是不完整的
调用_exit或_Exit Linux(编程-进程):01:---进程的终止(exit、atexit)
最后一个线程从其启动例程返回 Linux(编程-进程):01:---进程的终止(exit、atexit)
从最后一个线程调用pthread_exit Linux(编程-进程):01:---进程的终止(exit、atexit)
  • 异常终止的3种方式
调用abort 调用abort。它产生SIGABRT信号,所以是下一种异常终止的一种特
接到一个信号 当进程接收到某个信号时。信号可由进程本身(如调用abort函数)、其他进程和内核产生。例如,进程引用地址空间之外的存储单元,或者除以0,内核就会为该进程产生相应的信号
最后一个线程对取消请求做出响应 Linux(编程-进程):01:---进程的终止(exit、atexit)
  • 不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等等

进程终止状态:

  • 对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于exit、_exit、_Exit,实现这一点的方法是,将其退出状态作为参数传送给函数。在异常终止情 况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态( termination status)。
  • 在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态

如果父进程在子进程之前终止,则将如何呢?

  • 对于其父进程已 经终止的所有进程,它们的父进程都改变为init进程。我们称这些进程由init进程领养。其操作过程大致是:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程ID就更改为1 init进程的ID )。这种处理方法保证了每个进程有一个父进程

一个由init进程领养的进程终止时会发生什么 ?它会不会变成一 个僵死进程?

  • 对此问题的回答是“否”,因为init被编写成只要有一个子进程终止,init就会调用一个wait函数取得其终止状态。这样也就防止了在系统中有很多僵死进程。当提及“一个init的子进程”时,这指的是init直接产生的进程,或者是其父 进程已终止,由init 领养的进程。

如果子进程在父进程之前终止,父进程如何获取子进程的终止状态?

  • 内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用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);
  • _exit和_Exit立即进入进入内核。exit则先执行一些清理处理,再返回内核
  • 3个函数都用于正常终止一个程序

三、进程终止状态

  • exit、_exit、_Exit这三个函数都带一个整型参数,称为终止状态(或退出状态)

未定义的进程终止状态,概念如下:

  • ①若调用这些函数时不带终止状态②main执行了一个无返回值的return语句③main没有声明返回类型为整型

则该进程的终止状态是未定义的

  • 案例:执行下面没有返回值的main函数,其终止码是随机的
#include <stdio.h>
main()
{
    printf("hello, world\n");
}

Linux(编程-进程):01:---进程的终止(exit、atexit) 如果使用gcc的1999 ISO C标准,就会提示警告信息

  • 如果main的返回类型为整型,并且main执行到最后一条语句时返回(隐式返回),那么该进程的终止状态是0

main函数返回一个整型值与该值调用exit是等价的。例如:

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

1.功能

  • 这个函数用于登记终止处理程序

什么是终止处理程序?

  • 程序结束时,程序还需要调用的函数
  • ISO C规定,一个进程可以登记多至32个函数

2.atexit()与exit()的关系

关系:

  • atexit()来登记终止处理程序,然后exit()自动调用atexit()登记的这些终止处理程序
  • 根据ANSI  C和POSIX.1,exit首先调用各终止处理程序,然后关闭(通过fclose)所有的打开流。POSIX.1扩展ISO C标准,它说明,如若程序调用exec函数族中的任一函数,则将清楚所有已安装的终止处理程序
  • 注意,内核使程序执行的唯一方法是调用一个exec函数。进程自愿终止的唯一方法是显式 或隐式地(调用exit)调用_exit。进程也可非自愿地由一个信号使其终止

Linux(编程-进程):01:---进程的终止(exit、atexit)

3.特点

  • atexit先登记的终止函数后执行,后登记的先执行
  • 一个函数可以被登记多次

4.演示案例

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");
}

Linux(编程-进程):01:---进程的终止(exit、atexit)