中断和中断处理程序
1. 中斷
??????? Linux內(nèi)核要對連接到計(jì)算機(jī)上的所有硬件設(shè)備進(jìn)行管理,首先要能和它們互相通信。從所周知,處理器的速度跟外圍硬件設(shè)備的速度往往不在一個(gè)數(shù)量級上。所以,需要一種機(jī)制,如果輪詢(polling)是一種解決辦法,可以讓內(nèi)核定期對設(shè)備的狀態(tài)進(jìn)行查詢,然后做出相應(yīng)的處理,但這讓內(nèi)核做了不少無用功。
??? 更好的辦法是由我們來提供一種機(jī)制,讓硬件在需要的時(shí)候再向內(nèi)核發(fā)出信號。這就是中斷機(jī)制。中斷本質(zhì)上是一種特殊的電信號,由硬件設(shè)備生成,并直接送入中斷控制器的輸入引腳上,再由中斷控制器向處理器發(fā)送相應(yīng)的信號,處理器一經(jīng)檢測到此信號,便中斷自己當(dāng)前工作轉(zhuǎn)而處理中斷,最后由OS來負(fù)責(zé)處理新到來的數(shù)據(jù)。中斷是異步的。
?
什么是中斷?簡單地說就是CPU在忙著作自己的事情,這時(shí)候硬件(比如說鍵盤按了一下)觸發(fā)了一個(gè)電信號,這個(gè)信號通過中斷線到達(dá)中斷控制器i8259A,i8259A接受到這個(gè)信號后,向CPU發(fā)送INT信號申請CPU來執(zhí)行剛才的硬件操作,并且將中斷類型號也發(fā)給CPU,此時(shí)CPU保存當(dāng)前正在做的事情(REST指令把程序計(jì)數(shù)器PC中的下一條待執(zhí)行的指令的內(nèi)存地址保存到棧)的情景現(xiàn)場,然后去處理這個(gè)申請,根據(jù)中斷類型號找到它的中斷向量(即中斷程序在內(nèi)存中的地址),然后去執(zhí)行這段程序(這段程序已經(jīng)寫好,在內(nèi)存中),執(zhí)行完后再向i8259A發(fā)送一個(gè)INTA信號表示其已經(jīng)處理完剛才的申請。此時(shí)CPU就可以繼續(xù)做它剛才被打斷做的事情了,將剛才保存的情景現(xiàn)場恢復(fù)出來,CPU繼續(xù)執(zhí)行接下來下面的程序。
??? 不同的設(shè)備對應(yīng)的中斷不同,而每個(gè)中斷都通過一個(gè)唯一的數(shù)字標(biāo)識。這些中斷值通常被稱為中斷請求(IRQ)線。比如,IRQ0是時(shí)鐘中斷,而IRQ1是鍵盤中斷。并不是所有的中斷號都這樣嚴(yán)格定義,像PCI總線上的設(shè)備,中斷就是動(dòng)態(tài)分配的。
?
1.1. 異常與中斷
??? 異常與中斷不同,它在產(chǎn)生時(shí)必須考慮與處理器時(shí)鐘同步。實(shí)際上,異常也稱為同步中斷。比如,在處理器執(zhí)行到由于編程失誤而導(dǎo)致的錯(cuò)誤指令的時(shí)候,或者在執(zhí)行期間出現(xiàn)特殊情況(缺頁),必須靠內(nèi)核來處理的,處理器就產(chǎn)生一個(gè)異常。
中斷的的工作方式類似,其差異只在于中斷是由硬件而不是軟件引起的。
?
2. 中斷處理程序
??? 在響應(yīng)一個(gè)特定中斷的時(shí)候,內(nèi)核會(huì)執(zhí)行一個(gè)函數(shù),該函數(shù)叫中斷處理程序(interrupt handler)或中斷服務(wù)例程(interrupt service routine,ISR)。產(chǎn)生中斷的每個(gè)設(shè)備都有一個(gè)相應(yīng)的中斷處理程序。一個(gè)設(shè)備的中斷處理程序是它設(shè)備驅(qū)動(dòng)程序的一部分。中斷處理程序與其他內(nèi)核的真正區(qū)別在于:中斷處理程序是被內(nèi)核調(diào)用來響應(yīng)中斷的,而它們運(yùn)行于我們稱之為中斷上下文的特殊上下文中。
?
2.1. 上半部與下半部的對比
??? 又想程序運(yùn)行得快,又想程序完成的工作量太多,這兩個(gè)目的相互矛盾。鑒于兩個(gè)目的之間存在不可調(diào)和的矛盾,所以需要把中斷處理程序分成兩半或兩個(gè)部分。中斷處理程序是上半部(top half):接收到一個(gè)中斷,他就立即開始執(zhí)行,但只做嚴(yán)格時(shí)限的工作,例如對接收的中斷進(jìn)行應(yīng)答或復(fù)位硬件,這些工作都是在所有中斷被禁止的情況下完成的。能夠被允許稍后完成的工作會(huì)推遲到下半部(bottom half)去。此后,在合適的時(shí)機(jī),下半部被開中斷執(zhí)行。
?
3. 注冊中斷處理程序
?? 驅(qū)動(dòng)程序可以通過下面的函數(shù)注冊并激活一個(gè)中斷處理程序,以便處理中斷:
????? int request_irq(unsigned int irq,
irqretrun_t (*handler)(int,void *, struct pt_regs *),
unsigned long irqflags,
const char *devname,
void *dev_id);
第一個(gè)參數(shù)>irq表示要分配的中斷號。對于大多數(shù)其他設(shè)備來說,這個(gè)值要么是可以通過探測獲取,要么可以通過編程動(dòng)態(tài)確定。
第二個(gè)參數(shù)>hanlder是一個(gè)指針,指向處理這個(gè)中斷的實(shí)際中斷處理程序。hanhler函數(shù)的原型接收三個(gè)參數(shù)。
第三個(gè)參數(shù)>irqflags可以是0,也可以是多個(gè)標(biāo)志的掩碼。如果是SA_INTERRUPT,表面給定的中斷處理程序是一個(gè)快速中斷處理程序(fast interrupt hanlder)。使用了該標(biāo)志,快速中斷處理程序在禁止所有中斷的情況下的本地處理器上運(yùn)行。除了時(shí)鐘中斷,絕大數(shù)中斷都不使用該標(biāo)志。如果是SA_SAMPLE_RANDOM,表明這個(gè)設(shè)備產(chǎn)生的中斷對內(nèi)核熵池(entropy pool)有貢獻(xiàn)。如果是SA_SHARE標(biāo)志,表明可以在多個(gè)中斷處理程序之間共享中斷線。在同一個(gè)給定線上注冊的每個(gè)處理程序必須指定這個(gè)標(biāo)志。
第四個(gè)參數(shù)>devname是與中斷相關(guān)設(shè)備的ASCII文本表示法。這些名字會(huì)被/proc//irq和/proc/inerrupt文件使用,以便于用戶通信。
第五個(gè)參數(shù)>dev_id主要用戶共享中斷線。當(dāng)一個(gè)中斷處理程序需要釋放時(shí),dev_id將提供唯一的標(biāo)志信息,以便從共享中斷線的諸多中斷處理程序中刪除指定的那一個(gè)。如果無需共享中斷線,那么將該參數(shù)賦為空值(NULL)就可以了。
該函數(shù)執(zhí)行成功會(huì)返回0。如果返回非0值,就表示有錯(cuò)誤發(fā)生。
?? 注意:request_irq函數(shù)可能會(huì)睡眠,因此,不能在中斷上下文或其他不允許阻塞的代碼中使用該函數(shù)。在注冊的過程中,內(nèi)核需要在/proc/irq文件中創(chuàng)建一個(gè)與中斷對應(yīng)的項(xiàng)。函數(shù)proc_mkdir就是用來創(chuàng)建這個(gè)新的procfs項(xiàng)的。函數(shù)proc_mkdir通過調(diào)用函數(shù)proc_mkdir通過調(diào)用proc_create對這個(gè)profs項(xiàng)進(jìn)行設(shè)置,而proc_create會(huì)調(diào)用函數(shù)kmalloc函數(shù)請求分配內(nèi)存。函數(shù)kmalloc是可以睡眠的。
?
3.1. 釋放中斷處理程序
??? 卸載驅(qū)動(dòng)程序時(shí),需要注銷相應(yīng)的中斷處理程序,并釋放中斷線。可以調(diào)用void_free_irq(unsigned int irq, void * dev_id)來釋放中斷線。
如果指定的中斷線不是共享的,那么該函數(shù)刪除處理程序的同時(shí)將禁用這條中斷線。如果中斷線是共享的,則僅刪除dev_di對應(yīng)的處理程序,而這條中斷線只有在刪除了最后一個(gè)處理程序時(shí)才會(huì)被禁用。
?
4. 編寫中斷處理程序
??? 以下是一個(gè)典型的中斷處理程序聲明:
?????? static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs);
??? 第一個(gè)參數(shù)irq就是這個(gè)處理程序要響應(yīng)的中斷的中斷線號。
??? 第二個(gè)參數(shù)dev_id是一個(gè)通用指針,它與在中斷處理程序注冊時(shí)傳遞request_irq的參數(shù)的dve_id必須一致。另外dev_id也可能指向中斷處理程序使用的一個(gè)數(shù)據(jù)結(jié)構(gòu)。因?yàn)?#xff0c;對于每個(gè)設(shè)備而言,設(shè)備結(jié)構(gòu)是唯一的。
??? 第三個(gè)參數(shù)regs是一個(gè)指向結(jié)構(gòu)的指針,該結(jié)構(gòu)包含處理中斷之前處理器的寄存器和狀態(tài)。考慮到現(xiàn)有的中斷處理程序很少使用該參數(shù),因此可以忽略它。
??? 中斷處理程序的返回值是一個(gè)特殊類型:irqreturn_t。中斷處理程序可能會(huì)返回兩個(gè)特殊的值:IRQ_NONE和IRQ_HANDLED。當(dāng)中斷處理程序檢測到一個(gè)中斷,但該中斷對應(yīng)的設(shè)備并不是在注冊處理函數(shù)期間指定的產(chǎn)生源時(shí),返回IRQ_NONE;當(dāng)中斷處理程序被正確調(diào)用,且確實(shí)是它所對應(yīng)的設(shè)備產(chǎn)生了中斷,返回IRQ_HANDLED。而實(shí)際上irqreturn_t就是一個(gè)int類型。
??? 中斷處理程序通常會(huì)標(biāo)記為static,因?yàn)樗鼜膩聿粫?huì)被別人的文件中的代碼直接調(diào)用。
?
4.1. 重入和中斷處理程序
??????? Linux中的中斷處理程序是無需重入的。當(dāng)一個(gè)給定的中斷處理程序正在執(zhí)行時(shí),相應(yīng)的中斷線在所有處理器上都會(huì)被屏蔽掉,以防止在同一中斷線上接收另一個(gè)新的中斷。
?
4.2. 共享的中斷處理程序
??? 共享的處理程序與非共享的處理程序在注冊和運(yùn)行方式上比較類似,但差異如下:
??? 1) request_irq的參數(shù)flags必須設(shè)置SA_SHARE標(biāo)志
??? 2) 對每個(gè)注冊的中斷處理程序來說,dev_id參數(shù)必須唯一
??? 3) 中斷處理程序必須能夠區(qū)分它的設(shè)備是否真的產(chǎn)生了中斷。這既需要硬件的支持,耶需要處理程序有相關(guān)的處理邏輯。
?
??? 指定SA_SHARE標(biāo)志以調(diào)用request_irq時(shí),只有在以下兩種情況下才可能成功:
??? 1) 中斷線當(dāng)前未被注冊
??? 2) 在該線上的所有已經(jīng)注冊處理程序都指定了SA_SHARE。
?
??? 內(nèi)核接收一個(gè)中斷后,它將依次調(diào)用在該中斷線上注冊的每一個(gè)處理程序。因此,一個(gè)處理程序應(yīng)該必須知道它是否應(yīng)該為這個(gè)負(fù)責(zé)。如果與它相關(guān)的設(shè)備并沒有產(chǎn)生中斷,那么處理器應(yīng)該立即退出。
?
5. 中斷上下文
??? 當(dāng)執(zhí)行一個(gè)中斷處理程序或下半部時(shí),內(nèi)核處于中斷上下文(interrupt context)中。中斷上下文和進(jìn)程沒有關(guān)系,不可以睡眠。中斷上下文具有嚴(yán)格的時(shí)間限制,因?yàn)樗驍嗔似渌a。
??? 而進(jìn)程上下文是一種內(nèi)核所處的操作模式,此時(shí)內(nèi)核代表進(jìn)程執(zhí)行,比如執(zhí)行系統(tǒng)調(diào)用或運(yùn)行內(nèi)核線程。在進(jìn)程上下文中,可以通過current宏關(guān)聯(lián)當(dāng)前進(jìn)程,可以睡眠。
??? 中斷處理程序打斷了其他代碼,正是因?yàn)檫@種異步執(zhí)行的特性,所以所有的中斷處理程序必須盡可能的迅速、簡潔。盡量把工作從中斷處理程序中分離出來,交給下半部。
??? 中斷處理程序棧的設(shè)置是一個(gè)配置選項(xiàng),決定中斷處理程序是否共享中斷進(jìn)程的內(nèi)核棧。內(nèi)核棧的大小是兩頁。在2.6的內(nèi)核中,增加一個(gè)選項(xiàng),把棧的大小兩頁減到一頁,這就減輕了內(nèi)存的壓力,因?yàn)橄到y(tǒng)中每個(gè)進(jìn)程僅需要一頁內(nèi)核棧了。但是,為了應(yīng)對棧大小的減少,中斷處理程序擁有了自己的棧,每個(gè)處理器一個(gè),大小為一頁。這個(gè)棧稱為中斷棧。
?
6. 中斷處理機(jī)制的實(shí)現(xiàn)
??? 設(shè)備產(chǎn)生中斷,通過總線把電信號發(fā)送給中斷控制器,處理器會(huì)立即停止它正在做的事,關(guān)閉中斷系統(tǒng),然后跳到內(nèi)存中預(yù)定義的位置開始執(zhí)行那里的代碼。這個(gè)預(yù)定義的位置是由內(nèi)核設(shè)置的,是中斷處理程序的入口點(diǎn)。
??? 在內(nèi)核棧,中斷的旅程開始于預(yù)定義入口點(diǎn),這類似于系統(tǒng)調(diào)用通過預(yù)定義的異常句柄進(jìn)入內(nèi)核。對于每條中斷線,處理器都會(huì)跳到對應(yīng)的一個(gè)唯一的位置。初始入口點(diǎn)只是在棧中保存這個(gè)號,并存放當(dāng)前寄存器的值;然后,內(nèi)核調(diào)用do_IRQ函數(shù)。
??????? unsigned int do_IRQ(struct pt_regs regs);
??? 該函數(shù)計(jì)算出中斷號后,對所接收的中斷進(jìn)行應(yīng)答,禁止這條線上的中斷傳遞。在普通的PC機(jī)器上,這些操作由mask_and_ack_8259A來完成的。
??? 接著,該函數(shù)需要確保在這條中斷線上有個(gè)有效的處理程序,而且這個(gè)程序已經(jīng)啟動(dòng),但是當(dāng)前并沒有執(zhí)行。do_IRQ就調(diào)用handle_IRQ_event來運(yùn)行為這條中斷線安裝的中斷處理程序。
??? 最后,函數(shù)返回,回到do_IRQ。而do_IRQ做清理工作并返回到初始入口點(diǎn),然后再從這個(gè)入口點(diǎn)跳到函數(shù)ret_from_intr函數(shù)。這個(gè)例程會(huì)檢查重新調(diào)度是否正在掛起。如果重新調(diào)度正在掛起,而且內(nèi)核正在返回用戶空間(也就是中斷了用戶進(jìn)程),那么schedule被調(diào)用。如果內(nèi)核正在返回內(nèi)核空間(也就是中斷了內(nèi)核本身),只有在preempt_count為0,schedule才會(huì)被調(diào)用。在schedule返回之后,或者沒有掛起的工作,那么,原來的寄存器被恢復(fù),內(nèi)核恢復(fù)到曾經(jīng)中斷的點(diǎn)。
??? 在x86上,初始的匯編例程位于arch/i386/kernel/entry.S,C方法在arch/i386/kernel/irq.c中。
?
6.1. 文件/proc/interrupts
??????? procfs是一個(gè)虛擬文件系統(tǒng),它只存于內(nèi)核內(nèi)存,一般安裝與/proc目錄下。在procfs中讀寫都要調(diào)用內(nèi)核函數(shù),這些函數(shù)模擬從真實(shí)文件中讀或?qū)憽?/span>
?
7. 中斷控制
??????? Linux內(nèi)核提供了一組接口用于操作機(jī)器上的中斷狀態(tài)。可以在<asm/system.h>和<asm/irq.h>中找到。一般來說,控制中斷系統(tǒng)的原因是需要提供同步。通過禁止中斷,可以確保某個(gè)中斷處理程序不會(huì)搶占當(dāng)前的代碼。此外,禁止中斷還可以禁止內(nèi)核搶占。
?
7.1. 禁止和激活中斷
????用于禁止和激活當(dāng)前處理器上的本地中斷:
???????? local_irq_disable();
???????? local_irq_enable();
???????? local_irq_save(unsigned long flags);
???????? local_irq_restore(unsigned long flags);
??? 前兩個(gè)函數(shù)通常調(diào)用單個(gè)匯編指令來實(shí)現(xiàn)。實(shí)際上,在x86中它們分別使用cli指令和sti指令。如果在調(diào)用local_irq_disable例程之前已經(jīng)禁止了中斷,那么該例程往往帶來潛在的危險(xiǎn);同樣相應(yīng)的local_irq_enable例程耶存在危險(xiǎn),因?yàn)樗麑o條件地激活中斷,盡管這些中斷可能在開始時(shí)就是關(guān)閉的。后兩個(gè)函數(shù)可以保存現(xiàn)場,是系統(tǒng)更加安全。
????內(nèi)核2.5版本不再使用全局的cli,相應(yīng)地,所有中斷同步現(xiàn)在必須結(jié)合使用本地中斷控制器和自旋鎖。也就是說,為了確保對共享數(shù)據(jù)的互斥訪問,現(xiàn)在需要做更多的工作。取消全局cli的優(yōu)點(diǎn):一是強(qiáng)制驅(qū)動(dòng)程序編寫實(shí)現(xiàn)真正的加鎖,具有特定的細(xì)粒度比全局鎖快許多;二是這使得很多代碼更具流線型,避免了代碼的成簇布局。
前面的所有函數(shù)既可以在中斷中調(diào)用,也可以在進(jìn)程上下文中調(diào)用。
?
7.2. 禁止指定中斷線
??? 在某些情況下,只禁止整個(gè)系統(tǒng)中一條特定的中斷線就夠了。
???????? 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);
????前兩個(gè)函數(shù)禁止中斷控制器上指定的中斷線。另外函數(shù)只有在當(dāng)前正在執(zhí)行的所有處理程序完成后,disable_irq才能返回。因此。調(diào)用者不僅確保不在指定中斷線上傳遞新的中斷,同時(shí)還有確保所有已經(jīng)開始執(zhí)行的處理程序已經(jīng)全部退出。
????函數(shù)disable_irq_nosync不會(huì)等待當(dāng)前中斷處理程序執(zhí)行完畢。
函數(shù)synchronize_irq等待一個(gè)特定的中斷處理程序的退出。如果該處理程序正在執(zhí)行,那么該函數(shù)必須退出后才能返回。
??? 對于這些函數(shù)的調(diào)用可以嵌套。其中有三個(gè)函數(shù)可以從中斷或進(jìn)程上下文中調(diào)用,而且不會(huì)睡眠。禁止多個(gè)中斷處理程序共享的中斷線是不合適的,禁止中斷線也就禁止了這條線上所有設(shè)備的中斷傳遞。因此,用于新設(shè)備的驅(qū)動(dòng)程序應(yīng)該傾向于不使用這些接口。
?
7.3. 中斷系統(tǒng)的狀態(tài)
??? 宏irqs_disable定義在<asm/system.h>中。如果本地處理器上的中斷系統(tǒng)被禁止,則它返回非0,否則返回0。
??? 在<asm/hardirq.h>中定義的兩個(gè)宏提供一個(gè)用來檢測內(nèi)核的當(dāng)前上下文的接口:
??????? int_interrupt()
??????? int_irq()
??? 第一個(gè)宏in_interrup最有用:如果內(nèi)核處于中斷上下文中,返回非0。說明內(nèi)核此刻正在執(zhí)行中斷處理程序,或者正在執(zhí)行下半部處理程序。宏in_irq只有在內(nèi)核確實(shí)正在執(zhí)行中斷處理程序時(shí)返回非0。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
- 上一篇: MYSQL安装和配置
- 下一篇: 备忘(持续更新。。。)