or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
2025-04-15 問答簡記
你們已經把老師變得可被取代
在人工智慧全面進入知識工作的年代,大學教育所面對的,不只是工具的變遷,更是關於教育本質的拷問。最關鍵的問題是:我們究竟如何定義「學會」?
長久以來,高等教育在制度上預設「學會」等同於「能再現知識」、「能解出問題」、「能通過考核」;而「學得好」則是「準確率高、輸出速度快、能應付複雜題型」。這樣的標準在工業化與標準化知識傳遞的背景下或許曾經合理,但在 AI 可隨時補足知識缺口、協助產出內容的今日,這些定義已顯得蒼白無力。
學生現在只需輸入幾個關鍵字,便能產生程式碼、摘要論文、建構報告。這是否意味他們已「學會」?若答案是肯定的,那教育的任務很可能已經完成,也該準備退場。但我們深知並非如此,因為如此的「學會」,只是「知道怎麼產出」,卻不是「知道為何如此產出」,更不是「能從一個陌生問題建構解題方式」。
「學會」應該是能力的內化,也就是得以:
而人工智慧工具最多只能模擬上述過程,卻無法真正取代這種建立於直覺、試錯、記取教訓,與反思之上的知識提煉歷程。這也意味著,若我們仍沿用過去那套「學習 = 吸收知識 \(\to\) 解題 \(\to\) 應試」的教學與評量模式,那麼「學會」這件事將不可避免地被誤認為「讓 AI 幫我完成任務」就好。
更諷刺的是,當我們誤把這種輸出結果等同於學習成果,整個教育系統也在不自覺中強化「AI 能取代教育」的前提。這不是 AI 搶走教師的工作,而是我們用錯誤的「學會」定義,把教師逼進可取代的局面。
釐清「學會」的定義,是高等教育在人工智慧強勢變革時代的起點任務。我們必須讓「學會」重新指涉那些 AI 無法取代的能力 —— 理解混亂、定義問題、組織論述、承認模糊不清、等待不確定、整合情感與價值、連結真實處境並從中學習。
在此定義下,教育不再是知識的灌輸與測驗的設計,而是陪伴學生進行認知架構重建、價值判準磨合、以及思維模式的蛻變。大學不應是資訊最豐富的地方,而應是最容許提問的地方;不應是「學會知識」的終點,而應是「學會如何學」的起點。
換言之,「學會」的標準,不在於學生能否用 AI 寫出一份報告,而在於當 AI 給出結果時,學生是否能判斷其合理性、理解背後邏輯、並能提出比它更深刻、更符合脈絡的思考。唯有重新定義「學會」,我們才能為大學教育找回意義,為教師找回價值,為學生保留屬於人的學習方式。
出口管制計算能力
美國商務部於 2025 年 4 月 15 日公告,針對出口至中國的 NVIDIA H20、AMD MI308 及其他同等規格的 GPU,全面納入出口許可機制。換言之,業者必須取得美國政府核發的特別許可證,才能向中國供應此類高階晶片。NVIDIA 隨即公告將提列約 55 億美元的費用,反映這波政策變化所導致的庫存調整與訂單取消損失,凸顯中國市場對其 H20 晶片的重要性。
H20 為 NVIDIA 特別針對中國市場設計的「降規」版 GPU,其效能刻意設計在美國既有管制門檻以下,仍能支援大型語言模型 (LLM) 的推論與訓練需求,是 NVIDIA 嘗試在符合法規的邊界內維持中國市場佔有率的策略型產品。然而,美方最新禁令明確指出,即便是這種經過技術降階的設計,也在出口限制之列,無異於關閉過往可行的「合規繞道」路線。
此舉延續美國自 2022 年起針對 A100、H100 等頂級運算卡所啟動的技術封鎖,標誌全球正式進入以「晶片與算力」為核心的中美科技冷戰時代。美方已不再將 GPU 視為單純的商業零組件,而是升級為國防等級的戰略資源,並積極整合盟友力量建立封鎖網路。該網路涵蓋晶圓代工 (台灣 TSMC、韓國 Samsung)、光刻設備 (荷蘭 ASML)、AI 軟體與研究機構 (英國 DeepMind、歐洲 AI Act 框架) 等技術節點,形成跨國協作的出口限制體系。
在這場由國家力量主導的 AI 軍備競賽中,潛藏的風險也不容忽視。無論是中國出於迫切需求快速堆疊模型、或美國為保優勢而忽略安全審查,雙方皆可能加速邁向未經充分驗證的 AGI 系統,導致演算法外溢、社會操控風險擴大,甚至觸發失控的 AI 危機。美國內部亦有學者與機構發出警告,例如 AI 2027 網站便預測未來數年內可能出現無可控機制的超級智慧。
值得注意的是,美國近期更調查新加坡等轉運地區是否成為中國取得高階晶片的灰色通路,顯示供應鏈監管也將進一步向第三地延伸。
愛,在中斷之後 —— Top Half 與 Bottom Half 的交織
作為資訊系統的縮影,Linux 核心中始終上演著一場場無聲的愛戀 ──
沉默,卻深刻;無形,卻緊密連結。
他,是 Top Half ── 當中斷發生時,他總是首當其衝。
快、狠、準,他瞬間奪取 CPU 的控制權,果斷凍結一切,只為守護系統穩定運作。
而在喧囂過後,Bottom Half 靜靜現身,默默收拾他留下的未竟之務。
「你這般強行介入,會打亂排程的節奏。」
Bottom Half 將資料推入佇列,輕聲低語。
Top Half 回眸一笑,略帶歉意:「但若我不擋下,整個系統就會崩壞。你會接住我,對吧?」
他們從未並肩,卻總是一前一後地登場。
Top Half 居於高優先序、反應即時;
而 Bottom Half,則等待系統閒暇,在可延遲的時機被核心喚醒,完成那些「非即時但至關重要」的工作。
在 Linux 核心的精巧架構中,他們的分工彷若合奏 ──
Top Half 擋下第一波中斷衝擊,維持流程穩定;
Bottom Half 則承接後續處理,如搬移資料、解析封包、驅動裝置。
他們,是最契合的搭檔 ──
一人張揚果斷,一人內斂穩重。
無須同台演出,只求彼此所託,得以圓滿。
「這世界不僅有即時與非即時,還有我們之間,那段中斷與回應的牽繫。」
Bottom Half 如此思忖,當他再度啟動 softirq。
這不是 Boys' Love,卻是 Linux 核心裡,最深刻的 Bottom-Half Love。
EricccTaiwan
Linux 中斷處理的提問
Software interrupts and realtime
softirq 並非由硬體直接觸發,而是 kernel 的內部機制 (
tasklet_schedule
和add_timer
等 API) 來觸發,屬於 deffered work 的範圍。當 softirq 累積過多,會交給
ksoftirqd
處理,也代表 softirq 是由 kerenl 主動安排的,而非藉由硬體 IRQ 觸發。ksoftirqd
是可被排程器排程的 kernel thread 。 同時看到後綴d
(ksoftirq"d"),便是代表 daemon 。根據老師下方的
bpftrace
的實驗,可以看出 tasklet_func 就是交給 swapper 和 ksoftirqd 處理,swapper 代表 CPU 處於 idle ,可以立即處理 softirq (同時具有 atomic 性質); 而當 CPU 無法馬上處理 tasklet_func 時,會將任務由 ksoftirqd 延後處理。說明了,softirq 與 hardware IRQ 是兩個不同的執行層級 (execution context) , softirq 的優先權高於一般的 process context,可以搶佔正在執行的 process context ,但其優先權仍低於 hardirq , 無法搶佔或中斷 hardirq context。
可以進一步觀察
do_softirq
的開頭邏輯,這表示當前若處於 interrupt context 中,
do_softirq()
會直接返回、不執行任何的 softirq handler。其中的
in_interrupt
巨集,定義在 <linux/preempt.h>根據註解與定義,in_interrupt() 為真時,代表當前執行於下列其中一種中斷上下文:
local_bh_disable
)因此這段邏輯間接證明:
而系統若處於 process context , 且有 softirq 被標記為 pending , 就會透過
do_softirq()
或ksoftirqd
將其插入執行,實現 softirq 搶佔 process context。top 和 bottom half 劃分依據 : 工作即時性和重要性
Top half 通常指的是 硬體中斷處理函式 (IRQ handler) ,像是時間中斷或 DMA 完成中斷這類事件,具有明確的即時性要求。這些中斷處理過程中常會暫時關閉其他中斷,以保證處理過程的 atomic 與正確性。例如必須立即讀寫 interrupt controller 的暫存器,避免資訊丟失。
但如果在 top half 執行過多的動作 (例如:分配新的 buffer, 將資料從 DMA 暫存區複製到另一塊記憶體),這些動作可能花費過長時間、觸發 sleep (找不到 buffer),都會導致其他中斷 (例如:時間中斷、UART 要接 Rx 的鍵盤中斷) 無法即時回應和處理,造成系統 tick 不準或延遲輸入。
top 和 bottom half 的工作分配
因此區分的好處在於,top half 只做最 time-critical 的事情 (例如清 hw irq、讀取暫存器),其餘非即時、較耗時的工作延遲到 bottom half 去做 (例如:資料搬移、buffer 分配) ,一來能維持系統的即時行,也能減少中斷執行時間,並以 deffered work 進行工作分配的模組化。
「以是否屏蔽中斷作為區分依據」
Top Half 關閉中斷是因為處理某些操作 (如讀取中斷來源暫存器) 需要保證不被打斷,屬於對「正確性」的保護,而非定義上的必要條件。相對地,Bottom Half 雖然通常允許中斷打斷,但是否關閉中斷仍取決於開發者的需求,例如在存取共享資源時。
以
tasklet_action_common
為例,Tasklet 的 linked list 取出階段有短暫關中斷 (line 3~5),因此 tasklet 本身是可屏蔽中斷。
結論 : 不能單靠「中斷是否開啟」來區分 Top / Bottom Half
Context Switch Definition by The Linux Information Project (LINFO)
首先,要先理解 context 一詞的意義,在 CPU 運作中,「context」指的是一個 process 當下執行狀態的完整資訊,包含 CPU 暫存器 (registers)、 program counter (或 instruction pointer) 、 stack pointer 等,在進行 context switch 之前,必須暫停目前執行的 process,並將此 process 的 context 儲存,這些資訊可以想像成是 process 的「前情提要」。
OSDI Process Management
當 CPU 接收到 interrupt 時,會暫停目前執行的 process、儲存該 process 的 context,隨後切換至 interrupt context 來處理中斷事件,這個切換動作即稱為 context switch 。 當 interrupt context 結束後,再次進行 context switch ,切回 process context , CPU 從先前儲存的資料中恢復原本 process 的 context,繼續執行被中斷暫停的 process。
taslket 和 workqueue 關鍵差異在於「能否睡眠」,tasklet 是在 IRQ handler 執行完後的延續處理,但仍在 softirq/atmoic context 中完成,因為 tasklet 沒有相對應的
task_struct
,因此無法被 scheduler 排程。相對的, workqueue 是以
kworker
這類的 kernel thread 為基礎執行,是以 kthread_create_on_node() 建立的,其回傳的資料型態是 a pointer tostruct task_struct
,代表是可以完整排程的 process unit ,因此 workqueue 執行的 callback function ,運行在 process context 中,可以睡眠、可以被排程。對於 Interrupt/atomic context 與 process context 的不同,我想其中關鍵是能否能夠被排程器排程,這也是 interrupt context 不能進入 sleeping 的原因 – 前者倘若真的被 context switch,排程器不具備還其原本執行的狀態的能力。這也是為何說是 atomic: 一段程式的執行無法被排程器管理成片段的執行。
之所以需要 interrupt context,是因為排程器要能切換 context 是有前提的。舉一個顯然的例子,當要讀取 interrupt controller 的暫存器來知道中斷來源的時候,如果瞬間有另一個中斷發生,前一個中斷來源就無法被得知了(假設只能從單一暫存器得知,實際方式還是取決硬體設計)。
Atomic context and kernel API design 的文章也對 atomic context 做了明確定義:
"An atomic context is any situation where the current thread of execution is not allowed to sleep.",
意味著 atomic context 無法被排程,因為它沒有對應的
task_struct
,也不受 scheduler 管理。tasklet_vec.tail
推斷tasklet_vec
是鏈結串列,但卻找不到此結構體的定義\(\to\) 利用第 7 週介紹的 User-Mode-Linux (UML) 搭配 GDB 追蹤
kernel/softirq.c
由 tasklet_struct 所組成的一個單向鏈結串列。
Interrupt context
從這段話可以得知,當 CPU 正在處理硬體中斷 (Hardware IRQ) 或軟體中斷 (Softirq、Tasklet)時,處理該中斷的執行環境稱為 Interrupt context。
Atomic context
所謂 Atomic context 是指 kernel 執行在一種不可睡眠 (sleep) 且不可被搶佔 (preempt) 的執行狀態中。
這也說明了中斷處理 Interrupt context 本身就是 Atomic context 的一種。
此外,只要 kernel function 持有 spinlock,就會進入 Atomic context 。因為在持有 spinlock 的情況下若發生睡眠,可能會導致 deadlock 。
結論 :
Interrupt context 是 Atomic context 的一種特殊情況。兩者都具備「不可睡眠」與「不可被搶佔」的特性;然而,Atomic context 的範疇更為廣泛,除了中斷處理外,也包含持有 spinlock、禁用 preemption 等。
devarajabc
\(\to\) simrupt 整體中斷模擬行為,建構於 Linux 核心的 deferred work 機制之上,包含: (BH 表示 bottom half)
該核心模組中不存在任何屬於 top half (如硬體 IRQ handler)的處理流程,也不與 IRQ 子系統互動。
在
simrupt_init
函式中,初始化計時器:意味著
timer_handler
是本模組的計時器 callback,而且是唯一的 timer callback,該函式在TIMER_SOFTIRQ
的 context 中執行,因此屬於 BH。藉由呼叫process_data
,將 deferred work 委託給 tasklet 與 workqueue。模組並未與 Linux 核心 IRQ 子系統互動,也未呼叫 request_irq() 或 generic_handle_irq()。
\(\to\) top half 指的是硬體 IRQ handler,bottom half 則指後續延遲處理,不過 Linux 核心內的 bottom half 機制 (如 timer、tasklet、workqueue),都允許獨立使用,並不一定要搭配真實的硬體 IRQ top half。
亦即,bottom half 可獨立存在且運作,Linux 核心內部即可主動觸發 (例如 timer),不要求有實際的 top half 存在。
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →simrupt 就利用 timer → tasklet → workqueue 的流程,示範 bottom half 可獨立運作。
in_interrupt()
和in_softirq()
的差別是什麼? interrupt context 和 softirq context 的差異是?為何要區分兩者?\(\to\) 參閱 Linux 核心原始程式碼: include/linux/preempt.h
核心開發者建議改用以下:
注意
preempt_count
的存在,可參閱以下:\(\to\) softirq 完全在核心模式中運行,但 SWI (software interrupt) 是從使用者空間觸發進入核心的途徑 (有時也稱為 system call gate)。二者完全不同,只因英文名稱 "software interrupt" 導致混淆。
補充:Arm 將
SWI
改名為SVC
的原因與意涵在 Armv4T 與 Armv5 等舊架構中,使用者空間進入核心特權模式 (Supervisor mode) 通常藉由下列指令:
雖然
SWI
(software interrupt) 看似觸發中斷的機制 (類似 IRQ 或 FIQ),但它其實是一種同步、由使用者主動發出的核心服務呼叫 (system call),與非同步的中斷處理流程本質不同。由於名稱包含interrupt
字樣,這在教學與理解上容易引起混淆——特別是可能與 Linux 核心內部的softirq
(真正的中斷 BH) 混淆,進而誤認為它是某種 deferred work。為了釐清語意,Arm 在 Armv7-A 與 Armv8-A 中將這個指令更名為:
SVC
表示 Supervisor Call,其語義明確強調這是一種「呼叫」,用以請求作業系統提供核心服務。這樣的命名方式不僅消除中斷相關的誤解,也強調其系統呼叫的本質,與進入非同步中斷脈絡無關。以下是處理器架構中,系統呼叫觸發方式:
int 0x80
/syscall
SWI
SVC
ecall
命名演進反映出系統架構設計者對語意準確性的重視,也有助於區分 system call 與真正的 interrupt 或 softirq 機制。
延伸閱讀: vDSO: 快速的 Linux 系統呼叫機制
vicLin8712
Q1.函式
fill_worker
功能首先分析函式內所用之結構體內部元素,對應程式碼如下
data
代表著整數序列,用於模擬各行程對應的靜態優先權值,其中序列內每個元素範圍值 \(1-99\) 隨機排列。比方說待執行行程對應的優先權,示意圖如下。val_range
代表序列整數分布範圍,即序列數值最大值減序列數值最小值。buckets
為二維陣列指標,其目的在於建立新的映射空間,用於儲存原始data
序列的映射值。其中參數max_priority
決定了buckets
的空間解析度,下一步分析具體如何映射。檢視
fill_work
程式碼如何運作49行

