中斷上下文不能做的事情?


美團面試的時候,突然面試官有問到我為什么中斷上下文不能睡眠,當時不了解,后來上網搜搜,發現各種說法:

進程上下文:

  通過系統調用,用戶空間的應用程序就會進入內核空間,由內核代表該進程運行於內核空間,這就涉及到上下文的切換,用戶空間和內核空間具有不同的地址映射,通用或專用的寄存器組,而用戶空間的進程要傳遞很多變量、參數給內核,內核也要保存用戶進程的一些寄存器、變量等,以便系統調用結束后回到用戶空間繼續執行,所謂的進程上下文,就是一個進程在執行的時候,CPU的所有寄存器中的值、進程的狀態以及堆棧中的內容,當內核需要切換到另一個進程時,它需要保存當前進程的所有狀態,即保存當前進程的進程上下文,以便再次執行該進程時,能夠恢復切換時的狀態,繼續執行。

中斷上下文:

  硬件通過觸發信號,導致內核調用中斷處理程序,進入內核空間。這個過程中,硬件的一些變量和參數也要傳遞給內核,內核通過這些參數進行中斷處理,中斷上下文就可以理解為硬件傳遞過來的這些參數和內核需要保存的一些環境,主要是被中斷的進程的環境。

Linux內核工作在進程上下文或者中斷上下文。提供系統調用服務的內核代碼代表發起系統調用的應用程序運行在進程上下文;另一方面,中斷處理程序,異步運行在中斷上下文。中斷上下文和特定進程無關

運行在進程上下文的內核代碼是可以被搶占的(Linux2.6支持搶占)。但是一個中斷上下文,通常都會始終占有CPU(當然中斷可以嵌套,但我們一般不這樣做),不可以被打斷。正因為如此,運行在中斷上下文的代碼就要受一些限制,不能做下面的事情:

1、睡眠或者放棄CPU。

      這樣做的后果是災難性的,因為內核在進入中斷之前會關閉進程調度,一旦睡眠或者放棄CPU,這時內核無法調度別的進程來執行,系統就會死掉

2、嘗試獲得信號量

      如果獲得不到信號量,代碼就會睡眠,會產生和上面相同的情況

3、執行耗時的任務

      中斷處理應該盡可能快,因為內核要響應大量服務和請求,中斷上下文占用CPU時間太長會嚴重影響系統功能。

4、訪問用戶空間的虛擬地址

      因為中斷上下文是和特定進程無關的,它是內核代表硬件運行在內核空間,所以在終端上下文無法訪問用戶空間的虛擬地址

 

  信號量/互斥量/互斥鎖允許資源申請者進入睡眠,自旋鎖不允許調用者睡眠,而是讓其循環等待;所以信號量/互斥量/互斥鎖不允許使用在中斷上下文中,由於在睡眠或者放棄CPU后,內核無法進行進程調度,所以不能調度其他的進程來執行,系統會死掉。

 

這個問題有很多人問過,我看了下Linux得內核代碼,原因如下:(當然我不能保證一定對,如果有牛人理解得更好,歡迎指正)

1、 中斷處理的時候,不應該發生進程切換,因為在中斷context中,唯一能打斷當前中斷handler的只有更高優先級的中斷,它不會被進程打斷,如果在 中斷context中休眠,則沒有辦法喚醒它,因為所有的wake_up_xxx都是針對某個進程而言的,而在中斷context中,沒有進程的概念,沒 有一個task_struct(這點對於softirq和tasklet一樣),因此真的休眠了,比如調用了會導致block的例程,內核幾乎肯定會死。

2、schedule()在切換進程時,保存當前的進程上下文(CPU寄存器的值、進程的狀態以及堆棧中的內容),以便以后恢復此進程運行。中斷發生后,內核會先保存當前被中斷的進程上下文(在調用中斷處理程序后恢復);

但在中斷處理程序里,CPU寄存器的值肯定已經變化了吧(最重要的程序計數器PC、堆棧SP等),如果此時因為睡眠或阻塞操作調用了schedule(),則保存的進程上下文就不是當前的進程context了.所以不可以在中斷處理程序中調用schedule()。

3、2.4內核中schedule()函數本身在進來的時候判斷是否處於中斷上下文:

if(unlikely(in_interrupt()))

BUG();

因此,強行調用schedule()的結果就是內核BUG,但我看2.6.18的內核schedule()的實現卻沒有這句,改掉了。

