进程终止 linux

进程终止

通常,进程会以两种情况的之一结束:调用 exit 函数退出或从 main 函数返回。每个进程都有退出值(exit code):一个返回给父进程的数字。一个进程退出值就是程序调用 exit 函数的参数,或者 main 函数的返回值。

进程也可能由于信号的出现而异常结束。例如,之前提到的 SIGBUS,SIGSEGV 和 SIGFPE 信号的出现会导致进程结束。其它信号也可能显式结束进程。当用户在终端按下 Ctrl+C 时会发送一个 SIGINT 信号给进程。SIGTERM 信号由 kill 命令发送。这两个信号的默认处理方式都是结束进程。进程通过调用 abort 函数给自己发送一个 SIGABRT 信号,导致自身中止运行并且产生一个 core file。最强有力的终止信号是 SIGKILL,它会导致进程立刻终止,而且这个信号无法被阻止或被程序自主处理。 这里任何一个信号都可以通过指定一个特殊选项,由 kill 命令发送;例如,要通过发送 SIGKILL 中止一个出问题的进程,只需要执行下面的命令(这里pid是目标进程号):

% kill -KILL pid 

要从程序中发送信号,使用 kill 函数。第一个参数是目标进程号。第二个参数是要发送的信号;传递 SIGTERM 可以模拟 kill 命令的默认行为。例如,您可以利 用kill 函数,像这样从父进程中结束子进程的运行(这里 child_pid 包含的是子进程的进程号):

% kill (child_pid, SIGTERM); 

需要包含 <sys/types.h> 和 <signal.h> 头文件才能在程序中调用 kill 函数。

根据习惯,程序的退出代码可用来确认程序是否正常运行。返回值为0表示程序正确运行,而非零的返回值表示运行过程出现错误。在后一种情况下,返回值可能表示了特定的错误含义。通常应该遵守这个约定,因为 GNU/Linux 系统的其它组件会假设程序遵循这个行为模式。例如,当使用 &&(逻辑与)或 (逻辑或)连接多个程序的时候,shell 根据这个假定判断逻辑运算的结果。因此,除非有错误发生,您都应该在 main 结束的时候明确地返回0。 对于多数 shell 程序,最后运行的程序的返回值都保存在特殊环境变量 $? 中。在下面这个例子中 ls 命令被执行了两次,并且每次运行之后会输出返回值。第一次,ls 运行正常并且返回0。第二次,ls 运行出现一个错误(因为命令行参数指定的文件不存在)且返回了一个非零值。

% ls 
bin coda etc lib misc nfs proc sbin usr 
boot dev home lost+found mnt opt root tmp var 
% echo $? 
0 
% ls bogusfile 
ls: bogusfile: No such file or directory 
% echo $? 
1 

需要注意的是,尽管 exit 函数的参数类型是 int,而 main 函数返回值也是 int 类型,Linux 不会为返回值保留32位长度。实际上,您应该只使用0 到 127 之间的数值作为退出代码。大于128的退出代码有特殊的含义——当一个进程由于一个信号而结束运行,它的退出值就是128加上信号的值。

等待进程结束

如果您输入并且运行了代码代码3.4的 fork 和 exec 示例程序,您可能已经发现,ls 程序的输入很多时候出现在“主程序”结束之后。这是因为子进程,也就是运行 ls 命令的进程,是相独立于主进程被调度的。因为 Linux 是一个多任务操作系统,两个进程看起来是并行执行的,而且您无法猜测 ls 程序会在主程序运行之前还是之后获取运行的机会。 不过,在某些情况下,主程序可能希望暂停运行以等待子进程完成任务。可以通过 wait 族系统调用实现这一功能。这些函数允许您等待一个进程结束运行,并且允许父进程得到子进程结束的信息。wait 族系统调用一共有四个函数;通过选择不同的版本,您可以选择从退出进程得到信息的多少,也可以选择关注某个特定子进程的退出。

wait 系统调用

这一族函数中,最简单的是 wait。它会阻塞调用进程,直到某一个子进程退出(或者出现一个错误)。它通过一个传入的整型指针参数返回一个状态码,从而可以得到子进程的退出信息。例如,WEXITSTATUS 宏可以提取子进程的退出值。 您可以用 WIFEXITED 宏从一个子进程的返回状态中检测该进程是正常结束(利用 exit 函数或者从 main 函数返回)还是被没有处理的信号异常终止。对于后一种情况,可以用 WTERMSIG 宏从中得到结束该进程的信号。 这里还是 fork 和 exec 示例代码中的 main 函数。这一次,父进程通过调用 wait 等待子进程(也就是运行 ls 命令的进程)退出。

int main () 
{ 
  int child_status; 

  /* 传递给 ls 命令的参数列表 */ 
  char* arg_list[] = { 
    "ls", /* argv[0], 程序的名称 */ 
    "-l", 
    "/", 
    NULL /* 参数列表必须以 NULL 结束 */ 
  };
 
  /* 产生一个子进程运行 ls 命令。忽略返回的子进程 ID。*/ 
  spawn ("ls", arg_list); 

  /* 等待子进程结束。*/ 
  wait (&child_status); 
  if (WIFEXITED (child_status)) 
    printf ("the child proces exited normally, with exit code   %d\n", WEXITSTATUS (child_status)); 
  else 
    printf ("the child process exited abnormally\n"); 

  return 0; 
} 

Linux 还提供了一些相似的系统调用;其中一些更具弹性,而一些提供了与退出的子进程相关的更多的信息。wait3 函数可以获取退出进程的CPU占用情况,而 wait4 函数允许您通过更多参数指定等待的进程。

僵尸进程

