天天看點

Linux核心中斷系統處理機制-詳細分析一、中斷概述二、中斷資料處理結構三、Linux中斷機制四、多處理器系統中斷相關概念

日期 核心版本 架構 作者 内容
2018-8-23 Linux-2.6.32 X86 Bystander Linux核心中斷
  • 一、中斷概述

中斷是指在CPU正常運作期間,由于内外部事件或由程式預先安排的事件引起的CPU暫時停止正在運作的程式,轉而為該内部或外部事件或預先安排的事件服務的程式中去,服務完畢後再傳回去繼續運作被暫時中斷的程式。

1.1中斷類型

同步中斷由CPU本身産生,又稱為内部中斷。這裡同步是指中斷請求信号與代碼指令之間的同步執行,在一條指令執行完畢後,CPU才能進行中斷,不能在執行期間。是以也稱為異常(exception)。

異步中斷是由外部硬體裝置産生,又稱為外部中斷,與同步中斷相反,異步中斷可在任何時間産生,包括指令執行期間,是以也被稱為中斷(interrupt)。

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

從廣義上講,中斷又可分為四類:中斷、故障、陷阱、終止。這些類别之間的異同點請參考 表 1。

表 1:中斷類别及其行為
類别 原因 異步/同步 傳回行為
中斷 來自I/O裝置的信号 異步 總是傳回到下一條指令
陷阱 有意的異常 同步 總是傳回到下一條指令
故障 潛在可恢複的錯誤 同步 傳回到目前指令
終止 不可恢複的錯誤 同步 不會傳回

有些參考資料中按照中斷來源進行分類,如下圖所示(個人建議不采用這種方式):

Linux核心中斷系統處理機制-詳細分析一、中斷概述二、中斷資料處理結構三、Linux中斷機制四、多處理器系統中斷相關概念

                                                                     圖1-1

1.2區分中斷号與中斷向量

I/O裝置把中斷信号發送給中斷控制器(8259A)時與之相關聯的是一個中斷号,當中斷控制器把中斷信号發送給CPU時與之關聯的是一個中斷向量。換個角度分析就是中斷号是從中斷控制器層面劃分,中斷向量是從CPU層面劃分,是以中斷号與中斷向量之間存在一對一映射關系。在Intel X86中最大支援256種中斷,從0到255開始編号,這個8位的編号就是中斷向量。其中将0到31保留用于異常處理和不可屏蔽中斷。

  • 二、中斷資料處理結構

Linux核心中進行中斷主要有三個資料結構,irq_desc,irq_chip和irqaction。

在\include\linux\ irq.h中定義了

1)irq_desc用于描述IRQ線的屬性與狀态,被稱為中斷描述符。

/**

 * struct irq_desc - interrupt descriptor

 * @irq:           interrupt number for this descriptor

 * @timer_rand_state:    pointer to timer rand state struct

 * @kstat_irqs:        irq stats per cpu

 * @irq_2_iommu:  iommu with this irq

 * @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

 * @last_unhandled:      aging timer for unhandled count

 * @irqs_unhandled:      stats field for spurious unhandled interrupts

 * @lock:         locking for SMP

 * @affinity:            IRQ affinity on SMP

 * @node:       node index useful for balancing

 * @pending_mask: pending rebalanced interrupts

 * @threads_active: number of irqaction threads currently running

 * @wait_for_threads:    wait queue for sync_irq to wait for threaded handlers

 * @dir:           /proc/irq/ procfs entry

 * @name:             flow handler name for /proc/interrupts output

 */

struct irq_desc{

  unsigned int          irq;

  struct timer_rand_state *timer_rand_state;

  unsigned int            *kstat_irqs;

#ifdef CONFIG_INTR_REMAP

  struct irq_2_iommu      *irq_2_iommu;

#endif

  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 long              last_unhandled;     /* Aging timer for unhandled count */

  unsigned int          irqs_unhandled;

  spinlock_t             lock;

#ifdef CONFIG_SMP

  cpumask_var_t             affinity;

  unsigned int          node;

#ifdef CONFIG_GENERIC_PENDING_IRQ

  cpumask_var_t             pending_mask;

#endif

#endif

  atomic_t         threads_active;

  wait_queue_head_t       wait_for_threads;

#ifdef CONFIG_PROC_FS

  struct proc_dir_entry    *dir;

#endif

  const char             *name;

}
           

