目录
一、前后台进程:
1、前台进程:
定义:直接与当前终端(Shell)交互的进程,用户输入的命令默认以前台进程运行
理解:
- 占用终端控制权,用户必须等待其执行完毕或手动暂停,才能在终端中执行其他命令
- 终端的输入 / 输出(STDIN/STDOUT/STDERR)直接与进程关联
- 前台进程只能有一个
当我们在xshell上远程登录主机的时候,就会有一个bash这样的进程,这个进程就是前台进程,前台进程是能够接收键盘的消息的,也就是当bash为前台进程的时候才能够解释指令,只有前台进程才能够被ctrl+c杀死(bash不能够被ctrl+c的原因是在实现的时候进行检查了,发现是ctrl+c就不执行指令)
当我们正常启动一个进程的时候,就是启动的前台进程,这个时候由于只能有一个前台进程,所以bash进程就变成后台进程了,此时也就对应着我们的现象 --- 当我们启动前台进程的时候,不能够让bash进程解释我们的指令了,这个时候就能够使用ctrl+c向前台进程发信号,终止前台进程了,此时bash就会顶上来,重新成为前台进程
2、后台进程:
定义:在终端后台运行的进程,不占用终端控制权,用户可继续在终端中操作
理解:
- 不与终端交互,终端关闭可能影响其运行(除非特殊处理,如守护进程)
- 通常用于执行耗时任务(如文件拷贝、编译程序)或服务程序(如 Web 服务器)
- 后台进程可以有多个
当bash成为后台进程的时候,通过键盘输入指令就是输入给前台进程的,这也就是当正在执行一个进程的时候,bash为什么不能够解释指令的原因
3、session:

因为前后台进程都能够向显示器中打印消息,所以显示器并不是区分前后台的指标
那么谁才是呢?
谁拥有键盘文件就是前台进程,也就是后台进程不能够执行标准输入,电脑只有一个键盘,所以前台进程也就只有一个
哪一个进程是前台进程本质是谁拥有键盘文件
如果把某个前台进程暂停了,bash就需要从后台进程转移到前台进程,不然当前会话就无法读取键盘数据了,所以在命令行中,前台进程必须都要存在,并且只有一个
我们可以启动多个会话,那么OS就需要将session管理起来,怎么管理 --- 先描述在组织,所以每一个session都需要有它自己的结构体来存储其状态和属性
二、前后台进程管理命令:
1、启动后台进程:
启动后台进程就是在启动前台进程的基础上,在后面加上 &即可

像上述那样,出现**[1] 3914129**那样就是证明该进程后台启动成功

并且此时能够执行命令行指令,因为此时前台进程还是bash,能够提供指令解释
2、前台与后台进程切换
后台进程变为前台进程
命令:fg [job_id]
其中的job_id就是在启动后台进程时,显示的[ ]里面的数字
示例:

像上述那样使用fg指令就能够将后台进程变为前台进程,此时就能够使用ctrl+c终止进程了
将前台进程暂停并变成后台进程
命令:ctrl+z
将前台进程暂停并变成后台进程,此时又必须有一个前台进程,那么bash就顶上来了
将后台暂停进程恢复为后台运行
命令:bg [job_id]

3、查看后台进程
当我们启动多个后台进程并且暂停多个的时候,能够知道有几个后台进程的
命令:jobs

-
1\]:作业编号(Job ID)
- +默认作业(下次fg,bg操作的目标)
- -次默认作业

如上,当我们启动三个后台进程后,bash依然是前台进程,能用指令jobs查看当前后台进程有几个

当我们启动前台进程后,将其暂停后成为后台进程,此时用jobs仍然能够看到,用bg是能够启动的

4、终止后台进程
方法 1:通过作业编号终止
- 命令:kill %job_id
- 示例:kill %1(终止作业 1)
方法 2:通过进程 ID(PID)终止
- 命令:kill pid
- 示例:kill 1234(终止 PID 为 1234 的进程)
三、守护进程:
1、前置知识:

首先了解上述:以下四个是重要的:
- PPID:父进程的 PID(进程 ID)
- PID:进程的唯一标识符(系统内唯一)
- PGID:进程所属的进程组ID(默认等于进程的 PID,若进程创建新进程组则可能变化)
- SID:进程所属的会话 ID(也就是session id)
拓展:
TTY:进程关联的终端设备
TPGID:前台进程组的ID
STAT:进程的状态码
UID:进程所属用户的ID
TIME:进程累计占用的CPU时间
COMMAND:启动进程的命令行
接下来谈谈PGID:
这是进程组的ID

