# 系統程式設計 - Process Identifiers
[TOC]
## 課程影片
### W15 - 3: process group
{%youtube N5pWfbeE7jc %}
## 文件
相關內容可以參考 [`credentials(7)`](https://man7.org/linux/man-pages/man7/credentials.7.html)、[`setpgid(2) `](https://man7.org/linux/man-pages/man2/setpgid.2.html)、[`setsid(2)`](https://man7.org/linux/man-pages/man2/setsid.2.html),以及鳥哥的[程序管理與 SELinux 初探](http://linux.vbird.org/linux_basic/0440processcontrol.php#background_what)
## 行程的 PID 與 PPID
### PID
每一個行程有一個用以識別的唯一整數,這個整數稱為行程的 PID,型別為 `pid_t`。這個 PID 會在 `fork()` 時給定好(按:如果是用 `exec()` 前後 PID 會維持)。一個行程的 PID 可以鞥 `getpid()` 得到,或是使用 `ps` 這個命令列工具列出來。
### PPID
「父行程」的 PID。誰 `fork()` 出一個行程,誰就是該行程的父行程,可以用 `getppid()` 函式得到,或是在命令列工具使用 `-f` 選項。
### 例子:`pstree` 的 `-p` 選項
除了使用 `ps` 這個工具,也可以使用 `pstree` 這個工具列出行程監的關係:
```shell
$ pstree -p
```
當中的數字是 PID,而階層則是「誰 `fork()` 了誰」:
```python
systemd(1)─┬─accounts-daemon(1685)─┬─{accounts-daemon}(1693)
│ └─{accounts-daemon}(1719)
├─agetty(1747)
├─agetty(1749)
├─atd(1743)
├─containerd(1740)─┬─{containerd}(1775)
│ ├─{containerd}(1776)
│ ├─{containerd}(1777)
│ ├─{containerd}(1778)
│ ├─{containerd}(1779)
│ ├─{containerd}(1789)
│ ├─{containerd}(1790)
│ ├─{containerd}(1791)
│ ├─{containerd}(1792)
│ ├─{containerd}(1794)
│ ├─{containerd}(1795)
│ └─{containerd}(1796)
├─cron(1724)
├─dbus-daemon(1686)
├─fwupd(1975)─┬─{fwupd}(1989)
│ ├─{fwupd}(1999)
│ ├─{fwupd}(2000)
│ └─{fwupd}(2001)
├─irqbalance(1689)───{irqbalance}(1695)
├─multipathd(1552)─┬─{multipathd}(1553)
│ ├─{multipathd}(1554)
│ ├─{multipathd}(1555)
│ ├─{multipathd}(1556)
│ ├─{multipathd}(1557)
│ └─{multipathd}(1558)
```
## PGID
一個行程的 PGID (*process group id*) 是一個型別同樣為 `pid_t` 的整數。若兩個行程具有相同的 GID,則稱這兩個行程屬於同一個 *process group*。而一個 *process group* 是指所有具有相同 *process group id* 的行程所形成的集合。一個 process group 有時候又稱為一個 *job*。這些敘述可以在參考 `man` 當中查到:
> A *process group* (sometimes called a *"job"*) is a collection of processes that share the same process group ID;
一個行程被 `fork()` 出來時,其預設的 PGID 就是父行程的 PGID,而這個 PGID 可以事後使用 `setpgid(2)` 改變:
> A process's group membership can be set using `setpgid(2)`.
因為「兩個行程是否屬於一個 process group」是以「兩個行程的 PGID 是否相同」作為依據。因此改變一個行程的 PGID,就等於是改變一個行程所屬的 process group。特別地,當一個行程自己的 PID 跟 PGID 一樣時,就稱這個行程是這個 process group 的 *group leader*。
> The process whose process ID is the same as its process group ID is the process group leader for that group.
### 例子:`pstree` 的 `-g` 選項
使用 `pstree` 的 `-p` 與 `-g` 選項可以列出 PID 與 PGID:
```
$ pstree -pg
```
第一個數字是 PID,第二個數字是 PGID,階層關係表示誰 `fork()` 出誰:
```python
systemd(1,1)─┬─accounts-daemon(1685,1685)─┬─{accounts-daemon}(1693,1685)
│ └─{accounts-daemon}(1719,1685)
├─agetty(1747,1747)
├─agetty(1749,1749)
├─atd(1743,1743)
├─containerd(1740,1740)─┬─{containerd}(1775,1740)
│ ├─{containerd}(1776,1740)
│ ├─{containerd}(1777,1740)
│ ├─{containerd}(1778,1740)
│ ├─{containerd}(1779,1740)
│ ├─{containerd}(1789,1740)
│ ├─{containerd}(1790,1740)
│ ├─{containerd}(1791,1740)
│ ├─{containerd}(1792,1740)
│ ├─{containerd}(1794,1740)
│ ├─{containerd}(1795,1740)
│ └─{containerd}(1796,1740)
├─cron(1724,1724)
├─dbus-daemon(1686,1686)
├─fwupd(1975,1975)─┬─{fwupd}(1989,1975)
│ ├─{fwupd}(1999,1975)
│ ├─{fwupd}(2000,1975)
│ └─{fwupd}(2001,1975)
├─irqbalance(1689,1689)───{irqbalance}(1695,1689)
├─multipathd(1552,1552)─┬─{multipathd}(1553,1552)
│ ├─{multipathd}(1554,1552)
│ ├─{multipathd}(1555,1552)
│ ├─{multipathd}(1556,1552)
│ ├─{multipathd}(1557,1552)
│ └─{multipathd}(1558,1552)
...
```
### 建立 Process Group --- 自己把 PGID 設成自己
那這樣的 *process group* 什麼時候會被建立呢?若一個行程把自己的 PGID 設定成跟自己的 PID 一樣,那麼它就會自成一個「成員只有自己」的 *process group*。這就是建立一個新的 *process group* 的方法 [^process-group-create]。
[^process-group-create]: If the pid and pgid arguments specify the same process (i.e., pgid is 0 or matches the process ID of the process specified by pid), then a new process group is created, and the specified process is made the leader of the new group --- TLPI p.702
### 建立新 Process Group 的情境
一些例子是比如 *shell* 執行命令時。當 *shell* 執行單一一個命令時,這個命令自己就形成一個成員只有自已的 process group。這時,這個行程的 PGID 就會是自己。另外,若一個行程的輸出被 pipe 給另外一個行程作為輸入,那麼從 pipe 接收輸出的行程也會跟那個輸出資料的行程屬於同一個 process group:
> the shell creates a new process group for the process(es) used to execute single command or pipeline (e.g., the two processes created to execute the command "ls | wc" are placed in the same process group)
## SID
一個 *session* 是指一個由 *process group* 形成的集合 [^def-session] 同一個 *process group* 中的行程,都會具有相同的 SID[^group-sid] (所以每一個 process group 都屬於某個 session)。當使用類似「一個 *process group* 的 SID」的用語時,這個 SID 指得就是這個 SID。若兩個 *process group* 具有相同的 SID,就稱他們屬於同一個 session[^sid-membership]。
一個行程被 `fork()` 出來時,其 SID 會預設與其父行程相同[^sid-fork]。
### Control Terminal
在一個 session 所包含的所有 process group 中,至多只能有一個 process group 可以被終端機控制。這時稱這個 process group 為 *foreground job*,而這個 session 中其餘的 process group 則稱為 *background job*[^fg-bg]。舉例來說,平常按下 Ctrl + C 時,就是送信號給 foreground job [^ctrl-c-fg]
這樣的 terminal 跟 *session* 是一一對應的:一個 session 至多只能由一個 terminal 控制,而一個 terminal 至多只能用來控制一個 session [^uniq-terminal]。
[^fg-bg]: At most one of the jobs in a session may be the foreground job; other jobs in the session are background jobs. Only the foreground job may read from the terminal
[^ctrl-c-fg]: When terminal keys that generate a signal (such as the interrupt key, normally control-C) are pressed, the signal is sent to the processes in the foreground job.
[^uniq-terminal]: A terminal may be the controlling terminal of at most one session.
### 建立新 Session --- `setsid()`
一個行程呼叫 `setsid()` 時,會使自己的 SID 與 PGID 都變成跟自己的 PID 一樣。這個意思也就是建立一個只包含一個「只包含自己的 process group」的 session [^setsid-session-leader][^setsid-group-leader]:。這時,這個行程自己既是一個 process group leader,也同時是一個 sesson leader。
一個行程只能藉由呼叫 `setsid()` 成為 session leader。所以,一個行程的 SID 不是從父行程繼承而來,就是自立門戶,自己呼叫 `setsid()` 成為一個新 session 的老大。
須注意的是:若一個行程已經是某個 process group leader,則其不能另立 session [^setsid-error]。這是為了維持維持「每一個行程都屬於一個 process group; 每個 process group 都屬於一個 session」這個性質。
[^def-session]: A session is a collection of process groups. --- TLPI p.702
[^group-sid]:All of the members of a process group also have the same session ID (i.e., all of the members of a process group always belong to the same session, so that sessions and process groups form a strict two-level hierarchy of processes.)
[^sid-membership]: The session membership of a process is defined by its numeric session ID. --- TLPI p.704
[^sid-fork]: A new process inherits its parent’s session ID. --- TLPI p.704
[^setsid-session-leader]:A new session is created when a process calls `setsid(2)`, which creates a new session whose session ID is the same as the PID of the process that called `setsid(2)`. The creator of the session is called the session leader. (`man 7 credentials`)
[^setsid-group-leader]: The calling process also becomes the process group leader of a new process group in the session (i.e., its process group ID is made the same as its process ID). (`man 2 setsid`)
[^setsid-error]: `setsid()` creates a new session if the calling process is not a process group leader.