您的位置:首页 > 游戏 > 游戏 > 进程组,会话,控制终端,作业控制,守护进程的基本概念

进程组,会话,控制终端,作业控制,守护进程的基本概念

2024/12/23 16:43:28 来源:https://blog.csdn.net/m0_74099572/article/details/141905814  浏览:    关键词:进程组,会话,控制终端,作业控制,守护进程的基本概念

进程组

每一个进程除了有一个进程ID(PID)之外还属于一 个进程组。进程组是一个或者多个进程的集合,一个进程组可以包含多个进程。每一 个进程组也有一个唯一的进程组ID(PGID),并且这个PGID类似于进程ID,同样是 一个正整数,可以存放在pid_t数据类型中。 

在linux中 sleep其实也是一个指令,可以用管道执行

sleep 100 | sleep 200

 这样会让当前的进程组睡眠上300秒,出了内建命令,每一个指令其实都是一个进程或者进程组。

关于组长进程:
每一个进程组都有一个组长进程。组长进程的ID等于其进程ID。我们可以通过ps命 令看到组长进程的现象: 

比如结合管道执行指令

ps -o pid,pgid,ppid,comm | cat

 

可以看到,ps和cat是同一个进程组,并且组长是ps进程

进程组组长的作用: 进程组组长可以创建一个进程组或者创建该组中的进程 

进程组的生命周期: 从进程组创建开始到其中最后一个进程离开为止。 

注意:
主要某个进程组中有一个进程存在, 则该进程组就存在, 这与其组长进程是否已经终
止无关。

会话 

  先浅聊一下会话的形成

 当我们作为用户想登录linux的时候,在验证身份合法后,OS首先会给我们打开一个终端文件,然后再创建一个bash进程,以进程组的形式,这样就形成了一个会话。

比如我们打开两个会话

终端文件保存的目录我们是可以查看的。

保存在目录/dev/pts/中,查看它

ls /dev/pts/ -l

可以看到作为普通用户打开的会话,终端文件名分别是0和1,我们可以直接用echo往这两个文件中写入 “nihao”

 发送往终端文件1里面写入,另一个会话果然也看到了信息。

 关于会话ID:

可以先说一下会话首进程, 会话首进程是具有唯一进程 ID 的单个进程, 那么我们可以将会话首进程的进程 ID 当做是会话 ID 。注意:会话 ID 在有些地方也被称为 会话首进程的进程组 ID , 因为会话首 进程总是一个进程组的组长进程, 所以两者是等价的。

控制终端

 所谓控制终端,其实就是每次打开会话时,系统帮我们创建的一个终端文件。

一个会话可以被分为 一个前台进程组, 零个或者多个后台进程组,还有一个控制终端。

在执行的命令后加入 & ,表示以后台运行的方式启动。

作业控制 

作业 是针对用户来讲,用户完成某项任务而启动的进程,一个作业既可以只包含
一个进程,也可以包含多个进程,进程之间互相协作完成任务, 通常是一个进程
管道。

其实对于系统层面上来讲就是进程组。 

Shell 分前后台来控制的不是进程而是作业 或者进程组。一个前台作业可以由多
个进程组成,一个后台作业也可以由多个进程组成, Shell 可以同时运⾏一个前台
作业和任意多个后台作业,这称为 作业控制

关于作业号:

