当前位置: 首页 > news >正文

【Linux】(1)—进程概念-④fork、僵尸进程、孤儿进程

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、Linux进程状态

前言


提示:以下是本篇文章正文内容,下面案例可供参考

一、Linux进程状态

Linux内核将进程状态定义在task_state_array数组中,我们可以通过查看内核源代码来了解这些状态:

static const char *const task_state_array[] = {"R (running)",       /* 0 */"S (sleeping)",      /* 1 */"D (disk sleep)",    /* 2 */"T (stopped)",       /* 4 */"t (tracing stop)",  /* 8 */"X (dead)",          /* 16 */"Z (zombie)"         /* 32 */
};

主要进程状态详解

  1. R (Running) 运行状态

    • 表示进程正在CPU上执行或位于运行队列中准备执行

    • 注意:运行状态并不意味着进程一定正在CPU上运行,可能只是在就绪队列中等待调度

  2. S (Sleeping) 睡眠状态(可中断睡眠)

    • 进程正在等待某个事件完成(如I/O操作)

    • 这种睡眠可以被信号中断

    • 使用ps命令查看时,大多数用户进程通常处于此状态

  3. D (Disk Sleep) 磁盘休眠状态(不可中断睡眠)

    • 一种特殊的睡眠状态,通常发生在进程等待I/O操作完成时

    • 不可被信号中断,即使发送SIGKILL信号也无法终止

    • 常见于磁盘I/O或某些关键内核操作

  4. T (Stopped) 停止状态

    • 进程被信号暂停执行(如通过SIGSTOP信号)

    • 可以通过SIGCONT信号让进程继续执行

    • 调试器常用此状态来检查被调试进程

  5. X (Dead) 死亡状态

    • 进程已经终止,等待被父进程回收

    • 这是一个瞬时状态,用户通常看不到

  6. Z (Zombie) 僵尸状态

    • 进程已经终止,但其退出状态尚未被父进程读取

    • 会保留在进程表中,直到父进程读取其退出状态

    • 我们将在后面详细讨论僵尸进程

二、进程创建

在Linux中,创建新进程主要通过fork()系统调用实现。

fork()系统调用

fork()创建一个与父进程几乎完全相同的子进程,包括代码、数据段、堆栈等。

#include <unistd.h>
pid_t fork(void);
  • 成功时:

    • 父进程返回子进程的PID

    • 子进程返回0

  • 失败时返回-1

fork()特性

  1. 写时复制(Copy-On-Write)

    • 父子进程共享同一份物理内存,只有当任一进程尝试修改内存时,内核才会复制该内存页

    • 这种机制提高了fork的效率,减少了不必要的内存复制

  2. 两个返回值

    • fork()在父进程和子进程中各返回一次

    • 通过返回值区分父子进程

  3. 共享文件描述符

    • 子进程继承父进程打开的文件描述符

    • 父子进程共享文件偏移量

fork()使用示例

#include <stdio.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork failed");return 1;} else if (pid == 0) {// 子进程代码printf("Child process: PID=%d, PPID=%d\n", getpid(), getppid());} else {// 父进程代码printf("Parent process: PID=%d, Child PID=%d\n", getpid(), pid);}return 0;
}

fork()的典型应用场景

  1. 创建子进程执行新任务

    • 如Web服务器为每个连接创建子进程处理请求

  2. 进程替换

    • 结合exec系列函数,启动新程序

  3. 并行处理

    • 将任务分解,由多个子进程并行处理

三、僵尸进程

什么是僵尸进程

僵尸进程(Zombie Process)是指已经终止但其退出状态尚未被父进程读取的进程。在Linux中,这种进程处于"Z"状态。

僵尸进程的产生

当进程退出时,内核会保留该进程的一些基本信息(如退出状态、CPU时间等)直到父进程通过wait()waitpid()系统调用读取这些信息。如果父进程没有及时读取,子进程就会变成僵尸进程。

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork");return 1;} else if (pid > 0) {  // 父进程printf("Parent[%d] sleeping...\n", getpid());sleep(30);  // 父进程睡眠,不回收子进程} else {  // 子进程printf("Child[%d] exiting...\n", getpid());exit(0);  // 子进程立即退出}return 0;
}

运行后使用ps aux查看,可以看到子进程状态为"Z"。

僵尸进程的危害

  1. 资源泄漏

    • 内核需要维护进程的退出状态等信息,占用系统资源

    • 每个僵尸进程都会占用一个进程表项

  2. 系统性能下降

    • 大量僵尸进程会耗尽进程表,导致系统无法创建新进程

  3. 信息丢失风险

    • 如果父进程始终不读取子进程退出状态,可能导致重要信息丢失

如何避免僵尸进程

  1. 使用wait()/waitpid()

    • 父进程主动等待子进程结束并获取其退出状态

#include <sys/wait.h>pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
  1. 信号处理

    • 注册SIGCHLD信号处理函数,在回调中调用wait()

