linux軟中斷和工作佇列的作用是什麼

2022-04-14 19:00:48

linux中軟中斷和工作佇列的作用是實現中斷處理。軟中斷和工作佇列是中斷上下部機制中的下半部實現機制。軟中斷不能睡眠、不能阻塞、不能程序間切換,只能被硬體中斷打斷;而工作佇列可以睡眠,也能被阻塞,能夠在不同的程序間切換,以完成不同的工作。

本教學操作環境:linux5.9.8系統、Dell G3電腦。

linux中軟中斷和工作佇列的作用是實現中斷處理。

1.中斷概念

中斷是指在CPU正常執行期間,由於內外部事件或由程式預先安排的事件引起的CPU暫時停止正在執行的程式,轉而為該內部或外部事件或預先安排的事件服務的程式中去,服務完畢後再返回去繼續執行被暫時中斷的程式。Linux中通常分為外部中斷(又叫硬體中斷)和內部中斷(又叫異常)。

在實地址模式中,CPU把記憶體中從0開始的1KB空間作為一箇中斷向量表。表中的每一項佔4個位元組。但是在保護模式中,有這4個位元組的表項構成的中斷向量表不滿足實際需求,於是根據反映模式切換的資訊和偏移量的足夠使得中斷向量表的表項由8個位元組組成,而中斷向量表也叫做了中斷描述符表(IDT)。在CPU中增加了一個用來描述中斷描述符表暫存器(IDTR),用來儲存中斷描述符表的起始地址。

2. Linux中斷處理

2.1 系統中斷號

  由上述中斷定義可知,系統中斷向量表中共可儲存256箇中斷向量入口,即IDT中包含的256箇中斷描述符(對應256箇中斷向量)。

  而0-31號中斷向量被intel公司保留用來處理異常事件,不能另作它用。對這 0-31號中斷向量,作業系統只需提供異常的處理程式,當產生一個異常時,處理機就會自動把控制轉移到相應的處理程式的入口,執行相應的處理程式;而事實 上,對於這32個處理異常的中斷向量,2.6版本的 Linux只提供了0-17號中斷向量的處理程式,其對應處理程式參見下表、中斷向量和異常事件對應表;也就是說,17-31號中斷向量是空著未用的。

中斷向量號異常事件Linux的處理程式
0除法錯誤pide_error
1偵錯異常Debug
2NMI中斷Nmi
3單位元組,int 3Int3
4溢位Overflow
5邊界監測中斷Bounds
6無效操作碼Invalid_op
7裝置不可用Device_not_available
8雙重故障Double_fault
9協處理器段溢位Coprocessor_segment_overrun
10無效TSSIncalid_tss
11缺段中斷Segment_not_present
12堆疊異常Stack_segment
13一般保護異常General_protection
14頁異常Page_fault
15 (intel保留)Spurious_interrupt_bug
16協處理器出錯Coprocessor_error
17對齊檢查中斷Alignment_check

  0-31號中斷向量已被保留,那麼剩下32-255共224箇中斷向量可用。 這224箇中斷向量又是怎麼分配的呢?2.6版本的Linux中,除了0x80 (SYSCALL_VECTOR)用作系統呼叫總入口之外,其他都用在外部硬體中斷源上,其中包括可程式化中斷控制器8259A的15個irq;事實上,當 沒有定義CONFIG_X86_IO_APIC時,其他223(除0x80外)箇中斷向量,只利用了從32號開始的15個,其它208個空著未用。

 2.2 中斷請求

  2.2.1 中斷請求概述

  外部裝置當需要作業系統做相關的事情的時候,會產生相應的中斷。

  裝置通過相應的中斷線向中斷控制器傳送高電平以產生中斷訊號,而作業系統則會從中斷控制器的狀態位取得那根中斷線上產生的中斷。而且只有在裝置在對某一條中斷線擁有控制權,才可以向這條中斷線上傳送訊號。也由於現在的外設越來越多,中斷線又是很寶貴的資源不可能被一一對應。因此在使用中斷線前,就得對相應的中斷線進行申請。無論採用共用中斷方式還是獨佔一箇中斷,申請過程都是先講所有的中斷線進行掃描,得出哪些沒有別佔用,從其中選擇一個作為該裝置的IRQ。其次,通過中斷申請函數申請相應的IRQ。最後,根據申請結果檢視中斷是否能夠被執行。

  2.2.2 中斷相關結構

  中斷中核心處理資料結構為irq_desc,它完整的描述了一條中斷線,Linux 2.6。22.6中原始碼如下。

  irq_desc定義在include/linux/irq.h中

