偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

一文完全讀懂 Linux 中斷處理

系統(tǒng) Linux
中斷 是為了解決外部設(shè)備完成某些工作后通知CPU的一種機(jī)制(譬如硬盤(pán)完成讀寫(xiě)操作后通過(guò)中斷告知CPU已經(jīng)完成)。

[[432723]]

什么是中斷

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

從物理學(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í),這些值通常被稱(chēng)為中斷請(qǐng)求線。

中斷控制器

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

常見(jiàn)的中斷控制器有兩種:

可編程中斷控制器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è),如圖下所示。

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),如圖下所示。

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,可以在命令行輸入如下命令:

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

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

中斷分類(lèi)

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

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

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

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

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

表:中斷類(lèi)別及其行為

類(lèi)別 原因 異步/同步 返回行為
中斷 來(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è)。如下圖:

8259A

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

  1. typedef struct { 
  2.     unsigned int status;        /* IRQ status */ 
  3.     hw_irq_controller *handler; 
  4.     struct irqaction *action;   /* IRQ action list */ 
  5.     unsigned int depth;         /* nested irq disables */ 
  6.     spinlock_t lock; 
  7. } irq_desc_t; 

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

  • status: IRQ線的狀態(tài)。
  • handler: 類(lèi)型為 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: 類(lèi)型為 irqaction 結(jié)構(gòu),中斷信號(hào)的處理入口。由于一條IRQ線可以被多個(gè)硬件共享,所以 action 是一個(gè)鏈表,每個(gè) action 代表一個(gè)硬件的中斷處理入口。
  • depth: 防止多次開(kāi)啟和關(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):

  1. struct irqaction { 
  2.     void (*handler)(int, void *, struct pt_regs *); 
  3.     unsigned long flags; 
  4.     unsigned long mask; 
  5.     const char *name
  6.     void *dev_id; 
  7.     struct irqaction *next
  8. }; 

下面說(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)系如下圖:

irq_desc_t

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

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

  1. int setup_irq(unsigned int irq, struct irqaction * new) 
  2.     int shared = 0; 
  3.     unsigned long flags; 
  4.     struct irqaction *old, **p; 
  5.     irq_desc_t *desc = irq_desc + irq; 
  6.     ... 
  7.     spin_lock_irqsave(&desc->lock,flags); 
  8.     p = &desc->action
  9.     if ((old = *p) != NULL) { 
  10.         if (!(old->flags & new->flags & SA_SHIRQ)) { 
  11.             spin_unlock_irqrestore(&desc->lock,flags); 
  12.             return -EBUSY; 
  13.         } 
  14.  
  15.         do { 
  16.             p = &old->next
  17.             old = *p; 
  18.         } while (old); 
  19.         shared = 1; 
  20.     } 
  21.  
  22.     *p = new; 
  23.  
  24.     if (!shared) { 
  25.         desc->depth = 0; 
  26.         desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING); 
  27.         desc->handler->startup(irq); 
  28.     } 
  29.     spin_unlock_irqrestore(&desc->lock,flags); 
  30.  
  31.     register_irq_proc(irq); // 注冊(cè)proc文件系統(tǒng) 
  32.     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 字段沒(méi)有設(shè)置 SA_SHIRQ 標(biāo)志),那么就返回 EBUSY 錯(cuò)誤。

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

  1. static struct irqaction irq0  = { timer_interrupt, SA_INTERRUPT, 0, "timer"NULLNULL}; 
  2.  
  3. void __init time_init(void) 
  4.     ... 
  5.     setup_irq(0, &irq0); 

