Linux 2.6 內核軟中斷(softirq)執行分析

今天無意中看了眼 Linux 2.6 內核的軟中斷實現,發現和以前我看到的大不相同(以前也是走馬觀花,不大仔細),能說改動非常大。連 softirq 的調用點都不相同了,以前是三個調用點,今天搜索了一下原始碼,發目前多出了ksoftirqd 這個東西后,softirq 在系統中的調用點僅是在 ISR 返回時和使用了 local_bh_enable() 函數後被調用了。網卡部分的顯示調用,我覺得應該不算是系統中的調用點。ksoftirqd 返回去調用 do_softirq() 函數應該也只能算是其中的一個分支,因爲其本身從源頭上來講也還是在 ISR 返回時 irq_exit() 調用的。這樣一來就和前些日子寫的那份筆記(視窗系統/Linux/Solaris 軟中斷機制)裏介紹的 Linux 內核部分的軟中斷有出處了,看來以後討論 Linux kernel 代碼一定要以內核版本爲前題來說,要不非亂了不可。看來得買本 Linux 方面的書了,每次上來直接看相關代碼也不是回事,時間也不允許。

linux kernel source 2.6.19.1

/kernel/softirq.c

  1. //   
  2. // do_IRQ 函數執行完硬件 ISR 後退出時調用此函數。   
  3. //   
  4. void irq_exit(void)   
  5. {   
  6.         account_system_vtime(current);   
  7.         trace_hardirq_exit();   
  8.         sub_preempt_count(IRQ_EXIT_OFFSET);   
  9.         //   
  10.         // 判斷當前是否有硬件中斷嵌套,並且是否有軟中斷在   
  11.         // pending 狀態,注意:這裏只有兩個條件同時滿足   
  12.         // 時,纔有可能調用 do_softirq() 進入軟中斷。也就是   
  13.         // 說確認當前所有硬件中斷處理完成,且有硬件中斷安裝了   
  14.         // 軟中斷處理時理時纔會進入。   
  15.         //    
  16.         if (!in_interrupt() && local_softirq_pending())   
  17.                 //   
  18.                 // 其實這裏就是調用 do_softirq() 執行   
  19.                 //   
  20.                 invoke_softirq();   
  21.         preempt_enable_no_resched();   
  22. }   
  23. #ifndef __ARCH_HAS_DO_SOFTIRQ   
  24. asmlinkage void do_softirq(void)   
  25. {   
  26.         __u32 pending;   
  27.         unsigned long flags;   
  28.         //   
  29.         // 這個函數判斷,如果當前有硬件中斷嵌套,或   
  30.         // 有軟中斷正在執行時候,則馬上返回。在這個   
  31.         // 入口判斷主要是爲了和 ksoftirqd 互斥。   
  32.         //   
  33.         if (in_interrupt())   
  34.                 return;   
  35.         //   
  36.         // 關中斷執行以下代碼   
  37.         //   
  38.         local_irq_save(flags);   
  39.         //   
  40.         // 判斷是否有 pending 的軟中斷需要處理。   
  41.         //   
  42.         pending = local_softirq_pending();   
  43.         //   
  44.         // 如果有則調用 __do_softirq() 進行實際處理   
  45.         //   
  46.         if (pending)   
  47.                 __do_softirq();   
  48.         //   
  49.         // 開中斷繼續執行   
  50.         //   
  51.         local_irq_restore(flags);   
  52. }   
  53. //   
  54. // 最大軟中斷調用次數爲 10 次。   
  55. //   
  56. #define MAX_SOFTIRQ_RESTART 10   
  57. asmlinkage void __do_softirq(void)   
  58. {   
  59.         //   
  60.         // 軟件中斷處理結構,此結構中包括了 ISR 中   
  61.         // 註冊的回調函數。   
  62.         //   
  63.         struct softirq_action *h;   
  64.         __u32 pending;   
  65.         int max_restart = MAX_SOFTIRQ_RESTART;   
  66.         int cpu;   
  67.         //   
  68.         // 得到當前所有 pending 的軟中斷。   
  69.         //    
  70.         pending = local_softirq_pending();   
  71.         account_system_vtime(current);   
  72.         //   
  73.         // 執行到這裏要屏蔽其他軟中斷,這裏也就證實了   
  74.         // 每個 CPU 上同時運行的軟中斷只能有一個。   
  75.         //   
  76.         __local_bh_disable((unsigned long)__builtin_return_address(0));   
  77.         trace_softirq_enter();   
  78.         //   
  79.         // 針對 SMP 得到當前正在處理的 CPU   
  80.         //   
  81.         cpu = smp_processor_id();   
  82. //   
  83. // 循環標誌   
  84. //   
  85. restart:   
  86.         //   
  87.         // 每次循環在允許硬件 ISR 強佔前,首先重置軟中斷   
  88.         // 的標誌位。   
  89.         //   
  90.         /* Reset the pending bitmask before enabling irqs */  
  91.         set_softirq_pending(0);   
  92.         //   
  93.         // 到這裏纔開中斷運行,注意:以前運行狀態一直是關中斷   
  94.         // 運行,這時當前處理軟中斷纔可能被硬件中斷搶佔。也就   
  95.         // 是說在進入軟中斷時不是一開始就會被硬件中斷搶佔。只有   
  96.         // 在這裏以後的代碼纔可能被硬件中斷搶佔。   
  97.         //   
  98.         local_irq_enable();   
  99.         //   
  100.         // 這裏要注意,以下代碼運行時能被硬件中斷搶佔,但   
  101.         // 這個硬件 ISR 執行完成後,他的所註冊的軟中斷無法馬上運行,   
  102.         // 別忘了,目前雖是開硬件中斷執行,但前面的 __local_bh_disable()   
  103.         // 函數屏蔽了軟中斷。所以這種環境下只能被硬件中斷搶佔,但這   
  104.         // 個硬中斷註冊的軟中斷回調函數無法運行。要問爲什麼,那是因爲   
  105.         // __local_bh_disable() 函數設置了一個標誌當作互斥量,而這個   
  106.         // 標誌正是上面的 irq_exit() 和 do_softirq() 函數中的   
  107.         // in_interrupt() 函數判斷的條件之一,也就是說 in_interrupt()    
  108.         // 函數不僅檢測硬中斷而且還判斷了軟中斷。所以在這個環境下觸發   
  109.         // 硬中斷時註冊的軟中斷,根本無法重新進入到這個函數中來,只能   
  110.         // 是做一個標誌,等待下面的重複循環(最大 MAX_SOFTIRQ_RESTART)   
  111.         // 纔可能處理到這個時候觸發的硬件中斷所註冊的軟中斷。   
  112.         //   
  113.         //   
  114.         // 得到軟中斷向量表。   
  115.         //   
  116.         h = softirq_vec;   
  117.         //   
  118.         // 循環處理所有 softirq 軟中斷註冊函數。   
  119.         //    
  120.         do {   
  121.                 //   
  122.                 // 如果對應的軟中斷設置 pending 標誌則表明   
  123.                 // 需要進一步處理他所註冊的函數。   
  124.                 //   
  125.                 if (pending & 1) {   
  126.                         //   
  127.                         // 在這裏執行了這個軟中斷所註冊的回調函數。   
  128.                         //   
  129.                         h->action(h);   
  130.                         rcu_bh_qsctr_inc(cpu);   
  131.                 }   
  132.         //   
  133.         // 繼續找,直到把軟中斷向量表中所有 pending 的軟   
  134.         // 中斷處理完成。   
  135.         //   
  136.                 h++;   
  137.                 //   
  138.                 // 從代碼裏能看出按位���作,表明一次循環只   
  139.                 // 處理 32 個軟中斷的回調函數。   
  140.                 //   
  141.                 pending >>= 1;    
  142.         } while (pending);   
  143.         //   
  144.         // 關中斷執行以下代碼。注意:這裏又關中斷了,下面的   
  145.         // 代碼執行過程中硬件中斷無法搶佔。   
  146.         //   
  147.         local_irq_disable();   
  148.         //   
  149.         // 前面提到過,在剛纔開硬件中斷執行環境時只能被硬件中斷   
  150.         // 搶佔,在這個時候是無法處理軟中斷的,因爲剛纔開中   
  151.         // 斷執行過程中可能多次被硬件中斷搶佔,每搶佔一次就有可   
  152.         // 能註冊一個軟中斷,所以要再重新取一次所有的軟中斷。   
  153.         // 以便下面的代碼進行處理後跳回到 restart 處重複執行。   
  154.         //   
  155.         pending = local_softirq_pending();   
  156.         //   
  157.         // 如果在上面的開中斷執行環境中觸發了硬件中斷,且每個都   
  158.         // 註冊了一個軟中斷的話,這個軟中斷會設置 pending 位,   
  159.         // 但在當前一直屏蔽軟中斷的環境下無法得到執行,前面提   
  160.         // 到過,因爲 irq_exit() 和 do_softirq() 根本無法進入到   
  161.         // 這個處理過程中來。這個在上面周詳的記錄過了。那麼在   
  162.         // 這裏又有了一個執行的機會。注意:雖然當前環境一直是   
  163.         // 處於屏蔽軟中斷執行的環境中,但在這裏又給出了一個執行   
  164.         // 剛纔在開中斷環境過程中觸發硬件中斷時所註冊的軟中斷的   
  165.         // 機會,其實只要理解了軟中斷機制就會知道,無非是在一些特   
  166.         // 定環境下調用 ISR 註冊到軟中斷向量表裏的函數而已。   
  167.         //   
  168.         //   
  169.         // 如果剛纔觸發的硬件中斷註冊了軟中斷,並且重複執行次數   
  170.         // 沒有到 10 次的話,那麼則跳轉到 restart 標誌處重複以上   
  171.         // 所介紹的所有步驟:設置軟中斷標誌位,重新開中斷執行...   
  172.         // 注意:這裏是要兩個條件都滿足的情況下才可能重複以上步驟。    
  173.         //   
  174.         if (pending && --max_restart)   
  175.                 goto restart;   
  176.         //   
  177.         // 如果以上步驟重複了 10 次後更有 pending 的軟中斷的話,   
  178.         // 那麼系統在一定時間內可能達到了一個峯值,爲了平衡這點。   
  179.         // 系統專門建立了一個 ksoftirqd 線程來處理,這樣避免在一   
  180.         // 定時間內負荷太大。這個 ksoftirqd 線程本身是個大循環,   
  181.         // 在某些條件下爲了不負載過重,他是能被其他進程搶佔的,   
  182.         // 但注意,他是顯示的調用了 preempt_xxx() 和 schedule()   
  183.         // 纔會被搶佔和轉換的。這麼做的原因是因爲在他一旦調用    
  184.         // local_softirq_pending() 函數檢測到有 pending 的軟中斷   
  185.         // 需要處理的時候,則會顯示的調用 do_softirq() 來處理軟中   
  186.         // 斷。也就是說,下面代碼喚醒的 ksoftirqd 線程有可能會回   
  187.         // 到這個函數當中來,尤其是在系統需要響應非常多軟中斷的情況   
  188.         // 下,他的調用入口是 do_softirq(),這也就是爲什麼在 do_softirq()   
  189.         // 的入口處也會用 in_interrupt()  函數來判斷是否有軟中斷   
  190.         // 正在處理的原因了,目的還是爲了防止重入。ksoftirqd 實現   
  191.         // 看下面對 ksoftirqd() 函數的分析。   
  192.         //   
  193.         if (pending)   
  194.                //   
  195.                // 此函數實際是調用 wake_up_process() 來喚醒 ksoftirqd   
  196.                //    
  197.                 wakeup_softirqd();   
  198.         trace_softirq_exit();   
  199.         account_system_vtime(current);   
  200.         //   
  201.         // 到最後纔開軟中斷執行環境,允許軟中斷執行。注意:這裏   
  202.         // 使用的不是 local_bh_enable(),不會再次觸發 do_softirq()   
  203.         // 的調用。   
  204.         //    
  205.         _local_bh_enable();   
  206. }   
  207. static int ksoftirqd(void * __bind_cpu)   
  208. {   
  209.         //   
  210.         // 顯示調用此函數設置當前進程的靜態優先級。當然,   
  211.         // 這個優先級會隨調度器策略而變化。   
  212.         //   
  213.         set_user_nice(current, 19);   
  214.         //   
  215.         // 設置當前進程不允許被掛啓   
  216.         //   
  217.         current->flags |= PF_NOFREEZE;   
  218.         //   
  219.         // 設置當前進程狀態爲可中斷的狀態,這種睡眠狀   
  220.         // 態可響應信號處理等。   
  221.         //    
  222.         set_current_state(TASK_INTERRUPTIBLE);   
  223.         //   
  224.         // 下面是個大循環,循環判斷當前進程是否會停止,   
  225.         // 不會則繼續判斷當前是否有 pending 的軟中斷需   
  226.         // 要處理。   
  227.         //   
  228.         while (!kthread_should_stop()) {   
  229.                 //   
  230.                 // 如果能進行處理,那麼在此處理期間內禁止   
  231.                 // 當前進程被搶佔。   
  232.                 //   
  233.                 preempt_disable();   
  234.                 //   
  235.                 // 首先判斷系統當前沒有需要處理的 pending 狀態的   
  236.                 // 軟中斷   
  237.                 //   
  238.                 if (!local_softirq_pending()) {   
  239.                         //   
  240.                         // 沒有的話在主動放棄 CPU 前先要允許搶佔,因爲   
  241.                         // 一直是在不允許搶佔狀態下執行的代碼。   
  242.                         //   
  243.                         preempt_enable_no_resched();   
  244.                         //   
  245.                         // 顯示調用此函數主動放棄 CPU 將當前進程放入睡眠隊列,   
  246.                         // 並轉換新的進程執行(調度器相關不記錄在此)   
  247.                         //   
  248.                         schedule();   
  249.                         //   
  250.                         // 注意:如果當前顯示調用 schedule() 函數主動轉換的進   
  251.                         // 程再次被調度執行的話,那麼將從調用這個函數的下一條   
  252.                         // 語句開始執行。也就是說,在這裏當前進程再次被執行的   
  253.                         // 話,將會執行下面的 preempt_disable() 函數。   
  254.                         //   
  255.                         //   
  256.                         // 當進程再度被調度時,在以下處理期間內禁止當前進程   
  257.                         // 被搶佔。   
  258.                         //   
  259.                         preempt_disable();   
  260.                 }   
  261.                 //   
  262.                 // 設置當前進程爲運行狀態。注意:已設置了當前進程不可搶佔   
  263.                 // 在進入循環後,以上兩個分支不論走哪個都會執行到這裏。一是   
  264.                 // 進入循環時就有 pending 的軟中斷需要執行時。二是進入循環時   
  265.                 // 沒有 pending 的軟中斷,當前進程再次被調度獲得 CPU 時繼續   
  266.                 // 執行時。   
  267.                 //   
  268.                 __set_current_state(TASK_RUNNING);   
  269.                 //   
  270.                 // 循環判斷是否有 pending 的軟中斷,如果有則調用 do_softirq()   
  271.                 // 來做具體處理。注意:這裏又是個 do_softirq() 的入口點,   
  272.                 // 那麼在 __do_softirq() 當中循環處理 10 次軟中斷的回調函數   
  273.                 // 後,如果更有 pending 的話,會又調用到這裏。那麼在這裏則   
  274.                 // 又會有可能去調用 __do_softirq() 來處理軟中斷回調函數。在前   
  275.                 // 面介紹 __do_softirq() 時已提到過,處理 10 次還處理不完的   
  276.                 // 話說明系統正處於繁忙狀態。根據以上分析,我們能試想如果在   
  277.                 // 系統非常繁忙時,這個進程將會和 do_softirq() 相互交替執行,   
  278.                 // 這時此進程佔用 CPU 應該會非常高,雖然下面的 cond_resched()    
  279.                 // 函數做了一些處理,他在處理完一輪軟中斷後當前處理進程可能會   
  280.                 // 因被調度而減少 CPU 負荷,不過在非常繁忙時這個進程仍然有可   
  281.                 // 能大量佔用 CPU。   
  282.                 //   
  283.                 while (local_softirq_pending()) {   
  284.                         /* Preempt disable stops cpu going offline.  
  285.                            If already offline, we’ll be on wrong CPU:  
  286.                            don’t process */  
  287.                         if (cpu_is_offline((long)__bind_cpu))   
  288.                                 //   
  289.                                 // 如果當前被關聯的 CPU 無法繼續處理則跳轉   
  290.                                 // 到 wait_to_die 標記出,等待結束並退出。   
  291.                                 //    
  292.                                 goto wait_to_die;   
  293.                         //   
  294.                         // 執行 do_softirq() 來處理具體的軟中斷回調函數。注   
  295.                         // 意:如果此時有一個正在處理的軟中斷的話,則會馬上   
  296.                         // 返回,還記得前面介紹的 in_interrupt() 函數麼。   
  297.                         //   
  298.                         do_softirq();   
  299.                         //   
  300.                         // 允許當前進程被搶佔。   
  301.                         //   
  302.                         preempt_enable_no_resched();   
  303.                            
  304.                         //   
  305.                         // 這個函數有可能間接的調用 schedule() 來轉換當前   
  306.                         // 進程,而且上面已允許當前進程可被搶佔。也就是   
  307.                         // 說在處理完一輪軟中斷回調函數時,有可能會轉換到   
  308.                         // 其他進程。我認爲這樣做的目的一是爲了在某些負載   
  309.                         // 超標的情況下不至於讓這個進程長時間大量的佔用 CPU,   
  310.                         // 二是讓在有非常多軟中斷需要處理時不至於讓其他進程   
  311.                         // 得不到響應。   
  312.                         //   
  313.                         cond_resched();   
  314.                         //   
  315.                         // 禁止當前進程被搶佔。   
  316.                         //   
  317.                         preempt_disable();   
  318.                         //   
  319.                         // 處理完所有軟中斷了嗎?沒有的話繼續循環以上步驟   
  320.                         //   
  321.                 }   
  322.                 //   
  323.                 // 待一切都處理完成後,允許當前進程被搶佔,並設置   
  324.                 // 當前進程狀態爲可中斷狀態,繼續循環以上所有過程。   
  325.                 //   
  326.                 preempt_enable();   
  327.                 set_current_state(TASK_INTERRUPTIBLE);   
  328.         }   
  329.       
  330.         //   
  331.         // 如果將會停止則設置當前進程爲運行狀態後直接返回。   
  332.         // 調度器會根據優先級來使當前進程運行。   
  333.         //   
  334.         __set_current_state(TASK_RUNNING);   
  335.         return 0;   
  336. //   
  337. // 一直等待到當前進程被停止   
  338. //   
  339. wait_to_die:   
  340.         //   
  341.         // 允許當前進程被搶佔。   
  342.         //   
  343.         preempt_enable();   
  344.         /* Wait for kthread_stop */  
  345.         //   
  346.         // 設置當前進程狀態爲可中斷的狀態,這種睡眠狀   
  347.         // 態可響應信號處理等。   
  348.         //    
  349.         set_current_state(TASK_INTERRUPTIBLE);   
  350.         //   
  351.         // 判斷當前進程是否會被停止,如果不是的話   
  352.         // 則設置進程狀態爲可中斷狀態並放棄當前 CPU   
  353.         // 主動轉換。也就是說這裏將一直等待當前進程   
  354.         // 將被停止時候才結束。   
  355.         //   
  356.         while (!kthread_should_stop()) {   
  357.                 schedule();   
  358.                 set_current_state(TASK_INTERRUPTIBLE);   
  359.         }   
  360.         //   
  361.         // 如果將會停止則設置當前進程爲運行狀態後直接返回。   
  362.         // 調度器會根據優先級來使當前進程運行。   
  363.         //   
  364.         __set_current_state(TASK_RUNNING);   
  365.         return 0;   
  366. }  

1、加入了對軟中斷線程 ksoftirqd 的分析。

2、修正了對 softirq 調用點的解釋,因爲當時 local_bh_enable() 函數

   也在 /kernel/softirq.c 中,所以看遺漏了

發佈了12 篇原創文章 · 獲贊 7 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章