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

【Linux】linux进程概念(fork,进程状态,僵尸进程,孤儿进程)

本文是小编巩固自身而作,如有错误,欢迎指出!

目录

一、fork

1.fork的原理

2.fork的示例

3.fork的执行过程

二、进程状态

1.操作系统的进程状态

2.linux中的进程状态

1. R — Running / Runnable(运行 / 就绪)

2. S — Interruptible Sleep(可中断睡眠)

3. D — Uninterruptible Sleep(不可中断睡眠)

4. T — Stopped(暂停)

5. Z — Zombie(僵尸进程)

​编辑

状态示例

僵尸进程

僵尸进程的关键特征

孤儿进程


一、fork

在编程领域,fork是一个核心且高频出现的概念,最常见于Unix/Linux 系统编程.

1.fork的原理

fork()是 Unix/Linux 系统提供的一个系统调用(函数),作用是复制当前进程,创建一个全新的子进程。

  • 调用fork()后,操作系统会为子进程复制父进程的内存空间、代码、数据、文件描述符等资源。
  • 一次调用,两次返回:
    • 父进程中,fork()返回子进程的 PID(进程 ID)
    • 子进程中,fork()返回0
    • 若创建失败(如资源不足),返回-1

2.fork的示例

#include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main() { // 调用fork创建子进程 pid_t pid = fork(); if (pid < 0) { // 失败场景 perror("fork failed"); return 1; } else if (pid == 0) { // 子进程执行逻辑 printf("我是子进程,PID:%d,父进程PID:%d\n", getpid(), getppid()); // 子进程执行完退出 return 0; } else { // 父进程执行逻辑 printf("我是父进程,PID:%d,创建的子进程PID:%d\n", getpid(), pid); // 等待子进程结束,避免僵尸进程 wait(NULL); printf("子进程已结束\n"); } return 0; }

我们可以看到子进程的ppid和父进程的pid是一样的。说明fork的作用是在原有的进程基础上创建一个子进程,而不是在总的进程下创建一个子进程。

而我们自己创建的进程的父进程又是什么呢?很简单,在所有我们不曾用fork创建新的子进程的情况下,原本创建的进程的父进程都是bash。而bash也就是我们用fork创建进程的爷爷进程。

3.fork的执行过程

我们在看上述代码的时候,不免有一个疑问?为什么一个fork函数可以返回两个值?

不是fork()函数本身返回了两个值,而是操作系统让fork()调用在父进程和子进程中分别返回了不同的值

fork()的本质是「复制当前进程」,整个过程可以分成 3 步:

  1. 你在父进程中调用fork(),操作系统接收到这个请求;
  2. 操作系统会完整复制父进程的所有资源(代码、内存、文件描述符等),创建一个全新的子进程;
  3. 此时系统中存在两个独立的进程(父 + 子),它们会从fork()调用的位置继续往下执行代码

为什么需要「不同的返回值」?

父进程和子进程执行的是同一份代码,如果fork()返回的值相同,这两个进程就会执行完全一样的逻辑 —— 但实际开发中,我们往往需要让父子进程做不同的事(比如父进程监听端口,子进程处理请求)。

因此,操作系统设计了这个「差异化返回」的机制,目的是:让程序员能通过返回值区分「当前代码跑在父进程还是子进程」,从而编写不同的逻辑

二、进程状态

1.操作系统的进程状态

在操作系统中一般我们认为有三种状态

  • 运行(Running)正在用 CPU。

  • 就绪(Ready)除了 CPU,别的资源都有,等 CPU。

  • 阻塞(Blocked/Waiting)等事件(I/O、信号),就算 CPU 空着也不能运行

而在特殊情况下,我们还有一种状态就是挂起

挂起 =进程被调到外存(磁盘),不在内存里了原因:内存不够、进程太久不用、管理员手动挂起。

挂起分两种:

(1)就绪挂起(Ready Suspend)

  • 本来是就绪态
  • 被换到磁盘
  • 只要调回内存 → 就变成就绪

(2)阻塞挂起(Blocked Suspend)

  • 本来是阻塞态
  • 被换到磁盘
  • 等的事件来了 → 变成就绪挂起

2.linux中的进程状态

