linux内核的反复--一切都是过程
1. ZERO_PAGE
2.6.24內核中剔除了ZERO_PAGE這個雞肋,然而近期又準備添加近來,這樣做的原因還是在于當初它為何被當成了雞肋,當成雞肋的原因就是當zero頁面加入反向映射的時候會更新其page結構體的引用計數,從而造成了固定cacheline的沖刷,僅僅這一點就將zero頁面當成了雞肋未免也太殘酷了吧,所以2.6.32內核著手尋找更好的方法,為zero頁面平反,也就是盡力去除它作為雞肋的壞名聲。具體實現方式就是更改了vm_normal_page函數,也就是說不再將zero頁面當作normal頁面,而是將它當作非正常頁面,這樣的話,在根據虛擬地址得到的page的時候,一旦得知它是非正常頁面,比如zero頁面,就返回NULL,而vm_normal_page的調用者往往是根據其返回值進行進一步操作的,所以比如要進行更新引用計數之類的操作,一旦查出vm_normal_page返回NULL,那么更新操作就不再進行,因此也就避免了由于page結構體所在的內存被更新而導致的cacheline被沖刷,從而解決了zero頁面雞肋的問題,獲得的好處就是zero頁面的高效性,不再需要從內存管理器中進行分配和回收,效率是很高的。去除了zero頁面雞肋的名聲,那么它也就可以重返內核的mainline了,至于說zero頁面的另一個副作用,那就是會導致額外的缺頁,畢竟zero頁面是寫保護的,但是是否會因此導致額外的缺頁,那就看用戶空間程序了。
2. cpuidle-check
cpu在上面沒有任務運行的時候會執行cpu_idle,如果是以前的話直接調用halt就可以了,但是現在就不是那么簡單了,由于APM也好,ACPI也罷,都加入了節能機制,不能簡單地halt,還要可以做到諸如在不同的級別停掉cpu,因此內核當然需要一整套的機制來實現之,這就是cpuidle機制,框架就是將idle函數(halt的包裝)改成了回調函數,然后根據不同的“cpu現狀”采取不同的行為,這么做有什么內涵呢?實際上停掉cpu并不總是意味著節能,因為“停止-啟動”本身也是需要消耗能源的,特別是如果一個cpu僅僅停掉幾個毫秒,這樣是很不值得的,因為再次啟動它需要的能源足以抵消掉這幾個毫秒節省的能源,因此內核必須能夠監測到這種情況,但是畢竟內核不是智能的,要預先監測到一個cpu能被停掉多久是不現實的,雖然可以用hrtimer來驅動cpu,比方說將下一個timer到期的時刻定為cpu被啟動的時刻,可是多個cpu之間的交互和該cpu停掉之后的系統行為是完全不確定的,比如剛剛停止一個cpu的運行,瞬間一個用戶創建了大量的進程,結果就是各個cpu負載情況瞬間改變,可能已經和之前內核預測的大相徑庭,這就可能由于預測失誤從而導致cpu被瞬間啟動,在停止-啟動之間消耗掉了大量能源,短短的睡眠時間根本節省不了多少能源而彌補這些浪費,雖然發生上面的情況是可悲的,但是如果內核因此無動于衷的話,豈不更可悲,雖然內核不能智能的預測,但是人可以嗎?萬能的人也是不行的,人所有的預測都是基于經驗的,如果人將這種能力賦予操作系統的話,內河也還是可以根據經驗來預測的,也可以說成是所謂的“啟發式算法”具體來講就是根據以往cpu的負載情況來猜測出下面的負載,雖然不能保證絕對準確,但是由于世界事件的局部性原理,有這種預測總比沒有強,具體來講就是如果一個cpu的歷史一度負載很大,那么就盡量不要讓它進入深度相對較深的睡眠狀態(考慮到ACPI支持不同的睡眠深度),實際上的操作就是在idle回調函數中實現一個狀態機,不同的狀態代表不同的睡眠深度,狀態機的next狀態由cpu的歷史負載確定,歷史負載被劃分為不同的區間,落入某一個區間的負載指示一個確定的下一個狀態,這就是睡眠狀態機推進的原理。事實證明,內核在變得復雜,連idle函數都回調化了
3. child-runs-first
曾經,我發了一個內核patch,就是要確保在單核cpu上CFS調度器下實現承諾的子進程優先執行,當初想到的是為了避免額外的不必要的寫時復制,鑒于子進程一般都會瞬間調用exec函數系列,然而那已經是歷史了,考慮到這個避免cow的同時,沒有考慮到的是對cacheline的影響,問題是,讓本來就處于運行態的父進程繼續運行下去呢還是切換至新創建的子進程,如果切換的話,那么必然導致相關的cacheline被刷新,這也會影響效率,問題于是轉化為到底一次cow對系統效率影響大還是cacheline被刷新對系統效率影響大,內核開發者的回答是后者,畢竟現在很多都是多核處理器,在創建新的進程的時候根據負載均衡策略要有多處理器負載均衡的相關操作,這樣的話子進程就不一定和父進程在相同的處理器上運行,如果為了減少額外的cow硬要子進程優先運行的話,內核要做的工作就會很多很復雜,這樣做有點得不償失,事實上,子進程由于淺復制父進程的內存,如果在同一個處理器上運行的話會受益于cacheline,然而總會有更復雜的或者更重要的因素導致子進程被分配到別的處理器上,如此一來的話,就沒有必要必須實現child-runs-first這個承諾了。新的內核為了充分利用父進程的hot cacheline,于是干脆干掉了child-runs-first,當然也不是完全干掉了,只是將這個內核選項默認設置成了false,如果你要將它再搬回去,也是可以的。
4. child不再繼承父進程的實時優先級
這個論題是從一篇類似戰斗檄文的文章中引出來的,文章的標題是《RealtimeKit and the audio problem》
這篇文章在抱怨為何linux沒有更好地支持實時的音頻,為了得到更好的音頻質量不得不采取的行為是耦合其它的內核模塊,比如利用LSM來支持更好的音頻播放,但是這樣的話就會引入不安全因素,當然可以使用實時優先級,比如RR和FIFO,可是如果一個進程可以隨時簡單的得到實時優先級的話,那么惡意的進程也應該可以做到,為了支持高質量音頻播放而給與它實時優先級,但是如果一個進程偽裝成音頻播放進程的話,那么它瞬間就可以down掉系統,如此一來,文章中充滿激情的問道,難道內核開發人員就不能用一種更和諧的方式應對音頻播放的實時性了嗎?其實音頻播放僅僅是一個例子,很多別的需求也需要響應的實時性,另一方面安全是最重要的,它又不能被惡意利用,因此必然要采取一些措施,眾所周知,UNIX系列系統的所有進程是一個樹狀模型,init是樹根,其它所有的進程都是init的子孫,當然也繼承了init的一些特性,優先級就是其中之一,子進程或多或少的繼承父進程的優先級,如果一個惡意的進程得到了實時優先級,那么它的子進程就會得到實時優先級,如果該惡意進程拼命fork子進程的話,一個DOS***就完成了,這當然是需要避免的,于是新的內核補丁中就有人提出增加一個進程標識,就是reset標識,一個進程一旦有了這個標識,不管它是什么優先級執行什么調度策略,它所fork出來的子進程的優先級一律回歸為平均優先級,調度策略一律不能是實時調度,這就避免了惡意進程使用fork×××。這個標識的加入并沒有切斷父子的關系,相反的,聯系更加多了,父親不再擁有將一切給與兒子的權力,兒子必要的時候必須為其父親不被信任而負責,另一些時候,父親必須支付一些遺產稅。引入的這個機制避免了一些漏洞被利用,我認為這個機制很有必要,這樣可以保證一些實時性要求高的應用可以放心使用RT調度策略而不再為安全問題擔心。
5.雜
首先看看cfs調度器搶占粒度的微調對系統的影響(cfs沒有時間片概念,虛擬時鐘的時間片是隨著系統中進程數量的變化動態調整的,而不像以前O(n)或者O(1)那樣是靜態不變的),僅舉一例,如果你只運行很少幾個服務器,那么可以增大這個粒度,這樣一輪調度周期就會很長,進程切換相對不頻繁,開銷小,但是如果你運行桌面和多媒體應用,那就要調小這個粒度,進程切換頻繁,交互性增強,同時開銷也變大;再看一下用戶空間驅動,其實并不像很多人想象的那樣,用戶空間驅動沒有內核空間的效率高,事實是,用戶空間的程序唯一的劣勢就是系統調用開銷比較大,至于別的和內核是一樣的,要知道linux是完全按照進程來調度的,當然也會有中斷這樣的不速之客。用戶空間驅動為了效率需要做兩點:第一就是應用實時優先級;第二就是用mlock將內存鎖入存儲器,消掉頁面置換的開銷。
轉載于:https://blog.51cto.com/dog250/1273336
總結
以上是生活随笔為你收集整理的linux内核的反复--一切都是过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 周六早上读核感悟
- 下一篇: Visual Studio DSL 入门