【C语言】创建、避免和使僵尸进程终止的方法

【概要】我们将介绍如何使用由制药公司安布雷拉开发的T病毒创建僵尸,当然这只是个玩笑。实际上,我们将在C语言中介绍如何创建、避免和结束僵尸进程的方法。

【什么是僵尸进程】僵尸进程指的是父进程不管子进程,导致子进程无法结束的情况。虽然严格的定义可能有所区别,但只要能形象地理解僵尸进程,就可以了。

###【生態環境】

[vagrant@vagrant-centos65 ~]$ cat /etc/redhat-release 
CentOS release 6.5 (Final)

###【制作僵尸的方法】
####首先需要一个制作僵尸的资源

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

int
main(int argc, char *argv[])
{
    pid_t pid;

    // 引数チェック
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <command> <arg>\n", argv[0]);
        exit(1);
    }

    // forkして子プロセスを作成する
    // fork以降の処理は親プロセスと子プロセスの2つのプロセスが同時に実行されている
    pid = fork();
    if (pid < 0) {
        fprintf(stderr, "fork(2) failed\n");
        exit(1);
    }

    // 子プロセスのforkの戻り値は0
    if (pid == 0) { /* 子プロセスが実行する処理 */
        execl(argv[1], argv[1], argv[2], NULL);
        /* execl()は成功したら戻らないので、戻ったらすべて失敗 */
        perror(argv[1]);
        exit(99);
    }
    // 親プロセスのforkの戻り値は子プロセスのプロセスID
    else {          /* 親プロセスが実行する処理 */
        // ゾンビの生存時間は30秒
        // ここでは30秒にしていますが、while(1)などの無限ループの場合はずっとゾンビが存在することになります
        sleep(30);
        printf("child (PID=%d) finished;\n", pid);
        exit(0);
    }
}

请注意制作僵尸的方法。

选项一:
参数1:执行命令的完整路径(此次为/bin/echo)
参数2:针对参数1的命令的参数(此次为This is zombie)

如果按照下述方式执行,将会导致提示符在30秒内无法返回,并且在同一终端中无法确认僵尸进程的存在。
(如果您增加一个终端来确认僵尸进程,以下方法也是可行的)

[vagrant@vagrant-centos65 tmp]$ gcc -o zombie ./zombie.c 
[vagrant@vagrant-centos65 tmp]$ ./zombie /bin/echo "This is zombie"
This is zombie // → この状態で30秒待たなければならない、他の作業ができないなどの影響がある
child (PID=24579) finished; // → 30秒後に表示される
[vagrant@vagrant-centos65 tmp]$ 

因此,在命令的末尾加上&,以在后台进程中执行。
在存在僵尸的30秒内,还要用ps命令确认存在僵尸。
由于ps命令的结果显示为defunct,可以确认是僵尸。

[vagrant@vagrant-centos65 tmp]$ ./zombie /bin/echo "This is zombie" &
[1] 24601
[vagrant@vagrant-centos65 tmp]$ This is zombie
// → エンター押さないとプロンプトが返ってこないから、エンター押す
[vagrant@vagrant-centos65 tmp]$ 
[vagrant@vagrant-centos65 tmp]$ ps aux | grep defunct | grep -v grep
vagrant  24602  0.0  0.0      0     0 pts/0    Z    23:06   0:00 [echo] <defunct>
[vagrant@vagrant-centos65 tmp]$ 
[vagrant@vagrant-centos65 tmp]$ child (PID=24602) finished; // → 30秒後に表示される
// → エンター押さないとプロンプトが返ってこないから、エンター押す
[1]+  Done                    ./zombie /bin/echo "This is zombie"
[vagrant@vagrant-centos65 tmp]$ 

### 【避免僵尸进程的方法】
#### 方法① fork()后要使用waitpid()
当执行fork()函数后,父进程应该使用waitpid()函数来捕捉子进程的结束。
避免产生僵尸进程是父进程的责任。

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

