4,、下半部 在中斷處理過(guò)程中,,不能睡眠,。另外,,它運(yùn)行的時(shí)候,會(huì)把當(dāng)前中斷線在所有處理器上都屏蔽(在ack中完成屏蔽),;更糟糕的情況是,,如果一個(gè)處理程序是SA_INTERRUPT類型,它執(zhí)行的時(shí)候會(huì)禁上所有本地中斷(通過(guò)cli指令完成),,所以,,中斷處理應(yīng)該盡可能快的完成。所以Linux把中斷處理分為上半部和下半部,。 上半部由中斷處理程序完成,,它通常完成一些和硬件相關(guān)的操作,比如對(duì)中斷的到達(dá)的確認(rèn),。有時(shí)它還會(huì)從硬件拷貝數(shù)據(jù),,這些工作對(duì)時(shí)間非常敏感,只能靠中斷處理程序自己完成,。而把其它工作放到下半部實(shí)現(xiàn),。 下半部的執(zhí)行不需要一個(gè)確切的時(shí)間,它會(huì)在稍后系統(tǒng)不太繁忙時(shí)執(zhí)行,。下半部執(zhí)行的關(guān)鍵在于運(yùn)行的時(shí)候允許響應(yīng)所有的中斷,。最早,Linux用”bottom half”實(shí)現(xiàn)下半部,,這種機(jī)制簡(jiǎn)稱BH,但是即使屬于不同的處理器,,也不允許任何兩個(gè)bottom half同時(shí)執(zhí)行,,這種機(jī)制簡(jiǎn)單,但是卻有性能瓶頸,。不久,,又引入任務(wù)隊(duì)列(task queue)機(jī)制來(lái)實(shí)現(xiàn)下半部,但該機(jī)制仍不夠靈活,,沒(méi)法代替整個(gè)BH接口,。 從2.3開始,內(nèi)核引入軟中斷(softirqs)和tasklet,,并完全取代了BH,。2.5中,BH最終舍去,,在2.6中,,內(nèi)核用有三種機(jī)制實(shí)現(xiàn)下半部:軟中斷,tasklet和工作隊(duì)列,。Tasklet是基于軟中斷實(shí)現(xiàn)的,。軟中斷可以在多個(gè)CPU上同時(shí)執(zhí)行,,即使它們是同一類型的,所以,,軟中斷處理程序必須是可重入的,,或者顯示的用自旋鎖保護(hù)相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。而相同的tasklet不能同時(shí)在多個(gè)CPU上執(zhí)行,,所以tasklet不必是可重入的,;但是,不同類型的tasklet可以在多個(gè)CPU上同時(shí)執(zhí)行,。一般來(lái)說(shuō),,tasklet比較常用,它可以處理絕大部分的問(wèn)題,;而軟中斷用得比較少,,但是對(duì)于時(shí)間要求較高的地方,比如網(wǎng)絡(luò)子系統(tǒng),,常用軟中斷處理下半部工作,。 4.1、軟中斷 內(nèi)核2.6中定義了6種軟中斷: 下標(biāo)越低,,優(yōu)先級(jí)越高,。 4.1.1、數(shù)據(jù)結(jié)構(gòu) (1)軟中斷向量 //linux/interrupt.h 內(nèi)核定義了一個(gè)包含32個(gè)軟中斷向量的數(shù)組,,所以最多可有32個(gè)軟中斷,,實(shí)際上,內(nèi)核目前只使用了6個(gè)軟中斷,。struct softirq_action { void (*action)(struct softirq_action *); //待執(zhí)行的函數(shù) void *data; //傳給函數(shù)的參數(shù) }; //kernel/softirq.c //軟中斷向量數(shù)組 static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp; (2) preempt_count字段 位于任務(wù)描述符的preempt_count是用來(lái)跟蹤內(nèi)核搶占和內(nèi)核控制路徑嵌套關(guān)鍵數(shù)據(jù),。其各個(gè)位的含義如下: 位 描述 0——7 preemption counter,內(nèi)核搶占計(jì)數(shù)器(最大值255) 8——15 softirq counter,,軟中斷計(jì)數(shù)器(最大值255) 16——27 hardirq counter,,硬件中斷計(jì)數(shù)器(最大值4096) 28 PREEMPT_ACTIVE標(biāo)志 第一個(gè)計(jì)數(shù)用來(lái)表示內(nèi)核搶占被關(guān)閉的次數(shù),0表示可以搶占,。第二個(gè)計(jì)數(shù)器表示推遲函數(shù)(下半部)被關(guān)閉的次數(shù),,0表示推遲函數(shù)打開。第三個(gè)計(jì)數(shù)器表示本地CPU中斷嵌套的層數(shù),,irq_enter()增加該值,,irq_exit減該值。 宏in_interrupt()檢查current_thread_info->preempt_count的hardirq和softirq來(lái)斷定是否處于中斷上下文,。如果這兩個(gè)計(jì)數(shù)器之一為正,,則返回非零。 (3) 軟中斷控制/狀態(tài)結(jié)構(gòu) softirq_vec是個(gè)全局量,系統(tǒng)中每個(gè)CPU所看到的是同一個(gè)數(shù)組,。但是,,每個(gè)CPU各有其自己的“軟中斷控制/狀態(tài)”結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)形成一個(gè)以CPU編號(hào)為下標(biāo)的數(shù)組irq_stat[](定義在include/asm-i386/hardirq.h中) typedef struct { 4.1.2,、軟中斷初始化unsigned int __softirq_pending; unsigned long idle_timestamp; unsigned int __nmi_count; /* arch dependent */ unsigned int apic_timer_irqs; /* arch dependent */ } ____cacheline_aligned irq_cpustat_t; //位于kernel/softirq.c irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned; 可以通過(guò)open_softirq注冊(cè)軟中斷處理程序: //位于kernel/softirq.c 軟中斷執(zhí)行時(shí),,允許響應(yīng)中斷,但它自己不能睡眠,,//nr:軟中斷的索引號(hào) // softirq_action:處理函數(shù) //data:傳遞給處理函數(shù)的參數(shù)值 void open_softirq(int nr, void (*action)(struct softirq_action*), void *data) { softirq_vec[nr].data = data; softirq_vec[nr].action = action; } //軟中斷初始化 void __init softirq_init(void) { open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); } 4.1.3,、觸發(fā)軟中斷 raise_softirq會(huì)將軟中斷設(shè)置為掛起狀態(tài),并在下一次運(yùn)行do_softirq中投入運(yùn)行,。 //位于kernel/softirq.c 該函數(shù)觸發(fā)軟中斷前,,先要關(guān)閉中斷,之后再恢復(fù),;如果之前中斷已經(jīng)關(guān)閉,,可以直接調(diào)用raise_softirq_irqoff()觸發(fā)軟中斷。void fastcall raise_softirq(unsigned int nr) { unsigned long flags; //保存IF值,并關(guān)中斷 local_irq_save(flags); //調(diào)用wakeup_softirqd() raise_softirq_irqoff(nr); //恢復(fù)IF值 local_irq_restore(flags); } inline fastcall void raise_softirq_irqoff(unsigned int nr) { //把軟中斷設(shè)置為掛起狀態(tài) __raise_softirq_irqoff(nr); //喚醒內(nèi)核線程 if (!in_interrupt()) wakeup_softirqd(); } 在中斷服務(wù)例程中觸發(fā)軟中斷是最常見(jiàn)的形式,。而中斷服務(wù)例程通常作為設(shè)備驅(qū)動(dòng)的一部分,。例如,對(duì)于網(wǎng)絡(luò)設(shè)備,,當(dāng)接口收到數(shù)據(jù)時(shí),,會(huì)產(chǎn)生一個(gè)中斷,在中斷服務(wù)例程中,,最終會(huì)調(diào)用netif_rx函數(shù)處理接到的數(shù)據(jù),,而netif_rx作相應(yīng)處理,最終以觸發(fā)一個(gè)軟中斷結(jié)束處理,。之后,,內(nèi)核在執(zhí)行中斷處理任務(wù)后,會(huì)調(diào)用do_softirq(),。于是軟中斷就通過(guò)軟中斷處理函數(shù)去處理留給它的任務(wù)。 4.1.4,、軟中斷執(zhí)行 (1) do_softirq()函數(shù) //處理軟中斷,,位于arch/i386/kernel/irq.c (2)__do_softirq()函數(shù)asmlinkage void do_softirq(void) { //處于中斷上下文,表明軟中斷是在中斷上下文中觸發(fā)的,或者軟中斷被關(guān)閉 /*這個(gè)宏限制了軟中斷服務(wù)例程既不能在一個(gè)硬中斷服務(wù)例程內(nèi)部執(zhí)行, *也不能在一個(gè)軟中斷服務(wù)例程內(nèi)部執(zhí)行(即嵌套),。但這個(gè)函數(shù)并沒(méi)有對(duì)中斷服務(wù)例程的執(zhí)行 *進(jìn)行“串行化”限制,。這也就是說(shuō),不同的CPU可以同時(shí)進(jìn)入對(duì)軟中斷服務(wù)例程的執(zhí)行,,每個(gè)CPU *分別執(zhí)行各自所請(qǐng)求的軟中斷服務(wù),。從這個(gè)意義上說(shuō),軟中斷服務(wù)例程的執(zhí)行是“并發(fā)的”、多序的,。 *但是,,這些軟中斷服務(wù)例程的設(shè)計(jì)和實(shí)現(xiàn)必須十分小心,不能讓它們相互干擾(例如通過(guò)共享的全局變量),。 */ if (in_interrupt()) return; //保存IF值,并關(guān)中斷 local_irq_save(flags); //調(diào)用__do_softirq asm volatile( " xchgl %%ebx,%%esp \n" " call __do_softirq \n" " movl %%ebx,%%esp \n" : "=b"(isp) : "0"(isp) : "memory", "cc", "edx", "ecx", "eax" ); //恢復(fù)IF值 local_irq_restore(flags); //執(zhí)行軟中斷,位于kernel/softirq.c (3)軟中斷執(zhí)行點(diǎn)asmlinkage void __do_softirq(void) { struct softirq_action *h; __u32 pending; /*最多迭代執(zhí)行10次.在執(zhí)行軟中斷的過(guò)程中,由于允許中斷,所以新的軟中斷可能產(chǎn)生.為了使推遲函數(shù)能夠在 *較短的時(shí)間延遲內(nèi)執(zhí)行,__do_softirq會(huì)執(zhí)行所有掛起的軟中斷,這可能會(huì)執(zhí)行太長(zhǎng)的時(shí)間而大大延遲返回用戶 *空間的時(shí)間.所以,__do_softirq最多允許10次迭代.剩下的軟中斷在軟中斷內(nèi)核線程ksoftirqd中處理. */ int max_restart = MAX_SOFTIRQ_RESTART; int cpu; //用局部變量保存軟件中斷位圖 pending = local_softirq_pending(); /*增加softirq計(jì)數(shù)器的值.由于執(zhí)行軟中斷時(shí)允許中斷,當(dāng)do_IRQ調(diào)用irq_exit時(shí),另一個(gè)__do_softirq實(shí)例可能 *開始執(zhí)行.這是不允許的,推遲函數(shù)必須在CPU上串行執(zhí)行. */ local_bh_disable(); cpu = smp_processor_id(); restart: /* Reset the pending bitmask before enabling irqs */ //重置軟中斷位圖,使得新的軟中斷可以發(fā)生 local_softirq_pending() = 0; //開啟本地中斷,執(zhí)行軟中斷時(shí),允許中斷的發(fā)生 local_irq_enable(); h = softirq_vec; do { if (pending & 1) { //執(zhí)行軟中斷處理函數(shù) h->action(h); rcu_bh_qsctr_inc(cpu); } h++; pending >>= 1; } while (pending); //關(guān)閉中斷 local_irq_disable(); //再一次檢查軟中斷位圖,因?yàn)樵趫?zhí)行軟中斷處理函數(shù)時(shí),新的軟中斷可能產(chǎn)生. pending = local_softirq_pending(); if (pending && --max_restart) goto restart; /*如果還有多的軟中斷沒(méi)有處理,通過(guò)wakeup_softirqd喚醒內(nèi)核線程處理本地CPU余下的軟中斷. */ if (pending) wakeup_softirqd(); //減softirq counter的值 __local_bh_enable(); } 內(nèi)核會(huì)周期性的檢查是否有掛起的軟中斷,,它們位于內(nèi)核代碼的以下幾個(gè)點(diǎn): (1)內(nèi)核調(diào)用local_bh_enable()函數(shù)打開本地CPU的軟中斷: //位于kernel/softirq.c (2)do_IRQ函數(shù)完成I/O中斷處理,調(diào)用irq_exit()時(shí),。void local_bh_enable(void) { preempt_count() -= SOFTIRQ_OFFSET - 1; if (unlikely(!in_interrupt() && local_softirq_pending())) do_softirq(); //軟中斷處理 //…… } (3)內(nèi)核線程ksoftirqd被喚醒,。 (4) smp_apic_timer_interrupt()完成處理本地時(shí)鐘中斷。 |
|
來(lái)自: clover_xian > 《我的圖書館》