可以看到,時(shí)鐘中斷處理入口的IRQ號(hào)為0,處理函數(shù)為 timer_interrupt(),并且不支持共享IRQ線(flags 字段沒(méi)有設(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ù)代碼如下:

  1. asmlinkage unsigned int do_IRQ(struct pt_regs regs) 
  2.     int irq = regs.orig_eax & 0xff; /* 獲取IRQ號(hào)  */ 
  3.     int cpu = smp_processor_id(); 
  4.     irq_desc_t *desc = irq_desc + irq; 
  5.     struct irqaction * action
  6.     unsigned int status; 
  7.  
  8.     kstat.irqs[cpu][irq]++; 
  9.     spin_lock(&desc->lock); 
  10.     desc->handler->ack(irq); 
  11.  
  12.     status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING); 
  13.     status |= IRQ_PENDING; /* we _want_ to handle it */ 
  14.  
  15.     action = NULL
  16.     if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) { // 當(dāng)前IRQ不在處理中 
  17.         action = desc->action;    // 獲取 action 鏈表 
  18.         status &= ~IRQ_PENDING;   // 去除IRQ_PENDING標(biāo)志, 這個(gè)標(biāo)志用于記錄是否在處理IRQ請(qǐng)求的時(shí)候又發(fā)生了中斷 
  19.         status |= IRQ_INPROGRESS; // 設(shè)置IRQ_INPROGRESS標(biāo)志, 表示正在處理IRQ 
  20.     } 
  21.     desc->status = status; 
  22.  
  23.     if (!action)  // 如果上一次IRQ還沒(méi)完成, 直接退出 
  24.         goto out
  25.  
  26.     for (;;) { 
  27.         spin_unlock(&desc->lock); 
  28.         handle_IRQ_event(irq, &regs, action); // 處理IRQ請(qǐng)求 
  29.         spin_lock(&desc->lock); 
  30.          
  31.         if (!(desc->status & IRQ_PENDING)) // 如果在處理IRQ請(qǐng)求的時(shí)候又發(fā)生了中斷, 繼續(xù)處理IRQ請(qǐng)求 
  32.             break; 
  33.         desc->status &= ~IRQ_PENDING; 
  34.     } 
  35.     desc->status &= ~IRQ_INPROGRESS; 
  36. out
  37.  
  38.     desc->handler->end(irq); 
  39.     spin_unlock(&desc->lock); 
  40.  
  41.     if (softirq_active(cpu) & softirq_mask(cpu)) 
  42.         do_softirq(); // 中斷下半部處理 
  43.     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):

  1. int handle_IRQ_event(unsigned int irq, struct pt_regs * regs, struct irqaction * action
  2.     int status; 
  3.     int cpu = smp_processor_id(); 
  4.  
  5.     irq_enter(cpu, irq); 
  6.  
  7.     status = 1; /* Force the "do bottom halves" bit */ 
  8.  
  9.     if (!(action->flags & SA_INTERRUPT)) // 如果中斷處理能夠在打開(kāi)中斷的情況下執(zhí)行, 那么就打開(kāi)中斷 
  10.         __sti(); 
  11.  
  12.     do { 
  13.         status |= action->flags; 
  14.         action->handler(irq, action->dev_id, regs); 
  15.         action = action->next
  16.     } while (action); 
  17.     if (status & SA_SAMPLE_RANDOM) 
  18.         add_interrupt_randomness(irq); 
  19.     __cli(); 
  20.  
  21.     irq_exit(cpu, irq); 
  22.  
  23.     return status; 

handle_IRQ_event() 函數(shù)非常簡(jiǎn)單,就是遍歷 action 鏈表并且執(zhí)行其中的處理函數(shù),比如對(duì)于 時(shí)鐘中斷 就是調(diào)用 timer_interrupt() 函數(shù)。這里要注意的是,如果中斷處理過(guò)程能夠開(kāi)啟中斷的,那么就把中斷打開(kāi)(因?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ù)組,如下:

  1. static struct softirq_action softirq_vec[32]; 

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

  1. struct softirq_action 
  2.     void    (*action)(struct softirq_action *); 
  3.     void    *data; 
  4. }; 

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

  1. enum 
  2.     HI_SOFTIRQ=0, 
  3.     NET_TX_SOFTIRQ, 
  4.     NET_RX_SOFTIRQ, 
  5.     TASKLET_SOFTIRQ 
  6. }; 

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)行,代碼如下:

  1. void open_softirq(int nr, void (*action)(struct softirq_action*), void *data) 
  2.     unsigned long flags; 
  3.     int i; 
  4.  
  5.     spin_lock_irqsave(&softirq_mask_lock, flags); 
  6.     softirq_vec[nr].data = data; 
  7.     softirq_vec[nr].action = action
  8.  
  9.     for (i=0; i<NR_CPUS; i++) 
  10.         softirq_mask(i) |= (1<<nr); 
  11.     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:

  1. void __init softirq_init() 
  2.     ... 
  3.     open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); 
  4.     open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); 

處理softirq

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

  1. asmlinkage void do_softirq() 
  2.     int cpu = smp_processor_id(); 
  3.     __u32 active, mask; 
  4.  
  5.     if (in_interrupt()) 
  6.         return
  7.  
  8.     local_bh_disable(); 
  9.  
  10.     local_irq_disable(); 
  11.     mask = softirq_mask(cpu); 
  12.     active = softirq_active(cpu) & mask; 
  13.  
  14.     if (active) { 
  15.         struct softirq_action *h; 
  16.  
  17. restart: 
  18.         softirq_active(cpu) &= ~active; 
  19.  
  20.         local_irq_enable(); 
  21.  
  22.         h = softirq_vec; 
  23.         mask &= ~active; 
  24.  
  25.         do { 
  26.             if (active & 1) 
  27.                 h->action(h); 
  28.             h++; 
  29.             active >>= 1; 
  30.         } while (active); 
  31.  
  32.         local_irq_disable(); 
  33.  
  34.         active = softirq_active(cpu); 
  35.         if ((active &= mask) != 0) 
  36.             goto retry; 
  37.     } 
  38.  
  39.     local_bh_enable(); 
  40.  
  41.     return
  42.  
  43. retry: 
  44.     goto restart; 

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

  1. typedef struct { 
  2.     unsigned int __softirq_active; 
  3.     unsigned int __softirq_mask; 
  4.     ... 
  5. } irq_cpustat_t; 

