资讯专栏INFORMATION COLUMN

Linuxc - 僵尸进程的产生和清除

IntMain / 837人阅读

摘要:僵尸进程的产生和清除什么是僵尸进程僵尸进程是指它的父进程已经退出父进程没有等待调用它,而该进程之后没有进程接受,就成为僵尸进程,也就是进程。程序中避免僵尸进程的产生父进程调用。

linuxc僵尸进程的产生和清除 什么是僵尸进程

  僵尸进程是指它的父进程已经退出(父进程没有等待(调用wait/waitpid)它),而该进程dead之后没有进程接受,就成为僵尸进程,也就是(zombie)进程。

僵尸进程是怎么样产生

  一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。

  在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸。

  如果他的父进程没安装SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。

  但是如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程.

如下代码,子进程先于父进程退出,在父进程执行的5s内,子进程将为僵尸:

#include     
#include     
#include     
#include     
    
int main() {   
    //子进程的pid    
    int c_pid;   
    int pid;   
    
    if ((pid = fork())) {   
        //父进程    
        c_pid = pid;   
        printf("The child process is %d
", c_pid);   
        sleep(5);   
        exit(0);   
    } else {   
        //子进程    
        printf("I "m a child.
");   
        exit(0);   
    }   
}
如何快速找出僵尸程序

打开终端并输入下面命令:

ps aux | grep Z

会列出进程表中所有僵尸进程的详细内容。

怎么干掉这些僵尸程序 用 SIGCHLD信号来杀死僵尸进程

正常情况下我们可以用 SIGKILL 信号来杀死进程,但是僵尸进程已经死了, 你不能杀死已经死掉的东西。 因此你需要输入的命令应该是:

ps -ef | grep pid 首先查看僵尸进程的父进程pid,输出结果的第三列ppid就是父进程的pid

kill -s SIGCHLD ppid

将这里的 pid 替换成父进程的进程 id,这样父进程就会删除所有以及完成并死掉的子进程了。

程序中避免僵尸进程的产生

父进程调用wait/waitpid。

父进程先于子进程退出,子进程自动托管到init进程。

fork()两次,调用孙子进程。

绑定SIGCHLD信号处理函数,在信号处理函数中调用wait。

绑定SIGCHLD信号处理函数SIG_IGN。(不推荐)

父进程调用wait/waitpid

父进程调用waitpid()等函数来接收子进程退出状态。

#include     
#include     
#include     
#include     
#include     
    
int main() {   
    //子进程的pid    
    int c_pid;   
    int pid;   
    
    if ((pid = fork())) {   
        //父进程    
        c_pid = pid;   
        printf("The child process is %d
", c_pid);   
        //阻塞等待子进程    
        int status;   
        if ((pid = wait(&status)) != -1 && pid == c_pid) {   
            //成功回收子进程    
            printf("The child exit with %d
", WEXITSTATUS(status));   
            fflush(stdin);   
        }  
        printf("Now , The child has been exit , and I will sleep.
");   
        sleep(20);   
        exit(0);   
    } else {   
        //子进程    
        printf("I "m a child.
");   
        sleep(5);   
        exit(0);   
    }   
}  
父进程先exit,子进程托管到init进程

父进程先结束,子进程则自动托管到Init进程(pid = 1)。

#include     
#include     
#include     
#include     
#include     
    
int main() {   
    //子进程的pid    
    int c_pid;   
    int pid;   
    
    if ((pid = fork())) {   
        //父进程      
        printf("I"m father id = %d.The child process is %d
", getpid(), pid);   
        exit(0);   
    } else {   
        //子进程    
        printf("I "m a child.
");   
        sleep(5);   
        exit(0);   
    }   
}  
fork()两次,调用孙子进程

父进程一次fork()后产生一个子进程随后立即执行waitpid(子进程pid, NULL, 0)来等待子进程结束,然后子进程fork()后产生孙子进程随后立即exit(0)。这样子进程顺利终止(父进程仅仅给子进程收尸,并不需要子进程的返回值),然后父进程继续执行。这时的孙子进程由于失去了它的父进程(即是父进程的子进程),将被转交给Init进程托管。于是父进程与孙子进程无继承关系了,它们的父进程均为Init,Init进程在其子进程结束时会自动收尸,这样也就不会产生僵尸进程了。

#include     
#include     
#include     
#include     
#include     
    
int main() {   
    //子进程的pid    
    int c_pid;   
    int pid;   
    
    if ((pid = fork())) {   
        //父进程    
        c_pid = pid;   
        printf("I"m father id = %d.The child process is %d
", getpid(), c_pid);   
        //阻塞等待子进程    
        int status;   
        if ((pid = wait(&status)) != -1 && pid == c_pid) {   
            //成功回收子进程    
            printf("The child exit with %d
", WEXITSTATUS(status));   
            fflush(stdin);   
        } 
        exit(0);   
    } else {
        if ((pid = fork())) {
            //子进程 
            printf("I "m a child pid = %d. my child pid = %d. exit.
", getpid(), pid);
            exit(0);
        } else {
            //孙子进程
            sleep(20);
            printf("I "m a grandson pid = %d. exit
", pid);
            exit(0);
        }
           
    }   
} 
绑定SIGCHLD信号处理函数,在信号处理函数中调用wait

阻塞等待,那么父进程就无法做其他事;

父进程退出,在创建守护进程时候会有,但是我们并不想让父进程退出;

fork()两次,孤儿进程不易由我们程序来管理;

man wait,查看NOTES章节,可以找到:子进程退出的时候,会发送SIGCHLD信号,默认的POSIX不响应

所以设置处理SIGCHLD信号的函数,可以用于异步回收fork子进程。

注意:信号产生触发信号处理函数时,会中断某些函数,例如poll、select、epoll等。

​ 详情见《unix网络编程》5.8小节,POSIX信号处理

#include     
#include     
#include     
#include     
#include     
    
void sig_chld(int num) {    
    pid_t pid;
    int stat;
    while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) // 循环处理完所有的信号
        printf("child %d terminated
", pid);
    return;
}   
    
int main() {   
    //子进程的pid    
    int c_pid;   
    int pid;   
    
    signal(SIGCHLD, sig_chld);   
    
    if ((pid = fork())) {   
        //父进程    
        c_pid = pid;   
        printf("The child process is %d
", c_pid);   
    
        //父进程不用等待,做自己的事情吧~    
        for (int i = 0; i < 10; i++) {   
            printf("Do parent things.
");   
            sleep(1);   
        }   
    
        exit(0);   
    } else {   
        //子进程    
        printf("I "m a child.
");   
        sleep(2);   
        exit(0);   
    }   
}  
绑定SIGCHLD信号处理函数SIG_IGN

不推荐使用,因为POSIX标准中未规定SIG_IGN,会破坏程序的可移植性。

#include     
#include     
#include     
#include     
#include      
    
int main() {   
    //子进程的pid    
    int c_pid;   
    int pid;   
    
    signal(SIGCHLD,SIG_IGN); 
    
    if ((pid = fork())) {   
        //父进程    
        c_pid = pid;   
        printf("The child process is %d
", c_pid);   
    
        //父进程不用等待,做自己的事情吧~    
        for (int i = 0; i < 10; i++) {   
            printf("Do parent things.
");   
            sleep(1);   
        }   
    
        exit(0);   
    } else {   
        //子进程    
        printf("I "m a child.
");   
        sleep(2);   
        exit(0);   
    }   
}  

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/63173.html

相关文章

  • Linuxc - 关于Linux应用层定时器

    摘要:关于的应用层定时器使用定时器的目的无非是为了周期性的执行某一任务,或者是到了一个指定时间去执行某一个任务。程序可以将成员设为来防止定时器到期时产生信号。启动一个定时器所创建的定时器并未启动。如同,用于指定当前的定时器到期时间。 linuxc-关于Linux的应用层定时器 使用定时器的目的无非是为了周期性的执行某一任务,或者是到了一个指定时间去执行某一个任务。要达到这一目的,一般有两个常...

    dantezhao 评论0 收藏0
  • Linuxc - syslog使用方法小结

    摘要:使用方法小结已被许多日志函数采纳,它用在许多保护措施中任何程序都可以通过纪录事件。该文件由不同程序或消息分类的单个条目组成,每个占一行。通常应广播到所有用户告警,当前状态必须立即进行纠正。所有已登录的用户都将收到信息。 syslog使用方法小结 Syslog已被许多日志函数采纳,它用在许多保护措施中——任何程序都可以通过syslog 纪录事件。Syslog可以纪录系统事件,可以写到一个...

    madthumb 评论0 收藏0
  • linuxc - linuxc串口操作

    摘要:如下的,分别表示的是串口和串口。忽略桢错误和奇偶校验错。如果既没有设置也没有设置,将有奇偶校验错或桢错误的字符视为。允许输出产生奇偶信息以及输入的奇偶校验启用同位产生与侦测。 linux串口操作 man termios:http://www.man7.org/linux/man... 串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是RS-232-C接口...

    Jason 评论0 收藏0
  • Linuxc - 未命名管道-pipe

    摘要:未命名管道详情见环境高级编程小节管道试系统的最古老形式,也是最常用的。现在,某些系统提供全双工管道,但是为了最佳的可移植性,我们决定预先假定系统支持全双工管道。管道只能在具有公共祖先的两个进程之间使用。等所执行的命令都是协同进程。 linuxc-未命名管道-pipe --详情见《unix环境高级编程》15.2小节 管道试UNIX系统IPC的最古老形式,也是最常用的IPC。 pipe管道...

    ACb0y 评论0 收藏0
  • Linuxc - 进程间通信-XSI-IPC

    摘要:进程间通信有三种消息队列信号量共享存储器详情见环境高级编程小节命令查看删除某个函数标识符和键,每个结构都有一个非负整数的标识符。也用来使多个进程在同一上汇聚。创建一个可以是项目路径,或者其他 linuxc-进程间通信-XSI-IPC ​ 有三种:消息队列、信号量、共享存储器 ​ 详情见《unix环境高级编程》15.6小节 命令 ipcs:查看ipc ipcrm:删除某个ip...

    huangjinnan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<