当前位置: 首页 > news >正文

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

在前面的文章中,我们的服务器跑起来之后,实际上并不是会一直在运行,为什么这么说呢?因为受我们XShell的限制,只要我们将XShell关闭,我们运行的服务器也就中断了。现实中我们使用的软件肯定不是这样的,这些软件的服务器正常情况下会在主机上一直运行。所以我们这篇文章就来介绍一下如何做到

文章目录

  • 1. 进程组
    • 1.1 什么是进程组
    • 1.2 组长进程
  • 2. 会话
    • 2.1 什么是会话
    • 2.2 如何创建会话
    • 2.3 会话ID(SID)
  • 3. 控制终端
  • 4. 作业控制
    • 4.1 什么是作业(job)和作业控制(Job Control)?
    • 4.2 作业号
    • 在这里插入图片描述
    • 4.3 作业的挂起与切回
    • 4.4 查看后台执行或挂起的作业
    • 4.5 作业控制相关的信号
    • 在这里插入图片描述
  • 5. 守护进程
  • 6. 守护进程 vs 后台进程

1. 进程组

1.1 什么是进程组

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

如下代码:

#include <iostream>
#include <unistd.h>int main()
{while(true){std::cout << "hello server" <<std::endl;sleep(1);}return 0;
}

编译运行后通过ps指令查看

在这里插入图片描述
注意

# -e 选项表⽰every的意思, 表示输出每一个进程信息
# -o 选项以逗号操作符(,)作为定界符, 可以指定要输出的列

可以看到进程组pgid和进程pid相等,那是因为单个进程自己独立成为进程组


1.2 组长进程

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

ltx@My-Xshell-8-Pro-Max-Ultra:~$ ps -o pid,pgid,ppid,comm | catPID    PGID    PPID COMMAND634309  634309  634308 bash634381  634381  634309 ps634382  634381  634309 cat

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

  • 进程组组长的作用: 进程组组长可以创建一个进程组或者创建该组中的进程
  • 进程组的生命周期: 从进程组创建开始到其中最后一个进程离开为止。注意: 只要某个进程组中有一个进程存在, 则该进程组就存在, 这与其组长进程是否已经终止无关。此时的进程组被称为 ”孤儿进程组“

2. 会话

2.1 什么是会话

刚刚我们谈到了进程组的概念, 那么会话又是什么呢? 会话其实和进程组息息相关, 会话可以看成是一个或多个进程组的集合, 一个会话可以包含多个进程组。每一个会话也有一个会话ID(SID)
在这里插入图片描述
通常我们都是使用管道将几个进程编成一个进程组。 如上图的进程组2和进程组3可能是由下列命令形成的:

ltx@My-Xshell-8-Pro-Max-Ultra:~$ sleep 1000 | sleep 2000 | sleep 3000 &
[1] 638455
# &表⽰将进程组放在后台执⾏

我们通过ps指令查看

# 过滤sleep相关的进程信息
ltx@My-Xshell-8-Pro-Max-Ultra:~$ ps axj | head -n1 && ps axj | grep sleep | grep -v grepPPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND634309  638453  638453  634309 pts/0     638653 S     1004   0:00 sleep 1000634309  638454  638453  634309 pts/0     638653 S     1004   0:00 sleep 2000634309  638455  638453  634309 pts/0     638653 S     1004   0:00 sleep 3000# a选项表⽰不仅列当前用户的进程,也列出所有其他用户的进程
# x选项表⽰不仅列有控制终端的进程,也列出所有⽆控制终端的进程
# j选项表⽰列出与作业控制相关的信息, 作业控制后续会讲
# grep的-v选项表⽰反向过滤, 即不过滤带有grep字段相关的进程

从上述结果来看3个进程对应的PGID相同, 即属于同一个进程组。同样3个进程的SID也相同,因为属于同一个进程组肯定也属于同一个会话


2.2 如何创建会话

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

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

该接口调用之后会发生:

  • 调用进程会变成新会话的会话首进程。此时,新会话中只有唯一的一个进程
  • 调用进程会变成进程组组长。新进程组ID就是当前调用进程ID
  • 该进程没有控制终端。如果在调用setsid之前该进程存在控制终端, 则调用之后会切断联系

