關(guān)于linux中斷的雜記
ARM contex-A系列的內(nèi)核不支持中斷嵌套(不支持中斷嵌套的cpu在處理一個中斷時無法相應(yīng)其他中斷)。在內(nèi)核中斷函數(shù)中,如果中斷處理的時間過長,產(chǎn)生中斷嵌套,重者系統(tǒng)崩潰,
??? 輕者也會影響其他事件的處理,這就是中斷中不能使用延時函數(shù)的原因。
但是有些高實時性設(shè)備(比如網(wǎng)卡),就是需要處理大量業(yè)務(wù),為了滿足中斷處理時間盡量短的原則,我們將一些簡單的處理放在中斷中實現(xiàn),這個階段叫做中斷的上
??? 半部,剩下的復(fù)雜、耗時間的操作丟給內(nèi)核線程,讓內(nèi)核來調(diào)度其執(zhí)行,這就是中斷的下半部。
linux中斷的處理流程:
??? 中斷事件----->跳轉(zhuǎn)中斷處理程序入口------->執(zhí)行中斷處理程序上半部------->將中斷處理程序下半部交給內(nèi)核調(diào)度----->結(jié)束中斷處理
linux中斷的處理方式:
1.softirq:處理比較快,但是它是內(nèi)核級別的機制,需要修改整個內(nèi)核源碼,不推薦也不常用,會并發(fā)地執(zhí)行在不同的cpu上
2.tasklet:內(nèi)部實際調(diào)用了softirq,會并發(fā)地執(zhí)行在不同的cpu上,在軟中斷中劃分一部分線程專門用于處理tasklet數(shù)據(jù)結(jié)構(gòu)中待處理的接口。大部分中斷的下半部分使用
tasklet即可,只有對像網(wǎng)絡(luò)這樣對性能要求非常高的情況,才需要使用軟終端,2個相同的軟中斷有可能同時執(zhí)行。兩個不同類型的tasklet可以在不同的處理器上運行,但類型相同的tasklet不能同時執(zhí)行
3.workqueue:工作隊列,對資源的消耗更加少切只會線性地串行執(zhí)行而不會并發(fā)執(zhí)行。對于一些性能要求較高的子系統(tǒng),如網(wǎng)絡(luò)部分,不能勝任
tasklet:啟動下半部實際上就是tasklet結(jié)構(gòu)體描述的對象丟給內(nèi)核線程的操作,基本上就是定義好處理函數(shù)后初始化一個tasklet對象實例,然后將結(jié)構(gòu)體
交給內(nèi)核,并等待內(nèi)核調(diào)度軟中斷線程來執(zhí)行。
struct tasklet_struct
{
??? struct tasklet_struct *next;
??? unsigned long state;
??? atomic_t count;
??? void (*func)(unsigned long);//下半部的處理邏輯
??? unsigned long data;//傳遞給func
}
1、初始化對象
struct tasklet_struct mytasklet;
tasklet_init(struct tasklet_struct *t,void(*func(unsigned long),unsigned long data)
或者
DECLARE_TASKLET(name,function,data)
2、構(gòu)造”下半部“的實現(xiàn)邏輯
void key_tasklet_half_irq(unsigned long data)
{
}
3”上半部“啟動”下半部“
tasklet_schedule(&key_dev->mytasklet)
4、在模塊卸載時注銷內(nèi)核線程中的對象
tasklet_kill(&key_dev->mytasklet);
workqueue:
工作隊列的使用情況分為兩種:一是使用內(nèi)核定義的工作隊列,二是自行定義工作隊列。常規(guī)是使用內(nèi)核定義的工作隊列,除非由特別多的工作會加入到工作隊列中,
否則不需要自己定義工作隊列。
struct work_struct
{
??? atomic_long_t data;
??? struct list_head entry;
??? work_func_t func;
}
初始化對象:
struct work_struct mywork;
定義服務(wù)接口函數(shù):
void xxx_func(struct work_struct *work)
綁定處理函數(shù)接口
INIT_WORK(struct work_struct *work,work_func_t func);
構(gòu)造”下半部分“實現(xiàn)邏輯
void work_irq_half(struct work_struct *work_struct)
{
}
將工作計入到隊列中等待調(diào)度
schedule_work(struct work_struct *work);
確保沒有工作隊列入口在系統(tǒng)中任何地方運行,會等待所有在處理的隊列完成
void flush_schedule_work(void)
延時調(diào)度工作
int schedule_delayed_work(struct delayed_struct *work, unsigned long delay);
取消加入工作隊列的工作
int cancel_delayed_work(struct delay_struct *work);
取消加入工作隊列的工作的接口函數(shù)cancel_delayed_work只用于取消延時的工作隊列,因為非延時的工作隊列通常在執(zhí)行調(diào)度請求后很快就會執(zhí)行,此時取消
用處不大,當(dāng)然如果發(fā)起調(diào)度和取消調(diào)度都在中斷上下文是可以的。
中斷
1.硬件中斷------異步中斷
硬件中斷本質(zhì)上是一種電信號,有硬件設(shè)備發(fā)出,用于通知處理器特定事件,不同的設(shè)備對應(yīng)不同的中斷,每個中斷通過唯一的數(shù)字標示,稱為中斷請求(IRQ)。
處理器內(nèi)部對其進行編號后,也稱為中斷號。
2.異常----同步中斷
異常不同于中斷,產(chǎn)生時必須考慮與處理器時鐘同步。異常常常稱為同步中斷,是由于處理器執(zhí)行時由于編程失誤而導(dǎo)致的錯誤指令或執(zhí)行期間出現(xiàn)特殊情況(
如缺頁),必須靠內(nèi)核來處理,處理器就會產(chǎn)生一個異常。
我們通常所說的中斷,指的是硬件產(chǎn)生的異步中斷,由于許多處理器體系結(jié)構(gòu)處理異常與處理中斷的方式類似,因此內(nèi)核對他們的處理方式也類似。
軟中斷:
工作方式類似與異步中斷,區(qū)別是它是通過軟件引起的中斷,異步中斷是硬件引發(fā)的。
中斷處理程序:
相應(yīng)一個特定中斷時,內(nèi)核會執(zhí)行一個函數(shù),稱為中斷處理程序或者中斷服務(wù)程序。設(shè)備產(chǎn)生的每一個中斷都會與特定的處理函數(shù)綁定,一個設(shè)備可能會產(chǎn)生多個不同
中斷,那么該設(shè)備就可以對應(yīng)多個處理程序,相應(yīng)地,設(shè)備驅(qū)動程序就需要準備多個這樣的函數(shù)。
linux中斷處理程序特點:
linux中,中斷處理程序看起來想普通c函數(shù),按特定類型聲明,以便內(nèi)核能以標準的方式傳遞處理程序的信息。
中斷處理程序與其他內(nèi)核函數(shù)的區(qū)別:中斷處理程序是內(nèi)核調(diào)用來相應(yīng)中斷的,而他們被運行于被稱為中斷上下文的特殊上下文中,由于中斷隨時可能發(fā)生,因此
中斷處理程序必須隨時、盡快執(zhí)行,這樣才能盡快、及時地相應(yīng)中斷,同時恢復(fù)中斷代碼的執(zhí)行。
上下半部的對比:
1.上半部
中斷處理程序是上半部,接收到中斷以后,就立即開始執(zhí)行,但只能做嚴格限時的工作,如對接收中斷進行應(yīng)答或者復(fù)位硬件,這些工作都是在所有中斷被禁止的
情況下完成的。
2.下半部
中斷處理程序中,能被允許稍后完成的工作會推遲到下半部,linux提供下半部的各種機制。
注冊中斷處理程序
如果設(shè)備使用中斷,那么相應(yīng)的驅(qū)動程序就需要注冊一個中斷處理程序
驅(qū)動程序注冊并激活一個中斷處理程序:
request_irq:分配一個指定的中斷號,可能會休眠,因此不能在中斷上下文或者其他不被允許阻塞的代碼中調(diào)用
int request_irq(
??? unsigned int irq, /*要分配的中斷號*/
??? irqreturn_t (*handler)(int,void*,struct pt_regs *),/*指向?qū)嶋H的中斷處理程序,收到中斷時內(nèi)核調(diào)用*/
??? unsigned long irqflags,/*標志位*/
??? const char *devname,/*中斷相關(guān)的設(shè)備ASCLL文本表示法*/
??? void *dev_id /*用于共享中斷線*/
)
參數(shù)說明:
irq:表示要分配的中斷號。對應(yīng)某些設(shè)備,該值通常為預(yù)先固定的,對于大多數(shù)設(shè)備,該值可以探測或者通過編程動態(tài)確定
handler:函數(shù)指針,指向該中斷的實際中斷處理程序,系統(tǒng)接收到中斷,該函數(shù)就會被調(diào)用。
irqflags:可以為0,也可以為下面一個或者多個標志的位掩碼:
??? SA_INTERRUPT:表明給定的中斷處理程序是一個快速中斷處理程序,默認不帶該標志。
??? 在本地處理器上,快速中斷處理程序是在禁止所有中斷的情況下運行的,以便于不受其他中斷影響,快速執(zhí)行,適用于時鐘中斷。
??? SA_SAMPLE_RANDOM:表明該設(shè)備產(chǎn)生的中斷對內(nèi)核熵池有貢獻,內(nèi)核熵池負責(zé)提供從各種隨機事件導(dǎo)出真正的隨機數(shù),如果制定該標志,表明該設(shè)備
??? 的中斷事件間隔就會作為熵值填充到熵池,如果你的設(shè)備以預(yù)知的速率產(chǎn)生中斷(如系統(tǒng)定時器),或者可能受到外部攻擊,就不要設(shè)置該標志。
??? SA_SHIRQ:表明可以在多個中斷處理程序之間共享中短線。在同一個給定的中斷線上注冊的每個處理程序必須指定這個標志,否則在每條中斷線上只能有一個處理
??? 程序。
devname:與中斷相關(guān)的設(shè)備的ASCII文本表示法,例如,PC機上鍵盤中斷對應(yīng)的這個值為”keyboard“,這些名字會被/proc/irq和/proc/interrupt文件使用,
以便與用戶通信。可以理解為設(shè)備名字
dev_id:用于共享中斷線,當(dāng)一個中斷處理程序需要釋放時,dev_id將提供唯一的標志信息(cookie),以便從共享中斷線的諸多中斷處理程序中刪除指定的
那一個,如果沒有該參數(shù),那么內(nèi)核不可能知道在給定的中斷線上到底要刪除哪個處理程序,如果無需共享中斷線,那么設(shè)置該參數(shù)為NULL即可。若需要共享,則
必須吧dev_id這個指針傳遞給內(nèi)核,因為不同的處理程序函數(shù)可能位于不同的驅(qū)動中,它們共享一條中斷線,內(nèi)核必須準確為他們創(chuàng)造執(zhí)行環(huán)境,此時通過該指針
將有用的環(huán)境信息傳遞給他們。實踐中,經(jīng)常通過它傳遞驅(qū)動程序的設(shè)備結(jié)構(gòu),這個指針是唯一的,而且有可能在中斷處理程序內(nèi)及設(shè)備模式中被用到。
request_irq為什么會睡眠:
因為在注冊過程中,內(nèi)核需要在/proc/irq文件中創(chuàng)建一個與中斷對應(yīng)的項,則會調(diào)用proc_mkdir()來創(chuàng)建這個新的procfs項,proc_mkdir()調(diào)用proc_create()
對這個新的procfs項進行設(shè)置,proc_create申請內(nèi)存時調(diào)用的是kmalloc來請求內(nèi)核分配內(nèi)存,kmalloc()是可以睡眠的,因此request_irq會睡眠。
釋放中斷處理程序:
void free_irq(unsigned int irq,void *dev_id);
如果指定的中斷線不是共享的,那么該函數(shù)將刪除處理程序并且禁用中斷線;如果是共享,則僅刪除dev_id對應(yīng)的處理程序,該中斷線只有在所有的處理程序都被刪除后才會被禁用。
這也是dev_id必須唯一的原因:共享中斷線中,用來區(qū)分不同的處理程序。必須從進程上下文中調(diào)用free_irq();
編寫中斷處理程序:
典型的中斷處理程序聲明:
static irqreturn_t intr_handler(int irq,void *dev_id,struct pt_regs *regs);
參數(shù)說明:
irq:要相應(yīng)的中斷號
dev_id:與中斷處理程序注冊時傳遞給request_irq()的參數(shù)dev_id必須一致。
regs:指向結(jié)構(gòu)體的指針,包含了處理中斷前處理器的寄存器和狀態(tài),除了調(diào)試,其他時候很少使用。
irqreturn_t:實際上是int類型,為了與早起內(nèi)核兼容,2.6以前版本是void類型
linux中中斷處理程序無需重入,當(dāng)給定的中斷處理程序正在執(zhí)行使,相應(yīng)的終端線在所有的處理器上都會被屏蔽,以防止同一個中斷線上接收另一個新的中斷,
也就是說,同一個中斷線,同一個中斷處理程序,在執(zhí)行完畢前不可能同時在兩個處理器上執(zhí)行,加上中斷程序不能休眠,因此無需考慮可重入性。
注意:
1、如果中斷程序可以被中斷(不是同一個中斷線的中斷),稱為嵌套中斷或者中斷嵌套。
2、舊版本linux允許中斷嵌套,但是2010以后提交的版本以及禁止中斷嵌套。
中斷上下文:
當(dāng)執(zhí)行一個中斷處理程序(中斷上半部)或中斷下半部時,內(nèi)核處于中斷上下文中。
進程上下文是內(nèi)核所處的模式。在進程上下文中,可通過“current”宏關(guān)聯(lián)當(dāng)前進程,此外,因為進程是以進程上下文的形式連接到內(nèi)核中,因此,在進程上下文
中可以睡眠,也可以調(diào)度程序。
中斷上下文的特性:
1、中斷上下文和進程沒什么關(guān)系,跟“current”宏也不相干(盡管它會指向被中斷的進程),因為沒有進程的背景,所以中斷上下文不能睡眠,否則沒辦法對其重新調(diào)度。
畢竟調(diào)度器調(diào)度的是進程。
2、嚴格時間限制,盡可能迅速、簡短
中斷上下文具有嚴格的時間限制,因為它打斷了其他代碼
3、盡量把耗時間的工作放在下半部執(zhí)行
因為中斷處理程序要求迅速、簡短,因此需要將耗時間的部分抽離出來,放到下半部分執(zhí)行
4、中斷程序棧有限,謹慎使用
中斷處理程序共享所中斷進程的內(nèi)核棧,大小是兩頁,32位體系結(jié)構(gòu)上是8kB,64位體系是16kB
中斷處理上半部與下半部的工作劃分雖然沒有嚴格的標準限制,但可參照如下標準:
1、如果一個任務(wù)對時間非常敏感,將其放入上半部
2、如果一個任務(wù)與硬件相關(guān),將其放入上半部
3、如果一個任務(wù)要保證不被其他中斷打斷,將其放入上半部執(zhí)行
4、其他任務(wù)可以考慮放入下半部
為什么要使用下半部:
洗完減少中斷處理程序中需要完成的工作量,因為它運行時,當(dāng)前中斷線在所有的處理器上都會被屏蔽,如果是SA_INTERRUPT類型的中斷處理程序,執(zhí)行時會禁止所有的
本地中斷,因此縮短中斷屏蔽時間,將一些工作放到以后去做,對系統(tǒng)的響應(yīng)能力和性能都至關(guān)重要。
具體放到什么時候做:
沒有明確時間,只要在中斷恢復(fù)后執(zhí)行即可,通常下半部在處理程序一返回就會馬上執(zhí)行。關(guān)鍵在于,這些工作運行時,系統(tǒng)可以響應(yīng)所有中斷。
上下文:可以看做是運行所需的參數(shù)以及環(huán)境變量
中斷上下文:硬件傳過來的參數(shù)以及內(nèi)核所需保存和設(shè)置的環(huán)境變量