處理器的速度跟外圍硬件設備的速度每每再也不一個數量級上,所以,若是內核採起讓處理器向硬件發出一個請求。linux
而後專門等待迴應的辦法,若是專門等待迴應,明顯太慢。因此等待期間能夠處理其餘事務,等待完成了請求操做後,再回來進行處理。編程
因此內核提供了一種機制,讓內核在須要的時候再向內核發出信號。這就是中斷機制。dom
硬件中斷能夠隨時產生,所以,內核隨時可能由於新到來的中斷而被打斷。async
不一樣的設備對應的中斷不一樣,而每一箇中斷都經過一個惟一的數字標誌。ide
這些中斷值一般被稱爲中斷請求(IRQ)線。每一個IRQ線都會被關聯一個數值量。函數
異常:它產生時必須考慮處理器時鐘同步,經常也被稱爲同步中斷。this
在處理器執行到因爲編程失誤而致使的錯誤指令的時候,或者實在執行期間出現特殊狀況,必須靠內核來處理的時候,處理器就會產生一個異常。spa
在響應要給特定中斷的時候,內核會執行要給一個函數,該函數叫作中斷處理程序或中斷服務例程(ISR)。線程
linux中斷程序是普通的C函數,不過必須按照特定的方式聲明,以便內核識別。指針
真正的區別在於:中斷處理程序是被內核調用來響應中斷的,而它們運行於中斷上下文的特殊上下文中。
中斷上下文偶爾也稱爲原子上下文,該上下文中的執行代碼不可阻塞。
中斷程序最好儘快反應,而且執行時間儘量短。
通常把中斷程序分紅兩個部分,中斷處理程序是上半部(接收到一箇中斷,就當即執行,但只作有限的工做) 。
稍後完成的工做會推遲到下半部去,在適合的時機,下半部會被開中斷執行。
驅動程序能夠經過request_irq()函數註冊終端處理程序,它被聲明再文件<linux/interrupt.h>中
/* request_irq:分配一條給定的中斷線 */ int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) /* 第一個參數irq表示要分配的中斷號 */ /* 第二個參數handler時一個指針,指向處理這個中斷的實際中斷處理程序 */ /* handler原型 */ typedef irqreturn_t (*irq_handler_t)(int, void *);
第三個參數flags能夠爲0,再文件<linux/interrupt.h>中定義了掩碼
IRQF_DISABLED:意味着內核在處理中斷程序自己期間,要禁止全部的其餘中斷
IRQF_SAMPLE_RANDOM:此標誌代表這個設備產生的中斷對內核熵池有貢獻
IRQF_TIMER:特別爲系統定時器的中斷處理而準備的
IRQF_SHARED:能夠在多箇中斷的處理程序之間的貢獻中斷線
第四個參數name是與中斷相關的設備的ASCII文本表示,在/proc/irq和/proc/interrupts
第五個參數dev用於共享中斷線,當一箇中斷處理程序釋放時,dev將提供標誌信息。若是不須要共享中斷線,設空(NULL)就行。
request_irq(); if(request_irq(irqm, my_interrupt, IRQF_SHARED, "my_device", my_dev)) { printk(KERN_ERR "my_device: cannot register IRQ %d\n", irqn); return -EIO; }
若是調用返回0,則說明成功。必須先初始化程序,而後再註冊中斷
調用free_irq註銷相應的中斷處理程序。
void free_irq(unsigned int irq, void *dev)
若是指定的中斷線不是共享的,那麼函數刪除動做的同時會禁用此中斷線
若是設置了共享標誌,那麼函數刪除僅僅對dev對應的處理程序,除非刪除了最後一個處理程序後,中斷線纔會被禁用。
/* irq:參數已經沒有太大意義了 */ /* dev:一個通用指針,它與中斷處理程序註冊時傳遞的必須一致 */ static irqreturn_t intr_handler(int irq, void *dev)
返回值是一個特殊類型:irqreturn_t,可能返回兩個特殊的值:IRQ_NONE和IRQ_HANDLED
重入和中斷處理程序:中斷程序是不須要重入的,程序中斷時,其餘中斷會被屏蔽,並且也不會重複嵌套。
共享和非共享處理程序比較類似,可是差別主要有三處:
任何一個設備沒有按規則進行共享,那麼中斷就沒法共享了。
在driver/char/rtc.c中有RTC相關的例子。中斷髮生時,報警器或定時器就會啓動。
當RTC驅動程序裝載時,rtc_init()會被調用。
/* 對rtc_irq註冊rtc_interrupt */ if(request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc", (void *)&rtc_port)) { printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq); return -EIO; }
第一個參數說明RTC位於IRQ8,中斷程序爲rtc_interrupt,而且設置了共享中斷線,程序名爲"rtc"
static irqreturn_t rtc_interrupt(int irq, void *dev) { /* * 能夠是報警中孤單、更新完成的中斷或週期性中斷 * 咱們把裝填保存在rtc_irq_data的低字節中, * 而把從最後一次讀取以後所接收的中斷號保存在其他字節中 */ spin_lock(&rtc_lick); rtc_irq_data += 0x100; rtc_irq_data &= ~0xff; rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); if(rtc_status & RTC_TIMER_ON) mod_timer(&rtc_irq_timer, jiffies + HZ/rtq_freq + 2*HZ/100); spin_unlock(&rtc_lock); /* * 如今執行其他的操做 */ spin_lock(&rtc_task_lock); if(rtc_callback) rtc_callback->func(rtc_callback->private_data); spin_unlock(&rtc_task_lock); wake_up_interruptible(&rtc_wait); kill_fasync(&rtc_async_queue, SIGIO, POLL_IN); return IRQ_HANDLED; }
使用自旋鎖,保證不會被其餘處理器同時訪問。
rtc_irq_data存放RTC有關的信息。
後面部分,則是可能運行預先設置好的回調函數。每一個RTC驅動程序容許註冊要給回調函數,在中斷到來時執行。
最後返回IRQ_HANDLED,代表已經正確完成對此設備的操做。
當執行一箇中斷處理程序時,內核處於中斷上下文中。
中斷上下問和進程沒有什麼瓜葛。
中斷上下文具備較爲嚴格的時間限制,由於它打斷了其餘代碼。
中斷處理程序棧的設置是要給配置選項,32位8KB,64位16KB 。
總而言之,儘可能節約內核棧空間。
中斷髮生,經過電信號傳遞給處理器,除非屏蔽了此中斷,不然處理器會當即執行。
中斷旅程開始於預約義入口,每條中斷線,處理器都會跳到對應的一個惟一的位置,這樣內核就知道接收的IRQ中斷號。
unsigned int do_IRQ(struct pt_regs regs)
若是do_IRQ確保在中斷線上有一個有效的處理程序,並且程序已經啓動。do_IRQ()就調用handle_IRQ_event()來運行爲這條
中斷線所安裝的中斷處理程序。在文件kernel/irq/handler.c中
/** * handle_IRQ_event - irq action chain handler * @irq: the interrupt number * @action: the interrupt action chain for this irq * * Handles the action chain of an irq event */ irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action) { irqreturn_t ret, retval = IRQ_NONE; unsigned int status = 0; if(!(action->flags & IRQF_DISABLED)) local_irq_enable_in_hardirq(); do { trace_irq_handler_entry(irq, action); ret = action->handler(irq, action->dev_id); trace_irq_handler_exit(irq, action, ret); switch(ret) { case IRQ_WAKE_THREAD: /* * 把返回值設置爲已處理,以即可疑的檢查再也不觸發 */ ret = IRQ_HANDLED; /* * 捕獲返回值爲WAKE_THREAD的驅動程序,可是並不建立一個線程函數 */ if(unlikely(!action->thread_fn)) { warn_no_thread(irq, action); break; } /* * 爲此次中斷喚醒處理線程。萬一線程崩潰且被殺死,咱們僅僅僞裝已經處理了該中斷。上述的硬件中斷(hardirq)處理程序已經進制設備中斷,所以杜絕irq產生 */ if(likely(!test_bit(IRQTF_DIED, &action->thread_flags))) { set_bit(IRQTF_RUNTHREAD, &action->thread_flags); wake_up_process(action->thread); } /* Fall through to add to randomness */ case IRQ_HANDLED: status |= action->flags; break; default: break; } retval |= ret; action = action->next; } while (action); if(status & IRQF_SAMPLE_RANDOM) add_interrupt_randomness(irq); local_irq_disable(); return retval; }
chen@chen-K42F:~/Downloads/4412/linux-4.14.12$ cat /proc/interrupts CPU0 CPU1 0: 18 0 IO-APIC 2-edge timer 1: 25244 0 IO-APIC 1-edge i8042 9: 11 12 IO-APIC 9-fasteoi acpi 12: 207590 21487 IO-APIC 12-edge i8042 16: 197581 103440 IO-APIC 16-fasteoi ehci_hcd:usb1 17: 1087739 322896 IO-APIC 17-fasteoi ath9k 19: 0 0 IO-APIC 19-fasteoi i801_smbus 23: 15 26 IO-APIC 23-fasteoi ehci_hcd:usb2 24: 79350 28510 PCI-MSI 327680-edge xhci_hcd
第一列時中斷線,第二列時一個接收中斷的計數器,第三列時處理這個中斷的中斷控制器
相關文件在<asm/system.h> <asm/irq.h>中找到
9.1 禁止和激活中斷
用於禁止當前處理器上的本地中斷,隨後再就激活他們的語句。
local_irq_disable(); /* 禁止中斷 */ local_irq_enable();
對中斷的狀態操做以前禁止設備中斷的傳遞,linux提供了四個接口
void disable_irq(unsigned int irq); void disable_irq_nosync(unsigned int irq); void enable_irq(unsigned int irq); void synchronize_irq(unsigned int irq);
一般有必要了解中斷系統的狀態,或者你當前是否正處於中斷上下文的執行狀態中。
宏irqs_disable()定義在<asm/system.h>
若是本地處理器上的中斷系統被禁止,則返回非0,不然返回0
在<linux/hardirq.h>
local_irq_disable() 禁止本地中斷傳遞
local_irq_enable() 激活本地中斷傳遞
local_irq_save() 保存本地中斷傳遞的當前狀態,而後禁止本地中斷傳遞
local_irq_restore() 恢復本地中斷傳遞到給定的狀態
disable_irq() 禁止給定中斷線,並確保該函數返回以前在該中斷線上沒有處理程序再運行
disable_irq_nosync() 禁止給定中斷線
enable_irq() 激活給定中斷線
irqs_disabled() 若是本地中斷傳遞被禁止,則返回非0,不然返回0
in_interrupt() 若是在中斷上下文中,則返回非0,若是在進程上下文中,則返回0
in_irq() 若是當前正在執行中斷處理程序,則返回非0,不然返回0