1.进程组
每个进程除了有进程 ID(PID)之外,还属于一个进程组。进程组是一个或多个进程的集合,一个进程组可以包含多个进程,每个进程组也有一个唯一的进程组 ID(PGID),类似于进程 ID,
每个进程组都有一个组长进程。组长进程的 ID 等于其进程 ID,进程组组长可以创建一个进程组或者创建该组中的进程,
进程组的生命周期:从进程组创建开始到其中最后一个进程离开为止,只要进程组中有一个进程存在,则该进程组就存在,这与组长进程是否终止无关
2.会话
什么是会话
会话可以看作是一个或多个进程组的集合,一个会话可以包含多个进程组,每个会话也有一个会话 ID(SID)
创建一个会话,一般会形成一个终端文件,然后关联一个 bash 进程,bash 进程单独一个进程组,会话 ID 一般是会话中第一个进程 的 ID,一般是 bash 进程 ID
同一个会话中,可以同时存在多个进程组,但是,任何时刻只允许一个前台进程(组)运行,可以允许多个进程(组)后台运行
后台运行进程
如何创建会话
可以调用 setseid 函数,前提是调用进程不能是一个进程组的组长
会话 ID
会话首进程的进程 ID 就是会话 ID,会话首进程一般是 bash 进程,bash 进程组中就一个 bash 进程
3.控制终端
在UNIX 操作系统中,用户通过终端登录系统得到一个 shell 进程。这个终端成为 shell 进程的控制终端。控制终端是保存在 PCB 中的信息,我们知道 fork 进程会复制 PCB 中的信息,因此 shell 进程启动的其他进程的控制终端也是这个终端
- 一个会话可以有一个控制终端,通常会话首进程打开一个终端后,该终端就会成为该会话的控制终端
- 建立与控制终端连接的会话首进程被称为控制进程
- 一个会话中的进程组可被分为一个前台进程组和一个或多个后端进程组
- 进入终端的中断键(ctrl+c)或退出键(ctrl+\),就会将 信号发送给前台进程组的所有进程
4.作业控制
作业和作业控制
作业是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程,进程之间相互协作完成任务
shell 分前后台来控制的不是进程而是作业或进程组。一个前台作业可以有多个进程组成,shell 可以同时运行一个前台作业和任意多个后台作业,这被称为作业控制
作业号
放在后台执行的程序或命令称为后台命令,可以在命令的后面加上&符号从而让 shell 识别到这是一个后台命令,后台命令不用等待该命令执行完成,就可立即接受新的命令,另外后台进程执行完后会返回一个作业号以及一个进程号
可以看到作业号是 1,进程 ID 是 2600582
5.守护进程
守护进程(Daemon)是一类在后台运行的特殊进程,它们通常在系统启动时启动,并在系统运行期间提供各种服务,或者等待执行特定任务
守护进程的特点包括:
- 后台运行:守护进程在后台运行,不与任何控制终端直接关联,它们独立于用户直接交互。
- 长时间运行:守护进程通常在整个系统运行期间持续运行,或者至少在需要时保持活动状态。
- 服务提供者:守护进程提供系统或网络服务,例如Web服务器、数据库服务器、邮件服务器等。
- 监听请求:守护进程通常监听特定的端口或系统事件,等待客户端的请求或系统的通知。
- 无用户交互:守护进程不提供直接的用户交互界面,它们通过程序接口与系统或其他进程通信。
- 自动重启:在某些系统中,如果守护进程崩溃或被终止,系统可能会自动重启它们,以确保服务的连续性。
- 系统管理:守护进程可以用于执行系统管理任务,如日志记录、资源监控、定时任务等。
- 权限:守护进程通常以特定的用户身份运行(如root或其他系统用户),以执行需要特定权限的操作。
- 进程特性:在Unix-like系统中,守护进程可以通过修改进程的umask值、关闭文件描述符、创建新的会话和进程组等操作来设置自己的运行环境。
- 日志记录:守护进程通常会将运行状态和错误信息记录到日志文件中,以便系统管理员监控和调试。
守护进程的代码
#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 = "/";
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);
}
}
}
6.如何将服务进程变成守护进程
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;
}