http://blog.chinaunix.net/uid-9185047-id-445157.html
2010
這里就行了很深入的討論,值得一看:
http://bbs2.chinaunix.net/viewthread.php?tid=1618430但是,他們的討論最后沒有得出一個(gè)明確的結(jié)論。其中,cskyrain在8樓 的思考觸及到了一個(gè)要點(diǎn),但是沒有深入展開:
1樓 發(fā)表于 2009-11-24 20:36 | 只看該作者
一直認(rèn)為中斷處理函數(shù)不能休眠的是天經(jīng)地義的,可從沒認(rèn)真思考過問什么不能休眠,阻塞。最近看了一下ulk中對這個(gè)的解釋,感覺還是有點(diǎn)不太明白,
“The price to pay for allowing nested kernel control paths is that an interrupt handler must never block, that is, no process switch can take place until an interrupt handler is running. In fact, all the data needed to resume a nested kernel control path is stored in the Kernel Mode stack, which is tightly bound to the current process.”
上面把中斷處理程序不能休眠歸結(jié)為中斷處理程序可以嵌套,而恢復(fù)嵌套的中斷處理程序的相關(guān)數(shù)據(jù)放在內(nèi)核態(tài)堆棧中,這個(gè)棧和當(dāng)前進(jìn)程相關(guān)聯(lián),這里有一點(diǎn)不明白,既然有棧存儲(chǔ)數(shù)據(jù),而且進(jìn)程切換出去后,這個(gè)棧也不會(huì)被銷毀,等進(jìn)程在切回來時(shí),不同樣可以是嵌套的中斷處理程序返回嗎?(這里先不考了中斷處理時(shí)間的太長,影響對中斷處理的服務(wù)問題,只說明中斷是否可休眠)。同時(shí)我google一下,看到有人對中斷不能休眠的以一種解釋:
”
中斷處理程序用到的所有數(shù)據(jù)有保存在當(dāng)前進(jìn)程的內(nèi)核堆棧中(一般情況下),如果此時(shí)發(fā)生了
進(jìn)程切換,中斷處理程序?qū)⒈蛔枞?nbsp;當(dāng)在一次發(fā)生進(jìn)程切換時(shí),不一定馬上就換回來。比如當(dāng)
發(fā)生一個(gè)鍵盤中斷時(shí),鍵盤處理程序正在進(jìn)行,此時(shí)又恰好發(fā)生了進(jìn)程搶占,切換到了另一個(gè)進(jìn)程
中,假如那個(gè)進(jìn)程不會(huì)引起內(nèi)核的穩(wěn)定,那么中斷處理程序?qū)⒁恢弊枞?,?nèi)禾根本就沒法響應(yīng)那個(gè)中斷,
直到在一次發(fā)生進(jìn)程切換。假設(shè)最后又切換回執(zhí)行中斷處理程序的那個(gè)進(jìn)程中時(shí),如果它的內(nèi)核堆棧
受到了無意的破壞怎么辦呢?那中斷處理程序可能無法在繼續(xù)運(yùn)行下去了,此次中斷服務(wù)失敗。而且
現(xiàn)在的處理程序都允許中斷嵌套,一個(gè)中斷被阻塞,其它的都將被阻塞。所以面對錯(cuò)綜復(fù)雜的內(nèi)核邏輯,
最好的辦法就是在中斷處理程序中禁止發(fā)生進(jìn)程切換,這樣既提高了中斷處理程序的響應(yīng)速度,也增加了
內(nèi)核的穩(wěn)定性與安全性。
“
我感覺他的理由有兩個(gè):一個(gè)事效率,可能影響處理速度,另一就是:如果它的內(nèi)核堆棧
受到了無意的破壞怎么辦?
對于第一種理由先不討論,
可第二種,理由我感覺很牽強(qiáng),如果棧那么容易破壞,哪我也可以說描述進(jìn)程的數(shù)據(jù)結(jié)構(gòu)什么的也可能被破壞,哪豈不是進(jìn)程調(diào)度都是不安全的了,
這樣按他的說法,就只有第一個(gè)效率的原因了。
回到ulk的解釋,不知道是不是我理解的有問題,The price to pay for allowing nested kernel control paths is that an interrupt handler must never block
感覺他把中端不能休眠的原因都?xì)w結(jié)為可支持中斷處理程序的嵌套上了。那是不是可以這樣理解,如果不支持中斷嵌套,中斷處理程序就可以休眠了,呵呵呵,貌似這樣也不對吧,
問題:中斷處理程序不能休眠的原因究竟是什么呢?
7樓:
kouu 發(fā)表于 2009-11-25 11:52
回復(fù) #6 cskyrain 的帖子
中斷不能block,應(yīng)該特指異步中斷吧。
看了LZ這兩天的帖子,我覺得還是類似4樓的說法比較靠譜:異步中斷是獨(dú)立的上下文,與當(dāng)前進(jìn)程無關(guān),所以不能因?yàn)橹袛嗌舷挛牡腷lock而將無辜的進(jìn)程給block了??赡芫褪腔谶@一初衷吧~ 那些已經(jīng)將中斷處理程序線程化了的實(shí)時(shí)linux應(yīng)該是允許中斷block的。
而同步的中斷(比如系統(tǒng)調(diào)用、缺頁異常)是代表當(dāng)前進(jìn)程的,本來就是可以block的。
8樓:
回復(fù) #7 kouu 的帖子
呵呵,又翻了翻書,思考了一下,由于時(shí)間問題,沒心情再從頭讀ulk,只是跳著查了一下,難免會(huì)漏下很多東西,不過還是說說我的理解:
這里中斷只代表異步中斷,異常代表同步中斷,這樣系統(tǒng)調(diào)用是異常處理,不是中斷處理。
這里異常處理是可以休眠block的,因?yàn)楫惓L幚硭璧臄?shù)據(jù)是存儲(chǔ)在異常棧中,而每個(gè)進(jìn)程都有一個(gè)異常棧,所以異常處理和進(jìn)程是相關(guān)聯(lián)的,這樣異常處理可以block,被調(diào)度出去。
而對于中斷,分為兩種情況,一種是中斷使用單獨(dú)的中斷棧而不使用進(jìn)程的內(nèi)核棧的情況,這樣,由于所有中斷共享一個(gè)中斷棧,這個(gè)中斷棧不和特定進(jìn)程關(guān)聯(lián),所以,這種中斷時(shí)不能block的,block后他是不能再被調(diào)度。
第二種情況是,中斷不使用單獨(dú)的中斷棧,而是使用當(dāng)前進(jìn)程的內(nèi)核棧,這種情況我認(rèn)為是和異常處理時(shí)一樣的,這種中斷時(shí)可以block的,之所以不準(zhǔn)許中斷 block不是技術(shù)上切換不回來,而是邏輯上為了提高處理的效率強(qiáng)制其不能block。
以上是我現(xiàn)階段的理解,感覺,前倆個(gè)的解釋應(yīng)該沒什么問題,但最后對不是用中斷棧的中斷的解釋可能有錯(cuò)誤的認(rèn)識,不知kouu對這種解釋有何看法?
總體上,大家得到的初步結(jié)論是:內(nèi)核在中斷路徑內(nèi)不能睡眠,不是技術(shù)上做不到,而是沒有理由這么做,或者說在中斷路徑上睡眠不合理。一方面,外部事件導(dǎo)致當(dāng)前進(jìn)程時(shí)間片被剝奪,不合理;一方面,中斷服務(wù)程序應(yīng)該盡快處理完中斷,保證IO吞吐率。
-------------------------------分割線----------------------------
通過閱讀ULK中文版第三版164頁的內(nèi)容,讓我對這個(gè)問題有了一個(gè)較為清晰的認(rèn)識:
內(nèi)核在編譯的時(shí)候設(shè)置了THREAD_SIZE的值為8K的話, 那么每個(gè)進(jìn)程的內(nèi)核棧的大小就為8K, 此時(shí)如果發(fā)生中斷時(shí), 那么進(jìn)程的寄存器等值就會(huì)保存到它的8K的內(nèi)核棧中. 但是如果設(shè)置了THREAD_SIZE的大小為4K的話, 內(nèi)核就會(huì)使用3種類型的內(nèi)核棧, 異常棧, 硬件中斷請求棧以及軟中斷請求棧( When using 4K stacks, interrupts get their own stack instead of using the currently active kernel stack.)
* 異常棧:每個(gè)進(jìn)程一個(gè)。
* 硬中斷請求棧:每個(gè)CPU一個(gè),每個(gè)占用一個(gè)單獨(dú)的頁框。do_IRQ()函數(shù)內(nèi)部通過調(diào)用execute_on_irq_stack負(fù)責(zé)切換到該棧中來。
* 軟中斷請求棧:每個(gè)CPU一個(gè),每個(gè)占用一個(gè)單獨(dú)的頁框。
關(guān)于切換到中斷棧的方法,請看下面的代碼:
view plaincopy to clipboardprint?/*
* do_IRQ handles all normal device IRQ's (the special
* SMP cross-CPU interrupts have their own specific
* handlers).
*/
unsigned int do_IRQ(struct pt_regs *regs)
{
struct pt_regs *old_regs;
/* high bit used in ret_from_ code */
int overflow;
unsigned vector = ~regs->orig_ax;
struct irq_desc *desc;
unsigned irq;
old_regs = set_irq_regs(regs);
irq_enter();
irq = __get_cpu_var(vector_irq)[vector];
overflow = check_stack_overflow();
desc = irq_to_desc(irq);
if (unlikely(!desc)) {
printk(KERN_EMERG "%s: cannot handle IRQ %d vector %#x cpu %d\n",
__func__, irq, vector, smp_processor_id());
BUG();
}
if (!execute_on_irq_stack(overflow, desc, irq)) {
if (unlikely(overflow))
print_stack_overflow();
desc->handle_irq(irq, desc);
}
irq_exit();
set_irq_regs(old_regs);
return 1;
}
view plaincopy to clipboardprint?static inline int
execute_on_irq_stack(int overflow, struct irq_desc *desc, int irq)
{
union irq_ctx *curctx, *irqctx;
u32 *isp, arg1, arg2;
curctx = (union irq_ctx *) current_thread_info();
irqctx = hardirq_ctx[smp_processor_id()];
/*
* this is where we switch to the IRQ stack. However, if we are
* already using the IRQ stack (because we interrupted a hardirq
* handler) we can't do that and just have to keep using the
* current stack (which is the irq stack already after all)
*/
if (unlikely(curctx == irqctx))
return 0;
/* build the stack frame on the IRQ stack */
isp = (u32 *) ((char*)irqctx + sizeof(*irqctx));
irqctx->tinfo.task = curctx->tinfo.task;
irqctx->tinfo.previous_esp = current_stack_pointer;
/*
* Copy the softirq bits in preempt_count so that the
* softirq checks work in the hardirq context.
*/
irqctx->tinfo.preempt_count =
(irqctx->tinfo.preempt_count & ~SOFTIRQ_MASK) |
(curctx->tinfo.preempt_count & SOFTIRQ_MASK);
if (unlikely(overflow))
call_on_stack(print_stack_overflow, isp);
asm volatile("xchgl %%ebx,%%esp \n"
"call *%%edi \n"
"movl %%ebx,%%esp \n"
: "=a" (arg1), "=d" (arg2), "=b" (isp)
: "0" (irq), "1" (desc), "2" (isp),
"D" (desc->handle_irq)
: "memory", "cc", "ecx");
return 1;
}
最后,思考一下標(biāo)題中的問題:linux內(nèi)核在中斷路徑內(nèi)不能睡眠/調(diào)度的原因
Linux是以進(jìn)程為調(diào)度單位的,調(diào)度器只看到進(jìn)程內(nèi)核棧,而看不到中斷棧。在獨(dú)立中斷棧的模式下,如果linux內(nèi)核在中斷路徑內(nèi)發(fā)生了調(diào)度(從技術(shù)上講,睡眠和調(diào)度是一個(gè)意思),那么linux將無法找到“回家的路”,未執(zhí)行完的中斷處理代碼將再也無法獲得執(zhí)行機(jī)會(huì)。
先把中斷處理流程給出來
1.進(jìn)入中斷處理程序--->2.保存關(guān)鍵上下文---->3.開中斷(sti指 令)--->4.進(jìn)入中斷處理程序的handler--->5.關(guān)中斷(cli指令)---->6.寫EOI寄存器(表示中斷處理完 成)---->7.開中斷。
硬中斷:
對應(yīng)于上圖的1、2、3步驟,在這幾個(gè)步驟中,所有中斷是被屏蔽的,如果在這個(gè)時(shí)候睡眠了,操作系統(tǒng)不會(huì)收到任何中斷(包括時(shí)鐘中斷),系統(tǒng)就基本處于癱瘓狀態(tài)(例如調(diào)度器依賴的時(shí)鐘節(jié)拍沒有等等……)
軟中斷:
對應(yīng)上圖的4(當(dāng)然,準(zhǔn)確的說應(yīng)該是4步驟的后面一點(diǎn))。這個(gè)時(shí)候不能睡眠的關(guān)鍵是因?yàn)樯舷挛摹?div style="height:15px;">
大家知道操作系統(tǒng)以進(jìn)程調(diào)度為單位,進(jìn)程的運(yùn)行在進(jìn)程的上下文中,以進(jìn)程描述符作為管理的數(shù)據(jù)結(jié)構(gòu)。進(jìn)程可以睡眠的原因是操作系統(tǒng)可以切換不同進(jìn)程的上下文,進(jìn)行調(diào)度操作,這些操作都以進(jìn)程描述符為支持。
中斷運(yùn)行在中斷上下文,沒有一個(gè)所謂的中斷描述符來描述它,它不是操作系統(tǒng)調(diào)度的單位。一旦在中斷上下文中睡眠,首先無法切換上下文(因?yàn)闆]有中斷描述符,當(dāng)前上下文的狀態(tài)得不到保存),其次,沒有人來喚醒它,因?yàn)樗皇遣僮飨到y(tǒng)的調(diào)度單位。
如 果上述條件滿足了(也就是有中斷描述符,并成為調(diào)度器的調(diào)度單位,棧也不溢出了,理論上是可以做到中斷睡眠的),中斷是可以睡眠的,但會(huì)引起很多問題.例 如,你在時(shí)鐘中斷中睡眠了,那操作系統(tǒng)的時(shí)鐘就亂了,調(diào)度器也失去了依據(jù);例如,你在一個(gè)IPI(處理器間中斷)中,其它CPU都在死循環(huán)等你答復(fù),你確 睡眠了,那其它處理器也不工作了;例如,你在一個(gè)DMA中斷中睡眠了,上面的進(jìn)程還在同步的等待I/O的完成,性能就大大降低了……還可以舉出很多例子。 所以,中斷是一種緊急事務(wù),需要操作系統(tǒng)立即處理,不是不能做到睡眠,是它沒有理由睡眠。