//linux源代码如下 static const char * const task_state_array[] = { "R (running)" //运行 "S (sleeping)" //睡眠 "D (disk sleep)" //深度睡眠 "T (stopped)" //停止 "t (tracing stop)"//追踪停止 "X (dead)" //死亡 "Z (zombie)", //僵尸 };

1. R — Running / Runnable(运行 / 就绪)

  • 正在 CPU 上运行or在就绪队列排队等着运行
  • 只有 R 状态的进程才会被 CPU 调度

2. S — Interruptible Sleep(可中断睡眠)

  • 最常见状态
  • 进程在等待事件:网络、键盘、信号、sleep、锁等
  • 可以被信号唤醒、杀死

3. D — Uninterruptible Sleep(不可中断睡眠)

  • 一般在等待 I/O:磁盘读写
  • 不能被信号打断,不能被杀掉
  • 大量 D 状态 = 磁盘 I/O 卡住、系统很卡

4. T — Stopped(暂停)

  • 暂停执行
  • 比如:Ctrl+Z、被调试器(gdb)暂停

5. Z — Zombie(僵尸进程)

  • 进程已经结束、退出
  • 但父进程没有调用wait()回收它的 PCB 信息
  • 僵尸进程占进程号,不占内存,太多会炸系统

状态示例

比如我们这里写一个简单的死循环

#include <stdio.h> int main() { while(1) {} return 0; }

我们可以看到这样就是运行状态了。

但如果我们在循环中加入一个printf呢?

我们可以看到在这种情况下我们的进程状态居然变成了S

原因就在于终端输出的阻塞特性

  • printf向终端(pts/0)输出内容时,会触发终端 I/O 缓冲区操作,进程需要等待终端内核缓冲区就绪,这个等待过程会让进程短暂进入S(可中断睡眠)状态;
  • 虽然没有sleep,但printf与终端的交互是频繁的 “短暂运行(R)→ 短暂睡眠(S)” 循环,ps采样时大概率捕捉到的是S状态(而非瞬时的R状态)。

僵尸进程

#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { // 1. 创建子进程:fork() 是创建进程的系统调用 // 返回值 pid_t 是进程ID类型,不同返回值区分父子进程 pid_t id = fork(); // 2. 错误处理:fork() 失败时返回 -1 if(id < 0){ // perror 打印错误原因(如内存不足、进程数超限等) perror("fork"); // 非0返回表示程序异常退出 return 1; } // 3. 父进程逻辑:fork() 给父进程返回子进程的PID(大于0) else if(id > 0){ // 打印父进程PID,getpid() 获取当前进程的ID printf("parent[%d] is sleeping...\n", getpid()); // 父进程睡眠30秒:这段时间父进程不执行任何操作,也不回收子进程 // 核心:父进程睡眠期间,子进程退出后会变成僵尸进程 sleep(30); } // 4. 子进程逻辑:fork() 给子进程返回 0 else{ // 打印子进程PID printf("child[%d] is begin Z...\n", getpid()); // 子进程睡眠5秒:模拟子进程的业务执行时间 sleep(5); // 子进程正常退出:EXIT_SUCCESS 等价于 0,表示退出状态正常 // 子进程退出后,父进程还在sleep(30),未调用wait()回收资源 → 子进程变僵尸 exit(EXIT_SUCCESS); } // 父进程睡眠30秒后,程序正常结束(此时系统会自动回收子进程) return 0; }

僵尸进程(Zombie)= 子进程已退出(运行完毕 / 异常终止) + 父进程未调用wait()/waitpid()回收子进程的「退出状态和资源」

僵尸进程的关键特征

✅ 占用 PID,但不占用 CPU / 内存(只占极少量内核空间存储 PCB);

✅ 无法被kill -9杀死(因为进程已经退出,信号无接收者);

✅ 只有两种清理方式:

  • 父进程调用wait()/waitpid()主动回收;
  • 父进程退出,由「init 进程(PID=1)」接管并回收。

孤儿进程

