僵尸进程
- 定义
- 僵尸进程(Zombie Process)是指在子进程终止后,父进程尚未对其进行回收(获取子进程的退出状态等信息)的进程。在Linux和其他类UNIX系统中,当一个子进程结束运行时,它会留下一个包含一些基本信息(如进程ID、退出状态、资源使用信息等)的进程描述符,这个进程描述符会一直存在,直到父进程调用
wait
或waitpid
等系统调用来获取子进程的退出状态并释放这些资源,此时子进程才真正结束。如果父进程没有及时回收,子进程就会变成僵尸进程。
- 产生原因
- 父进程没有等待子进程结束:在一个进程创建了子进程后,如果父进程没有通过适当的系统调用(如
wait
或waitpid
)来等待子进程退出并回收其资源,子进程结束后就会变成僵尸进程。例如,以下是一个简单的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;
}
- 在这个例子中,子进程结束后,父进程没有调用
wait
或waitpid
来回收子进程的资源,所以子进程会变成僵尸进程。
- 危害
- 资源浪费:僵尸进程会占用系统的进程表项(
PCB
,进程控制块)空间。虽然僵尸进程本身不占用太多的其他系统资源(如内存、CPU等)(这些都会释放),但是大量的僵尸进程会导致进程表被填满,从而使系统无法创建新的进程。因为系统的进程表大小是有限的,当进程表满时,即使系统还有足够的内存和CPU资源,也无法创建新的进程。 - 可能导致系统异常:如果僵尸进程的父进程是一个长期运行的守护进程,并且不断地产生子进程而不回收,随着时间的推移,可能会使系统出现各种异常情况,如系统响应变慢、服务无法正常启动等。
- 如何避免和处理僵尸进程
- 父进程等待子进程结束:在父进程中使用
wait
或waitpid
系统调用来等待子进程结束并回收其资源。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
信号。父进程可以注册一个信号处理函数来处理这个信号,在信号处理函数中调用wait
或waitpid
来回收子进程资源。例如:
#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是具体实现;
它们在大的方面有些共性, 但一些具体细节会有些差异
运行
阻塞
如果某个进程需要等待某个硬件资源, 则这个进程就会从运行队列, 移动到需要的资源的等待队列中(这就是阻塞状态, 不运行了), 待资源可用后, 再将第一个进程, 移到运行队列(运行状态)
挂起态
内存吃紧时, os会将一些不急着运行的进程的代码和数据, 放入磁盘的swap分区, 待内存充足时, 再唤入内存;
swap一般设置成内存的1倍左右, 如果设置的过大, os就会过于依赖swap, 导致频繁唤入换出(需要时间的), 降低系统效率, 本质是一种用效率换空间
Linux命令fdisk磁盘的区域划分 和 写入文件系统的