2015
在上一篇文章中,我們看到中斷實際分為了兩個部分,俗稱就是一部分是硬中斷,一部分是軟中斷。軟中斷是專門用于處理中斷過程中費時費力的操作,而為什么系統(tǒng)要分硬中斷和軟中斷呢?問得明白點就是為什么需要軟中斷。我們可以試著想想,如果只有硬中斷的情況下,我們需要在中斷處理過程中執(zhí)行一些耗時的操作,比如浮點數(shù)運算,復(fù)雜算法的運算時,其他的外部中斷就不能得到及時的響應(yīng),因為在硬中斷過程中中斷是關(guān)閉著的,甚至一些很緊急的中斷會得不到響應(yīng),系統(tǒng)穩(wěn)定性和及時性都受到很大影響。所以linux為了解決上述這種情況,將中斷處理分為了兩個部分,硬中斷和軟中斷。首先一個外部中斷得到響應(yīng)時,會先關(guān)中斷,并進入到硬中斷完成較為緊急的操作,然后開中斷,并在軟中斷執(zhí)行那些非緊急、可延時執(zhí)行的操作;在這種情況下,緊急操作可以立即執(zhí)行,而其他的外部中斷也可以獲得一個較為快速的響應(yīng)。這也是軟中斷存在的必要性。在軟中斷過程中是不可以被搶占也不能被阻塞的,也不能在一個給定的CPU上交錯執(zhí)行。 軟中斷
軟中斷是在中斷框架中專門用于處理非緊急操作的,在SMP系統(tǒng)中,軟中斷可以并發(fā)地運行在多個CPU上,但在一些路徑在需要使用自旋鎖進行保護。在系統(tǒng)中,很多東西都分優(yōu)先級,軟中斷也不例外,有些軟中斷要求更快速的響應(yīng)運行,在內(nèi)核中軟中斷一共分為10個,同時也代表著10種不同的優(yōu)先級,系統(tǒng)用一個枚舉變量表示:
- enum
- {
- HI_SOFTIRQ=0, /* 高優(yōu)先級tasklet */ /* 優(yōu)先級最高 */
- TIMER_SOFTIRQ, /* 時鐘相關(guān)的軟中斷 */
- NET_TX_SOFTIRQ, /* 將數(shù)據(jù)包傳送到網(wǎng)卡 */
- NET_RX_SOFTIRQ, /* 從網(wǎng)卡接收數(shù)據(jù)包 */
- BLOCK_SOFTIRQ, /* 塊設(shè)備的軟中斷 */
- BLOCK_IOPOLL_SOFTIRQ, /* 支持IO輪詢的塊設(shè)備軟中斷 */
- TASKLET_SOFTIRQ, /* 常規(guī)tasklet */
- SCHED_SOFTIRQ, /* 調(diào)度程序軟中斷 */
- HRTIMER_SOFTIRQ, /* 高精度計時器軟中斷 */
- RCU_SOFTIRQ, /* RCU鎖軟中斷,該軟中斷總是最后一個軟中斷 */ /* 優(yōu)先級最低 */
-
- NR_SOFTIRQS /* 軟中斷數(shù),為10 */
- };
注釋中的tasklet我們之后會說明,這里先無視它。每一個優(yōu)先級的軟中斷都使用一個struct softirq_action結(jié)構(gòu)來表示,在這個結(jié)構(gòu)中,只有一個成員變量,就是action函數(shù)指針,因為不同的軟中斷它的處理方式可能不同,從優(yōu)先級表中就可以看出來,有塊設(shè)備的,也有網(wǎng)卡處理的。系統(tǒng)將這10個軟中斷用softirq_vec[10]的數(shù)組進行保存。 - /* 用于描述一個軟中斷 */
- struct softirq_action
- {
- /* 此軟中斷的處理函數(shù) */
- void (*action)(struct softirq_action *);
- };
-
- /* 10個軟中斷描述符都保存在此數(shù)組 */
- static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
系統(tǒng)一般使用open_softirq()函數(shù)進行軟中斷描述符的初始化,主要就是將action函數(shù)指針指向該軟中斷應(yīng)該執(zhí)行的函數(shù)。在start_kernel()進行系統(tǒng)初始化中,就調(diào)用了softirq_init()函數(shù)對HI_SOFTIRQ和TASKLET_SOFTIRQ兩個軟中斷進行了初始化 - void __init softirq_init(void)
- {
- int cpu;
-
- for_each_possible_cpu(cpu) {
- per_cpu(tasklet_vec, cpu).tail =
- &per_cpu(tasklet_vec, cpu).head;
- per_cpu(tasklet_hi_vec, cpu).tail =
- &per_cpu(tasklet_hi_vec, cpu).head;
- }
-
- /* 開啟常規(guī)tasklet */
- open_softirq(TASKLET_SOFTIRQ, tasklet_action);
- /* 開啟高優(yōu)先級tasklet */
- open_softirq(HI_SOFTIRQ, tasklet_hi_action);
- }
-
-
- /* 開啟軟中斷 */
- void open_softirq(int nr, void (*action)(struct softirq_action *))
- {
- softirq_vec[nr].action = action;
- }
可以看到,TASKLET_SOFTIRQ的action操作使用了tasklet_action()函數(shù),HI_SOFTIRQ的action操作使用了tasklet_hi_action()函數(shù),這兩個函數(shù)我們需要結(jié)合tasklet進行說明。我們也可以看看其他的軟中斷使用了什么函數(shù): - open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
- open_softirq(NET_TX_SOFTIRQ, net_tx_action);
- open_softirq(NET_RX_SOFTIRQ, net_rx_action);
- open_softirq(BLOCK_SOFTIRQ, blk_done_softirq);
- open_softirq(BLOCK_IOPOLL_SOFTIRQ, blk_iopoll_softirq);
- open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);
- open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);
- open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);
其實很明顯可以看出,除了TASKLET_SOFTIRQ和HI_SOFTIRQ,其他的軟中斷更多地是用于特定的設(shè)備和環(huán)境,對于我們普通的IO驅(qū)動和設(shè)備而已,使用的軟中斷幾乎都是TASKLET_SOFTIRQ和HI_SOFTIRQ,而系統(tǒng)為了對這些不同IO設(shè)備進行統(tǒng)一的處理,就在TASKLET_SOFTIRQ和HI_SOFTIRQ的action函數(shù)中使用到了tasklet。
對于每個CPU,都有一個irq_cpustat_t的數(shù)據(jù)結(jié)構(gòu),里面有一個__softirq_pending變量,這個變量很重要,用于表示該CPU的哪個軟中斷處于掛起狀態(tài),在軟中斷處理時可以根據(jù)此值跳過不需要處理的軟中斷,直接處理需要處理的軟中斷。內(nèi)核使用local_softirq_pending()獲取此CPU的__softirq_pending的值。
當使用open_softirq設(shè)置好某個軟中斷的action指針后,該軟中斷就會開始可以使用了,其實更明了地說,從中斷初始化完成開始,即使所有的軟中斷都沒有使用open_softirq()進行初始化,軟中斷都已經(jīng)開始使用了,只是所有軟中斷的action都為空,系統(tǒng)每次執(zhí)行到軟中斷都沒有軟中斷需要執(zhí)行罷了。
在每個CPU上一次軟中斷處理的一個典型流程是:
- 硬中斷執(zhí)行完畢,開中斷。
- 檢查該CPU是否處于嵌套中斷的情況,如果處于嵌套中,則不執(zhí)行軟中斷,也就是在最外層中斷才執(zhí)行軟中斷。
- 執(zhí)行軟中斷,設(shè)置一個軟中斷執(zhí)行最多使用時間和循環(huán)次數(shù)(10次)。
- 進入循環(huán),獲取CPU的__softirq_pending的副本。
- 執(zhí)行此__softirq_pending副本中所有需要執(zhí)行的軟中斷。
- 如果軟中斷執(zhí)行完畢,退出中斷上下文。
- 如果還有軟中斷需要執(zhí)行(在軟中斷期間又發(fā)發(fā)生了中斷,產(chǎn)生了新的軟中斷,新的軟中斷記錄在CPU的__softirq_pending上,而我們的__softirq_pending只是個副本)。
- 檢查此次軟中斷總共使用的時間和循環(huán)次數(shù),條件允許繼續(xù)執(zhí)行軟中斷,循環(huán)次數(shù)減一,并跳轉(zhuǎn)到第4步。
我們具體看一下代碼,首先在irq_exit()中會檢查是否需要進行軟中斷處理:
- void irq_exit(void)
- {
- #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
- local_irq_disable();
- #else
- WARN_ON_ONCE(!irqs_disabled());
- #endif
-
- account_irq_exit_time(current);
- /* 減少preempt_count的硬中斷計數(shù)器 */
- preempt_count_sub(HARDIRQ_OFFSET);
-
- /* in_interrupt()會檢查preempt_count上的軟中斷計數(shù)器和硬中斷計數(shù)器來判斷是否處于中斷嵌套中 */
- /* local_softirq_pending()則會檢查該CPU的__softirq_pending變量,是否有軟中斷掛起 */
- if (!in_interrupt() && local_softirq_pending())
- invoke_softirq();
-
- tick_irq_exit();
- rcu_irq_exit();
- trace_hardirq_exit(); /* must be */
- }
我們再進入到invoke_softirq(): - static inline void invoke_softirq(void)
- {
-
- if (!force_irqthreads) {
- #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK
- /*
- * We can safely execute softirq on the current stack if
- * it is the irq stack, because it should be near empty
- * at this stage.
- */
- /* 軟中斷處理函數(shù) */
- __do_softirq();
- #else
- /*
- * Otherwise, irq_exit() is called on the task stack that can
- * be potentially deep already. So call softirq in its own stack
- * to prevent from any overrun.
- */
- do_softirq_own_stack();
- #endif
- } else {
- /* 如果強制使用軟中斷線程進行軟中斷處理,會通知調(diào)度器喚醒軟中斷線程ksoftirqd */
- wakeup_softirqd();
- }
- }
重頭戲就在__do_softirq()中,我已經(jīng)注釋好了,方便大家看: - asmlinkage __visible void __do_softirq(void)
- {
- /* 為了防止軟中斷執(zhí)行時間太長,設(shè)置了一個軟中斷結(jié)束時間 */
- unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
- /* 保存當前進程的標志 */
- unsigned long old_flags = current->flags;
- /* 軟中斷循環(huán)執(zhí)行次數(shù): 10次 */
- int max_restart = MAX_SOFTIRQ_RESTART;
- /* 軟中斷的action指針 */
- struct softirq_action *h;
- bool in_hardirq;
- __u32 pending;
- int softirq_bit;
-
- /*
- * Mask out PF_MEMALLOC s current task context is borrowed for the
- * softirq. A softirq handled such as network RX might set PF_MEMALLOC
- * again if the socket is related to swap
- */
- current->flags &= ~PF_MEMALLOC;
-
- /* 獲取此CPU的__softirq_pengding變量值 */
- pending = local_softirq_pending();
- /* 用于統(tǒng)計進程被軟中斷使用時間 */
- account_irq_enter_time(current);
-
- /* 增加preempt_count軟中斷計數(shù)器,也表明禁止了調(diào)度 */
- __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET);
- in_hardirq = lockdep_softirq_start();
-
- /* 循環(huán)10次的入口,每次循環(huán)都會把所有掛起需要執(zhí)行的軟中斷執(zhí)行一遍 */
- restart:
- /* 該CPU的__softirq_pending清零,當前的__softirq_pending保存在pending變量中 */
- /* 這樣做就保證了新的軟中斷會在下次循環(huán)中執(zhí)行 */
- set_softirq_pending(0);
-
- /* 開中斷 */
- local_irq_enable();
-
- /* h指向軟中斷數(shù)組頭 */
- h = softirq_vec;
-
- /* 每次獲取最高優(yōu)先級的已掛起軟中斷 */
- while ((softirq_bit = ffs(pending))) {
- unsigned int vec_nr;
- int prev_count;
- /* 獲取此軟中斷描述符地址 */
- h += softirq_bit - 1;
-
- /* 減去軟中斷描述符數(shù)組首地址,獲得軟中斷號 */
- vec_nr = h - softirq_vec;
- /* 獲取preempt_count的值 */
- prev_count = preempt_count();
-
- /* 增加統(tǒng)計中該軟中斷發(fā)生次數(shù) */
- kstat_incr_softirqs_this_cpu(vec_nr);
-
- trace_softirq_entry(vec_nr);
- /* 執(zhí)行該軟中斷的action操作 */
- h->action(h);
- trace_softirq_exit(vec_nr);
-
- /* 之前保存的preempt_count并不等于當前的preempt_count的情況處理,也是簡單的把之前的復(fù)制到當前的preempt_count上,這樣做是防止最后軟中斷計數(shù)不為0導(dǎo)致系統(tǒng)不能夠執(zhí)行調(diào)度 */
- if (unlikely(prev_count != preempt_count())) {
- pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
- vec_nr, softirq_to_name[vec_nr], h->action,
- prev_count, preempt_count());
- preempt_count_set(prev_count);
- }
- /* h指向下一個軟中斷,但下個軟中斷并不一定需要執(zhí)行,這里只是配合softirq_bit做到一個處理 */
- h++;
- pending >>= softirq_bit;
- }
-
- rcu_bh_qs();
- /* 關(guān)中斷 */
- local_irq_disable();
-
- /* 循環(huán)結(jié)束后再次獲取CPU的__softirq_pending變量,為了檢查是否還有軟中斷未執(zhí)行 */
- pending = local_softirq_pending();
- /* 還有軟中斷需要執(zhí)行 */
- if (pending) {
- /* 在還有軟中斷需要執(zhí)行的情況下,如果時間片沒有執(zhí)行完,并且循環(huán)次數(shù)也沒到10次,繼續(xù)執(zhí)行軟中斷 */
- if (time_before(jiffies, end) && !need_resched() &&
- --max_restart)
- goto restart;
- /* 這里是有軟中斷掛起,但是軟中斷時間和循環(huán)次數(shù)已經(jīng)用完,通知調(diào)度器喚醒軟中斷線程去執(zhí)行掛起的軟中斷,軟中斷線程是ksoftirqd,這里只起到一個通知作用,因為在中斷上下文中是禁止調(diào)度的 */
- wakeup_softirqd();
- }
-
- lockdep_softirq_end(in_hardirq);
- /* 用于統(tǒng)計進程被軟中斷使用時間 */
- account_irq_exit_time(current);
- /* 減少preempt_count中的軟中斷計數(shù)器 */
- __local_bh_enable(SOFTIRQ_OFFSET);
- WARN_ON_ONCE(in_interrupt());
- /* 還原進程標志 */
- tsk_restore_flags(current, old_flags, PF_MEMALLOC);
- }
流程就和上面所說的一致,如果還有不懂,可以去內(nèi)核代碼目錄/kernel/softirq.c查看源碼。
tasklet
軟中斷有多種,部分種類有自己特殊的處理,如從NET_TX_SOFTIRQ和NET_RT_SOFTIRQ、BLOCK_SOFTIRQ等,而如HI_SOFTIRQ和TASKLET_SOFTIRQ則是專門使用tasklet。它是在I/O驅(qū)動程序中實現(xiàn)可延遲函數(shù)的首選方法,如上一句所說,它建立在HI_SOFTIRQ和TASKLET_SOFTIRQ這兩種軟中斷之上,多個tasklet可以與同一個軟中斷相關(guān)聯(lián),系統(tǒng)會使用一個鏈表組織他們,而每個tasklet執(zhí)行自己的函數(shù)處理。而HI_SOFTIRQ和TASKLET_SOFTIRQ這兩個軟中斷并沒有什么區(qū)別,他們只是優(yōu)先級上的不同而已,系統(tǒng)會先執(zhí)行HI_SOFTIRQ的tasklet,再執(zhí)行TASKLET_SOFTIRQ的tasklet。同一個tasklet不能同時在幾個CPU上執(zhí)行,一個tasklet在一個時間上只能在一個CPU的軟中斷鏈上,不能同時在多個CPU的軟中斷鏈上,并且當這個tasklet正在執(zhí)行時,其他CPU不能夠執(zhí)行這個tasklet。也就是說,tasklet不必要編寫成可重入的函數(shù)。
系統(tǒng)會為每個CPU維護兩個鏈表,用于保存HI_SOFTIRQ的tasklet和TASKLET_SOFTIRQ的tasklet,這兩個鏈表是tasklet_vec和tasklet_hi_vec,它們都是雙向鏈表,如下:
- struct tasklet_head {
- struct tasklet_struct *head;
- struct tasklet_struct **tail;
- };
-
- static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
- static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
在softirq_init()函數(shù)中,會將每個CPU的tasklet_vec鏈表和tasklet_hi_vec鏈表進行初始化,將他們的頭尾相連,實現(xiàn)為一個空鏈表。由于tasklet_vec和tasklet_hi_vec處理方式幾乎一樣,只是軟中斷的優(yōu)先級別不同,我們只需要理解系統(tǒng)如何對tasklet_vec進行處理即可。需要注意的是,tasklet_vec鏈表都是以順序方式執(zhí)行,并不會出現(xiàn)后一個先執(zhí)行,再到前一個先執(zhí)行(在軟中斷期間被中斷的情況),之后的代碼我們詳細說明。
介紹完tasklet_vec和tasklet_hi_vec鏈表,我們來看看tasklet,tasklet簡單來說,就是一個處理函數(shù)的封裝,類似于硬中斷中的irqaction結(jié)構(gòu)。一般來說,在一個驅(qū)動中如果需要使用tasklet進行軟中斷的處理,只需要一個中斷對應(yīng)初始化一個tasklet,它可以在每次中斷產(chǎn)生時重復(fù)使用。系統(tǒng)使用tasklet_struct結(jié)構(gòu)進行描述一個tasklet,而且對于同一個tasklet_struct你可以選擇放在tasklet_hi_vec鏈表或者tasklet_vec鏈表上。我們來看看:
- struct tasklet_struct
- {
- struct tasklet_struct *next; /* 指向鏈表下一個tasklet */
- unsigned long state; /* tasklet狀態(tài) */
- atomic_t count; /* 禁止計數(shù)器,調(diào)用tasklet_disable()會增加此數(shù),tasklet_enable()減少此數(shù) */
- void (*func)(unsigned long); /* 處理函數(shù) */
- unsigned long data; /* 處理函數(shù)使用的數(shù)據(jù) */
- };
tasklet狀態(tài)主要分為以下兩種:
- TASKLET_STATE_SCHED:這種狀態(tài)表示此tasklet處于某個tasklet鏈表之上(可能是tasklet_vec也可能是tasklet_hi_vec)。
- TASKLET_STATE_RUN:表示此tasklet正在運行中。
這兩個狀態(tài)主要就是用于防止tasklet同時在幾個CPU上運行和在同一個CPU上交錯執(zhí)行。
而func指針就是指向相應(yīng)的處理函數(shù)。在編寫驅(qū)動時,我們可以使用tasklet_init()函數(shù)或者DECLARE_TASKLET宏進行一個task_struct結(jié)構(gòu)的初始化,之后可以使用tasklet_schedule()或者tasklet_hi_schedule()將其放到相應(yīng)鏈表上等待CPU運行。我們使用一張圖描述一下軟中斷和tasklet結(jié)合運行的情況:
我們知道,每個軟中斷都有自己的action函數(shù),在HI_SOFTIRQ和TASKLET_SOFTIRQ的action函數(shù)中,就用到了它們對應(yīng)的TASKLET_HI_VEC鏈表和TASKLET_VEC鏈表,并依次順序執(zhí)行鏈表中的每個tasklet結(jié)點。
在SMP系統(tǒng)中,我們會遇到一個問題:兩個CPU都需要執(zhí)行同一個tasklet的情況,雖然一個tasklet只能放在一個CPU的tasklet_vec鏈表或者tasklet_hi_vec鏈表上,但是這種情況是有可能發(fā)生的,我們設(shè)想一下,中斷在CPU1上得到了響應(yīng),并且它的tasklet放到了CPU1的tasklet_vec上進行執(zhí)行,而當中斷的tasklet上正在執(zhí)行時,此中斷再次發(fā)生,并在CPU2上進行了響應(yīng),此時CPU2將此中斷的tasklet放到CPU2的tasklet_vec上,并執(zhí)行到此中斷的tasklet。
實際上,為了處理這種情況,在HI_SOFTIRQ和TASKLET_SOFTIRQ的action函數(shù)中,會先將對應(yīng)的tasklet鏈表取出來,并把對應(yīng)的tasklet鏈表的head和tail清空,如果在執(zhí)行過程中,某個tasklet的state為TASKLET_STATE_RUN狀態(tài),則把此tasklet加入到原先已清空的tasklet鏈表的末尾,然后設(shè)置__softirq_pending變量,這樣,在下次循環(huán)軟中斷的過程中,會再次運行這個tasklet。
我們可以看看TASKLET_SOFTIRQ的action處理:
- static void tasklet_action(struct softirq_action *a)
- {
- struct tasklet_struct *list;
-
- local_irq_disable();
- /* 將tasklet鏈表從該CPU中拿出來 */
- list = __this_cpu_read(tasklet_vec.head);
- /* 將該CPU的此軟中斷的tasklet鏈表清空 */
- __this_cpu_write(tasklet_vec.head, NULL);
- __this_cpu_write(tasklet_vec.tail, this_cpu_ptr(&tasklet_vec.head));
- local_irq_enable();
-
- /* 鏈表已經(jīng)處于list中,并且該CPU的tasklet_vec鏈表為空 */
- while (list) {
- struct tasklet_struct *t = list;
-
- list = list->next;
-
- /* 檢查并設(shè)置該tasklet為TASKLET_STATE_RUN狀態(tài) */
- if (tasklet_trylock(t)) {
- /* 檢查是否被禁止 */
- if (!atomic_read(&t->count)) {
- /* 清除其TASKLET_STATE_SCHED狀態(tài) */
- if (!test_and_clear_bit(TASKLET_STATE_SCHED,
- &t->state))
- BUG();
- /* 執(zhí)行該tasklet的func處理函數(shù) */
- t->func(t->data);
- /* 清除該tasklet的TASKLET_STATE_RUN狀態(tài) */
- tasklet_unlock(t);
- continue;
- }
- tasklet_unlock(t);
- }
-
- /* 以下為tasklet為TASKLET_STATE_RUN狀態(tài)下的處理 */
- /* 禁止中斷 */
- local_irq_disable();
- /* 將此tasklet添加的該CPU的tasklet_vec鏈表尾部 */
- t->next = NULL;
- *__this_cpu_read(tasklet_vec.tail) = t;
- __this_cpu_write(tasklet_vec.tail, &(t->next));
- /* 設(shè)置該CPU的此軟中斷處于掛起狀態(tài),設(shè)置irq_cpustat_t的__sofirq_pending變量,這樣在軟中斷的下次執(zhí)行中會再次執(zhí)行此tasklet */
- __raise_softirq_irqoff(TASKLET_SOFTIRQ);
- /* 開啟中斷 */
- local_irq_enable();
- }
- }
軟中斷處理線程
當有過多軟中斷需要處理時,為了保證進程能夠得到一個滿意的響應(yīng)時間,設(shè)計時給定軟中斷一個時間片和循環(huán)次數(shù),當時間片和循環(huán)次數(shù)到達但軟中斷又沒有處理完時,就會把剩下的軟中斷交給軟中斷處理線程進行處理,這個線程是一個內(nèi)核線程,其作為一個普通進程,優(yōu)先級是120。其核心處理函數(shù)是run_ksoftirqd(),其實此線程的處理也很簡單,就是調(diào)用了上面的__do_softirq()函數(shù),我們可以具體看看:
- /* 在smpboot_thread_fun的一個死循環(huán)中被調(diào)用 */
- static void run_ksoftirqd(unsigned int cpu)
- {
- /* 禁止中斷,在__do_softirq()中會開啟 */
- local_irq_disable();
- /* 檢查該CPU的__softirq_pending是否有軟中斷被掛起 */
- if (local_softirq_pending()) {
- /*
- * We can safely run softirq on inline stack, as we are not deep
- * in the task stack here.
- */
- /* 執(zhí)行軟中斷 */
- __do_softirq();
- rcu_note_context_switch(cpu);
- /* 開中斷 */
- local_irq_enable();
- /* 檢查是否需要調(diào)度 */
- cond_resched();
- return;
- }
- /* 開中斷 */
- local_irq_enable();
- }