news 2026/10/1 22:04:55

【Linux笔记】Linux进程与守护进程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux笔记】Linux进程与守护进程

一、 进程组

1.1 进程组的本质

进程组:是一个或多个进程的集合,主要用于作业控制。

进程组具有以下特征:

  • 唯一标识:每个进程组拥有一个进程组 ID(PGID),其数据类型与 PID 完全相同。

  • 成员关系:一个进程组由一个或多个进程组成,组内进程共享同一个 PGID。

  • 用途:主要用于作业控制中的信号分发和状态管理。

1.2 组长进程

组长进程:进程的 PID 等于其所在进程组的 PGID, 即PGID == PID时,该进程为组长。

组长进程的职责:

  • 创建进程组:当某个进程首次被放入一个全新的进程组时,它自动成为该组的组长。

  • 在组内派生子进程:子进程默认继承父进程的 PGID(本质是子进程PCB写实拷贝自父进程)。

进程组的生命周期:

  • 起点:组长进程创建进程组的时刻。

  • 终点:组内最后一个进程终止或转入其他进程组的时刻。

  • 关键点:组长进程的终止并不意味着进程组消亡,只要组内仍有其他存活进程,进程组就继续存在。

1.3 示例解释

通常我们都是使用管道将多个进程编成一个进程组

hamber@VM-0-14-ubuntu:~$ sleep 100 | sleep 200 | sleep 300 & hamber@VM-0-14-ubuntu:~$ ps axj |head -1 && ps ajx | grep sleep | grep -v grep PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 281066 312705 312705 281066 pts/1 313057 S 1003 0:00 sleep 100 281066 312706 312705 281066 pts/1 313057 S 1003 0:00 sleep 200 281066 312707 312705 281066 pts/1 313057 S 1003 0:00 sleep 300

在 Shell 中执行ps -o pid,pgid,ppid,comm | cat时:

ps -o pid,pgid,ppid,comm | cat PID PGID PPID COMMAND 281066 281066 281058 bash 310950 310950 281066 ps 310951 310950 281066 cat bash (PID=281066, PGID=281066) ├── ps (PID=310950, PGID=310950) ← 组长,PGID 等于自身 PID └── cat (PID=310951, PGID=310950) ← 加入 ps 所在的进程组

二、会话

2.1 会话的本质

会话:是比进程组更高一层的抽象,它是一个或多个进程组的集合。

会话的设计目的是将一次完整的用户登录交互(从登录到退出)所涉及的所有进程组织在一起。

2.2 会话的完整生命周期

将一次完整的用户登录交互(从登录到退出)所涉及的所有进程组织在一起。

这期间发生的所有事情构成了"一次完整的交互",它包括:

- Shell 本身(bash/zsh) - 你执行的每一条命令及其子进程(vim file.txt、gcc main.c、make) - 管道链中的所有进程(cat log | grep error | sort | uniq -c) - 后台运行的任务(sleep 3600 &、find / -name "*.tmp" &) - 被 Ctrl+Z 挂起后尚未恢复的作业 - 甚至是你启动的子 Shell(bash 嵌套调用)

它们都有一个共同的身份标签:它们都是"这次登录"产生的,会话就是用来给这批进程贴上同一个标签的容器。

当你在终端上执行ssh user@server登录后,到最终exit退出为止。

用户 ssh 登录 │ ▼ login/sshd 创建新会话 (setsid) │ SID = Shell PID │ 绑定控制终端 /dev/pts/N │ ├── 用户执行各种命令(进程不断创建、终止) ├── 进程组不断创建、消亡 ├── 前台进程组反复切换 │ ... 可能持续数小时、数天 ... │ ▼ 用户 exit / 关闭终端 / 网络断开 │ ▼ 控制终端释放 SIGHUP 发送给会话首进程 会话内所有进程收到清理信号 最后一个进程退出 → 会话消亡

三、控制终端

3.1 控制终端的认识

控制终端是:一个与会话绑定的终端设备

每个会话最多拥有一个控制终端,每个控制终端也最多被一个会话所控制。

终端设备:由内核管理的、带有输入输出处理管线的双向字符流通道。

关键点:

  • 控制终端不是终端设备本身的属性,而是会话的属性

  • 例如:对同一个/dev/pts/0设备文件,在被某个会话设为控制终端时才是"控制终端";在没有会话绑定它时,它只是一个普通的字符设备。

对于同一个/dev/pts/0设备文件而言:

普通打开终端设备 作为控制终端 ───────────────── ──────────────── open("/dev/pts/0", O_RDWR) 通过特定机制建立控制关系 仅获得一个文件描述符 会话获得信号路由能力 无特殊内核行为 内核自动分发 SIGINT/SIGTSTP/SIGHUP 任何进程都可以打开 仅会话首进程可以建立(或继承) 关闭 fd 即断开 需要显式脱离或会话终结才断开