如上,首先启动process.exe后台启动,然后启动sleep,这样的指令启动三个
注意看他们的PID和PGID,我们发现只有一个进程的进程组的组长就是自己
sleep那样三个进程为一个组,组长就是第一个进程
并且可以看到SID都是一样的,证明这些进程是在一个终端被启动的
接下来看看会话session:
bash
ps ajx | head -1 && ps ajx | grep -E '\-bash$'
当前会话也是一个进程,可以看到一个会话对应着一个-bash进程,前面的-表示远端登录

接下来增加会话:


依次增加session,就能够增加bash进程的个数,有这么多的session会话需要被OS管理起来 --- 先描述在组织,需要创建session结构体,然后里面的sessionid就是表示哪一个session
倘若当前会话退出了,会话里面创建的进程也会退出的:

如上左边终端里面有着如下的4个进程
然后我们把左边终端关掉,继续查看就看不到启动的4个进程了

2、什么是守护进程:

像上述那样,红色框框的进程从上面的会话自成一个新会话,从包含关系变成并且关系,这样无论之前的会话怎么样,和新成的会话无关,这样就成为了守护进程
而守护进程在后台独立运行,不受终端关闭的影响,也不向终端输出交互信息
特性 | 普通进程 | 守护进程 |
---|---|---|
终端关联 | 依赖终端,终端关闭可能终止 | 无终端关联,独立运行 |
交互性 | 可接收用户输入 / 输出信息 | 不与用户直接交互 |
生命周期 | 任务完成或主动退出即终止 | 随系统启动,长期运行 |
启动方式 | 由用户主动启动(如命令行) | 系统自动启动(如systemd ) |
3、编写守护进程:
需要用到如下接口:

当调用后的返回值就是新会话的ID,也就是SID
调用这个函数的进程不能够是进程组的组长,但是如果只有一个进程,这个进程自成进程组,那么这个进程也就是自己进程组的组长,也就是说,自成进程组的进程是不能够直接调用上述接口成为新会话的,那么就需要在代码中fork创建子进程,让自己直接终止,让子进程去调用setsid,进而让子进程成为新会话,成为守护进程
所以守护进程的本质是孤儿进程
那么我们自己写守护进程的思路:
1、忽略其他信号:让守护进程不受终端信号干扰,保持稳定运行,因为守护进程脱离终端后,不需要响应终端相关信号,否则可能导致进程意外终止
2、将自己变为独立会话:脱离原终端控制,成为新会话的组长,因为会话是进程组的集合,一个会话通常与一个终端关联,通过setsid系统调用创建新会话后,守护进程成为会话组长,不再受原终端的控制(如终端关闭不会影响进程),这样调用这个Daemon后就能够让父进程直接变为子进程
3、更改当前进程的工作目录,避免依赖启动时的工作目录,确保进程稳定运行,因为守护进程启动时的工作目录可能是用户目录或临时目录,若该目录被卸载或删除,会导致进程无法访问资源,通常将工作目录设置为根目录(/)或特定系统目录,确保目录始终存在
4、将标准输出, 标准错误进行重定向到dev/null,垃圾文件,为了切断与终端的输入输出联系,避免无意义的输出或错误,因为守护进程没有终端,若保留标准输入输出,任何读写操作都会导致错误,至于dev/null,这是Linux中的"垃圾桶",写入其中的内容会被丢弃,读取会立即返回 EOF,确保进程不会因 I/O 操作受阻
cpp
#pragma once
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
const string nullfile = "dev/null";
void Daemon(const string& cwd = "")
{
//忽略其他信号
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
//将自己变成独立的会话
if (fork() > 0) exit(0);
setsid();
//更改当前调用进程的工作目录
if (!cwd.empty())
{
chdir(cwd.c_str());
}
//将标准输出, 标准错误进行重定向到dev/null, 垃圾文件
int fd = open(nullfile.c_str(), O_RDWR);
if (fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
最后除了自己写的守护进程,系统也自带守护进程

参数解析:
nochdir(控制是否改变工作目录):
- 0:将工作目录切换为根目录(/),避免原工作目录被卸载 / 删除导致进程异常。
- 1:保持当前工作目录不变(需确保目录长期可用,否则有风险)。
noclose(控制是否重定向标准 I/O):
- 0:将 stdin、stdout、stderr 重定向到 /dev/null(切断与终端的 I/O 关联,避免阻塞)。
- 1:保持原文件描述符不变(需自行处理 I/O,否则可能因终端关闭导致错误)。
返回值
- 成功:返回 0(进程已成为守护进程)。
- 失败:返回 -1,并设置 errno(如 setsid 失败、chdir 失败等)。