进程组与会话 Linux Process Groups and Sessions
在类Unix系统中,用户通常会跟各种相关的进程打交道。虽然在登录的时候只有一个终端进程(用户对应的登录shell ,通过这个shell启动各种程序和服务),但通常不久以后就会产生许多相关的进程,例如进行如下动作:
- 在后台运行无交互的程序(例如bash命令中末位的"&")
- 通过shell的 job control在各种交互进程之间切换
- 通过管道启动一组程序
- 在图形环境下(例如X window system)启用多个终端窗口
为了管理这些进程,内核便对这些进程进行了分组,称其为进程组,几个进程组又构成一个会话。例如下图所示,ls
和less
在一个进程组里,而grep
和wc
在一个进程组里。这两个进程组又同属于一个会话。
下面具体讲一讲进程组和会话。
1.1 进程组
每一个进程都属于一个“进程组”,当一个进程被创建的时候,它默认是其父进程所在组的成员。传统上,一个进程的组ID(pgid)等于这个组的第一个成员(也称为进程组领导)。
可以使用ps j
这个命令获取进程的PPID (父进程ID), PID (本进程 ID), PGID (进程组 ID) and SID (会话 ID)。
frank@under:~$ ps j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
24120 24125 24125 24125 pts/5 24125 Ss+ 1000 0:00 bash
24120 30633 30633 30633 pts/6 31468 Ss 1000 0:00 bash
30633 31468 31468 30633 pts/6 31468 R+ 1000 0:00 ps j
当使用不具有工作管理(job control)的shell时(例如ash
),每一个shell创建的子进程都会和shell在同一个进程组和会话里。当使用具有工作管理的shell时(例如bash
),如果使用管道(参考:Pipes: A Brief Introduction), 那么这些管道连接起来的进程单独组成一个进程组,和shell在一个会话中。例如:
% cat paper | ideal | pic | tbl | eqn | ditroff > out
这几个程序运行后的进程都在一个进程组里面。
前台进程组
每一个会话最多有一个进程组是“前台进程组”,控制终端(下面的第二部分会讲)会将输入和信号 传给该进程组的成员(例如你在终端按下Ctrl+C就会向前台进程组发送SIGINT信号)。进程可以通过系统调用 tcgetpgrp(fd)
获取所在会话的前台进程组ID,其中fd
对应会话的控制终端的文件描述符;也可以通过tcsetpgrp(fd,pgrp)
设置所在会话的前台进程组,其中fd
对应会话的控制终端的文件描述符, pgrp
是这个会话中的一个进程组。
那么如何得到fd
呢? ctermid()
调用会返回控制终端的名字,在符合 POSIX标准的系统上,它会返回 /dev/tty
这个文件,然后我们就可以用系统调用open()
打开这个文件从而得到文件描述符了。
后台进程组
在一个会话中,除前台进程组外的进程组都称为“后台进程组”,这些后台进程组的进程不参与终端的输入输出,如果它们尝试从终端读取数据,会收到SIGTTIN
信号(其默认操作是Stop),同时终端会通知用户:
SIGTTIN 21,21,26 Stop Terminal input for background process
但是如果后台进程忽略或者blockSIGTTIN
信号了,或者它所在的进程组是一个“孤儿进程组”(下面有讲),那么读取终端就会得到一个EIO(error in operation)错误而非收到SIGTTIN
信号。当后台进程尝试向终端写操作时,它可能会受到SIGTTOU
信号:
SIGTTOU 22,22,27 Stop Terminal output for background process
同样地,如果后台进程忽略或者blockSIGTTOU
信号了,或者它所在的进程组是一个“孤儿进程组”,那么读取终端就会得到一个EIO(error in operation)错误而非收到SIGTTOU
信号。
为了让后台进程加入前台进程组,可以使用fg
命令。
相应操作
可以通过系统调用 setpgid()
将进程加入到另一个进程组中:
int setpgid(pid_t pid, pid_t pgid);
其中pid
是要操作的进程,0代表本进程;pgid
是要加入的进程组,0代表要加入的进程组的pgid是这个进程的pid(也就是说,这个组的组领导就是这个进程)。
使用 setpgid()
要注意以下几点:
- 一个进程可能将
pgid
设置为自己或者它所在组的其他成员,这样的操作可能不会改变其他任意进程的进程组,即使这个进程有root权限。 - 会话头进程(第二部分会介绍)不能改变自己所在的进程组。
- 一个进程不能被加入到另一个会话中的进程组中,换句话说,
setpgid
只能在一个会话中使用。由于setpgid()
只能将进程在本会话中“移动”,所以两个会话不可能有相同的进程组或者进程。
一个进程可以通过 getpgrp()
系统调用获得自己所在组的ID,也可以通过 getpgid(p)
获得pid为p的进程所在组的组ID,当p为0时,获得本进程的组ID:
pid_t getpgid(pid_t pid)
pid_t getpgrp(void)
下面是一个简单的示例图:
#### 断开连接(我对于Linux下的终端、shell通信机制不了解,这个地方暂时贴上参考资料里的原文,以后懂了再翻译)
If the terminal goes away by modem hangup, and the line was not local, then a SIGHUP is sent to the session leader. Any further reads from the gone terminal return EOF. (Or possibly -1 with errno
set to EIO.)
If the terminal is the slave side of a pseudotty, and the master side is closed (for the last time), then a SIGHUP is sent to the foreground process group of the slave side.
When the session leader dies, a SIGHUP is sent to all processes in the foreground process group. Moreover, the terminal stops being the controlling terminal of this session (so that it can become the controlling terminal of another session).
Thus, if the terminal goes away and the session leader is a job control shell, then it can handle things for its descendants, e.g. by sending them again a SIGHUP. If on the other hand the session leader is an innocent process that does not catch SIGHUP, it will die, and all foreground processes get a SIGHUP.
1.2 孤儿进程组
现在我们来讨论当会话消失的时候进程是如何终止的。
假设有一个在终端下运行的会话,其会话头是一个shell。当这个shell存在时,会话中的进程组处于不同的环境中,可能在运行,也可能被挂起了。当终端关闭时,如果它正在运行,当终端关闭后它就无法读入或者输出了;如果它被挂起了,则它可能永远不会被唤醒(也不会终止)。在这种情况下,原会话的进程组就被称为“孤儿进程组”。POSIX定义为该进程组的父进程也是该进程组的成员或者是别的会话的成员。总之,只要一个进程组的父进程在同一会话的不同组中,它就不是孤儿进程组。
当一个进程组成为孤儿进程组之后,在这个进程组中的每一个进程都会被发送一个SIGHUP
信号——通常进程将会被正常关闭。对于收到SIGHUP
信号后,选择不终止的程序将会被发送一个SIGCONT
,这个信号将会重启任何被挂起的进程。这个信号流程能够关闭大多数的进程并保证剩下的是正在运行的进程(不被挂起)。
当进程被“遗弃”后,它被强制和控制终端分离(其他的用户便可以使用这个终端)。原来的会话ID继续被保留(不作为新生进程的PID)直到每一个会话中的程序退出。
2.1 会话
当一个用户注销的时候,内核会终止用户之前启动的所有进程(不然这些进程会在那一直运行并等待输入的到来)。为了简化这个任务,内核将几个进程组并为一个“会话”。会话的ID就是通过setsid()
启动这个会话的进程的PID (也就是这个会话的第一个进程,通常是用户的shell),这个进程也称为“会话头”,它随后产生的所有子孙进程都默认在这个会话里。
进程也可以使用setsid
使自己离开自己的会话,其参数为空,返回新的会话的ID:
#include <unistd.h>
pid_t setsid(void);
2.2 控制终端
每一个会话有且仅有一个对应的终端,会话中的进程从这个终端得到输入并输出,该终端被称为“控制终端”(controlling terminal)。这个终端可能是机器本地的控制台、桌面环境的伪终端、网络上的伪终端等等。
虽然一个会话对应的控制终端是可以改变的,但这通常都是由初始化用户登录环境的那个进程设定的。
主要参考:
- The Process Model of Linux Application Development
- Processes (里面有关于进程和线程的简略介绍)
- 《深入理解计算机系统》第三版
进程组与会话 Linux Process Groups and Sessions的更多相关文章
- Linux进程组和会话
Linux的进程相互之间有一定的关系.比如说,在Linux进程基础中,我们看到,每个进程都有父进程,而所有的进程以init进程为根,形成一个树状结构.我们在这里讲解进程组和会话,以便以更加丰富的方式了 ...
- linux内核之进程的基本概念(进程,进程组,会话关系)
进程是操作系统的一个核心概念.每个进程都有自己唯一的标识:进程ID,也有自己的生命周期.一个典型的进程的生命周期如图4-1所示. 进程都有父进程,父进程也有父进程,这就形成了一个以init进程为根的家 ...
- Linux 的进程组、会话、守护进程
一.进程组ID 每个进程都属于一个进程组.每个进程组有一个领头进程.进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号.每个进程组都有唯一的进程组ID(整数,也可以 ...
- 进程 、进程组、会话、控制终端之间的关系 (转载 http://blog.csdn.net/yh1548503342/article/details/41891047)
一个进程组可以包含多个进程 进程组中的这些进程之间不是孤立的,他们彼此之间或者存在者父子.兄弟关系,或者在功能有相近的联系. 那linux为什么要有进程组呢?其实提供进程组就是方便管理这些进程.假设要 ...
- 重读APUE(8)-进程、进程组、会话
进程: 是系统中一段程序执行的实体,也是资源分配和调度的基本单位: 进程组: 为了方便管理多个进程,可以将多个进程加入到一个进程组内: 每个进程都属于一个进程组,但是同一个进程组内可以有多个进程: 每 ...
- APUE 2 - 进程组(process group) 会话(session) job
进程组(process group) 进程组顾名思义是指一个或多个进程的集合.他们通常与同一个job(可以从同一个终端接收信号)相关联.每个进程组拥有一个唯一的Process Group Id.可以使 ...
- linux 会话 进程组 守护进程
Linux 下每个进程都会有一个非负整数表示的唯一进程 ID ,简称 pid . Linux 提供了 getpid 函数来获取 进程的 pid ,同时还提供了 getppid 函数来获取父进程的 pi ...
- 进程的基本属性:进程ID、父进程ID、进程组ID、会话和控制终端
摘要:本文主要介绍进程的基本属性,基本属性包含:进程ID.父进程ID.进程组ID.会话和控制终端. 进程基本属性 1.进程ID(PID) 函数定义: #include <sys/typ ...
- The Linux Process Principle,NameSpace, PID、TID、PGID、PPID、SID、TID、TTY
目录 . 引言 . Linux进程 . Linux命名空间 . Linux进程的相关标识 . 进程标识编程示例 . 进程标志在Linux内核中的存储和表现形式 . 后记 0. 引言 在进行Linux主 ...
随机推荐
- null transform hack 强制使用硬件加速
-webkit-transform: translateZ(0); -webkit-transform: translate3d(0,0,0); 作用: 1.切换到硬件合成模式,通常所有事情都CP ...
- Oracle - java创建Oracle 的触发器
Oracle - java创建Oracle 的触发器 今天碰到这个问题,遇到点问题,到这来 总结一下解决的办法, 需求,为一个用户当中的表增加一个自动增长列,我还没有学Oracle 的这部分,只是简单 ...
- ldap数据库--ODSEE--复制协议
简单介绍一下ODSEE的复制拓扑的建立,复制协议可以通过管理界面进行创建,也可以通过命令行创建.在此之前需要了解一些复制协议的相关概念,这里针对OESEE. 1,复制角色 master(提供者,也可以 ...
- C# Ioc容器Unity,简单实用
开头先吐槽一下博客园超级不好用,添加图片后就写不动字了,难道是bug 好进入正题,先来说下依赖注入,简单来说就是定义好接口,上层代码调用接口,具体实现通过配置文件方式去指定具体实现类. 首先我们需要通 ...
- Spring 高级依赖注入方式
1.处理自动装配的歧义性 1.1 标记首选的bean 使用@Primary 来说明一个bean是首选的. @Component @Primary public class GuoRongCD im ...
- [转载] Storm:最火的流式处理框架
转载自http://www.cnblogs.com/langtianya/p/5199529.html 伴随着信息科技日新月异的发展,信息呈现出爆发式的膨胀,人们获取信息的途径也更加多样.更加便捷,同 ...
- pt-tcp-model
http://blog.9minutesnooze.com/analyzing-http-traffic-tcpdump-perconas-pttcpmodel/ #获取200k个packets tc ...
- Linux系统查找文件目录的命令
查找目录名autobackup,并且列出路径:find -name 'autobackup'-type d find -name 'directoryname'-type d
- Maven工程下报错:The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path
Maven工程下,webapp下面新建index.jsp文件,报如下错误. 原因很简单,没有安装如下maven依赖包: <dependencies> <!-- JSP相关 --> ...
- 了解Python列表的一些方法
首先定义一个名字列表,然后使用print() BIF在屏幕上显示这个列表. 接下来,使用len() BIF得出列表中有多少个数据项,然后再访问并显示第2个数据项的值: 创建了列表之后,可以使用列表方法 ...