放在后台执⾏的程序或命令称为后台命令,可以在命令的后面加上 & 符号从而让
Shell 识别这是一个后台命令,后台命令不用等待该命令执⾏完成,就可立即接收
新的命令, 另外后台进程执行完后会返回一个作业号以及一个进程号( PID

比如

前面的数字1就是作业号

可以用

ps ajx | head -1 && ps ajx | grep sleep
    • ps 命令用于显示当前系统中的进程状态。
    • ajx 是 ps 命令的选项组合:
      • a 显示所有用户的进程(包括其他用户的进程)。
      • j 显示作业控制信息(job control information),这通常包括进程组ID(PGID)和会话ID(SID)。
      • x 显示没有控制终端的进程。
    • | 是管道符号,用于将一个命令的输出作为另一个命令的输入。
    • head -1 从输入中选取第一行。head 命令默认显示文件的前10行,但 -1 选项使其只显示第一行。

    因此,ps ajx | head -1 的作用是显示 ps ajx 命令输出的第一行,这通常是标题行,包含了列名,如PID(进程ID)、PGID(进程组ID)、SID(会话ID)、TTY(控制终端)、TIME(CPU时间)、CMD(命令名/命令行)等。

  1. ps ajx | grep sleep

    • 这部分命令的作用与第一部分类似,但 grep sleep 用于过滤 ps ajx 的输出,只显示包含“sleep”字符串的行。
    • 这意味着,它会列出所有包含“sleep”在其命令行中的进程的信息。

将这两部分命令放在一起,如 ps ajx | head -1 && ps ajx | grep sleep,使用 && 连接符,这表示第一个命令(ps ajx | head -1)执行成功后(即成功输出了第一行,通常总是这样),才会执行第二个命令(ps ajx | grep sleep)。

 来查看sleep进程组的PID等信息。

根据PGID的方式杀掉进程组,在 PGID前加 - 。比如

kill -9 -208773

jobs
jobs -l
jobs fg +作业号 // 讲作业切换为前台进程
jobs bg +作业号 // 启动后台任务,将暂停的任务重新启动
jobs -p         // 只显示作业的PID

的方式可以看到当前会话启动的后台进程,-l可以更详细。

 另外作为前台进程,linux不允许前台进程暂停,毕竟暂停了整个会话就卡死了。如果暂停了,会自动由前台进程切换到后台进程。

可以结合ctrl + z,和jobs命令,来达到作业的挂起和与切回。

这个就是作业控制了。

关于默认作业:对于一个用户来说,只能有一个默认作业 + ,同时也只能有一
个即将成为默认作业的作业 - ,当默认作业退出后,该作业会成为默认作业。
+ : 表示该作业号是默认作业
- :表示该作业即将成为默认作业
无符号: 表示其他作业

关于作业的状态:

守护进程

  前台进程和后台进程都是属于同一个会话的,如果我们把会话关闭了,那么这些进程都会受到影响。

  而守护进程的原理其实就是,让这个进程组单独成为一个会话,这样就不会受原来的会话的影响了。

需要用到的函数

#include <unistd.h>
/*
*功能:创建会话
*返回值:创建成功返回 SID, 失败返回-1
*/
pid_t setsid(void);

 可以调用 setseid 函数来创建一个会话, 前提是调用进程不能是一个进程组的组长。

该接口调用之后会发生:
调用进程会变成新会话的 会话首进程。 此时, 新会话中只有唯一的一个进
调用进程会变成进程组组长。 新进程组 ID 就是当前调用进程 ID
该进程没有控制终端。 如果在调用 setsid 之前该进程存在控制终端, 则调
用之后会切断联系
需要注意的是: 这个接口如果调用进程原来是进程组组长, 则会报错, 为了避免这
种情况, 我们通常的使用方法是先调用 fork 创建子进程, 父进程终止, 子进程继续
执行, 因为子进程会继承父进程的进程组 ID, 而进程 ID 则是新分配的, 就不会出现错误
另外还有两个细节点:
 
1.是否要更改该进程的执行路径。
首先我们需要知道我们的进程执行也是要有目录的,可以在/proc/进程PID路径下
比如

下看有一个cwd,这里指向的就是这个进程运行的目录。

作为一个网络服务,它写入文件可以是从它启动的地方,也就是相对根目录的方式写入,也可以从 / 开始,也就是绝对根目录的方式启动。所以可以对它进行修改。

 

#include <unistd.h>  int chdir(const char *path);

 

  • path 参数是一个指向以 null 结尾的字符串的指针,该字符串指定了新的工作目录的路径。
  • 函数返回值为整数。如果成功,返回 0;如果失败,返回 -1,并设置全局变量 errno 以指示错误。

 2.是否要关闭文件描述符

作为后台运行的守护进程,我们可以关闭 0,1,2号文件描述符,也就是关闭标准输入和标准输出和标准错误。然后用日志的形式把进程的运行状况往文件里写入。

但是直接关闭0,1,2是不推荐的,万一程序出现了往0,1,2文件写入的情况,就直接报错了。

所以这个做法不是很推荐。

在linux下,有一个文件是专门处理不要的信息的,在目录 /dev/null下

可以往这个文件里面写入,但是无法从这个文件里面读取到任何数据。

所以我们可以把 0,1,2重定向到这个文件中,这样有写入也不会报错了。

代码:

#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);// 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);}}
}

将进程守护化后,发现PPID是1,说明是孤儿进程,然后PID,PGID,SID全部都是相等的,也验证了之前的说法。

 关于杀掉守护进程

killall 守护进程名会向所有名为“守护进程名”的进程发送SIGTERM信号。

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com