其中 __softirq_active 字段表示有哪種softirq觸發(fā)了(int類(lèi)型有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í)行該類(lèi)型的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 的定義:

  1. struct tasklet_head 
  2.     struct tasklet_struct *list; 
  3. }; 
  4.  
  5. struct tasklet_struct 
  6.     struct tasklet_struct *next
  7.     unsigned long state; 
  8.     atomic_t count
  9.     void (*func)(unsigned long); 
  10.     unsigned long data; 
  11. }; 

從 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,定義如下:

  1. struct tasklet_head tasklet_vec[NR_CPUS]; 
  2.  
  3. 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è):

  1. static inline void tasklet_hi_schedule(struct tasklet_struct *t) 
  2.     if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) { 
  3.         int cpu = smp_processor_id(); 
  4.         unsigned long flags; 
  5.  
  6.         local_irq_save(flags); 
  7.         t->next = tasklet_hi_vec[cpu].list; 
  8.         tasklet_hi_vec[cpu].list = t; 
  9.         __cpu_raise_softirq(cpu, HI_SOFTIRQ); 
  10.         local_irq_restore(flags); 
  11.     } 

函數(shù)參數(shù)的類(lèi)型是 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 類(lèi)型的softirq,我們來(lái)看看 __cpu_raise_softirq() 函數(shù)的實(shí)現(xiàn):

  1. static inline void __cpu_raise_softirq(int cpu, int nr) 
  2.     softirq_active(cpu) |= (1<<nr); 

可以看出,__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:

  1. void __init softirq_init() 
  2.     ... 
  3.     open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); 
  4.     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):

  1. static void tasklet_hi_action(struct softirq_action *a) 
  2.     int cpu = smp_processor_id(); 
  3.     struct tasklet_struct *list; 
  4.  
  5.     local_irq_disable(); 
  6.     list = tasklet_hi_vec[cpu].list; 
  7.     tasklet_hi_vec[cpu].list = NULL
  8.     local_irq_enable(); 
  9.  
  10.     while (list != NULL) { 
  11.         struct tasklet_struct *t = list; 
  12.  
  13.         list = list->next
  14.  
  15.         if (tasklet_trylock(t)) { 
  16.             if (atomic_read(&t->count) == 0) { 
  17.                 clear_bit(TASKLET_STATE_SCHED, &t->state); 
  18.  
  19.                 t->func(t->data);  // 調(diào)用tasklet處理函數(shù) 
  20.                 tasklet_unlock(t); 
  21.                 continue
  22.             } 
  23.             tasklet_unlock(t); 
  24.         } 
  25.         ... 
  26.     } 

 

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

 

責(zé)任編輯:武曉燕 來(lái)源: Linux內(nèi)核那些事
相關(guān)推薦

2023-12-22 19:59:15

2021-08-04 16:06:45

DataOps智領(lǐng)云

2018-09-28 14:06:25

前端緩存后端

2022-09-22 09:00:46

CSS單位

2022-11-06 21:14:02

數(shù)據(jù)驅(qū)動(dòng)架構(gòu)數(shù)據(jù)

2025-04-03 10:56:47

2021-08-11 10:10:26

Linux定時(shí)器數(shù)組

2023-11-27 17:35:48

ComponentWeb外層

2021-12-29 18:00:19

無(wú)損網(wǎng)絡(luò)網(wǎng)絡(luò)通信網(wǎng)絡(luò)

2023-05-20 17:58:31

低代碼軟件

2025-10-14 09:01:20

2022-10-20 08:01:23

2022-07-05 06:30:54

云網(wǎng)絡(luò)網(wǎng)絡(luò)云原生

2022-07-26 00:00:03

語(yǔ)言模型人工智能

2022-12-01 17:23:45

2021-10-20 07:18:51

Linux延時(shí)隊(duì)列

2021-04-24 09:02:36

Linux 內(nèi)存分配

2024-12-27 14:45:59

2024-01-03 08:54:17

Kubernetes策略工具
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)