进程组
概念
进程组就是一个或多个进程的集合。
一个进程组可以包含多个进程。
下面我们通过一句简单的命令行来展示:
为什么会有进程组?
- 批量操作:进程组允许将多个进程组织在一起,形成一个逻辑上的整体。当需要对多个进程执行相同的操作时,可以通过进程组进行操作实现,不用对每一个进程执行相同的操作,这样大大提高执行效率。
- 任务控制:在Linux操作系统中,进程组与作业控制紧密联系。用户可以通过作业控制指令来管理进程组中的进程,从而实现任务的启动、暂停、恢复、停止等功能。
- 功能联系:进程组中的进程通常在功能上都有相近的联系,它们协同工作完成特定任务。通过进程组可以快速的管理和这些具有共同目标的进程。
如果只有一个进程,是否有进程组?
组长进程
每一个进程组都有一个组长进程,这个进程的PID与进程组ID一样。
- 作用:进程组组长可以创建一个进程组
- 生命周期:从进程组创建存在到其中一个最后进程离开为止。
会话
概念
由多个进程组组成的集合,称为会话(session ID)。
它提供了一个运行环境和资源共享的上下文,包含了一组相关的进程,这些进程具有共同的会话标识符(SID)。
像我们通过Xshell打开的一个会话页面,就是一个会话。
我们可以通过命令查看已打开的会话:
ls /dev/pts/
setsid()
setsid() 函数在 Unix 和类 Unix 系统中用于创建一个新的会话(session),并使调用该函数的进程成为新会话的领头进程(session leader)。这通常与创建守护进程(daemon processes)相关,因为守护进程需要独立于任何控制终端运行。
但setsid()被执行时:
- 创建新的会话:如果调用 setsid() 的进程不是进程组的领头进程,则该函数会创建一个新的会话,并使调用进程成为该会话的领头进程。新会话的会话ID(SID)是该进程的PID。
- 使调用进程脱离控制终端:如果调用 setsid() 的进程之前有一个控制终端,那么调用之后,该进程将不再具有控制终端。这意味着该进程不再是任何终端进程组的成员,也不再与任何终端相关联。
- 使调用进程成为新进程组的领头进程:调用 setsid() 会导致调用进程成为一个新进程组的领头进程,该进程组的ID也是该进程的PID。
注意:
如果这个进程是进程组的组长,那么将会创建会话失败;为了避免这种情况,可以在子进程里面执行该语句,同时让父进程终止;这样子进程会形成一个孤儿进程,进程ID一定是新分配的,就不会出现错误的情况了。
作业控制
作业是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含一个进程,也可以包含多个进程,进程之间互相协作完成任务, 通常是一个进程管道。
Shell 分前后台来控制的不是进程而是作业 或者进程组。一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell 可以同时运⾏一个前台作业和任意多个后台作业,这称为作业控制。
例如:
守护进程
在一个会话中,会有一个进程是用来创建对应的会话,这个进程与会话对应的,这个进程被称为守护进程
。
守护进程(Daemon Process)或称为服务进程,是在Unix、Linux及类Unix操作系统中运行的一种特殊类型的后台进程。守护进程独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程通常在系统启动时由系统初始化脚本启动,并在系统关闭时关闭。它们没有控制终端,因此它们不能接收来自终端的输入,也不能在终端上显示输出。
主要特点
- 在后台运行:守护进程在后台运行,不占用任何终端。
- 独立于终端:守护进程与启动它的终端无关,即使启动它的终端被关闭,守护进程仍然运行。
- 周期性地执行某些任务:守护进程可以定期执行特定的任务,如检查系统状态、备份数据等。
- 响应系统事件:守护进程也可以监听系统事件,并在事件发生时执行相应的操作。
代码演示如何创建一个会话
Deamon.hpp
#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 Deamon(bool ischdir, bool isclose)
{
// 1. 忽略可能引起程序异常退出的信号
signal(SIGCHLD, SIG_IGN); // 忽略到子进程创建的信号
signal(SIGPIPE, SIG_IGN); // 忽略到管道信号
// 2.创建子进程,关闭父进程
if (fork() > 0)
exit(0);
// 设置让自己成为一个新的会话, 后面的代码其实是子进程在走
setsid();
// 是否改变会话路径
if (ischdir)
chdir(root);
//成为守护进程,将对应的标准流进行关闭,表示到后台运行了
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);
}
}
}
main.cc
#include "Deamon.hpp"
int main()
{
Deamon(true,false);
while(true)
{
sleep(1);
}
return 0;
}
将服务器守护进程化
链接:Socket编程TCP