4、中斷handler會使用被中斷的進程內核堆棧,但不會對它有任何影響,因為handler使用完后會完全清除它使用的那部分堆棧,恢復被中斷前的原貌。

5、處於中斷context時候,內核是不可搶占的。因此,如果休眠,則內核一定掛起。

-------------------------------------------------------

 

原帖地址:http://bbs.chinaunix.net/thread-2115820-1-1.html

 

這里引用個人認為比較OK的解析:

 

呵呵,我最喜歡這種討論了。先來獻丑了,說說我的看法。
先把中斷處理流程給出來

 

  1. 1.進入中斷處理程序--->2.保存關鍵上下文---->3.開中斷(sti指令)--->4.進入中斷處理程序的 handler--->5.關中斷(cli指令)---->6.寫EOI寄存器(表示中斷處理完成)---->7.開中斷。
復制代碼


硬中斷:
對應於上圖的1、2、3步驟,在這幾個步驟中,所有中斷是被屏蔽的,如果在這個時候睡眠了,操作系統不會收到任何中斷(包括時鍾中斷),系統就基本處於癱瘓狀態(例如調度器依賴的時鍾節拍沒有等等……)

軟中斷:
對應上圖的4(當然,准確的說應該是4步驟的后面一點,先把話說保險點,免得思一克又開始較真 )。這個時候不能睡眠的關鍵是因為上下文。
大家知道操作系統以進程調度為單位,進程的運行在進程的上下文中,以進程描述符作為管理的數據結構。進程可以睡眠的原因是操作系統可以切換不同進程的上下文,進行調度操作,這些操作都以進程描述符為支持。
中斷運行在中斷上下文,沒有一個所謂的中斷描述符來描述它,它不是操作系統調度的單位。一旦在中斷上下文中睡眠,首先無法切換上下文(因為沒有中斷描述符,當前上下文的狀態得不到保存),其次,沒有人來喚醒它,因為它不是操作系統的調度單位。
此外,中斷的發生是非常非常頻繁的,在一個中斷睡眠期間,其它中斷發生並睡眠了,那很容易就造成中斷棧溢出導致系統崩潰。

如 果上述條件滿足了(也就是有中斷描述符,並成為調度器的調度單位,棧也不溢出了,理論上是可以做到中斷睡眠的),中斷是可以睡眠的,但會引起很多問題.例 如,你在時鍾中斷中睡眠了,那操作系統的時鍾就亂了,調度器也了失去依據;例如,你在一個IPI(處理器間中斷)中,其它CPU都在死循環等你答復,你確 睡眠了,那其它處理器也不工作了;例如,你在一個DMA中斷中睡眠了,上面的進程還在同步的等待I/O的完成,性能就大大降低了……還可以舉出很多例子。 所以,中斷是一種緊急事務,需要操作系統立即處理,不是不能做到睡眠,是它沒有理由睡眠。

======================================================

 

另一篇:

 

http://blog.openrays.org/blog.php?do=showone&tid=455

 

其結論:

 

5. 中斷處理時可否睡眠問題

Linux 設計中,中斷處理時不能睡眠,這個內核中有很多保護措施,一旦檢測到內核會異常。

當 一個進程A因為中斷被打斷時,中斷處理程序會使用 A 的內核棧來保存上下文,因為是“搶”的 A 的CPU,而且用了 A 的內核棧,因此中斷應該盡可能快的結束。如果 do_IRQ 時又被時鍾中斷打斷,則繼續在 A 的內核棧上保存中斷上下文,如果發生調度,則 schedule 進 switch_to,又會在 A 的 task_struct->thread_struct 里保存此時時種中斷的上下文。

假如其是在睡眠時被時鍾中斷打斷,並 schedule 的話,假如選中了進程 A,並 switch_to 過去,時鍾中斷返回后則又是位於原中斷睡眠時的狀態,拋開其擾亂了與其無關的進程A的運行不說,這里的問題就是:該如何喚醒之呢??

另外,和該中斷共享中斷號的中斷也會受到影響。

 

======================================================

 

再一篇,也分析的很到位:

 

http://blog.csdn.net/maray/article/details/5770889

 

其結論:

Linux是以進程為調度單位的,調度器只看到進程內核棧,而看不到中斷棧。在獨立中斷棧的模式下,如果linux內核在中斷路徑內發生了調度(從技術上講,睡眠和調度是一個意思),那么linux將無法找到“回家的路”,未執行完的中斷處理代碼將再也無法獲得執行機會。

