# 2026-04-14 問答簡記
## 美伊衝突
> [伊朗戰爭的真正根源和未來走向](https://youtu.be/yrBOtI7lhlg)
美國與以色列對伊朗關鍵軍事與核設施發動攻擊,進而引發區域性衝突升級,並可能波及全球能源與金融體系。伊朗最高領袖遇襲身亡、政權快速交接,霍爾木茲海峽遭全面封鎖並導致油價劇烈飆升。
特別是霍爾木茲海峽這一關鍵航道,一旦受到軍事威脅,即使只是部分干擾,都可能對全球能源供應鏈造成顯著衝擊。這種結構性的脆弱性,使得任何美伊衝突都不只是區域軍事問題,而會迅速外溢為全球經濟與金融風險。即便美國在傳統軍力與空中優勢上佔據上風,但若衝突進一步升高,伊朗可能透過非對稱作戰方式(例如無人機、水雷、區域代理人力量)將戰局拖入長期消耗,從而提高美方的政治與軍事成本。。
1953 年由 阿賈克斯行動 所引發的政權更迭,確實被普遍認為是伊朗長期反美情緒的重要根源;而 1971 年 尼克森衝擊 之後逐步形成的石油美元體系,也確實讓能源貿易與美元地位高度綁定。
---
### Gagura1236
* 遠端面試的時候要換位思考,**顧慮面試官的感受**,請確保收音來源沒有迴音、背景音或雜訊。請獨立收音。
* 回答問題時**第一句就直接講重點**,後面再補充自己的想法,如果面試官有興趣則會繼續話題,務必記得不要冗長及贅字。
> 「15 秒原則」:針對任何問題,在 15 秒內提供得以延伸、詳述的關鍵字或表述
:::info
示範「Mutex 與 Semaphore 的差異」: [Mutex, Semaphore, the difference, and Linux kernel](https://blog.louie.lu/2016/10/22/mutex-semaphore-the-difference-and-linux-kernel/)
15 秒乘上 2 的冪 (1, 2, 4, 8, 16, ...),亦即 15, 30, 60, 120 秒當作查核點,每一個查核點都該傳遞關鍵資訊,而且確保 interviewer 是否有所回應
:::
示範「Mutex 與 Semaphore 的差異」:
15秒 (Elevator Pitch):Mutex 就像是一把鑰匙,『誰上鎖,就必須由誰解鎖』,
30秒 (The "How"):
60秒 (The "Deep Dive"):
120秒 (The "Wisdom")
* 面試的問題都不會事先提供,為的是快速得知回答者的 <s>語言</s> 組織和溝通能力,是否能把已學習過的知識:
**1.快速抓回腦中** -> 想知道你是否真的懂
**2.重點摘要** -> 想知道你的表達跟邏輯能力
**3.解釋給別人聽懂** -> 想知道你和他人協作時的溝通態度
* Q : Threads 跟 Process 的差別是什麼?
- [ ] Ans :
兩者差異在透過 **clone()** 系統呼叫建立任務時,**資源分享**的程度。
Process 不共享 **Address Space** ,是獨立的。
Threads 要共享 **Address Space** ( *透過clone_VM* )
但其實 Linux Kernel 不區分 Threads 跟 Process , 統一都用 **task_struct** 來表示一個任務。
:::warning
:warning: FOLLOW-UP: 為何已有 fork 系統呼叫,Linux 還要引入 clone 系統呼叫?fork 是否只是 clone 的特例?
:::
FOLLOW-UP:
---
* Q :這兩項的實例應用場景是?
- [ ] Ans :
網頁伺服器適合用 Thread ,因為需要同時處理大量使用者短暫的並行請求 (HTTP Request)。因為會共享記憶體,所以<s>不需要複製 **Address Space** 就能快速存取</s>。
:::danger
使用精準描述
:::
比如 **狀態機 (state machine)**,如同時選課,發現沒餘額了就要退回上一步。
* Q :多檔案同時編輯不適合用 Thread ?
- [ ] Ans :
不適合,多檔案編輯該用 **Process** 。
:::danger
核實下方描述,使用精準詞彙和針對場景去改進。
:::
如果多個 Threads 對同一個檔案在不同記憶體位置隨機讀寫,會造成 File offset 混亂,造成不一致並導致 Lock Contention。
**Locks** 只是保護共享資源的手段,是**針對執行中的資源**,這和 Threads 跟 Process **一建立時就區分**了資源分享的程度沒有關係。
**而 Process Pool 的話每個 Process 有獨立的系統資源。** 確保一個任務不會影響到其它 Worker or Task。
:::warning
:warning: FOLLOW-UP: 參見 [I/O 模型演化](https://hackmd.io/@sysprog/linux-io-model),用 NGINX 的 worker 模型來解說
:::
---
### ArBin1020
:::danger
TODO: 探究關鍵程式碼,並進行對應實驗,利用 https://hackmd.io/@sysprog/linux-process 提及的工具,量化分析
:::
thread 跟 process 應用場景
- 以 PostgreSQL 為例
以 PostgreSQL 為例,PostgreSQL 採用的是 Process per-usr 的 client/server model,根據 PostgreSQL 官方架構文件,一個 PostgreSQL 會有幾個主要的 process 組成:
1. Postmaster:負責管理 cluster 中的資料庫及監聽來自網路或本地端的連線請求
2. Backend Server Process:由 Postmaster 為每一個新建立的 client 連線所 fork 出來的獨立的 process,負責處理該次連先的所有查詢操作
> [參考資料](https://www.postgresql.org/docs/7.3/arch-pg.html)
在微軟的開源資料庫駭客 Thomas Munro 在 2025 年的 pgconf.dev 上發表了一篇主題為 「Investigating Multithreaded PostgreSQL」的報告
> [影片](https://www.youtube.com/watch?v=7BvLaRkaijc)
> [簡報](https://speakerdeck.com/macdice/investigating-multithreaded-postgresql)
裡面提到當初 POSTGRES 的設計者就已經計畫將系統轉換成 threads,但因為當時各作業系統的執行緒 API 不相容,像是 POSIX 於 1995 年才標準化,所以最初採取了程序 process 模型
由於不同 process 映射共用記憶體的位址不同,PostgreSQL 必須使用 Dynamic Shared Areas 與 dsa_pointer,每次存取都需要額外的位址轉換,若使用 thread,就可以不用繞這麼大圈處理,另外 process 之間也難以共享 I/O 完成的事件,所以有許多專案或分支已經在嘗試將 PostgreSQL 轉換為 thread 模型
但是從架構穩定性角度來看,因為 process 預設不共用記憶體,每一個 Backend process 在獨立的位址空間中運作,如果某個 backed process 因為遇到未預期的錯誤,不會影響到其他 process
在原始碼中 `Postmaster.c` 是系統的進入點,根據 [Postmaster 原始碼註解](https://github.com/postgres/postgres/blob/master/src/backend/postmaster/postmaster.c)
```c
/*
* The postmaster process creates the shared memory and semaphore
* pools during startup, but as a rule does not touch them itself.
* In particular, it is not a member of the PGPROC array of backends
* and so it cannot participate in lock-manager operations. Keeping
* the postmaster away from shared memory operations makes it simpler
* and more reliable. The postmaster is almost always able to recover
* from crashes of individual backends by resetting shared memory;
* if it did much with shared memory then it would be prone to crashing
* along with the backends.
*/
```
Postmaster 的初衷在於避免因存取損毀的共用記憶體而被卡死
```c
/*
* When a request message is received, we now fork() immediately.
* The child process performs authentication of the request, and
* then becomes a backend if successful. This allows the auth code
* to be written in a simple single-threaded style...
* More importantly, it ensures that blockages in non-multithreaded
* libraries like SSL or PAM cannot cause denial of service to other
* clients.
*/
```
在 Linux 中,process 與 thread 的核心差異在於記憶體空間的共享方式。Thread 之間共享記憶體空間,而 process 之間則彼此隔離。thread 可存取其所屬 process 的 address space。
Process 視為執行中的程式,概念上提供每一個運行中的程式擁有假想的獨立運行環境:每個 process 都認為自己是電腦上唯一執行的程式,獨享 CPU、記憶體和硬體資源。Thread 則是隸屬於 process 的執行單元。在記憶體層面,process 之間彼此隔離互不影響,而屬於相同親代的 thread 則共享記憶體和其他資源。
以 CPU 排程的觀點,排程器操作的對象並非 `task_struct`,而是內嵌於其中的 scheduling entity。在 Linux v6.18 中,`task_struct` 包含多個 scheduling entity,分別對應不同的排程策略:
```c
struct task_struct {
...
struct sched_entity se;
struct sched_rt_entity rt;
struct sched_dl_entity dl;
struct sched_dl_entity *dl_server;
struct sched_ext_entity scx;
...
};
```
其中:
- `se`:fair scheduling (EEVDF/CFS) 使用的排程實體
- `rt`:real-time 排程使用的排程實體
- `dl`:deadline 排程使用的排程實體
- `dl_server`:指向為此 task 服務的 deadline server (用於以 deadline 機制驅動 fair scheduling 的頻寬控制)
- `scx`:sched_ext 框架使用的排程實體,允許透過 BPF 程式自訂排程策略
排程器直接操作 `sched_entity`,而非 `task_struct`。這項設計的關鍵在於:`sched_entity` 不一定對應單一 task,也可以代表一整個 task group。以 fair scheduling 為例,`sched_entity` 的關鍵欄位如下:
```c
struct sched_entity {
struct load_weight load;
struct rb_node run_node;
u64 deadline;
u64 vruntime;
u64 slice;
u64 exec_start;
u64 sum_exec_runtime;
...
#ifdef CONFIG_FAIR_GROUP_SCHED
int depth;
struct sched_entity *parent;
struct cfs_rq *cfs_rq; /* 此 entity 所屬的 runqueue */
struct cfs_rq *my_q; /* 此 entity 擁有的 runqueue (group scheduling) */
...
#endif
};
```
當 `CONFIG_FAIR_GROUP_SCHED` 啟用時,`sched_entity` 具備階層結構:
- 若 `my_q` 為 `NULL`,此 entity 代表單一 task,其 `vruntime` 和 `deadline` 直接參與排程競爭
- 若 `my_q` 非 `NULL`,此 entity 代表一個 task group,`my_q` 指向該 group 擁有的 `cfs_rq`,其下可包含更多 `sched_entity` (可能是 task 或更深層的 group)
- `cfs_rq` 指向此 entity 所屬的上層 runqueue,`parent` 指向上層的 `sched_entity`
這種脫勾設計使排程器得以用統一的演算法處理個別 task 與 task group,不需為 group scheduling 撰寫特殊路徑。排程器在選擇下一個執行的 entity 時,從根部 `cfs_rq` 開始,逐層挑選 `vruntime` 最小 (或 deadline 最早) 的 `sched_entity`,直到找到代表實際 task 的 entity 為止。
Process leader 擁有自己的 `mm_struct`,同一 thread group 內的 thread 則共享該 `mm_struct`。但在排程層面,每個 thread 各自持有獨立的 `sched_entity`,CPU 排程器對其一視同仁。
---
### keep90ing
process 跟 thread 的應用場景,以選課系統為例
- 選課系統的狀態機(state machine)
:::warning
:warning: FOLLOW-UP: 提供細節,並說明如何規劃多執行緒在選課系統的場景
:::
分成以下兩個層面說明:
1. 建立選課系統的狀態機
首先參考 [UML State Machine Diagrams: An Agile Introduction](https://agilemodeling.com/artifacts/statemachinediagram.htm),此內容由 [Scott Ambler](https://en.wikipedia.org/wiki/Scott_Ambler) 節錄自己的書籍〈The Object Primer 3rd Edition: Agile Model Driven Development with UML 2〉第 11 章。其中提到,物件同時具備行為與狀態,為了了解某些複雜物件如何根據自身狀態展現出不同的行為,因此要透過繪製 [UML 2 state machine](https://en.wikipedia.org/wiki/UML_state_machine) diagrams 來具體描述其運作機制。
接著查閱 [UML state machine](https://en.wikipedia.org/wiki/UML_state_machine),其中提到這是計算機科學中[有限狀態機](https://en.wikipedia.org/wiki/Finite-state_machine)此數學概念的延伸,UML state machine 的運作邏輯是確保一個 entity 或是其 sub-entities 在任何時刻都精確地處於多個可能狀態中的唯一一個狀態,並且具備明確轉換狀態之條件,常用於組織電腦程式或流程的運作方式。故參考 [Application Note:A Crash Course in UML State Machines](https://www.state-machine.com/doc/AN_Crash_Course_in_UML_State_Machines.pdf)(其內容是由 [Miro Samek](https://www.state-machine.com/about) 整理自己的書籍〈Practical UML Statecharts in C/C++, Second Edition〉之第二章的內容而來) 和 [UML State Machine Diagrams: Diagramming Guidelines](https://agilemodeling.com/style/statechartdiagram.htm)(其內容是由 [Scott Ambler](https://en.wikipedia.org/wiki/Scott_Ambler) 整理自己的書籍〈The Elements of UML 2.0 Style〉之內容而來),來了解 UML state machine diagrams 的基本組成與規範,統整如下:
- events(事件):
指發生了會影響系統的事情,且根據下方敘述,其指的是發生的「類型」,而不是指具體單次發生的動作。
>Strictly speaking, in the UML specification, the term event refers to the type of occurrence rather than to any concrete instance of that occurrence.
來源:[UML state machine](https://en.wikipedia.org/wiki/UML_state_machine#Events)
同時 event 可以有相關的參數來表示實際事件的發生,並傳達關於該次發生相關的量化資訊。
>An event can have associated parameters, allowing the event instance to convey not only the occurrence of some interesting incident but also quantitative information regarding that occurrence.
來源:[UML state machine](https://en.wikipedia.org/wiki/UML_state_machine#Events)
- states(狀態):其決定狀態機對於事件的反應方式,在軟體狀態機中,通常被視為單一且有限的 state 變數,其可列舉,且在任何時間點,可以透過 state 變數的值來定義系統當前的狀態,例如鍵盤切換大小寫時會在 `default` 狀態和 `caps_locked` 狀態間切換。。
- extended states(擴展狀態):為了保持彈性來處理複雜系統,因此藉由 extended state 變數來將 state machine 擴充成 extended state machine,UML state machine 即屬於此,其中 extended state 變數可以量化,例如紀錄按下鍵盤次數的 `count` 變數。
- guard conditions:是指在系統動態執行時,根據 extended state 變數和 event 參數的值來進行評估的 Boolean expression,藉此決定是否允許轉換狀態或動作。
- actions and transitions:當一個實際事件被分派給狀態機時,狀態機會做出相對應的 actions 來回應,其中包含更改變數、呼叫函式、切換到另一個狀態等。同時在 extended state machine 中,若這個回應牽涉到 transition(狀態轉換),則必須滿足特定的 guard condition 才能被觸發。為了處理複雜邏輯,單一狀態可以針對同一個觸發事件設計多條不同的 transition 路徑,前提是這些路徑的 guard condition 互不重疊,以確保在任何時間點,都只有唯一一條路徑的條件成立並成功 transition。
- run-to-completion execution model:
遵循 [Run-to-completion(RTC)](https://en.wikipedia.org/wiki/Run-to-completion_scheduling) 的執行模型,即狀態機必須完整處理完當前的事件後,才能開始處理下一個事件,且系統會以離散且不可分割的 RTC 步驟來處理事件,新傳入的事件不能中斷當前事件的處理,並且會被儲存在 [event queue](https://en.wikipedia.org/wiki/Message_queue#Graphical_user_interfaces) 中,直到狀態機閒置為止。
但是根據下方敘述,這並不代表狀態機在完成 RTC 步驟期間必須獨佔 CPU,該 No Preemption 的限制是針對該狀態機自身的 task context。
>Note, however, that RTC does not mean that a state machine has to monopolize the CPU until the RTC step is complete. The preemption restriction only applies to the task context of the state machine that is already busy processing events.
來源:[UML state machine](https://en.wikipedia.org/wiki/UML_state_machine#Run-to-completion_execution_model)
至於在 multitasking environment 中,只要這些不同的狀態機之間沒有共用變數或其他資源([no concurrency hazards](https://en.wikipedia.org/wiki/Thread_(computing)#Concurrency_and_data_structures)),OS 可以 preempt 正在執行的狀態機,把 CPU 資源先拿去處理其他無關的 tasks。
>In a multitasking environment, other tasks (not related to the task context of the busy state machine) can be running, possibly preempting the currently executing state machine. As long as other state machines do not share variables or other resources with each other, there are no concurrency hazards.
來源:[UML state machine](https://en.wikipedia.org/wiki/UML_state_machine#Run-to-completion_execution_model)
根據 CISAT 2022 的論文 [Design of highly concurrent course selection system based on microservice](https://www.spiedigitallibrary.org/conference-proceedings-of-spie/12451/124513X/Design-of-highly-concurrent-course-selection-system-based-on-microservice/10.1117/12.2656610.full)(已購買),其中提到大學選課系統的主要問題在於面對短時間內大量的 requests 時,會造成伺服器當機。
>At present, the main problem of college course selection system is that when facing a large number of requests in a short time, there will be server downtime.
再根據論文 [Securing High-Concurrency Ticket Sales: A Framework Based on Microservice](https://arxiv.org/pdf/2512.24941),其中提到鐵路的售票系統每逢假日節慶等尖峰時刻,會面臨大量使用者同時存取所帶來的高並行挑戰。
>The railway ticketing system is one of the most important public service infrastructure. In peak periods such as holidays, it is often faced with the challenge of high concurrency scenarios because of a large number of users accessing at the same time. The traditional aggregation architecture can not meet the peak user requirements because of its insufficient fault tolerance and low ability.
因此綜合以上兩點,選課系統和售票系統除了系統結構相似外,都面臨到大量使用者在即短時間內爭奪有限資源的高並行問題,故可以相互參考兩者的系統設計和多執行緒的場景進行討論。
因此將根據上述 [Application Note:A Crash Course in UML State Machines](https://www.state-machine.com/doc/AN_Crash_Course_in_UML_State_Machines.pdf) 的基本與延伸規範,並參考 [UML State Machine Diagrams: An Agile Introduction](https://agilemodeling.com/artifacts/statemachinediagram.htm) 中 seminar 的範例,以及 [Design a Movie Ticket Booking System](https://github.com/tssovi/grokking-the-object-oriented-design-interview/blob/master/object-oriented-design-case-studies/design-a-movie-ticket-booking-system.md),使用 [PlantUML](https://en.wikipedia.org/wiki/PlantUML) 來建立選課系統的 UML state machine diagrams,如下圖所示
2. 規劃多執行緒在選課系統的場景
- 選課系統是建立 process 為主還是 thread為主?
(實務上,先建立多個 process,防止 crash,再建立多個 thread,<s>slow path 的概念</s>)
:::danger
不要亂說,你知道建立 thread 和 process 個別的成本嗎?沒有測量之前,就不該輕易說 "slow"
:::
---
### frank0988
:::danger
使用課程的一致書寫規範,中英文間用一個半形空白字元區隔
:::
- [ ] 爲何 linux 系統使用 task 而不是使用恐龍書裏面的 process thread
* 第一個好處有程式碼共享,例如 creadte_task(),exec_task() 比create_process().create_thread()... 減少重複造輪子,因爲 thread 跟 process 需要的東西是接近的
* 第二個好處在排程中,sche 的設計在分配執行資源時,如果使用 thread process 雙軌制的時候我就要有兩套 sche 的分配,<s>而者可能有race,如何平衡,造成大量額外的問題</s> 避免二套不同的排程進入點和不同的排程策略
- [ ] linux 最小排序任務單元爲 sched_entity
* <s> time_slice 是恐龍書中對作業系統的最小單元,但對 scheduler 最小的單元是 sched_entity,而沒有傳統意義上的 time_slice,而是由 vruntime 去決定執行 </s>
:::danger
核實上方說法
:::
https://yarchive.net/comp/linux/light_weight_processes.html
* 第一 task_struct 過於笨重上千行,<s>排序</s> 要存取這麼沉重的東西是個愚蠢的設計,而 sched_enitity 是 task_struct 的一個 member
* 還有不以 task 爲最小單位的最重要的關鍵是 task group ,如果有50個 task , User1 有1個,User49 有49個,這樣實際上 User49 佔用了98%的 CPU 資源
:::warning
:warning: FOLLOW-UP: Linux 的 scheduling entity 要考慮到 CPU topology 和 group scheduling,搭配應用場景予以解說
:::
拓撲:在數學中,它研究空間在連續變化下(如拉伸、彎曲但不撕裂)保持不變的性質
傳統 cfs 建立 在 SMP 上,但對於非 SMP 的架構有響應的擴充,例如,CPU topology 的例子有大小核, 對應的處理方案, CPU Capacity 這個參數。如果大核能力100小核60,一個被評估80分的任務就該優先給大核
>CPU capacity is a number that provides the scheduler information about CPUs
heterogeneity.
這指出 scheduling entity 在排程時有使用到 CPU 資訊
>Cpusets provide a mechanism for assigning a set of CPUs and Memory Nodes to a set of tasks. In this document “Memory Node” refers to an on-line node that contains memory.Cpusets constrain the CPU and Memory placement of tasks to only the resources within a task’s current cpuset. They form a nested hierarchy visible in a virtual file system. These are the essential hooks, beyond what is already present, required to manage dynamic job placement on large systems.
>開發人員會在 cgroups.json 檔案中說明 cgroup 設定,定義 cgroup 集,以及其掛接位置和屬性。所有 cgroup 都會在初始化程序的早期初始化階段掛接。工作設定檔。這些介面提供抽象化功能,可將必要功能與實作詳細資料分離https://source.android.com/docs/core/perf/cgroups?hl=zh-tw
Android 會先透過 cgroup/cpuset 將任務分類,限制其可執行的 CPU 範圍;因此 scheduler 在為該 scheduling entity 挑選 CPU 時,並不是對所有 CPU 做選擇,而是只在其允許的 CPU 集合內,結合 topology 與負載資訊做決策。
>./init.hardware.rc
# Setup final cpuset
write /dev/cpuset/top-app/cpus 0-7
write /dev/cpuset/foreground/boost/cpus 0-3,6-7
write /dev/cpuset/foreground/cpus 0-3,6-7
write /dev/cpuset/background/cpus 0-1
write /dev/cpuset/system-background/cpus 0-3
write /dev/cpuset/restricted/cpus 0-3
在排程時,scheduler 並不是只看 entity 的執行時間或權重,還必須考慮 CPU topology 與群組限制。
---
### max0305
- [ ] clone 傳入什麼參數區隔 process 及 thread?
:::warning
區分 spec (規格) 和 man-pages (手冊)
:::
spec: 定義系統或軟體應該如何設計與運作,確保跨平台與跨版本的相容性。
man-pages: 記錄目前系統上已安裝的<s>指令</s> 命令、系統呼叫、函式庫或設定檔實際如何使用的操作說明。
:::danger
區分「命令」(command) 和「指令」(instruction),注意細節!
:::
linux 並未明確區隔兩者 process 及 thread,而是透過傳入旗標來決定資源共享的程度,根據 <s>規格書</s> man-pages 中 `clone()` 的原型
```c
int clone(typeof(int (void *_Nullable)) *fn,
void *stack,
int flags,
void *_Nullable arg, ...
/* pid_t *_Nullable parent_tid,
void *_Nullable tls,
pid_t *_Nullable child_tid */ );
```
可以發現旗標以整數型態傳入。
- process:`fork()` 會經過封裝,最終以特定參數觸發 clone 系統呼叫。根據 `sysdeps/unix/sysv/linux/arch-fork.h` 中定義的旗標
```c
const int flags = CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD;
```
此組合說明不共享資源,僅在子行程結束時發送 SIGCHLD 訊號並於其生命週期的起訖自動寫入與清除使用者空間的執行緒 TID。
- thread:`/nptl/pthread_create.c` 使用在 `/sysdeps/unix/sysv/linux/createthread.c` 中定義的 `thread_create` ,觀察其中傳入的旗標
```C
const int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM
| CLONE_SIGHAND | CLONE_THREAD
| CLONE_SETTLS | CLONE_PARENT_SETTID
| CLONE_CHILD_CLEARTID
| 0);
```
代表共享了虛擬記憶體空間、檔案系統、檔案描述符、System V Semaphore 等等。
:::danger
依據 https://hackmd.io/@sysprog/linux-process 進行實驗,來確認上述說法,而非只是臆測
:::
- 實驗
- 解析系統呼叫參數:
撰寫程式呼叫 `fork()`,並使用 `strace` 攔截系統呼叫,並過濾 `clone`
```
strace -e trace=clone ./test_fork
```
輸出如下
```
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7252d679fa10) = 72983
```
可以發現參數 `flags` 中明確包含 `CLONE_CHILD_CLEARTID`、`CLONE_CHILD_SETTID` 與 `SIGCHLD`。並且系統呼叫回傳值大於 0(尾端 = 47438,代表建立行程的 PID)。
再測試建立 thread
```c
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
```
輸出如下
```
clone3({flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, child_tid=0x7252d63ff910, parent_tid=0x7252d63ff910, exit_signal=0, stack=0x7252d5bff000, stack_size=0x7fff00, tls=0x7252d63ff640} => {parent_tid=[72984]}, 88) = 72984
```
與上方 `thread_create` 中傳入的旗標相同。
- 驗證資源隔離與共享
- `CLONE_VM`:將虛擬記憶體位址轉換為實體記憶體 PFN([參考](https://github.com/ccurtsinger/get-pfn))並驗證 process 與 thread 的記憶體共享狀況。
```c
/* Process (without CLONE_VM) */
pid_t pid = fork();
if (pid == 0) {
printf("[Process] Child PFN BEFORE write: 0x%lx (Value: %d)\n", get_pfn(shared_var), *shared_var);
*shared_var = 200; /* Trigger Copy-on-Write */
printf("[Process] Child PFN AFTER write: 0x%lx (Value: %d)\n", get_pfn(shared_var), *shared_var);
_exit(0);
} else {
wait(NULL);
printf("[Main] PFN AFTER Child exit : 0x%lx (Value: %d)\n\n", get_pfn(shared_var), *shared_var);
}
```
```c
void *thread_vm_func(void *arg) {
int *val = (int *)arg;
printf("[Thread] PFN Before write: 0x%lx (Value: %d)\n", get_pfn(val), *val);
*val = 300; /* Trigger write */
printf("[Thread] PFN AFTER write: 0x%lx (Value: %d)\n", get_pfn(val), *val);
return NULL;
}
/* Thread (with CLONE_VM) */
pthread_t tid;
pthread_create(&tid, NULL, thread_vm_func, shared_var);
pthread_join(tid, NULL);
printf("[Main] PFN AFTER Thread join : 0x%lx (Value: %d)\n\n", get_pfn(shared_var), *shared_var);
```
輸出如下
```c
[Main] Initial PFN : 0x49d352 (Value: 100)
[Process] Child PFN BEFORE write: 0x49d352 (Value: 100)
[Process] Child PFN AFTER write: 0x456b59 (Value: 200)
[Main] PFN AFTER Child exit : 0x49d352 (Value: 100)
[Thread] PFN Before write: 0x49d352 (Value: 100)
[Thread] PFN AFTER write: 0x49d352 (Value: 300)
[Main] PFN AFTER Thread join : 0x49d352 (Value: 300)
```
可以發現因為 Process 因為 CoW 機制,在寫入前與親代進程映射到同一個 PFN,但寫入過後即映射到不同位址;Thread 則與親代進程共用同一實體記憶體位址。
- `CLONE_FS`:讀取 `/proc/self/cwd` 符號連結
```c
/* Process (without CLONE_FS) */
pid_t pid = fork();
if (pid == 0) {
chdir("/usr");
get_proc_cwd(cwd, sizeof(cwd));
printf("[Process] Child CWD changed to: %s\n", cwd);
_exit(0);
} else {
wait(NULL);
get_proc_cwd(cwd, sizeof(cwd));
printf("[Main] CWD AFTER Child : %s\n\n", cwd);
}
```
```c
void *thread_fs_func(void *arg) {
chdir("/tmp");
char cwd[PATH_MAX];
get_proc_cwd(cwd, sizeof(cwd));
printf("[Thread] CWD changed to : %s\n", cwd);
return NULL;
}
/* Thread (with CLONE_FS) */
pthread_t tid;
pthread_create(&tid, NULL, thread_fs_func, NULL);
pthread_join(tid, NULL);
get_proc_cwd(cwd, sizeof(cwd));
printf("[Main] CWD AFTER Thread: %s\n\n", cwd);
```
輸出如下
```
[Main] Initial CWD : /var
[Process] Child CWD changed to: /usr
[Main] CWD AFTER Child : /var
[Thread] CWD changed to : /tmp
[Main] CWD AFTER Thread: /tmp
```
可以發現 Process 切換目錄後不會影響親代進程的目錄,代表分配獨立的 `fs_struct`;Thread 則與親代進程共享同一 `fs_struct`。
- `CLONE_FILES`:使用 [kcmp](https://man7.org/linux/man-pages/man2/kcmp.2.html) 比對兩個行程的結構指標,根據手冊若回傳 `0` 代表兩者在記憶體中指向同一個實體結構;若回傳大於 0 則代表兩者指向不同的記憶體位址(資源不共享)。
```c
/* Process (without CLONE_FILES) */
pid_t pid = fork();
if (pid == 0) {
_exit(0);
} else {
/* Compare files_struct of parent and child */
int cmp_res = sys_kcmp(parent_tid, pid, KCMP_FILES, 0, 0);
printf("[Process] kcmp(KCMP_FILES) = %d -> %s (Isolated files_struct)\n",
cmp_res, cmp_res == 0 ? "Shared" : "Different");
wait(NULL);
}
```
```c
void *thread_files_func(void *arg) {
struct thread_sync *sync = (struct thread_sync *)arg;
sync->tid = sys_gettid();
while (!sync->parent_done) { usleep(1000); } /* Wait for kcmp */
return NULL;
}
/* Thread (with CLONE_FILES) */
struct thread_sync sync = {0, 0};
pthread_t tid;
pthread_create(&tid, NULL, thread_files_func, &sync);
while (sync.tid == 0) { usleep(1000); } /* Wait for thread init */
int cmp_res = sys_kcmp(parent_tid, sync.tid, KCMP_FILES, 0, 0);
printf("[Thread] kcmp(KCMP_FILES) = %d\n\n", cmp_res);
sync.parent_done = 1;
pthread_join(tid, NULL);
```
輸出如下
```
[Process] kcmp(KCMP_FILES) = 1
[Thread] kcmp(KCMP_FILES) = 0
```
說明 Process 並未共享檔案描述子表(file descriptor table);Thread 則共享。
- `CLONE_SIGHAND`:與 `CLONE_FILES` 相似,使用 `kcmp` 驗證子進程是否指向同一信號處理函式表(table of signal handlers)
```c
/* Process (without CLONE_SIGHAND) */
pid_t pid = fork();
if (pid == 0) {
_exit(0);
} else {
int cmp_res = sys_kcmp(parent_tid, pid, KCMP_SIGHAND, 0, 0);
printf("[Process] kcmp(KCMP_SIGHAND) = %d\n", cmp_res);
wait(NULL);
}
```
```c
void *thread_sighand_func(void *arg) {
struct thread_sync *sync = (struct thread_sync *)arg;
sync->tid = sys_gettid();
while (!sync->parent_done) { usleep(1000); } /* Wait for kcmp */
return NULL;
}
/* Thread (with CLONE_SIGHAND) */
struct thread_sync sync = {0, 0};
pthread_t tid;
pthread_create(&tid, NULL, thread_sighand_func, &sync);
while (sync.tid == 0) { usleep(1000); } /* Wait for thread initialization*/
int cmp_res = sys_kcmp(parent_tid, sync.tid, KCMP_SIGHAND, 0, 0);
printf("[Thread] kcmp(KCMP_SIGHAND) = %d\n\n", cmp_res);
sync.parent_done = 1;
pthread_join(tid, NULL);
```
輸出如下
```
[Process] kcmp(KCMP_SIGHAND) = 2
[Thread] kcmp(KCMP_SIGHAND) = 0
```
說明 Process 並未共享信號處理函式表;Thread 則共享。
- `CLONE_THREAD`:透過 [getpid](https://man7.org/linux/man-pages/man2/getpid.2.html) 與 [gettid](https://man7.org/linux/man-pages/man2/gettid.2.html) 取得TGID 與 TID 分配
```c
/* Process (without CLONE_THREAD) */
pid_t pid = fork();
if (pid == 0) {
pid_t child_pid = sys_getpid();
pid_t child_tid = sys_gettid();
printf("[Process] Kernel PID (TGID): %d, Kernel TID: %d\n", child_pid, child_tid);
_exit(0);
} else {
wait(NULL);
}
```
```c
void *thread_thread_func(void *arg) {
struct thread_sync *sync = (struct thread_sync *)arg;
pid_t my_pid = sys_getpid();
pid_t my_tid = sys_gettid();
printf("[Thread] Kernel PID (TGID): %d, Kernel TID: %d\n", my_pid, my_tid);
sync->parent_done = 1;
return NULL;
}
/* Thread (with CLONE_THREAD) */
struct thread_sync sync = {0, 0};
pthread_t tid;
pthread_create(&tid, NULL, thread_thread_func, &sync);
while (!sync.parent_done) { usleep(1000); }
pthread_join(tid, NULL);
```
輸出以下
```
[Main] Kernel PID (TGID): 83628, Kernel TID: 83628
[Process] Kernel PID (TGID): 83637, Kernel TID: 83637
[Thread] Kernel PID (TGID): 83628, Kernel TID: 83638
```
Process 的 `PID` 與 `TID` 相同,但與親代行程不同,成為新的 Thread Group Leader;Thread 的 `PID` 與親代行程相同,但 `TID` 不同,代表被歸納於親代的 Thread Group 中。
- [ ] 以甚麼方式傳入?
根據<s>規格書</s> man-pages 中對於 The flags mask 的說明
> This bit mask—the _flags_ argument of _clone()_ or the _cl_args.flags_ field passed to _clone3()_
> The flags mask is specified as a bitwise OR of zero or more of the
constants listed below. [...]
旗標以位元遮罩的方式傳入,並且使用 `OR` 運算來達成傳入多個旗標,因此可以想像每個位元欄帶有不同意義。例如以下幾個在 `/include/uapi/linux/sched.h` 中定義的旗標常數:
```c
#define CLONE_VM 0x00000100 /* set if VM shared between processes */
#define CLONE_FS 0x00000200 /* set if fs info shared between processes */
#define CLONE_FILES 0x00000400 /* set if open files shared between processes */
#define CLONE_SIGHAND 0x00000800 /* set if signal handlers and blocked signals shared */
#define CLONE_PIDFD 0x00001000 /* set if a pidfd should be placed in parent */
#define CLONE_PTRACE 0x00002000 /* set if we want to let tracing continue on the child too */
#define CLONE_VFORK 0x00004000 /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_PARENT 0x00008000 /* set if we want to have the same parent as the cloner */
```
若使用系統呼叫 `clone()` 搭配 `CLONE_VM` 做為參數,新建立的 task 的 `mm` 會指向與其親代相同的 `mm_struct` 實體,使其 address space 與親代共享,並且在該
共用的 `mm_struct` 實體中以 `mm_users` 紀錄共用者數量。
:::danger
依據 https://hackmd.io/@sysprog/linux-process 進行實驗,來確認上述說法,而非只是臆測
:::
`clone()` 系統呼叫由 `kernel_clone()` 啟動,定義於 [`kernel/fork.c`](https://github.com/torvalds/linux/blob/v6.18/kernel/fork.c):
```c
pid_t kernel_clone(struct kernel_clone_args *args)
{
...
p = copy_process(NULL, trace, NUMA_NO_NODE, args);
...
}
```
`copy_process()` 內部呼叫 `copy_mm()`。若帶有 `CLONE_VM` 參數,新 task 的 `mm` 直接沿用親代的 `mm`;否則透過 `dup_mm()` 複製一份獨立副本。以下為 v6.18 的 `copy_mm()`:
```c
static int copy_mm(u64 clone_flags, struct task_struct *tsk)
{
struct mm_struct *mm, *oldmm;
tsk->min_flt = tsk->maj_flt = 0;
tsk->nvcsw = tsk->nivcsw = 0;
tsk->mm = NULL;
tsk->active_mm = NULL;
oldmm = current->mm;
if (!oldmm)
return 0;
if (clone_flags & CLONE_VM) {
mmget(oldmm);
mm = oldmm;
} else {
mm = dup_mm(tsk, current->mm);
if (!mm)
return -ENOMEM;
}
tsk->mm = mm;
tsk->active_mm = mm;
sched_mm_cid_fork(tsk);
return 0;
}
```
相較早期版本,Linux v6.18 的 `copy_mm()` 有兩處值得留意的差異:參數型別從 `unsigned long` 改為 `u64`,以及尾端的 `sched_mm_cid_fork(tsk)` (v6.6 起新增) 負責初始化排程器所需的 per-mm concurrency ID。
參考資料: [Understanding the Linux Virtual Memory Manager](https://www.kernel.org/doc/gorman/html/understand/understand007.html)
### sakinu
Q:同一個 process 內有多個 thread 時,`getpid` 的行為是什麼?結果會一樣嗎
> 上學期修平行程式設計時有做過類似實驗,答案是 YES,也符合原本作業系統學到的 process 和 thread 的關係。但是思考了一下新學到的知識,process 和 thread 其實都是 task_struct,那我原本想像他們應該就都要有一套可以分別的 ID 而這個 ID 就是 PID,這無法解釋先前為何 PID 會一樣的實驗結果。
:::danger
不要說「印象中」「不太能」這種含糊的話語,理工人說話要精準。注意 TGID
:::
:::danger
注意用語!
詳閱 https://hackmd.io/@sysprog/it-vocabulary
尊重台灣資訊科技前輩的篳路藍縷!!!!!!!!!!!
:::
以下程式利用 `pthread` 在同一 process 內產出兩個 thread,並輸出 `getpid` 的結果
```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>
void *child(void *arg) {
int *input = (int *) arg;
pid_t pid = getpid();
printf("child pid = %d\n", pid);
pthread_exit(NULL);
}
int main() {
pthread_t t1, t2;
int input;
pthread_create(&t1, NULL, child, (void*) input);
pthread_create(&t2, NULL, child, (void*) input);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("main pid = %d\n", getpid());
return 0;
}
```
會輸出一樣的 PID。
發現在 linux [kernel/sys.c](https://github.com/torvalds/linux/blob/master/kernel/sys.c) 提到:
```c
/**
* sys_getpid - return the thread group id of the current process
*
* Note, despite the name, this returns the tgid not the pid. The tgid and
* the pid are identical unless CLONE_THREAD was specified on clone() in
* which case the tgid is the same in all threads of the same group.
*/
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
```
這代表 `getpid` 實際上得到的是 TGID (thread group ID),而非核心內部的 PID。`task_tgid_vnr` 定義於 [`include/linux/pid.h`](https://github.com/torvalds/linux/blob/v6.18-/include/linux/pid.h):
```c
static inline pid_t task_tgid_vnr(struct task_struct *tsk)
{
return __task_pid_nr_ns(tsk, PIDTYPE_TGID, NULL);
}
```
此函式以 `PIDTYPE_TGID` 查詢目前 namespace 下的 thread group ID。由於 `pthread_create` 透過 `clone()` 並帶入 `CLONE_THREAD` flag,同一 process 內所有 thread 共享相同的 TGID,因此 `getpid()` 在任何 thread 中都回傳相同的值。
這反映 Linux 核心中 PID 與 TGID 的語意分離:
- 核心內部的 `task_struct->pid`: 每個 thread 獨有,用於核心排程與管理
- 核心內部的 `task_struct->tgid`: 同一 thread group 共享,等於 group leader 的 `pid`
- user space 的 `getpid()`: 回傳 TGID,符合 POSIX 語意 (同一 process 內所有 thread 看到相同的 process ID)
- user space 的 `gettid()`: 回傳核心內部的 PID,即 `task_pid_vnr(current)`
[CPU Scheduling of the Linux Kernel](https://docs.google.com/presentation/d/1qDSFOfhGF-AX3O8CNzoAkQr_5ohr0nMVDlNfPnM9hOI/) 歸納:
- 每個 thread 擁有一個 PID
- 數個 thread 組成一個 thread group,該 group 擁有一個 TGID。其中 thread group leader 的 PID 等於 TGID,此 thread group 即為 process
- 數個 process 組成一個 process group,該 group 擁有一個 PGID。Process group leader 必為 thread group leader,其 PID 等於 PGID
- 數個 process group 組成一個 login session,對應 SID
POSIX 所謂的 "process ID" 對應 Linux 核心的 TGID,而 POSIX 所謂的 "thread ID" (透過 `gettid()` 取得) 對應 Linux 核心的 PID。這個命名上的錯位源自歷史因素:Linux 最初沒有 thread 的概念,`pid` 就是 process 的唯一識別碼;後來引入 thread group 時,為了維持 user space API 的相容性,`getpid()` 改為回傳 TGID,而非重新設計整套介面。
:::warning
:warning: FOLLOW-UP: 如何在 Linux 觀察 TGID 並分析其排程?
:::
最簡單的方式是用 `top` 或查看 `/proc` 等等方法,但是這不夠好,只能看到當下的狀態且難以量化。
:::danger
依據 https://hackmd.io/@sysprog/linux-process 進行實驗,務必量化,和建立對應的數學模型並驗證
:::
### jeremylu0830
- clone 傳入什麼參數區隔 process 及 thread?
:::warning
:warning: 在 Linux 進行實驗並提出分析方式
:::
Linux 核心中 thread 與 process 的差異,本質上就是一組 `clone()` flag 的選擇:
- `CLONE_VM`:共享 address space (即共用 `mm_struct`)
- `CLONE_THREAD`:加入同一 thread group (共用 TGID)
- `CLONE_SIGHAND`:共享 signal handler 表
- `CLONE_FILES`:共享 file descriptor 表
- `CLONE_FS`:共享檔案系統資訊 (root、cwd、umask)
glibc 的 `pthread_create` 在呼叫 `clone()` 時同時帶入上述所有 flag,使得同一 process 內的 thread 共享記憶體、signal handler、file descriptor 和檔案系統上下文。`fork()` 則不帶這些 flag,因此子行程獲得獨立的副本。
Linux 核心仍有區別 process 與 thread 的概念,但 process 可視為 thread 的容器。一個 process 至少包含一個 thread,這些 thread 彼此共享 address space,僅有 CPU 暫存器 (context) 及 stack 是各 thread 獨立持有的。
以 CPU 排程的觀點,不論 thread 或 process 都是可排程的 `task_struct`。Process leader 擁有自己的 `mm_struct`,同一 thread group 內的 thread 則共享該 `mm_struct`。
### wenny2377
#### 實驗環境
- OS:Windows 11 + WSL2 (Ubuntu 22.04)
- Kernel:6.6.87.2-microsoft-standard-WSL2
- GCC:11.4.0
Week7 隨堂測驗影片檢討:
> 影片說:「`fork()` 複製 process,產生子行程,共享 address space」
> 正確:`fork()` 產生子行程,子行程有獨立的 address space,共享 address space 的是 thread
:::danger
依據 https://hackmd.io/@sysprog/linux-process 進行實驗,來確認上述說法
:::
---
- [ ] `fork()` 與 address space、MMU 的關係?
Ans:
`fork()` 產生獨立的 address space。每個 process 有自己的虛擬
address space,MMU 負責將虛擬位址對應到實體位址。`fork()` 後
子行程有獨立的頁表(page table),兩個 process 虛擬位址可以相同,
但 MMU 對應到不同實體記憶體
```
parent process → page table A → MMU → 實體記憶體 A
child process → page table B → MMU → 實體記憶體 B
```
實際上採用 copy-on-write,寫入時才真正複製,為節省記憶體
實驗:觀察 `fork()` 後 address space 是否獨立
```c
#include
#include
int global = 100;
int main() {
pid_t pid = fork();
if (pid == 0) {
global = 999;
printf("child process: pid=%d, &global=%p, global=%d\n",
getpid(), (void*)&global, global);
} else {
sleep(1);
printf("parent process: pid=%d, &global=%p, global=%d\n",
getpid(), (void*)&global, global);
}
return 0;
}
```
```
child process: pid=2011, &global=0x632c7d863010, global=999
parent process: pid=2010, &global=0x632c7d863010, global=100
```
兩個 process 的虛擬位址 `&global` 相同(`0x632c7d863010`),但值不同。child process 將 `global` 改為 999,parent process的值仍為 100,確認 `fork()` 後 address space 獨立。虛擬位址相同是因為每個 process 各有自己的頁表,MMU 將相同虛擬位址對應到不同實體記憶體
---
- [ ] Linux 核心如何描述 process 和 thread?
Ans:
Linux 核心中,process 和 thread 都用 `task_struct` 來描述,沒有獨立的「thread 結構」,統一用 `task_struct` 表示一個任務
:::danger
依據 https://hackmd.io/@sysprog/linux-process 進行實驗,來確認上述說法,而非只是臆測
:::
實驗:透過 `/proc` 觀察每個 thread 對應一個 `task_struct`
```c
#include
#include
#include
#include <sys/syscall.h>
void *worker(void *arg) {
sleep(30);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, worker, NULL);
pthread_create(&t2, NULL, worker, NULL);
printf("Main PID: %d\n", getpid());
pthread_join(t1, NULL);
return 0;
}
```
```bash
#terminal 1
gcc -o threads threads.c -lpthread
./threads &
#terminal 2
PID=$(pgrep threads)
ls /proc/$PID/task/
for tid in /proc/$PID/task/*; do
echo "--- Task Directory: $tid ---"
grep -E "^(Pid|Tgid)" $tid/status
done
```
```
--- Task Directory: /proc/852/task/852 ---
Tgid: 852
Pid: 852
--- Task Directory: /proc/852/task/853 ---
Tgid: 852
Pid: 853
--- Task Directory: /proc/852/task/854 ---
Tgid: 852
Pid: 854
```
`/proc/<pid>/task/` 下有三個目錄,對應三個 `task_struct`。
每個 thread 的 `Pid` 各不相同,但 `Tgid` 全部相同,確認kernel 以 `task_struct` 統一描述 process 與 thread,無獨立的 thread 結構。
參考:
- https://elixir.bootlin.com/linux/latest/source/include/linux/sched.h
---
- [ ] Lock 是什麼?為何需要?
Ans:
Lock 是為了保護執行中 process / thread 共享的 resource
實驗:開了 2 個 terminal 同時編輯同一個 vim 檔案
```bash
# terminal 1
vim test.txt
# terminal 2
vim test.txt
```
terminal 2 會出現:
```
TODO : warning
```
因為 read / write 來自不同 process,存在競爭問題,這就是為何需要 lock 機制
---
- [ ] `clone()` 與 thread 的關係?`fork()` 與 `clone()` 有何差異?
Ans:
Linux 建立 thread 實際上是呼叫 `clone()`,可以控制哪些資源要
共享,哪些要獨立? `fork()` 本質上也是 `clone()`,但參數不同
| | address space | 實作 |
|---|---|---|
| `fork()` | 獨立 | `clone()` 不帶 `CLONE_VM` |
| `thread` | 共享 | `clone()` 帶 `CLONE_VM` |
參考 Linux 核心原始碼 `kernel/fork.c` 中的 `copy_mm()`:
```c
if (clone_flags & CLONE_VM) {
mmget(oldmm);
mm = oldmm; /* thread:共享同一份 mm_struct */
} else {
mm = dup_mm(tsk, current->mm); /* process:複製獨立副本 */
if (!mm)
return -ENOMEM;
}
```
:::danger
注意用語!
* "command" 是「命令」,而非「指令」(instruction)
:::
<s>查詢指令</s>:
```bash
man 2 clone # 搜尋 /CLONE_THREAD
```
`man 2 clone` 中關於 `CLONE_THREAD` 的官方說明:
```
CLONE_THREAD (since Linux 2.4.0)
If CLONE_THREAD is set, the child is placed in the same thread
group as the calling process. Thread groups were a feature added
in Linux 2.4 to support the POSIX threads notion of a set of
threads that share a single PID.
If CLONE_THREAD is set, so must CLONE_SIGHAND.
If CLONE_THREAD is set, the child's thread group ID (tgid) is
the same as the parent's.
```
:::danger
注意用語,詳細閱讀 https://hackmd.io/@sysprog/linux-rbtree
> 避免父權主義的遺毒,本文將 parent node 翻譯為「親代節點」,而非「父節點」或「母節點」,不僅更中性,也符合英文原意。若寫作「父」,則隱含「母」的存在,但以二元樹來說,沒有這樣成對關連性。若用「上代」會造成更多的混淆,在漢語中,「上一代」沒有明確的血緣關係 (例如「炎黃子孫」與其說是血緣關係,不如說是傾向文化認同),但「親」的本意就是指名血緣和姻親關係。
:::
<s>重點:</s>
- `CLONE_THREAD` 讓新 task 加入同一個 thread group
- 設定 `CLONE_THREAD` 必須同時設定 `CLONE_SIGHAND`
- `CLONE_THREAD` 讓子 task 與<s>父</s> task 共享同一個 `tgid`
:::danger
依據 https://hackmd.io/@sysprog/linux-process 進行實驗,來確認上述說法,而非只是臆測
:::
---
- [ ] 為何需要 `tgid` 的設計?
Ans:
`tgid` 的設計目的是為了排程器(scheduler),讓排程器知道哪些
thread 屬於同一個 process,作為排程的依據
參考 `man 7 credentials`,Session / Process Group / Thread 的關係:
```
Session
└── Process Group
├── [Process/tgid]
│ ├── thread (pid=100, tgid=100) ← main thread
│ ├── thread (pid=101, tgid=100)
│ └── thread (pid=102, tgid=100)
└── [Process/tgid]
├── thread
└── thread
```
- **Session**:最外層,通常對應一個 terminal
- **Process Group**:一組相關的 process
- **Process(tgid)**:每個方塊,裡面包含多個 thread
- `tgid` 的目的:讓排程器識別同一個 process 下的 thread 群組
:::danger
依據 https://hackmd.io/@sysprog/linux-process 進行實驗,驗證上述說法
:::
實驗:觀察 `getpid()` 回傳 `tgid` 而非 kernel pid
```c
#include
#include
#include <sys/syscall.h>
#include
void *worker(void *arg) {
printf("Thread [getpid()=%d, gettid()=%ld]\n",
getpid(), syscall(SYS_gettid));
return NULL;
}
int main() {
pthread_t t;
printf("Main [getpid()=%d, gettid()=%ld]\n",
getpid(), syscall(SYS_gettid));
pthread_create(&t, NULL, worker, NULL);
pthread_join(t, NULL);
return 0;
}
```
```
Main [getpid()=1001, gettid()=1001]
Thread [getpid()=1001, gettid()=1002]
```
main thread 的 `getpid()` 與 `gettid()` 相同;worker thread 的`getpid()` 回傳的仍是 main thread 的 pid,即 `tgid`,確認`getpid()` 在核心層回傳的是 `tgid`
參考 Linux 核心 `kernel/sys.c`:
```c
/**
* sys_getpid - return the thread group id of the current process
* Note, despite the name, this returns the tgid not the pid.
*/
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
```
:::danger
command 是「命令」,而非「指令」(instruction),重視細節!
:::
查詢<s>指令</s>:
```bash
man 7 credentials
```
---
### hubertwyc
- context switch需要儲存什麼資料?
要先考慮stackful或是stackless
如果是stackful,要存cpu registers,program counter,stack pointer
如果是stackless就不需要存stack pointer
:::danger
避免只是武斷地列出片面答覆,實際 coroutine 的設計考量是什麼?該在什麼場景使用 stackless、該在何種場景使用 stackful 呢?務必詳閱「並行程式設計: 排程器原理」(及解說錄影),並依循教材作為出發,明確探討,附上 PoC
:::
- 什麼是<s>搶佔式</s>排程?
在有優先權的系統中,每一個行程都有其優先權,若該系統採用可搶佔的排程策略,即代表正在運行中的行程可能被中斷執行。可能被搶佔的情況是當有一個行程進入可執行的狀態,其優先權高於正在運行的行程,即有可能觸發搶佔。
:::danger
是「搶佔」,而非「搶占」
> [Linux 核心搶佔](https://hackmd.io/@sysprog/linux-preempt)
> 查閱辭海,得知「占」和「佔」的差異
:::