进程组
每一个进程除了有一个进程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:
控制终端
所谓控制终端,其实就是每次打开会话时,系统帮我们创建的一个终端文件。
一个会话可以被分为 一个前台进程组, 零个或者多个后台进程组,还有一个控制终端。
在执行的命令后加入 & ,表示以后台运行的方式启动。
作业控制
其实对于系统层面上来讲就是进程组。
关于作业号:
比如
前面的数字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(命令名/命令行)等。 -
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 函数来创建一个会话, 前提是调用进程不能是一个进程组的组长。
下看有一个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信号。