#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { pid_t id = fork(); // 创建父子进程 if(id < 0){ perror("fork"); return 1; } // 父进程逻辑:先退出,制造孤儿进程 else if(id > 0){ printf("父进程[PID: %d] 即将退出,子进程[PID: %d] 变成孤儿\n", getpid(), id); sleep(1); // 留时间打印信息 exit(EXIT_SUCCESS); // 父进程主动退出 } // 子进程逻辑:持续运行,成为孤儿 else{ printf("子进程[PID: %d] 开始运行,父进程退出后,我的新父进程会变成 1\n", getpid()); // 子进程持续运行10秒,方便你查看状态 for(int i = 1; i <= 10; i++){ // getppid():获取当前进程的父进程PID printf("子进程[PID: %d] - 第%d秒,父进程PID: %d\n", getpid(), i, getppid()); sleep(1); } printf("子进程[PID: %d] 退出\n", getpid()); return 0; } }

孤儿进程 = 父进程先退出 + 子进程还在运行→ 此时子进程会被系统的「1 号 init 进程(或 systemd)」接管,成为 init 进程的 “养子”。

  • init 进程的作用:系统启动后第一个进程(PID=1),专门负责收养孤儿进程,子进程退出后会被 init 进程自动回收,不会变成僵尸;
  • 孤儿进程无危害:它就是一个普通的运行中进程,只是 “没了亲爹”,由系统接管,资源使用和普通进程完全一致;
  • 与僵尸进程的本质区别
    • 僵尸进程是「子死父不管」;
    • 孤儿进程是「父死子还在」。

本次分享就到这里结束了,后续会继续更新,感谢阅读!

http://www.jsqmd.com/news/502566/

相关文章:

  • 悠哉字体:3个维度解决中文手写排版难题的开源方案
  • Llama-3.2V-11B-cot在VMware虚拟机中的部署与性能测试
  • 快马AI助力:一分钟用自然语言生成Android Studio天气应用原型
  • [解决方案]如何突破炉石传说信息不对称困境?HSTracker的实时数据融合技术
  • 12 Components
  • L2-044 大众情人()
  • 【 每天学习一点算法 2026/03/19】子集
  • C++的左右值引用该怎么理解?注意点有什么?
  • ViT-B-16处理小尺寸图片的实战技巧(CIFAR-100案例解析)
  • 新手也能看懂的X站cms渗透实战:从广告设置到代码执行的完整漏洞链分析
  • xManager终极指南:解锁无广告音乐体验的免费应用管理器
  • 5个理由为什么Style-Bert-VITS2正在改变语音合成游戏规则
  • 中兴B860AV3.2-M_可刷移动高清6A_2+32G_灯绿色_带root_当贝桌面线刷固件包(内存显示正常)
  • 5大核心功能赋能Windows语音识别:FunASR社区版高效部署指南
  • 保姆级教程:基于Qwen3-Embedding-4B,快速部署可视化语义搜索系统
  • 90%的人降AI失败都栽在这一步:只降了标红段落没传全文
  • 斯坦福 CS336 从零构建大模型 (2025 春) - 第十一讲:缩放定律的工业界实践与底层机制 (Scaling Laws 2)
  • 当 JavaScript 试图做加法:一场混乱的“相亲”大会
  • 超级AI医院:以AI为核心大脑,重构全生命周期医疗生态
  • Linux虚拟显示器终极指南:3分钟将平板变免费扩展显示器
  • 斯坦福 CS336 从零构建大模型 (2025 春) - 第十六讲:强化学习与自对齐 (Alignment - RL 1)
  • MMWAVE SDK中的RF控制与数据路径详解:从理论到实践
  • 国内开发者福音:SwanLab替代Wandb实现具身智能训练参数可视化(附完整配置流程)
  • Abaqus与Isight联合仿真:从参数优化到自动化流程实战
  • Cogito-V1-Preview-Llama-3B实战:构建基于智能体(Agent)的自动化任务系统
  • FUTURE POLICE与AI Agent联动实战:构建自主语音任务处理智能体
  • SDL_ttf 3.0 迁移策略深度解析:构建系统适配与API兼容性挑战
  • Eclipse项目迁移到IntelliJ IDEA避坑指南:解决Web项目导入后无法运行的问题
  • 桌面级德州扑克GTO求解器:Desktop Postflop完全指南
  • VideoAgentTrek-ScreenFilter性能优化教程:C语言底层接口调用与内存管理