前幾天被人問到了“中斷里為什么不能睡眠”這個問題,之前我並沒有深究過這個問題。后來查了一下資料,基本上所有的論壇和博客都說:因為中斷沒有上下文,如果睡眠被切換出去之后就無法再切回來。我實在是不能認同這種說法,中斷確實沒有自己的上下文,但是中斷發生的時候它使用的是被中斷進程的上下文,如果真的讓中斷睡眠的話在switch_to函數里會把當前中斷的cs:eip保存到被中斷進程的thread里,所以在一些特殊情況下中斷是能切回來的。
我比較認同http://bbs.chinaunix.net/thread-2115820-2-1.html這里19樓的說法。把它貼出來:
我們都是從理論講下面這些問題, 因為linux在很多地方做了保護, 所以直接sleep或者schedule()會導致內核異常.
首先分清楚, 我們討論的是不能sleep, 而不是不能preempt.
1. 毫無疑問, 在關中斷的時候不能sleep, 這點大家都知道, 因為時鍾中斷無法觸發. 但不是所有情況下, 在關中斷時sleep都會導致系統死掉, 在SMP的情況下, 可能系統不會死掉.
2. 中斷的handler能否sleep?
這其實和"中斷沒有自己的上下文"無關. CPU沒有關中斷, 中斷有自己的上下文, 中斷的上下文就是搶占的任務A的上下文.
和棧溢出也沒有關系, 現在的中斷都是可以嵌套的, 如果中斷sleep只會讓后面的中斷搶占其他任務, 根本不存在棧溢出問題, 不過現在內核的4K中斷單獨棧會有問題. 這會導致棧被破壞.
假設中斷sleep了, 在調度的時候, 內核將中斷的CS:eip和SS:esp保存在被搶占任務A的thread_info中, 當任務A被重新喚醒的時候, 任務A從中斷的CS:eip開始執行, 這也能正常執行下去, 中斷執行完后, 從ret_from_intr中返回. 可以恢復任務A的搶占前的場景.
Linux內核要實現成這樣, 必須解決下面問題:
中斷sleep會增加普通任務的不確定性, 普通任務執行的時間, 實時性都得不到保障.
和中斷共享中斷號的中斷會受到影響, 現在的內核設置了INPROGRESS標志.
中斷因為借用了被搶占任務的上下文, 所以中斷的處理受到任務上下文屬性的限制.
等等很多其他問題, 總之, 中斷sleep會導致被搶占任務的不確定性, 並可能導致其他中斷受影響.
總結:
異步異常(中斷)handler不是沒有上下文, 而是沒有固定的上下文, 如果使用被搶占的任務作為上下文, 一,自身的處理無法得到實時保障,導致系統不確定性, 二,任務受到影響.
如何解決:
給中斷handler提供固定的內核線程上下文!!
這樣, 中斷不能sleep, 但中斷的handler可以sleep!
為每個中斷號創建一個內核任務, 中斷入口函數do_irq只是喚醒相應的中斷任務, 中斷任務去執行相應的handler.
好處:
提高了系統的實時性. 后面可以詳細講.
壞處:
降低了中斷, 軟中斷的實時性, 所以不是所有的中斷handler都可以在固定內核任務上下文中處理. 一般來說, 時鍾中斷必須保證其實時性, 所以留在中斷上下文中.
- 介紹: Linux系統的實時性瓶頸在哪里??
- 一個實時性系統, 必須保證: 系統中優先級高的任務, 被喚醒后, 在很小的可控的延時內, CS:eip指令得到執行.
- 一個好的實時性系統, 必須保證: 系統中的所有等待運行的任務, 可以在一個固定的可接受的延時內, CS:eip指令得到執行.
- 這些延時包括 中斷響應延時, 中斷處理延時, 調度響應和調度處理延時.
試想現有的系統, 一個任務可能在以下上下文中被喚醒:
1. 中斷上下文, 如dma數據傳輸完成等等設備驅動.
中斷上下文喚醒任務后, 任務被加入到running隊列, 返回, 繼續執行中斷, 中斷執行完成后, 執行軟中斷, 中間可能會出現中斷嵌套. 直到最后ret_from_intr, 才會判斷是否需要搶占當前任務. 然后調用schedule(). 從隊列被加入running隊列到schedule()函數真正開始執行, 這段時間是中斷處理延時+調度響應延時.
如何縮短這部分延時和我們前面討論的東西很有關.
正是因為中斷,軟中斷不能preempt, 不能sleep, 導致了系統的實時性變差. 而在這些時間中, 中斷handler和軟中斷handler消耗絕大部分時間.
設想一下, 如果中斷handler和軟中斷handler放在專門的內核任務中執行, 中斷handler中喚醒任務A, wakeup中通過優先級判斷handler任務是否需要被任務A搶占, 如果需要, 設置handler任務的NEED_RESCHEDULE標志, 調用resched_task()可以馬上進入schedule(), 其中的延時非常小, 而且非常穩定. 當然, 可能wakeup后, 立即有中斷到來, 但因為中斷執行路徑變得非常短, 只是喚醒響應的handler任務, 可能只需要100行以內的代碼.所以這些時間可以忽略不計. 這種做法可以極大提高系統的實時性.
2. 軟中斷上下文: 如定時器到期, 目的地是本地的報文送到socket層. 等等
軟中斷的情況和中斷類似, 可以通過將軟中斷線程化, 提高系統實時性.
3. 普通任務上下文, 如任務間通信等等.
普通任務一旦在內核中執行了spin_lock(), 其他任務無法搶占這個CPU, 即使另外優先級高的任務不和它共享資源, 也無法及時得到調度. 當CPU變多, 內核代碼變大的時候, 這個問題也變得非常突出, 所以spin_lock()也是一個影響系統實時性的設計. 如果將spin_lock變成可以搶占的鎖, 會是怎樣? 如果spin_lock可以搶占, 一旦任務B搶占了任務A, 而任務A執行了spin_lock(xxx), 恰好任務B也執行spin_lock(xxx), 必然導致內核死鎖. 如果spin_lock()可以搶占, 且可以sleep(), 結果又會怎樣. 任務B執行spin_lock(xxx)必然導致自己sleep, 這時任務A可以接着執行spin_unlock(xxx),喚醒任務B. 這時又有一個優先級反轉問題需要解決, 假設任務B的優先級最高, 任務A最低, 任務C中等, B因為拿不到xxx鎖而sleep, 重新調度, 結果調度到任務C執行, 任務B因此喪失了優先級優勢, 這種情況在嵌入式系統中可能會導致很嚴重的問題. 所以任務A必須繼承任務B的優先級, 重新調度的時候才能調度到任務A先運行.
--------------------------------------------------------------------------------------
上面這些系統問題的改正可以提高系統的實時性, 但整個內核的編程模型會變得更復雜. 這些東西就是Ingo Molnar在2005年實現的Realtime Patch中的核心思想, 但在2006年的Kernel Summit中, 引發了下面的討論:
http://lwn.net/Articles/191782/
注意下面這段話.
The question was asked: why bother with sleeping locks? Making locks preemptible is seen by some as a way of papering over the real problem: long lock hold times. Why not simply fix those? The answer comes in a couple of parts:
* Extensive efforts have been expended toward fixing lock problems for many years, and those efforts will continue into the future. The use of sleeping locks is not being used as an excuse to avoid fixing code which holds locks for too long.
* Ensuring realtime response in the absence of preemptible locks requires auditing the entire body of kernel source - all eight million lines or so. That's a big job, and one which is hard to keep up with in an environment where nearly ten thousand lines of code are being changed every day. Sleeping locks reduce the audit requirements to a couple thousand lines - a much more tractable problem. For those who need realtime response, guaranteed, that is a good deal.
因為眾多的爭議, 中斷和軟中斷的線程化和spin_lock的可sleep化沒有合入主流內核中.
如果realtime patch合並到主流內核中, 可以滿足: 系統中優先級高的任務, 被喚醒后, 在很小的可控的延時內, CS:eip指令得到執行.
但不能保證低優先級的時延, 這正是CFS要解決的問題, 設想一下, 要讓系統中的任何優先級的任務在一定的時間內得到執行, 必然要求等待時間長的任務優先得到執行, CFS就是用等待時間來作為調度的依據, 而優先級居次. 有時間在討論CFS的實現.