int
main(int argc, char *argv[])
{
    pid_t pid;

    // 引数チェック
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <command> <arg>\n", argv[0]);
        exit(1);
    }

    // forkして子プロセスを作成する
    // fork以降の処理は親プロセスと子プロセスの2つのプロセスが同時に実行されている
    pid = fork();
    if (pid < 0) {
        fprintf(stderr, "fork(2) failed\n");
        exit(1);
    }

    // 子プロセスのforkの戻り値は0
    if (pid == 0) { /* 子プロセスが実行する処理 */
        execl(argv[1], argv[1], argv[2], NULL);
        /* execl()は成功したら戻らないので、戻ったらすべて失敗 */
        perror(argv[1]);
        exit(99);
    }
    // 親プロセスのforkの戻り値は子プロセスのプロセスID
    else {          /* 親プロセスが実行する処理 */
        int status;

        waitpid(pid, &status, 0);
        sleep(30);
        printf("child (PID=%d) finished; ", pid);
        if (WIFEXITED(status))
            printf("exit, status=%d\n", WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            printf("signal, sig=%d\n", WTERMSIG(status));
        else
            printf("abnormal exit\n");
        exit(0);
    }
}

#####【执行结果】
可以通过ps命令确认不存在僵尸进程。

[vagrant@vagrant-centos65 tmp]$ gcc -o zombie_avoid1 ./zombie_avoid1.c 
[vagrant@vagrant-centos65 tmp]$ ./zombie_avoid1 /bin/echo "This is zombie" &
[1] 24619
[vagrant@vagrant-centos65 tmp]$ This is zombie

[vagrant@vagrant-centos65 tmp]$ 
[vagrant@vagrant-centos65 tmp]$ ps aux | grep defunct | grep -v grep
[vagrant@vagrant-centos65 tmp]$ 
[vagrant@vagrant-centos65 tmp]$ child (PID=24620) finished; exit, status=0

[1]+  Done                    ./zombie_avoid1 /bin/echo "This is zombie"
[vagrant@vagrant-centos65 tmp]$ 

方法2:双重fork
通过父进程创建子进程,然后通过子进程创建孙进程。
然后,通过结束子进程,孙进程不会变成僵尸进程,因为没有来自孙进程的父进程。
确认即使父进程和子进程已经结束,孙进程仍然存在。

プロセスプロセスの終了時間親プロセス30秒経過したら終了子プロセス即終了孫プロセス60秒経過したら終了
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int
main(int argc, char *argv[])
{
    pid_t pid;

    // 引数チェック
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <command> <arg>\n", argv[0]);
        exit(1);
    }

    // forkして子プロセスを作成する
    // fork以降の処理は親プロセスと子プロセスの2つのプロセスが同時に実行されている
    pid = fork();
    if (pid < 0) {
        fprintf(stderr, "fork(2) failed\n");
        exit(1);
    }

    // 子プロセスのforkの戻り値は0
    if (pid == 0) { /* 子プロセスが実行する処理 */
        pid_t pid_child;
        pid_child = fork();
        if (pid_child < 0) {
            fprintf(stderr, "child fork(2) failed\n");
            exit(1);
        }

        if (pid_child == 0) { /* 孫プロセスが実行する処理 */
            execl(argv[1], argv[1], argv[2], NULL);
            /* execl()は成功したら戻らないので、戻ったらすべて失敗 */
            perror(argv[1]);
            exit(99);
        } else { /* 子プロセスが実行する処理 */
            printf("grandchild (PID=%d) finished; ", pid_child);
            exit(0);
        }
    }
    // 親プロセスのforkの戻り値は子プロセスのプロセスID
    else {          /* 親プロセスが実行する処理 */
        int status;

        waitpid(pid, &status, 0);
        sleep(30);
        printf("child (PID=%d) finished; ", pid);
        if (WIFEXITED(status))
            printf("exit, status=%d\n", WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            printf("signal, sig=%d\n", WTERMSIG(status));
        else
            printf("abnormal exit\n");
        exit(0);
    }
}

