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

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

目录

一、fork

1.fork的原理

2.fork的示例

3.fork的执行过程

二、进程状态

1.操作系统的进程状态

2.linux中的进程状态

[1. R --- Running / Runnable(运行 / 就绪)](#1. R — Running / Runnable(运行 / 就绪))

[2. S --- Interruptible Sleep(可中断睡眠)](#2. S — Interruptible Sleep(可中断睡眠))

[3. D --- Uninterruptible Sleep(不可中断睡眠)](#3. D — Uninterruptible Sleep(不可中断睡眠))

[4. T --- Stopped(暂停)](#4. T — Stopped(暂停))

[5. Z --- Zombie(僵尸进程)](#5. Z — Zombie(僵尸进程))

​编辑

状态示例

僵尸进程

僵尸进程的关键特征

孤儿进程


一、fork

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

1.fork的原理

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

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

2.fork的示例

cpp 复制代码
#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中的进程状态

bash 复制代码
//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 信息
  • 僵尸进程占进程号,不占内存,太多会炸系统

状态示例

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

cpp 复制代码
#include <stdio.h>

int main()
{
  while(1)
  {}

  return 0;
}

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

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

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

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

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

僵尸进程

cpp 复制代码
#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)」接管并回收。

孤儿进程

cpp 复制代码
#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 进程自动回收,不会变成僵尸;
  • 孤儿进程无危害:它就是一个普通的运行中进程,只是 "没了亲爹",由系统接管,资源使用和普通进程完全一致;
  • 与僵尸进程的本质区别
    • 僵尸进程是「子死父不管」;
    • 孤儿进程是「父死子还在」。

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

相关推荐
牛十二2 小时前
宝塔安装openclaw+企业微信操作手册
linux·运维·服务器
森屿山茶2 小时前
hot100题解 —— 146.LRU缓存
java·开发语言
⑩-2 小时前
API 网关的作用?Spring Cloud Gateway 原理?
java·服务器·网络·spring cloud
70asunflower2 小时前
CUDA基础知识巩固检验练习题【附有参考答案】(7)
c++·人工智能·cuda
开开心心_Every2 小时前
免费抽奖软件支持内定名单+防重复中奖
linux·运维·服务器·edge·pdf·c5全栈·c4python
feng68_2 小时前
Discuz! X5 高性能+高可用
linux·运维·服务器·前端·后端·高性能·高可用
superkcl20222 小时前
C++初始化 和 赋值
开发语言·c++·算法
IMPYLH2 小时前
Linux 的 chgrp 命令
linux·运维·服务器
optimistic_chen2 小时前
【Vue入门】scoped与组件通信
linux·前端·javascript·vue.js·前端框架·组件通信