需要注意的是: 这个接口如果调用进程原来是进程组组长,则会报错,为了避免这种情况, 我们通常的使用方法是先调用fork创建子进程,父进程终止,子进程继续执行,因为子进程会继承父进程的进程组ID,而进程ID则是新分配的,也就意味此时子进程不可能是进程组长,就不会出现错误的情况。


2.3 会话ID(SID)

上边我们提到了会话ID, 那么会话ID是什么呢? 我们可以先说一下会话首进程, 会话首进程是具有唯一进程ID的单个进程,那么我们可以将会话首进程的进程ID当做是会话ID。注意:会话ID在有些地方也被称为会话首进程的进程组ID, 因为会话首进程总是一个进程组的组长进程, 所以两者是等价的。

下面我们来详细介绍一下会话首进程

会话首进程 (session leader) 是创建该会话的进程,或者更准确地说,它是会话中第一个进程,并且该进程的进程ID就是会话ID(SID)。在Unix/Linux系统中,一个会话是一个或多个进程组的集合,通常与一个终端(控制终端)相关联。

会话首进程的关键特性

  1. 创建会话:通过调用setsid(),一个进程会创建一个新会话,并成为该会话的首进程。调用setsid()的进程不能是进程组组长,因此通常先fork一个子进程,然后让子进程调用setsid()。

  2. 会话ID(SID):会话ID就是会话首进程的进程ID(PID)。因此,会话首进程的PID和SID是相同的。

  3. 进程组组长:会话首进程同时也是一个进程组的组长,其进程组ID(PGID)等于它的PID(也就是SID)。

  4. 控制终端:一个会话可以有一个控制终端,通常是用户登录时使用的终端设备。会话首进程通常是打开控制终端的进程,但并不是必须的。不过,如果会话有控制终端,那么会话首进程通常是与控制终端建立连接的进程。

  5. 终端断开信号:当控制终端断开连接(例如,终端窗口关闭)时,会话首进程会收到SIGHUP信号。通常,会话首进程会处理这个信号,比如终止会话中的所有进程。

会话首进程的作用

  • 管理会话:会话首进程通常用于管理整个会话的生命周期。例如,当用户注销时,系统会向会话首进程发送SIGHUP信号,会话首进程通常会将这个信号转发给会话中的所有进程组,然后终止会话。
  • 作业控制:在shell中,每个作业(一个命令或管道序列)通常是一个进程组,而shell本身是会话首进程。shell使用会话和进程组来管理作业控制,比如前后台作业的切换。

示例说明

在一个典型的登录环境中:

  1. 用户登录时,系统启动一个登录shell,该shell成为新会话的首进程。它的PID就是会话ID。

  2. 当在这个shell中运行命令时,每个命令(可能是多个进程通过管道连接)会被放入一个新的进程组。但所有这些进程组都属于同一个会话。

  3. 如果shell退出,会话首进程终止,那么系统会向该会话中的所有进程发送SIGHUP信号,导致它们终止(除非它们已经忽略了SIGHUP信号或者已经变成了守护进程)。


3. 控制终端

先说一下什么是控制终端?

在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端。控制终端是保存在PCB中的信息,我们知道fork进程会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况下没有重定向,每个进程的标准输入、标准输出和标准错误都指向控制终端,进程从标准输入读,也就是读用户的键盘输入,进程往标准输出或标准错误输出写,也就是输出到显示器上。另外会话、进程组以及控制终端还有一些其他的关系,我们在下边详细介绍
一下:

  • 一个会话可以有一个控制终端,通常会话首进程打开一个终端(终端设备或伪终端设备)后,该终端就成为该会话的控制终端。
  • 建立与控制终端连接的会话首进程被称为控制进程。
  • 一个会话中的几个进程组可被分成一个前台进程组以及一个或者多个后台进程组。
  • 如果一个会话有一个控制终端,则它有一个前台进程组,会话中的其他进程组则为后台进程组。
  • 无论何时进入终端的中断键(ctrl+c)或退出键(ctrl+\),就会将中断信号发送给前台进程组的所有进程。
  • 如果终端接口检测到调制解调器(或网络)已经断开,则将挂断信号发送给控制进程(会话首进程)。