/**
 * struct irq_desc - interrupt descriptor
 *
 * @handle_irq:        highlevel irq-events handler [if NULL, __do_IRQ()]
 * @chip:        low level interrupt hardware access
 * @msi_desc:        MSI descriptor
 * @handler_data:    per-IRQ data for the irq_chip methods
 * @chip_data:        platform-specific per-chip private data for the chip
 *            methods, to allow shared chip implementations
 * @action:        the irq action chain
 * @status:        status information
 * @depth:        disable-depth, for nested irq_disable() calls
 * @wake_depth:        enable depth, for multiple set_irq_wake() callers
 * @irq_count:        stats field to detect stalled irqs
 * @irqs_unhandled:    stats field for spurious unhandled interrupts
 * @lock:        locking for SMP
 * @affinity:        IRQ affinity on SMP
 * @cpu:        cpu index useful for balancing
 * @pending_mask:    pending rebalanced interrupts
 * @dir:        /proc/irq/ procfs entry
 * @affinity_entry:    /proc/irq/smp_affinity procfs entry on SMP
 * @name:        flow handler name for /proc/interrupts output */struct irq_desc {
    irq_flow_handler_t    handle_irq;    struct irq_chip        *chip;    struct msi_desc        *msi_desc;    void            *handler_data;    void            *chip_data;    struct irqaction    *action;    /* IRQ action list */
    unsigned int        status;        /* IRQ status */

    unsigned int        depth;        /* nested irq disables */
    unsigned int        wake_depth;    /* nested wake enables */
    unsigned int        irq_count;    /* For detecting broken IRQs */
    unsigned int        irqs_unhandled;
    spinlock_t        lock;
#ifdef CONFIG_SMP
    cpumask_t        affinity;
    unsigned int        cpu;#endif#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
    cpumask_t        pending_mask;#endif#ifdef CONFIG_PROC_FS    struct proc_dir_entry    *dir;#endif
    const char        *name;
} ____cacheline_internodealigned_in_smp;

irq_desc

  其相關聯的幾個結構體如下:

  定義在include/linux/ interrupt.h中的中斷行動結構體:struct irqaction

struct irqaction {
    irq_handler_t handler;
    unsigned long flags;
    cpumask_t mask;    const char *name;    void *dev_id;    struct irqaction *next;    int irq;    struct proc_dir_entry *dir;
};

  定義在include/linux 中的:irq_chip 晶片相關的處理常式集合

/**
 * struct irq_chip - hardware interrupt chip descriptor
 *
 * @name:        name for /proc/interrupts
 * @startup:        start up the interrupt (defaults to ->enable if NULL)
 * @shutdown:        shut down the interrupt (defaults to ->disable if NULL)
 * @enable:        enable the interrupt (defaults to chip->unmask if NULL)
 * @disable:        disable the interrupt (defaults to chip->mask if NULL)
 * @ack:        start of a new interrupt
 * @mask:        mask an interrupt source
 * @mask_ack:        ack and mask an interrupt source
 * @unmask:        unmask an interrupt source
 * @eoi:        end of interrupt - chip level
 * @end:        end of interrupt - flow level
 * @set_affinity:    set the CPU affinity on SMP machines
 * @retrigger:        resend an IRQ to the CPU
 * @set_type:        set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
 * @set_wake:        enable/disable power-management wake-on of an IRQ
 *
 * @release:        release function solely used by UML
 * @typename:        obsoleted by name, kept as migration helper */struct irq_chip {    const char    *name;
    unsigned int    (*startup)(unsigned int irq);  //中斷開始    void        (*shutdown)(unsigned int irq);    //中斷關閉    void        (*enable)(unsigned int irq);      //中斷使能    void        (*disable)(unsigned int irq);    //中斷禁用    void        (*ack)(unsigned int irq);    void        (*mask)(unsigned int irq);    void        (*mask_ack)(unsigned int irq);    void        (*unmask)(unsigned int irq);    void        (*eoi)(unsigned int irq);    void        (*end)(unsigned int irq);    void        (*set_affinity)(unsigned int irq, cpumask_t dest);    int        (*retrigger)(unsigned int irq);    int        (*set_type)(unsigned int irq, unsigned int flow_type);    int        (*set_wake)(unsigned int irq, unsigned int on);    /* Currently used only by UML, might disappear one day.*/#ifdef CONFIG_IRQ_RELEASE_METHOD    void        (*release)(unsigned int irq, void *dev_id);#endif
    /*
     * For compatibility, ->typename is copied into ->name.
     * Will disappear.     */
    const char    *typename;
};

