//
// do_IRQ 函數執行完硬件 ISR 後退出時調用此函數。
//
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
//
// 判斷當前是否有硬件中斷嵌套,並且是否有軟中斷在
// pending 狀態,注意:這裡只有兩個條件同時滿足
// 時,才有可能調用 do_softirq() 進入軟中斷。也就是
// 說確認當前所有硬件中斷處理完成,且有硬件中斷安裝了
// 軟中斷處理時理時才會進入。
//
if (!in_interrupt() && local_softirq_pending())
//
// 其實這裡就是調用 do_softirq() 執行
//
invoke_softirq();
preempt_enable_no_resched();
}
#ifndef __ARCH_HAS_DO_SOFTIRQ
asmlinkage void do_softirq(void)
{
__u32 pending;
unsigned long flags;
//
// 這個函數判斷,如果當前有硬件中斷嵌套,或者
// 有軟中斷正在執行時候,則馬上返回。在這個
// 入口判斷主要是為了與 ksoftirqd 互斥。
//
if (in_interrupt())
return;
//
// 關中斷執行以下代碼
//
local_irq_save(flags);
//
// 判斷是否有 pending 的軟中斷需要處理。
//
pending = local_softirq_pending();
//
// 如果有則調用 __do_softirq() 進行實際處理
//
if (pending)
__do_softirq();
//
// 開中斷繼續執行
//
local_irq_restore(flags);
}
//
// 最大軟中斷調用次數為 10 次。
//
#define MAX_SOFTIRQ_RESTART 10
asmlinkage void __do_softirq(void)
{
//
// 軟件中斷處理結構,此結構中包括了 ISR 中
// 注冊的回調函數。
//
struct softirq_action *h;
__u32 pending;
int max_restart = MAX_SOFTIRQ_RESTART;
int cpu;
//
// 得到當前所有 pending 的軟中斷。
//
pending = local_softirq_pending();
account_system_vtime(current);
//
// 執行到這裡要屏蔽其他軟中斷,這裡也就證明了
// 每個 CPU 上同時運行的軟中斷只能有一個。
//
__local_bh_disable((unsigned long)__builtin_return_address(0));
trace_softirq_enter();
//
// 針對 SMP 得到當前正在處理的 CPU
//
cpu = smp_processor_id();
//
// 循環標志
//
restart:
//
// 每次循環在允許硬件 ISR 強占前,首先重置軟中斷
// 的標志位。
//
/* Reset the pending bitmask before enabling irqs */
set_softirq_pending(0);
//
// 到這裡才開中斷運行,注意:以前運行狀態一直是關中斷
// 運行,這時當前處理軟中斷才可能被硬件中斷搶占。也就
// 是說在進入軟中斷時不是一開始就會被硬件中斷搶占。只有
// 在這裡以後的代碼才可能被硬件中斷搶占。
//
local_irq_enable();
//
// 這裡要注意,以下代碼運行時可以被硬件中斷搶占,但
// 這個硬件 ISR 執行完成後,它的所注冊的軟中斷無法馬上運行,
// 別忘了,現在雖是開硬件中斷執行,但前面的 __local_bh_disable()
// 函數屏蔽了軟中斷。所以這種環境下只能被硬件中斷搶占,但這
// 個硬中斷注冊的軟中斷回調函數無法運行。要問為什麼,那是因為
// __local_bh_disable() 函數設置了一個標志當作互斥量,而這個
// 標志正是上面的 irq_exit() 和 do_softirq() 函數中的
// in_interrupt() 函數判斷的條件之一,也就是說 in_interrupt()
// 函數不僅檢測硬中斷而且還判斷了軟中斷。所以在這個環境下觸發
// 硬中斷時注冊的軟中斷,根本無法重新進入到這個函數中來,只能
// 是做一個標志,等待下面的重復循環(最大 MAX_SOFTIRQ_RESTART)
// 才可能處理到這個時候觸發的硬件中斷所注冊的軟中斷。
//
//
// 得到軟中斷向量表。
//
h = softirq_vec;
//
// 循環處理所有 softirq 軟中斷注冊函數。
//
do {
//
// 如果對應的軟中斷設置 pending 標志則表明
// 需要進一步處理它所注冊的函數。
//
if (pending & 1) {
//
// 在這裡執行了這個軟中斷所注冊的回調函數。
//
h->action(h);
rcu_bh_qsctr_inc(cpu);
}
//
// 繼續找,直到把軟中斷向量表中所有 pending 的軟
// 中斷處理完成。
//
h++;
//
// 從代碼裡可以看出按位操作,表明一次循環只
// 處理 32 個軟中斷的回調函數。
//
pending >>= 1;
} while (pending);
//
// 關中斷執行以下代碼。注意:這裡又關中斷了,下面的
// 代碼執行過程中硬件中斷無法搶占。
//
local_irq_disable();
//
// 前面提到過,在剛才開硬件中斷執行環境時只能被硬件中斷
// 搶占
,在這個時候是無法處理軟中斷的,因為剛才開中
// 斷執行過程中可能多次被硬件中斷搶占,每搶占一次就有可
// 能注冊一個軟中斷,所以要再重新取一次所有的軟中斷。
// 以便下面的代碼進行處理後跳回到 restart 處重復執行。
//
pending = local_softirq_pending();
//
// 如果在上面的開中斷執行環境中觸發了硬件中斷,且每個都
// 注冊了一個軟中斷的話,這個軟中斷會設置 pending 位,
// 但在當前一直屏蔽軟中斷的環境下無法得到執行,前面提
// 到過,因為 irq_exit() 和 do_softirq() 根本無法進入到
// 這個處理過程中來。這個在上面詳細的記錄過了。那麼在
// 這裡又有了一個執行的機會。注意:雖然當前環境一直是
// 處於屏蔽軟中斷執行的環境中,但在這裡又給出了一個執行
// 剛才在開中斷環境過程中觸發硬件中斷時所注冊的軟中斷的
// 機會,其實只要理解了軟中斷機制就會知道,無非是在一些特
// 定環境下調用 ISR 注冊到軟中斷向量表裡的函數而已。
//
//
// 如果剛才觸發的硬件中斷注冊了軟中斷,並且重復執行次數
// 沒有到 10 次的話,那麼則跳轉到 restart 標志處重復以上
// 所介紹的所有步驟:設置軟中斷標志位,重新開中斷執行...
// 注意:這裡是要兩個條件都滿足的情況下才可能重復以上步驟。
//
if (pending && --max_restart)
goto restart;
//
// 如果以上步驟重復了 10 次後還有 pending 的軟中斷的話,
// 那麼系統在一定時間內可能達到了一個峰值,為了平衡這點。
// 系統專門建立了一個 ksoftirqd 線程來處理,這樣避免在一
// 定時間內負荷太大。這個 ksoftirqd 線程本身是一個大循環,
// 在某些條件下為了不負載過重,它是可以被其他進程搶占的,
// 但注意,它是顯示的調用了 preempt_xxx() 和 schedule()
// 才會被搶占和切換的。這麼做的原因是因為在它一旦調用
// local_softirq_pending() 函數檢測到有 pending 的軟中斷
// 需要處理的時候,則會顯示的調用 do_softirq() 來處理軟中
// 斷。也就是說,下面代碼喚醒的 ksoftirqd 線程有可能會回
// 到這個函數當中來,尤其是在系統需要響應很多軟中斷的情況
// 下,它的調用入口是 do_softirq(),這也就是為什麼在 do_softirq()
// 的入口處也會用 in_interrupt() 函數來判斷是否有軟中斷
// 正在處理的原因了,目的還是為了防止重入。ksoftirqd 實現
// 看下面對 ksoftirqd() 函數的分析。
//
if (pending)
//
// 此函數實際是調用 wake_up_process() 來喚醒 ksoftirqd
//
wakeup_softirqd();
trace_softirq_exit();
account_system_vtime(current);
//
// 到最後才開軟中斷執行環境,允許軟中斷執行。注意:這裡
// 使用的不是 local_bh_enable(),不會再次觸發 do_softirq()
// 的調用。
//
_local_bh_enable();
}
static int ksoftirqd(void * __bind_cpu)
{
//
// 顯示調用此函數設置當前進程的靜態優先級。當然,
// 這個優先級會隨調度器策略而變化。
//
set_user_nice(current, 19);
//
// 設置當前進程不允許被掛啟
//
current->flags |= PF_NOFREEZE;
//
// 設置當前進程狀態為可中斷的狀態,這種睡眠狀
// 態可響應信號處理等。
//
set_current_state(TASK_INTERRUPTIBLE);
//
// 下面是一個大循環,循環判斷當前進程是否會停止,
// 不會則繼續判斷當前是否有 pending 的軟中斷需
// 要處理。
//
while (!kthread_should_stop()) {
//
// 如果可以進行處理,那麼在此處理期間內禁止
// 當前進程被搶占。
//
preempt_disable();
//
// 首先判斷系統當前沒有需要處理的 pending 狀態的
// 軟中斷
//
if (!local_softirq_pending()) {
//
// 沒有的話在主動放棄 CPU 前先要允許搶占,因為
// 一直是在不允許搶占狀態下執行的代碼。
//
preempt_enable_no_resched();
//
// 顯示調用此函數主動放棄 CPU 將當前進程放入睡眠隊列,
// 並切換新的進程執行(調度器相關不記錄在此)
//
schedule();
//
// 注意:如果當前顯示調用 schedule() 函數主動切換的進
// 程再次被調度執行的話,那麼將從調用這個函數的下一條
// 語句開始執行。也就是說,在這裡當前進程再次被執行的
// 話,將會執行下面的 preempt_disable() 函數。
//
//
// 當進程再度被調度時,在以下處理期間內禁止當前進程
// 被搶占。
//
preempt_disable();
}
//
// 設置當前進程為運行狀態。注意:已經設置了當前進程不可搶占
// 在進入循環後,以上兩個分支不論走哪個都會執行到這裡。一是
// 進入循環時就有 pending 的軟中斷需要執行時。二是進入循環時
// 沒有 pending 的軟中斷,當前進程再次被調度獲得 CPU 時繼續
// 執行時。
//
__set_current_state(TASK_RUNNING);
//
// 循環判斷是否有 pending 的軟中斷,如果有則調用 do_softirq()
// 來做具體處理。注意:這裡又是一個 do_softirq() 的入口點,
// 那麼在 __do_softirq() 當中循環處理 10 次軟中斷的回調函數
// 後,如果還有 pending 的話,會又調用到這裡。那麼在這裡則
// 又會有可能去調用 __do_softirq() 來處理軟中斷回調函數。在前
// 面介紹 __do_softirq() 時已經提到過,處理 10 次還處理不完的
// 話說明系統正處於繁忙狀態。根據以上分析,我們可以試想如果在
// 系統非常繁忙時,這個進程將會與 do_softirq() 相互交替執行,
// 這時此進程占用 CPU 應該會很高,雖然下面的 cond_resched()
// 函數做了一些處理,它在處理完一輪軟中斷後當前處理進程可能會
// 因被調度而減少 CPU 負荷,但是在非常繁忙時這個進程仍然有可
// 能大量占用 CPU。
//
while (local_softirq_pending()) {
/* Preempt disable stops cpu going offline.
If already offline, we'll be on wrong CPU:
don't process */
if (cpu_is_offline((long)__bind_cpu))
//
// 如果當前被關聯的 CPU 無法繼續處理則跳轉
// 到 wait_to_die 標記出,等待結束並退出。
//
goto wait_to_die;
//
// 執行 do_softirq() 來處理具體的軟中斷回調函數。注
// 意:如果此時有一個正在處理的軟中斷的話,則會馬上
// 返回,還記得前面介紹的 in_interrupt() 函數麼。
//
do_softirq();
//
// 允許當前進程被搶占。
//
preempt_enable_no_resched();
//
// 這個函數有可能間接的調用 schedule() 來切換當前
// 進程,而且上面已經允許當前進程可被搶占。也就是
// 說在處理完一輪軟中斷回調函數時,有可能會切換到
// 其他進程。我認為這樣做的目的一是為了在某些負載
// 超標的情況下不至於讓這個進程長時間大量的占用 CPU,
// 二是讓在有很多軟中斷需要處理時不至於讓其他進程
// 得不到響應。
//
cond_resched();
//
// 禁止當前進程被搶占。
//
preempt_disable();
//
// 處理完所有軟中斷了嗎?沒有的話繼續循環以上步驟
//
}
//
// 待一切都處理完成後,允許當前進程被搶占,並設置
// 當前進程狀態為可中斷狀態,繼續循環以上所有過程。
//
preempt_enable();
set_current_state(TASK_INTERRUPTIBLE);
}
//
// 如果將會停止則設置當前進程為運行狀態後直接返回。
// 調度器會根據優先級來使當前進程運行。
//
&nbs
p; __set_current_state(TASK_RUNNING);
return 0;
//
// 一直等待到當前進程被停止
//
wait_to_die:
//
// 允許當前進程被搶占。
//
preempt_enable();
/* Wait for kthread_stop */
//
// 設置當前進程狀態為可中斷的狀態,這種睡眠狀
// 態可響應信號處理等。
//
set_current_state(TASK_INTERRUPTIBLE);
//
// 判斷當前進程是否會被停止,如果不是的話
// 則設置進程狀態為可中斷狀態並放棄當前 CPU
// 主動切換。也就是說這裡將一直等待當前進程
// 將被停止時候才結束。
//
while (!kthread_should_stop()) {
schedule();
set_current_state(TASK_INTERRUPTIBLE);
}
//
// 如果將會停止則設置當前進程為運行狀態後直接返回。
// 調度器會根據優先級來使當前進程運行。
//
__set_current_state(TASK_RUNNING);
return 0;
}