这些特性的关系如下图所示:
在这里插入图片描述

  1. 控制终端与整个会话关联,而不是仅与前台进程组关联。但是,只有前台进程组中的进程可以从控制终端读取输入并向其输出,同时接收终端产生的信号(如SIGINT、SIGQUIT、SIGTSTP等)。

  2. 会话首进程是创建该会话的进程,它通常是一个shell进程。会话首进程不一定在前台进程组中。实际上,当我们在shell中启动一个前台作业时,shell(会话首进程)会将自己设置为后台进程组,而将新启动的进程组设置为前台进程组。

所以,当有一个前台进程时,控制终端与前台进程组关联,会话首进程(通常是shell)则在后台进程组中。但是,会话首进程仍然与控制终端关联(因为它是会话的一部分),只是它不接收终端输入和终端产生的信号(因为只有前台进程组接收这些)。

举个例子
假设我们有一个shell(比如bash),它是会话首进程。我们在shell中运行一个前台命令,比如 vim

  1. 此时,shell会创建一个新的进程组,将vim放入该进程组,并将该进程组设置为前台进程组。

  2. 然后,shell自身所在的进程组就成为后台进程组。

  3. 控制终端仍然与整个会话关联,但只有vim(前台进程组)可以接收终端输入和信号。如果按下Ctrl+C,SIGINT会发送给vim,而不会发送给shell(因为shell在后台进程组)。

但是,如果控制终端断开(比如网络断开),则挂断信号(SIGHUP)会发送给会话首进程(即shell),然后shell通常会将SIGHUP发送给所有子进程(包括vim),然后自己退出。


4. 作业控制

4.1 什么是作业(job)和作业控制(Job Control)?

作业是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程,进程之间互相协作完成任务, 通常是一个进程管道。
Shell分前后台来控制的不是进程而是作业或者进程组。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制。

例如下列命令就是一个作业,它包括两个命令,在执行时Shell将在前台启动由两个进程组成的作业:

ltx@My-Xshell-8-Pro-Max-Ultra:~$ cat /etc/filesystems | head -n 5
xfs
ext4
ext3
ext2
nodev proc

4.2 作业号

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

例如下面的命令在后台启动了一个作业,该作业由两个进程组成,两个进程都在后台运行:

ltx@My-Xshell-8-Pro-Max-Ultra:~$ cat /etc/filesystems | grep ext &
[1] 2202
ext4
ext3
ext2
# 按下回⻋
[1]+ 完成 cat /etc/filesystems | grep --color=auto ext
  • 第一行表示作业号和进程ID,可以看到作业号是1,进程ID是2202
  • 第3-4行表示该程序运行的结果,过滤 /etc/filesystems 有关 ext 的内容
  • 第6行分别表示作业号、默认作业、作业状态以及所执行的命令

关于默认作业:对于一个用户来说,只能有一个默认作业 (+) ,同时也只能有一个即将成为默认作业的作业 (-) ,当默认作业退出后,该作业会成为默认作业。

  • + : 表示该作业号是默认作业
  • - :表示该作业即将成为默认作业
  • 无符号: 表示其他作业

在Shell中,当我们启动多个后台作业时,Shell会为每个作业分配一个作业号,并标记其中一个为默认作业(通常是最近启动的作业或最近被操作的作业)。

常见的作业状态如下表所示:

在这里插入图片描述

4.3 作业的挂起与切回

作业挂起
我们在执行某个作业时,可以通过 Ctrl+Z 键将该作业挂起,然后Shell会显示相关的作业号、状态以及所执行的命令信息。

例如我们运行一个死循环的程序, 通过 Ctrl+Z 将该作业挂起, 观察一下对应的作业状态:

#include <iostream>
#include <unistd.h>int main()
{while(true){std::cout << "hello server" <<std::endl;sleep(1);}return 0;
}

下面我运行这个程序, 通过 Ctrl+Z 将该作业挂起:

ltx@My-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ ./proc
hello server
hello server
^Z #键⼊Ctrl + Z观察现象
# 结果依次对应作业号 默认作业 作业状态 运⾏程序信息
[1]+  Stopped                 ./proc

可以发现通过 Ctrl+Z 将作业挂起, 该作业状态已经变为了停止状态

作业切回
如果想将挂起的作业切回,可以通过 fg 命令, fg 后面可以跟 作业号 或 作业的命令名称 。如果参数缺省则会默认将作业号为1的作业切到前台来执行,若当前系统只有一个作业在后台进行,则可以直接使用fg命令不带参数直接切回。 具体的参数参考如下:
在这里插入图片描述
例如我们把刚刚挂起来的 ./test 作业切回到前台:

ltx@My-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ fg %%
./proc
hello server
hello server
hello server
hello server
hello server
^C

运行结果为开始无限循环打印 hello server,可以发现该作业已经切换到前台了。

注意: 当通过 fg 命令切回作业时,若没有指定作业参数,此时会将默认作业切到前台执行,即带有“+”的作业号的作业


4.4 查看后台执行或挂起的作业

我们可以直接通过输入 jobs 命令查看用户当前后台执行或挂起的作业

  • 参数 -l 则显示作业的详细信息
  • 参数 -p 则只显示作业的PID
  • 参数 -r 只显示运行中的作业
  • 参数 -s 只显示停止的作业
  • 参数 -n 显示状态变化的作业 (只显示自上次通知后状态发生变化的作业)

例如, 我们先在后台及前台运行两个作业,并将前台作业挂起, 来用 jobs 命令查看作业相关的信息:

ltx@My-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ sleep 300 &
[1] 800147
ltx@My-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ ./proc
hello server
hello server
^Z
[2]+  Stopped                 ./proc
ltx@My-Xshell-8-Pro-Max-Ultra:~/gitLinux/Linux_network/Daemon/test$ jobs -l
[1]- 800147 Running                 sleep 300 &
[2]+ 800188 Stopped                 ./proc

注意: jobs 命令是Shell内置的作业控制功能,作用范围仅限于当前Shell会话。


4.5 作业控制相关的信号

上面我们提到了键入 Ctrl + Z 可以将前台作业挂起,实际上是将 STGTSTP 信号发送至前台进程组作业中的所有进程, 后台进程组中的作业不受影响。 在unix系统中, 存在3个特殊字符可以使得终端驱动程序产生信号, 并将信号发送至前台进程组作业, 它们分别是:

  • Ctrl + C : 中断字符, 会产生 SIGINT 信号
  • Ctrl + \ : 退出字符, 会产生 SIGQUIT 信号
  • Ctrl + Z :挂起字符, 会产生 STGTSTP 信号

终端的I/O(即标准输入和标准输出)和终端产生的信号总是从前台进程组作业连接打破实际终端。我们可以通过下体来看到作业控制的功能:

在这里插入图片描述

5. 守护进程

守护进程(Daemon Process)是计算机操作系统中一种在后台运行的特殊进程,它独立于控制终端,通常用于执行周期性任务或长期运行的服务。这类进程在系统启动时自动运行,直到系统关闭才终止。

主要特点

  • 脱离终端控制:守护进程在后台运行,不与任何用户终端直接交互,避免被终端信号中断。
  • 以 root 权限运行:通常需要特殊权限(如使用特定端口或资源),因此多以 root 用户身份启动。
  • 孤儿进程特性:其父进程在 fork() 创建子进程后立即退出,使守护进程被 init 进程(PID=1)接管,成为孤儿进程。
  • 常驻内存:生命周期长,从系统启动持续运行到关机,提供持续服务(如日志记录、网络服务等)。

传统的守护进程创建

#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. 每一个进程都有自己的CWD,是否将当前进程的CWD更改成为 / 根目录if (ischdir)chdir(root);// 5. 已经变成守护进程啦,不需要和用户的输入输出,错误进行关联了if (isclose){close(0);close(1);close(2);}else{// 这里一般建议就用这种int fd = open(dev_null, O_RDWR);if (fd > 0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}}
}

常见的系统守护进程

守护进程功能管理命令
sshdSSH服务器systemctl status sshd
crond定时任务systemctl status crond
rsyslogd系统日志systemctl status rsyslog
httpd/nginxWeb服务器systemctl status nginx
mysqld数据库systemctl status mysql

6. 守护进程 vs 后台进程

守护进程(Daemon Process)和后台进程(Background Process)都是在后台运行的进程,但它们在设计目标、运行方式和生命周期管理上存在本质区别。

守护进程(Daemon Process)

  • 守护进程是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

  • 守护进程通常随着系统的启动而启动,并一直运行直到系统关闭。它们不受用户登录和注销的影响。

  • 守护进程的父进程是init进程(PID=1),因为它是由init进程收养的。

  • 守护进程没有控制终端(它的TTY是?),所以它不会与用户交互。

  • 常见的守护进程有:sshd、httpd、mysqld等。