2)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

 *

 * @bus_lock:         function to lock access to slow bus (i2c) chips

 * @bus_sync_unlock:    function to sync and unlock slow bus (i2c) chips

 *

 * @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);

  int          (*set_affinity)(unsigned int irq,

                              const struct cpumask *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);


  void        (*bus_lock)(unsigned int irq);

  void        (*bus_sync_unlock)(unsigned int irq);


  /* 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;

}
           

在\include\linux\ interrupt.h中定義了 irqaction用來描述特定裝置所産生的中斷描述符。

/**

 * struct irqaction - per interrupt action descriptor

 * @handler:   interrupt handler function

 * @flags: flags (see IRQF_* above)

 * @name:      name of the device

 * @dev_id:     cookie to identify the device

 * @next:  pointer to the next irqaction for shared interrupts

 * @irq:    interrupt number

 * @dir:    pointer to the proc/irq/NN/name entry

 * @thread_fn: interupt handler function for threaded interrupts

 * @thread:     thread pointer for threaded interrupts

 * @thread_flags:    flags related to @thread

 */

struct irqaction {

  irq_handler_t handler;

  unsigned long flags;

  const char *name;

  void *dev_id;

  struct irqaction *next;

  int irq;

  struct proc_dir_entry *dir;

  irq_handler_t thread_fn;

  struct task_struct *thread;

  unsigned long thread_flags;

};
           
  • 三、Linux中斷機制

Linux中斷機制由三部分組成:

  1. 中斷子系統初始化:核心自身初始化過程中對中斷處理機制初始化,例如中斷的資料結構以及中斷請求等。
  2. 中斷或異常處理:中斷整體處理過程。
  3. 中斷API:為裝置驅動提供API,例如注冊,釋放和激活等。

3.1中斷子系統初始化

3.1.1中斷描述符表(IDT)初始化

中斷描述符表初始化需要經過兩個過程:

  1. 第一個過程在核心引導過程。由兩個步驟組成,首先給配置設定IDT配置設定2KB空間(256中斷向量,每個向量由8bit組成)并初始化;然後把IDT起始位址存儲到IDTR寄存器中。
  2. 第二個過程核心在初始化自身的start_kernal函數中使用trap_init初始化系統保留中斷向量,使用init_IRQ完成其餘中斷向量初始化。

3.1.2中斷請求隊列初始化

init_IRQ調用pre_intr_init_hook,進而最終調用init_ISA_irqs初始化中斷控制器以及每個IRQ線的中斷請求隊列。

3.2中斷或異常處理

中斷處理過程:裝置産生中斷,并通過中斷線将中斷信号送往中斷控制器,如果中斷沒有被屏蔽則會到達CPU的INTR引腳,CPU立即停止目前工作,根據獲得中斷向量号從IDT中找出門描述符,并執行相關中斷程式。

異常處理過程:異常是由CPU内部發生是以不會通過中斷控制器,CPU直接根據中斷向量号從IDT中找出門描述符,并執行相關中斷程式。

Linux核心中斷系統處理機制-詳細分析一、中斷概述二、中斷資料處理結構三、Linux中斷機制四、多處理器系統中斷相關概念

                                                                                      圖3-1

中斷控制器處理主要有5個步驟:1.中斷請求 2.中斷響應 3.優先級比較 4.送出中斷向量 5.中斷結束。這裡不再贅述5個步驟的具體流程。

CPU處理流程主要有6個步驟:1.确定中斷或異常的中斷向量 2.通過IDTR寄存器找到IDT 3.特權檢查 4.特權級發生變化,進行堆棧切換 5.如果是異常将異常代碼壓入堆棧,如果是中斷則關閉可屏蔽中斷 6.進入中斷或異常服務程式執行。這裡不再贅述6個步驟的具體流程。

3.3中斷API

核心提供的API主要用于驅動的開發。

注冊IRQ:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
           

釋放IRQ:

void free_irq(unsigned int, void *);
           

注:IRQ線資源非常寶貴,我們在使用時必須先注冊,不使用時必須釋放IRQ資源。

激活目前CPU中斷:

local_irq_enable();
           

禁止目前CPU中斷:

local_irq_disable();
           

激活指定中斷線:

void enable_irq(unsigned int irq);
           

禁止指定中斷線:

void disable_irq(unsigned int irq);
           

禁止指定中斷線:

void disable_irq_nosync(unsigned int irq);
           

注:此函數調用irq_chip中disable禁止指定中斷線,是以不會保證中斷線上執行的中斷服務程式已經退出。

3.4中斷機制劃分

 由于中斷會打斷核心中程序的正常排程運作,是以要求中斷服務程式盡可能的短小精悍;但是在實際系統中,當中斷到來時,要完成工作往往進行大量的耗時處理。是以期望讓中斷處理程式運作得快,并想讓它完成的工作量多,這兩個目标互相制約,誕生——頂/底半部機制。

  中斷處理程式是頂半部——接受中斷,它就立即開始執行,但隻有做嚴格時限的工作。能夠被允許稍後完成的工作會推遲到底半部去,此後,在合适的時機,底半部會被開終端執行。頂半部簡單快速,執行時禁止一些或者全部中斷。

 底半部稍後執行,而且執行期間可以響應所有的中斷。這種設計可以使系統處于中斷屏蔽狀态的時間盡可能的短,以此來提高系統的響應能力。頂半部隻有中斷處理程式機制,而底半部的實作有軟中斷,tasklet和工作隊列實作。

