中斷上下文與行程上下文
當程式碼在核心裡執行時,它總是處於兩種「情緒」之一,而是哪一種,決定了那段程式碼被允許做什麼。行程上下文意味核心正代表某個特定行程執行——例如服務某個程式發出的系統呼叫——所以這份工作背後有一個真正的工作(task),可以正當地被放去睡眠、稍後再喚醒。中斷上下文(有時稱原子上下文)意味核心之所以執行,是因為一個硬體中斷觸發了;它背後沒有友善的行程,只是一個突如其來的「現在就處理我」劫持了當下正在跑的任何東西。
實務上的差別很尖銳,值得背下來。在行程上下文裡,核心可以睡眠:它能阻塞著等待一把鎖、呼叫一個可能等待記憶體的函式,或做任何可能讓目前工作入睡的事,因為有一個工作可以被放去睡,排程器同時可以跑別的東西。在中斷上下文裡,核心「絕不可」睡眠——沒有相關聯的行程可暫停,所以阻塞會凍結整個 CPU 且無人能喚醒,使系統死結。因此中斷上下文的程式碼只能用非阻塞的操作:用自旋鎖而非會睡眠的互斥鎖,以及被告知絕不等待的記憶體配置。
為何這是個承重的概念:核心裡幾乎每一條「我可以在這裡呼叫這個嗎?」的規則,都歸結到你身處哪個上下文。上半部中斷處理常式與 softirq 跑在中斷上下文(不可睡眠);系統呼叫程式碼與 workqueue 處理常式跑在行程上下文(允許睡眠)。極大比例的初學者核心臭蟲,來自在中斷上下文裡呼叫一個會睡眠的函式。核心甚至有一個除錯檢查(in_atomic/might_sleep),當程式碼試圖在不該睡眠之處睡眠時就會大聲尖叫,正是因為這個錯誤如此常見、又如此致命。
行程上下文(系統呼叫處理常式):mutex_lock(&m); kmalloc(n, GFP_KERNEL); // 可睡眠——OK。中斷上下文(softirq):spin_lock(&l); kmalloc(n, GFP_ATOMIC); // 絕不可睡眠——在此用 mutex_lock 或 GFP_KERNEL 是臭蟲。
行程上下文可睡眠(mutex、GFP_KERNEL);中斷上下文不可(spinlock、GFP_ATOMIC)。上下文決定哪些呼叫是合法的。
「不可睡眠」這句話不是風格偏好——在中斷上下文裡睡眠可能讓機器死結,因為沒有工作可以暫停與恢復。不確定身在哪個上下文時,就假設你不能睡眠;核心的 might_sleep() 除錯檢查之所以存在,正是因為這個錯誤太容易犯了。