————————————————————————————————————————————————————————————————

為什么軟中斷中也不能睡眠

 

    這個問題實際上是一個老生常談的問題,答案也很簡單,Linux在軟中斷上下文中是不能睡眠的,原因在於Linux的軟中斷實現上下文有可能是中斷上下文,如果在中斷上下文中睡眠,那么會導致Linux無法調度,直接的反應是系統Kernel Panic,並且提示dequeue_task出錯。所以,在軟中斷上下文中,我們不能使用信號量等可能導致睡眠的函數,這一點在編寫IO回調函數時需要特別注意。在最近的一個項目中,我們在dm-io的callback函數中去持有semaphore訪問競爭資源,導致了系統的kernel panic。其原因就在於dm-io的回調函數在scsi soft irq中執行,scsi soft irq是一個軟中斷,其會在硬中斷發生之后被執行,執行上下文為中斷上下文。

 

       中斷上下文中無法睡眠的原因大家一定很清楚,原因在於中斷上下文不是一個進程上下文,其沒有一個專門用來描述CPU寄存器等信息的數據結構,所以無法被調度器調度。如果將中斷上下文也設計成進程上下文,那么調度器就可以對其進行調度,如果在開中斷的情況下,其自然就可以睡眠了。但是,如果這樣設計,那么中斷處理的效率將會降低。中斷(硬中斷、軟中斷)處理都是些耗時不是很長,對實時性要求很高,執行頻度較高的應用,所以,如果采用一個專門的后台daemon對其處理,顯然並不合適。

 

       Linux對中斷進行了有效的管理,一個中斷發生之后,都會通過相應的中斷向量表獲取該中斷的處理函數。在Linux操作系統中都會調用do_IRQ這個函數,在這個函數中都會執行__do_IRQ(),__do_IRQ函數調用該中斷的具體執行函數。在執行過程中,該函數通過中斷號找到具體的中斷描述結構irq_desc,該結構對某一具體硬件中斷進行了描述。在irq_desc結構中存在一條鏈表irqaction,這條鏈表中的某一項成員都是一個中斷處理方法。這條鏈表很有意思,其實現了中斷共享,例如傳統的PCI總線就是采用共享中斷的方法,該鏈表中的一個節點就對應了一個PCI設備的中斷處理方法。在PCI設備驅動加載時,都需要注冊本設備的中斷處理函數,通常會調用request_irq這個函數,通過這個函數會構造一個具體的irq action,然后掛接到某個具體irq_desc的action鏈表下,實現中斷處理方法的注冊。在__do_IRQ函數中會通過handle_IRQ_event()函數遍歷所有的action節點,完成中斷處理過程。到目前為止,中斷處理函數do_IRQ完成的都是上半部的工作,也就是設備注冊的中斷服務程序。在中斷上半部中,通常都是關中斷的,基本都是完成很簡單的操作,否則將會導致中斷的丟失。耗時時間相對較長,對實時性要求不是最高的應用都會被延遲處理,都會在中斷下半部中執行。所以,在中斷上半部中都會觸發軟中斷事件,然后執行完畢,退出服務。

 

       __do_IRQ完成之后,返回到do_IRQ函數,在該函數中調用了一個非常重要的函數irq_exit(),在該函數中調用invoke_softirq(),invoke_softirq調用do_softirq()函數,執行軟中斷的操作。此時,程序的執行環境還是中斷上下文,但是與中斷上半部不同的是,軟中斷執行過程中是開中斷的,能夠被硬中斷而中斷。所以,如果用戶的程序在軟中斷中睡眠,操作系統該如何調度呢?只有kernel panic了。另外,軟中斷除了上述執行點之外,還有其他的執行點,在內核中還有一個軟中斷的daemon處理軟中斷事務,驅動程序也可以自己觸發一個軟中斷事件,並且在軟中斷的daemon上下文中執行。但是硬中斷觸發的事件都不會在這個daemon的上下文中執行,除非修改Linux中的do__IRQ代碼。

 

       上述對軟中斷的執行做了簡要分析,我對Linux中的硬中斷管理機制做了一些代碼分析,這一塊代碼量不是很大,可移植性非常的好~~建議大家閱讀,對我上述的分析和理解存在什么不同意見,歡迎大家討論。

 

轉載:http://blog.csdn.net/mihouge/article/details/44198375

http://blog.csdn.net/maray/article/details/5770889


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM