【Linux】进程间关系与守护进程

1. 进程组

1.1 进程组概念

  • 每一个进程除了有一个进程ID(PID)之外,还属于一个进程组
  • 进程组是一个或者多个进程的集合,一个进程组可以包含多个进程
  • 每一个进程组也有一个唯一的进程组 ID(PGID),并且这个PGID类似于进程ID,同样是一个正整数,可以存放在pid_t数据类型中
bash 复制代码
$ ps -eo pid,pgid,ppid,comm | grep test
#结果如下
PID PGID PPID COMMAND
2830 2830 2259 test
# -e 选项表⽰every的意思, 表⽰输出每⼀个进程信息
# -o 选项以逗号操作符(,)作为定界符, 可以指定要输出的列

1.2 组长进程

每一个进程组都有一个组长进程。组长进程的ID等于其进程ID

bash 复制代码
[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和PGID相同,说明ps进程是该进程组的组长进程,该进程组包括ps和cat两个进程
  • 进程组组长的作用:进程组组长可以创建一个进程组或者创建该组中的进程
  • 进程组的生命周期:从进程组创建开始到其中最后一个进程离开为止
  • 注意:只要某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否已经终止无关

2. 会话

2.1 会话概念

  1. 会话(Session)一个或多个进程组的集合;
  2. 一个会话可容纳多个进程组
  3. 会话拥有唯一标识:会话 ID(SID)

一条管道命令,自动形成一个进程组。管道里的所有进程,都属于同一个进程组

末尾加 & → 整个进程组丢到后台运行

bash 复制代码
proc2 | proc3 &
proc2 和 proc3 用管道连在一起放进同一个进程组,这个进程组成为后台进程组
bash 复制代码
ps axj | head -n1

ps axj:列出所有进程的详细信息(含进程组、会话)
       a → 显示所有用户的进程 
       x → 显示无控制终端的进程
       j → 显示作业控制 / 进程组 / 会话信息
|:管道,把左边的输出传给右边
head -n1:只取第一行

2.2 如何创建会话

可以调用setseid函数来创建一个会话,前提是调用进程不能是一个进程组的组长

cpp 复制代码
#include <unistd.h>
/*
 *功能:创建会话 
 *返回值:创建成功返回SID, 失败返回-1 
 */
pid_t setsid(void);

调用setsid()的进程,会一口气完成 3 个操作

  1. 创建新会话,自己成为会话首进程: 新建一个全新的会话, 自己成为这个会话的首进程, 此时会话里只有它一个进程
  2. 创建新进程组,自己成为进程组组长,新进程组 ID = 自己的 PID
  3. 脱离控制终端 (最重要):调用后彻底断开终端联系, 变成没有终端的后台进程

如果调用进程本身就是组长进程,setsid () 会直接失败!为什么?系统规定:组长进程不能创建新会话

标准解决方法:先 fork,再让子进程调用:子进程继承父进程的进程组 ID**,** PID 是新的,子进程一定不是组长进程

2.3 会话ID(SID)

会话 ID (SID) = 会话首进程的 PID = 会话首进程的进程组 ID (PGID)

**3.**控制终端

3.1 什么是控制终端?

用户登录用的终端 / 伪终端 ,就是 Shell 进程的控制终端

  • 打开的命令行窗口、SSH 登录,都是控制终端
  • 它是进程和用户交互的入口:键盘输入、屏幕输出
  • 终端信息存在进程 PCB 里,子进程会继承父进程的控制终端

3.2 核心特性

默认标准输入输出都指向它

fork 子进程会继承控制终端

3.3 终端 + 会话 + 进程组 的绑定规则

  1. 一个会话最多有一个控制终端
  2. 会话首进程 = 控制进程
  3. 一个会话分两种进程组:前台进程组(1 个) 独占终端输入输出,**后台进程组(N 个)**不能直接读终端
  4. 有控制终端,就一定有前台进程组
  5. 终端按键信号 → 发给前台进程组所有进程: Ctrl+C(中断)Ctrl+\(退出
  6. 终端断开连接 → 信号发给会话首进程

4. 作业控制

4.1 什么是作业,作业控制

  • 在 Shell 里输入的一整条命令,就是一个作业
  • 一个作业 = 一个进程组
  • 一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制

4.2 作业号

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

默认作业:

  1. + :当前默认作业
  2. -候补默认作业(即将成为默认作业
  3. 无符号:普通普通非默认作业

4.3 前后台任务管理命令

|------------|-------------------------------------------------|
| jobs | 查看当前用户终端 下,所有后台 / 暂停的任务列表,同时会显示任务的+-标记 |
| fg 任务号 | foreground,把指定后台任务,拉回到前台运行,接管终端交互 |
| Ctrl+C | 只能终止当前正在前台运行的任务;后台任务不受这个按键影响 |
| Ctrl+Z | 暂停当前前台正在运行的进程,将它挂起放到后台暂停状态,终端会立刻恢复 bash 命令行 |
| bg 任务号 | background,让刚才被Ctrl+Z暂停的后台任务,在后台继续运行 |

5. 守护进程(Daemon)

守护进程,就是真正脱离终端、常驻系统后台运行的特殊进程,也叫精灵进程

怎么手动把普通程序变成守护进程?

  1. fork () 创建子进程,父进程直接退出
  2. setsid () 创建新会话(最关键!脱离终端)
  3. chdir ("/") 把工作目录改成根目录
  4. umask (0) 重置文件权限(可选)
  5. 关闭 / 重定向 0、1、2 标准文件描述符

Deamon.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <cstdlib>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

const char *root = "/";
const char *dev_null = "/dev/null";

void Daemon(bool ischdir, bool isclose)
{
    // 1. 忽略可能引起程序异常退出的信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    // 2. 让自己不要成为组长,父进程退出
    if (fork() > 0)
        exit(0);

    // 3. 创建新会话,彻底脱离终端
    setsid();

    // 4. 修改进程的工作目录为根目录
    if (ischdir)
        chdir(root);

    // 5. 处理标准输入、输出、错误
    if (isclose)
    {
        close(0);
        close(1);
        close(2);
    }
    else
    {
        // 推荐方式:重定向到 /dev/null
        int fd = open(dev_null, O_RDWR);
        if (fd > 0)
        {
            dup2(fd, 0);
            dup2(fd, 1);
            dup2(fd, 2);
            close(fd);
        }
    }
}
cpp 复制代码
dup2(旧的文件描述符, 新的文件描述符);
  意思:把【旧的】复制到【新的】,让新的指向旧的
dup2 (谁复制,复制给谁);

6. 如何将服务守护进程化

cpp 复制代码
// ./server port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "Usage : " << argv[0] << " port" << std::endl;
        return 0;
    }

    uint16_t localport = std::stoi(argv[1]);

    // 将程序设置为守护进程
    Daemon(false, false);

    std::unique_ptr<TcpServer> svr(new TcpServer(localport, HandlerRequest));
    svr->Loop();

    return 0;
}
相关推荐
小书房1 小时前
搭建本地的源码查询服务器
运维·服务器·tomcat·opengrok·代码查询
Amnesia0_01 小时前
文件和fd,文件的内核级缓冲区,重定向
linux·运维·服务器
wwyyxx262 小时前
Linux 下 .NET 程序 CPU 异常占用排查记录
linux·.net·调试
石油人单挑所有2 小时前
基于多设计模式下的同步&异步日志系统测试报告
服务器·c++·vscode·设计模式
学Linux的语莫2 小时前
langgraph实操
服务器·数据库·mysql
.千余2 小时前
【Linux】开发工具1
linux·运维·服务器·c语言·学习
Ops菜鸟(Xu JieHao)2 小时前
Linux Rear系统热备份 【详细教程】
linux·运维·服务器·linux备份·系统备份·rear·热备份
TBrL7UtdTELTTdut4BAL2 小时前
XG-140G-TF 极简 OpenWrt | 修复2.5G | NPU硬件加速
服务器·智能路由器·openwrt·光猫·xg-140g-tf
小袁搬码2 小时前
Ubuntu2026.04LTS_长期支持本已发布
linux·ubuntu2026.04