一、进程组
1、理解
每一个进程除了有一个进程 ID(PID)之外 还属于一个进程组, **进程组是一个或者多个进程的集合, 一个进程组可以包含多个进程。**每一个进程组也有一个唯一的进程组 ID(PGID), 并且这个 PGID 类似于进程 ID, 同样是一个正整数, 可以存放在 pid_t 数据类型中。
2、举例
上图就是用管道创建了一个进程组。
我们可以用 ps ajx | head -1 && ps ajx | grep sleep,来查看指定的进程。
发现PGID是一样的2114632。
3、结论
PGID是进程组组长的PID,进程组组长可以创建一个进程组,也可以在自己的进程组里面创建新进程(父进程)
进程组的声明周期终止于最后一个进程退出,与组长无关。
二、会话
1、理解
会话可以看成是一个或多个进程组的集合, 一个会话可以包含多个进程组。每一个会话也有一个会话 ID(SID)
如三个进程SID相同,说明组成的进程组在同一个会话中。
2、Linux会话结构
首先我们登录Linux之后,先有 bash 进程创建终端文件,此时 bash 不仅是进程组组长,bash 进程组还是会话组长,我们大部分的工作都是在 bash 创建的会话里面完成的。建立与控制终端连接的会话首进程是控制进程 bash。
3、结论
(1)在同一个会话中允许存在多个进程组。
(2)但是前台进程只有一个,后台进程可以有多个
例如我在前台启动一个进程组,输入指令 bash 由于在后台,就不会处理指令。
(3)前后台:前台可以从标准输入中获取数据。进程在启动时最后带上&就是后台进程
(4)无论何时,发送的中断信号只能给前台进程。
三、控制终端
在 UNIX 系统中, 用户通过终端登录系统后得到一个 Shell 进程,这个终端成为 Shell 进程的控制终端。控制终端是保存在 PCB 中的信息,我们知道 fork 进程会复制 PCB 中的信息,因此由 Shell 进程启动的其它进程的控制终端也是这个终端。 默认情况下 没有重定向,每个进程的标准输入、标准输出和标准错误都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。另外会话、进程组以及控制终端还有一些其他的关系:
一个会话可以有一个控制终端,通常会话首进程打开一个终端(终端设备或伪终端设备)后,该终端就成为该会话的控制终端。
建立与控制终端连接的会话首进程被称为控制进程 。
一个会话中的几个进程组可被分成一个前台进程组 以及一个或者多个 后台进 程组 。
如果一个会话有一个控制终端,则它有一个前台进程组,会话中的其他进程组则为后台进程组。
无论何时进入终端的中断键(ctrl+c )或退出键( ctrl+\ ),就会将中断信号发送给前台进程组的所有进程。
如果终端接口检测到调制解调器(或网络)已经断开,则将挂断信号发送给控制进程(会话首进程)。
四、作业与作业控制
1、理解
作业 是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程,进程之间互相协作完成任务, 通常是一个进程管道。
2、作业控制操作
jobs:查看作业。-p查看作业pid
关于默认作业:对于一个用户来说,只能有一个默认作业(+),同时也只能有一个即将成为默认作业的作业(-),当默认作业退出后,该作业会成为默认作业。
- : 表示该作业号是默认作业
-:表示该作业即将成为默认作业
无符号: 表示其他作业
fg 作业号:把一个指定作业提到前台(接受用户输入的数据)
把作业放回后台:(1)作业暂停(ctrl z,也叫挂起)之后OS就会把作业放回后台。(2)bg 作业号:在后台启动作业
3、作业状态
4、举例
五、守护进程
1、理解
一个服务进程独立成为一个会话,就不会受原来会话的影响。
2、把进程变成守护进程
不是进程组组长调用函数 setsid 就可以把自己变成守护进程。调用的进程就会变成新会话的首进程,此时会话中只有一个进程,也是一个进程组的组长。
做法:父进程先 fork 子进程,父进程退出,子进程调用函数,由于子进程继承父进程的进程组id,进程id被重新分配,所以守护进程是孤儿进程。
这种进程已经不属于 bash 会话,只能用 kill 杀死进程。
3、代码实现
cpp
#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 = "/";
// 路径/dev/null 文件里面的数据不能被读取,相当于是被丢弃
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);
}
}
}