当前位置: 首页 > 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. 认识孤儿进程的特性及其与僵尸进程的区别

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

http://www.lqws.cn/news/175429.html

相关文章:

  • vue3 按钮级别权限控制
  • 数学复习笔记 28
  • camera功能真的那么难用吗
  • UniApp系列
  • 静态相机中的 CCD和CMOS的区别
  • [ElasticSearch] DSL查询
  • 软件功能测试目的是啥?如何通过测试用例确保产品达标?
  • java教程笔记(十一)-泛型
  • 软件功能测试报告都包含哪些内容?
  • .net webapi http参数自定义绑定模型
  • .net 使用MQTT订阅消息
  • 赋能大型语言模型与外部世界交互——函数调用的崛起
  • 元图CAD:一键解锁PDF转CAD,OCR技术赋能高效转换
  • c# List<string>.Add(s) 报错:UnsupportedOperationException
  • .Net Framework 4/C# 关键字(非常用,持续更新...)
  • 【HarmonyOS 5】教育开发实践详解以及详细代码案例
  • Java -jar命令运行外部依赖JAR包的深度场景分析与实践指南
  • 浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
  • 基于大模型的 UI 自动化系统
  • 分布式协同自动化办公系统-工作流引擎-流程设计
  • 在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
  • LRU 和 DiskLRU实现相册缓存器
  • 使用 Python 自动化 Word 文档样式复制与内容生成
  • LeetCode 热题 100 34. 在排序数组中查找元素的第一个和最后一个位置
  • 3 个优质的终端 GitHub 开源工具
  • vue+elementUI+springboot实现文件合并前端展示文件类型
  • 【推荐算法】DeepFM:特征交叉建模的革命性架构
  • jmeter之导出接口
  • 【JMeter】后置处理器 - 提取器
  • Jmeter如何进行多服务器远程测试?