天天看點

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将記憶體鎖入存儲器,消掉頁面置換的開銷。

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1273336

繼續閱讀