Overview
Linux使用struct task_struct
数据结构来关联所有与进程有关的数据和结构,定义了pid、tgid、pgid、sid四种进程类型。
Note:
- pid —— Process identifier,进程标识符。
- tid —— Thread identifier,线程标识符。
- tgid —— Thread group identifier,线程组标识符。
- pgid —— Process group identifier,进程组标识符。
- sid —— Session identifier,会话标识符。
在v4.12中,与四种进程类型有关的系统调用有以下8个:
getpid | gettid |
getppid | getpgrp |
getpgid | setpgid |
getsid | setsid |
Introduction
- pid —— 在特定PID namespaces中,Linux使用pid唯一标识一个进程。使用
fork / clone
创建一个进程均会被分配一个新的且唯一的pid。 - tid —— Linux的线程由Native POSIX Thread Library(NPTL)实现,本质上是用进程模拟线程,因此内核中并没有tid这种数据,它由pid表示。线程是通过带有
CLONE_THREAD
flag参数clone
建立的进程。 - tgid —— 同一个进程通过
clone
建立了的线程处于同一个线程组,该线程组的ID叫做tgid,线程组组长的tgid与其pid相同。若一个进程没有线程,则其tgid与pid也相同。 - pgid —— 进程组是一个或多个进程的集合,每个进程都属一个进程组,拥有相同的pgid。进程组组长(process group leader)的pid是进程组的ID,用以识别进程,可通过
setpgrp
为进程设置pgid。 - sid —— 几个进程组使用
setsid
可以合并成为一个会话组,会话组内的所有进程拥有相同的sid。
Pre-internal
PID namespace
PID namespaces隔离了进程ID,指的是不同的进程在不同的PID namespaces可以有着相同的PID。这个特性是在主机之间迁移容器的先决条件;只有一个PID namespaces的时候,在保持PID不变(保持PID不变是一种需求)的情况下将其迁移到另一个主机可能会失败,因为目标节点上可能存在相同PID的进程。
PID namespaces具有层级顺序,低级的PID namespaces对高级的不可见。一个PID namespaces可以具有多个child PID namespaces,每一个PID namespaces都可以看做parent PID namespaces的一个局部视图。一个新的PID namespaces B从当前PID namespaces A被创建后,处于当前PID namespaces A的进程可以看到B中的所有进程,但反之不成立。
新的PID namespaces通过带有CLONE_NEWPID
flag参数的clone
系统调用创建。新namespace种第一个进程的PID是1,它是这个namespace的init进程,属于此namespace的孤儿进程将会被它收养;若这个进程死亡,则整个namespace都会被中止。
Overview of struct task_struct
design
一个进程对应一个struct task_struct
不考虑进程之间的关系、命名空间,仅仅是一个pid对应一个struct task_struct
,可以设计如下数据结构:
1 | struct task_struct { |
能用下图描述:
上图中,
pid_hash[]
—— 是一个hash表的结构,根据struct pid
的nr值哈希到其某个表项,若有多个nr值对应到同一个表项,则使用散列表法解决冲突。利用链表的container_of
机制13可以利用tasks
反向得到task_struct
。pid_map
—— 是一个位图(bitmap),是用来唯一分配pid值的结构。
这种设计可以达到:
- 快速地给新进程在可见的命名空间内分配一个唯一的pid
- 快速地利用
task_struct
找到pid
- 快速地利用
pid
反向得到task_struct
进程区分了id类型
考虑到进程/线程之间的复杂关系,原来的struct task_struct
中的pid_link
需要增加几项,用以指向到其组长进程的pid,相应的struct pid
也需要增加几项用以链接那些以该pid为组长的所有进程组组内进程:
1 | enum pid_type { |
新的结构设计示意图如下:
增加了pid namespaces的struct task_struct
在第二种情形下再增加pid namespaces,同一个进程在不同的pid namespaces下有不同的pid,因此新的数据结构如下:
1 | enum pid_type { |
最终成了这样:
Note:upid
是unique pid
的缩写。
real_parent
vs parent
struct task_struct
中有俩parent:
1 | struct task_struct { |
Wikipedia中的parent process中有提到456,大意是:
Linux内核的process与POSIX threads差异极其小,对于每个进程/线程来说都有两种类型的parent process,分别为real_parent与parent。Real_parent仅仅是使用
clone
创建子进程/线程的的进程,对创建出来的进程/线程并没有控制权,而parent进程才是在子进程/线程中止的时候接收SIGCHLD
的进程。通常情况下,他们的值是相同的;但对于POSIX threads来说,他们的值可能会有差异。
Internal
getpid
& gettid
& getpgrp
& getpgid
& getsid
在linux v4.12的include/linux/pid.h中定义了五种与进程/线程有关的id类型:
1 | enum pid_type |
实际上,与进程有关的各类id都是group_leader的id,与线程有关的tid才是线程所属进程的pid。至于__PIDTYPE_TGID
,它有着如下做法:
1 | if (type != PIDTYPE_PID) { |
因此,源码中有了以下的注释(thread group id即process group leader pid)
1 | /* |
下图展示了一个进程fork
一次、每个进程再pthread_create
两次的大致关系8:
setsid
setsid
的实现中,有这么一行代码:
1 | proc_clear_tty(group_leader); |
它的作用使脱离终端。《那些永不消逝的进程》7一文对此有相应的解释,梗概如下:
忽略SIGHUP
信号的子进程不会因父进程的退出而退出,这是通过nohup运行的子程序可以在后台保持运行、不随终端退出而中止的原理。但是,为什么忽略了SIGHUP
信号的子进程就不会随着父进程的结束而消逝?在什么样的场景下,一个进程会收到SIGHUP
信号呢?这与Linux系统中描述进程关系的两个术语进程组(process group)和会话(session)有关。
在早期Unix的设计中,每当有一个终端(terminal)通过某一tty来访问服务器,一个包含login shell进程的进程组就会被建立起来,所有在该shell中被建立的进程都会自动隶属于同一进程组之下,同时该tty也会被设置成该进程组下所有进程共有的终端控制器(controlling terminal)。但是,进程组对控制终端缺乏有效的管理手段、所有进程无差别地共享控制终端等的设计带来了许多弊端。因此作业控制(job control)的概念被提了出来,会话的设计也被引入,简单地说:新的设计将控制终端(tty或pty)的访问和控制完全置于了会话的管理之下。
SIGHUP
是当终端连接中断或关闭时,直接或间接地被发送给会话中的所有进程组,一般进程对于该信号的默认处理方式也同样是终结自己。但是,nohup通过屏蔽SIGHUP
的方式来实现守护进程并不理想,会出现缺少控制终端(SIGHUP
不再起作用)、守护进程的工作目录无法umount
等一系列问题。
Glibc把实现守护进程所需的工作封装到了daemon
函数中,通过fork
创建出的子进程执行setsid
、同时父进程退出来实现,并且执行setsid
成功的进程不仅成为新的会话组长和新的进程组长,还不会关联任何终端。
TODD:Question —— 为什么当前进程为process group leader的时候要使调用失败?
1 | if (pid_task(sid, PIDTYPE_PGID)) |
References
- LWN.net: PID namespaces in the 2.6.24 kernel
- Linux Programmer’s Manual: pid_namespaces
- Professional Linux Kernel Architecture - Linux
- Wikipedia: Parent process
- trace 30個基本Linux系統呼叫第九日:getpid與getppid
- A Sneak-Peek into Linux Kernel - Chapter 2: Process Creation
- IBM developerWorks:那些永不消逝的进程
- cnblogs: Linux 内核进程管理之进程ID
- CSDN: Linux中的进程关系详解
- CSDN: Linux内核原理-pid namespace
- LWN.net: Namespaces in operation, part 3: PID namespaces
- CSDN: linux内核PID管理