欢迎光临
我们一直在努力

《Linux网络编程·加餐》进程间关系与守护进程

🔥小叶-duck:个人主页

❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》 《Linux系统从入门到实践》《Linux网络从入门到实践》 《Qt 方寸极境》 《MySQL》

未择之路,不须回头 已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

一、进程组

1.1 什么是进程组

1.2 组长进程

1.2.1 进程组组长的作用及生命周期

1.3 补充:ps 命令选项 -e/-o

二、会话

2.1 什么是会话

2.2 Linux Session(会话)与登录完整过程分析

2.3 补充:grep 命令选项 -E

2.4 会话 ID(SID)

三、控制终端

3.1 什么是控制终端

3.2 会话、进程组和控制终端关系

3.3 关键易错点及关系链路总结

四、作业控制

4.1 什么是作业 job

4.2 什么是作业控制 Job Control

4.3 作业号

4.3.1 后台命令 & 的本质

4.3.2 默认作业 + 与 −

4.4 作业状态

4.5 作业的挂起与切回

4.5.1 作业挂起 Ctrl+Z

4.5.2 作业切回 fg 命令

4.6 作业控制相关的信号

五、守护进程

5.1 什么是守护进程

5.2 守护进程的核心条件

5.3 如何创建会话 setsid ()

5.3.1 为什么不能由进程组组长调用 setsid?

5.4 守护进程代码设计

5.4.1 为什么存在 nochdir 参数?

5.4.2 为什么存在 noclose 参数?

补充:/dev/null 文件

结束语


前言

      在 Linux 系统编程中,进程组、会话、控制终端与作业控制,是理解 Shell 交互逻辑、后台任务、守护进程的底层基础。很多人日常熟练使用&后台运行、Ctrl+Z挂起、fg/bg切换作业,却并不清楚这些命令在内核层面如何运作。

      本篇将从进程组的基础概念入手,讲解进程组长的生命周期,结合ps实操命令观察进程组信息;接着延伸到会话 Session,拆解 SSH 登录时会话的完整建立流程,搞懂会话首进程与 SID 的含义;再梳理会话‑进程组‑控制终端三者之间的关联,理清前台、后台进程组的划分逻辑,解释终端快捷键产生信号的底层原理。之后围绕作业控制,讲解作业号、后台&的本质、作业状态切换,剖析Ctrl+C、Ctrl+Z等按键对应的信号行为。最后落脚到守护进程,解析setsid()创建会话的约束条件,一步步完成守护进程代码实现,解释nochdir、noclose参数以及/dev/null的作用。

      读完本文,你可以打通 Shell 交互底层逻辑,不仅知其命令用法,更能理解内核层面的模型,为信号、进程编程、守护进程开发打下扎实理论基础。

一、进程组

1.1 什么是进程组

在之前的学习中我们已经了解了进程的基本概念,每一个进程都有一个唯一的进程 ID(PID)用来标识自身。但实际上,除了 PID 之外,每个进程还归属于一个进程组(PGID)。

进程组是一个或者多个进程的集合,一个进程组可以包含多个进程。每一个进程组也有一个唯一的进程组 ID(PGID),PGID 和 PID 类似,同样是一个正整数,可以存放在pid_t数据类型中。 在 Linux 内核中,task_struct结构体里有pgrp字段记录进程所属的进程组 ID,进程组是内核进行进程批量管理的基本单位。

1.2 组长进程

每一个进程组都有一个组长进程,组长进程的 ID 等于其进程 ID,也就是说组长进程的 PID 和 PGID 是相同的。我们可以通过ps命令观察到组长进程的现象:

[node@localhost code]$ ps -o pid,pgid,ppid,comm | cat

输出结果:

PID PGID PPID COMMAND
2806 2806 2805 bash
2880 2880 2806 ps
2881 2880 2806 cat

从结果上看,ps进程的 PID(2880)和 PGID(2880)相同,说明ps进程是该进程组的组长进程,该进程组包括ps和cat两个进程。 在 Shell 中执行管道命令时,Shell 会为整条管道创建一个新的进程组,管道中的第一个命令通常成为组长进程,所以这也是为什么 ps 的PID 和 PGID 相同,并且 ps 的 PGID 和 cat 的 PGID 相同的原因。

1.2.1 进程组组长的作用及生命周期

进程组组长的作用

  • 进程组组长可以创建一个进程组,也可以创建该组中的其他进程。
  • 组长进程是进程组的创建者,但它并不拥有对组内其他进程的特殊控制权,组内进程之间是平等的。

