文中内容是参考了多位网友的资料整理而成的,个人一直对进程组,线程组,会话,终端的概念比较模糊,在学习的过程中整理了这篇文章。
将阐述Linux内核中的如下几个概念
1.概念:
a)进程组
Shell 上的一条命令行形成一个进程组 ,譬如 :./test | ./test1一条命令会创建两个进程test,test1这两个进程就属于一个进程组。在test,test1中创建的子进程也都属于该进程组。
每个进程属于一个进程组 ,每个进程组有一个领头进程 ,通常领头进程是进程组的第一个进程,进程组号是第一个进程的pid.
进程组的生命周期到组中最后一个进程终止, 或加入其他进程组为止。
getpgrp: 获得进程组 id, 即领头进程的 pid 。
setpgid: 加入进程组和建立新的进程组 。
前台进程组和后台进程组,前台进程组可以和控制终端交互,处理控制终端发出的各种信号,比如:ctrl+c等,ctrl+c会将前台进程组所有进程都给杀死,后台进程组则不会处理相关的控制终端的信号。 即使关闭终端和会话,后台进程组也不会退出。
===============================================================================
#include
int setpgid (pid_t pid, pid_t pgid);
pid_t getpgid (pid_t pid);
int setpgrp (void);
pid_t getpgrp (void);
-------------------------------------------------------------------------------
进程只能将自身和其子进程设置为进程组 id.
某个子进程调用 exec 函数之后, 就不能再将该子进程的 id 作为进程组 id.
后台的文件描述符也是继承于父进程,例如shell,所以它也可以在当前终端下显示输出数据。
但是daemon进程自己变成了进程组长,其文件描述符号和控制终端没有关联,是控制台无关的。
===============================================================================
b)会话
一次登录形成一个会话
一个会话可包含多个进程组, 但只能有一个前台进程组.
setsid 可建立一个新的会话
===============================================================================
#include
pid_t setsid(void);
-------------------------------------------------------------------------------
如果调用进程不是进程组的领头进程, 该函数才能建立新的会话.
调用 setsid 之后, 进程成为新会话的领头进程.进程成为新进程组的领头进程. 进程失去控制终端
===============================================================================
c)控制终端
会话的领头进程打开一个终端之后, 该终端就成为该会话的控制终端 (SVR4/Linux)
控制终端建立连接的会话领头进程称为控制进程 (session leader)
一个会话只能有一个控制终端
产生在控制终端上的输入和信号将发送给会话的前台进程组中的所有进程
终端上的连接断开时 (比如网络断开或 Modem 断开), 挂起信号将发送到控制进程(session leader)
2实验
编写程序:
- /*test.c*/
- #include "../include/apue.h"
- void main() {
- pid_t pid;
- char *args[] = {"/bin/echo", "Hello", "World!", NULL };
- if((pid = fork()) < 0)
- err_sys("fail to fork!");
- else if(pid == 0) {
- execve("./test1", args, NULL);
- }
- /*
- if((pid = fork()) < 0)
- err_sys("fail to fork!");
- if(pid == 0) {
- execve("./test2", NULL, NULL);
- }
- */
- while(1) { };
- }
- /*test1.c*/
- #include <stdio.h>
- int main(int args, char* argv[]) {
- char s[5] = "ABCD";
- while(1) {}
- return;
- }
- /* in test2.c */
- #include <stdio.h>
- #include <stdlib.h>
- int main()
- {
- pid_t pid;
- pid = fork();
- if (pid <0) {
- printf("fork err\n");
- exit(-1);
- } else if (pid == 0) {
- /* in child process */
- execve("./test3", NULL, NULL);
- }
- while(1) {}
- }
- /*test3.c*/
- #include <stdio.h>
- void main() {
- while(1) {
- //printf("test3\n\n");
- }
- }
实验一:
编写shell文件,shell文件的作用是同时启动两个进程:
#!/bin/bash
./test | ./test2
$ps -ejH
PID PGID SID TTY TIME CMD
3888 3888 3888 pts/4 00:00:00 bash
4961 4961 3888 pts/4 00:00:00 ps
4394 4394 4394 pts/0 00:00:00 bash
4957 4957 4394 pts/0 00:00:00 test
4959 4957 4394 pts/0 00:00:00 test1
4958 4957 4394 pts/0 00:00:00 test2
4960 4957 4394 pts/0 00:00:00 test3
$pstree -H 4394
|-gnome-terminal
| |-bash| | `-pstree -all
| |-bash
| |-bash
| | |-test
| | | `-test1 Hello World!
| | `-test2
| | `-(test3)
| |-gnome-pty-helpe
| `-3*[{gnome-terminal}]
结果分析:
PID为4394的进程是我开启的用于运行程序的终端,也就是一个会话(session),其ID为4394。在该session下运行的程序的SID当然都是
4394.我们同时运行了两个进程test和test2,其PID分别为4057和4958,其组ID都是4057。看到,test和test2同属一个进程组,其组ID为该
组创建的第一个进程的ID,即test的ID。由test和test2创建的子进程的组ID同样为4057。
实验二:
$./test & //在后台运行test
$./test2 & //在后台运行test2
PID PGID SID TTY TIME CMD
2697 1501 1501 ? 00:08:20 gnome-terminal
2705 1501 1501 ? 00:00:00 gnome-pty-helpe
3050 3050 3050 pts/1 00:00:00 bash
3888 3888 3888 pts/4 00:00:00 bash
5294 5294 3888 pts/4 00:00:00 ps
4394 4394 4394 pts/0 00:00:00 bash
5288 5288 4394 pts/0 00:00:05 test
5289 5288 4394 pts/0 00:00:04 test1
5290 5290 4394 pts/0 00:00:00 test2
5291 5290 4394 pts/0 00:00:01 test3
$pstree -all
|-gnome-terminal
| |-bash
| | `-pstree -all
| |-bash
| |-bash
| | |-test
| | | `-test1 Hello World!
| | `-test2
| | `-(test3)
| |-gnome-pty-helpe
| `-3*[{gnome-terminal}]
实验结果分析:
我们先后运行了两个进程,可以看到其组ID是不一致的。
实验三:
修改test.c源程序,取消对注释部分的注释,重新编译
$gcc -g -o test test.c
$./test
PID PGID SID TTY TIME CMD
2697 1501 1501 ? 00:08:20 gnome-terminal
2705 1501 1501 ? 00:00:00 gnome-pty-helpe
3050 3050 3050 pts/1 00:00:00 bash
3888 3888 3888 pts/4 00:00:00 bash
5397 5397 3888 pts/4 00:00:00 ps
4394 4394 4394 pts/0 00:00:00 bash
5393 5393 4394 pts/0 00:00:01 test
5394 5393 4394 pts/0 00:00:01 test1
5395 5393 4394 pts/0 00:00:00 test2
5396 5393 4394 pts/0 00:00:01 test3
|-gnome-keyring-d --daemonize --login
| `-4*[{gnome-keyring-}]
|-gnome-screensav --no-daemon
| `-2*[{gnome-screensa}]
|-gnome-terminal
| |-bash
| | `-pstree -all
| |-bash
| |-bash
| | `-test
| | |-test1 Hello World!
| | `-test2 Hello World!
| | `-(test3)
| |-gnome-pty-helpe
| `-3*[{gnome-terminal}]
结果分析:
由进程树可以看到test3是test2的子进程,test2是test的子进程。其组ID都是test的PID。