Linux核心中斷系統處理機制-詳細分析一、中斷概述二、中斷資料處理結構三、Linux中斷機制四、多處理器系統中斷相關概念

                                             圖3-2 注:登記中斷,将底半部處理程式挂到該裝置的低半部執行隊列中。

3.4.1頂/底半部劃分原則:

 1) 如果一個任務對時間非常敏感,将其放在頂半部中執行;

 2) 如果一個任務和硬體有關,将其放在頂半部中執行;

 3) 如果一個任務要保證不被其他中斷打斷,将其放在頂半部中執行;

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

3.4.2底半部實作機制

Linux核心中斷系統處理機制-詳細分析一、中斷概述二、中斷資料處理結構三、Linux中斷機制四、多處理器系統中斷相關概念

                                                                                            圖3-3

軟中斷:

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

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

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

核心中定義了幾種軟中斷的用途:

enum

{
   HI_SOFTIRQ=0,

   TIMER_SOFTIRQ,

   NET_TX_SOFTIRQ,

   NET_RX_SOFTIRQ,

   BLOCK_SOFTIRQ,

   BLOCK_IOPOLL_SOFTIRQ,

   TASKLET_SOFTIRQ,

   SCHED_SOFTIRQ,

   HRTIMER_SOFTIRQ,

   RCU_SOFTIRQ,  /* Preferable RCU should always be the last softirq */

   NR_SOFTIRQS

};
           

Tasklet:

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

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

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

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

  總結下tasklet的優點:

  (1)無類型數量限制;

  (2)效率高,無需循環查表;

  (3)支援SMP機制;

  它的特性如下:

  1)一種特定類型的tasklet隻能運作在一個CPU上,不能并行,隻能串行執行。

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

3)軟中斷是靜态配置設定的,在核心編譯好之後,就不能改變。但tasklet就靈活許多,可以在運作時改變(比如添加子產品時)。

工作隊列:

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

工作隊列能運作在程序上下文,它将工作給一個核心線程,作為中斷守護線程來使用。多個中斷可以放在一個線程中,也可以每個中斷配置設定一個線程。我們用結構體workqueue_struct表示工作者線程,工作者線程是用核心線程實作的。而工作者線程是如何執行被推後的工作——有這樣一個連結清單,它由結構體work_struct組成,而這個work_struct則描述了一個工作,一旦這個工作被執行完,相應的work_struct對象就從連結清單上移去,當連結清單上不再有對象時,工作者線程就會繼續休眠。因為工作隊列是線程,是以我們可以使用所有可以線上程中使用的方法。

如何選擇下半部機制:

  1. 軟中斷和tasklet運作在中斷上下文,工作隊列運作在程序上下文。如果需要休眠則選擇工作隊列,否則選擇tasklet;如果對性能要求較高則選擇軟中斷。
  2. 從易用性考慮,首選工作隊列,然後tasklet,最後是軟中斷,因為軟中斷需要靜态建立。
  3. 從代碼安全考慮,如果對底半部代碼保護不夠安全,則選擇tasklet,因為相對軟中斷,tasklet對鎖要求低,上面也簡述它們工作方式以及運用場景。

四、多處理器系統中斷相關概念

4.1處理器間中斷

 在多處理器系統中,作業系統需要在多個處理器中協調操作,是以需要處理器中斷(Inter-Processor-Interrupt,IPI)實作,IPI是一種特殊硬體中斷,由CPU送出,其他CPU接收,處理CPU之間通信和同步操作。以下是x86中SMP定義的IPI,中斷向量号用十六進制表示:

SPURIOUS_APIC_VECTOR		0xff
ERROR_APIC_VECTOR		0xfe
RESCHEDULE_VECTOR		0xfd
CALL_FUNCTION_VECTOR		0xfc
CALL_FUNCTION_SINGLE_VECTOR	0xfb
THERMAL_APIC_VECTOR		0xfa
THRESHOLD_APIC_VECTOR		0xf9
REBOOT_VECTOR			0xf8
INVALIDATE_TLB_VECTOR_END	0xf7
INVALIDATE_TLB_VECTOR_START	0xf0
           

4.2中斷親和力

     将一個或多個中斷服務程式綁定到特定的CPU上處理,這就是中斷親和力(SMP IRQ affinity)。我們可以使用中斷親和力來均衡各個CPU的負載,提高系統處理能力。

以上便是本人對Linux中斷的了解,若有纰漏歡迎指正!

繼續閱讀