【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;
}
相关推荐
土星云SaturnCloud12 分钟前
从云端到边缘:电子装配线AI视频分析在土星云SE110S-WA32上的落地实践
服务器·人工智能·ai·边缘计算
宇晨T18 分钟前
BurpSuite实战:WackoPicko敏感目录探测
linux·运维·服务器
yyuuuzz18 分钟前
云服务器软件部署的几个常见问题
运维·服务器·开发语言·网络·云计算·php·apache
BomanGe224 分钟前
NSK W1406FA系列长行程高速精密丝杠技术指南
运维·服务器·数据库·经验分享·规格说明书
只说证事1 小时前
2026 国家认可的计算机专业证书
服务器
月巴月巴白勺合鸟月半1 小时前
在Linux下开发桌面程序
linux·运维·服务器
zh路西法1 小时前
【tmux入门】终端分屏、SSH远程守护与一键启动脚本
linux·运维·ssh·bash
qq_163135751 小时前
Linux 【03-pwd命令超详细教程】
linux
学途路漫漫1 小时前
Ubuntu 24.04 国内网络环境全面优化指南
linux·网络·ubuntu
c238561 小时前
GDB 进程概念详解(下篇)—— 多进程与进阶调试能力
linux·服务器·数据库