僵死进程及Linux文件操作 详解

目录

前言

一、僵死进程(僵尸进程)

[1. 什么是僵死进程](#1. 什么是僵死进程)

2.进程状态转换示例

3.僵死进程的产生原因

4.查看僵死进程

5.如何处理僵死进程

方法一:使用wait()系列函数

方法二:使用信号处理

方法三:双重fork(孤儿进程法)

二、Linux文件操作详解

[1. 文件描述符](#1. 文件描述符)

[2. 基本文件操作函数](#2. 基本文件操作函数)

3.文件操作标志

三、总结

僵死进程关键点:

文件操作关键点:


前言

本文主要讨论了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;
}

总结:

  1. 父进程先结束。
  2. 父进程调用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");


三、总结

僵死进程关键点:

  1. 产生原因:子进程先于父进程退出,父进程未回收

  2. 危害:占用系统资源(PCB),可能导致资源耗尽

  3. 解决方法

    • 父进程调用wait()/waitpid()

    • 使用信号处理SIGCHLD

    • 双重fork技术

    • 重启父进程

文件操作关键点:

  1. 文件描述符:进程与文件交互的桥梁

  2. 基本操作:open/read/write/close/lseek

  3. 高级功能:内存映射、文件锁、目录遍历

  4. 错误处理:必须检查每个系统调用的返回值

  5. 资源管理:及时关闭文件描述符,防止泄露

相关推荐
IMPYLH2 小时前
Linux 的 comm 命令
linux·运维·算法
淡泊if2 小时前
1.2GB → 98MB,我的 Docker 镜像瘦身实战记录
运维·docker·容器
Sst的头号粉丝2 小时前
Docker——cgroups
运维·docker·容器
薛定谔的悦2 小时前
嵌入式设备OTA升级实战:从MQTT命令到自动重启的全流程解析
linux·算法·ota·ems
于慨3 小时前
tauri
java·服务器·前端
2501_918126913 小时前
学习所有6502写游戏控制器的语句
java·linux·网络·汇编·嵌入式硬件
JuckenBoy3 小时前
Linux环境安装SGLang框架运行自选大模型(以Rocky9.7为例)
linux·运维·大模型·qwen·rocky·deepseek·sglang
十巷无终3 小时前
Kali Virtual Machines(虚拟机镜像)安装后问题及解决办法
linux·运维·服务器
赵民勇3 小时前
gtkmm库之GtkWindow与ApplicationWindow用法详解
linux·c++