# 解釋 softirq 是否有 task_struct
contributed by < `HeatCrab` >
## 定義清楚 task_struct
`task_struct` 是 Linux 核心用來表示一個行程或執行緒的資料型別,包含了行程的狀態、調度信息、記憶體映射等。只有真正的行程或執行緒(例如用戶行程、核心執行緒等)才會有自己的 `task_struct`。
## 什麼是 softirq ?
> IRQ = interrupt request
是 Linux 核心用來處理延遲工作(deferred work)的機制,屬於 **bottom half** 處理的一部分,用於執行那些不需要立即在 hardirq 上下文完成的任務。
通常在以下情況觸發:
1. **hardirq 返回路徑**:在 hardirq 處理完成後,核心檢查是否有 pending 的 softirq 並執行。
2. **系統呼叫返回前**:在某些行程從核心態返回用戶態之前,核心可能會處理 pending 的 softirq。
3. **ksoftirqd 執行緒**:當 softirq 負載過高時,核心會喚醒 per-CPU 的 `ksoftirqd` 執行緒來處理。
由於 softirq 不屬於任何行程或執行緒,不依賴於 `task_struct`,而是由核心直接調用其 handler (處理函數)。
## 核心調用 softirq 的方法:`ksoftirqd`
`ksoftirqd` 是 Linux 核心為每個 CPU 創建的一個 kernel thread,用於處理過載的 softirq 任務。當 softirq 的處理次數超過一定閾值(參考筆記中 [Software interrupts and realtime](https://lwn.net/Articles/520076/)),核心會喚醒 `ksoftirqd` 來接手 softirq 的處理。而 `ksoftirqd` 運行在 **process context** 中,是一個可被調度的執行緒,因此它**擁有自己的 `task_struct`**。
```bash
$ systemd-cgls -k | grep ksoft
├─ 16 [ksoftirqd/0]
├─ 26 [ksoftirqd/2]
├─ 32 [ksoftirqd/4]
├─ 38 [ksoftirqd/6]
├─ 44 [ksoftirqd/8]
├─ 50 [ksoftirqd/10]
├─ 56 [ksoftirqd/1]
├─ 62 [ksoftirqd/3]
├─ 68 [ksoftirqd/5]
├─ 74 [ksoftirqd/7]
├─ 80 [ksoftirqd/9]
├─ 86 [ksoftirqd/11]
│ │ └─14394 grep --color=auto ksoft
```
但是 `ksoftirqd` 僅是 softirq 處理的載體之一,當它執行 softirq 時,實際上是在模擬 softirq 的處理函數運行,但其執行環境已切換為 process context。這意味著 softirq 的 handler 本身並不依賴 `task_struct`,但當由 `ksoftirqd` 執行時,`ksoftirqd` 的 `task_struct` 提供了調度和上下文管理的支持。
使用 bpftrace 得到以下輸出:
```
tasklet_func: comm=ksoftirqd/0, pid=16, tid=16
work_func: comm=kworker/u48:1, pid=13847, tid=13847
tasklet_func: comm=swapper/0, pid=0, tid=0
work_func: comm=kworker/u48:13, pid=471, tid=471
tasklet_func: comm=swapper/0, pid=0, tid=0
work_func: comm=kworker/u48:1, pid=13847, tid=13847
tasklet_func: comm=swapper/8, pid=0, tid=0
work_func: comm=kworker/u48:1, pid=13847, tid=13847
tasklet_func: comm=ksoftirqd/0, pid=16, tid=16
work_func: comm=kworker/u48:13, pid=471, tid=471
tasklet_func: comm=swapper/0, pid=0, tid=0
work_func: comm=kworker/u48:13, pid=471, tid=471
tasklet_func: comm=swapper/0, pid=0, tid=0
work_func: comm=kworker/u48:1, pid=13847, tid=13847
tasklet_func: comm=swapper/0, pid=0, tid=0
work_func: comm=kworker/u48:1, pid=13847, tid=13847
tasklet_func: comm=ksoftirqd/2, pid=26, tid=26
work_func: comm=kworker/u48:13, pid=471, tid=471
tasklet_func: comm=swapper/0, pid=0, tid=0
work_func: comm=kworker/u48:13, pid=471, tid=471
tasklet_func: comm=swapper/0, pid=0, tid=0
work_func: comm=kworker/u48:1, pid=13847, tid=13847
tasklet_func: comm=swapper/0, pid=0, tid=0
work_func: comm=kworker/u48:13, pid=471, tid=471
tasklet_func: comm=ksoftirqd/0, pid=16, tid=16
work_func: comm=kworker/u48:13, pid=471, tid=471
tasklet_func: comm=KMS thread, pid=9977, tid=10005
work_func: comm=kworker/u48:13, pid=471, tid=471
```
這表明 `simrupt_tasklet_func` 由 `ksoftirqd/0` 執行,`ksoftirqd/0` 是一個核心執行緒,擁有 `pid=16` 和 `tid=16`,對應其 `task_struct`。
然而,若 tasklet 在其他行程(如 `systemd-journal`)的 context 中直接執行,這表明 softirq 在該行程的返回路徑中直接執行,仍然不涉及 softirq 自身的 `task_struct`,而是借用了當前行程的上下文。
## softirq 與 tasklet 的關係
根據筆記我們可以知道tasklet 是 softirq 的封裝,但是 **tasklet 本身也沒有 `task_struct`**(如筆記中明確指出 `tasklet_struct ≠ task_struct`)。筆記中也提到 tasklet 可能在以下情況執行:
- direct softirq execution
> 為何要強調 "direct softirq execution"?
> - softirq 並非執行緒,無明確排程機制
- deferred by ksoftirqd
## atomic context 與 interrupt context
筆記中說明:
> Interrupt context ⊂ Atomic context
**Interrupt context** 包括 hardirq 、 softirq 和 NMI (非遮蔽中斷) 的執行環境,這些都是 atomic context 的子集。而 **Atomic context** 更廣泛,包括任何不可睡眠的執行環境,例如:
- 中斷處理(hardirq、softirq)。
- 持有 spinlock 的情況。
- 禁用搶佔(preemption disabled)的情況。
其中, softirq 屬於 atomic context,因此不可睡眠,無 `task_struct`。
## 結論
1. **softirq 無 task_struct**
softirq 不屬於行程或執行緒,因此不具備 `task_struct`。當 softirq 由 `ksoftirqd` 執行時,`ksoftirqd` 作為核心執行緒擁有 `task_struct`,但這是 `ksoftirqd` 的屬性,而非 softirq 本身。
2. **tasklet 與 softirq**
tasklet 是 softirq 的封裝,同樣無 `task_struct`,其執行可能由 `ksoftirqd` 或其他行程上下文觸發。
## 問題與發想
筆記中有學長提到:
```c=745
static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
```
可以參考 [kernel / softirq.c](https://elixir.bootlin.com/linux/v6.14.5/source/kernel/softirq.c) 中的程式碼定義,知道 softirq 與 tasklet_head 這個資料型別的定義。
可是在同樣的程式碼中的開頭:
```c=37
/*
- No shared variables, all the data are CPU local.
- If a softirq needs serialization, let it serialize itself
by its own spinlocks.
- Even if softirq is serialized, only local cpu is marked for
execution. Hence, we get something sort of weak cpu binding.
Though it is still not clear, will it result in better locality
or will not.
Examples:
- NET RX softirq. It is multithreaded and does not require
any global serialization.
- NET TX softirq. It kicks software netdevice queues, hence
it is logically serialized per device, but this serialization
is invisible to common code.
- Tasklets: serialized wrt itself.
*/
#ifndef __ARCH_IRQ_STAT
DEFINE_PER_CPU_ALIGNED(irq_cpustat_t, irq_stat);
EXPORT_PER_CPU_SYMBOL(irq_stat);
#endif
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
DEFINE_PER_CPU(struct task_struct *, ksoftirqd);
const char * const softirq_to_name[NR_SOFTIRQS] = {
"HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "IRQ_POLL",
"TASKLET", "SCHED", "HRTIMER", "RCU"
};
/*
* we cannot loop indefinitely here to avoid userspace starvation,
* but we also don't want to introduce a worst case 1/HZ latency
* to the pending events, so lets the scheduler to balance
* the softirq load for us.
*/
static void wakeup_softirqd(void)
{
/* Interrupts are disabled: no need to stop preemption */
struct task_struct *tsk = __this_cpu_read(ksoftirqd);
if (tsk)
wake_up_process(tsk);
}
```
也使用了 task_struct 定義了一顆 CPU ,註解的部分我看得有些似懂非懂,但是根據筆記與上述我整理的資料來看, softirq 理論上是沒有 task_struct 定義的,所以這一段的用意為何呢?
> 未仔細閱讀清楚,此處定義的是 ksoftirqd ,而非 softirq 。
> ksoftirqd 是屬於 linux thread ,擁有 task_struct 。
### 理解註解
```c
/*
- No shared variables, all the data are CPU local.
- If a softirq needs serialization, let it serialize itself
by its own spinlocks.
- Even if softirq is serialized, only local cpu is marked for
execution. Hence, we get something sort of weak cpu binding.
Though it is still not clear, will it result in better locality
or will not.
Examples:
- NET RX softirq. It is multithreaded and does not require
any global serialization.
- NET TX softirq. It kicks software netdevice queues, hence
it is logically serialized per device, but this serialization
is invisible to common code.
- Tasklets: serialized wrt itself.
*/
```
通過以上註解我們可以明白 softirq 設計的原則。 Linux 核心透過 per-CPU 與 self serialization 來實現最大化並行性、局部性和靈活性。並舉了三個例子:
1. NET RX softirq
2. NET TX softirq
3. Tasklets
此外, `wakeup_softirqd` 的功能正如前述所說,即是喚醒 `ksoftirqd` 處理 softirq 推遲或是過載的任務負擔。
## References
[2025-04-15 問答簡記](https://hackmd.io/V7bD2XjnQuCZqDMt4ZizGA?view)
[Linux 核心的並行處理](https://hackmd.io/@sysprog/linux2025-kxo/%2F%40sysprog%2Flinux2025-kxo-c)
[kernel / softirq.c](https://elixir.bootlin.com/linux/v6.14.5/source/kernel/softirq.c)
[include / linux / interrupt.h](https://elixir.bootlin.com/linux/v6.14.5/source/include/linux/interrupt.h)