本文參考了:
- 極客時間專欄《趣談Linux作業系統》12.程式資料結構(上):專案多了就需要專案管理系統
- Chapter 3. Process Management- Shichao's Notes
- How to kill an individual thread under a process in linux?
基本概念清單
- Linux 裡面,程式和執行緒到了核心,統一都叫做任務(Task)。
- 每個 task 都有一個資料介面
task_struct
,用來儲存task 狀態。
任務列表
linux 核心中有一個包含所有 task 的連結串列,把所有的 task_struct 連起來。
如圖所示:
task_struct
struct 定義:
struct list_head tasks;
複製程式碼
看一下每個task_struct
包含了哪些重要的欄位。
任務 ID
和任務 ID 相關的欄位有下面這些:
pid_t pid;
pid_t tgid;
struct task_struct *group_leader;
複製程式碼
這三個欄位的具體含義為:
- pid : 每個 task 都有一個 pid,是唯一的,不管是程式還是執行緒。
- tgid: 指向主執行緒的 pid
- group_leader: 指向程式的主執行緒
任何一個程式,如果只有主執行緒,那 pid 是自己,tgid 是自己,group_leader 指向的還是自己。
但是,如果一個程式建立了其他執行緒,那就會有所變化了。執行緒有自己的 pid,tgid 就是程式的主執行緒的 pid,group_leader 指向的就是程式的主執行緒。
有了 tgid 之後,我們就可以判斷一個 task 是執行緒還是程式了。
那麼區分是程式還是執行緒有什麼用呢?考慮下面幾個場景:
ps
命令
ps
預設展示的是所有程式的列表,而不是把所有的執行緒都列出來,那會顯得很亂沒有重點。
- 給執行緒傳送
kill -9
訊號?
假如說我們給某個程式中的一個執行緒傳送了退出訊號(比如kill -9
),那麼我們不應該只退出這個執行緒,而是退出整個程式(至於為什麼請看下文)。所以就需要某種方式,能夠獲取該執行緒所在程式中所有執行緒的 pid。
從一個程式中殺死某一個執行緒是非常危險的操作。 比如說某個 thread正在進行分配記憶體的工作,這時候它會hold 記憶體分配器的 lock。如果你把它強制殺死了,這個鎖就永遠不會釋放,那麼其他的 thread 也會停止。所以需要主程式的協助,來優雅地退出所有的執行緒。
上圖來源於 這個so 上的問答。
不信的話,我們可以來做一個實驗。
下圖顯示的是htop
工具,白色的表示程式,綠色的表示執行緒。可以看到每個執行緒確實都有一個唯一的 PID。
現在讓我們來給圖中標記的PID 為21656
的code-server
執行緒傳送kill -9
訊號,然後發現,整個程式都退出了:
上圖中,code-server
這個 docker 容器程式在一分鐘前退出了。
訊號處理
原始碼地址:github.com/torvalds/li…
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked;
sigset_t real_blocked;
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
複製程式碼
- blocked : 被阻塞暫不處理
- pending : 等待處理
- sighand : 哪個訊號正在被處理
注意這裡的struct signal_struct *signal;
指向了一個signal
struct。這個struct 中還有一個struct sigpending pending;
。前面提到過需要區分執行緒和程式,這裡也可以看出一點端倪。第一個是執行緒組共享的,一個是本任務的。
任務狀態
一個 task 的任務狀態一共可以取下面的這些值:
/* Used in tsk->state: */
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* Used in tsk->exit_state: */
#define EXIT_DEAD 16
#define EXIT_ZOMBIE 32
#define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_DEAD 64
#define TASK_WAKEKILL 128
#define TASK_WAKING 256
#define TASK_PARKED 512
#define TASK_NOLOAD 1024
#define TASK_NEW 2048
#define TASK_STATE_MAX 4096
複製程式碼
總結一下
我的公眾號:全棧不存在的