0
点赞
收藏
分享

微信扫一扫

进程状态-下

僵尸进程

  1. 定义
  • 僵尸进程(Zombie Process)是指在子进程终止后,父进程尚未对其进行回收(获取子进程的退出状态等信息)的进程。在Linux和其他类UNIX系统中,当一个子进程结束运行时,它会留下一个包含一些基本信息(如进程ID、退出状态、资源使用信息等)的进程描述符,这个进程描述符会一直存在,直到父进程调用waitwaitpid等系统调用来获取子进程的退出状态并释放这些资源,此时子进程才真正结束。如果父进程没有及时回收,子进程就会变成僵尸进程。
  1. 产生原因
  • 父进程没有等待子进程结束:在一个进程创建了子进程后,如果父进程没有通过适当的系统调用(如waitwaitpid)来等待子进程退出并回收其资源,子进程结束后就会变成僵尸进程。例如,以下是一个简单的C代码示例,会产生僵尸进程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("子进程正在运行,进程ID为 %d\n", getpid());
        // 子进程退出
        exit(0);
    } else if (pid > 0) {
        // 父进程
        // 父进程不等待子进程结束,直接继续执行其他任务
        while (1) {
            printf("父进程正在运行,进程ID为 %d\n", getpid());
            sleep(1);
        }
    } else {
        // fork失败
        perror("fork");
        return 1;
    }
    return 0;
}

  • 在这个例子中,子进程结束后,父进程没有调用waitwaitpid来回收子进程的资源,所以子进程会变成僵尸进程。
  1. 危害
  • 资源浪费:僵尸进程会占用系统的进程表项(PCB,进程控制块)空间。虽然僵尸进程本身不占用太多的其他系统资源(如内存、CPU等)(这些都会释放),但是大量的僵尸进程会导致进程表被填满,从而使系统无法创建新的进程。因为系统的进程表大小是有限的,当进程表满时,即使系统还有足够的内存和CPU资源,也无法创建新的进程。
  • 可能导致系统异常:如果僵尸进程的父进程是一个长期运行的守护进程,并且不断地产生子进程而不回收,随着时间的推移,可能会使系统出现各种异常情况,如系统响应变慢、服务无法正常启动等。
  1. 如何避免和处理僵尸进程
  • 父进程等待子进程结束:在父进程中使用waitwaitpid系统调用来等待子进程结束并回收其资源。wait函数会阻塞父进程,直到有一个子进程结束,然后获取子进程的退出状态并回收资源。waitpid函数更加灵活,它可以指定要等待的子进程,并且可以设置为非阻塞模式。例如,修改上面的代码来避免僵尸进程:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("子进程正在运行,进程ID为 %d\n", getpid());
        // 子进程退出
        exit(0);
    } else if (pid > 0) {
        // 父进程
        int status;
        // 父进程等待子进程结束并回收资源
        waitpid(pid, &status, 0);
        while (1) {
            printf("父进程正在运行,进程ID为 %d\n", getpid());
            sleep(1);
        }
    } else {
        // fork失败
        perror("fork");
        return 1;
    }
    return 0;
}

  • 信号处理:可以通过信号处理机制来自动处理子进程的结束。当子进程结束时,会向父进程发送SIGCHLD信号。父进程可以注册一个信号处理函数来处理这个信号,在信号处理函数中调用waitwaitpid来回收子进程资源。例如:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void sigchld_handler(int signo) {
    pid_t pid;
    int status;
    // 循环回收所有结束的子进程
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        printf("回收子进程 %d 的资源\n", pid);
    }
}

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("子进程正在运行,进程ID为 %d\n", getpid());
        // 子进程退出
        exit(0);
    } else if (pid > 0) {
        // 父进程
        // 注册SIGCHLD信号处理函数
        struct sigaction sa;
        sa.sa_handler = sigchld_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART;
        if (sigaction(SIGCHLD, &sa, NULL) == -1) {
            perror("sigaction");
            return 1;
        }
        while (1) {
            printf("父进程正在运行,进程ID为 %d\n", getpid());
            sleep(1);
        }
    } else {
        // fork失败
        perror("fork");
        return 1;
    }
    return 0;
}

  • 在这个例子中,当子进程结束时,会发送SIGCHLD信号给父进程。父进程的信号处理函数sigchld_handler会调用waitpid来回收子进程的资源,从而避免僵尸进程的产生。

孤儿进程

1号进程是OS的init

进程状态-下_子进程

进程提前退出 / 不等待退出的子进程,在父进程退出后,子进程被init领养

进程的阻塞, 挂起, 运行

操作系统课本的理论, 只是个指导思想; 一个具体的OS是具体实现;

它们在大的方面有些共性, 但一些具体细节会有些差异

进程状态-下_父进程_02

运行

进程状态-下_父进程_03

阻塞

如果某个进程需要等待某个硬件资源, 则这个进程就会从运行队列, 移动到需要的资源的等待队列中(这就是阻塞状态, 不运行了), 待资源可用后, 再将第一个进程, 移到运行队列(运行状态)

进程状态-下_子进程_04

挂起态

内存吃紧时, os会将一些不急着运行的进程的代码和数据, 放入磁盘的swap分区, 待内存充足时, 再唤入内存;

swap一般设置成内存的1倍左右, 如果设置的过大, os就会过于依赖swap, 导致频繁唤入换出(需要时间的), 降低系统效率, 本质是一种用效率换空间

进程状态-下_子进程_05

Linux命令fdisk磁盘的区域划分 和 写入文件系统的


进程切换

进程状态-下_僵尸进程_06

举报

相关推荐

0 条评论