☰
30天速通Linux 第五章Linux 进程管理
2026/10/8 7:23:12 网站建设 项目流程

学习资料https://pan.baidu.com/s/1NZpC4NyH0OHGOPPbO9J10g?pwd=h9m2 提取码: h9m2

一. 进程基础

1.1 进程基本概念

1. 程序 vs 进程:

  • 程序是静态的指令集合,不占用系统运行资源。
  • 进程是程序在系统中的一次动态执行过程,占用系统资源(CPU、内存、文件等),一个程序运行多次会产生多个进程。

2. Linux 进程的三种类型:

  • 交互进程:由 Shell 启动,可在前台(可与用户交互)或后台(加&运行)运行。
  • 批处理进程:与终端无直接联系,是一个按指定方式执行的进程序列。
  • 守护进程:Linux 启动时运行的后台服务,屏幕不显示任何信息,在后台默默为用户服务(如各类网络服务)。

3. 执行模式与 Shell:

  • 进程在用户模式(用户空间)下运行,当发生系统调用或中断时切换为内核模式(核心程序运行,具有特权指令,不受用户干预)。
  • 用户登录后打开的终端会建立一个Shell 进程,接收用户命令并创建子进程执行;进程结束后,返回当前 Shell 继续接收新命令。

1.2 进程创建和命令执行

1.fork()系统调用 —— 创建子进程

  • 头文件:#include <unistd.h>
  • 函数原型:pid_t result = fork(void);
  • 核心机制(返回两次):
    • 父进程:返回新创建子进程的PID(>0)。
    • 子进程:返回0。
    • 出错:返回-1。
  • 内核完成的工作:
    ① 为子进程分配新内存块和内核数据结构;
    ② 复制父进程信息(代码段、数据段等),二者拥有相同可执行代码;
    ③ 将新进程加入运行进程集合;
    ④ 执行完毕,控制权返回给两个进程,由调度器决定执行顺序。
  • 关键特性(数据独立性):fork之后,父子进程的全局变量和局部变量虽然名称和初始值相同,但它们在物理内存中是相互独立的,互不影响。
    • 示例分析:父进程执行glob++(变为11),子进程执行local--(变为7),两者输出互不干扰。

2.exec系列调用 —— 执行新程序

  • 当父进程希望子进程执行特定任务时,会使用exec家族函数,用新程序的指令代码替换子进程原有的代码段、数据段和堆栈(相当于给子进程“换脑”)。
  • 函数分类:
    • execl/execv:要求提供可执行文件的绝对或相对路径。
    • execlp/execvp:使用PATH环境变量查找可执行文件(p代表 PATH)。
    • 传参方式:l(list)逐个列举参数;v(vector)使用字符串数组(指针数组)传递参数。
  • 执行流程:将指定程序复制到调用它的进程 → 将字符串数组作为argv[]传递 → 运行新程序。
  • 核心特性:exec成功调用后不返回任何值,子进程原有的后续代码(如printf)将被新程序替换而不再执行;但如果exec调用失败,返回 -1,子进程会继续执行后续代码。

二. 进程退出

进程退出主要分为正常退出和异常退出两类。

1. 正常退出的 3 种方式:

  • 程序正常运行结束,进程自动消失。
  • 程序调用exit()函数退出。
  • 程序调用_exit()退出。

2.exit()与_exit()的核心区别(图 5.3):

  • exit():属于库函数。会先刷新缓冲区中的所有流,调用用户通过atexit和on_exit注册的函数,执行完其他与退出相关的操作后,最后才调用_exit()。
  • _exit():属于系统调用。会直接终止进程,不刷新缓冲区,也不执行用户注册的清理函数。

3. 异常退出:
进程在运行时收到系统传来的外部信号而终止执行。例如:kill -9 pid就是向进程号为 pid 的进程发送强制结束信号。

2.1 守护进程(Daemon)

1. 概念:
守护进程是脱离终端且在后台运行的进程。它不受终端控制,即使终端退出,它依然在后台运行(如ftpd、crond、httpd)。系统引导时会开启很多这种服务为用户提供功能。

