备案 控制台
学习
实践
活动
专区
工具
TVP
写文章
专栏首页 txp玩Linux Linux系统进程编程之回收子进程(三)
2 0

海报分享

Linux系统进程编程之回收子进程(三)

在上一篇文章中,我们介绍了“僵尸进程”和“孤儿进程”的基本用法。但是我们还没有意识到出现了“僵尸进程”的危害。“僵尸进程”是一个早已死亡的进程,而且“僵尸子进程”已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,但在进程表(processs table)中仍占了一个位置(slot),记载该进程的退出状态信息供其他进程收集。但是由于进程表的容量是有限的,所以,僵尸进程还是占用一定系统的内存资源,影响系统的性能,而且如果其数目太多,还会导致系统瘫痪(举一个非常不恰当的例子,这就好比一部丧尸电影,里面的正常人,因为得了某种病毒,而成为了丧尸,没有了意识,它不在消耗地球这个大系统的可吃的食物资源了(因为它饿不死),但是丧尸病毒会爆发,越来越多的人会被感染,而成了丧尸,由于不会被饿死,所以,丧尸就会一直存在,也就占用土地资源,要用特殊方法来消灭他们)。那么说了这么多,我们该怎么样来回收Linux系统的僵尸进程呢?来,让我们下面细说:

一、第一招:使用wait()函数来回收:

1、我们先来看这个函数的原型和它所包含的头文件(在Linux系统下,使用man 手册来查看它的具体用法:man 2 wait):

   #include <sys/types.h>
   #include <sys/wait.h>
   pid_t wait(int *wstatus);

说明:wait的参数wstatus。wstatus用来返回子进程结束时的状态,父进程通过wait得到wstatus后就可以知道子进程的一些结束状态信息。返回值是子进程的ID,当前父进程有可能有多个子进程,wait函数阻塞直到其中一个子进程结束wait就会返回,wait的返回值就可以用来判断到底是哪一个子进程本次被回收了:

 wait(): on success, returns the process ID of the terminated child; on error, -1 is returned.

2、代码示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>  
#include <sys/wait.h>
#include <stdlib.h>
 int main(void)
pid_t pid = -1;
pid_t ret = -1;
int  wstatus = -1;
pid = fork();
if (pid > 0)
    // 父进程
    //sleep(1);
    printf("parent.\n");
    ret = wait(&wstatus);
    printf("子进程已经被回收,子进程pid = %d.\n", ret);
    printf("子进程是否正常退出:%d\n", WIFEXITED(wstatus));
    printf("子进程是否非正常退出:%d\n", WIFSIGNALED(wstatus));
    printf("正常终止的终止值是:%d.\n", WEXITSTATUS(wstatus));
else if (pid == 0)
    // 子进程
    printf("child pid = %d.\n", getpid());
    return 51;
    //exit(0);
    perror("fork");
    return -1;
return 0;

演示结果:

说明:上面的WIFEXITED、WIFSIGNALED、WEXITSTATUS这几个函数宏用来获取子进程的退出状态:WIFEXITED宏用来判断子进程是否正常终止(return、exit、_exit退出);WIFSIGNALED宏用来判断子进程是否非正常终止(被信号所终止);WEXITSTATUS宏用来得到正常终止情况下的进程返回值的,这里正常终止的值是return 终止后面的51。

   WIFEXITED(wstatus)
          returns true if the child terminated normally, that is, by call‐
          ing exit(3) or _exit(2), or by returning from main().
   WEXITSTATUS(wstatus)
          returns the exit status of the  child.   This  consists  of  the
          least  significant  8 bits of the status argument that the child
          specified in a call to exit(3) or _exit(2) or  as  the  argument
          for a return statement in main().  This macro should be employed
          only if WIFEXITED returned true.
   WIFSIGNALED(wstatus)
          returns true if the child process was terminated by a signal.

3、小结:wait主要是用来回收子进程资源,回收同时还可以得知被回收子进程的(ID和退出状态)。

二、第二招:发送SIGCHILD信号:

1、我们还可以发送SIGCHILD信号来防止产生僵尸进程,当子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号,然后用上面信号处理函数中调用wait来处理僵尸进程,下面是signal()函数的介绍以及代码示例:

SYNOPSIS
   #include <signal.h>
   typedef void (*sighandler_t)(int);
   sighandler_t signal(int signum, sighandler_t handler);
DESCRIPTION
   The  behavior  of signal() varies across UNIX versions, and has also varied historically across different versions of Linux.  Avoid its use: use sigaction(2) instead.  See
   Portability below.
   signal() sets the disposition of the signal signum to handler, which is either SIG_IGN, SIG_DFL, or the address of a programmer-defined function (a "signal handler").
   If the signal signum is delivered to the process, then one of the following happens:
   *  If the disposition is set to SIG_IGN, then the signal is ignored.
   *  If the disposition is set to SIG_DFL, then the default action associated with the signal (see signal(7)) occurs.
   *  If the disposition is set to a function, then first either the disposition is reset to SIG_DFL, or the signal is blocked (see Portability below), and  then  handler  is
      called with argument signum.  If invocation of the handler caused the signal to be blocked, then the signal is unblocked upon return from the handler.
   The signals SIGKILL and SIGSTOP cannot be caught or ignored.