进程组的生命周期

  • 进程组从创建开始,到其中最后一个进程离开为止。
  • 注意:只要某个进程组中还有一个进程存在,该进程组就存在,这与其组长进程是否已经终止无关。组长进程退出后,进程组依然存在,剩余进程的 PGID 不会发生变化,直到组内最后一个进程退出,进程组才会被内核销毁。

为什么即使进程组组长都被终止了这个进程组还能存在呢?原因后面讲解了setsid函数时会进行解释。

1.3 补充:ps 命令选项 -e/-o

 ps 作为 Linux 用来查看进程快照的工具,‑e 和 ‑o 是非常高频的组合参数,常用于排查进程 PID、PGID、PPID 等属性。

-e(等价于 -A)

  • 全称:every,显示系统内全部进程。
  • 默认不加 -e:ps 默认只输出当前终端会话下启动的进程,很多后台进程看不到。
  • 加上 -e:遍历系统所有进程,不管属于哪个用户、哪个终端,全部展示出来。

示例:ps -e,打印整机全部进程。

-o:自定义输出字段(格式化输出)

  • ‑o 允许手动指定需要打印的列,以逗号,分隔多个字段,逗号前后可以加空格。
  • 语法格式:

ps -o 字段1,字段2,字段3

  • 示例中:ps -eo pid,pgid,ppid,comm
    • pid:进程 ID
    • pgid:进程组 ID
    • ppid:父进程 ID
    • comm:程序命令名(进程名)

注意:‑o只定义输出哪些列,不会扩大进程的查询范围;所以一般搭配 -e 使用:‑eo,代表:查看全部进程,并且只输出我指定的这几列。

完整命令拆解:

ps -eo pid,ppid,pgid,cmd | head -1 && ps -eo pid,ppid,pgid,cmd | grep process_task

输出结果:

PID PPID PGID CMD
2972349 2970661 2972349 ./process_task
2972350 2972349 2972349 ./process_task
2972354 2972040 2972353 grep –color=auto process_task