2. 核心概念:进程组与会话期(图 5.4)

  • 进程组:一组进程的集合,由进程组 PID 表示。每个进程除了进程 ID 外,还必须属于某个进程组。通常一个终端就是进程组组长。
  • 会话期:由一个或多个进程组组成的集合。一个会话期开始于用户登录,结束于用户退出。

3. 创建守护进程的 5 个步骤(图 5.5 + 例 5-5):
①创建子进程,父进程退出:调用fork()创建子进程,然后让父进程exit()。这样子进程就变成了孤儿进程,被系统的 init 进程(PID 1)收养,从而脱离原终端的控制。
②让子进程脱离控制终端:在子进程中调用setsid()系统调用(见表 5.3)。它会创建一个新的会话,并让调用它的进程成为该会话的组长。作用是让进程摆脱原会话、原进程组、原控制终端的控制。
③改变当前目录为根目录:子进程执行chdir("/")。防止当前目录(如某个挂载点)因为守护进程的存在而无法被卸载。
④修改文件权限掩码:调用umask(0)。将文件权限掩码设置为 0,即取消子进程对文件操作的屏蔽限制,增强守护进程的灵活性。
⑤关闭文件描述符:关闭从父进程继承的已打开的文件描述符。因为守护进程已与控制终端失去联系,标准输入输出(0、1、2)等文件描述符不再被使用,且会耗费系统资源,应使用close(fd)关闭。


2.2 僵尸进程(Zombie)

1. 产生原因:
当子进程先于父进程结束,而父进程没有使用wait()系统调用获取子进程的结束状态时,子进程就会变成僵尸进程。
注意:如果是父进程先结束,则不会产生僵尸进程。

2. 特点与危害:
僵尸进程没有任何代码、数据或堆栈,占用资源极少,但它依然存在于系统的任务列表中(进程表中占据一个位置)。如果大量产生,会耗尽系统的进程表资源。
识别方法:使用ps命令查看时,如果进程名称旁边出现<defunct>,则表明该进程为僵尸进程(例 5-6 演示了如何通过让父进程长时间睡眠来观察僵尸进程)。


2.3 进程退出状态与等待

为了防止子进程变成僵尸进程,父进程需要调用wait()或waitpid()来等待子进程结束并获取其状态。

1.wait()系统调用(表 5.4)

  • 头文件:#include <sys/wait.h>、#include <sys/types.h>
  • 原型:pid_t pid = wait(int *status);
  • 功能:暂停调用它的进程(父进程),直到有子进程结束。
  • 返回值:如果不存在子进程,返回 -1;若有任何一个子进程结束,则返回该子进程的 PID 并保存其返回状态在status中。

2.waitpid()系统调用(表 5.5)与wait()的区别

  • wait()的局限性:只能等待任意一个子进程结束。当一个父进程有多个子进程时,若某个子进程结束,wait只能得到其结束状态,无法再得到其他子进程的状态,容易导致其他子进程变成僵尸进程。
  • waitpid()的改进:
    • 提供了非阻塞调用方式(通过设置Options选项,如WNOHANG:如果没有已结束的子进程则马上返回,不等待)。
    • 可以等待指定的子进程(通过传入特定的pid参数,如pid > 0等待指定 ID 的子进程;pid = 0等待同组任意子进程等)。
    • status参数用于保存返回状态,options参数用于控制等待行为。

3. 退出状态检测宏(表 5.6)
系统提供了专门的宏来检测status变量,判断子进程的退出情况:

  • WIFEXITED(status):如果子进程正常结束,则此宏为真,此时可调用WEXITSTATUS(status)取得子进程exit()返回的结束代码。
  • WIFSIGNALED(status):如果子进程是因为信号而结束,则此宏为真,此时可调用WTERMSIG(status)取得导致子进程终止的信号代码。
  • WIFSTOPPED(status):如果子进程处于暂停执行情况(如收到 SIGSTOP),则此宏为真,此时可调用WSTOPSIG(status)取得引发子进程暂停的信号代码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询