Linux —— 进程间关系和守护进程

目录

[1. 进程组](#1. 进程组)

[1.1 什么是进程组](#1.1 什么是进程组)

[1.2 组长进程](#1.2 组长进程)

[2. 会话](#2. 会话)

[2.1 什么是会话](#2.1 什么是会话)

[3. 守护进程](#3. 守护进程)


之前的UDP不用解决字节流的问题,UDP也是需要序列与反序列化,之前写的聊天室就可以在客户端和服务端定义 Message,Message 结构体中可以包含昵称,发的内容,头像等等信息,用的是json串,可用udp来做。

第一次接触面向字节流是在管道中,写进去简单,但是读取的话是很难受的,比较麻烦,因为缺了序列化和反序列化的部分。所以很多的配置文件很多都是json,这是因为vscode软件本身就是通过读取json串,反序列化,根据key读取value,得到你的配置信息。

其实一个真正的服务器是不能直接这样跑的:

正儿八经的一个服务器是要以守护进程的样式去运行。

输入指令:ps ajx | grep ssh

要了解守护进程就先要了解进程间关系:

1. 进程组

输入指令:sleep 100

再次输入其它的指令没有反应,这种在命令行当中直接启动的,导致shell没办法直接解析的,sleep 100 这种进程叫做前台进程

输入指令:sleep 100 &

不影响后续指令的运行,这种指令称为后台进程,后台进程的特点就是在命令后面带上 & 符号,后台进程,不影响bash,用户可以在进程运行期间,继续访问Linux。一般执行耗时的任务时就会启动后台进程。

输入指令:sleep 1000 | sleep 2000 | sleep 3000 &

会有三个子进程:

188736 就是父进程。

PPID:表示的是父进程,PID:表示的是子进程,PGID:表示的就是进程组ID。进程之间存在兄弟、父子关系,兄弟和父子之间有可能还会存在其它的关系:进程组关系。以上三个进程属于同一个组,进程组的组ID一般是以多个进程当中的第一个被创建的进程的PID为进程组ID。所以,命令:sleep 1000 | sleep 2000 | sleep 3000 & 就是一个进程组,是启动一个进程组,共同完成一个任务。还存在一个概念:任务。

再次执行命令:sleep 1000 | sleep 2000 | sleep 3000 &

确实是后台进程,因为在状态中S后面没有 + 号,(有 + 就在前台,没有 + 号 就在后台)

此时再启动一个命令:sleep 4000 | sleep 5000 | sleep 6000 &

所以此时就存在两个任务。

  • 进程组:就是第一个进程的PID
  • 任务:从1开始的
  • 进程组和任务之间的关系:进程组是手段,完成某种任务是目的(某一个进程组完成某项任务)

所以,当我在操作命令行的时候,启动一个进程组(至少一个进程),完成一种任务

查看后端任务的命令:jobs

将任务提到前台的命令:fg 1(1:任务号)

在输入:ctrl+c,终止该任务

任务前后台切换与管理:

  1. 将任务2放到前台:fg 2
  2. 又不想终止任务2,又想放回后台:ctrl + z
  3. jobs 查的时候发现:ctrl + z 是把任务暂停了,自动放到后台了,本来是Running的
  4. 想在后台继续运行起来:bg 2
  5. 再次 jobs 可以看见2号任务再次Running

1.1 什么是进程组

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

sy@iZ2vc2ummd1vkuxyf2vgvkZ:~/linux/lesson51/NetCal$ ps -eo pid,pgid,ppid,comm | grep sleep

188838 188838 188736 sleep

188839 188838 188736 sleep

188840 188838 188736 sleep

-e 选项表⽰every的意思, 表⽰输出每⼀个进程信息

-o 选项以逗号操作符(,)作为定界符, 可以指定要输出的列

1.2 组长进程

每⼀个进程组都有⼀个组⻓进程。 组⻓进程的ID等于其进程ID。(进程之间是兄弟关系的第一个是组长进程,进程之间是父子关系的话,父进程就是组长进程).

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

进程组的⽣命周期: 从进程组创建开始到其中最后⼀个进程离开为⽌。注意: 只要某个进程组中有⼀个进程存在, 则该进程组就存在, 这与其组⻓进程是否已经终⽌⽆关。

从上面可以看见如果组长退了,PGID也是不变的。

2. 会话

以上3个进程属于一个会话,SID的值对应的是当前进程bash进程对应的ID。

2.1 什么是会话

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

通常我们都是使⽤管道将⼏个进程编成⼀个进程组。 如上图的进程组2和进程组3可能是由下列命令形成的:

$ proc2 | proc3 &

$ proc4 | proc5 | proc6 &

&表⽰将进程组放在后台执⾏

首次登录Linux机器的时候,登陆成功之后,Linux 操作系统首先要为该用户创建一个bash进程组,完成命令行解释任务(其实只有一个进程),所以就会看见命令行提示符这样的东西,不管是前台还是后台,启动的所有任务都是以进程组的形式体现的,哪怕只有一个进程,Linux执行ls、pwd,所以会构建成同时存在其它进程组,bash进程将终端文件打开就有 0,1,2 了,创建进程组都是bash进程的子进程,子进程继承文件描述符表,所以在Linux系统中,任何进程默认打开012

如何证明?

云服务器没有键盘,所以将012都放入/dev/pts/0中

如何证明上面的进程组属于同一个会话?------ 让它们的SID相同就行了!!!

mei新建一个渠道,都会有一个新建的 bash

每一次登录新建一个tty和bash,bash内部可以新建一个进程,所以每一次登录,Linux系统会为当前用户创建一次会话,每一个会话都有会话ID

将以上一次会话放大:

所以,在启动服务器的时候,./Server 8080 属于是该会话内部的任务

登陆时要创建会话,退出的时候就会将整个会话干掉,整个会话内部的进程组就会被跟着退出,注意:老系统中会退出,但是现在的系统是不会退出的。

所以不让这个任务放在会话内部,应该将该任务单独拿出来,自己形成一个和 bash 并行的独立会话!!!独立会话的后端进程叫做守护进程!!!

结论:

  • 一次会话中,只允许一个前台进程组(任务),可以同时存在多个(0)后台进程组(任务)。
  • 前后台,都可以向终端文件进行写入。但是只有前台进程,能够从标准输入(终端文件中)获取数据!后台进程是获取不了数据的!

将新起的任务放在了后台,而我们可以同时存在多个后台任务,依旧属于前台,所以就可以获取键盘上的任务,可以进行命令的执行

问题1:为什么一次会话中只允许一个前台任务??

因为前台任务需要和用户交互,需要获取用户的标准输入,(键盘)标准输入只有一个,前台进程必须只有一个!!!

区分前后台的标准:

进程组能从键盘这个标准输入流获取数据,能获取数据,就是前台的,不能获取数据就是后台的。

所以 ctrl +c 为什么只能终止前台进程??因为只有前台进程能够收到键盘的输入,所以只能终止前台进程。但是后台进程依旧能够向显示器打印,说明前后台进程,都没有彻底脱离终端!!!(想脱离将012可以关闭掉)

所以 :指令 sleep 1000执行后,成为前台进程,bash成为后台进程,再次输入pwd类似的命令后,是没有反应的。当ctrl + c 退出当前的前台进程后,bash进程此时就会变为前台进程,再次输入pwd、ls命令的时候就会有反应。

例子:

有时 windows 电脑会变得比较卡,在老式的电脑中还有注销的选项,点击注销这个选项也是可以解决卡顿的问题。

当登陆windows时,windows就会创建一个会话,这个会话只有一个前台进程组,就是图像化界面,在图形化界面永远都是单机双击,登录就是新建会话的过程,访问机器的时候,在这个会话中创建了很多作业 or 任务,后面变得卡顿的原因就是启动的任务过多或者是启动的任务失控了,但是依旧属于这个会话,所以可以采取注销,所谓的注销就是将这次的会话删掉,会话删掉就会将会话所启动的任务自动退出。有时,启动的任务会自动的变为独立的守护进程,脱离对应的会话,此时便只能重启,有些重启也没有用,因为被设置为开机启动,所以得将开机启动项关闭。

3. 守护进程

如何让我的进程(组),成为守护进程呢???首先的是一个独立的会话,如何成为一个独立的会话呢?? --------- 调用setsid

setsid:创建一个会话并且设置一个会话中的进程组ID

  • 返回值:
  • 成功调用 setsid 的要求:调用 setsid 的进程,创建新的会话的进程一定不能是进程组的组长。

此时的进程,就是组长,因为这个组里面只有它一个。如何解决让自己不是组长呢??------ fork() > 0; exit(0) -> 让子进程执行后续的代码。组长就是之前的父进程,父进程退出,子进程不是组长,让子进程调用setsid就可以了调用成功了。

所以:守护进程本质就是一种孤儿进程!!!

/dev/null 属于字符类的设备文件:

/dev/null 的好处就是:/dev/null 跟垃圾桶一样,写入数据时自动被丢弃,允许写入,但是会被丢弃掉;同样也允许你读,读到的数据什么也没有。

cpp 复制代码
// Deamon.hpp

#pragma once

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

void Deamon()
{

    // 1.忽略可能导致进程退出的信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    // 2. 让进程不要成为组长
    if (fork() > 0)
        exit(0);

    // ???
    
    // 3.将自己设计成为新的会话
    setsid();

    // 4. 如何看待标准输入,标准输出,标准错误
    // a. 关闭  b. 标准输入、标准输出、标准错误 -> 重定向 -> /dev/null(最佳实践)
    int fd = open("/dev/null",O_RDWR);  // 以读写的方式打开
    if(fd >= 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
    }
}

此时再运行,就不会被卡住了:

将服务终止 ctrl + c

系统中的守护进程:

将 xshell 关掉,换一台云服务器(我这里用的是Centos7的虚拟机)同样也是可以进行运行的。

在 Linux 系统中,当你通过 Xshell 连接到服务器并运行程序时,程序进程是 SSH 会话的子进程,它们之间存在进程树层级关系。当 Xshell 关闭时,SSH 会话终止,系统会向该会话中的所有子进程发送 SIGHUP 信号,默认行为是终止这些进程,因此普通运行的程序会随着 Xshell 关闭而停止。守护进程的核心目标就是摆脱这种对终端会话的依赖,而信号处理是实现这一目标的重要技术手段之一。

代码中的 signal(SIGCHLD, SIG_IGN) 用于忽略子进程终止信号,这告诉系统当子进程结束时不需要向父进程发送通知,由系统自动回收子进程资源,避免产生僵尸进程,这在守护进程 fork 子进程处理并发连接时尤为重要,确保系统资源不会被耗尽。而 signal(SIGPIPE, SIG_IGN) 则忽略管道破裂信号,在网络通信中,当客户端突然断开连接而服务器仍在尝试写入数据时,系统会发送 SIGPIPE 信号导致程序崩溃退出,忽略该信号后写操作会返回错误而非终止程序,大大增强了守护进程的健壮性,特别是在长时间运行且需要处理大量网络连接的服务程序中。

那么如何关掉守护进程呢??

查看一下守护进程的详细信息:

查看 fd:ls /proc/208121/fd -l

上图中的4的对应的就是套接字。

bash 复制代码
# Makefile

.PHONY:all
all:Server Client

Client::Client.cc
	g++ -o $@ $^ -std=c++17 -ljsoncpp

Server:Main.cc
	g++ -o $@ $^ -std=c++17 -ljsoncpp

.PHONY:clean
clean:
	rm -rf Server Client output

.PHONY:output
output:
	mkdir -p output/bin
	mkdir -p output/lib
	mkdir -p output/conf
	mkdir -p output/log
	cp Server output/bin
	cp Client output/bin

make output:

所以在Windows下载软件的时候,下载的不是源代码,而是别人给你打包好的软件,直接解压,install,等待点击 exe,正在安装中,实际上是在做拷贝!!实际上现在的很多软件,下载的就只有安装脚本。

cpp 复制代码
// Daemon.hpp
#pragma once

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

void Deamon()
{

    // 1.忽略可能导致进程退出的信号
    signal(SIGCHLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);

    // 2. 让进程不要成为组长
    if (fork() > 0)
        exit(0);

    // 3. 更改守护进程的工作路径,建议/,访问内置的配置文件的时候,用绝对路径来访问
    chdir("/");

    // 4.将自己设计成为新的会话
    setsid();

    // 5. 如何看待标准输入,标准输出,标准错误
    // a. 关闭  b. 标准输入、标准输出、标准错误 -> 重定向 -> /dev/null(最佳实践)
    int fd = open("/dev/null",O_RDWR);  // 以读写的方式打开
    if(fd >= 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
    }


}

log下面就是我们对应的日志:

此时对应的日志就会往系统中写入:

守护进程可以手动写,也可以不用自己写,系统中存在一个系统调用:daemon()

  • nochdir:是否更改路径
  • nochdir:是否关闭文件描述符

上面参数都为0,对应的就是我们写的如上的Daemon.hpp

相关推荐
FII工业富联科技服务1 小时前
工业设备运维如何Agentic化?拆解Factory Brain的设备运维架构
大数据·运维·人工智能·架构·制造
林浅不想努力2 小时前
LVS知识点总结
服务器·云原生·lvs
Dawn-bit2 小时前
Linux磁盘管理详解
linux·运维·服务器·计算机网络·云计算
RisunJan3 小时前
Linux命令-sftp(SSH 文件传输协议客户端)
linux·运维
极客先躯4 小时前
高级java每日一道面试题-2026年05月03日-实战篇[Docker]-如何实现容器化环境的数据加密?
java·运维·docker·容器·金融·加解密·高级面试
龙仔7255 小时前
人大金仓OS_Core数据库自动备份实施笔记(银河麒麟Linux)
linux·数据库·笔记·备份·人大金仓
老杨聊技术5 小时前
CentOS 7 安装 MySQL 8 保姆级教程
linux·mysql·centos
xiaoye-duck6 小时前
《Linux系统编程》Linux 系统多线程(六):<线程同步与互斥>线程同步(下):POSIX 信号量与环形队列生产者消费者模型详解
linux·线程
三言老师6 小时前
CentOS7.9:Redis‑Cluster集群部署结构化实战教程
linux·运维·服务器·数据库