RETURN VALUE
   signal() returns the previous value of the signal handler, or SIG_ERR on error.  In the event of an error, errno is set to indicate the cause.
 #include <stdio.h>
 #include <unistd.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <signal.h>
 6    #include <sys/types.h>  
            #include <sys/wait.h> 7 static void sig_child(int signo);
 9 int main()
11 pid_t pid;
12 //创建捕捉子进程退出信号
13 signal(SIGCHLD,sig_child);
14pid = fork();
15if (pid < 0)
17    perror("fork error:");
18    exit(1);
20else if (pid == 0)     {
21     printf("I am child process,pid id %d.I am exiting.\n",getpid());
22     exit(0);
24 printf("I am father process.I will sleep two seconds\n");
25//等待子进程先退出
26 sleep(2);
27 //输出进程信息
28 system("ps -o pid,ppid,state,tty,command");
29printf("father process is exiting.\n");
30 return 0;
33 static void sig_child(int signo)
35 pid_t        pid;
36 int        stat;
37  //处理僵尸进程
38  while ((pid = wait(&stat) >0)
39         printf("child %d terminated.\n", pid);

演示结果:

三、第三招:使用waitpid函数回收子进程:

1、还是先来看waitpid()函数的原型:

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
The waitpid() system call suspends execution of the calling process until a child specified by pid argument has changed state.  By default, waitpid() waits only for termi‐
   nated children, but this behavior is modifiable via the options argument, as described below.
   The value of pid can be:
   < -1   meaning wait for any child process whose process group ID is equal to the absolute value of pid.
   -1     meaning wait for any child process.
   0      meaning wait for any child process whose process group ID is equal to that of the calling process.
   > 0    meaning wait for the child whose process ID is equal to the value of pid.
   The value of options is an OR of zero or more of the following constants:
   WNOHANG     return immediately if no child has exited.
   WUNTRACED   also return if a child has stopped (but not traced via ptrace(2)).  Status for traced children which have stopped is provided even if this option is not speci‐
               fied.
   WCONTINUED (since Linux 2.6.10)
               also return if a stopped child has been resumed by delivery of SIGCONT.

说明:第三个参数options表示waitpid()函数可以阻塞式或非阻塞式两种工作模式,WNOHANG表示非阻塞式, WUNTRACED 表示阻塞式。第二个参数表示返回的状态;第一个表示选择性来指定返回的子进程的ID:

---pid == -1 等待任一子进程。于是在这一功能方面waitpid与wait等效。

---pid > 0 等待其进程I D与p i d相等的子进程。

---pid == 0 等待其组I D等于调用进程的组I D的任一子进程。换句话说是与调用 者进程同在一个组的进程。

----pid < -1 等待其组I D等于p i d的绝对值的任一子进程

2、代码示例:

   #include <stdio.h>
   #include <unistd.h>
   #include <sys/types.h>  
   #include <sys/wait.h>
   #include <stdlib.h>
   int main(void)
pid_t pid = -1;
pid_t ret = -1;
int status = -1;
pid = fork();
if (pid > 0)
    // 父进程
    sleep(1);
    printf("parent, 子进程id = %d.\n", pid);
    //ret = wait(&status);
    //ret = waitpid(-1, &status, 0);    
    /* -1表示不等待某个特定PID的子进程而是回收任意一个子进程,0表示用默认的方式(阻塞式)来进行等待,返回值ret是本次回收的子进程的PID */
    /*  等待回收PID为pid的这个子进程,如果当前进程并没有一个ID号为pid的子进程,则返回值为-1;如果成功回收了pid这个子进程则返回值为回收的进程的PID  */
    //ret = waitpid(pid, &status, 0);
    /* 非阻塞式    这种表示父进程要【非阻塞式】的回收子进程。
    此时如果父进程执行waitpid时子进程已经先结束等待回收则waitpid直接回收成功,返回值是回收的子进程的PID;
    如果父进程waitpid时子进程尚未结束则父进程立刻返回(非阻塞),但是返回值为0(表示回收不成功)。
    ret = waitpid(pid, &status, WNOHANG);       
    printf("子进程已经被回收,子进程pid = %d.\n", ret);
    printf("子进程是否正常退出:%d\n", WIFEXITED(status));
    printf("子进程是否非正常退出:%d\n", WIFSIGNALED(status));
    printf("正常终止的终止值是:%d.\n", WEXITSTATUS(status));
else if (pid == 0)
    // 子进程
    //sleep(1);
    printf("child pid = %d.\n", getpid());
    return 51;
    //exit(0);
    perror("fork");
    return -1;
return 0;

演示结果:

3、小结:

wait与waitpid区别:

---在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择----项,可使调用者不阻塞。

---waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控----制它所等待的特定进程。

---实际上wait函数是waitpid函数的一个特例。waitpid(-1, &status, 0);

四、总结:

上面对子进程的回收,简单的介绍了一下,要更加深入的理解,还是要自己去亲身写代码体会。上面的源代码链接:https://github.com/1121518wo/linux-/tree/master

文章分享自微信公众号:
txp玩Linux

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者: txp玩Linux
原始发表时间: 2019-11-15
如有侵权,请联系 cloudcommunity@tencent.com 删除。