目录
[1. 什么是僵死进程](#1. 什么是僵死进程)
[1. 文件描述符](#1. 文件描述符)
[2. 基本文件操作函数](#2. 基本文件操作函数)
前言
本文主要讨论了Linux系统中的僵死进程问题和文件操作机制。、
关于僵死进程,当子进程先于父进程终止且父进程未调用wait()获取其状态时,子进程会变为僵死进程,占用系统资源。
文章介绍了三种处理方法:使用wait()函数、信号处理机制和双重fork技术。
在文件操作方面,详细解释了文件描述符的概念,以及open/close/read/write等基本函数的使用方法,同时列举了常见的文件操作标志和权限标志。
最后总结了僵死进程的危害和解决方案,以及文件操作的关键要点,强调资源管理和错误检查的重要性。
一、僵死进程(僵尸进程)
1. 什么是僵死进程
僵死进程是指进程已经终止,但其父进程尚未调用wait()或waitpid()来获取其终止状态的进程。此时进程的PCB(进程控制块)仍保留在系统中,占用少量资源。
当子进程先于父进程结束,父进程没有获取子进程的退出码,此时子进程变成僵死进程。
简而言之,就是子进程先结束,并且父进程没有获取它的退出码。
那么僵死进程产生的原因或者条件 就是:子进程先于父进程结束,并且父进程没有获取子进程的退出码。
2.进程状态转换示例
cpp
// 进程状态转换示例
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程 PID: %d\n", getpid());
exit(0); // 子进程终止,变为僵死进程
} else if (pid > 0) {
// 父进程
printf("父进程 PID: %d\n", getpid());
sleep(30); // 父进程睡眠,此时子进程处于僵死状态
wait(NULL); // 回收子进程,僵死进程消失
}
return 0;
}
3.僵死进程的产生原因
cpp
// 产生僵死进程的代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("子进程运行中... PID: %d\n", getpid());
exit(0); // 子进程退出,变为僵尸
} else if (pid > 0) {
printf("父进程运行中... PID: %d\n", getpid());
printf("子进程变为僵死进程,PID: %d\n", pid);
sleep(60); // 父进程不调用wait()
}
return 0;
}
4.查看僵死进程
# 查看系统中的僵死进程
ps aux | grep Z
ps -el | grep Z# 输出示例
USER PID STAT COMMAND
root 1234 Z [process_name] <defunct>
5.如何处理僵死进程
方法一:使用wait()系列函数
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("子进程退出\n");
exit(0);
} else if (pid > 0) {
int status;
pid_t ret = wait(&status); // 阻塞等待子进程退出
if (WIFEXITED(status)) {
printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
}
printf("父进程回收子进程\n");
}
return 0;
}
方法二:使用信号处理
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdlib.h>
void sigchld_handler(int signo) {
pid_t pid;
int status;
// 循环回收所有已终止的子进程
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("回收子进程 PID: %d\n", pid);
}
}
int main() {
// 注册SIGCHLD信号处理函数
signal(SIGCHLD, sigchld_handler);
pid_t pid = fork();
if (pid == 0) {
printf("子进程运行...\n");
sleep(2);
exit(0);
} else if (pid > 0) {
printf("父进程继续执行...\n");
sleep(10); // 父进程不需要主动wait
}
return 0;
}
方法三:双重fork(孤儿进程法)
cpp
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 第一子进程
pid_t pid2 = fork(); // 创建第二子进程
if (pid2 == 0) {
// 第二子进程,将被init进程收养
printf("实际工作的子进程 PID: %d\n", getpid());
sleep(10);
exit(0);
} else if (pid2 > 0) {
// 第一子进程立即退出
exit(0);
}
} else if (pid > 0) {
// 父进程回收第一子进程
wait(NULL);
printf("父进程继续执行...\n");
sleep(15);
}
return 0;
}
总结:
- 父进程先结束。
- 父进程调用wait()方法获取子进程的退出码。
其实这两种处理僵死进程的方法本质都是一样的,都调用了wait获取子进程退出码。
方法一是父进程先结束后子进程被Init收养,Init之后调用wait获取子进程退出码。
方法二是父进程直接调用wait()。
但是两种方法又有区别,就是父进程调用wait会阻塞,等子进程执行完之后,父进程才会执行.
若想父进程调用wait不阻塞,那么我们需要结合信号一起。
二、Linux文件操作详解
1. 文件描述符
文件描述符是内核为每个进程维护的打开文件表的索引。
- 0:标准输入
- 1:标准输出
- 2:标准错误输出
cpp
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
int main() {
// 标准文件描述符
printf("标准输入: %d\n", STDIN_FILENO); // 0
printf("标准输出: %d\n", STDOUT_FILENO); // 1
printf("标准错误: %d\n", STDERR_FILENO); // 2
// 打开文件获取新的文件描述符
int fd = open("test.txt", O_CREAT | O_RDWR, 0644);
if (fd == -1) {
perror("open");
exit(1);
}
printf("新文件描述符: %d\n", fd); // 通常是3
close(fd);
return 0;
}
2. 基本文件操作函数
open/close:
open的返回值是int,称之为"文件描述符"。每打开一个文件,我们就会得到一个文件描述符,这个文件描述符是一个整型,通过文件描述符就可以对文件进行读写这样的操作。
open失败返回-1,成功返回一个大于等于0的值; 0,1,2是默认打开的。
cpp
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
// 打开文件
int fd = open("example.txt", O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
return 1;
}
// 写入数据
char *data = "Hello, Linux!\n";
ssize_t bytes_written = write(fd, data, strlen(data));
printf("写入 %ld 字节\n", bytes_written);
// 定位到文件开头
lseek(fd, 0, SEEK_SET);
// 读取数据
char buffer[100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
buffer[bytes_read] = '\0';
printf("读取内容: %s", buffer);
// 关闭文件
close(fd);
return 0;
}
3.文件操作标志
printf("文件打开标志:\n");
printf("O_RDONLY : 只读模式 (0)\n");
printf("O_WRONLY : 只写模式 (1)\n");
printf("O_RDWR : 读写模式 (2)\n");
printf("O_CREAT : 如果文件不存在则创建\n");
printf("O_TRUNC : 如果文件存在则截断\n");
printf("O_APPEND : 追加模式\n");
printf("O_EXCL : 与O_CREAT一起使用,确保文件不存在\n");
printf("O_NONBLOCK: 非阻塞模式\n");printf("\n文件权限标志:\n");
printf("S_IRUSR : 用户读权限 (0400)\n");
printf("S_IWUSR : 用户写权限 (0200)\n");
printf("S_IXUSR : 用户执行权限 (0100)\n");
printf("S_IRGRP : 组读权限 (0040)\n");
printf("S_IWGRP : 组写权限 (0020)\n");
printf("S_IXGRP : 组执行权限 (0010)\n");
printf("S_IROTH : 其他读权限 (0004)\n");
printf("S_IWOTH : 其他写权限 (0002)\n");
printf("S_IXOTH : 其他执行权限 (0001)\n");
三、总结
僵死进程关键点:
-
产生原因:子进程先于父进程退出,父进程未回收
-
危害:占用系统资源(PCB),可能导致资源耗尽
-
解决方法:
-
父进程调用wait()/waitpid()
-
使用信号处理SIGCHLD
-
双重fork技术
-
重启父进程
-
文件操作关键点:
-
文件描述符:进程与文件交互的桥梁
-
基本操作:open/read/write/close/lseek
-
高级功能:内存映射、文件锁、目录遍历
-
错误处理:必须检查每个系统调用的返回值
-
资源管理:及时关闭文件描述符,防止泄露