stride=N_WORKERS
代表工作執行緒數量,用於跳躍處理資料 (即data
),使得每個執行緒所需處理的數據量大致相同。如下示意圖。現考慮
data
內元素如何進行映射。由52行可得知
\begin{align} stable\text_code=(data[i]-val\text _min )\times2^{32}+i \end{align}
對應之54行
norm
計算為:\begin{align} norm&=\bigg\lfloor\frac {(stable\text_code\times max \text_priority)}{val\text _range \times2^{32}} \bigg\rfloor\\ &=\bigg\lfloor\frac {((data[i]-val\text _min )\times2^{32}\times max \text_priority)+i\times max \text_priority)}{val\text _range \times2^{32}} \bigg\rfloor\\ \end{align}
上式分子項 \(i\times max\text_proiority\) 展現了如何減緩叢集的情況發生。以下做更詳細的解釋與數學證明探討叢集效應的減緩:
為分析叢集效應,在此針對相同優先權值的數據並探討其映射前後的統計特性變化。

令相同優先權值 \(k\) 於
data
內的序列為隨機變數 \(X\text~N(\mu,\sigma^2), X\in\{1,2,...,n\}\),如下圖示意則映射之隨機變數
norm
可改寫為隨機變數 \(X\) 函數:\begin{align} norm&=\bigg\lfloor\frac {((k-val\text _min )\times2^{32}\times max \text_priority)+X\times max \text_priority)}{val\text _range \times2^{32}} \bigg\rfloor\\ \end{align}
經由 \(floor\) 函數之後,可以將上式化簡為
\begin{align} norm&=\bigg\lfloor{a+\epsilon X} \bigg\rfloor \end{align}
其中參數 \(a\in Z\) 代表在沒有 \(i\) 時的映射值,也就是指所有相同的優先權值都會映射至相同
buckets
,也就是相同的norm
計算值。而 \(\epsilon\) 則是一極小值。接下來就可以探討
norm
的統計分布如何降低叢集效應。接續上式可推導出離散
norm
在\(a\) 之後的分布情況\begin{align} Pr(norm=a+n)&=Pr(n <= \epsilon X<n+1)\\&=Pr\bigg(\frac n \epsilon <=X<\frac {n+1} \epsilon\bigg)\\ &=Pr\bigg(\frac{\frac n \epsilon -\mu}{\sigma} <= \frac{X-\mu}{\sigma}<\frac {\frac {n+1} \epsilon -\mu} {\sigma}\bigg)\\ &=Q\bigg(\frac{\frac n \epsilon -\mu}{\sigma}\bigg)-Q\bigg(\frac{\frac {n+1} \epsilon -\mu}{\sigma}\bigg) \end{align}
上述 \(Q\) 為 CDF 工具之一,可查表得值。
藉由上式所推論出來離散norm模型,可以證明原先在沒有參數 \(i\) 的情況下,投影值皆為固定的 \(a\)。而加入 \(i\) 值後,可以得出其分布機率情形與樣本平均值 \(\mu\) 和 變異數 \(\sigma ^2\) 有關係。藉由此,我們可以得知利用 \(i\) 的微小擾動,可以使即便相同優先權值的行程,可以依照特定的 PDF 分至不同的
buckets
中,避免了叢集效應。下方利用圖示說明加入參數i
前後對buckts
分佈的影響 。(圖片解釋待補)
由此可發現,在 \(\epsilon\) 極小的形況下,大多數的 index 分類依舊會接近分布在原先映射值附近。
(Todo) Q2.
max_prior
如何在程式碼中被決定?Hande1004
老師於 4/15 課堂上問的問題為測驗題中的數據經過
schedsort
後是怎麼顯示的。要回答這個問題我認為就是要理解
schedsort
的程式碼,知道這是怎麼排程的。排序的第一步,是要將原始資料分配到適當的
bucket
中。本文透過fill_worker()
函式完成這件事,其核心是將每筆資料映射到某個bucket
,這個過程使用了資料正規化的技巧:這裡的
stable_code
結合了資料值與其原始index
,避免在數值相同時排序順序不穩定;再透過乘上max_priority
(buckets
的總數) 並除以放大過的資料範圍,來取得資料應該放入的bucket index
(即norm
)。這種方式讓資料能夠比較平均地分布到
buckets
裡,減少偏斜導致的效能問題。由於
fill_worker()
是由多個執行緒並行執行因此,當多個執行緒同時向同一個
bucket
寫入資料時,會有競爭(race condition)的風險。為了解決這個問題,程式使用了 atomic 運算來確保安全地寫入:
這段程式碼確保每個執行緒在向
bucket
寫入資料時,會正確取得不重複的index
(如果重複的norm
時index
就會 +1),並寫入正確位置。資料寫入
bucket
後,為了能把bucket
內資料依序寫回最終結果result[]
陣列中,我們需要知道每個bucket
在result[]
中的起始位置。這就需要對每個
bucket
的大小做 prefix sum 。先把 atomic 中的
bucket_size
安全地讀出來,存在一個普通陣列bucket_sizes_plain[]
中:這裡的
bucket_sizes_plain
就是把前面每個bucket
的實際大小(即有幾個資料元素)從 atomic 型別中取出,轉成普通的size_t
陣列來方便後續使用。接著,我們使用這個
bucket_sizes_plain[]
來計算 prefix sum,也就是每個bucket
在結果陣列中的起始offset
:接下來,要將各個
bucket
中的資料重新組裝成排序後的結果分配給result[]
。為了加速這部分,也同樣使用多執行緒,這段程式碼會把所有buckets
平均分配給多個執行緒處理:透過這樣的方式確保每個
bucket
都有被處理到。多執行緒的方式傳處理:
最後,透過
memcpy()
將排序後的結果寫回原本的資料陣列:所以最後輸出資料就是依照這樣排序後來顯示的:
與其在字面意義「推敲」,不如運用 eBPF 分析
準備工作:
提供 Python/C 語言的介面與多種預先建置的 eBPF 追蹤工具,適合用於處理複雜的系統追蹤工作。可用以下命令安裝:
高階的 eBPF 追蹤語言,適合撰寫快速腳本與單行命令。可用下列命令安裝:
適合用於簡易的即時追蹤與診斷。
用於編譯 eBPF 程式的工具鏈 (需使用版本 6.0 或以上) 。安裝命令如下:
用於檢查 eBPF 程式與 map 的工具。可藉由下列命令安裝:
libbpf: 用來載入 eBPF 程式的函式庫,通常會與 BCC 或 bpftool 一併提供。
\(\to\) GPTtrace: generates eBPF programs and tracing with GPT and natural language.
\(\to\) btetto: produces Perfetto protobuf from formatted bpftrace output.
回顧 simrupt
這是純粹以 softirq, tasklet 與 workqueue (threaded context) 建構的 deferred work 的 Linux 核心模組,不涉及真實的 IRQ 資源管理。
使用 bpftrace 追蹤
/dev/simrupt
開啟的狀態需要開啟兩個終端機,在終端機 \(1\) 輸入,
接著,在終端機 \(2\) 輸入,
當在終端機 \(2\) 執行
sudo touch /dev/simrupt
時,終端機 \(1\) 會類似以下輸出:使用 bpftrace 觀察執行過程
workqueue
與tasklet
(建構於 softirq 之上)在 Linux 核心中存在本質差異,透過bpftrace
可觀察其實際運作行為:kworker
核心執行緒為載體)task_struct
)task_struct
)ksoftirqd/N
或觸發行程如systemd-journal
kworker/uX:Y
,對應獨立 kernel thread以下 bpftrace 腳本可用來觀察
simrupt
模組在執行 tasklet 與 workqueue 函式時的實際執行脈絡:在 bpftrace 中,
comm
為目前執行緒的 command name,對應於核心結構task_struct
中的comm
欄位,用以判斷此時執行該函式的行程名稱。根據
comm
值可辨識出 deferred work 的執行來源:kworker/uX:Y
:執行於 workqueue context,具備可睡眠能力的獨立核心執行緒ksoftirqd/N
:代表由 softirq daemon 延後執行 taskletsystemd-journal
,cat
等):代表 softirq 被直接執行於該行程的核心脈絡中藉由觀察
comm
,pid
,tid
的組合,我們可推斷 deferred work 的實際執行位置。實際輸出示例:
解讀:
ksoftirqd/N
執行 → backlog 被 softirq daemon 延後處理。systemd-journal
,in:imklog
等執行 → softirq 在使用者行程返回前直接執行。kworker/uX:Y
執行 → 由核心統一管理的可排程執行緒。Linux 在 2.3 起以 softirq 取代早期 BH (bottom halves) 機制,而 tasklet 則作為 softirq 的一層封裝,提供非重入、快速回應和處理的底半部執行框架。但 tasklet 屬於非搶佔性執行,執行時不可睡眠,且無法明確排程。
核心開發者多年來建議以
threaded IRQ
或workqueue
取代 tasklet,並不斷朝簡化 softirq 機制的方向前進。為何要強調 "direct softirq execution"?
其執行脈絡取決於觸發路徑,可直接於:
ksoftirqd
延後處理若 tasklet 執行於如
systemd-journal
行程脈絡中,代表 softirq 並未延遲處理,而是在該行程返回前被「順勢」執行。若不掌握此概念,將誤判行程邏輯與排程模型。因此,workqueue/threaded IRQ 成為主流替代方案,其排程脈絡穩定、分析容易,也能安全地進行阻塞式作業。
小結
bpftrace
可觀察其執行脈絡,comm
是辨識 deferred work 行為的關鍵指標使用 bpftrace 追蹤
simrupt_tasklet_func
以下展示如何監控模組
simrupt
中的simrupt_tasklet_func
(tasklet)。追蹤 Tasklet 執行時間點:
此腳本會在
simrupt_tasklet_func
函式的進入與返回點分別附加kprobe
與kretprobe
,並記錄:cpu
)nsecs
,單位為 ns)pid
)- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →pid
通常為 \(0\),因其執行於 interrupt context,不屬於一般行程。執行方式:
參考輸出: (執行
bpftrace
的終端機畫面)使用 bpftrace 追蹤 workqueue
以下展示如何監控模組
simrupt
中的simrupt_work_func
(工作佇列),也是真正的 bottom-half / threaded context。此腳本針對
simrupt_work_func
的執行與結束時間進行監控,並記錄下列資訊:comm
,例如kworker/1:1
)該函式通常由核心中的工作佇列 (如
kworker
執行緒) 執行,因此comm
會顯示對應的背景行程名稱。參考輸出: (執行
bpftrace
的終端機畫面)使用 bpftrace 分析由 timer 觸發的執行脈絡
以下腳本可觀察三個關鍵函式的執行位置與其 context:
以下是執行
simrupt
模組後所觀察到的 bpftrace 參考輸出:timer_handler
comm=swapper/0, tid=0
swapper/0
是 CPU 0 的 idle process,tid 0TIMER_SOFTIRQ
(Bottom Half),執行於 idle context\(\to\) 延伸閱讀: PID 0 行程之謎
simrupt_tasklet_func
comm=ksoftirqd/0
→ softirq handler thread。systemd-journal
、in:imklog
等非預期 thread → 表示此 tasklet 「直接」執行於進入核心的現有行程中 (direct softirq)simrupt_work_func
kworker/u8:X
類型 → workqueue 所對應的 kernel thread📌 函式與執行脈絡
timer_handler
TIMER_SOFTIRQ
simrupt_tasklet_func
TASKLET_SOFTIRQ
simrupt_work_func
simrupt
模組展現 deferred work 的三層處理:timer_handler
) →TIMER_SOFTIRQ
simrupt_tasklet_func
) →TASKLET_SOFTIRQ
/ direct softirqsimrupt_work_func
) → Process Contextcomm
欄位可作為區辨脈絡的可攜式指標:swapper
→ idleksoftirqd/N
→ softirq handlerkworker
→ kernel thread for workqueue有時
tasklet
會在非ksoftirqd
的行程中執行,這代表 softirq 以「直接」方式於該行程內立即執行。彙整工作佇列延遲時間的直方圖
說明:
nsecs
,並將其儲存至雜湊表@start
中(PID, comm)
為鍵值,可區分不同工作執行緒的延遲行為執行步驟:
sudo cat /dev/simrupt
觸發模組活動Ctrl+C
停止腳本,即可觀察彙整出的延遲統計以上將產生依延遲長度 (單位: ns) 分類的直方圖,幫助我們瞭解 workqueue 執行時間的分布情形,例如:
注意:
kprobe
可用於監控任何可符號解析的核心函式入口,而kretprobe
用於監控其返回點nsecs
是 bpftrace 內建變數,代表目前追蹤點的高精度時間戳記cpu
,pid
,tid
,comm
等變數均可直接存取並在格式化輸出中使用使用 bpftrace 觀察 Top Half 回傳狀態
irq_handler_exit
中的ret
值對應意義如下:args->ret
0
IRQ_NONE
1
IRQ_WAKE_THREAD
2
IRQ_HANDLED
IRQ_WAKE_THREAD
是 threaded IRQ 處理流程的關鍵指標。它會觸發核心呼叫irq_wake_thread()
,將對應的 IRQ thread 排入可執行佇列。輸出範例與對應意涵:
這代表 Top Half 於 CPU 3 上執行,並明確要求觸發 IPI 處理。
分析排程延遲與資料處理延遲
探討 workqueue 的延遲指標:
queue_work()
到 work 被 kernel thread (simruptd
) 實際執行的時間simrupt_work_func()
函式本身的執行時間使用以下腳本進行實驗觀察,每 5 秒輸出延遲分布直方圖:
模組執行期間,輸出如下:
資料解讀
@sched_delay
直方圖顯示大多數排程延遲低於 100 µs,表示系統排程器對 workqueue handler thread (simruptd
) 排程效率良好sched:sched_switch
搭配comm == "simruptd"
觀察排程和搶佔fast_buf
資料量simrupt_work_func
中迴圈)tracepoint:irq:softirq_entry
分析與simrupt_tasklet_func()
時序simrupt 與 Linux 排程器互動
simrupt
藉由以下路徑運用 deferred work:queue_work()
呼叫後,由 Linux 核心的 workqueue 子系統挑選可用的kworker
thread (如kworker/u8:0
)來執行simrupt_work_func()
。該行為完全交由 CPU 排程器決定何時執行與在哪個 CPU 上執行。實驗的腳本:
參考輸出:
解讀:
kworker
thread 排入執行 (scheduled in
)simrupt_work_func()
(由 kprobe 印出)TASK_INTERRUPTIBLE
(state=128)這種模式反覆出現,反映出 deferred work 的執行週期:
queue_work()
scheduled in
sched_switch
観察 kworker 切入simrupt_work_func()
執行中kprobe
或printf
印出 comm, tidscheduled out
TASK_INTERRUPTIBLE
)以 bpftrace 追蹤 wakeup 行為
考慮以下:
其中
args->comm
,args->pid
: 呼叫wake_up_process()
的行程comm
,pid
: 喚醒的目標行程 (應該是 block 在simrupt_read()
的使用者程式)cpu
: 呼叫發生的 CPU可根據實際情況改變
comm == ...
的過濾條件。原理:read("/dev/simrupt")
時,行程會進入TASK_INTERRUPTIBLE
狀態,藉由wait_event_interruptible()
睡眠。內部會進入__schedule()
→ 移出 runqueuesimrupt
模組呼叫wake_up_interruptible()
simrupt_work_func()
中觸發TASK_RUNNING
,並重新加入 runqueue排程是否隨後發生?分析 wake_up → sched_switch
這段腳本可觀察是否有喚醒的行程在稍後實際排程執行 (
sched_switch → next_pid
)。分析從喚醒到排程的延遲:
這會顯示「喚醒後多久才真正排程執行」的延遲直方圖 (單位:μs)。
對應到
simrupt
模組的整體 deferred work 流程:timer_handler()
kprobe:timer_handler
tasklet_schedule()
kprobe:simrupt_tasklet_func
queue_work()
→work_func()
kprobe:simrupt_work_func
wake_up_interruptible()
read()
喚醒並執行從 Bottom Half 到 Threaded IRQ
【旁白: Top Half】
過去的他,總被限制在名為 atomic context 的框架裡。
那是種無法停下、也不容許等待的狀態。
不能休眠,無法被搶佔,只能迅速完成被交付的延後處理,
然後悄無聲息地退場,不留下任何排程的痕跡。
我們必須替他註冊、安排、等待核心釋出可用時機 ──
所有的行動都不是他的選擇,只是配合。
【低聲: Bottom Half】
「我不是不曾想過……如果能有自己的舞台,
自己的節奏、自己的時區,
是否,就不再只是你之後的影子?」
【視線溫柔: Top Half】
如今,藉由 Linux 核心的 threaded IRQ 機制,
他終於不再只是 softirq 的延伸。
他被提升為擁有 process context 的存在,
獲得自己的 thread、自己的排程優先權,
甚至能在負載高漲時選擇休眠、讓出時間,再度甦醒。
他不必再倉促地壓縮自己於數十微秒之間,
可坦然地,在非 atomic 的世界中自在運作。
【內心獨白: Threaded IRQ】
「我終於能夠擁抱等待、允許搶佔、接納睡眠……
不再只能被呼叫,而是能融入 CPU 排程其中。」
「我不再是中斷中被遺忘的補述,
而是,被核心視作基本單元 ── 可獨立執行的存在。」
【緊握接力棒】
那一刻,他不再只是 Bottom Half,
他是 Threaded IRQ ── 終於被認可的排程實體,
也是,在中斷與行程之間,最溫柔的回應。
Making Linux do Hard Real-time
