Linux 內核啟動過程中會創建數十個內核線程,它們在後台默默處理中斷底半部、延遲任務、內存回收等關鍵工作。本文聚焦兩個最「知名」且最常被提及的線程——ksoftirqd(軟中斷處理)與 events/kworker(工作隊列執行器),通過源碼級分析揭示其運行機制與設計哲學。
Linux 中斷處理分為「硬中斷(Top Half)」與「軟中斷」:
問題:高負載下(如萬兆網卡收包),軟中斷頻繁觸發且可能重新調度自身,導致用戶進程長期無法調度(「餓死」)。
解決方案:內核規定——軟中斷處理函數中若再次調度同類軟中斷,不立即執行,而是標記 pending,喚醒 per-CPU 的 ksoftirqd 線程在進程上下文中以最低優先級(nice=19)處理。這兼顧了:
ksoftirqd/0、ksoftirqd/1…ksoftirqd(void *__bind_cpu),主循環調用 do_softirq()do_softirq() 又是 irq_exit()(硬中斷返回)與 ksoftirqd 共同調用的統一入口void irq_exit(void) {
// ... 更新 preempt_count 表示退出硬中斷上下文 ...
if (!in_interrupt() && local_softirq_pending())
invoke_softirq(); // 實際調用 do_softirq()
}
in_interrupt() 通過 preempt_count 的 bit 位判斷是否仍處於硬中斷/軟中斷/NMI 上下文,防止重入。
asmlinkage void do_softirq(void) {
if (in_interrupt()) return; // 若在中斷上下文,交給 ksoftirqd
local_irq_save(flags);
if (local_softirq_pending())
__do_softirq();
local_irq_restore(flags);
}
#define MAX_SOFTIRQ_RESTART 10
void __do_softirq(void) {
__local_bh_disable(); // 屏蔽本地軟中斷,防止嵌套
local_irq_enable(); // 開啟硬中斷,允許硬中斷搶占
restart:
set_softirq_pending(0); // 清 pending 位(需在關中斷下)
// 遍歷 32 位 pending bitmap,調用對應 action(h)
do { if (pending & 1) h->action(h); h++; pending >>= 1; } while (pending);
local_irq_disable(); // 關硬中斷,重新檢查 pending
pending = local_softirq_pending();
if (pending && --max_restart) goto restart; // 最多 10 次
if (pending) wakeup_softirqd(); // 還有剩餘,喚醒 ksoftirqd 線程
_local_bh_enable(); // 開啟軟中斷
}
設計精妙點:
static int ksoftirqd(void *__bind_cpu) {
set_user_nice(current, 19); // 最低優先級
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
preempt_disable();
if (!local_softirq_pending()) {
preempt_enable_no_resched();
schedule(); // 無事可做,睡眠讓出 CPU
preempt_disable();
}
__set_current_state(TASK_RUNNING);
while (local_softirq_pending()) {
if (cpu_is_offline((long)__bind_cpu)) goto wait_to_die;
do_softirq(); // 處理 pending 軟中斷
preempt_enable_no_resched();
cond_resched(); // 主動讓調度,防止長期佔用 CPU
preempt_disable();
}
preempt_enable();
set_current_state(TASK_INTERRUPTIBLE);
}
// ... 退出清理 ...
}
關鍵點:cond_resched() 讓出 CPU,確保高優先級任務可搶占;preempt_disable/enable 保護鏈表操作原子性。
tasklet 基於 TASKLET_SOFTIRQ/HI_SOFTIRQ 實現,屬於軟中斷一種。當 tasklet 大量調度時,同樣會觸發 ksoftirqd 處理,表現為 ksoftirqd CPU 佔用率升高。
Workqueue 是內核提供的「可睡眠的異步執行框架」,解決:
核心數據結構:
work_list 等待隊列 waitqfunc、私有數據 data、延遲定時器 timer系統啟動早期創建 keventd_wq,每 CPU 對應一個線程,命名 events/0、events/1…(新內核稱 kworker/uX:Y)。驅動可直接使用 schedule_work() 將任務投遞至此隊列,無需自建線程。
| API | 線程模型 | 適用場景 |
|---|---|---|
create_workqueue(name) | 每 CPU 一個線程 | 高並行、CPU 密集型任務 |
create_singlethread_workqueue(name) | 單一全局線程 | 串行化、順序敏感、低頻任務 |
自建隊列需調用 destroy_workqueue() 釋放。
| 接口 | 隊列 | 特性 |
|---|---|---|
schedule_work(&work) | keventd_wq | 立即調度 |
schedule_delayed_work(&work, delay) | keventd_wq | 延遲調度(jiffies) |
queue_work(wq, &work) | 自定義 wq | 立即調度 |
queue_delayed_work(wq, &work, delay) | 自定義 wq | 延遲調度 |
while (!kthread_should_stop()) {
// 從 work_list 取任務
work = list_first_entry(&cwq->work_list);
if (!work) {
sleep_on(&cwq->waitq); // 隊列空,睡眠等待
continue;
}
// 執行回調
work->func(work);
}
| 維度 | ksoftirqd | events/kworker |
|---|---|---|
| 觸發源 | 硬中斷調度軟中斷、tasklet | 驅動/子系統主動提交 work |
| 上下文 | 進程上下文(可睡眠) | 進程上下文(可睡眠) |
| 優先級 | nice=19(最低) | 普通內核線程優先級 |
| 並行模型 | 每 CPU 固定一個 | 每 CPU 可多個(多隊列) |
| 典型用途 | 網絡收發、塊設備完成、定時器 | 驅動初始化、固件加載、異步 I/O 完成 |
理解這兩類線程,能幫助你在 top/perf 中快速定位「高 CPU 佔用是軟中斷風暴還是工作隊列積壓」,並在驅動開發時正確選擇「軟中斷/tasklet」還是「workqueue」。