1.前台进程与后台进程
1.1守护进程
在上一章中,我们实现了一个Tcp服务器,但是这个服务器还存在一些问题,例如,我们将云服务器(xshell)关闭之后,服务器就无法使用了。
但是真正的服务器肯定不是这样的,启动之后不受用户影响,除非主动将这个服务器kill掉。我们考虑将服务器修改成为守护进程
守护进程(Daemon)是在计算机操作系统中以后台形式运行的一类特殊进程。这些进程在系统启动时被启动,并持续在后台运行,直到系统关闭。它们不依赖于任何用户交互,也不与任何终端相关联。
1.2前台进程
前台进程就是那些我们能够直接看到并与之交互的进程。比如我们使用xshell,Linux服务器会为我们提供一个命令行解释器bash。这个命令行解释器就是一个前台进程,会与用户交互。
1.3后台进程
后台进程(Background Process)是指在计算机操作系统中运行但不在前台显示界面的进程。与前台进程不同,后台进程通常不会直接与用户交互,而是在系统后台默默地执行各种任务。
当我们调用一个sleep之后,就会卡住,原因就是操作系统会执行创建一个进程去执行sleep 1000。此时这个进程就自动变成了前台进程,而bash变成了后台进程,此时我们想执行shell命令也无法执行,因为此时与我们交互的进程是sleep 1的进程。
而我们刚刚说的守护进程其实就属于后台进程。
1.4前后台进程转换
在启动服务时,在后面加一个 & 符号就可以让该进程在后台运行
可以看到,sleep变成后台进程后,我们不会像上面一样卡住了。
我们可以使用jobs命令查看后台运行的任务
如果是已经运行的前台进程或者后台进程呢
后台进程转前台
使用 fg + 任务编号 就可以把一个后台任务放到前台
注意:前台进程只能有一个,当一个进程变成前台进程后,bash会自动变成后台进程,此时命令行就无法使用了。
前台进程转后台
使用 [Ctrl Z] + bg + 任务编号 就可以把前台进程放到后台
先使用 [Ctrl Z] 我们会发现,这个进程就会进入停止状态(Stopped), 再使用bg + 任务编号,就可以将这个进程在后台开始运行(Runing)
2.任务管理
2.1进程组
每个进程属于一个进程组,每个进程组中包含一个或多个进程。并且每个进程组都会有个进程组组长,通常是创建这个组的第一个进程的PID,也就是说进程组的组长的PID和组ID相等。
当我们使用ps命令就可以查看进程的组ID(PGID),上面的例子中,两个进程属于不同的进程组。
我们执行了sleep命令后,系统会帮我们创建进程去执行sleep,此时我们发现,使用管道连接的几个进程的组ID(PGID)是相同的。
2.2作业
作业是用户在一次解题或一个事务处理过程中要求计算机系统所做的工作的集合,包括用户程序、所需的数据和命令等。作业管理涉及从提交作业到得到运行结果并最终退出系统的整个管理过程。作业与进程组有一定的关联,因为进程组可以被视为作业的表示。
jobs就是用于查看linux中的作业列表,最左边的编号就是任务号。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。
2.3会话
会话就是一个或者多个进程组的集合
在一个会话中,通常有一个特殊的进程被指定为会话首领(session leader),它是该会话的控制进程,通常是由用户启动的程序(如shell)。会话首领的进程ID就是该会话的ID。一个会话可以包含多个进程组,每个进程组有一个唯一的进程组ID。
任何时刻,一个会话中会有很多个进程组。但是任何时刻只允许一个在前台。
SID就是会话ID
2.4如何理解
我们先写两个命令,第一个是启动两个进程,并且放到后台运行,第二个是启动三个进程在前台运行
此时我们查看一下这几个进程
前面两个属于一个后台进程组,后面的三个属于一个前台进程组。但是这五个进程都属于一个会话
实际上我们每次登录的时候,都会为我们创建一个新会话,我们可以使用ps命令查看会话
我们登录四个用户,查看每个bash的会话ID发现都不一样
再举个例子,理解进程组,作业,会话三者之间的关系
进程组
进程组就像是一个团队,他们在一起做同样的事情。进程组可以帮助操作系统集中管理这些相关的进程,比如发送相同的信号给所有的进程。
会话
会话就像是一场厨艺比赛。在这个比赛上,可能有多个团队,他们都在同一个环境下(场地)进行各自的活动。会话首领就像是这个比赛的负责人,负责整个比赛的组织和协调。
作业
作业就像是比赛过程中的任务(比如做饭)。这个任务需要多个进程(切菜、炒菜、煮饭等)合作完成,这些进程组成了一个作业。作业管理就是确保这些进程能够按照正确的顺序和方式执行,最终完成整个任务。
3.守护进程
守护进程(Daemon)是在计算机操作系统中以后台形式运行的一类特殊进程,它们在系统启动时被启动,一直运行在后台,不依赖于任何用户交互,也不与任何终端相关联。主要用于执行特定的系统任务,如管理网络服务、定时任务、系统监控、日志记录等。
守护进程是一个独立的会话,不隶属于任何一个bash
左边就是我们之前的方式,右边是守护进程的方式。
3.1成为守护进程的条件
- 守护进程必须是孤儿进程
- 守护进程不能是进程组的组长
- 忽略掉异常信号(可能会导致进程退出)
- 守护进程要脱离终端(不能与显示器交互),所以我们要关闭 / 重定向0,1,2三个文件描述符
我们不建议之间将这几个文件描述符关闭,因为可能会有一些日志信息需要打印,但是0,1,2文件描述符已经关闭,所以会向不存在的文件中写入时就会出错。
系统为我们提供了一个/dev/null文件,/dev/null文件会帮我们清空写入的数据,就像是一个垃圾桶一样。所以我们可以将文件重定向到/dev/null中。
3.2成为守护进程的步骤
- 忽略信号:忽略可能会引起程序异常退出的信号,例如SIGCHLD,SIGPIPE等。
- 创建子进程:通过调用
fork()
创建新的子进程,然后让父进程立即退出。这样,子进程将成为一个孤儿进程,被init进程(PID为1的进程)接管,从而脱离控制终端。- 创建新的会话:在子进程中调用
setsid()
,创建一个新的会话,并成为会话的首领。这样,守护进程将拥有一个独立的会话和进程组,不再与终端相关联。- 改变工作目录:将当前工作目录改为根目录或其他合适的目录,以切断与原始文件系统的联系。
- 重定向标准I/O:将标准输入、标准输出和标准错误输出重定向到
/dev/null
,以确保守护进程不依赖于任何终端的I/O操作。- 关闭不必要的文件描述符:关闭所有不必要的文件描述符,防止它们被继承并在后续操作中造成冲突。
我们先学习一下一个函数
#include <unistd.h>
pid_t setsid(void);
setsid系统函数用于启动一个进程,并将其与当前会话分离。调用setsid函数的进程会成为新的会话的领头进程,并与其父进程的会话组和进程组脱离。
调用setsid函数的进程需要满足一定条件,它不能是组长进程,并且需要有对应的创建session的权限。
在实际应用中,setsid系统函数常用于实现守护进程(daemon),即那些在系统启动时开始运行,并在后台持续运行以提供服务或执行任务的进程。通过将进程与会话分离,守护进程可以独立于控制终端运行,从而确保即使终端关闭,守护进程也能继续执行其任务。
3.3代码实现
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const char* defaultPwd = "/";
const char* devnull = "/dev/null";
void Daemon(bool ifchdir, bool ifclose)
{
//1.忽略异常信号
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
//2.创建子进程
if (fork() > 0)
exit(0);
//3.创建新的会话
setsid();
//4.更改路径
if (ifchdir = true)
chdir(defaultPwd);
//5.关闭或者重定向文件描述符
if (ifclose = true)
{
close(0);
close(1);
close(2);
}
else
{
int fd = open(devnull, O_RDWR);
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
}
}
我们运行一下试试。
最终发现,确实变成了一个守护进程(TPGID为-1)
我们把xshell关闭周再打开试试。
重启之后,这个进程依旧再运行,此时这个进程就变成了守护进程。如果想关闭,直接使用kill命令杀死进程就可以了。
所以现在我们就可以把我们上次写的TCP服务器改成守护进程了。