如果一个子进程结束的时候,它的父进程正在调用 wait 函数,子进程会直接消失,而退出代码则通过 wait 函数传递给父进程。但是,如果子进程结束的时候,父进程并没有调用 wait,则又会发生什么?它是不是简单地就消失了呢?不,因为如果这样,它退出时返回的相关信息——譬如它是否正常结束,以及它的退出值——会直接丢失掉。在这种情况下,子进程死亡的时候会转化为一个僵尸进程。 一个僵尸进程是一个已经中止而没有被清理的进程。清理僵尸子进程是父进程的责任。wait 函数会负责这个清理过程,所以您不必在等待一个子进程之前检测它是否正在运行。假设,一个进程创建了一个子进程,进行了另外一些计算,然后调用了 wait。如果子进程还没有结束,这个进程会在 wait 调用中阻塞,直到子进程结束。如果子进程在父进程调用 wait 之前结束,子进程会变成一个僵尸进程。当父进程调用 wait,僵尸子进程的结束状态被提取出来,子进程被删除,并且 wait 函数立刻返回。 如果父进程不清理子进程会如何?它们会作为僵尸进程,一直被保留在系统中。代码3.6里的程序在产生一个立刻结束的子进程之后然后休眠一分钟退出而不清理子进程。

代码 3.7 (zombie.c)制作一个僵尸进程

#include <stdlib.h> 
#include <sys/types.h> 
#include <unistd.h> 

int main () 
{ 

  pid_t child_pid; 
  /* 创建一个子进程 */ 
  child_pid = fork (); 
  if (child_pid > 0) { 
    /*这是父进程。休眠一分钟。 */ 
    sleep (60); 
  } 
  else { 
    /*这是子进程。立刻退出。 */ 
    exit (0); 
  } 

  return 0; 
} 

试着把这个程序编译成一个名为 make-zombie 的程序。运行这个程序;在它还在运行的同时,用下列命令在另外一个窗口列出系统中的进程:

% ps -e -o pid,ppid,stat,cmd 

该命令会列出进程ID、父进程ID、进程状态和进程命令行。观察结果,发现除了父进程 make-zombie 之外,还有一个 make-zombie 出现在列表中。这是子进程;注意它的父进程ID就是第一个 make-zombie 进程的ID。子进程被标记为 <defunct> 而且它的状态代码为Z,表示僵尸(Zombie)。 如果 make-zombie 进程退出而没有调用wait会出现什么情况?僵尸进程会停留在系统中吗?不——试着再次运行ps,您会发现两个make-zombie进程都消失了。当一个程序退出,它的子进程被一个特殊进程继承,这就是 init 进程。Init 进程总以进程ID 1运行(它是 Linux 启动后运行的第一个进程)。init 进程会自动清理所有它继承的僵尸进程。

异步清理子进程

如果您创建一个子进程只是简单的调用 exec 运行其它程序,在父进程中立刻调用 wait 进行等待并没有什么问题,只是会导致父进程阻塞等待子进程结束。但是,很多时候您希望在子进程运行的同时,父进程继续并行运行。怎么才能保证能清理已经结束运行的子进程而不留下任何僵尸进程在系统中浪费资源呢? 一种解决方法是让父进程定期调用 wait3 或 wait4 以清理僵尸子进程。在这种情况调用 wait 并不合适,因为如果没有子进程结束,这个调用会阻塞直到子进程结束为止。然而,您可以传递 WNOHANG 标志给 wait3 或 wait4 函数作为一个额外的参数。如果设定了这个标志,这两个函数将会以非阻塞模式运行——如果有结束的子进程,它们会进行清理;否则会立刻返回。第一种情况下返回值是结束的子进程ID,否则返回0。 另一种更漂亮的解决方法是当一个子进程结束的时候通知父进程。有很多途径可以做到这一点;在第五章“进程间通信”介绍了这些方法,不过幸运的是Linux 利用信号机制替您完成了这些。当一个子进程结束的时候,Linux 给父进程发送 SIGCHLD 信号。这个信号的默认处理方式是什么都不做;这也许是为什么之前您忽略了它的原因。 因此,一个简单的清理结束运行的子进程的方法是响应 SIGCHLD 信号。当然,当清理子进程的时候,如果需要相关信息,一个很重要的工作就是保存进程退出状态,因为一旦用 wait 清理了进程,就再也无法得到这些信息了。代码3.7中就是一个利用 SIGCHLD 信号处理函数清理子进程的程序代码。

代码 3.7 (sigchld.c)利用 SIGCHLD 处理函数清理子进程

#include <signal.h> 
#include <string.h> 
#include <sys/types.h> 
#include <sys/wait.h> 

sig_atomic_t child_exit_status; 

void clean_up_child_process (int signal_number) 
{ 
  /* 清理子进程。*/ 
  int status; 
  wait (&status); 
  /* 在全局变量中存储子进程的退出代码。*/ 
  child_exit_status = status; 
} 

int main () 
{ 
  /* 用 clean_up_child_process 函数处理 SIGCHLD。*/ 
  struct sigaction sigcihld_action; 
  memset (&sigchld_action, 0, sizeof (sigchld_action)); 
  sigcihld_action.sa_handler = &clean_up_child_process; 
  sigaction (SIGCHLD, &sigchld_action, NULL); 

  /* 现在进行其它工作,包括创建一个子进程。*/ 
  /* ... */ 

  return 0; 
} 

注意信号处理函数中将进程退出代码保存到了全局变量中,从而可以在主程序中访问它。因为这个变量在信号处理函数中被赋值,我们将它声明为 sig_atomic_t 类型。

http://sourceforge.net/apps/trac/elpi/wiki/AlpProcessTermination

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值