Linux下 进程间关系与守护进程

欢迎来到我的频道!【点击跳转专栏】

本文所有代码已托管至码云:【点此转跳】

文章目录

  • [1. 进程组&&作业](#1. 进程组&&作业)
    • [1.1 什么是进程组](#1.1 什么是进程组)
    • [1.2 组⻓进程](#1.2 组⻓进程)
    • [1.3 作业号](#1.3 作业号)
    • [1.4 前、后台作业](#1.4 前、后台作业)
      • [1.4.1 jobs](#1.4.1 jobs)
      • [1.4.2 fg](#1.4.2 fg)
      • [1.4.3 bg](#1.4.3 bg)
      • [1.4.4 作业状态](#1.4.4 作业状态)
    • [1.5 进程组长作业?组长进程关闭,进程组会消失吗?](#1.5 进程组长作业?组长进程关闭,进程组会消失吗?)
  • [2. 会话](#2. 会话)
    • [2.1 TTY(字符文件)](#2.1 TTY(字符文件))
    • [2.2 什么是会话](#2.2 什么是会话)
    • [2.3 关闭会话](#2.3 关闭会话)
    • [2.4 创建会话(守护进程是什么)](#2.4 创建会话(守护进程是什么))
  • [3. 守护进程的创建](#3. 守护进程的创建)
    • [3.1 daemon](#3.1 daemon)
    • [3.2 dev/null&&dev/zero](#3.2 dev/null&&dev/zero)
    • [3.3 手动实现daemon()](#3.3 手动实现daemon())

1. 进程组&&作业

1.1 什么是进程组

之前我们提到了进程的概念,其实每一个进程除了有一个进程ID(PID)之外还属于一个进程组。进程组是一个或者多个进程的集合,一个进程组可以包含多个进程 。每一个进程组也有一个唯一的进程组ID(PGID),并且这个 PGID类似于进程ID,同样是一个正整数,可以存放在pid_t数据类型中。

1.2 组⻓进程

每⼀个进程组都有⼀个组⻓进程。 组⻓进程的ID等于其进程ID。我们可以通过ps命令看到组⻓进程的现象:

bash 复制代码
# 输出结果
PID PGID PPID COMMAND
2806 2806 2805 bash
2880 2880 2806 ps
2881 2880 2806 cat

从结果上看ps进程的PID和PGID相同, 那也就是说明ps进程是该进程组的 组⻓进程 , 该进程组包括ps和cat两个进程

该组创建的第一个进程,一般为进程组长


当我创建以下进程,这些以前学过都是bash的子进程 :

当我用ctrl + c杀死前台进程的时候 发现该组进程(PGID相同)都死了

说明前后台进程管理,是以进程组为单位的!ctrl + c是向进程组发信号(前台进程),杀死的是进程组中所有进程!

1.3 作业号

当我创建多个后台进程的时候:

这些1、2、3就是作业号!

结论:进程组和作业号是一个硬币的两面!作业要由进程组完成,作业强调该组进程要做什么!进程组强调如何来完成由谁完成!

举个例子,有一批垃圾要运走,这个就是作业,而你雇佣了5个农民工移走垃圾,这五个农名工就是进程组,而进程组就是要完成作业!

作业分前台、后台作业(可以进程键盘输入的叫前台作业),启动一项进程组感觉怪怪的的,但是启动一项作业就很舒服了!作业是站在用户视角的!开发者角度就是进程组了,你想怎么说都行其实,只是视角不同!

1.4 前、后台作业

1.4.1 jobs

jobs 是 bash 内建命令,只看当前这个 shell 会话的后台 / 暂停作业,不能看别的终端 shell 的任务

1.4.2 fg

fg 是 Shell 内置命令(作业控制)

作用:把后台 / 暂停的作业切换到前台运行。

bash 复制代码
fg           # 将当前默认作业放到前台
fg %jobnum   # 指定作业号,把对应作业切前台

+:当前默认作业,直接执行 fg / bg,不加 % 号就操作它

1.4.3 bg

我们可以通过ctrl+z让前台进程进入T状态 ,此时该进程会自动由前台进程变成后台

我们可以用bg命令启动该进程:

作用:把 已经暂停(Stopped) 的作业,在后台恢复运行

bash 复制代码
bg          # 对带 + 的默认作业执行后台恢复
bg %jobid   # 指定作业号,后台继续运行

1.4.4 作业状态

常⻅的作业状态如下表所⽰:

1.5 进程组长作业?组长进程关闭,进程组会消失吗?

进程组组⻓的作⽤: 进程组组⻓可以创建⼀个进程组或者创建该组中的进程!

进程组的⽣命周期: 从进程组创建开始到其中最后⼀个进程离开为⽌。

注意: 只要某个进程组中有⼀个进程存在, 则该进程组就存在, 这与其组⻓进程是否已经终⽌⽆关。

2. 会话

2.1 TTY(字符文件)

TTY 可以理解成"字符设备文件"。指 Linux 中 让你输入命令、查看输出的终端设备文件。

  • 键盘输入会被 TTY 读取
  • 程序输出会通过 TTY 显示到终端

图中 TTY 一栏显示的是 pts/0,表示这个 sleep 1000 进程运行在编号为 0 的"伪终端"上。

pts 是 pseudo terminal(伪终端)的缩写,常见于打开 Terminal、SSH 或终端模拟器时。简单说,pts/0 就是" 当前终端窗口 "。

当然,终端窗口本身负责显示界面,pts/0 负责在窗口和程序之间传递输入输出。因此说 pts/0 是"当前终端窗口"是便于理解的说法,更准确地说,它是这个终端窗口对应的通信通道。


此时bash会显示:

这也符合Linux中一切皆文件!

而只有前台进程组才有读取写入字符文件(TTY)的权利!


TTY 和 Bash 的关系可以理解为: TTY 是"输入输出通道",Bash 是"处理命令的程序"。

你打开终端后,系统通常会启动一个 Bash 进程,并把它连接到某个 TTY,例如 pts/0:

text 复制代码
键盘输入 → TTY → Bash → 执行命令
屏幕显示 ← TTY ← Bash 输出

例如你输入:

bash 复制代码
ls

Bash 从 TTY 读取命令,执行 ls,再把结果通过 TTY 显示到屏幕上。

所以,TTY 负责传递输入输出,Bash 负责解析和执行命令;Bash 通常运行在 TTY 上,但 TTY 本身不是 Bash。

2.2 什么是会话

在我们用电脑,使用xshell、xterminal等软件远程登录软件的时候,会打开bash、新的字符文件(TTY),然后你会在这个终端(bash)下创建的新的作业,这些东西的总和就是一个会话!

简单说: 会话就是"从登录终端开始,到退出终端结束的一组进程和操作"。

会话其实和进程组息息相关, 会话可以看成是⼀个或多个进程组的集合, ⼀个会话可以包含多个进程组。每⼀个会话也有⼀个会话ID(SID)

其中会话的第一个进程(一般是bash),它的pid就是SID!当然 会话ID在有些地⽅也被称为 会话⾸进程的进程组ID, 因为会话⾸进程总是⼀个进程组的组⻓进程, 所以两者是等价的

2.3 关闭会话

当我们关闭会话的时候,bash会被杀死,字符文件会被关闭!那么会话内部的进程组和作业呢???

Linux下可能 你的前台进程可能会变成后台进程然后随其他后台进程被自动领养,也可能自动关闭 但有一点是肯定的 你的网络服务,一定会受到用户登录或者注销的影响!!

那么我希望让我们的网络服务,是一个真正的网络服务,那就必须要保证把我们的网络服务从原本的会话剥离开来,自成 会话!

2.4 创建会话(守护进程是什么)

可以调⽤setseid函数来创建⼀个会话, 前提是 调⽤进程不能是⼀个进程组的组⻓。

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

该接⼝调⽤之后会发⽣:

  • 调⽤进程会变成新会话的会话⾸进程。 此时, 新会话中只有唯⼀的⼀个进程
  • 调⽤进程会变成进程组组⻓。 新进程组ID就是当前调⽤进程ID
  • 该进程没有控制终端。 如果在调⽤setsid之前该进程存在控制终端(bash), 则调⽤之后会切断联系!

而这些调用过setseid的进程就是 所谓的守护进程(也是一种特殊的孤儿进程),有时我们还称为 精灵进程(daemon)!

所以我们看到sshd,后面为什么有个d,现在知道了吧!

3. 守护进程的创建

3.1 daemon

daemon() 是 Linux/Unix 中用于把当前进程变成"后台进程"的函数,常用于编写服务程序。

c 复制代码
#include <unistd.h>

int daemon(int nochdir, int noclose);

参数含义:

  • nochdir = 0:把当前工作目录切换为 /;
  • nochdir = 1:保持当前工作目录不变;
  • noclose = 0:关闭标准输入、标准输出、标准错误,并重定向到 /dev/null;
  • noclose = 1:不关闭这些文件描述符。

示例:

c 复制代码
#include <stdio.h>
#include <unistd.h>

int main()
{
    if (daemon(0, 0) == -1)
    {
        perror("daemon");
        return 1;
    }

    while (1)
    {
        sleep(10);
    }

    return 0;
}

调用成功后,程序会脱离终端在后台运行,关闭终端通常也不会影响它。简单说,daemon() 就是帮助程序"离开终端,进入后台长期运行"。

3.2 dev/null&&dev/zero

/dev/null 和 /dev/zero 都是 Linux 中的特殊设备文件。

  • /dev/null:像"垃圾桶",写入其中的数据会被直接丢弃;读取时通常读到的0【什么都读不到】。常用于隐藏输出:
bash 复制代码
command > /dev/null 2>&1

表示丢弃正常输出和错误输出。


  • /dev/zero: 像"无限的零数据源",读取它会不断得到 0 字节。常用于生成空白数据:

3.3 手动实现daemon()

cpp 复制代码
#pragma once

#include <csignal>
#include <fcntl.h>
#include <iostream>
#include <unistd.h>
void Daemon()
{
    // 1.忽略可能引起程序异常退出的信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    // 2.不能是组长
    if (fork() > 0)
    {
        exit(0);
    }

    // 3.设置新会话
    pid_t id = setsid();
    (void)id;
   
    //4. 应用程序,守护进程,一定要有一个路径起始(一般为根目录),方便我们强制使用绝对路径方式找到所有其他相关文件或者资源!
    //如果守护进程的当前工作目录在某个可卸载的文件系统上,进程会持有该文件系统的引用,
    // 导致umount卸载失败。调用chdir("/")将工作目录切换到根文件系统,根文件系统不会被卸载,释放对其他挂载点的占用,保证其他分区可以正常卸载。
    
    chdir("/");


    // 5.在企业级开发中我们99%的调试都是靠日志和打印(所以为了未来调试方便不可能删除打印程序【cout、printf】)
    //   当我们设置新会话 fd =0、1、2都会失效 所以我们要将这些打印内容重定向!
    //  如果只关掉 fd =1 2 3,不影响运行  但是cout之类还是在报错!

    // 重定向0、1、2
    int fd = open("/dev/null", O_RDWR);
    if (fd >= 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}

如果你不想重定向,不想改变工作路径你还可以带一些参数进行判断(和deamon一样),我这里就不写!

相关推荐
g10565591391 小时前
公有云_云运维服务
java·运维·服务器
码农小韩2 小时前
Linux驱动理论(二)——Linux字符设备驱动
linux·嵌入式软件开发·linux操作系统·linux应用开发·linux驱动理论
well06122 小时前
Linux粘滞位与Makefile机制深度解析
linux·运维·服务器
再写一行代码就下班2 小时前
linux sh脚本在windows修改导致无法使用解决方式
java·linux·centos
hzxpaipai2 小时前
杭州企业官网怎么建设?从策划到上线的5步建站方法
运维·服务器·前端·网络
哲霖软件2 小时前
非标机械设备公司怎么做信息化?破解通用ERP水土不服难题
大数据·运维
smartpi_ai2 小时前
通用脱机烧录器为什么烧不进 CI-03?下载协议的门槛、免唤醒 10 条的建议值属性
服务器·网络·ci/cd
额额额对了2 小时前
SPI通信
linux·c语言·汇编·单片机·嵌入式硬件·arm
子木HAPPY阳VIP3 小时前
Ubuntu 关闭防火墙操作步骤
linux·运维·ubuntu