吉林省 网站建设,手机便宜网站建设,安徽省工程建设监理协会网站,交易猫钓鱼网站制作教学文章目录1.僵尸进程2.产生僵尸进程的原因3.利用 wait 函数销毁僵尸进程4.使用 waitpid 函数销毁僵尸进程1.僵尸进程
进程完成工作后#xff08;执行完 main 函数中的程序后#xff09;应被销毁#xff0c;但有时这些进程将变成僵尸进程#xff0c;占用系统中的重要资源。这…
文章目录1.僵尸进程2.产生僵尸进程的原因3.利用 wait 函数销毁僵尸进程4.使用 waitpid 函数销毁僵尸进程1.僵尸进程
进程完成工作后执行完 main 函数中的程序后应被销毁但有时这些进程将变成僵尸进程占用系统中的重要资源。这种状态下的进程称作“僵尸进程”这也是给系统带来负担的原因之一。我们应该消灭这种进程。当然应掌握正确的方法否则它会死灰复燃。
从下图可以看到父进程的 PID 是 1166子进程的 PID 是 1167。 如果我们使用 kill -9 1167 命令将子进程干掉观察父进程会收到什么信号
从下图可以看到父进程收到了 SIGCHLD 信号子进程变成了僵尸进程。 在 Unix 系统中一个子进程结束了但是他的父进程还活着但该父进程没有调用 wait()/waitpid() 函数来进行额外的处置那么这个子进程就会变成一个僵尸进程。
僵尸进程已经被终止不干活了但是依旧没有被内核丢弃掉因为内核认为父进程可能还需要该子进程的一些信息。作为开发者坚决不允许僵尸进程的存在。
如何干掉僵尸进程
重启电脑。手动地把僵尸进程的父进程干掉僵尸进程就会自动消失。一个进程被终止或者停止时SIGCHLD 信号会被发送给父进程所以对于源码中有 fork() 行为的进程我们应该拦截并处理 SIGCHLD 信号。
#include stdio.h
#include stdlib.h
#include unistd.h
#include signal.h
#include sys/wait.h// 信号处理函数
void sig_usr(int signo)
{int status;switch (signo){case SIGUSR1:printf(收到了SIGUSR1信号进程id%d!\n, getpid()); break;case SIGCHLD:printf(收到了SIGCHLD信号进程id%d!\n, getpid());// waitpid()函数获取子进程的终止状态这样子进程就不会成为僵尸进程了// 第一个参数为-1表示等待任何子进程// 第二个参数保存子进程的状态信息// 第三个参数提供额外选项WNOHANG表示不要阻塞让这个waitpid()立即返回pid_t pid waitpid(-1, status, WNOHANG);if (pid 0) return; // 子进程没结束会立即返回这个数字if (pid -1) return; // 这表示这个waitpid()调用有错误有错误也立即返回出去return; // 走到这里表示成功那也return吧break;}
}int main(int argc, char* const* argv)
{pid_t pid;printf(进程开始执行!\n);// 系统函数第一个参数是个信号第二个参数是个函数指针代表一个针对该信号的捕捉处理函数if (signal(SIGUSR1, sig_usr) SIG_ERR){printf(无法捕捉SIGUSR1信号!\n);exit(1);}if (signal(SIGCHLD, sig_usr) SIG_ERR){printf(无法捕捉SIGCHLD信号!\n);exit(1);}pid fork(); // 创建一个子进程// 要判断子进程是否创建成功if (pid 0){printf(子进程创建失败很遗憾!\n);exit(1);}// 现在父进程和子进程同时开始运行了 for (;;){sleep(1);printf(休息1秒进程id%d!\n, getpid());}printf(再见了!\n);return 0;
}2.产生僵尸进程的原因
向 exit 函数传递的参数值和 main 函数的 return 语句返回的值都会传递给操作系统。而操作系统不会销毁子进程直到把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。也就是说将子进程变成僵尸进程的正是操作系统。
问此僵尸进程何时被销毁呢
答应该向创建子进程的父进程传递子进程的 exit 参数值或 return 语句的返回值。
问如何向父进程传递这些值呢
答操作系统不会主动把这些值传递给父进程。只有父进程主动发起请求函数调用时操作系统才会传递该值。换言之如果父进程未主动要求获得子进程的结束状态值操作系统将一直保存并让子进程长时间处于僵尸进程状态。也就是说父母要负责收回自己生的孩子。
接下来的示例将创建僵尸进程。
#include stdio.h
#include unistd.h
#include sys/types.hint main(int argc, char *argv[])
{pid_t pid fork();if (pid 0) // if Child Process{puts(Hi, I am a child process);}else{// 输出子进程ID。可以通过该值查看子进程状态是否为僵尸进程。printf(Child Process ID: %d\n, pid);// 父进程暂停30秒。如果父进程终止处于僵尸状态的子进程将同时销毁。因此延缓父进程的执行以验证僵尸进程。sleep(30); // Sleep 30 sec.}if (pid 0)puts(End child process);elseputs(End parent process);return 0;
}编译运行
gcc zombie.c -o zombie
./zombie程序开始运行后将在如上所示状态暂停。跳出这种状态前30秒内应验证子进程是否为僵尸进程该验证在其他控制台窗口进行。 可以看出PID 为 1387 的进程状态为僵尸进程Z。另外经过 30 秒的等待时间后PID 为 1386 的父进程和 PID 为 1387 的僵尸子进程同时销毁。
3.利用 wait 函数销毁僵尸进程
如前所述为了销毁子进程父进程应主动请求获取子进程的返回值。
#include sys/wait.hpid_t wait(int *statloc);// 成功时返回终止的子进程ID失败时返回-1调用此函数时如果已有子进程终止那么子进程终止时传递的返回值exit 函数的参数值、main 函数的 return 返回值将保存到该函数的参数所指内存空间。但函数参数指向的单元中还包含其他信息因此需要通过下列宏进行分离
WIFEXITED子进程正常终止时返回“真”true。WEXITSTATUS返回子进程的返回值。
也就是说向 wait 函数传递变量 status 的地址时调用 wait 函数后应编写如下代码
if (WIFEXITED(status)) // 是正常终止的吗
{puts(Normal termination!);printf(Child pass num: %d, WEXITSTATUS(status)); // 那么返回值是多少
}根据上述内容编写如下示例在下面示例中不会再让子进程变成僵尸进程。
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/wait.hint main(int argc, char *argv[])
{int status;// 第11行创建的子进程将在第15行通过main函数中的return语句终止。pid_t pid fork();if (pid 0){return 3;}else{printf(Child PID: %d\n, pid);// 第22行中创建的子进程将在第26行通过调用exit函数终止。pid fork();if (pid 0){exit(7);}else{printf(Child PID: %d\n, pid);// 调用wait函数。之前终止的子进程相关信息将保存到status变量同时相关子进程被完全销毁。wait(status);// 第36行中通过WIFEXITED宏验证子进程是否正常终止。如果正常退出则调用WEXITSTATUS宏输出子进程的返回值。if (WIFEXITED(status))printf(Child send one: %d\n, WEXITSTATUS(status));// 因为之前创建了2个进程所以再次调用wait函数和宏。wait(status);if (WIFEXITED(status))printf(Child send two: %d\n, WEXITSTATUS(status));// 为暂停父进程终止而插入的代码。此时可以查看子进程的状态。sleep(30); // Sleep 30 sec.}}return 0;
}编译运行
gcc wait.c -o wait
./wait输出结果 可以看出此时系统中并没有 PID 为 1497 和 1498 的进程这是因为调用了 wait 函数完全销毁了该子进程。另外两个子进程终止时返回的 333 和 777 传递到了父进程。
这就是通过调用 wait 函数消灭僵尸进程的方法。调用 wait 函数时如果没有已终止的子进程那么程序将阻塞Blocking直到有子进程终止因此要谨慎调用该函数。
4.使用 waitpid 函数销毁僵尸进程
wait 函数会引起程序阻塞还可以考虑调用 waitpid 函数。这是防止僵尸进程的第二种方法也是防止阻塞的方法。
#include sys/wait.hpid_t waitpid(pid_t pid, int *statloc, int options);// 成功时返回终止的子进程ID或0失败时返回-1
// pid等待终止的目标子进程的ID若传递-1则与wait函数相同可以等待任意子进程终止
// statloc与wait函数的statloc参数具有相同含义
// options传递头文件sys/wait.h中声明的常量WNOHANG即使没有终止的子进程也不会进入阻塞状态而是返回0并退出函数下面介绍调用 waitpid 函数的示例。调用 waitpid 函数时程序不会阻塞。
#include stdio.h
#include unistd.h
#include sys/wait.hint main(int argc, char *argv[])
{int status;pid_t pid fork();if (pid 0){// 调用sleep函数推迟子进程的执行。这会导致程序延迟15秒。sleep(15);return 24;}else{// while循环中调用waitpid函数。向第三个参数传递WNOHANG因此若之前没有终止的子进程将返回0。while (!waitpid(-1, status, WNOHANG)){sleep(3);puts(sleep 3sec.);}if (WIFEXITED(status))printf(Child send %d\n, WEXITSTATUS(status));}return 0;
}编译运行
gcc waitpid.c -o waitpid
./waitpid输出结果 可以看出第 222222 行共执行了 555 次。另外这也证明 waitpid 函数并未阻塞。