控制终端与会话:

会话 (SID) │ ├── 控制终端(最多一个) │ ├── 控制进程 = 会话首进程 │ ├── 前台进程组(同一时刻最多一个) │ └── 后台进程组(零个或多个) │ └── 信号分发规则: ├── Ctrl+C → SIGINT → 前台进程组所有成员 ├── Ctrl+\ → SIGQUIT → 前台进程组所有成员 ├── Ctrl+Z → SIGTSTP → 前台进程组所有成员 └── 终端断开 → SIGHUP → 控制进程(会话首进程)

3.2 进程组的分类

在一个会话内部,进程组并非平级罗列,而是根据其与控制终端的关系被严格划分为:

  • 前台进程组

  • 后台进程组

3.2.1 前台进程组

定义:当前正在"使用"控制终端的那个进程组,在同一时刻,一个会话中有且仅有一个前台进程组。

前台进程组核心特征:

  • 可以从控制终端读取输入(read()、fgets()等不会被阻塞或拒绝)。

  • 向控制终端写入输出时不受限制。

  • 接收来自终端的信号:Ctrl+C → SIGINT,Ctrl+Z → SIGTSTP,Ctrl+\ → SIGQUIT。

典型场景:

你在 Shell 中输入vim file.txt并回车,Shell 将vim所在的进程组设为前台进程组。此时你的键盘输入直接送达vim,Ctrl+C 也是发给vim。

3.2.2 后台进程组

定义:属于某个会话,且不是该会话当前前台进程组的所有进程组,都是后台进程组。

类比理解:

场景:“老师上课” 1. 会话 = 一个班级。 - 例如:“高三一班”,所有在这个班里的人和事,都属于这个会话。 2. 前台进程组 = 当前正在被老师“点名提问”的那个同学。 - 只有这个同学能直接跟老师对话(接收键盘输入),他的回答全班都能听到(输出到屏幕)。 - 此时,全班同学的目光和注意力都集中在他身上。 3. 后台进程组 = 班里其他所有正在埋头做题的同学。
  • "属于某个会话" = 他们都是"高三一班”的人(同一班级)。

  • "且不是该会话当前前台进程组"= 只要这个同学现在没有被老师点名站起来回答问题。

  • "都是后台进程组"= 那无论他在做数学卷子、在翻书、还是在发呆,他都属于"后台”状态。

后台进程组的限制:

  • 不能从控制终端读取输入。

    • 如果尝试read(),内核会向该进程组发送 SIGTTIN 信号,默认行为是暂停进程(SIGSTOP 效果)。

    • 这是为了防止后台进程偷偷抢走用户的键盘输入。

  • 写入控制终端通常被允许,但可以通过stty tostop禁止。

    • 若禁止后后台进程尝试写终端,内核发送 SIGTTOU 信号,同样导致暂停。

    • 这避免了后台输出意外覆盖前台程序的界面。

  • 不接收终端产生的信号。

    • Ctrl+C 只影响前台进程组,后台进程组完全不受影响。

四、作业控制

4.1 作业的认识

作业:是针对Shell在用户层面的概念,表示用户为完成某项任务而启动的一个或多个进程的集合,进程间互相协作完成任务。

例如:作业通常是一个进程管道

# 这是一个作业,包含三个进程,前台执行 cat access.log | grep "404" | wc -l # 后台执行作业 cat access.log | grep "404" | wc -l & # 输出示例:[1] 2345 # [1] 是作业号(Job Number),2345 是组长进程的 PID

作业与进程组:作业与进程组相等于一枚硬币的两面。

  • 对于用户层面而言,进程组执行用户指定的任务被视为做作业

  • 对于内核层面而言,进程组是作业的底层实现机制。

4.2 作业号与标识符

作业号:是 Shell 内部的小整数(1、2、3...),由 Shell 分配和管理。

hamber@VM-0-14-ubuntu:~$ jobs ↓ 作业号 [1]- Running sleep 1 | sleep 2 | sleep 10 & [2]+ Running sleep 1 | sleep 2 | sleep 12 &

标识符:%

%符号用于引用作业:

  • %1、%2:按编号引用。

  • %+或%%:当前默认作业(最近被放入后台或挂起的作业)。

  • %-:上一个默认作业(当前默认作业之前的那个)。

4.3 作业状态转换

Start command (启动命令) │ | ▼ 前台运行 暂停运行 ┌────────────┐ Ctrl+Z (SIGTSTP) ┌──────────┐ │ Foreground │ ──────────────────────→ │ Stopped │ │ (Running) │ └──────────┘ └────────────┘ │ │ │ & │ fg / bg + SIGCONT ▼ ▼ 后台启动 前台恢复 ┌────────────┐ ┌──────────────┐ │ Background │ ←────────────────── │ Foreground │ │ (Running) │ fg %N │ (Resumed) │ └────────────┘ └──────────────┘ │ │ 正常退出或被信号终止 ▼ ┌───────────┐ │ Completed │ └───────────┘

