【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
通过父进程创建子进程,然后通过子进程创建孙进程。
然后,通过结束子进程,孙进程不会变成僵尸进程,因为没有来自孙进程的父进程。
确认即使父进程和子进程已经结束,孙进程仍然存在。
#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方法