Linux网络:守护进程
文章目录
- 前言
- 一,会话和进程组
- 1-1 会话的定义
- 1-2 进程组的定义
- 1-3 进程组和会话的区别
- 二,守护进程
- 2-1 setsid
- 2-2 daemon
前言
什么是守护进程?
-
守护进程是
Linux
里在后台运行的程序,不跟用户直接互动(没界面),悄悄提供服务。它们脱离终端(TTY
是 ?),通常系统启动就运行,直到关机。 -
比喻:守护进程像餐厅的“隐形帮手”(比如自动洗碗机或空调)。你吃饭时不用管它们,但它们让餐厅(系统)正常运转。
-
当我们关闭终端会话(比如退出 SSH 或关闭终端窗口)时,终端会关闭,与该会话关联的前台进程会终止,后台进程也可能因收到
SIGHUP
信号而终止,但服务器本身仍在运行。如果我们希望某些进程在会话关闭后继续运行,可以使用守护进程(daemon
),它通过创建新会话(setsid
)脱离终端,适合长期运行的服务
一,会话和进程组
1-1 会话的定义
- 会话是
Linux
中一组相关进程的集合,通常与一个终端(比如你打开的Ubuntu
终端窗口pts/0
或SSH
连接)绑定。 - 每个会话有唯一的会话
ID(SID)
,由会话的第一个进程(会话领导者)的进程ID(PID)
决定。 - 会话里的进程通常一起工作,比如你运行一个命令
(ls | grep txt)
,ls
和grep
这些进程属于同一个会话。
比喻:
会话像餐厅的一个“工作小组”,由一个“组长”(会话领导者,比如 bash
)带领。小组里的成员(进程)一起干活,组长负责和餐厅前台(终端)沟通。如果前台关门(终端关闭),整个小组通常会解散(进程终止)。
作用:
- 组织进程:把相关的进程(比如一个命令涉及的多个进程)归到一起,方便管理。
- 终端控制:会话通常与终端关联,控制输入/输出(比如键盘输入、屏幕显示)。
- 信号传递:当终端关闭时,会话中的进程会收到
SIGHUP
信号,通常导致终止。
像在xshell
这里的这个新建会话就是新建一个bash
,如果退出bash那么这个bash
下的进程都会退出,或者我们后续在bash
中通过setsid
创建会话
root@hcss-ecs-f59a:~# ps -jPID PGID SID TTY TIME CMD52589 52589 52589 pts/0 00:00:00 bash52990 52990 52589 pts/0 00:00:00 sleep52991 52991 52589 pts/0 00:00:00 sleep52992 52992 52589 pts/0 00:00:00 ps
像在这里的SID
相同的就是该会话bash
下的进程
1-2 进程组的定义
进程组是由一个或多个进程构成的,每个进程组有唯一的进程组 ID(PGID)
,通常由组内第一个进程(进程组领导者)的 PID
决定。
进程组中的进程通常一起工作,比如执行一个命令或管道(ls | grep txt)
,共享同一个 PGID
,ls
和grep
都是一个独立的进程,将它们规定在一个进程组织,实现整体效果
作用:
-
统一管理:Linux 可以给整个进程组发送信号(如 Ctrl+C 终止所有相关进程)
-
终端控制:进程组决定哪些进程是前台(接受键盘输入)或后台(用 & 运行)
-
组织相关任务:比如管道命令的多个进程组成一个进程组
sleep 200 &
[1] 48062
root@hcss-ecs-f59a:~# sleep 300 &
[2] 48074
root@hcss-ecs-f59a:~# sleep 400 &
[3] 48075
这里我们运行三个进程,在后台休眠200秒,300秒,400秒
查看进程
root@hcss-ecs-f59a:~# ps -jPID PGID SID TTY TIME CMD47807 47807 47807 pts/0 00:00:00 bash48062 48062 47807 pts/0 00:00:00 sleep48074 48074 47807 pts/0 00:00:00 sleep48075 48075 47807 pts/0 00:00:00 sleep48153 48153 47807 pts/0 00:00:00 ps
-
SID
:每个进程有一个SID
,表示它属于哪个会话(一组相关进程的集合)。 -
bash
:这是你的终端shell
(命令行解释器),通常是会话的领导者,它的进程ID(PID)
等于会话的SID
。 -
SID
和bash
相同说明:该进程属于当前终端的会话,即它和bash
在同一个“工作小组”中,这个进程受当前终端控制,它可以访问终端的输入/输出(键盘、屏幕),但如果关闭终端,会话结束,进程可能会收到SIGHUP
信号而终止。
简单说:进程“还在终端的管辖范围内”,像一个“团队成员”,跟着 bash(组长)一起工作
1-3 进程组和会话的区别
会话构成:会话是由一个或多个进程组构成的,通常与一个终端(如 pts/0
或 SSH
连接)绑定,每个会话有一个会话领导者
,通常是启动会话的进程(比如 bash
),其 PID(进程 ID)
等于SID(会话 ID)
,会话中的所有进程组共享同一个 SID
,SID
由会话领导者的 PID
决定
进程组构成:进程组是由一个或多个进程
构成的,每个进程组有唯一的进程组 ID(PGID)
,通常由组内第一个进程(进程组领导者)的 PID
决定。进程组中的进程通常一起工作,比如执行一个命令或管道(ls | grep txt)
,共享同一个 PGID
比喻:
会话是大团队(由 bash
领导,SID = bash PID
),进程组是小分队(由一个进程领导,PGID = 其 PID
),大团队(会话)包含多个小分队(进程组),小分队由队员(进程)组成
你的终端会话(SID=35415,bash 的 PID)可能包含:
- 进程组 1:运行
ls | grep txt
(PGID=36133,包含 ls 和 grep)。 - 进程组 2:运行
sleep 200 &
(PGID=36135,包含 sleep)。
用 setsid sleep 200 &
,创建一个新会话(SID=36132
),包含一个进程组(PGID=36132
,只有一个进程 sleep
)。
二,守护进程
守护进程的特点:脱离终端(不会因为用户退出而结束),独立于用户会话,不受终端控制,可以长期在后台运行。
当会话退出时,也就是当我们关闭xshell
关闭了我们的bash
,我们的会话也就结束了,那么会话当前下的进程组也就结束了,如果我们不想关闭xshell
后bash
就关闭的话我们可以选择使用setsid
或则daemon
新建一个会话来执行这个进程组
2-1 setsid
setsid()
是一个系统调用,用来 创建一个新的会话,并让调用进程成为这个新会话的首进程
函数原型(在 <unistd.h>
里):
pid_t setsid(void);
返回值:成功返回新会话的 ID
,失败返回 -1
演示代码
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdlib>int main() {pid_t pid = fork();if (pid < 0) {std::cerr << "fork failed" << std::endl;exit(1);}if (pid > 0) {// 父进程直接退出std::cout << "Parent exit, child PID: " << pid << std::endl;exit(0);}// 改变当前目录(可选,防止占用挂载点)chdir("/");// 关闭标准输入输出(可选)close(STDIN_FILENO);close(STDOUT_FILENO);close(STDERR_FILENO);// 守护进程的核心逻辑while (true) {int fd = open("/tmp/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);if (fd >= 0) {std::string msg = "Daemon is alive, PID: " + std::to_string(getpid()) + "\n";write(fd, msg.c_str(), msg.size());close(fd);}sleep(5); // 每隔 5 秒写一次}return 0;
}
重点讲解:在这里我们实现了一个进程,不断向/tmp/daemon.log
中打印数据。
在这里我们将进程的工作路径重定向到/
根目录中,是为了防止工作目录被删除,进程工作丢失,其它的代码不过多讲解
setsid()
:让子进程创建一个新的会话,成为会话首进程,脱离控制终端
2-2 daemon
setsid()
是用来创建新会话、脱离终端的底层系统调用,通常需要配合 fork()
、chdir("/")
、关闭文件描述符等步骤才能完整实现守护进程;而 daemon()
是对这些步骤的封装,调用后可自动完成会话创建、切换目录、重定向文件描述符等操作,更方便但灵活性较低
函数声明:
#include<unistd.h>
int daemon(int nochdir, int noclose)
nochdir
:改变工作目录- 传入0:改变工作目录为根目录
- 传入1:保持当前工作目录
nclose
:改变输入输出流- 传入0:输入输出流重定向到/dev/null
- 传入1:不改变输入输出流
演示代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>int main() {// 创建守护进程if (daemon(0, 0) == -1) {perror("daemon error");exit(1);}FILE *fp;while (1) {fp = fopen("/tmp/daemon_test.log", "a+");if (fp) {time_t t = time(NULL);fprintf(fp, "Daemon is running at: %s", ctime(&t));fclose(fp);}sleep(5); // 每隔5秒写一次日志}return 0;
}
这里我们也是创建了一个脱离bash
的会话,但是我们的daemon
封装了setsid
和chdir
和fork