后台进程(Background Process)

  • 后台进程也是运行在后台的进程,但是它通常是由用户通过Shell(如bash)在终端中启动的,并在后台运行。

  • 后台进程与终端有关联(虽然它在后台运行,但它仍然属于当前终端会话)。如果终端关闭,那么后台进程会收到SIGHUP信号(默认会终止)。

  • 后台进程的父进程是启动它的Shell进程。

  • 用户可以通过fg命令将后台进程切换到前台,也可以通过bg命令将暂停的进程放到后台运行。

  • 后台进程通常用于让用户能够继续在同一个终端中工作,而不必等待进程结束。

关键差异详解

  1. 与会话和终端的关系
  • 后台进程:通过 & 或 Ctrl+Z + bg 命令创建,它仍然是当前登录会话(Session)‍ 和进程组(Process Group)‍ 的一部分。这意味着当用户注销或启动它的终端关闭时,该进程会收到 SIGHUP 信号并被终止。
  • 守护进程:通过调用 setsid() 等系统调用创建了一个新的独立会话,并摆脱了与任何控制终端的关联。因此,终端关闭或用户注销不会影响其运行,它由 init 进程(PID 1)直接接管。
  1. 文件描述符与输出
  • 后台进程:默认继承父进程(通常是Shell)的文件描述符。它仍然可以向终端输出信息,也可能意外地读取终端输入。
  • 守护进程:会主动关闭所有从父进程继承的文件描述符(包括标准输入、输出、错误),并将它们重定向到 /dev/null 或特定的日志文件,确保其运行不受终端干扰。
  1. 运行环境
  • 后台进程:其工作目录通常是启动它时所在的目录。如果该目录是一个可卸载的文件系统(如U盘),可能会导致问题。
  • 守护进程:通常会通过 chdir(“/”) 将其工作目录更改为根目录,以避免此类问题,并调用 umask(0) 以确保创建文件时具有预期的权限。

总结

简单来说,后台进程是“依附”于终端会话的临时后台任务,而守护进程是“脱离”一切终端独立存在的系统服务。


所以要如何做到一直在服务器上一直运行,不受会话影响呢,那就是成为守护进程

http://www.dtcms.com/a/545409.html

相关文章:

  • 建设部网站监理工程师报名wordpress菜单修改
  • vue 做网站 seo大连网站设计培训班
  • 【含文档+PPT+源码】基于SpringBoot和Vue的服装在线搭配及销售管理系统
  • 数据结构入门:深入理解顺序表与链表
  • 网站怎么做百度推广课题组网站怎么做
  • 前端React实战项目 全球新闻发布系统
  • 【React】 严格模式的 “双重执行” 机制,useEffect 执行两次
  • 使用 ngrok 在本地测试 Paddle Webhook 教程
  • React 入门 01:快速写一个React的HelloWorld项目
  • 地方旅游网站建设必要性网站怎么做站内美化
  • 设计网站栏目wordpress 三一重工
  • 黄冈网站建设策划海口建网站公司
  • 电子元器件学习-DC-DC篇:原理、拓扑结构、参数接收、手册解读、外围器件选型、Layout设计案例分析
  • SSD和HDD存储应该如何选择?
  • wordpress 博客 免费主题哈尔滨关键词优化方式
  • 河北网站排名网站内置字体
  • Yocto —— Linux Kernel 配置和修改
  • Rust结构体:数据组织的优雅范式与实例化实践
  • 【Harmony】鸿蒙相机拍照使用简单示例
  • 论文笔记:“Mind the Gap Preserving and Compensating for the Modality Gap in“
  • 国产光学软件突破 | 3D可视化衍射光波导仿真
  • 仓颉语言中的Option类型与空安全处理:深度解析与实践
  • 无穷级数概念
  • mysql的事务、锁以及MVCC
  • [Dify 实战] 使用插件实现内容抓取与格式转换流程:从网页到结构化输出的自动化方案
  • 李宏毅机器学习笔记35
  • 类和对象深层回顾:(内含面试题)拷贝构造函数,传值返回和传引用返回区别
  • Rust环境搭建
  • 潍坊做网站价格个人网页设计软件
  • LeetCode 刷题【138. 随机链表的复制】