进程组
进程组是一个或者多个进程的集合。每个进程除了pid,ppid之外还有pgid这个属性,即进程组id,它表示该进程属于哪个进程组:

在多进程任务中,第一个被创建的进程是组长进程,组长进程的pid就是本组的pgid。而子进程也会继承父进程的pgid(但可以通过setpgid函数改变,如果不能修改的话所有的进程就都成bash的组员了)。
需要说明的是,进程组会在组中所有进程都退出了之后销毁,至于组长是否提前退出对于组的存在是没有影响的。
会话
当我们使用xshell登录云服务器成功后,云服务器上的sshd服务会做这样的事情:
- 启动一个bash进程(相当于linux的命令行)以及一个终端文件。
- 然后调用setsid函数设置bash进程的会话id,即SID(一般来说就是会话中的第一个进程组的组长进程的pid)
结果如下:

- 上面sshd服务所做的工作其实就是在linux系统中创建一个session,由于进程间继承的缘故,bash进程创建的所有任务(进程组)包含的进程的SID都是bash进程最初设置的SID。所以逻辑上这些进程组属于同一个会话
- bash不直接从网络中读取xshell发送过来的命令,而是从终端文件中读取,输出执行结果的时候也直接输出到终端文件中。sshd服务会将命令解密后打在终端文件并将结果加密后返回给xshell。
- 会话中的所有进程组都可以向终端文件写入,但同一时刻只有一个进程组可以读取终端文件,可以读取终端文件的进程组叫做前台进程组,其他所有进程组都叫做后台进程组。初始时,前台进程组是bash。所以一但我们在bash上启动前台进程,bash将无法读取其他命令和接收信号直到bash重新变成前台进程。
整个示意图可以如下表示:

下面是查看终端文件的方法:

- 我们甚至可以直接往终端文件中写数据,也会被对应的bash拿到
作业控制
作业是对于用户来说,对于linux系统来说其实就是进程组。下面是对于作业的控制操作:
bash
fg 作业号 //将作业放到前台运行
bg 作业号 //让后台暂停的作业重新运行
jobs //查看当前会话的所有作业
ctrl+Z //暂停前台作业并将其变成后台作业
//这几个命令互相配合实现作业控制
实操:

作业的状态如下:

守护进程
首先要区分一个概念,把一个进程变成守护进程并不是说把它变成后台进程,因为不论是前台进程还是后台进程,bash进程一旦销毁,他们就都成了孤儿进程,难免会收到影响(可能是终止,也可能是其他影响),并且即使他们的暂时没被销毁,但会话ID仍然是原来的SID,一但OS以后清理会话就会把守护进程也杀死。而守护进程指的是不被任何其他进程所影响,默默运行的一种进程。
把一个进程变成守护进程本质上就是让该进程从当前会话中脱离出来,其实就是让其自成一个会话**。下面是把一个进程变成守护进程的函数,只要调用它进程就变成守护进程:**
cpp
const char* root = "/";
const char* dev_null = "/dev/null";
void Daemon(bool ischdir, bool isclose)
{
// 1. 忽略可能引起程序异常退出的信号,不止这三个
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGHUP, SIG_IGN)
// 2. 让自己不要成为组长,因为规定调用setsid的进程不能是组长进程
if (fork() > 0)
exit(0);
// 3. 设置让自己成为一个新的会话, 后面的代码其实是子进程在走
setsid();
// 4.守护进程一般有自己的工作目录,可以选择是否将当前进程的 CWD 更改成为根目录
if (ischdir)
chdir(root);
// 5. 可以选择是否关闭默认输入输出流
if (isclose)
{
close(0);
close(1);
close(2);
}
else
{
//如果怕关闭会导致守护进程读写失败而崩溃,可以用这种/dev/null是linux中的特殊文件,读不到内容,写了的数据会被丢弃
int fd = open(dev_null, O_RDWR);
if (fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
}
调用该函数之后,进程会变成孤儿进程,因为我们是创建了子进程然后直接让父进程退出。