超出能力之外的事,
如果永远不去做,
那你就永远无法进步。
--- 乌龟大师 《功夫熊猫》---
之前我们提到了进程的概念, 其实每一个进程除了有一个进程 ID(PID)之外 ,还属于一个进程组。 进程组是一个或者多个进程的集合, 一个进程组可以包含多个进程。 每一个进程组也有一个唯一的进程组 ID(PGID), 并且这个 PGID 类似于进程 ID, 同样是一个正整数, 可以存放在 pid_t 数据类型中!
我们现在启动一些程序sleep 1000 | sleep 2000 | sleep 3000
,可以来看看:
这三个sleep
进程就属于同一个进程组,进程组PGID是34379!他们的PPID是bash,都是34345!
同样的通过fork创建的父子进程也是属于同一个进程组:
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int n = fork();
if(n == 0)
{
while(true)
{
std::cout << "I am Child process ,PID: "<< getpid() <<std::endl;
sleep(1);
}
}
std::cout << "I am Parent process ,PID: "<< getpid() <<std::endl;
sleep(100);
return 0;
}
每一个进程组都有一个组长进程。 组长进程的 ID 等于其进程 ID。 我们可以通过 ps 命令看到组长进程!
提到了会话,我们先来谈一谈我们平时是怎么通过Xshell进行登录的。每当我们通过Xshell客户端正确的登录到Linux系统后,系统会给我们创建一个终端文件,并且配套一个bash进程(进程组的形式)!我们写的命令写入到终端文件,然后通过bash进程执行在返回结果。 我们来看看系统是不是会在我们登录时创建一个终端文件,并且配套一个bash进程
可以看到我们每次打开一个新的的会话,就会产生一个对应的bash文件!终端文件也是如此:
刚刚我们谈到了进程组的概念, 那么会话又是什么呢? 会话其实和进程组息息相关,会话可以看成是一个或多个进程组的集合, 一个会话可以包含多个进程组。 每一个会话也有一个会话 ID(SID),一般是会话的第一个进程操PID。
通常我们都是使用管道将几个进程编成一个进程组
可以调用 setseid 函数来创建一个会话, 前提是调用进程不能是一个进程组的组长。
include <unistd.h>
//功能: 创建会话
//返回值: 创建成功返回 SID, 失败返回-1
pid_t setsid(void);
该接口调用之后会发生:
上边我们提到了会话 ID, 那么会话 ID 是什么呢? 我们可以先说一下会话首进程, 会话首进程是具有唯一进程 ID 的单个进程, 那么我们可以将会话首进程的进程 ID 当做是会话 ID。 注意: 会话 ID 在有些地方也被称为 会话首进程的进程组 ID, 因为会话首进程总是一个进程组的组长进程, 所以两者是等价的。
先说一下什么是控制终端? 在 UNIX 系统中, 用户通过终端登录系统后得到一个 Shell 进程, 这个终端成为 Shell进程的控制终端。 控制终端是保存在 PCB 中的信息, 我们知道 fork 进程会复制 PCB中的信息, 因此由 Shell 进程启动的其它进程的控制终端也是这个终端。 默认情况下没有重定向, 每个进程的标准输入、 标准输出和标准错误都指向控制终端, 进程从标准输入读也就是读用户的键盘输入, 进程往标准输出或标准错误输出写也就是输出到显示器上。 另外会话、 进程组以及控制终端还有一些其他的关系。
我们在下边详细介绍一下:
通常我们执行程序,都是在前台进行运行的。当我们在启动程序后加入&
就会在后台运行程序。
在同一个会话中可以运行同时存在多个进程组,但是,任何时刻,只允许一个前台进程组,可以运行多个后台进程组!需要注意的是只有前台进程组可以获取到标准输入!后台不能获取标准输入!
作业在Linux环境中,是指为完成用户指定任务而启动的一组进程。一个作业可能仅包含单一进程,也可能由多个相互协作的进程构成,这些进程通常通过管道机制进行通信。
在Shell的管理下,控制单元并非单个进程,而是作业或进程组。前台作业可能由多个进程联合执行,同样,后台作业也可以由一系列进程共同构成。Shell能够同时管理一个前台作业和多个后台作业,这种能力我们称之为作业控制。通过这种方式,用户可以在不中断前台操作的前提下,有效地调度和监控后台任务。
我们来看:[作业号] 进程组长pid
我们来看看详细的信息,可以看到正在Runing。
fg 作业号
将后台作业移动到前台
ctrl + z
暂停进程就将其放回到后台了,然后再通过bg 作业号
启动就可以了!
来看一下作业的状态:
状态名称 | 描述 |
---|---|
运行中 Running | 作业正在执行。 |
暂停 Suspended | 作业被挂起,等待继续执行。 |
停止 Stopped | 作业已经结束执行。 |
后台运行 Background | 作业在后台执行,不占用命令行界面。 |
前台运行 Foreground | 作业在前台执行,用户必须等待其完成后才能进行其他操作。 |
已完成 Completed | 作业成功执行完毕。 |
已终止 Terminated | 作业因错误或其他原因被强制终止。 |
等待中 Waiting | 作业正在等待系统资源或其他作业的完成。 |
在Linux中,作业状态的产生如下:
Ctrl+Z
暂停前台作业。暂停的作业可以通过bg命令将其放入后台,或者通过fg命令将其恢复到前台运行。&
或使用bg
命令。fg
命令。守护进程,又称为Daemon:守护进程是一种在操作系统后台运行的进程,它通常在系统启动时开始运行,并在系统关闭时终止。它独立于任何控制终端,不会因为用户登录或注销而受到影响。守护进程通常用于执行系统级别的任务,如网络服务、系统监控、日志记录等,它们默默地工作,不需要用户直接交互,确保了系统服务的持续性和稳定性。
那么守护进程是怎么实现的?
对于守护进程,每一个程序员实现守护进程的方法可能都不太一样,这里只展示最基础的实现方法:
const char *root = "/";
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);
}
}
}
这样就设置好了守护进程!!!