#include <signal.h>
#include <sys/wait.h>void sigchld_handler(int sig) {while (waitpid(-1, NULL, WNOHANG) > 0);
}int main() {signal(SIGCHLD, sigchld_handler);// ... fork()等代码
}
  1. 双重fork

    • 让子进程再fork一个孙进程后立即退出,孙进程由init进程接管

  2. 忽略SIGCHLD信号

    • 简单但不推荐,可能导致无法获取子进程退出状态

signal(SIGCHLD, SIG_IGN);

四、孤儿进程

什么是孤儿进程

孤儿进程(Orphan Process)是指父进程先于子进程退出,导致子进程被init进程(PID=1)收养的进程。

孤儿进程的产生

当父进程意外终止(如崩溃或被杀死)而子进程仍在运行时,这些子进程就变成了孤儿进程。Linux内核会将孤儿进程的父进程PID改为1(init进程)。

示例代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork");return 1;} else if (pid == 0) {  // 子进程printf("Child[%d] starts, parent is %d\n", getpid(), getppid());sleep(10);  // 子进程睡眠10秒printf("Child[%d] ends, parent is now %d\n", getpid(), getppid());} else {  // 父进程printf("Parent[%d] exiting...\n", getpid());exit(0);  // 父进程立即退出}return 0;
}

运行后可以看到子进程的父进程ID从原来的父进程变成了1。

孤儿进程的特点

  1. 被init进程收养

    • init进程会定期调用wait()清理其收养的孤儿进程

  2. 不会变成僵尸进程

    • init进程会自动处理孤儿进程的退出状态

  3. 运行不受影响

    • 除了父进程变化外,孤儿进程可以继续正常执行

孤儿进程 vs 僵尸进程

特性孤儿进程僵尸进程
产生原因父进程先退出子进程先退出且未被父进程回收
父进程被init进程(PID=1)收养保持原有父进程
进程状态仍然运行已终止(Z状态)
资源占用正常运行的进程资源占用进程表项
系统影响无负面影响可能导致进程表耗尽
清理方式由init进程自动回收需要父进程调用wait()回收

五、实际应用建议

  1. 服务器程序设计

    • 必须正确处理SIGCHLD信号,避免僵尸进程积累

    • 考虑使用进程池而非为每个请求fork新进程

  2. 长时间运行进程

    • 定期检查子进程状态

    • 实现心跳机制检测子进程健康状况

  3. 调试技巧

    • 使用ps aux查看进程状态

    • top命令可以查看进程的实时状态

    • strace跟踪系统调用,分析进程行为

  4. 最佳实践

    • 总是检查fork()的返回值

    • 在父进程中正确处理子进程退出

    • 考虑使用更现代的进程管理方式,如systemd

结语

理解Linux进程状态、掌握进程创建方法以及正确处理僵尸进程和孤儿进程,是Linux系统编程的基础。通过本文的介绍,希望读者能够:

  1. 清楚区分Linux的各种进程状态及其转换条件

  2. 熟练使用fork()创建子进程并正确处理父子进程关系

  3. 理解僵尸进程的形成机制并知道如何避免

  4. 认识孤儿进程的特性及其与僵尸进程的区别

良好的进程管理习惯不仅能提高程序稳定性,也能避免系统资源浪费。在实际开发中,应当根据具体需求选择合适的进程管理策略,确保系统高效可靠地运行。

相关文章:

  • daz3d + PBRSkin (MDL)+ SSS
  • Python爬虫实战:研究mechanize库相关技术
  • spring:实例化类过程中方法执行顺序。
  • tpc udp http
  • 鸿蒙开发——如何修改模拟器的显示图标/标题
  • React-表单受控绑定和获取Dom元素
  • ​​高频通信与航天电子的材料革命:猎板PCB高端压合基材技术解析​​
  • ✅ 常用 Java HTTP 客户端汇总及使用示例
  • C#子线程更新主线程UI及委托回调使用示例
  • openLayers实现实时轨迹
  • 【HarmonyOS 5】出行导航开发实践介绍以及详细案例
  • 29.【新型数据架构】-边缘计算数据架构
  • 边缘计算网关提升水产养殖尾水处理的远程运维效率
  • Windows系统中如何使用符号链接将.vscode等配置文件夹迁移到D盘(附 CMD PowerShell 双版本命令)
  • 摆脱硬件依赖:SkyEye在轨道交通中的仿真应用
  • hbuildx运行uzapp项目初始化配置
  • day35-系统编程之网络编程IV及MQTT协议
  • 相机Camera日志分析之二十七:高通相机Camx 基于预览1帧的process_capture_result二级日志分析详解
  • AI系统提示词:V0
  • PDF转Markdown/JSON软件MinerU最新1.3.12版整合包下载
  • 合肥专业做公司网站/做一个企业网站大概需要多少钱
  • 城乡与住房建设厅网站/网络推广方式方法
  • 医疗保健 网站 备案/东莞网络排名优化
  • 数据网站建设工具模板/抖音搜索seo排名优化
  • 2019年建设什么网站好/seo搜索优化怎么做
  • 做网站和做app那个简单/网站维护收费标准