中斷上半部、下半部的概念
實(shí)現(xiàn)中斷下半部的三種方法
軟中斷
軟中斷模版
tasklet
tasklet函數(shù)模版
工作隊(duì)列
工作隊(duì)列函數(shù)模版
進(jìn)程上下文和中斷上下文
軟中斷和硬中斷的區(qū)別
硬中斷、軟中斷和信號(hào)的區(qū)別
??設(shè)備的中斷會(huì)打斷內(nèi)核進(jìn)程中的正常調(diào)度和運(yùn)行,系統(tǒng)對(duì)更高吞吐率的追求勢(shì)必要求中斷服務(wù)程序盡量短小精悍。但是,這個(gè)良好的愿望往往與現(xiàn)實(shí)并不吻合。在大多數(shù)真實(shí)的系統(tǒng)中,當(dāng)中斷到來時(shí),要完成的工作往往并不會(huì)是短小的,它可能要進(jìn)行較大量的耗時(shí)處理。
??下圖描述了Linux內(nèi)核的中斷處理機(jī)制。為了在中斷執(zhí)行時(shí)間盡量短和中斷處理需完成的工作盡量大之間找到一個(gè)平衡點(diǎn),Linux將中斷處理程序分解為兩個(gè)半部:頂半部和底半部。
??頂半部用于完成盡量少的比較緊急的功能,它往往只是簡(jiǎn)單地讀取寄存器中的中斷狀態(tài),并在清除中斷標(biāo)志后就進(jìn)行“登記中斷”的工作?!暗怯浿袛唷币馕吨鴮⒌装氩刻幚沓绦驋斓皆撛O(shè)備的底半部執(zhí)行隊(duì)列中去。這樣,頂半部執(zhí)行的速度就會(huì)很快,從而可以服務(wù)更多的中斷請(qǐng)求。
??現(xiàn)在,中斷處理工作的重心就落在了底半部的頭上,需用它來完成中斷事件的絕大多數(shù)任務(wù)。底半部幾乎做了中斷處理程序所有的事情,而且可以被新的中斷打斷,這也是底半部和頂半部的最大不同,因?yàn)?strong style="word-break: break-all;word-wrap: break-word;-webkit-;;">頂半部往往被設(shè)計(jì)成不可中斷。底半部相對(duì)來說并不是非常緊急的,而且相對(duì)比較耗時(shí),不在硬件中斷服務(wù)程序中執(zhí)行。
??盡管頂半部、底半部的結(jié)合能夠善系統(tǒng)的響應(yīng)能力,但是,僵化地認(rèn)為L(zhǎng)inux設(shè)備驅(qū)動(dòng)中的中斷處理一定要分兩個(gè)半部則是不對(duì)的。如果中斷要處理的工作本身很少,則完全可以直接在頂半部全部完成。
??其他操作系統(tǒng)中對(duì)中斷的處理也采用了類似于 Linux的方法,真正的硬件中斷服務(wù)程序都斥盡量短。因此,許多操作系統(tǒng)都提供了中斷上下文和非中斷上下文相結(jié)合的機(jī)制,將中斷的耗時(shí)工作保留到非中斷上下文去執(zhí)行。
??軟中斷( Softirq)也是一種傳統(tǒng)的底半部處理機(jī)制,它的執(zhí)行時(shí)機(jī)通常是頂半部返回的時(shí)候, tasklet是基于軟中斷實(shí)現(xiàn)的,因此也運(yùn)行于軟中斷上下文。
??在Linux內(nèi)核中,用 softing_action結(jié)構(gòu)體表征一個(gè)軟中斷,這個(gè)結(jié)構(gòu)體包含軟中斷處理函數(shù)指針和傳遞給該函數(shù)的參數(shù)。使用 open_softirq()函數(shù)可以注冊(cè)軟中斷對(duì)應(yīng)的處理函數(shù),而 raise_softirq()函數(shù)可以觸發(fā)一個(gè)軟中斷。
??軟中斷和 tasklet運(yùn)行于軟中斷上下文,仍然屬于原子上下文的一種,而工作隊(duì)列則運(yùn)行于進(jìn)程上下文。因此,在軟中斷和 tasklet處理函數(shù)中不允許睡眠,而在工作隊(duì)列處理函數(shù)中允許睡眠。
??local_bh_disable()和 llocal_bh_enable()是內(nèi)核中用于禁止和使能軟中斷及 tasklet底半部機(jī)制的函數(shù)
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/qq_16933601/article/details/107239908
asmlinkage void do_softirq(void){__u32 pending;unsigned long flags;/* 判斷是否在中斷處理中,如果正在中斷處理,就直接返回 */if (in_interrupt())return;/* 保存當(dāng)前寄存器的值 */local_irq_save(flags);/* 取得當(dāng)前已注冊(cè)軟中斷的位圖 */pending = local_softirq_pending();/* 循環(huán)處理所有已注冊(cè)的軟中斷 */if (pending)__do_softirq();/* 恢復(fù)寄存器的值到中斷處理前 */local_irq_restore(flags);}
??tasklet的使用較簡(jiǎn)單,它的執(zhí)行上下文是軟中斷,執(zhí)行時(shí)機(jī)通常是頂半部返回的時(shí)候。我們只需要定義 tasklet及其處理函數(shù),并將兩者關(guān)聯(lián)則可,例如
void my_tasklet_func(unsigned long); /*定義一個(gè)處理函數(shù)*/DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);/*定義一個(gè)tasklet結(jié)構(gòu)my_tasklet,與my_tasklet_func(data)函數(shù)相關(guān)聯(lián)*/
??代碼DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)實(shí)現(xiàn)了定義名稱為my_tasklet的tasklet,并將其與my_tasklet_func()這個(gè)函數(shù)綁定,而傳入這個(gè)函數(shù)的參數(shù)為data。
在需要調(diào)度tasklet的時(shí)候引用一個(gè)tasklet_schedule()函數(shù)就能使系統(tǒng)在適當(dāng)?shù)臅r(shí)候進(jìn)行調(diào)度運(yùn)行:
tasklet_schedule(&my_tasklet);
??使用tasklet作為底半部處理中斷的設(shè)備驅(qū)動(dòng)程序模板下所示(僅包含與中斷相關(guān)的部
分)。
/* 定義tasklet和底半部函數(shù)并將它們關(guān)聯(lián) */void xxx_do_tasklet(unsigned long);DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);/* 中斷處理底半部 */void xxx_do_tasklet(unsigned long).../* 中斷處理頂半部 */ irqreturn_t xxx_interrupt(int irq, void *dev_id){ ... tasklet_schedule(&xxx_tasklet); ...}/* 設(shè)備驅(qū)動(dòng)模塊加載函數(shù) */ int __init xxx_init(void){ ... /* 申請(qǐng)中斷 */ result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL); ... return IRQ_HANDLED;}/* 設(shè)備驅(qū)動(dòng)模塊卸載函數(shù) */ void __exit xxx_exit(void){ ... /* 釋放中斷 */ free_irq(xxx_irq, xxx_interrupt); ...}
??上述程序在模塊加載函數(shù)中申請(qǐng)中斷(第24~25行),并在模塊卸載函數(shù)free_irq(xxx_irq, xxx_interrupt);中釋放它。對(duì)應(yīng)于xxx_irq的中斷處理程序被設(shè)置為xxx_interrupt()函數(shù),在這個(gè)函數(shù)中,tasklet_schedule(&xxx_tasklet)調(diào)度被定義的tasklet函數(shù)xxx_do_tasklet()在適當(dāng)?shù)臅r(shí)候執(zhí)行。
??工作隊(duì)列的使用方法和tasklet非常相似,但是工作隊(duì)列的執(zhí)行上下文是內(nèi)核線程,因此可以調(diào)度和睡眠。下面的代碼用于定義一個(gè)工作隊(duì)列和一個(gè)底半部執(zhí)行函數(shù)
struct work_struct my_wq; /* 定義一個(gè)工作隊(duì)列 */void my_wq_func(struct work_struct *work); /* 定義一個(gè)處理函數(shù) */
??通過INIT_WORK()可以初始化這個(gè)工作隊(duì)列并將工作隊(duì)列與處理函數(shù)綁定:
INIT_WORK(&my_wq, my_wq_func);/* 初始化工作隊(duì)列并將其與處理函數(shù)綁定 */
??與tasklet_schedule()對(duì)應(yīng)的用于調(diào)度工作隊(duì)列執(zhí)行的函數(shù)為schedule_work(),如:
schedule_work(&my_wq); /* 調(diào)度工作隊(duì)列執(zhí)行 */
/* 定義工作隊(duì)列和關(guān)聯(lián)函數(shù) */struct work_struct xxx_wq;void xxx_do_work(struct work_struct *work);/* 中斷處理底半部 */void xxx_do_work(struct work_struct *work).../*中斷處理頂半部*/ irqreturn_t xxx_interrupt(int irq, void *dev_id){ ... schedule_work(&xxx_wq); ... return IRQ_HANDLED;}/* 設(shè)備驅(qū)動(dòng)模塊加載函數(shù) */ int xxx_init(void){ ... /* 申請(qǐng)中斷 */ result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL); ... /* 初始化工作隊(duì)列 */ INIT_WORK(&xxx_wq, xxx_do_work); ...}/* 設(shè)備驅(qū)動(dòng)模塊卸載函數(shù) */ void xxx_exit(void){ ... /* 釋放中斷 */ free_irq(xxx_irq, xxx_interrupt); ...}
??工作隊(duì)列早期的實(shí)現(xiàn)是在每個(gè)CPU核上創(chuàng)建一個(gè)worker內(nèi)核線程,所有在這個(gè)核上調(diào)度的工作都在該worker線程中執(zhí)行,其并發(fā)性顯然差強(qiáng)人意。在Linux 2.6.36以后,轉(zhuǎn)而實(shí)現(xiàn)“Concurrency-managedworkqueues”,簡(jiǎn)稱cmwq,cmwq會(huì)自動(dòng)維護(hù)工作隊(duì)列的線程池以提高并發(fā)性,同時(shí)保持了API的向后兼容。
談?wù)勥M(jìn)程上下文、中斷上下文及原子上下文的一些概念
硬中斷:
??1. 硬中斷是由硬件產(chǎn)生的,比如,像磁盤,網(wǎng)卡,鍵盤,時(shí)鐘等。每個(gè)設(shè)備或設(shè)備集都有它自己的IRQ(中斷請(qǐng)求)?;贗RQ,CPU可以將相應(yīng)的請(qǐng)求分發(fā)到對(duì)應(yīng)的硬件驅(qū)動(dòng)上(注:硬件驅(qū)動(dòng)通常是內(nèi)核中的一個(gè)子程序,而不是一個(gè)獨(dú)立的進(jìn)程)。
??2. 處理中斷的驅(qū)動(dòng)是需要運(yùn)行在CPU上的,因此,當(dāng)中斷產(chǎn)生的時(shí)候,CPU會(huì)中斷當(dāng)前正在運(yùn)行的任務(wù),來處理中斷。在有多核心的系統(tǒng)上,一個(gè)中斷通常只能中斷一顆CPU(也有一種特殊的情況,就是在大型主機(jī)上是有硬件通道的,它可以在沒有主CPU的支持下,可以同時(shí)處理多個(gè)中斷。)。
??3. 硬中斷可以直接中斷CPU。它會(huì)引起內(nèi)核中相關(guān)的代碼被觸發(fā)。對(duì)于那些需要花費(fèi)一些時(shí)間去處理的進(jìn)程,中斷代碼本身也可以被其他的硬中斷中斷。
??4. 對(duì)于時(shí)鐘中斷,內(nèi)核調(diào)度代碼會(huì)將當(dāng)前正在運(yùn)行的進(jìn)程掛起,從而讓其他的進(jìn)程來運(yùn)行。它的存在是為了讓調(diào)度代碼(或稱為調(diào)度器)可以調(diào)度多任務(wù)。
軟中斷:
??1. 軟中斷的處理非常像硬中斷。然而,它們僅僅是由當(dāng)前正在運(yùn)行的進(jìn)程所產(chǎn)生的。
??2. 通常,軟中斷是一些對(duì)I/O的請(qǐng)求。這些請(qǐng)求會(huì)調(diào)用內(nèi)核中可以調(diào)度I/O發(fā)生的程序。對(duì)于某些設(shè)備,I/O請(qǐng)求需要被立即處理,而磁盤I/O請(qǐng)求通??梢耘抨?duì)并且可以稍后處理。根據(jù)I/O模型的不同,進(jìn)程或許會(huì)被掛起直到I/O完成,此時(shí)內(nèi)核調(diào)度器就會(huì)選擇另一個(gè)進(jìn)程去運(yùn)行。I/O可以在進(jìn)程之間產(chǎn)生并且調(diào)度過程通常和磁盤I/O的方式是相同。
??3. 軟中斷僅與內(nèi)核相聯(lián)系。而內(nèi)核主要負(fù)責(zé)對(duì)需要運(yùn)行的任何其他的進(jìn)程進(jìn)行調(diào)度。一些內(nèi)核允許設(shè)備驅(qū)動(dòng)的一些部分存在于用戶空間,并且當(dāng)需要的時(shí)候內(nèi)核也會(huì)調(diào)度這個(gè)進(jìn)程去運(yùn)行。
??4. 軟中斷并不會(huì)直接中斷CPU。也只有當(dāng)前正在運(yùn)行的代碼(或進(jìn)程)才會(huì)產(chǎn)生軟中斷。這種中斷是一種需要內(nèi)核為正在運(yùn)行的進(jìn)程去做一些事情(通常為I/O)的請(qǐng)求。有一個(gè)特殊的軟中斷是Yield調(diào)用,它的作用是請(qǐng)求內(nèi)核調(diào)度器去查看是否有一些其他的進(jìn)程可以運(yùn)行。
??硬中斷是外部設(shè)備對(duì)CPU的中斷,軟中斷是中斷底半部的一種處理機(jī)制,而信號(hào)則是由內(nèi)核(或其他進(jìn)程)對(duì)某個(gè)進(jìn)程的中斷。在涉及系統(tǒng)調(diào)用的場(chǎng)合,人們也常說通過軟中斷(例如ARM為swi)陷入內(nèi)核,此時(shí)軟中斷的概念是指由軟件指令引發(fā)的中斷,和我們這個(gè)地方說的softirq是兩個(gè)完全不同的概念,一個(gè)是software,一個(gè)是soft。
??需要特別說明的是,軟中斷以及基于軟中斷的tasklet如果在某段時(shí)間內(nèi)大量出現(xiàn)的話,內(nèi)核會(huì)把后續(xù)軟中斷放入ksoftirqd內(nèi)核線程中執(zhí)行。總的來說,中斷優(yōu)先級(jí)高于軟中斷,軟中斷又高于任何一個(gè)線程。軟中斷適度線程化,可以緩解高負(fù)載情況下系統(tǒng)的響應(yīng)。
聯(lián)系客服