详解 Linux 过程组
-
每个过程都有一个过程组号 (PGID)
- 过程组:一个或多个过程的汇合(汇合中的过程并不孤立)
- 过程组中的过程通常存在父子关系,兄弟关系,或 性能相近
-
过程组可不便过程治理(如:同时杀死多个过程,发送一个信号给多个过程)
- 每个过程必然属于一个过程组,也只能属于一个过程组
- 过程除了有 PID 外,还有 PGID(惟一,可变,即某一个过程能够切换过程组)
- 每个过程组有一个过程组长,过程组长的 PID 和 PGID 雷同
> ps -o pgid 19843
PGID 977
> kill -- -977
pid_t getpgrp(void);
// 获取以后过程的组标识pid_t getpgid(pid_t pid);
// 获取指定过程的组标识-
int setpgid(pid_t pid, pid_t pgid);
// 设置过程的组标识pid == pgid
, 将 pid 指定的过程设为组长pid == 0
, 设置以后过程的组标识为 pgidpid == 0
, 将 pid 设置为组标识(行将 pid 所代表的过程设置为过程组长)
过程组示例程序
默认状况下,子过程与父过程属于同一过程组,是
fork
工作机制的产物(子过程复制以后过程自身)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pid = 0;
int i = 0;
printf("parent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
while (i < 5) {if ((pid = fork()) > 0) {printf("new : %d\n", pid);
}
else if (pid == 0) {sleep(1);
printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
sleep(60);
printf("last == pgid = %d\n", getpgrp());
break;
}
else {printf("fork error...\n");
}
++i;
}
if (pid) {sleep(60);
}
return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out &
[1] 3022
parent = 3022, ppid = 2125, pgid = 3022
new : 3024
new : 3025
new : 3026
new : 3027
new : 3028
tiansong@tiansong:~/Desktop/linux$ child = 3025, ppid = 3022, pgid = 3022
child = 3026, ppid = 3022, pgid = 3022
child = 3027, ppid = 3022, pgid = 3022
child = 3024, ppid = 3022, pgid = 3022
child = 3028, ppid = 3022, pgid = 3022
tiansong@tiansong:~/Desktop/linux$ ps
PID TTY TIME CMD
2125 pts/3 00:00:00 bash
3022 pts/3 00:00:00 a.out
3024 pts/3 00:00:00 a.out
3025 pts/3 00:00:00 a.out
3026 pts/3 00:00:00 a.out
3027 pts/3 00:00:00 a.out
3028 pts/3 00:00:00 a.out
3061 pts/3 00:00:00 ps
tiansong@tiansong:~/Desktop/linux$ kill 3022 // kill 过程组长
[1]+ Terminated ./a.out
tiansong@tiansong:~/Desktop/linux$ ps
PID TTY TIME CMD
2125 pts/3 00:00:00 bash
3024 pts/3 00:00:00 a.out
3025 pts/3 00:00:00 a.out
3026 pts/3 00:00:00 a.out
3027 pts/3 00:00:00 a.out
3028 pts/3 00:00:00 a.out
3149 pts/3 00:00:00 ps
tiansong@tiansong:~/Desktop/linux$ kill -- -3022 // kill 过程组
tiansong@tiansong:~/Desktop/linux$ ps
PID TTY TIME CMD
2125 pts/3 00:00:00 bash
3163 pts/3 00:00:00 ps
过程组深度分析
深刻了解过程组
- 过程组长终止,过程组仍然存在(过程组长仅用于创立新过程组)
- 父过程创立子过程后立刻通过
setpgid()
扭转其组标识(PGID)【当须要将子过程设置到其它过程组时】 - 同时,子过程也须要通过
setpgid()
扭转本身组标识(PGID)【当须要将子过程设置到其它过程组时】 -
当子过程调用
exec()
后- 父过程无奈通过
setpgid()
扭转子过程组标识(PGID) - 只能本身通过
setpgid()
扭转其组标识(PGID)
- 父过程无奈通过
过程组标识设置技巧
试验 1:设置子过程为过程组长 👇 为例
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pid = 0;
printf("parent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
if((pid = fork()) > 0) {int r = setpgid(pid, pid); // ① 子过程设置新的过程组
printf("new: %d, r = %d\n", pid, r);
}
else if (pid == 0) {setpgid(pid, pid); // ② -> setpgid(0,0) -> setpgid(子过程 pid, 用子过程 id 作为过程组 id)
sleep(1);
printf("child = %d, ppid = %d, pgid =%d\n", getpid(), getppid(), getpgrp());
}
else {printf("fork error ...\n");
}
return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out
parent = 3434, ppid = 2125, pgid = 3434
new: 3435, r = 0
child = 3435, ppid = 1, pgid =3435
问:为什么在父子过程都须要调用
setpgid
呢?
答:为了双保险。
fork 实现之后,无奈确认是父过程先执行还是子过程限制性(古代操作系统个别子过程先执行)。
为了确保不让子过程与父过程在“短暂的工夫内”仍呈现在雷同的过程组中,须要在子过程创立进去之后立刻对“子过程”进行过程组设置。
试验 2:当子过程调用
exec()
后, 父过程无奈通过setpgid()
扭转子过程组标识(PGID)
main.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pid = 0;
printf("parent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
if((pid = fork()) > 0) {int r = setpgid(pid, pid);
printf("new: %d, r = %d\n", pid, r);
}
else if (pid == 0) {
char *out = "./helloword.out";
char *const ps_argv[] = {out, NULL};
char *const ps_envp[] = {"PATH=/bin:/usr/bin", NULL};
execve(out, ps_argv, ps_envp);
}
else {printf("fork error ...\n");
}
sleep(60);
return 0;
}
helloword.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
printf("hello world\n");
sleep(30);
return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out &
[1] 5660
parent = 5660, ppid = 2125, pgid = 5660
new: 5662, r = 0 // r 等于 0, 示意父过程 setpgid 调用胜利
child = 5662, ppid = 5660, pgid = 5662
hello world
批改 main.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pid = 0;
printf("parent = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
if((pid = fork()) > 0) {
int r = 0;
sleep(1); // 确保子过程先执行并调用了 execve
r = setpgid(pid, pid);
printf("new: %d, r = %d\n", pid, r);
}
else if (pid == 0) {
char *out = "./helloword.out";
char *const ps_argv[] = {out, NULL};
char *const ps_envp[] = {"PATH=/bin:/usr/bin", NULL};
execve(out, ps_argv, ps_envp);
}
else {printf("fork error ...\n");
}
sleep(60);
return 0;
}
批改 helloword.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{sleep(5); // 期待父过程 setpgid 后再进行后续打印
printf("child = %d, ppid = %d, pgid = %d\n", getpid(), getppid(), getpgrp());
printf("hello world\n");
sleep(30);
return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out &
parent = 5917, ppid = 2125, pgid = 5917
tiansong@tiansong:~/Desktop/linux$ new: 5919, r = -1 // r 等于 -1, 示意 setpgid 执行失败
child = 5919, ppid = 5917, pgid = 5917 // pgid 也未发生变化
hello world
会话与终端的关系
Linux 会话(session)
- 用户通过终端登录零碎后产生一个会话
- 会话是一个或多个 过程组 的汇合
-
每个会话有一个会话标识(
SID
)- 终端登陆后的第一个过程成为会话首过程,通常是一个
shell/pash
- 对于会话首过程(
session leader
), 其 PID 与 SID 相等
- 终端登陆后的第一个过程成为会话首过程,通常是一个
-
通厂状况下,会话与一个终端(管制终端)相关联用于执行输入输出操作
- 会话首过程建设与管制终端的连贯(会话首过程又叫做管制过程)
-
会话中的过程组可分为:
- 前台过程组:可接管管制终端中的输出,也可输入数据到管制终端
- 后盾过程组:所有过程后盾运行,无奈接管终端中的输出,但可输入数据到终端
其中 getty 用于关联终端
会话与前后台过程组
会话中的前台过程组
问题
在终端中输出命令后,产生了什么?
- 当命令行(shell)运行命令后 创立一个新的过程组
- 如果运行的命令中有多个子命令则创立多个过程(新创建的过程处于新建的过程组中)
-
命令不带 &
- shell 将新见的过程组设置为前台过程组,并将本人临时设置为后盾过程组
-
命令带 shell
- shell 将新见的过程组设置为后盾过程组,本人仍旧是前台过程组
什么是终端过程组标识(TPGID)
- 标识过程是否处于一个和终端相干的过程组中
- 前台过程:TPGID == PGID (因为前台过程组可能扭转,TPGID 用于标识以后的前台过程组)
- 后盾过程:TPGID != PGID
- 若过程和任何终端无关:TPGID == -1
通过比拟 TPGID 与 PGID 可判断:一个过程属于前台过程组,还是后盾过程组
如果过程组和终端相关联,那么当终端敞开断开连接,过程组的过程将全副完结
tiansong@tiansong:~/Desktop/linux$ ps -ajx | grep TPGID
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2125 6933 6932 2125 pts/3 6932 S+ 1000 0:00 grep --color=auto TPGID
会话编程深度分析
Linux 会话接口
#include <unistd.h>
pid_t getsid(pd_t pid);
// 获取指定过程的 SID, (pid == 0) → 以后过程-
pid_t setpid(void);
// 用于创立新会话,其中调用过程不能是过程组长,执行了如下动作:- 创立新会话,SID == PID,调用过程会成为会话首过程(在创立的会话中是惟一过程)
- 创立新过程组,PGID == PID,调用过程成为过程组长(在创立的过程组中是惟一过程)
- 调用过程没有管制终端,若调用前关联了管制终端,调用后与管制终端断联
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pid = 0;
if((pid = fork()) > 0) {printf("parent = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
printf("new: %d\n", pid);
}
else if (pid == 0) {setsid(); // 子过程脱离以后会话,创立新的过程会话,新的过程组
sleep(180);
printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
}
else {printf("fork error ...\n");
}
sleep(240);
return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out // 前台运行
parent = 7291, ppid = 2125, pgid = 7291, sid = 2125
new: 7292
^C // ctrl + c 终止前台过程组中的前台过程
tiansong@tiansong:~/Desktop/linux$ ps // 查看以后终端窗口中运行的根本信息,发现没有 a.out
PID TTY TIME CMD
2125 pts/3 00:00:00 bash
7361 pts/3 00:00:00 ps
tiansong@tiansong:~/Desktop/linux$ ps -ajx | grep a.out // 显示没有管制终端的过程(-x),搜寻 a.out
1 7292 7292 7292 ? -1 Ss 1000 0:00 ./a.out // 7292 可知为创立的子过程。? 表明与任何一个终端都不关联,同时 TPGID 为 -1
2125 7383 7382 2125 pts/3 7382 S+ 1000 0:00 grep --color=auto a.out
tiansong@tiansong:~/Desktop/linux$ pstree -p -s -A 7292
systemd(1)---a.out(7292) // 父过程终止运行,被初始化过程接管
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int pid = 0;
if((pid = fork()) > 0) {printf("parent = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
printf("new: %d\n", pid);
}
else if (pid == 0) {setsid();
sleep(3); // 批改此处,不便察看打印
printf("child = %d, ppid = %d, pgid = %d, sid = %d\n", getpid(), getppid(), getpgrp(), getsid(getpid()));
}
else {printf("fork error ...\n");
}
sleep(240);
return 0;
}
tiansong@tiansong:~/Desktop/linux$ ./a.out
parent = 7947, ppid = 2125, pgid = 7947, sid = 2125
new: 7948
child = 7948, ppid = 7947, pgid = 7948, sid = 7948 // pgid(过程组长),sid(会话首过程)为以后过程,合乎预期
^C // ctrl + c 终止前台过程组中的前台过程
tiansong@tiansong:~/Desktop/linux$ ps -ajx | grep 7948
1 7948 7948 7948 ? -1 Ss 1000 0:00 ./a.out // ? 表明与任何一个终端都不关联,同时 TPGID 为 -1
2125 8028 8027 2125 pts/3 8027 S+ 1000 0:00 grep --color=auto 7948
问题:在上述测试中,子过程创立新会话,与以后终端断开,与任何终端都不关联,那么为什么会在以后终端有输入呢?
只管 setsid 导致子过程再无相关联的终端,但因为 fork 的关系,子过程的 stdout 仍标记的以后所操作的终端上,因而子过程的打印会在以后终端输入
总结:规范输入输出与终端是“无关”的。只不过在默认状况下,规范输入输出和终端挂接到了一起(能够通过重定向使其断连)。(新会话能够没有管制终端,但还是能够有规范输入输出)