易踩坑点

  • -o 只控制显示哪些列,不会改变进程集合;不加‑e就只会看到当前会话进程。
  • 字段之间用逗号分隔,逗号不能加引号;逗号两侧可以带空格。
  • -eo 等价于 -e -o,是合并简写写法。
  • 二、会话

    2.1 什么是会话

    刚刚我们谈到了进程组的概念,那么会话又是什么呢?会话其实和进程组息息相关,会话可以看成是一个或多个进程组的集合,一个会话可以包含多个进程组。每一个会话也有一个会话ID(SID)。

    2.2 Linux Session(会话)与登录完整过程分析

    核心概念

    • 会话 Session:一次登录连接对应的进程集合,1 个 Xshell/SSH 终端窗口就对应1 个会话。
    • 会话首进程 (session leader):创建这个会话的进程,登录场景下就是bash,会话 ID (SID) = bash 的 PID。
    • 一个会话内部可以包含多个进程组(job 作业);同一时刻,会话只能有1 个前台进程组,其余为后台进程组。
    • bash 自身独占一个进程组;我们敲命令执行的业务程序,会新建其他进程组。

    SSH/Xshell 登录完整流程

  • 客户端连接:Xshell(SSH 客户端)通过网络向 Linux 服务器发起 ssh 连接请求。
  • 认证阶段:服务器sshd守护进程接收连接,校验用户名、密码 / 密钥,完成用户登录认证。
  • 创建新会话(Session):登录成功之后,操作系统会为本次登录新建一个会话。
  • 生成 bash 会话首进程:sshd 会 fork、exec 启动bash。 bash 调用setsid(),把自己的 PID 设置为会话 ID (SID),bash 就成为这个会话的会话首进程。 同时 bash 自己形成独立的进程组(PID=PGID),也就是图里的进程组 1。
  • 分配终端控制:该会话会绑定当前伪终端(/dev/pts/X),这个终端就是会话的控制终端。 输入输出全部交给这个 bash,我们就拿到可以敲命令的 shell 窗口。
  • 用户执行命令,产生更多进程组
    • 在 bash 敲一条普通命令、管道命令,shell 会 fork 出新进程,创建新的进程组(job)。
    • 例:proc2 | proc3,管道整条命令是一个作业,形成进程组 2;
    • 再执行proc4;proc5;proc6,又生成进程组 3。
    • bash 仍然留在自己原始的进程组 1,不参与业务进程组。

    一个 Session 下允许多个进程组并存,但只有一个前台进程组,可以读取键盘输入;其余放后台。

    2.3 补充:grep 命令选项 -E

    -E 全称 –extended-regexp,开启扩展正则表达式模式。

    -E选项使用方式:

    grep -E 'sleep|process_task'

    | 在扩展正则中代表或,匹配包含 sleep 或者 process_task 的行。

    如果不加 -E,基础正则里的 | 只是普通字符,没有 “或” 的含义;想要或逻辑需要写成 \\| 转义。

    等价写法

  • grep -E 'sleep|process_task'
  • egrep 'sleep|process_task' (egrep 就是 grep -E 的别名)
  • 不使用 – E 的等价写法:grep 'sleep\\|process_task'
  • 2.4 会话 ID(SID)

    上边我们提到了会话ID,那么会话ID是什么呢?我们可以先说一下会话首进程,会话首进程是具有唯一进程ID的单个进程,那么我们可以将会话首进程的进程ID当做是会话ID。 注意:会话ID在有些地方也被称为会话首进程的进程组ID,因为会话首进程总是一个进程组的组长进程,所以两者是等价的。

    三、控制终端

    3.1 什么是控制终端

    在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端。控制终端是保存在PCB中的信息,我们知道fork进程会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端

    在没有做重定向的前提下,进程的标准输入、标准输出、标准错误全部绑定到控制终端:

    • 进程读取标准输入,本质就是读取键盘输入;
    • 进程向标准输出、标准错误写数据,内容就打印到终端显示器上。

    3.2 会话、进程组和控制终端关系

    会话、进程组和控制终端之间存在紧密的约束关系,核心规则整理如下:

    • 一个会话最多拥有一个控制终端。会话首进程打开终端设备(物理终端或者 SSH 伪终端/dev/pts/*)之后,该终端就成为本次会话的控制终端。
    • 和控制终端建立连接的会话首进程,叫做控制进程,登录场景下就是 bash。
    • 同一个会话下可以存在多个进程组,划分为1 个前台进程组 + 若干个后台进程组。
    • 只要会话绑定了控制终端,就一定存在唯一的前台进程组,会话剩下全部进程组都属于后台进程组。
    • 在终端按下中断快捷键Ctrl + C、退出快捷键Ctrl + \\,内核会把对应的信号发送给前台进程组里面的全部进程,而不是只发给某一个进程。
    • 当终端检测到网络、物理链路断开(例如关闭 Xshell/SSH 窗口),内核会向控制进程(会话首进程 bash)发送挂断信号SIGHUP。

    这些特性的关系如下图所示:

    补充拓展:

  • 后台进程组默认无法读取控制终端键盘输入,如果尝试读标准输入会收到SIGTTIN信号,进程会停止;
  • 关闭终端窗口,bash 收到 SIGHUP,bash 退出,会话销毁;会话内的进程默认也会收到 SIGHUP;nohup就是让进程忽略挂断信号,脱离终端继续运行(后续会讲解);
  • 控制终端属于会话层面的属性,不是单个进程属性,组内所有进程共享同一个控制终端。
  • 3.3 关键易错点及关系链路总结

    关键易错点:

  • 控制终端属性跟随 PCB,fork 会继承;但是 exec 不会改变。
  • 快捷键信号只发给前台进程组,后台进程不会收到 Ctrl+C。
  • SSH 断开,最先收到 SIGHUP 的是会话首进程 (bash),不是业务子进程。这就是为什么后台运行程序需要 nohup,用来忽略 SIGHUP 信号。
  • 一个会话最多一个控制终端;一个终端同一时刻只属于一个会话。
  • 后台程序尝试读取控制终端键盘,会收到SIGTTIN信号,默认暂停进程。
  • 关系链路:

    终端设备 → 会话(session) → 会话首进程(控制进程bash)

    会话内划分多个进程组
    ├─前台进程组(占用终端,接收键盘信号)
    └─后台进程组(无法读键盘)

    四、作业控制

    4.1 什么是作业 job

    作业是 Shell 用户视角 的任务概念,内核层面没有 “job” 这个结构体,内核只认识进程、进程组、会话。

    • 一个作业 = 用户在 Shell 敲一条命令启动的任务。
    • 作业可以只有单个进程:sleep 100
    • 作业可以包含多个进程(管道):ps ajx | grep sleep,管道每一段都是独立进程,它们同属同一个进程组,也就是同一个 job。

    本质:Shell 的 job 几乎等价于内核的进程组。Shell 给每个 job 分配一个作业编号 [1] [2],内核用 PGID 标识进程组。

    4.2 什么是作业控制 Job Control

    Shell 管理前后台,操作对象不是单个进程,而是作业(进程组)。

    • 前台作业:占有控制终端,可以读取键盘输入;一个会话同一时刻只能有 1 个前台作业。按下Ctrl+C / Ctrl+\\信号发给整个前台作业(整个进程组所有进程)。
    • 后台作业:不占用终端键盘输入;一个 Shell 可以同时存在任意多个后台作业。后台作业尝试读键盘会收到SIGTTIN信号,进程被暂停。

    job、进程组、会话三者关系:

  • 内核:Session 会话 → 多个进程组 (PGID) → 多个 task_struct 进程。
  • Shell 用户层:把一个进程组包装成一个 Job 作业,给它分配 job 号。
  • 1 个 Job ↔ 1 个进程组。一个会话(bash)里面可以有很多 job。

    示例:

    ps ajx | grep sleep | wc -l &

    管道产生 3 个进程,PGID 完全相同,属于同一个进程组,也就是同一个后台 job,jobs只会看到一条作业记录。

    4.3 作业号

    放在后台执行的程序或命令称为后台命令,可以在命令的后面加上 & 符号从而让Shell识别这是一个后台命令,后台命令不用等待该命令执行完成,就可立即接收新的命令,另外后台进程执行完后会返回一个作业号以及一个进程号(PID)。

    4.3.1 后台命令 & 的本质

    例如下面的命令在后台启动了一个作业,该作业由两个进程组成,两个进程都在后台运行:

    cat /etc/filesystems | grep ext &

    • 命令末尾加 & → Shell 把这条命令作为后台作业启动。
    • Shell 不阻塞等待它执行完,立刻返回提示符,可以继续敲下一条命令。
    • 这条管道命令包含 2 个进程(cat + grep),但它们属于同一个进程组(同一个作业),所以 Shell 只分配 1 个作业号。

    关键:作业号是 Shell 层概念,一个作业(管道)对应一个进程组,不管里面有多少个进程。

    执行结果如下:

    [1] 2202 ← 第1行:作业号 + 进程组ID(PGID)
    ext4 ← 第2行:grep 过滤结果
    ext3 ← 第3行
    ext2 ← 第4行
    # 按下回车
    [1]+ 完成 cat /etc/filesystems | grep –color=auto ext ← 第6行

    第 1 行 [1] 2202

    • [1]:作业号(Shell 分配,从 1 递增)
    • 2202:进程组 ID(PGID),也是管道中最后一个进程的 PID
    • 注意:管道里 cat 和 grep 是两个独立进程,但只显示一个数字 —— 因为显示的是进程组领头进程的 PID,不是全部进程。

    第 2 – 4 行 ext4 ext3 ext2

    • 后台作业的标准输出仍然打印到控制终端(后台作业可以写终端,只是不能读键盘)。

    第 6 行 [1]+ 完成 …

    • 这行是作业结束通知,Shell 不会主动打断你,而是在你下一次按回车 / 执行命令时才打印出来(避免污染当前输入行)。

    4.3.2 默认作业 + 与 −

    Shell 维护一个作业栈,用两个特殊标记区分优先级:

    标记含义作用
    + 当前默认作业 不带作业号的 fg / bg / wait 默认操作它
    次默认作业 上一个默认作业,当 + 作业退出后,它自动递补为新的 +
    无符号 普通后台作业 必须显式指定 %n 才能操作

    规则:

  • 同一时刻只有一个 + 作业,只有一个 − 作业。
  • 新启动的后台作业会挤掉原来的 +:原来的 + 变成 −,新作业变成 +。
  • + 作业结束后,− 自动升级为 +。
  • 实操演示:

    sleep 100 & # [1] → [1]+ (唯一作业,既是+也是-)
    sleep 200 & # [2] → [2]+ [1]- (新作业变+,旧作业降为-)
    jobs
    # [1]- Running sleep 100
    # [2]+ Running sleep 200
    fg # 不带参数,默认把 [2]+ 提到前台

    4.4 作业状态

    常见的作业状态如下表所示:

    作业状态含义
    正在运行【Running】 后台作业(&),表示正在执行
    完成【Done】 作业已完成,返回的状态码为 0
    完成并退出【Done (code)】 作业已完成并退出,返回的状态码为非 0
    已停止【Stopped】 前台作业,当前被 Ctrl+Z 挂起
    已经终止【Terminated】 作业被终止

    4.5 作业的挂起与切回

    4.5.1 作业挂起 Ctrl+Z

    在前台运行程序时按下 Ctrl + Z: 终端会向整个前台进程组(当前 Job 作业)发送 SIGTSTP 信号,进程组内所有进程暂停执行(停止状态),不是退出,进程还在内核中留存

    示例:运行死循环程序 ./test1,按下 Ctrl+Z 输出:[1]+ 已停止 ./test1

    • [1]:作业号
    • +:代表是默认作业
    • 已停止:作业被挂起,进程暂停,不再运行代码,资源没有释放。

    区分:

    • Ctrl+C:发送SIGINT,终止前台作业,进程直接退出;
    • Ctrl+Z:发送SIGTSTP,暂停前台作业,进程保留,进入后台停止状态。

    重要:Ctrl+Z 只对前台作业生效;后台作业按此快捷键不会产生任何影响。

    4.5.2 作业切回 fg 命令

    fg:把后台停止 / 后台运行的作业切回前台,重新占有控制终端。

    • 不带参数 fg:操作带 + 的默认作业。
    • 支持多种作业选择语法,表格整理:(不需要掌握,了解即可)
    参数写法含义
    %n n 为数字,指定作业号,例如 %1
    %string 匹配以 string 开头的命令的作业
    %?string 匹配命令中包含 string的作业
    %+ / %% 最近的默认作业(带+标记),等价不带参数 fg
    %- 倒数第二个作业(带-标记)

    示例:fg %%,等价直接写fg,把带+标记的挂起作业切回前台,程序恢复继续运行,继续运行死循环程序 ./test1。

    4.6 作业控制相关的信号

    上面我们提到了键入 Ctrl + Z 可以将前台作业挂起,实际上是将 SIGTSTP 信号发送至前台进程组作业中的所有进程,后台进程组中的作业不受影响。在 unix 系统中,存在 3 个特殊字符可以使得终端驱动程序产生信号,并将信号发送至前台进程组作业,它们分别是:

    • Ctrl + C:中断字符,会产生 SIGINT 信号
    • Ctrl + \\:退出字符,会产生 SIGQUIT 信号
    • Ctrl + Z:挂起字符,会产生 SIGTSTP 信号

    终端的 I/O(即标准输入和标准输出)和终端产生的信号总是从前台进程组作业连接打破实际终端。我们可以通过下体来看到作业控制的功能:

    五、守护进程

    5.1 什么是守护进程

    守护进程是脱离控制终端、后台长期运行的服务进程。

    • 典型例子:sshd、mysqld、nginx、systemd。
    • 特征:
      • 不属于任何终端,关闭终端、SSH 断开不会导致进程退出;不受SIGHUP挂起信号影响。
      • 在后台默默提供服务,没有交互界面。
      • 不属于 Shell 的作业(job),jobs命令看不到它。

    对比普通后台 & 进程: ./a.out & 放到后台作业,它仍然属于当前会话、仍然绑定控制终端。SSH 断开,会话首进程 bash 收到SIGHUP,bash 会向会话内所有子进程发送SIGHUP,普通后台进程大概率退出。 👉 普通&后台 ≠ 守护进程。

    普通后台进程为什么会随 SSH 断开退出?

    SSH连接 → 伪终端pts → 会话(session)首进程bash
    ↳ ./a.out & (后台job,属于这个session,继承控制终端)

    SSH 网络断开 → 终端检测链路断开 → 发送SIGHUP给会话首进程 bash。 bash 收到 SIGHUP,默认转发 SIGHUP 给会话内所有进程,于是后台进程退出。

    5.2 守护进程的核心条件

    要真正成为守护进程,必须脱离原来的会话、脱离原来的进程组、脱离控制终端。回顾前面学习的 Unix 体系:

    会话 Session → 进程组 → 进程;一个会话绑定一个控制终端。 进程默认继承父进程的会话 ID、进程组 ID、控制终端。 fork 出来的子进程,和父进程在同一个会话,自然继承控制终端。

    想要摆脱原有会话和终端,就必须创建一个全新会话。 既然守护进程要求进程脱离原有会话与控制终端,那么操作系统如何让一个进程脱离旧会话、生成全新会话?这就引出系统调用 setsid() ——创建会话。

    5.3 如何创建会话 setsid ()

    setsid () 函数原型:

    #include <unistd.h>
    pid_t setsid(void);

    调用前提:调用进程不能是进程组组长,否则调用失败返回‑1。

    调用成功后发生三件事:

  • 创建全新会话:调用进程成为新会话的会话首进程(session leader),会话 ID = 当前进程 PID。
  • 创建全新进程组:调用进程成为新进程组组长,进程组 ID = 当前进程 PID。
  • 进程脱离原来的控制终端;新会话此时没有任何控制终端。
  • 这正是守护进程想要的效果:脱离旧会话、旧进程组、丢掉控制终端。

    5.3.1 为什么不能由进程组组长调用 setsid?

    设计约束:防止一个会话内的进程随意 “跳槽” 到新会话,破坏会话 / 进程组层级。

    解决办法:先 fork,子进程不是进程组组长,在子进程内部调用setsid()。

    5.4 守护进程代码设计

    守护进程标准步骤:

  • fork() 创建子进程,父进程直接 exit 退出;子进程继续运行。子进程不是原进程组组长,满足 setsid 调用条件。
  • 子进程调用 setsid():创建新会话,脱离旧会话、旧进程组、脱离控制终端。
  • 可选:再次 fork,避免会话首进程重新打开终端设备获得控制终端。
  • 修改工作目录chdir("/"),防止占用挂载点。
  • 设置文件掩码umask(0)。
  • 重定向标准输入、输出、错误到/dev/null,彻底断绝终端 IO。
  • #ifndef DAEMON_HPP
    #define DAEMON_HPP
    #include <iostream>
    #include <string>
    #include <signal.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include "Common.hpp"
    using namespace LogModule;
    const std::string dev = "/dev/null";
    // 系统调用守护进程化:int daemon(int nochdir, int noclose);
    // nochdir为0表示更改进程的工作路径到根目录"/",非0则不操作
    // noclose为0表示重定向标准输入、标准输出、标准错误到/dev/null,非0则不操作
    // 封装守护进程化
    void Daemon(int nochdir, int noclose)
    {
    // 1、忽略IO、子进程退出等相关信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    // 2、创建子进程,父进程直接退出,子进程成为后台进程
    if (fork() &gt; 0)
    {
    exit(OK);
    }

    // 3. 创建新会话,成为会话首进程和进程组组长,脱离原控制终端
    setsid(); // 成为一个独立的会话

    // 可选:切换工作目录到根目录
    if (nochdir == 0) // 更改进程的工作路径,为什么??
    {
    chdir("/");
    }

    // 4、孤儿进程依然可能和显示器、键盘、stdin、stdout、stderr相关联
    // 守护进程:不从键盘输入,也不需要向显示器上输出打印
    // 方法一:关闭文件描述符0、1、2 — 不推荐(后续如果进程存在std::cout、std::cin操作会存在问题)
    // 方法二:打开/dev/null,重定向标准输入、标准输出、标准错误到/dev/null(dup2)
    if (noclose == 0)
    {
    int fd = open(dev.c_str(), O_RDWR);
    if (fd &lt; 0)
    {
    LOG(LogLevel::FATAL) &lt;&lt; "open " &lt;&lt; dev &lt;&lt; " error";
    exit(OPEN_ERR);
    }

    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd); // 必须关闭临时文件描述符,避免资源泄漏
    }
    }
    #endif

    5.4.1 为什么存在 nochdir 参数?

    5.4.2 为什么存在 noclose 参数?

    补充:/dev/null 文件

          本文完整梳理了 Linux 下进程组、会话、控制终端、作业控制以及守护进程整套知识体系。进程组把一批相关进程归为一组,进程组长负责管理整个组;会话则是登录连接的整体载体,登录时通过 setsid 创建会话,bash 成为会话首进程。控制终端与会话绑定,区分前台进程组和后台进程组,我们熟悉的 Ctrl+C、Ctrl+Z 本质就是终端向前台进程组发送信号。作业控制就是 shell 对作业的调度管理,实现任务后台运行、挂起、前后台切换。

          守护进程正是利用会话的特性,脱离原有控制终端、会话与进程组,变成不受终端影响的后台服务。setsid() 的调用限制、工作目录重定向、标准输入输出重定向到 /dev/null,每一步都有对应的底层原因。很多命令行操作看似简单,背后都依托这套会话‑进程组模型。理解这套模型,能够帮助我们看懂信号投递规则、处理后台任务异常、正确编写守护进程代码,也能为后续信号编程、服务器程序开发筑牢底层认知。

    赞(0)
    未经允许不得转载:171主机测评 » 《Linux网络编程·加餐》进程间关系与守护进程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址