Linux 进程核心知识

目录

一、核心进程概念

[1. 父进程 & 子进程(fork 创建的核心)](#1. 父进程 & 子进程(fork 创建的核心))

[2. 孤儿进程](#2. 孤儿进程)

[3. 僵尸进程](#3. 僵尸进程)

二、核心工具:查看进程状态

[1. ps:查看进程快照](#1. ps:查看进程快照)

[2. pstree:以树形结构展示进程关系](#2. pstree:以树形结构展示进程关系)

三、核心函数

[1. fork ():创建子进程](#1. fork ():创建子进程)

[2. getpid ()/getppid ():获取进程 PID](#2. getpid ()/getppid ():获取进程 PID)

[3. execl ():进程替换](#3. execl ():进程替换)

[4. 进程终止](#4. 进程终止)

[5. wait ()/waitpid ():监控并回收子进程](#5. wait ()/waitpid ():监控并回收子进程)

[(1)wait ():阻塞等待子进程退出](#(1)wait ():阻塞等待子进程退出)

[(2)waitpid ():灵活监控子进程](#(2)waitpid ():灵活监控子进程)

四、实战核心逻辑梳理

五、关键易错点总结


一、核心进程概念

1. 父进程 & 子进程(fork 创建的核心)

  • 定义 :通过 fork() 系统调用创建的新进程为子进程,发起创建的进程为父进程。当子进程被创建后将和父进程并行运行,但是父进程的运行速度要比子进程的运行速度快,要注意子进程变成孤儿进程。
  • 核心特性
    • fork() 执行后,操作系统会复制父进程的内存、代码、文件描述符等资源,形成两个完全独立的进程,子进程会完整复制父进程的内存空间(包括全局变量、堆、栈等),此时父子进程的全局变量值完全相同;
    • 父子进程有各自独立的 PID,由操作系统独立调度(宏观上并行运行),子进程的执行 / 退出 / 崩溃不会直接影响父进程;
    • 父进程执行路径:fork() 返回子进程 PID(正数),继续执行原代码逻辑;
    • 子进程执行路径:fork() 返回 0,从 fork() 所在行开始执行,可通过 execl() 替换为其他程序。
  • 易错点 :子进程调用 execl() 成功后,会完全替换自身代码,永远不会回到原进程代码;只有 execl() 失败时,才会执行后续代码(如打印错误、退出)。

2. 孤儿进程

  • 定义 :父进程先于子进程退出,子进程失去父进程,此时子进程会被系统的 init 进程(PID=1)接管,成为 "孤儿进程"。
  • 特点:孤儿进程本身是正常运行的进程,只是父进程已消亡,由 init 进程负责回收其退出资源,不会占用系统资源,无需手动处理。

3. 僵尸进程

  • 定义 :子进程先于父进程退出,但父进程未调用 wait()/waitpid() 回收其退出状态,子进程残留的 "退出信息 + PID" 会成为僵尸进程(状态标记为 defunct)。
  • 核心原理
    • 子进程退出后,PID 不会立即销毁,内核会保留其退出状态(如退出码、终止原因),等待父进程查询;
    • 若父进程不回收,僵尸进程会一直占用 PID 资源,大量僵尸进程会导致系统无法创建新进程。
  • 解决方式 :父进程通过 wait()/waitpid() 主动回收子进程资源,释放 PID;若父进程退出,孤儿化的僵尸进程会被 init 进程自动回收。

二、核心工具:查看进程状态

1. ps:查看进程快照

  • 常用命令
    • ps -ef:查看所有进程的完整信息(UID、PID、PPID、运行命令等);
    • ps -ef | grep defunct:筛选出所有僵尸进程;
    • ps -p <PID>:查看指定 PID 进程的状态。
  • 关键字段
    • PID:进程唯一标识;
    • PPID:父进程 PID;
    • STAT:进程状态(Z = 僵尸进程,R = 运行,S = 睡眠)。

2. pstree:以树形结构展示进程关系

  • 常用命令pstree -p(显示 PID,直观看到父子进程层级);
  • 作用 :快速定位进程的父子关系,比如查看 fork() 创建的子进程归属。

三、核心函数

1. fork ():创建子进程

  • 函数原型pid_t fork(void);
  • 返回值
    • 父进程中:返回子进程 PID(正数);
    • 子进程中:返回 0;
    • 失败:返回 -1(如系统资源不足)。
  • 实战注意
    • 全局变量在父子进程中是 "复制关系",修改子进程的全局变量不会影响父进程;

    • 代码示例(进程守护核心): c

      运行

      复制代码
      pid_t pid = fork();
      if (pid == 0) {
          // 子进程逻辑:执行目标程序
          execl("./hello", "./hello", NULL);
          exit(1); // execl失败才执行
      } else if (pid < 0) {
          // fork失败处理
          perror("fork error");
          exit(1);
      }
      // 父进程逻辑:继续执行监控代码

2. getpid ()/getppid ():获取进程 PID

  • getpid():返回当前进程的 PID;
  • getppid():返回当前进程的父进程 PID;
  • 作用:调试时确认父子进程关系,比如打印父 / 子进程 PID 验证独立性。

3. execl ():进程替换

  • 函数原型int execl(const char *path, const char *arg, ... /* (char *)NULL */);
  • 核心作用 :将当前进程的代码、数据、栈完全替换为指定程序,从新程序的 main() 开始执行。
  • 关键规则
    • 参数必须以 NULL 结尾(如 execl("/bin/ls", "ls", "-l", NULL));
    • 成功:当前进程代码被完全替换,原代码后续逻辑永远不执行;
    • 失败:返回 -1,执行后续代码(需打印错误并退出);
    • 路径要求:必须指定可执行文件的完整路径(或当前目录相对路径)。

4. 进程终止

  • 正常终止
    • 子进程执行完 main() 或调用 exit(int status)(status 为退出码,0 = 正常,非 0 = 异常);
    • _exit(int status):直接终止进程,不刷新缓冲区(区别于 exit())。
  • 异常终止 :被信号杀死(如 kill <PID>、段错误、断言失败)。

5. wait ()/waitpid ():监控并回收子进程

(1)wait ():阻塞等待子进程退出

  • 原型pid_t wait(int *wstatus);
  • 特点:父进程阻塞,直到任意一个子进程退出,回收其资源并返回退出子进程的 PID;
  • 缺点:无法指定监控某个子进程,也无法非阻塞监控。

(2) waitpid():灵活监控子进程

函数原型

复制代码
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);

核心参数

  • pid:指定要监控的子进程 PID

    • -1:监控任意子进程(等价于 wait()
    • > 0:监控指定 PID 的子进程
    • 0:监控与当前进程同组的所有子进程
    • < -1:监控进程组 ID 等于 |pid| 的所有子进程
  • wstatus:存储子进程退出状态的指针

    • 可通过宏 WIFEXITEDWEXITSTATUSWIFSIGNALED 等解析子进程的退出原因和状态码
    • 若不需要状态,可传入 NULL
  • options:等待模式选项

    • 0:阻塞等待,直到指定子进程终止
    • WNOHANG:非阻塞模式,立即返回,不等待子进程终止
    • WUNTRACED:同时捕获子进程停止(未终止)的状态
    • WCONTINUED:捕获子进程从停止状态恢复运行的状态(Linux 特有)

返回值(核心)

1. 调用成功
  • 返回被捕获的子进程 PID (正数)
    • 子进程正常退出、被信号终止或停止时,waitpid 成功回收其状态,返回其 PID
    • pid=-1,则返回任意一个已退出 / 停止子进程的 PID
2. 非阻塞模式(options = WNOHANG
  • 返回 0:子进程仍在运行,未退出、未停止,且无状态可回收
  • 返回 子进程 PID:子进程已退出 / 停止,成功回收其状态
3. 调用失败
  • 返回 -1 ,并设置全局变量 errno 表示错误原因,常见场景:
    • ECHILD:没有可等待的子进程(子进程已全部回收或不存在)
    • EINTR:调用被信号中断
    • EINVALoptions 参数无效

补充说明

  • options=0(阻塞模式)时,不会返回 0,它会一直阻塞直到子进程终止或调用失败。
  • 子进程退出后,若未被父进程通过 waitpid 回收,会变成僵尸进程(zombie),占用系统资源。
  • 父进程退出后,未被回收的子进程会被 init 进程(PID=1)接管并自动回收。
  • 实战场景(进程守护监控)

    c

    运行

    复制代码
    while (1) {
        int status;
        pid_t result = waitpid(child_pid, &status, WNOHANG);
        if (result > 0) {
            // 子进程已退出,重启子进程
            printf("子进程退出,重启...\n");
            fork() + execl(); // 重新创建子进程
        } else if (result == -1) {
            perror("waitpid error");
            exit(1);
        }
        usleep(10*1000); // 10ms检查一次,降低CPU占用
    }

四、实战核心逻辑梳理

  1. 父进程调用 fork() 创建子进程,子进程通过 execl() 执行目标程序(如 ./hello);
  2. 父进程不退出,进入 while(1) 循环,通过 waitpid(pid, &status, WNOHANG) 非阻塞监控子进程;
  3. 子进程正常运行时,waitpid 返回 0,父进程每隔 10ms 重复检查;
  4. 子进程退出时,waitpid 返回子进程 PID(>0),自动回收僵尸进程、释放 PID;
  5. 父进程调用 fork()+execl() 重启子进程,更新监控的 PID,循环往复;
  6. 全程父子进程独立并行:父进程只负责监控重启,子进程只负责运行目标程序,互不干扰。

五、关键易错点总结

  1. execl() 成功后,子进程代码被完全替换,原代码后续逻辑永不执行;
  2. 僵尸进程的核心解决方式是父进程调用 wait()/waitpid(),而非等待系统自动释放;
  3. WNOHANG 是实现 "非阻塞监控" 的关键,无此参数则父进程会阻塞到子进程退出;
  4. fork() 创建的是独立进程(非线程),父子进程有独立 PID 和资源,线程则共享进程资源;
  5. 父进程的 main() 逻辑(如监控循环)会持续执行,不受子进程 execl()/ 退出的影响。
相关推荐
努力努力再努力wz1 小时前
【Linux网络系列】:TCP 的秩序与策略:揭秘传输层如何从不可靠的网络中构建绝对可靠的通信信道
java·linux·开发语言·数据结构·c++·python·算法
2401_858286113 小时前
OS55.【Linux】理解信号量(不是信号)
linux·运维·服务器·计数器·信号量
S-码农4 小时前
Linux进程通信——消息队列
linux
零基础的修炼5 小时前
Linux网络---数据链路层
linux·服务器·网络
楼田莉子6 小时前
Linux学习:线程的同步与互斥
linux·运维·c++·学习
小草儿7996 小时前
PG18备份恢复
linux·运维·服务器
笑口常开xpr7 小时前
Linux 命 令 界 的 “王 炸 组 合”!Gitee 提 交 + 权 限 控 制 + 热 键 神 操,学 会 直 接 霸 屏 终 端!
linux·gitee·权限
Starry_hello world7 小时前
Linux http代码
linux·运维·http
开开心心_Every9 小时前
全屏程序切换工具,激活选中窗口快速切换
linux·运维·服务器·pdf·ocr·测试用例·模块测试