2.2.3 中斷請求實現

上下半部機制

  我們期望讓中斷處理程式執行得快,並想讓它完成的工作量多,這兩個目標相互制約,如何解決——上下半部機制

  我們把中斷處理切為兩半。中斷處理程式是上半部——接受中斷,他就立即開始執行,但只有做嚴格時限的工作。能夠被允許稍後完成的工作會推遲到下半部去,此後,在合適的時機,下半部會被開終端執行。上半部簡單快速,執行時禁止一些或者全部中斷

  下半部稍後執行,而且執行期間可以響應所有的中斷。這種設計可以使系統處於中斷遮蔽狀態的時間儘可能的短,以此來提高系統的響應能力。上半部只有中斷處理程式機制,而下半部的實現有軟中斷實現,tasklet實現和工作佇列實現。

  我們用網路卡來解釋一下這兩半。當網路卡接受到封包時,通知核心,觸發中斷,所謂的上半部就是,及時讀取封包到記憶體,防止因為延遲導致丟失,這是很急迫的工作。讀到記憶體後,對這些資料的處理不再緊迫,此時核心可以去執行中斷前執行的程式,而對網路封包的處理則交給下半部處理。

上下半部劃分原則

  1) 如果一個任務對時間非常敏感,將其放在中斷處理程式中執行;

  2) 如果一個任務和硬體有關,將其放在中斷處理程式中執行;

  3) 如果一個任務要保證不被其他中斷打斷,將其放在中斷處理程式中執行;

  4) 其他所有任務,考慮放置在下半部執行。

下半部實現機制之軟中斷

  軟中斷作為下半部機制的代表,是隨著SMPshare memory processor)的出現應運而生的,它也是tasklet實現的基礎(tasklet實際上只是在軟中斷的基礎上新增了一定的機制)。軟中斷一般是可延遲函數的總稱,有時候也包括了tasklet(請讀者在遇到的時候根據上下文推斷是否包含tasklet)。它的出現就是因為要滿足上面所提出的上半部和下半部的區別,使得對時間不敏感的任務延後執行,軟中斷執行中斷處理程式留給它去完成的剩餘任務,而且可以在多個CPU上並行執行,使得總的系統效率可以更高。它的特性包括:

  a)產生後並不是馬上可以執行,必須要等待核心的排程才能執行。軟中斷不能被自己打斷,只能被硬體中斷打斷(上半部)。

  b)可以並行執行在多個CPU上(即使同一型別的也可以)。所以軟中斷必須設計為可重入的函數(允許多個CPU同時操作),因此也需要使用自旋鎖來保護其資料結構。

