文章目录
- 1.进程组
- 1.1什么是进程组
- 1.2组长进程
- 2.会话
- 2.1什么是会话
- 2.2如何创建会话
- 3.作业
- 3.1什么是作业、作业控制?
- 3.2作业号
- 3.3常见作业状态
- 3.4作业的切换
- 4.守护进程
- 4.1什么是守护进程?
- 4.2如何创建守护进程
- 4.3模拟实现daemon
1.进程组
1.1什么是进程组
之前我们提到了进程的概念, 其实每一个进程除了有一个进程 ID(PID)之外,还属于一个进程组。 进程组是一个或者多个进程的集合, 一个进程组可以包含多个进程。
每一个进程组也有一个唯一的进程组 ID(PGID), 并且这个 PGID 类似于进程 ID, 同样是一个正整数, 可以存放在 pid_t 数据类型中。
$ ps -eo pid,pgid,ppid,comm | grep test
#结果如下
PID PGID PPID COMMAND
2830 2830 2259 test
# -e 选项表示 every 的意思, 表示输出每一个进程信息
# -o 选项以逗号操作符(,) 作为定界符, 可以指定要输出的列
1.2组长进程
每一个进程组都有一个组长进程。 组长进程的 ID 等于其进程组的 ID。 我们可以通过ps
命令看到组长进程的现象。
[node@localhost code]$ ps -o pid,pgid,ppid,comm | cat
# 输出结果
PID PGID PPID COMMAND
2806 2806 2805 bash
2880 2880 2806 ps
2881 2880 2806 cat
从结果上看 ps 进程的 PID 和 PGID 相同, 那也就是说明 ps 进程是该进程组的组长进程, 该进程组包括 ps 和 cat 两个进程。
注意:
- 进程组组长的作用: 进程组组长可以创建一个进程组或者创建该组中的进程。
- 进程组的生命周期: 从进程组创建开始到其中最后一个进程离开为止,与其组长进程是否已经终止无关。
2.会话
2.1什么是会话
会话可以看成是一个或多个进程组的集合, 一个会话可以包含多个进程组。
每一个会话也有一个会话 ID(SID) ,会话ID一般是一个会话中的第一个进程ID,一般是bash 。
当我们登录linux服务器时,系统会给我们分配一个终端文件,并且给这个终端文件关联一个bash进程,而更重要的是,这个bash进程也一定是一个进程组,此时分配的这个终端文件和关联的进程组就构建了一个会话。
那么什么是终端文件呢,你可以理解为就是xshell的一个窗口,这个终端文件存储在linux文件目录下的/dev/pts
目录下,我们可以做一个实验来证明:
我们将“hello”写入到/dev/pts/1
这个文件中就会发现,在另一个新打开的窗口中显示了出来。
并且每新打开一个窗口,系统中会新增一个bash进程,并且他们属于不同的会话(会话ID不同)。
所以我们已经证明了,一个新的窗口也是一个新的会话,每打开一个会话都会有新的终端文件和bash进程。
同一个会话中,可以允许同时存在多个进程组,但是任何时刻仅允许存在一个前台进程(组),可以允许一个或多个后台进程(组)。
当我们在bash中要求前台执行我们自己的进程时(./myprocess),bash就会自己转化为后台进程,前台进程就变为我们的进程,当我们的进程执行完后或者被强制退出后(ctrl + c),bash就会自动的切换为前台进程。
后台执行在后面加’&',比如
sleep 100 &
。
最明显的前后台区别是:谁应该从标准输入中获取数据。
当会话关闭时, 会话中的进程组都会受到影响,取决于不同的系统。
windows在登录时也是相当于一个会话,当你觉得系统卡顿时,可以尝试“注销”,因为注销就相当于关闭当前会话,相应的会话中的所有进程组会关闭。
2.2如何创建会话
可以调用setseid
函数来创建一个会话,前提是调用进程不能是一个进程组的组长。
#include <unistd.h>
/*
*功能: 创建会话
*返回值: 创建成功返回 SID, 失败返回-1
*/
pid_t setsid(void);
调用该函数后:
- 调用进程会变成新会话的会话首进程。 此时, 新会话中只有唯一的一个进程
- 调用进程会变成进程组组长。 新进程组 ID 就是当前调用进程 ID 。
- 该进程没有控制终端。 如果在调用
setsid
之前该进程存在控制终端, 则调用之后会切断联系。
注意:该函数调用的前提是调用进程不能是一个进程组的组长,所以在调用该函数前,我们需要先创建子进程,让子进程执行该函数。
即:
if(fork()>0) exit(0);setsid();
3.作业
3.1什么是作业、作业控制?
Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。
一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制。
3.2作业号
放在后台执行的程序或命令称为后台命令, 可以在命令的后面加上’‘&’'符号,从而让Shell 识别这是一个后台命令, 后台命令不用等待该命令执行完成, 就可立即接收新的命令, 另外后台进程执行完后会返回一个作业号以及一个进程号(PID) 。
例如:
我们后台运行了两个作业,通过jobs
命令可以查看运行的作业信息。
- 参数
-l
:则显示作业的详细信息。 - 参数
-p
:则只显示作业的 PID。
其中:
- [1]:1表示作业号;
- +:表示该作业号是默认作业;
- -:表示该作业即将成为默认作业 ;
- 无符号:表示其他作业;
- Running:表示作业状态。
关于默认作业: 对于一个用户来说, 只能有一个默认作业(+),同时也只能有一个即将成为默认作业的作业(-) ,当默认作业退出后, 该作业会成为默认作业。
3.3常见作业状态
作业状态 | 含义 |
---|---|
正在运行【Running】 | 后台作业,表示正在执行。 |
完成【Done】 | 作业已完成,返回状态码为0。 |
完成并退出【Done(code)】 | 作业已完成并退出,返回状态码为code(非0)。 |
已停止【Stopped】 | 前台作业,当前被CTRL + Z挂起 |
已终止【Terminated】 | 作业被终止 |
3.4作业的切换
我们可以通过fg 作业号
命令将后台运行的作业切换到前台运行。
同样的我们可以将暂停的作业放到后台运行,首先我们需要CTRL+Z
将作业设置为暂停状态,然后利用bg 作业号
命令将前台运行的作业切换到后台运行。
4.守护进程
4.1什么是守护进程?
守护进程也称精灵进程(Daemon),是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
我们知道用户登录的这种行为就会创建出一个会话,而如果我们的服务器进程运行在该会话上,当用户关闭这个会话时,运行在该会话上的进程就会收到影响,这肯定是我们不希望的。
所以我们一般需要将该服务器进程独立出来,即新创建一个会话出来,这样一来,我们的服务器进程就不会受到用户登陆注销影响了,此时这种进程就被称为守护进程。
4.2如何创建守护进程
int daemon(int nochdir, int noclose);
参数说明:
- 如果参数nochdir为0,则将守护进程的工作目录该为根目录,否则不做处理。
- 如果参数noclose为0,则将守护进程的标准输入、标准输出以及标准错误重定向到
/dev/null
,否则不做处理。
4.3模拟实现daemon
- 设置文件掩码为0。
- fork后终止父进程,子进程创建新会话,所以守护进程是一种特殊的孤儿进程。
- 忽略SIGCHLD信号。
- 再次fork,终止父进程,保持子进程不是会话首进程,从而保证后续不会再和其他终端相关联。
- 更改工作目录为根目录。
- 将标准输入、标准输出、标准错误重定向到/dev/null。
/dev/null,该文件是一个字符类文件,特点:从该文件读到的内容为空,向该文件写入的内容都会被系统丢弃。
void my_daemon(int nochdir, int noclose)
{//1、忽略不要的信号signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);//2、fork后终止父进程,子进程创建新会话if (fork() > 0)exit(0);//3、setsidsetsid();//4、确认是否更改工作目录if (nochdir == 0){chdir("/");}//5、将标准输入、标准输出、标准错误重定向到/dev/null(可选的选项)if (noclose == 0){int fd = open("/dev/null", O_RDWR);if(fd>0){dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);}}else{close(0);close(1);close(2);}
}
说明:
- 调用setsid创建新会话时,要求调用进程不能是进程组组长,但是当我们在命令行上启动多个进程协同完成某种任务时,其中第一个被创建出来的进程就是组长进程,因此我们需要fork创建子进程,让子进程调用setsid创建新会话并继续执行后续代码,而父进程我们直接让其退出即可。
- 我们一般会将守护进程的工作目录设置为根目录,便于让守护进程以绝对路径的形式访问某种资源。(该操作不是必须的)
- 由于守护进程已经与终端去关联了,因此一般我们会将守护进程的标准输入、标准输出以及标准错误都重定向到/dev/null。
去成为你本应该成为的人,任何时候都不会太晚。 —乔治·艾略特
出来的进程就是组长进程,因此我们需要fork创建子进程,让子进程调用setsid创建新会话并继续执行后续代码,而父进程我们直接让其退出即可。
- 我们一般会将守护进程的工作目录设置为根目录,便于让守护进程以绝对路径的形式访问某种资源。(该操作不是必须的)
- 由于守护进程已经与终端去关联了,因此一般我们会将守护进程的标准输入、标准输出以及标准错误都重定向到/dev/null。
去成为你本应该成为的人,任何时候都不会太晚。 —乔治·艾略特