Linux 2.6.23以前廣泛採用了O(1)調度器,它是一種基於優先級的時間片調度算法,所謂的O(1)只是它的一些精巧的數據結構使然,在不考慮動態補償/懲 罰的狀況下,只要優先級肯定,那麼時間片就是固定的。2.6.23之後的CFS呢,它是一種基於權重的非時間片調度算法,進程每次執行的時間並非固定 的,而是根據進程數在一個準固定週期內按照其權重比例的時間,依然以時間片爲術語,CFS下,進程每次運行的時間與進程的總量有關。
即使在不考慮動態補償/懲罰的前提下,O(1)依然面臨雙斜率問題,爲了解釋這個問題,我先給出進程優先級公式:
prio=MAX_RT_PRIO+nice+20
其中,MAX_RT_PRIO爲100,nice爲-20到19閉區間內的任意整數。接下來時間片的計算體現了雙斜率:
若是prio小於120:time_slice=20*(140-prio)
若是prio大於等於120:time_slice=5*(140-prio)
可 見,只要prio肯定了,每一個進程的時間片也就肯定了,以120爲分界,高優先級與低優先級的時間片計算是不一樣的,之因此這樣是爲了:既要體現高優先級的 優點,又不過於削弱低優先級。經過O(1)的邏輯,咱們能夠算出,全部進程必須完成一輪的調度,即每個進程必須有機會運行一次,所以「一輪調度」的時間 隨着進程數量的增長是增長了的。
咱們如今看看CFS是怎麼逆轉這個結局的。CFS調度很是簡單,沒有太多的計算公式。依然不考慮動態補償/懲罰,CFS徹底按照權重,Linux內核將 40個優先級映射了40個權重,爲了簡化討論,我假設權重分別爲1,1*1.2,1*1.2*1.2,1*1.2*1.2*1.2,....以1.2倍等 比例增長,而後定義一個固定的調度週期或以任意一段時間slice內,一個進程運行的時間就是slice*(進程權重/權重和),可見,若是進程數量增 加,全部的進程集體平滑變慢,意思是每次運行的時間減小(時間片再也不固定),所謂的「徹底公平」意味着權值大的進程其虛擬時鐘步進比較慢,權值小的進程其 虛擬時鐘步進比較快,CFS在每個調度點(好比時鐘tick,wake up,fork等)選擇虛擬時鐘最小的進程運行,這是相對於O(1)來說更加平滑的一種方式,所以體現了一種延遲公平,至於吞吐,仍是按照權重來的,而權 重映射到了優先級。而O(1)更多的是吞吐公平。
總結來說,就是O(1)爲每一個進程計算固定的時間片,而CFS則是在相同的時間段內計算每一個進程運行的時間比例,可見兩者基點不一樣,甚至是徹底相反的。
如今,咱們給出評價。CFS更加平滑,很是適合交互式進程,由於交互進程是飢餓敏感的,可是它們不常常佔有CPU,然而一旦須要CPU,必須立刻讓其予取 予求。對於有高吞吐需求的服務進程,CFS並不適合,這種進程的需求是一旦佔據CPU,則儘量讓其運行久一些,固定時間片的O(1)更加適合。按照慣常 的分類法,I/O密集型的進程多屬於交互(可能還有存儲類)的,這種進程由I/O驅動,應該知足其任什麼時候刻的CPU需求,由於它們不會佔據過久,然而對於 CPU密集型進程,獲得CPU的機會應該比I/O密集應用少,由於它們一旦得到CPU,就要長期佔據。總的來說,對於桌面客戶端,CFS更適合,對於服務 器,O(1)更加適合。
本文沒有談及另外兩種調度器,也就是Windows調度器以及Linux BFS調度器,前者基於動態優先級提高/恢復,適合桌面應用,後者基於優先級分類O(n)算法,不考慮衆核和NUMA擴展,更適合移動終端。
算法
所 有進程的虛擬地址空間共享一個限量的物理內存,勢必須要按需調頁,這種作法之因此可行是由於每個時間點,CPU們只須要少許的物理頁面得到映射。如今的 問題是,考慮若是出現缺頁-頁表項中的「存在位」爲0,從哪裏得到新的page。答案很簡單,固然是從代價最小的地方獲取page。
咱們此時必須考慮缺頁時所需page的類型,大體能夠分爲3類:
1).徹底的地址缺頁,即該地址曾經沒有映射過物理頁面。
2).該地址曾經映射過頁面,可是被換出到交換空間了。
3).該地址曾經映射的page屬於一個文件系統的文件,可是已經解除了映射。
針對以上3種狀況,所謂的「代價最小」擁有不一樣的策略。
首先看1),這個很簡單,直接從夥伴系統分配page便可,固然分配單獨一個page所付出的代價至關小,由於夥伴系統之上有一個per cpu的page pool,這個pool的分配不須要任何lock。如今咱們看看這個代價小是否足夠小,看來是的,可是並不絕對。對於讀操做來說,假設以前有一個page 映射於該缺頁虛擬地址,後來解除了映射,咱們知道此時該page的部分數據已經cache到了CPU cache line中,當再次須要讀該page可是缺頁時,咱們但願得到原先的那個page,願景是好的,但是咱們怎麼追蹤這個page呢?追蹤這個page和缺頁 進程的關係的代價是否抵消保持cache熱度的收益呢?事實上,這很難,由於你要考慮到共享內存的狀況,這是一個多對一的雙向關係,也就是一個多對多的關 系。然而Linux的內存子系統並無什麼都不作,而是它基於一種機率行爲將釋放到per cpu的page pool的行爲分爲了cold release和hot release,hot release將page添加到pool的隊頭,反之到隊尾,而per cpu page pool的分配行爲是隊頭分配,若是足夠幸運,也許進程能夠得到剛剛被解除映射的那個作過讀操做的page。內核是怎麼保證一個進程是足夠幸運的呢?這個 很形而上但卻也實用,內核採用了一個準LRU算法防止了page在進程之間顛簸,局部性保證了在進程內部一個page被訪問後的一段時間內再被訪問的概率 很大。
再看2),內核裏面運行着一個page回收交換的守護內核線程,發現一個不常被訪問要被回收的page是髒page時,內核線程並非直接啓動IO將其寫 入交換空間,而是暫時先將其排入一個swap cache,也就是給了一個page一次不須要IO而被再次使用的機會,作這樣的策略其背後仍是局部性原理。當缺頁發生時,首先會在swap cache裏面尋找,若是找到就不須要進行IO了。作這個策略的現實意義是巨大的,在分級存儲原理咱們能夠知道,內存訪問和磁盤IO的時間差了幾個數量 級,因此不到必需要作,是不會刷swap cache到swap分區的。
最後咱們看3),和2)相似,可是這個涉及到了filesystem的文件page cache,由radix樹組織,這個樹和page回收是無關的,所謂刷掉一個屬於文件的page指的是僅僅將該page解除頁表項映射,實際上它徹底可 能還在文件的radix樹中,在發生缺頁的時候,若是在radix樹中找到了該page,那麼只需創建一個映射便可,無需再進行磁盤IO。
綜上,咱們能夠知道,只要不進行磁盤IO就儘可能不要,只要不進行磁盤IO的缺頁處理就是Minor,進行了IO的則是Major,一個名稱而已。現在的內核將Minor進行了細分,可是這並非重點,所以統一稱爲Other。
在此不得不提的是LRU算法,通常而言,幾乎全部的操做系統都採用了準LRU而不是標準的LRU,這是由於標準LRU只是理論上的,實際實現起來不現實, 並非說硬件消耗巨大,更可能是由於「它的效果並不比準LRU好甚至更糟糕」,標準的LRU是一個棧式管理系統,空間局部性誠然重要,然而考慮到循環的話, 在循環邊界將會面對空間局部性的對立極端,這就是列維長跳!!列維短跳是符合空間局部性的,可是列維長跳是空間局部性的對立。順便說一句,整我的類社會的 任何行爲都符合列維長跳原則,若是把量變看作列維短跳,那麼質變就是列維長跳,這是根本原則,馬克思說過的。
Linux內核採用雙時鐘二次機會算法模擬了LRU算法,效果很是好。
數據結構
Linux內核採用vma來表示進程地址空間中的一段,至於這一段映射了什麼vma本身管理,對上層只是提供地址空間的一段連續的虛擬內存。
通常而言,一個文件的一部分對應一個vma,若是須要映射一個文件的不一樣部分,就須要不一樣的vma,若是足夠幸運,這幾個vma能夠牢牢挨在一塊兒,可是在 兩次映射之間,一些別的映射佔據了hole,那麼就很差玩了,所以須要一種針對文件「從新佈局」的方式,下面的圖示展現了這個想法:
ide
但 是僅僅針對文件作這個解釋不免有點不盡興。操做系統中有一個工做集的概念,這個概念也是依託局部性原理。工做集就是將不一樣的內容映射到一個固定的虛擬地址 空間窗口,若是CPU的cache line是依據虛擬地址尋址的,那麼時間空間局部性將會發揮很大的做用,在這個過程當中,TLB也會發揮做用。基於虛擬地址的工做集是虛擬地址空間和物理內 存之間的真正隔離。
本質上來說,非線性映射並不必定要針對文件,它要作的就是「將不一樣的內容映射到相同的虛擬地址區段」。
佈局