Apipi*头像
关注
30天速通Linux 第五章Linux 进程管理封面图

30天速通Linux 第五章Linux 进程管理

学习资料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) 取得引发子进程暂停的信号代码。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2501_93802159/article/details/167225950

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--