APUE——进程终止

abort 与return exit的关系
进程终止
linux进程控制函数–fork,exec,exit,wait,sleep

1. 进程终止方式

进程有下面5种正常终止方式:

  1. 在main函数内执行return语句。这等效于调用exit。

  2. 调用exit函数。此函数有ISO C定义,其操作包括调用各终止处理程序(终止处理程序在调用atexit函数时登记),然后关闭所有标准I/O流等。

  3. 调用_exit或_Exit函数。ISO C定义_Exit,其目的是为进程提供一种无需运行终止处理程序或信号处理程序而终止的方法。对标准I/O流是否进行冲洗,这取决于实现。在UNIX系统中,_Exit和_exit是同义的,并不清洗标准I/O流。_exit函数由exit调用,它处理UNIX特定的细节。

在大多数UNIX系统实现中,exit(3)是标准C库中的一个函数,而_exit(2)则是一个系统调用。

  1. 进程的最后一个线程在其启动例程中执行返回语句。但是,该线程的返回值不会用作进程的返回值。当最后一个线程从其启动例程返回时,该进程以终止状态0返回。

  2. 进程的最后一个线程调用pthread_exit函数。在这种情况下,进程终止状态总是0,这与传送给pthread_exit的参数无关。

三种异常终止方式如下:

  1. 调用abort。它产生SIGABRT信号,这是下一种异常终止的特例。

  2. 当进程接收到某些信号时。信号可由进程自身(例如调用abort函数)、其他进程或内核产生。

  3. 最后一个线程对“取消”(cancellation)请求作出响应。按系统默认,“取消”以延迟方式发生:一个线程要求取消另一个线程,一段时间之后,目标线程终止。

2. exit函数

有三个函数用于正常终止一个程序:_exit和_Exit立即进入内核,exit则先执行一些清理处理(包括调用执行各终止处理程序,关闭所有标准I/O流等),然后进入内核。

#include <stdlib.h>
void exit( int status ); //会先调用atexit函数,执行清理函数,然后清理IO,将buffer里的打印出去
void _Exit( int status );//
#include <unistd.h>
void _exit( int status );

注意这里status,为退出时的状态,与errno不同!!!,后者为系统调用出错的值!strerror(errno)

exit函数总是执行一个标准I/O库的清理关闭操作:为所有打开流调用fclose函数。这会造成所有缓冲的输出数据都被冲洗(写到文件上)。
exit(0)== return(0);0表示正常结束,通过exit的返回值,检查是否正确返回
在这里插入图片描述

/*exit.c*/ 
#include <stdio.h> 
#include <stdlib.h> 

int main() 
{ 
      printf("Using exit...\n"); 
      printf("This is the content in buffer"); 
      exit(0); 
}
输出Using exit...
This is the content in buffer

/*exit.c*/ 
#include <stdio.h> 
#include <stdlib.h> 

int main() 
{ 
      printf("Using exit...\n"); 
      printf("This is the content in buffer"); 
      _exit(0); 
}
输出Using exit...

3. atexit函数

按照ISO C规定,一个进程可以登记多达32个函数,这些函数将由exit自动调用。我们称这些函数为终止处理程序(exit handler),并调用atexit函数来登记这些函数。

#include <stdlib.h>
int atexit( void (*func)(void) ); // void pthread_cleanup_push(void (*rtn)(void *), void *arg);
返回值:若成功则返回0,若出错则返回非0

注意
线程退出清理函数和进行退出清理函数:

  1. 一个有参数,一个无参数
  2. 都是在栈上,都是起到注册作用,pthread_cleanup_push是在pop或者exit,cancel时候调用,而atexit是在exit
void pthread_cleanup_push(void (*rtn)(void *), void *arg);//有入参,为arg
int atexit( void (*func)(void) );//无入参

根据ISO C和POSIX.1,exit首先调用各终止处理程序,然后按需要多次调用fclose,关闭所有打开流。POSIX.1扩展了ISO C标准,它指定如若程序调用exec函数族中的任一函数,则将清除所有已安装的终止处理程序。图7-1显示了一个C程序是如何启动的,以及它可以终止的各种方式

在这里插入图片描述

[root@localhost apue]# cat prog7-2.c
#include "apue.h"

static void my_exit1(void);
static void my_exit2(void);

int
main(void)
{
        if(atexit(my_exit2) != 0)
                err_sys("can't register my_exit2");

        if(atexit(my_exit1) != 0)
                err_sys("can't register my_exit1");

        if(atexit(my_exit1) != 0)
                err_sys("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");
}

[root@localhost apue]# ./prog7-2
main is done
first exit handler
first exit handler
second exit handler
  1. 在说明fork函数时,显而易见,子进程是在父进程调用fork后生成的。上面又说了子进程将其终止状态返回给父进程。但是如果父进程在子进程之前终止,则将如何呢?其回答是:对于父进程已经终止的所有进程,它们的父进程都改变为init进程。我们称这些进程由init进程领养。其操作过程大致如下:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止进程的子进程,如果是,则将该进程的父进程ID更改为1(init进程的ID)。这种处理方法保证了每个进程都有一个父进程。

  2. 另一个我们关心的情况是如果子进程在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?对此问题的回答是:内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括进程ID、该进程的终止状态、以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储区,关闭其所有打开文件。在UNIX术语中,一个已经终止,但是其父进程尚未对其进行善后处理(使用wait获取终止子进程的有关信息,释放它仍占用的资源)的进程称为僵死进程(zombie)(更多关于僵尸进程可参考:http://www.cnblogs.com/bettercoder/p/3501086.html)。ps(1)命令将僵死进程的状态打印为Z。如果编写一个长期运行的程序,它调用fork产生了很多子进程,那么除非父进程等待取得子进程的终止状态,否则这些子进程终止后就会变成僵死进程。

  3. 最后一个要考虑的问题是:一个由init进程领养的进程终止时会发生什么?它会不会变成一个僵死进程?对此问题的回答是:“否”,因为init被编写成无论何时只要有一个子进程终止,init就会调用wait函数取得其终止状态。这样也就防止了在系统中有很多僵死进程。当提及“一个init的子进程”时,这指的可能是init直接产生的进程,也可能是其父进程已终止,由init领养的进程。

猜你喜欢

转载自blog.csdn.net/weixin_44537992/article/details/106123730