进程组
进程组是一个或者多个进程的集合,一个进程组可以包含多个进程。
PGID就是代表该进程所属进程组,我们同时启动了三个sleep进程,我们发现这三个进程的PGID是相同的,且他们的PGID都与第一个创建的进程的PID相同。
所以三个sleep是一个进程组,组长进程是他们中第一个创建的进程。
- 进程组组长的作用: 进程组组长可以创建一个进程组或者创建该组中的进程
- 进程组的生命周期: 从进程组创建开始到其中最后一个进程离开为止。注意:
只要某个进程组中有一个进程存在, 则该进程组就存在, 这与其组长进程是否已经终
止无关。
会话
什么是会话
一个会话可以是一个或者多个进程组的集合,一个会话可以包含多个进程组,每个会话也会有一个会话ID(SID)
我们知道我们的bash进程一直在前端运行的,如果我们想指定我们的命令在后端运行呢?
在要执行的命令后面加 & 即可。
至于1代表什么以及后面的数值代表什么后面会讲。
所以这三个进程是依然能查到的,我们如果想退出他们,只能使用命令杀掉他们!
值得注意的是:每个终端上只能有一个前端进程,可以有多个后端进程。
如何创建一个会话
使用系统调用接口setsid。
无需传参,返回的是SID,错误返回-1。只有一个进程时其实就是这个进程的PID,默认都是所有进程的第一个进程的PID。
需要注意的是: 这个接口如果调用进程原来是进程组组长, 则会报错, 为了避免这种情况, 我们通常的使用方法是先调用 fork 创建子进程, 父进程终止, 子进程继续执行, 因为子进程会继承父进程的进程组 ID, 而进程 ID 则是新分配的, 就不会出现错误的情况。
会话ID(SID)
假如我们启动多个xshell终端会怎样?
这里我们启动四个xshell!
我们果然查到了四个bash进程,其实每打开一个xshell终端就创建了一个会话,而且这些会话的SID都不相同,在同一会话创建的所有进程组的进程都有一个相同的SID。
控制终端
- 一个会话可以有一个控制终端, 通常会话首进程打开一个终端(终端设备或伪终端设备) 后, 该终端就成为该会话的控制终端。
- 建立与控制终端连接的会话首进程被称为控制进程。
- 一个会话中的几个进程组可被分成一个前台进程组以及一个或者多个后台进程组。
- 如果一个会话有一个控制终端, 则它有一个前台进程组, 会话中的其他进程组则为后台进程组。
- 无论何时进入终端的中断键(ctrl+c) 或退出键(ctrl+\) , 就会将中断信号发送给前台进程组的所有进程。
- 如果终端接口检测到调制解调器(或网络) 已经断开, 则将挂断信号发送给控制进程(会话首进程) 。
作业控制
什么是作业(job)和作业控制(job control)?
一个作业可以包含一个进程或者多个进程,共同协作完成一件事情,我们称这个过程为一个作业,当通常是一个进程管道。
Shell 分前后台来控制的不是进程而是作业 或者进程组。 一个前台作业可以由多个进程组成, 一个后台作业也可以由多个进程组成, Shell 可以同时运⾏一个前台作业和任意多个后台作业, 这称为作业控制。
作业号
&代表让该次作业在后台运行,我们看到的1和后面的数字是什么?
1代表的是这个作业的编号,而14136是最后一个进程的PID。
再创建一个作业,自然编号就变为2了!
作业状态
查看作业状态
jobs -l
fd + 作业号 — 把该作业调到前端运行。
Ctrl + Z暂停后台任务
bg + 作业号,启动后台任务
- +: 表示该作业号是默认作业
- -: 表示该作业即将成为默认作业
- 无符号: 表示其他作业
作业切回
使用fg命令
注意: 当通过 fg 命令切回作业时, 若没有指定作业参数, 此时会将默认作业切到前台执行, 即带有“+”的作业号的作业
守护进程
守护进程就是在原会话基础上另外创建一个会话,这个会话内的进程就被称为守护进程。
#pragma once
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string defaultpath = "/";
const std::string defaultdev = "/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(defaultpath.c_str());
// 5.成为了守护进程,不需要和用户的输入输出,错误进行关联了
if (isclose)
{
close(0);
close(1);
close(2);
}
else
{
// 这里建议一般就用这种
int fd = ::open(defaultdev.c_str(), O_RDWR);
if (fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
::close(fd);
}
}
}
如何将服务守护进程化
// ./server port
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cout << "Usage : " << argv[0] << " port" << std::endl;
return 0;
}
uint16_t localport = std::stoi(argv[1]);
Daemon(false, false);//往下都是子进程在运行了
std::unique_ptr<TcpServer> svr(new TcpServer(localport,
HandlerRequest));
svr->Loop();
return 0;
}