下半部實現機制之tasklet

  tasklet是通過軟中斷實現的,所以它本身也是軟中斷。

  軟中斷用輪詢的方式處理。假如正好是最後一種中斷,則必須迴圈完所有的中斷型別,才能最終執行對應的處理常式。顯然當年開發人員為了保證輪詢的效率,於是限制中斷個數為32個。

  為了提高中斷處理數量,順道改進處理效率,於是產生了tasklet機制。

  Tasklet採用無差別的佇列機制,有中斷時才執行,免去了迴圈查表之苦。Tasklet作為一種新機制,顯然可以承擔更多的優點。正好這時候SMP越來越火了,因此又在tasklet中加入了SMP機制,保證同種中斷只能在一個cpu上執行。在軟中斷時代,顯然沒有這種考慮。因此同一種軟中斷可以在兩個cpu上同時執行,很可能造成衝突。

  總結下tasklet的優點:

  (1)無型別數量限制;

  (2)效率高,無需迴圈查表;

  (3)支援SMP機制;

  它的特性如下:

  1)一種特定型別的tasklet只能執行在一個CPU上,不能並行,只能序列執行。

  2)多個不同型別的tasklet可以並行在多個CPU上。

  3)軟中斷是靜態分配的,在核心編譯好之後,就不能改變。但tasklet就靈活許多,可以在執行時改變(比如新增模組時)。

下半部實現機制之工作佇列(work queue)

  上面我們介紹的可延遲函數執行在中斷上下文中(軟中斷的一個檢查點就是do_IRQ退出的時候),於是導致了一些問題:軟中斷不能睡眠、不能阻塞。由於中斷上下文出於核心態,沒有程序切換,所以如果軟中斷一旦睡眠或者阻塞,將無法退出這種狀態,導致核心會整個僵死。但可阻塞函數不能用在中斷上下文中實現,必須要執行在程序上下文中,例如存取磁碟資料塊的函數。因此,可阻塞函數不能用軟中斷來實現。但是它們往往又具有可延遲的特性。

  上面我們介紹的可延遲函數執行在中斷上下文中,於是導致了一些問題,說明它們不可掛起,也就是說軟中斷不能睡眠、不能阻塞,原因是由於中斷上下文出於核心態,沒有程序切換,所以如果軟中斷一旦睡眠或者阻塞,將無法退出這種狀態,導致核心會整個僵死。因此,可阻塞函數不能用軟中斷來實現。但是它們往往又具有可延遲的特性。而且由於是序列執行,因此只要有一個處理時間較長,則會導致其他中斷響應的延遲。為了完成這些不可能完成的任務,於是出現了工作佇列,它能夠在不同的程序間切換,以完成不同的工作。

  如果推後執行的任務需要睡眠,那麼就選擇工作佇列,如果不需要睡眠,那麼就選擇軟中斷或tasklet。工作佇列能執行在程序上下文,它將工作託付給一個核心執行緒。工作佇列說白了就是一組核心執行緒,作為中斷守護執行緒來使用。多箇中斷可以放在一個執行緒中,也可以每個中斷分配一個執行緒。我們用結構體workqueue_struct表示工作者執行緒,工作者執行緒是用核心執行緒實現的。而工作者執行緒是如何執行被推後的工作——有這樣一個連結串列,它由結構體work_struct組成,而這個work_struct則描述了一個工作,一旦這個工作被執行完,相應的work_struct物件就從連結串列上移去,當連結串列上不再有物件時,工作者執行緒就會繼續休眠。因為工作佇列是執行緒,所以我們可以使用所有可以線上程中使用的方法。

Linux軟中斷和工作佇列的作用是什麼

linux 中的軟中斷和工作佇列的作用是實現中斷處理;它們是中斷上下部機制中的下半部實現機制。

  1.軟中斷一般是可延遲函數的總稱,它不能睡眠,不能阻塞,它處於中斷上下文,不能程序間切換,軟中斷不能被自己打斷,只能被硬體中斷打斷(上半部),可以並行的執行在多個CPU上。所以軟中斷必須設計成可重入的函數,因此也需要自旋鎖來保護其資料結構。

  2.工作佇列中的函數處在程序上下文中,它可以睡眠,也能被阻塞,能夠在不同的程序間切換,以完成不同的工作。

可延遲函數和工作佇列都不能存取使用者的程序空間,可延時函數在執行時不可能有任何正在執行的程序,工作佇列的函數有核心程序執行,他不能存取使用者空間地址。

相關推薦:《Linux視訊教學

以上就是linux軟中斷和工作佇列的作用是什麼的詳細內容,更多請關注TW511.COM其它相關文章!