【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 小时前
戴尔R720xd服务器卡死在 “Configuring Memory...Done“,换了内存还是卡?问题根本不在内存
运维·服务器
且从容.2 小时前
ubuntu安装flink
linux·ubuntu·flink
数字新视界3 小时前
动环监控信创适配在智能机房管理中的应用价值与未来展望
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
爱和冰阔落3 小时前
【Linux网络】TCP 服务器如何并发处理多个客户端?从 fork、僵尸进程到双重 fork
linux·运维·服务器·网络
程序员沉梦听雨4 小时前
WSL2的安装与使用
linux·wsl
微小的鱼4 小时前
Linux的渲染与显示系统链路
android·java·linux
lisanmengmeng4 小时前
alertmanger 标签 rules 打上分组标签
linux·运维·服务器
Android系统攻城狮5 小时前
Linux FFmpeg深度解析之av_write_frame调用流程与实战(三十三)
linux·运维·服务器·ffmpeg·音视频进阶·ffmpeg音视频
上火的金鱼妹5 小时前
k8s生产实际问题排查(网关,数据库读写等)
运维·服务器
Linux-lucky16 小时前
45-Linux学习之旅之防火墙与WEB加固
linux·前端·网络·学习·ubuntu