<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文完全讀懂 | Linux中斷處理

          共 11251字,需瀏覽 23分鐘

           ·

          2021-11-03 16:19

          什么是中斷

          中斷?是為了解決外部設(shè)備完成某些工作后通知CPU的一種機(jī)制(譬如硬盤完成讀寫操作后通過(guò)中斷告知CPU已經(jīng)完成)。早期沒有中斷機(jī)制的計(jì)算機(jī)就不得不通過(guò)輪詢來(lái)查詢外部設(shè)備的狀態(tài),由于輪詢是試探查詢的(也就是說(shuō)設(shè)備不一定是就緒狀態(tài)),所以往往要做很多無(wú)用的查詢,從而導(dǎo)致效率非常低下。由于中斷是由外部設(shè)備主動(dòng)通知CPU的,所以不需要CPU進(jìn)行輪詢?nèi)ゲ樵儯蚀蟠筇嵘?/p>

          從物理學(xué)的角度看,中斷是一種電信號(hào),由硬件設(shè)備產(chǎn)生,并直接送入中斷控制器(如 8259A)的輸入引腳上,然后再由中斷控制器向處理器發(fā)送相應(yīng)的信號(hào)。處理器一經(jīng)檢測(cè)到該信號(hào),便中斷自己當(dāng)前正在處理的工作,轉(zhuǎn)而去處理中斷。此后,處理器會(huì)通知 OS 已經(jīng)產(chǎn)生中斷。這樣,OS 就可以對(duì)這個(gè)中斷進(jìn)行適當(dāng)?shù)奶幚怼2煌脑O(shè)備對(duì)應(yīng)的中斷不同,而每個(gè)中斷都通過(guò)一個(gè)唯一的數(shù)字標(biāo)識(shí),這些值通常被稱為中斷請(qǐng)求線。

          中斷控制器

          X86計(jì)算機(jī)的 CPU 為中斷只提供了兩條外接引腳:NMI 和 INTR。其中 NMI 是不可屏蔽中斷,它通常用于電源掉電和物理存儲(chǔ)器奇偶校驗(yàn);INTR是可屏蔽中斷,可以通過(guò)設(shè)置中斷屏蔽位來(lái)進(jìn)行中斷屏蔽,它主要用于接受外部硬件的中斷信號(hào),這些信號(hào)由中斷控制器傳遞給 CPU。

          常見的中斷控制器有兩種:

          可編程中斷控制器8259A

          傳統(tǒng)的 PIC(Programmable Interrupt Controller,可編程中斷控制器)是由兩片 8259A 風(fēng)格的外部芯片以“級(jí)聯(lián)”的方式連接在一起。每個(gè)芯片可處理多達(dá) 8 個(gè)不同的 IRQ。因?yàn)閺?PIC 的 INT 輸出線連接到主 PIC 的 IRQ2 引腳,所以可用 IRQ 線的個(gè)數(shù)達(dá)到 15 個(gè),如圖下所示。

          86f827619074b6f5690c6e0d0a554d99.webp

          8259A

          高級(jí)可編程中斷控制器(APIC)

          8259A 只適合單 CPU 的情況,為了充分挖掘 SMP 體系結(jié)構(gòu)的并行性,能夠把中斷傳遞給系統(tǒng)中的每個(gè) CPU 至關(guān)重要。基于此理由,Intel 引入了一種名為 I/O 高級(jí)可編程控制器的新組件,來(lái)替代老式的 8259A 可編程中斷控制器。該組件包含兩大組成部分:一是“本地 APIC”,主要負(fù)責(zé)傳遞中斷信號(hào)到指定的處理器;舉例來(lái)說(shuō),一臺(tái)具有三個(gè)處理器的機(jī)器,則它必須相對(duì)的要有三個(gè)本地 APIC。另外一個(gè)重要的部分是 I/O APIC,主要是收集來(lái)自 I/O 裝置的 Interrupt 信號(hào)且在當(dāng)那些裝置需要中斷時(shí)發(fā)送信號(hào)到本地 APIC,系統(tǒng)中最多可擁有 8 個(gè) I/O APIC。

          每個(gè)本地 APIC 都有 32 位的寄存器,一個(gè)內(nèi)部時(shí)鐘,一個(gè)本地定時(shí)設(shè)備以及為本地中斷保留的兩條額外的 IRQ 線 LINT0 和 LINT1。所有本地 APIC 都連接到 I/O APIC,形成一個(gè)多級(jí) APIC 系統(tǒng),如圖下所示。

          4c2a0d92216709c5e537aadb018030f6.webp

          APIC

          目前大部分單處理器系統(tǒng)都包含一個(gè) I/O APIC 芯片,可以通過(guò)以下兩種方式來(lái)對(duì)這種芯片進(jìn)行配置:

          • 作為一種標(biāo)準(zhǔn)的 8259A 工作方式。本地 APIC 被禁止,外部 I/O APIC 連接到 CPU,兩條 LINT0 和 LINT1 分別連接到 INTR 和 NMI 引腳。
          • 作為一種標(biāo)準(zhǔn)外部 I/O APIC。本地 APIC 被激活,且所有的外部中斷都通過(guò) I/O APIC 接收。

          辨別一個(gè)系統(tǒng)是否正在使用 I/O APIC,可以在命令行輸入如下命令:

          #?cat?/proc/interrupts
          ???????????CPU0???????
          ??0:??????90504????IO-APIC-edge??timer
          ??1:????????131????IO-APIC-edge??i8042
          ??8:??????????4????IO-APIC-edge??rtc
          ??9:??????????0????IO-APIC-level??acpi
          ?12:????????111????IO-APIC-edge??i8042
          ?14:???????1862????IO-APIC-edge??ide0
          ?15:?????????28????IO-APIC-edge??ide1
          177:??????????9????IO-APIC-level??eth0
          185:??????????0????IO-APIC-level??via82cxxx
          ...

          如果輸出結(jié)果中列出了 IO-APIC,說(shuō)明您的系統(tǒng)正在使用 APIC。如果看到 XT-PIC,意味著您的系統(tǒng)正在使用 8259A 芯片。

          中斷分類

          中斷可分為同步(synchronous)中斷和異步(asynchronous)中斷:

          • 同步中斷是當(dāng)指令執(zhí)行時(shí)由 CPU 控制單元產(chǎn)生,之所以稱為同步,是因?yàn)橹挥性谝粭l指令執(zhí)行完畢后 CPU 才會(huì)發(fā)出中斷,而不是發(fā)生在代碼指令執(zhí)行期間,比如系統(tǒng)調(diào)用。
          • 異步中斷是指由其他硬件設(shè)備依照 CPU 時(shí)鐘信號(hào)隨機(jī)產(chǎn)生,即意味著中斷能夠在指令之間發(fā)生,例如鍵盤中斷。

          根據(jù) Intel 官方資料,同步中斷稱為異常(exception),異步中斷被稱為中斷(interrupt)。

          中斷可分為?可屏蔽中斷(Maskable interrupt)和?非屏蔽中斷(Nomaskable interrupt)。異常可分為?故障(fault)、陷阱(trap)、終止(abort)三類。

          從廣義上講,中斷可分為四類:中斷故障陷阱終止。這些類別之間的異同點(diǎn)請(qǐng)參看 表。

          表:中斷類別及其行為

          類別原因異步/同步返回行為
          中斷來(lái)自I/O設(shè)備的信號(hào)異步總是返回到下一條指令
          陷阱有意的異常同步總是返回到下一條指令
          故障潛在可恢復(fù)的錯(cuò)誤同步返回到當(dāng)前指令
          終止不可恢復(fù)的錯(cuò)誤同步不會(huì)返回

          X86 體系結(jié)構(gòu)的每個(gè)中斷都被賦予一個(gè)唯一的編號(hào)或者向量(8 位無(wú)符號(hào)整數(shù))。非屏蔽中斷和異常向量是固定的,而可屏蔽中斷向量可以通過(guò)對(duì)中斷控制器的編程來(lái)改變。

          中斷處理 - 上半部(硬中斷)

          由于?APIC中斷控制器?有點(diǎn)小復(fù)雜,所以本文主要通過(guò)?8259A中斷控制器?來(lái)介紹Linux對(duì)中斷的處理過(guò)程。

          中斷處理相關(guān)結(jié)構(gòu)

          前面說(shuō)過(guò),8259A中斷控制器?由兩片 8259A 風(fēng)格的外部芯片以?級(jí)聯(lián)?的方式連接在一起,每個(gè)芯片可處理多達(dá) 8 個(gè)不同的 IRQ(中斷請(qǐng)求),所以可用 IRQ 線的個(gè)數(shù)達(dá)到 15 個(gè)。如下圖:

          427948734fd65ce5c758b03850e5de82.webp

          8259A

          在內(nèi)核中每條IRQ線由結(jié)構(gòu)體?irq_desc_t?來(lái)描述,irq_desc_t?定義如下:

          typedef?struct?{
          ????unsigned?int?status;????????/*?IRQ?status?*/
          ????hw_irq_controller?*handler;
          ????struct?irqaction?*action;???/*?IRQ?action?list?*/
          ????unsigned?int?depth;?????????/*?nested?irq?disables?*/
          ????spinlock_t?lock;
          }?irq_desc_t;

          下面介紹一下?irq_desc_t?結(jié)構(gòu)各個(gè)字段的作用:

          • status: IRQ線的狀態(tài)。
          • handler: 類型為?hw_interrupt_type?結(jié)構(gòu),表示IRQ線對(duì)應(yīng)的硬件相關(guān)處理函數(shù),比如?8259A中斷控制器?接收到一個(gè)中斷信號(hào)時(shí),需要發(fā)送一個(gè)確認(rèn)信號(hào)才會(huì)繼續(xù)接收中斷信號(hào)的,發(fā)送確認(rèn)信號(hào)的函數(shù)就是?hw_interrupt_type?中的?ack?函數(shù)。
          • action: 類型為?irqaction?結(jié)構(gòu),中斷信號(hào)的處理入口。由于一條IRQ線可以被多個(gè)硬件共享,所以?action?是一個(gè)鏈表,每個(gè)?action?代表一個(gè)硬件的中斷處理入口。
          • depth: 防止多次開啟和關(guān)閉IRQ線。
          • lock: 防止多核CPU同時(shí)對(duì)IRQ進(jìn)行操作的自旋鎖。

          hw_interrupt_type?這個(gè)結(jié)構(gòu)與硬件相關(guān),這里就不作介紹了,我們來(lái)看看?irqaction?這個(gè)結(jié)構(gòu):

          struct?irqaction?{
          ????void?(*handler)(int,?void?*,?struct?pt_regs?*);
          ????unsigned?long?flags;
          ????unsigned?long?mask;
          ????const?char?*name;
          ????void?*dev_id;
          ????struct?irqaction?*next;
          };

          下面說(shuō)說(shuō)?irqaction?結(jié)構(gòu)各個(gè)字段的作用:

          • handler: 中斷處理的入口函數(shù),handler?的第一個(gè)參數(shù)是中斷號(hào),第二個(gè)參數(shù)是設(shè)備對(duì)應(yīng)的ID,第三個(gè)參數(shù)是中斷發(fā)生時(shí)由內(nèi)核保存的各個(gè)寄存器的值。
          • flags: 標(biāo)志位,用于表示?irqaction?的一些行為,例如是否能夠與其他硬件共享IRQ線。
          • name: 用于保存中斷處理的名字。
          • dev_id: 設(shè)備ID。
          • next: 每個(gè)硬件的中斷處理入口對(duì)應(yīng)一個(gè)?irqaction?結(jié)構(gòu),由于多個(gè)硬件可以共享同一條IRQ線,所以這里通過(guò)?next?字段來(lái)連接不同的硬件中斷處理入口。

          irq_desc_t?結(jié)構(gòu)關(guān)系如下圖:

          103efee17f53aac11e29f4e5a45bce49.webp

          irq_desc_t

          注冊(cè)中斷處理入口

          在內(nèi)核中,可以通過(guò)?setup_irq()?函數(shù)來(lái)注冊(cè)一個(gè)中斷處理入口。setup_irq()?函數(shù)代碼如下:

          int?setup_irq(unsigned?int?irq,?struct?irqaction?*?new)
          {
          ????int?shared?=?0;
          ????unsigned?long?flags;
          ????struct?irqaction?*old,?**p;
          ????irq_desc_t?*desc?=?irq_desc?+?irq;
          ????...
          ????spin_lock_irqsave(&desc->lock,flags);
          ????p?=?&desc->action;
          ????if?((old?=?*p)?!=?NULL)?{
          ????????if?(!(old->flags?&?new->flags?&?SA_SHIRQ))?{
          ????????????spin_unlock_irqrestore(&desc->lock,flags);
          ????????????return?-EBUSY;
          ????????}

          ????????do?{
          ????????????p?=?&old->next;
          ????????????old?=?*p;
          ????????}?while?(old);
          ????????shared?=?1;
          ????}

          ????*p?=?new;

          ????if?(!shared)?{
          ????????desc->depth?=?0;
          ????????desc->status?&=?~(IRQ_DISABLED?|?IRQ_AUTODETECT?|?IRQ_WAITING);
          ????????desc->handler->startup(irq);
          ????}
          ????spin_unlock_irqrestore(&desc->lock,flags);

          ????register_irq_proc(irq);?//?注冊(cè)proc文件系統(tǒng)
          ????return?0;
          }

          setup_irq()?函數(shù)比較簡(jiǎn)單,就是通過(guò)?irq?號(hào)來(lái)查找對(duì)應(yīng)的?irq_desc_t?結(jié)構(gòu),并把新的?irqaction?連接到?irq_desc_t?結(jié)構(gòu)的?action?鏈表中。要注意的是,如果設(shè)備不支持共享IRQ線(也即是?flags?字段沒有設(shè)置?SA_SHIRQ?標(biāo)志),那么就返回?EBUSY?錯(cuò)誤。

          我們看看?時(shí)鐘中斷處理入口?的注冊(cè)實(shí)例:

          static?struct?irqaction?irq0??=?{?timer_interrupt,?SA_INTERRUPT,?0,?"timer",?NULL,?NULL};

          void?__init?time_init(void)
          {
          ????...
          ????setup_irq(0,?&irq0);
          }

          可以看到,時(shí)鐘中斷處理入口的IRQ號(hào)為0,處理函數(shù)為?timer_interrupt(),并且不支持共享IRQ線(flags?字段沒有設(shè)置?SA_SHIRQ?標(biāo)志)。

          處理中斷請(qǐng)求

          當(dāng)一個(gè)中斷發(fā)生時(shí),中斷控制層會(huì)發(fā)送信號(hào)給CPU,CPU收到信號(hào)會(huì)中斷當(dāng)前的執(zhí)行,轉(zhuǎn)而執(zhí)行中斷處理過(guò)程。中斷處理過(guò)程首先會(huì)保存寄存器的值到棧中,然后調(diào)用?do_IRQ()?函數(shù)進(jìn)行進(jìn)一步的處理,do_IRQ()?函數(shù)代碼如下:

          asmlinkage?unsigned?int?do_IRQ(struct?pt_regs?regs)
          {
          ????int?irq?=?regs.orig_eax?&?0xff;?/*?獲取IRQ號(hào)??*/
          ????int?cpu?=?smp_processor_id();
          ????irq_desc_t?*desc?=?irq_desc?+?irq;
          ????struct?irqaction?*?action;
          ????unsigned?int?status;

          ????kstat.irqs[cpu][irq]++;
          ????spin_lock(&desc->lock);
          ????desc->handler->ack(irq);

          ????status?=?desc->status?&?~(IRQ_REPLAY?|?IRQ_WAITING);
          ????status?|=?IRQ_PENDING;?/*?we?_want_?to?handle?it?*/

          ????action?=?NULL;
          ????if?(!(status?&?(IRQ_DISABLED?|?IRQ_INPROGRESS)))?{?//?當(dāng)前IRQ不在處理中
          ????????action?=?desc->action;????//?獲取?action?鏈表
          ????????status?&=?~IRQ_PENDING;???//?去除IRQ_PENDING標(biāo)志,?這個(gè)標(biāo)志用于記錄是否在處理IRQ請(qǐng)求的時(shí)候又發(fā)生了中斷
          ????????status?|=?IRQ_INPROGRESS;?//?設(shè)置IRQ_INPROGRESS標(biāo)志,?表示正在處理IRQ
          ????}
          ????desc->status?=?status;

          ????if?(!action)??//?如果上一次IRQ還沒完成,?直接退出
          ????????goto?out;

          ????for?(;;)?{
          ????????spin_unlock(&desc->lock);
          ????????handle_IRQ_event(irq,?®s,?action);?//?處理IRQ請(qǐng)求
          ????????spin_lock(&desc->lock);
          ????????
          ????????if?(!(desc->status?&?IRQ_PENDING))?//?如果在處理IRQ請(qǐng)求的時(shí)候又發(fā)生了中斷,?繼續(xù)處理IRQ請(qǐng)求
          ????????????break;
          ????????desc->status?&=?~IRQ_PENDING;
          ????}
          ????desc->status?&=?~IRQ_INPROGRESS;
          out:

          ????desc->handler->end(irq);
          ????spin_unlock(&desc->lock);

          ????if?(softirq_active(cpu)?&?softirq_mask(cpu))
          ????????do_softirq();?//?中斷下半部處理
          ????return?1;
          }

          do_IRQ()?函數(shù)首先通過(guò)IRQ號(hào)獲取到其對(duì)應(yīng)的?irq_desc_t?結(jié)構(gòu),注意的是同一個(gè)中斷有可能發(fā)生多次,所以要判斷當(dāng)前IRQ是否正在被處理當(dāng)中(判斷?irq_desc_t?結(jié)構(gòu)的?status?字段是否設(shè)置了?IRQ_INPROGRESS?標(biāo)志),如果不是處理當(dāng)前,那么就獲取到?action?鏈表,然后通過(guò)調(diào)用?handle_IRQ_event()?函數(shù)來(lái)執(zhí)行 action 鏈表中的中斷處理函數(shù)。

          如果在處理中斷的過(guò)程中又發(fā)生了相同的中斷(irq_desc_t?結(jié)構(gòu)的?status?字段被設(shè)置了?IRQ_INPROGRESS?標(biāo)志),那么就繼續(xù)對(duì)中斷進(jìn)行處理。處理完中斷后,調(diào)用?do_softirq()?函數(shù)來(lái)對(duì)中斷下半部進(jìn)行處理(下面會(huì)說(shuō))。

          接下來(lái)看看?handle_IRQ_event()?函數(shù)的實(shí)現(xiàn):

          int?handle_IRQ_event(unsigned?int?irq,?struct?pt_regs?*?regs,?struct?irqaction?*?action)
          {
          ????int?status;
          ????int?cpu?=?smp_processor_id();

          ????irq_enter(cpu,?irq);

          ????status?=?1;?/*?Force?the?"do?bottom?halves"?bit?*/

          ????if?(!(action->flags?&?SA_INTERRUPT))?//?如果中斷處理能夠在打開中斷的情況下執(zhí)行,?那么就打開中斷
          ????????__sti();

          ????do?{
          ????????status?|=?action->flags;
          ????????action->handler(irq,?action->dev_id,?regs);
          ????????action?=?action->next;
          ????}?while?(action);
          ????if?(status?&?SA_SAMPLE_RANDOM)
          ????????add_interrupt_randomness(irq);
          ????__cli();

          ????irq_exit(cpu,?irq);

          ????return?status;
          }

          handle_IRQ_event()?函數(shù)非常簡(jiǎn)單,就是遍歷 action 鏈表并且執(zhí)行其中的處理函數(shù),比如對(duì)于?時(shí)鐘中斷?就是調(diào)用?timer_interrupt()?函數(shù)。這里要注意的是,如果中斷處理過(guò)程能夠開啟中斷的,那么就把中斷打開(因?yàn)镃PU接收到中斷信號(hào)時(shí)會(huì)關(guān)閉中斷)。

          中斷處理 - 下半部(軟中斷)

          由于中斷處理一般在關(guān)閉中斷的情況下執(zhí)行,所以中斷處理不能太耗時(shí),否則后續(xù)發(fā)生的中斷就不能實(shí)時(shí)地被處理。鑒于這個(gè)原因,Linux把中斷處理分為兩個(gè)部分,上半部?和?下半部上半部?在前面已經(jīng)介紹過(guò),接下來(lái)就介紹一下?下半部?的執(zhí)行。

          一般中斷?上半部?只會(huì)做一些最基礎(chǔ)的操作(比如從網(wǎng)卡中復(fù)制數(shù)據(jù)到緩存中),然后對(duì)要執(zhí)行的中斷?下半部?進(jìn)行標(biāo)識(shí),標(biāo)識(shí)完調(diào)用?do_softirq()?函數(shù)進(jìn)行處理。

          softirq機(jī)制

          中斷下半部?由?softirq(軟中斷)?機(jī)制來(lái)實(shí)現(xiàn)的,在Linux內(nèi)核中,有一個(gè)名為?softirq_vec?的數(shù)組,如下:

          static?struct?softirq_action?softirq_vec[32];

          其類型為?softirq_action?結(jié)構(gòu),定義如下:

          struct?softirq_action
          {

          ????void????(*action)(struct?softirq_action?*);
          ????void????*data;
          };

          softirq_vec?數(shù)組是?softirq?機(jī)制的核心,softirq_vec?數(shù)組每個(gè)元素代表一種軟中斷。但在Linux中只定義了四種軟中斷,如下:

          enum
          {
          ????HI_SOFTIRQ=0,
          ????NET_TX_SOFTIRQ,
          ????NET_RX_SOFTIRQ,
          ????TASKLET_SOFTIRQ
          };

          HI_SOFTIRQ?是高優(yōu)先級(jí)tasklet,而?TASKLET_SOFTIRQ?是普通tasklet,tasklet是基于softirq機(jī)制的一種任務(wù)隊(duì)列(下面會(huì)介紹)。NET_TX_SOFTIRQ?和?NET_RX_SOFTIRQ?特定用于網(wǎng)絡(luò)子模塊的軟中斷(不作介紹)。

          注冊(cè)softirq處理函數(shù)

          要注冊(cè)一個(gè)softirq處理函數(shù),可以通過(guò)?open_softirq()?函數(shù)來(lái)進(jìn)行,代碼如下:

          void?open_softirq(int?nr,?void?(*action)(struct?softirq_action*),?void?*data)
          {
          ????unsigned?long?flags;
          ????int?i;

          ????spin_lock_irqsave(&softirq_mask_lock,?flags);
          ????softirq_vec[nr].data?=?data;
          ????softirq_vec[nr].action?=?action;

          ????for?(i=0;?i????????softirq_mask(i)?|=?(1<????spin_unlock_irqrestore(&softirq_mask_lock,?flags);
          }

          open_softirq()?函數(shù)的主要工作就是向?softirq_vec?數(shù)組添加一個(gè)softirq處理函數(shù)。

          Linux在系統(tǒng)初始化時(shí)注冊(cè)了兩種softirq處理函數(shù),分別為?TASKLET_SOFTIRQ?和?HI_SOFTIRQ

          void?__init?softirq_init()
          {
          ????...
          ????open_softirq(TASKLET_SOFTIRQ,?tasklet_action,?NULL);
          ????open_softirq(HI_SOFTIRQ,?tasklet_hi_action,?NULL);
          }

          處理softirq

          處理softirq是通過(guò)?do_softirq()?函數(shù)實(shí)現(xiàn),代碼如下:

          asmlinkage?void?do_softirq()
          {
          ????int?cpu?=?smp_processor_id();
          ????__u32?active,?mask;

          ????if?(in_interrupt())
          ????????return;

          ????local_bh_disable();

          ????local_irq_disable();
          ????mask?=?softirq_mask(cpu);
          ????active?=?softirq_active(cpu)?&?mask;

          ????if?(active)?{
          ????????struct?softirq_action?*h;

          restart:
          ????????softirq_active(cpu)?&=?~active;

          ????????local_irq_enable();

          ????????h?=?softirq_vec;
          ????????mask?&=?~active;

          ????????do?{
          ????????????if?(active?&?1)
          ????????????????h->action(h);
          ????????????h++;
          ????????????active?>>=?1;
          ????????}?while?(active);

          ????????local_irq_disable();

          ????????active?=?softirq_active(cpu);
          ????????if?((active?&=?mask)?!=?0)
          ????????????goto?retry;
          ????}

          ????local_bh_enable();

          ????return;

          retry:
          ????goto?restart;
          }

          前面說(shuō)了?softirq_vec?數(shù)組有32個(gè)元素,每個(gè)元素對(duì)應(yīng)一種類型的softirq,那么Linux怎么知道哪種softirq需要被執(zhí)行呢?在Linux中,每個(gè)CPU都有一個(gè)類型為?irq_cpustat_t?結(jié)構(gòu)的變量,irq_cpustat_t?結(jié)構(gòu)定義如下:

          typedef?struct?{
          ????unsigned?int?__softirq_active;
          ????unsigned?int?__softirq_mask;
          ????...
          }?irq_cpustat_t;

          其中?__softirq_active?字段表示有哪種softirq觸發(fā)了(int類型有32個(gè)位,每一個(gè)位代表一種softirq),而?__softirq_mask?字段表示哪種softirq被屏蔽了。Linux通過(guò)?__softirq_active?這個(gè)字段得知哪種softirq需要執(zhí)行(只需要把對(duì)應(yīng)位設(shè)置為1)。

          所以,do_softirq()?函數(shù)首先通過(guò)?softirq_mask(cpu)?來(lái)獲取當(dāng)前CPU對(duì)應(yīng)被屏蔽的softirq,而?softirq_active(cpu) & mask?就是獲取需要執(zhí)行的softirq,然后就通過(guò)對(duì)比?__softirq_active?字段的各個(gè)位來(lái)判斷是否要執(zhí)行該類型的softirq。

          tasklet機(jī)制

          前面說(shuō)了,tasklet機(jī)制是基于softirq機(jī)制的,tasklet機(jī)制其實(shí)就是一個(gè)任務(wù)隊(duì)列,然后通過(guò)softirq執(zhí)行。在Linux內(nèi)核中有兩種tasklet,一種是高優(yōu)先級(jí)tasklet,一種是普通tasklet。這兩種tasklet的實(shí)現(xiàn)基本一致,唯一不同的就是執(zhí)行的優(yōu)先級(jí),高優(yōu)先級(jí)tasklet會(huì)先于普通tasklet執(zhí)行。

          tasklet本質(zhì)是一個(gè)隊(duì)列,通過(guò)結(jié)構(gòu)體?tasklet_head?存儲(chǔ),并且每個(gè)CPU有一個(gè)這樣的隊(duì)列,我們來(lái)看看結(jié)構(gòu)體?tasklet_head?的定義:

          struct?tasklet_head
          {

          ????struct?tasklet_struct?*list;
          };

          struct?tasklet_struct
          {

          ????struct?tasklet_struct?*next;
          ????unsigned?long?state;
          ????atomic_t?count;
          ????void?(*func)(unsigned?long);
          ????unsigned?long?data;
          };

          從?tasklet_head?的定義可以知道,tasklet_head?結(jié)構(gòu)是?tasklet_struct?結(jié)構(gòu)隊(duì)列的頭部,而?tasklet_struct?結(jié)構(gòu)的?func?字段正式任務(wù)要執(zhí)行的函數(shù)指針。Linux定義了兩種的tasklet隊(duì)列,分別為?tasklet_vec?和?tasklet_hi_vec,定義如下:

          struct?tasklet_head?tasklet_vec[NR_CPUS];
          struct?tasklet_head?tasklet_hi_vec[NR_CPUS];

          可以看出,tasklet_vec?和?tasklet_hi_vec?都是數(shù)組,數(shù)組的元素個(gè)數(shù)為CPU的核心數(shù),也就是每個(gè)CPU核心都有一個(gè)高優(yōu)先級(jí)tasklet隊(duì)列和一個(gè)普通tasklet隊(duì)列。

          調(diào)度tasklet

          如果我們有一個(gè)tasklet需要執(zhí)行,那么高優(yōu)先級(jí)tasklet可以通過(guò)?tasklet_hi_schedule()?函數(shù)調(diào)度,而普通tasklet可以通過(guò)?tasklet_schedule()?調(diào)度。這兩個(gè)函數(shù)基本一樣,所以我們只分析其中一個(gè):

          static?inline?void?tasklet_hi_schedule(struct?tasklet_struct?*t)
          {
          ????if?(!test_and_set_bit(TASKLET_STATE_SCHED,?&t->state))?{
          ????????int?cpu?=?smp_processor_id();
          ????????unsigned?long?flags;

          ????????local_irq_save(flags);
          ????????t->next?=?tasklet_hi_vec[cpu].list;
          ????????tasklet_hi_vec[cpu].list?=?t;
          ????????__cpu_raise_softirq(cpu,?HI_SOFTIRQ);
          ????????local_irq_restore(flags);
          ????}
          }

          函數(shù)參數(shù)的類型是?tasklet_struct?結(jié)構(gòu)的指針,表示需要執(zhí)行的tasklet結(jié)構(gòu)。tasklet_hi_schedule()?函數(shù)首先判斷這個(gè)tasklet是否已經(jīng)被添加到隊(duì)列中,如果不是就添加到?tasklet_hi_vec?隊(duì)列中,并且通過(guò)調(diào)用?__cpu_raise_softirq(cpu, HI_SOFTIRQ)?來(lái)告訴softirq需要執(zhí)行?HI_SOFTIRQ?類型的softirq,我們來(lái)看看?__cpu_raise_softirq()?函數(shù)的實(shí)現(xiàn):

          static?inline?void?__cpu_raise_softirq(int?cpu,?int?nr)
          {
          ????softirq_active(cpu)?|=?(1<}

          可以看出,__cpu_raise_softirq()?函數(shù)就是把?irq_cpustat_t?結(jié)構(gòu)的?__softirq_active?字段的?nr位?設(shè)置為1。對(duì)于?tasklet_hi_schedule()?函數(shù)就是把?HI_SOFTIRQ?位(0位)設(shè)置為1。

          前面我們也介紹過(guò),Linux在初始化時(shí)會(huì)注冊(cè)兩種softirq,TASKLET_SOFTIRQ?和?HI_SOFTIRQ

          void?__init?softirq_init()
          {
          ????...
          ????open_softirq(TASKLET_SOFTIRQ,?tasklet_action,?NULL);
          ????open_softirq(HI_SOFTIRQ,?tasklet_hi_action,?NULL);
          }

          所以當(dāng)把?irq_cpustat_t?結(jié)構(gòu)的?__softirq_active?字段的?HI_SOFTIRQ?位(0位)設(shè)置為1時(shí),softirq機(jī)制就會(huì)執(zhí)行?tasklet_hi_action()?函數(shù),我們來(lái)看看?tasklet_hi_action()?函數(shù)的實(shí)現(xiàn):

          static?void?tasklet_hi_action(struct?softirq_action?*a)
          {
          ????int?cpu?=?smp_processor_id();
          ????struct?tasklet_struct?*list;

          ????local_irq_disable();
          ????list?=?tasklet_hi_vec[cpu].list;
          ????tasklet_hi_vec[cpu].list?=?NULL;
          ????local_irq_enable();

          ????while?(list?!=?NULL)?{
          ????????struct?tasklet_struct?*t?=?list;

          ????????list?=?list->next;

          ????????if?(tasklet_trylock(t))?{
          ????????????if?(atomic_read(&t->count)?==?0)?{
          ????????????????clear_bit(TASKLET_STATE_SCHED,?&t->state);

          ????????????????t->func(t->data);??//?調(diào)用tasklet處理函數(shù)
          ????????????????tasklet_unlock(t);
          ????????????????continue;
          ????????????}
          ????????????tasklet_unlock(t);
          ????????}
          ????????...
          ????}
          }

          tasklet_hi_action()?函數(shù)非常簡(jiǎn)單,就是遍歷?tasklet_hi_vec?隊(duì)列并且執(zhí)行其中tasklet的處理函數(shù)。


          瀏覽 54
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人毛片网站视频 | 欧美日韩一级黄色电影 | 欧美成人精品在线播放 | 在线观看黄a| 亚洲日本黄色片 |