【执行结果】
为了确认子进程的存在,这次我们使用了 sleep 而不是 echo。

[vagrant@vagrant-centos65 tmp]$ gcc -o zombie_avoid2 ./zombie_avoid2.c
[vagrant@vagrant-centos65 tmp]$ ./zombie_avoid2 /bin/sleep 60 &
[1] 25674
[vagrant@vagrant-centos65 tmp]$ grandchild (PID=25676) finished; 
[vagrant@vagrant-centos65 tmp]$ 
[vagrant@vagrant-centos65 tmp]$ 
// ゾンビは存在していない
[vagrant@vagrant-centos65 tmp]$ ps aux | grep defunct | grep -v grep
// 孫プロセスは存在している
[vagrant@vagrant-centos65 tmp]$ ps aux | grep 25676 | grep -v grep
vagrant  25676  0.0  0.1 100924   620 pts/0    S    01:29   0:00 /bin/sleep 60
[vagrant@vagrant-centos65 tmp]$ child (PID=25675) finished; exit, status=0

[1]+  Done                    ./zombie_avoid2 /bin/sleep 60
[vagrant@vagrant-centos65 tmp]$ 
// 親プロセスが終了しても孫プロセスは存在している
[vagrant@vagrant-centos65 tmp]$ ps aux | grep 25676 | grep -v grep
vagrant  25676  0.0  0.1 100924   620 pts/0    S    01:29   0:00 /bin/sleep 60
[vagrant@vagrant-centos65 tmp]$
// 60秒経過すると孫プロセスも終了する 
[vagrant@vagrant-centos65 tmp]$ ps aux | grep 25676 | grep -v grep
[vagrant@vagrant-centos65 tmp]$ 

使用sigaction方法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>

static void detach_children(void);
static void noop_handler(int sig);

int
main(int argc, char *argv[])
{
    pid_t pid;

    // 引数チェック
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <command> <arg>\n", argv[0]);
        exit(1);
    }

    detach_children();

    // forkして子プロセスを作成する
    // fork以降の処理は親プロセスと子プロセスの2つのプロセスが同時に実行されている
    pid = fork();
    if (pid < 0) {
        fprintf(stderr, "fork(2) failed\n");
        exit(1);
    }

    // 子プロセスのforkの戻り値は0
    if (pid == 0) { /* 子プロセスが実行する処理 */
        execl(argv[1], argv[1], argv[2], NULL);
        /* execl()は成功したら戻らないので、戻ったらすべて失敗 */
        perror(argv[1]);
        exit(99);
    }
    // 親プロセスのforkの戻り値は子プロセスのプロセスID
    else {          /* 親プロセスが実行する処理 */
    	printf("child (PID=%d) finished;\n", pid);
    	// sleepではシグナルが補足されてしまうため、whileで無限ループする
    	while(1);
        exit(0);
    }
}

static void
detach_children(void)
{
    struct sigaction act;

    act.sa_handler = noop_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_RESTART | SA_NOCLDWAIT;
    if (sigaction(SIGCHLD, &act, NULL) < 0) {
        printf("sigaction() failed: %s", strerror(errno));
    }
}

static void
noop_handler(int sig)
{
    ;
}

【执行结果】

[vagrant@vagrant-centos65 tmp]$ gcc -o zombie_avoid3 ./zombie_avoid3.c
[vagrant@vagrant-centos65 tmp]$ ./zombie_avoid3 /bin/echo "This is zombie" &
[1] 25895
[vagrant@vagrant-centos65 tmp]$ child (PID=25896) finished;
This is zombie