4.4 作业相关命令

命令功能
command &在后台启动作业
Ctrl+Z挂起当前前台作业(发送 SIGTSTP 给前台进程组)
jobs列出当前 Shell 的所有作业及其状态
jobs -l列出作业及其所有进程的 PID
jobs -p仅列出组长进程的 PID
fg %N将第 N 号作业调回前台继续执行
bg %N让第 N 号已停止的作业在后台继续执行(发送 SIGCONT)
kill %N向第 N 号作业的所有进程发送 SIGTERM

五、守护进程

5.1 守护进程的认识

守护进程:存在于独立的会话中,脱离原有的控制终端,是在后台长期运行的特殊进程。

守护进程的典型特征:

  • 没有控制终端,独立于用户登录会话。

  • 不产生终端输出,日志写入文件或系统日志。

  • 生命周期很长,往往伴随整个系统运行周期。

  • 命名惯例:进程名以d结尾(如sshd、httpd、crond、syslogd)。

需要守护进程化的原因:

以网络服务器为例:

  • 若服务器进程直接由用户终端启动,当用户关闭终端或 SSH 断开时,SIGHUP 信号会终止服务器。

  • 守护进程化使服务器脱离任何终端和会话,不再受用户登录状态影响。

5.2 守护进程化的步骤

5.2.1 第一步:忽略特定信号

signal(SIGCHLD, SIG_IGN); // 忽略子进程退出信号,由内核进行回收子进程,避免僵尸进程 signal(SIGPIPE, SIG_IGN); // 忽略管道断裂信号,避免 write 导致进程终止

忽略特定信号SIGCHLD的原因:

守护进程的核心特征是长期运行且无交互,它通常在主循环中处理业务逻辑(如接受网络连接、处理定时任务),而不是在一个专门的"子进程回收循环"中等待。

SIGCHLD:默认动作是 SIG_DFL,而 SIG_DFL 对 SIGCHLD 的语义是"不做任何事"。

这意味着内核不会自动回收子进程,显式忽略后,内核会自动回收子进程资源,无需父进程调用wait()。

忽略特定信号SIGPIPE的原因:

在一个网络服务器(守护进程),如果用户关闭客户端,而此时服务端还在向客户端写入时,会触发SIGPIPE信号,导致服务端进程被信号杀死,从而引起服务器进程被关闭。

SIGPIPE:当向已关闭读端的管道或 socket 写入时触发。

忽略后,write()返回 -1 并设置errno = EPIPE,程序可自行处理错误而非直接崩溃。

5.2.2 第二步:fork 并让父进程退出

pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(EXIT_FAILURE); } if (pid > 0) { // 父进程退出 exit(EXIT_SUCCESS); } // 此后代码仅在子进程中执行

核心目的: 让子进程能够创建新会话,创建新会话的前提是当前进程不是进程组长(PID = PGID)

  • 确保子进程不是进程组组长。

  • 子进程 PID 是新的,而 PGID 继承自父进程,PGID 是进程组长的PID,因为PID具有唯一性,所以子进程的PID不可能等于PGID

  • 因为父进程退出,子进程被 init 进程(PID=1)收养,成为孤儿进程,不会导致僵尸进程。

5.2.3 第三步:创建新会话

pid_t sid = setsid(); if (sid < 0) { perror("setsid"); exit(EXIT_FAILURE); }

创建新会话后的效果:

  • 子进程成为新会话的首进程(SID = PID)。

  • 子进程成为新进程组的组长(PGID = PID)。

  • 彻底脱离原控制终端。

  • 此时该进程不再属于任何终端会话,Ctrl+C、Ctrl+Z、终端关闭都不会影响它。

5.2.4 更改工作目录

将守护进程的工作路径放置在根目录下

if (chdir("/") < 0) { perror("chdir"); exit(EXIT_FAILURE); }

5.2.5 重置文件描述符

守护进程存在于独立的会话中失去了控制终端,但失去控制终端 ≠ 文件描述符自动关闭。

fd 0/1/2 仍然打开着,仍然指向原来的设备节点,这可能导致的问题:

  • 阻止终端/文件系统卸载,只要守护进程持有这些 fd,原有对应的设备或文件系统就无法被释放或卸载

  • 写入已失效的终端导致不可预测行为

  • 第三方库的隐式 I/O

将守护进程的文件描述符重定向到 /dev/null的原因:

  • 写入即消失:所有重定向或写入到该 /dev/null 文件的数据,都会被操作系统内核直接丢弃,不占用任何磁盘空间。

  • 读取即空:如果你尝试从它里面读取内容,它会立刻返回一个" 文件结束符(EOF)",即什么都读不到。

// 方式一:关闭后重定向到 /dev/null int fd = open("/dev/null", O_RDWR); if (fd != -1) { dup2(fd, STDIN_FILENO); // fd 0 → /dev/null dup2(fd, STDOUT_FILENO); // fd 1 → /dev/null dup2(fd, STDERR_FILENO); // fd 2 → /dev/null close(fd); //关闭fd文件描述,此时/dev/null存在标准输入 fd=0、标准输出 fd=1、标准错误fd=2, 进行指向,引用计数为3。 }

5.3 完整的守护进程化函数实现

// Daemon.hpp #include <unistd.h> #include <stdlib.h> #include <signal.h> #include <fcntl.h> #include <sys/stat.h> void Daemon() { // 1. 忽略可能干扰的信号 signal(SIGCHLD, SIG_IGN); signal(SIGPIPE, SIG_IGN); // 2. fork,父进程退出 pid_t pid = fork(); if (pid < 0) { exit(EXIT_FAILURE); } if (pid > 0) { exit(EXIT_SUCCESS); } // 3. 创建新会话,脱离控制终端 if (setsid() < 0) { exit(EXIT_FAILURE); } // 4. 更改工作目录 if (chdir("/") < 0) { exit(EXIT_FAILURE); } // 5. 重置文件描述符到 /dev/null int fd = open("/dev/null", O_RDWR); //读写的方式打开 if (fd != -1) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); if (fd > STDERR_FILENO) { close(fd); } } }

六、总结

用户登录 │ ▼ 创建会话 (Session) │ SID = Shell 的 PID │ 控制终端 = /dev/pts/N │ ├── Shell 进程(会话首进程 / 控制进程) │ PGID = PID = SID │ ├── 前台进程组(同一时刻最多一个) │ 接收 Ctrl+C / Ctrl+Z 信号 │ 可读写终端 │ ├── 后台进程组 1 │ 不接收终端信号 │ └── 后台进程组 2 不接收终端信号 守护进程: 通过 fork + setsid 脱离上述h结构 无会话归属(或自建新会话) 无控制终端 不受任何用户操作影响 独立运行直至系统关闭或手动终止
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 22:01:41

053级联合并排序

级联合并排序 (Cascade Merge Sort) 053级联合并排序&#xff1a;瀑布流的排序智慧故事&#xff1a;瀑布流的智慧 想象一条山间瀑布&#xff1a;水从高处的宽大水潭开始&#xff0c;分成多股支流奔涌而下&#xff0c;每一级台阶都比上一级更窄&#xff0c;但最终所有水流在山脚…

作者头像 李华
网站建设 2026/10/1 21:57:53

从监工到派活:用Claude实现自动化任务执行的高效方法

1. 从“监工模式”到“派活模式”&#xff1a;为什么你该换一种方式用 Claude大多数人用 Claude 的方式&#xff0c;本质上跟盯工地没什么区别。打开对话框&#xff0c;敲一句“帮我写个函数”&#xff0c;等它吐出来&#xff0c;看一眼&#xff0c;不满意&#xff0c;再补一句…

作者头像 李华
网站建设 2026/10/1 21:57:47

16G显存如何流畅运行Qwen-Image 2.1?全套量化方案与避坑指南

先说结论&#xff1a;能跑&#xff0c;但要看你怎么跑。这里的“跑”分为几种情况&#xff1a;如果你指望用一张16G显存的显卡把官方原版FP16权重完整加载进来&#xff0c;然后“一键出图”&#xff0c;那答案是否定的&#xff1b;但如果选择量化版本&#xff0c;用第三方插件或…

作者头像 李华
网站建设 2026/10/1 21:57:05

呼啦圈越练越强,ABAP 里怎样设计一件会成长的装备

小雪拿到呼啦圈时,吸引人的地方不是它的外形,而是它会随着使用逐渐变强。玩家攻略对这件武器的描述相当一致,基础攻击力为 100,熟练度每增加 1%,攻击效果增加 3,练到 100% 时可按 400 来理解。攻略也提到,练熟练度需要持续战斗,呼啦圈并非拿到手就处于最强状态。这里讨…

作者头像 李华
网站建设 2026/10/1 21:55:34

AI Agent 到底是什么:从聊天机器人到数字员工的三个阶段

AI Agent 硬件系列 第一篇AI Agent 不是「更聪明的聊天机器人」&#xff0c;而是 AI 应用形态的第三次换代——从「回答问题」到「辅助工作」再到「自主执行任务」。三者的分界线在于&#xff1a;任务流程是谁定的、执行是不是闭环。 一句话结论 AI Agent &#xff1d; AI 大模…

作者头像 李华
网站建设 2026/10/1 21:53:54

纯HTML5劳动节网页:零依赖、可配置、跨端动效实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华