[vagrant@vagrant-centos65 tmp]$ 
// ゾンビは存在していない
[vagrant@vagrant-centos65 tmp]$ ps aux | grep defunct | grep -v grep
// zombie_avoid3のプロセスは存在している
[vagrant@vagrant-centos65 tmp]$ ps aux | grep zombie_avoid3 | grep -v grep
vagrant  25895  102  0.0   3924   448 pts/0    R    02:42   0:13 ./zombie_avoid3 /bin/echo This is zombie
[vagrant@vagrant-centos65 tmp]$ 
[vagrant@vagrant-centos65 tmp]$ kill 25895
[vagrant@vagrant-centos65 tmp]$ 
[1]+  Terminated              ./zombie_avoid3 /bin/echo "This is zombie"
[vagrant@vagrant-centos65 tmp]$ 

【如何使僵尸进程安息】以下是关于如何结束由zombie.c创建的僵尸进程的方法。

僵尸无法攻击并击败其他僵尸个体(子进程)。

[vagrant@vagrant-centos65 tmp]$ ./zombie /bin/echo "This is zombie" &
[1] 25932
[vagrant@vagrant-centos65 tmp]$ This is zombie

[vagrant@vagrant-centos65 tmp]$ 
[vagrant@vagrant-centos65 tmp]$ ps aux | grep -e zombie -e defunct | grep -v grep
vagrant  25932  0.0  0.0   3920   372 pts/0    S    02:47   0:00 ./zombie /bin/echo This is zombie
vagrant  25933  0.0  0.0      0     0 pts/0    Z    02:47   0:00 [echo] <defunct>
[vagrant@vagrant-centos65 tmp]$ 
// ゾンビをkill
[vagrant@vagrant-centos65 tmp]$ kill 25933
// ゾンビをkillしたが、ゾンビは存在している
[vagrant@vagrant-centos65 tmp]$ ps aux | grep -e zombie -e defunct | grep -v grep
vagrant  25932  0.0  0.0   3920   372 pts/0    S    02:47   0:00 ./zombie /bin/echo This is zombie
vagrant  25933  0.0  0.0      0     0 pts/0    Z    02:47   0:00 [echo] <defunct>
[vagrant@vagrant-centos65 tmp]$ 
[vagrant@vagrant-centos65 tmp]$ child (PID=25933) finished;

[1]+  Done                    ./zombie /bin/echo "This is zombie"
[vagrant@vagrant-centos65 tmp]$ 

只要攻击该僵尸的头部(即父进程),就能够击败它。

[vagrant@vagrant-centos65 tmp]$ ./zombie /bin/echo "This is zombie" &
[1] 25965
[vagrant@vagrant-centos65 tmp]$ This is zombie

[vagrant@vagrant-centos65 tmp]$ 
[vagrant@vagrant-centos65 tmp]$ ps aux | grep -e zombie -e defunct | grep -v grep
vagrant  25965  0.0  0.0   3920   372 pts/0    S    02:50   0:00 ./zombie /bin/echo This is zombie
vagrant  25966  0.0  0.0      0     0 pts/0    Z    02:50   0:00 [echo] <defunct>
[vagrant@vagrant-centos65 tmp]$ 
// 親プロセスをkillする
[vagrant@vagrant-centos65 tmp]$ kill 25965
[vagrant@vagrant-centos65 tmp]$ 
[1]+  Terminated              ./zombie /bin/echo "This is zombie"
[vagrant@vagrant-centos65 tmp]$
// 親プロセスもゾンビも存在していない 
[vagrant@vagrant-centos65 tmp]$ ps aux | grep -e zombie -e defunct | grep -v grep
[vagrant@vagrant-centos65 tmp]$ 

双重fork方式创建的子进程并非僵尸进程,因此您可以通过kill命令来终止它。

【参考书】
《普通的Linux编程 第2版》 从Linux的机制学习gcc编程的正路

【参考网站】
避免僵尸进程的双重fork方法

广告
将在 10 秒后关闭
bannerAds