如今的 Linux 內核和 Linux 2.6 的內核有多大區別?

這個問題挺大的。php

2.6 時代跨度很是大,從2.6.0 (2003年12月發佈[36]) 到 2.6.39(2011年5月發佈), 跨越了 40 個大版本。
3.0(原計劃的 2.6.40, 2011年7月發佈) 到 3.19(2015年2月發佈)。
4.0(2015年4月發佈)到4.2(2015年8月底發佈)。html

總 的來講,從進入2.6以後,每一個大版本跨度開發時間大概是 2 - 3 個月。2.6.x , 3.x, 4.x,數字的遞進並無很是根本性,很是很是很是引人注目的大變化,但每一個大版本中都有一些或大或小的功能改變。主版本號只是一個數字而已。不過要直接 從 2.6.x 升級 到 3.x, 乃至 4.x,隨着時間間隔增大,出問題的機率固然大不少。前端

我的以爲 Linux 真正走入嚴肅級別的高穩定性,高可用性,高可伸縮性的工業級別內核大概是在 2003 年後吧。一是隨着互聯網的更迅速普及,更多的人使用、參與開發。二也是社區通過11年發展,已經慢慢摸索出一套很穩定的協同開發模式,一個重要的特色是 社區開始使用版本管理工具進入管理,脫離了以前純粹手工(或一些輔助的簡陋工具)處理代碼郵件的方式,大大加快了開發的速度和力度。node

所以,我彙總分析一下從 2.6.12 (2005年6月發佈,也就是社區開始使用 git 進行管理後的第一個大版本),到 4.2 (2015年8月發佈)這中間共 51個大版本,時間跨度10年的主要大模塊的一些重要的變革。linux

linux內核版本差別

(感謝知友 @costa 提供無水印題圖 ;-) )ios

預計內容目錄:git

  • 調度子系統(scheduling) [已完成]
  • 內存管理子系統(memory management) [填坑中]
  • 時間子系統(timer)
  • 中斷子系統(interrupt)
  • 同步機制子系統(synchronization)
  • 塊層(block layer)
  • 文件子系統(Linux 通用文件系統層VFS, various fs)
  • 網絡子系統(networking)
  • 調試和追蹤子系統(debugging, tracing)
  • 虛擬化子系統(kvm)
  • 控制組(cgroup)

============== 海量正文分割線 ==============程序員

  • 調度子系統(scheduling)

概述:Linux 是一個遵循 POSIX 標準的類 Unix 操做系統(然而它並非 Unix 系統[1]),POSIX 1003.1b 定義了調度相關的一個功能集合和 API 接口[2]。調度器的任務是分配 CPU 運算資源,並以協調效率和公平爲目的。效率可 從兩方面考慮: 1) 吞吐量(throughput) 2)延時(latency)。不作精肯定義,這兩個有相互矛盾的衡量標準主要體現爲兩大類進程:一是 CPU 密集型,少許 IO 操做,少許或無與用戶交互操做的任務(強調吞吐量,對延時不敏感,如高性能計算任務 HPC), 另外一則是 IO 密集型, 大量與用戶交互操做的任務(強調低延時,對吞吐量無要求,如桌面程序)。公平在於有區分度的公平,多媒體任務和數值計算任務對延時和限定性的完成時間的敏感度顯然是不一樣的。
爲此, POSIX 規定了操做系統必須實現如下調度策略(scheduling policies), 以針對上述任務進行區分調度:
- SCHED_FIFO
- SCHED_RR
這兩個調度策略定義了對實時任務,即對延時和限定性的完成時間的高敏感度的任務。前者提
供 FIFO 語義,相同優先級的任務先到先服務,高優先級的任務能夠搶佔低優先級的任務;後 者提供 Round-Robin 語義,採用時間片,相同優先級的任務當用完時間片會被放到隊列尾
部,以保證公平性,一樣,高優先級的任務能夠搶佔低優先級的任務。不一樣要求的實時任務可
以根據須要用 sched_setscheduler() API 設置策略。
- SCHED_OTHER
此調度策略包含除上述實時進程以外的其餘進程,亦稱普通進程。採用分時策略,根據動態優
先級(可用 nice() API設置),分配 CPU 運算資源。 注意:這類進程比上述兩類實時進程優先
級低,換言之,在有實時進程存在時,實時進程優先調度。算法

Linux 除了實現上述策略,還額外支持如下策略:
SCHED_IDLE 優先級最低,在系統空閒時才跑這類進程(如利用閒散計算機資源跑地外文明搜索,蛋白質結構分析等任務,是此調度策略的適用者)
- SCHED_BATCH 是 SCHED_OTHER 策略的分化,與 SCHED_OTHER 策略同樣,但針對吞吐量優化
- SCHED_DEADLINE 是新支持的實時進程調度策略,針對突發型計算,且對延遲和完成時間高度敏感的任務適用。docker

除了完成以上基本任務外,Linux 調度器還應提供高性能保障,對吞吐量和延時的均衡要有好的優化;要提供高可擴展性(scalability)保障,保障上千節點的性能穩定;對於普遍做爲服務器領域操做系統來講,它還提供豐富的組策略調度和節能調度的支持。

-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 重要功能和時間點 -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

目錄:
1 搶佔支持(preemption)
2 普通進程調度器(SCHED_OTHER)之糾極進化史
3 有空時再跑 SCHED_IDLE
4 吭哧吭哧跑計算 SCHED_BATCH
5 十萬火急,限期完成 SCHED_DEADLINE
6 普通進程的組調度支持(Fair Group Scheduling)
7 實時進程的組調度支持(RT Group Scheduling)
8 組調度帶寬控制(CFS bandwidth control)
9 極大提升體驗的自動組調度(Auto Group Scheduling)
10 基於調度域的負載均衡
11 更精確的調度時鐘(HRTICK)
12 自動 NUMA 均衡(Automatic NUMA balancing)
13 CPU 調度與節能

-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 正文 -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

1 搶佔支持(preemption): 2.6 時代開始支持
(首次在2.5.4版本引入[37], 感謝知友 @costa 考證! 關於 Linux 版本規則,可看我文章[4]).

可搶佔性,對一個系統的調度延時具備重要意義。2.6 以前,一個進程進入內核態後,別的進程沒法搶佔,只能等其完成或退出內核態時才能搶佔, 這帶來嚴重的延時問題,2.6 開始支持內核態搶佔。

2 普通進程調度器(SCHED_OTHER)之糾極進化史:
Linux 一開始,普通進程和實時進程都是基於優先級的一個調度器, 實時進程支持 100 個優先級,普通進程是優先級小於實時進程的一個靜態優先級,全部普通進程建立時都是默認此優先級,但可經過 nice() 接口調整動態優先級(共40個). 實時進程的調度器比較簡單,而普通進程的調度器,則歷經變遷[5]:

2.1 O(1) 調度器: 2.6 時代開始支持(2002年引入)。顧名思義,此調度器爲O(1)時間複雜度。該調度器修正以前的O(n) 時間複雜度調度器,以解決擴展性問題。爲每個動態優先級維護隊列,從而能在常數時間內選舉下一個進程來執行。

2.2 夭折的 RSDL(The Rotating Staircase Deadline Scheduler)調度器, 2007 年 4 月提出,預期進入 2.6.22, 後夭折。

O(1) 調度器存在一個比較嚴重的問題: 複雜的交互進程識別啓發式算法 - 爲了識別交互性的和批處理型的兩大類進程,該啓發式算法融入了睡眠時間做爲考量的標準,但對於一些特殊的狀況,常常判斷不許,並且是改完一種狀況又發現一種狀況。

Con Kolivas (八卦:這傢伙白天是個麻醉醫生)爲解決這個問題提出RSDL(The Rotating Staircase Deadline Scheduler)算法。該算法的亮點是對公平概念的從新思考: 交互式(A)和批量式(B)進程應該是被徹底公平對待的,對於兩個動態優先級徹底同樣的 A, B 進程,它們應該被同等地對待,至於它們是交互式與否(交互式的應該被更快調度), 應該從他們對分配給他們的時間片的使用天然地表現出來,而不是應該由調度器自做高明地根據他們的睡眠時間去猜想。這 個算法的核心是Rotating Staircase, 是一種衰減式的優先級調整,不一樣進程的時間片使用方式不一樣,會讓它們以不一樣的速率衰減(在優先級隊列數組中一級一級降低,這是下樓梯這名字的由來), 從而天然地區分開來進程是交互式的(間歇性的少許使用時間片)和批量式的(密集的使用時間片)。具體算法細節可看這篇文章:The Rotating Staircase Deadline Scheduler [LWN.net]

2.3 徹底公平的調度器(CFS), 2.6.23(2007年10月發佈)

Con Kolivas 的徹底公平的想法啓發了原 O(1)調度器做者 Ingo Molnar, 他從新實現了一個新的調度器,叫 CFS(Completely Fair Scheduler)。新調度器的核心一樣是徹底公平性, 即平等地看待全部普通進程,讓它們自身行爲彼此區分開來,從而指導調度器進行下一個執行進程的選舉。

具 體說來,此算法基於一個理想模型。想像你有一臺無限個 相同計算力的 CPU, 那麼徹底公平很容易,每一個 CPU 上跑一個進程便可。可是,現實的機器 CPU 個數是有限的,超過 CPU 個數的進程數不可能徹底同時運行。所以,算法爲每一個進程維護一個理想的運行時間,及實際的運行時間,這兩個時間差值大的,說明受到了不公平待遇,更應獲得 執行。

至於這種算法如何區分交互式進程和批量式進程,很簡單。交互式的進程大部分時間在睡眠,所以它的實際運行時間很小,而理想運行時間 是隨着時間的前進而增長的,因此這兩個時間的差值會變大。與之相反,批量式進程大部分時間在運行,它的實際運行時間和理想運行時間的差距就較小。所以,這 兩種進程被區分開來。

CFS 的測試性能比 RSDS 好,並獲得更多的開發者支持,因此它最終替代了 RSDL 在 2.6.23 進入內核,一直使用到如今。能夠八卦的是,Con Kolivas 所以離開了社區,不過他本人否定是由於此事而心生齟齬。後來,2009 年,他對愈來愈龐雜的 CFS 不滿意,認爲 CFS 過度注重對大規模機器,而大部分人都是使用少 CPU 的小機器,開發了 BFS 調度器[6], 這個在 Android 中有使用,沒進入 Linux 內核。

3 有空時再跑 SCHED_IDLE, 2.6.23(2007年10月發佈)

此 調度策略和 CFS 調度器在同一版本引入。系統在空閒時,每一個 CPU 都有一個 idle 線程在跑,它什麼也不作,就是把 CPU 放入硬件睡眠狀態以節能(須要特定CPU的driver支持), 並等待新的任務到來,以把 CPU 從睡眠狀態中喚醒。若是你有任務想在 CPU 徹底 idle 時才執行,就能夠用sched_setscheduler() API 設置此策略。

4 吭哧吭哧跑計算 SCHED_BATCH, 2.6.16(2006年3月發佈)

概述中講到 SCHED_BATCH 並不是 POSIX 標準要求的調度策略,而是 Linux 本身額外支持的。
它 是從 SCHED_OTHER 中分化出來的, 和 SCHED_OTHER 同樣,不過該調度策略會讓採用策略的進程比 SCHED_OTHER 更少受到 調度器的重視。所以,它適合非交互性的,CPU 密集運算型的任務。若是你事先知道你的任務屬於該類型,能夠用 sched_setscheduler() API 設置此策略。

在引入該策略後,原來的 SCHED_OTHER 被更名爲 SCHED_NORMAL, 不過它的值不變,所以保持 API 兼容,以前的 SCHED_OTHER 自動成爲 SCHED_NORMAL, 除非你設置 SCHED_BATCH。

5 十萬火急,限期完成 SCHED_DEADLINE, 3.14(2014年3月發佈)

此 策略支持的是一種實時任務。對於某些實時任務,具備陣發性(sporadic), 它們陣發性地醒來執行任務,且任務有 deadline 要求,所以要保證在 deadline 時間到來前完成。爲了完成此目標,採用該 SCHED_DEADLINE 的任務是系統中最高優先級的,它們醒來時能夠搶佔任何進程。

若是你有任務屬於該類型,能夠用 sched_setscheduler() 或 sched_setattr() API 設置此策略。

更多可參看此文章:Deadline scheduling: coming soon? [LWN.net]

6 普通進程的組調度支持(Fair Group Scheduling), 2.6.24(2008年1月發佈)

2.6.23 引入的 CFS 調度器對全部進程徹底公平對待。但這有個問題,設想當前機器有2個用戶,有一個用戶跑着 9個進程,還都是 CPU 密集型進程;另外一個用戶只跑着一個 X 進程,這是交互性進程。從 CFS 的角度看,它將平等對待這 10 個進程,結果致使的是跑 X 進程的用戶受到不公平對待,他只能獲得約 10% 的 CPU 時間,讓他的體驗至關差。

基於此,組調度的概念被引入[6]。CFS 處理的再也不是一個進程的概念,而是調度實體(sched entity), 一個調度實體能夠只包含一個進程,也能夠包含多個進程。所以,上述例子的困境能夠這麼解決:分別爲每一個用戶創建一個組,組裏放該用戶全部進程,從而保證用 戶間的公平性。

該功能是基於控制組(control group, cgroup)的概念,須要內核開啓 CGROUP 的支持纔可以使用。關於 CGROUP ,之後可能會寫。

7 實時進程的組調度支持(RT Group Scheduling), 2.6.25(2008年4月發佈)

該功能同普通進程的組調度功能同樣,只不過是針對實時進程的。

8 組調度帶寬控制(CFS bandwidth control) , 3.2(2012年1月發佈)

組 調度的支持,對實現多租戶系統的管理是十分方便的,在一臺機器上,能夠方便對多用戶進行 CPU 均分.而後,這還不足夠,組調度只能保證用戶間的公平,但若管理員想控制一個用戶使用的最大 CPU 資源,則須要帶寬控制.3.2 針對 CFS組調度,引入了此功能[8], 該功能可讓管理員控制在一段時間內一個組能夠使用 CPU 的最長時間.

9 極大提升體驗的自動組調度(Auto Group Scheduling), 2.6.38(2011年3月發佈)

試想,你在終端裏熟練地敲擊命令,編譯一個大型項目的代碼,如 Linux內核,而後在編譯的同時清閒地看着電影等待,結果電腦卻很是卡,體驗必定很不爽.

2.6.38 引入了一個針對桌面用戶體驗的改進,叫作自動組調度.短短400多行代碼[9], 就很大地提升了上述情形中桌面使用者體驗,引發不小轟動.

其實原理不復雜,它是基於以前支持的組調度的一個延伸.Unix 世界裏,有一個會話(session) 的概念,即跟某一項任務相關的全部進程,能夠放在一個會話裏,統一管理.好比你登陸一個系統,在終端裏敲入用戶名,密碼,而後執行各類操做,這全部進程,就被規劃在一個會話裏.

所以,在上述例子裏,編譯代碼和終端進程在一個會話裏,你的瀏覽器則在另外一個會話裏.自動組調度的工做就是,把這些不一樣會話自動分紅不一樣的調度組,從而利用組調度的優點,使瀏覽器會話不會過多地受到終端會話的影響,從而提升體驗.

該功能能夠手動關閉.

10 基於調度域的負載均衡, 2.6.7(2004年6月發佈)

計 算機依靠並行度來突破性能瓶頸,CPU個數也是與日俱增。最先的是 SMP(對稱多處理), 因此 CPU共享內存,並訪問速度一致。隨着 CPU 個數的增長,這種作法不適應了,由於 CPU 個數的增多,增長了總線訪問衝突,這樣 CPU 增長的並行度被訪問內存總線的瓶頸給抵消了,因而引入了 NUMA(非一致性內存訪問)的概念。機器分爲若干個node, 每一個node(其實通常就是一個 socket)有本地可訪問的內存,也能夠經過 interconnect 中介機構訪問別的 node 的內存,可是訪問速度下降了,因此叫非一致性內存訪問。Linux 2.5版本時就開始了對 NUMA 的支持[7]。

而在調度器領域,調度器有一個重要任務就是作負載均衡。當某個 CPU 出現空閒,就要從別的 CPU 上調整任務過來執行; 當建立新進程時,調度器也會根據當前負載情況分配一個最適合的 CPU 來執行。而後,這些概念是大大簡化了實際情形。

在一個 NUMA 機器上,存在下列層級:

1. 每個 NUMA node 是一個 CPU socket(你看主板上CPU位置上那一塊東西就是一個 socket).
2. 每個socket上,可能存在兩個核,甚至四個核。
3. 每個核上,能夠打開硬件多純程(HyperThread)。

若是一個機器上同時存在這三人層級,則對調度器來講,它所見的一個邏輯 CPU實際上是一人 HyperThread.處理同一個core 中的CPU , 能夠共享L1, 乃至 L2 緩存,不一樣的 core 間,能夠共享 L3 緩存(若是存在的話).

基於此,負載均衡不能簡單看不一樣 CPU 上的任務個數,還要考慮緩存,內存訪問速度.因此,2.6.7 引入了調度域(sched domain) 的概念,把 CPU 按上述層級劃分爲不一樣的層級,構建成一棵樹,葉子節點是每一個邏輯 CPU, 往上一層,是屬於 core 這個域,再往上是屬於 socket 這個域,再往上是 NUMA 這個域,包含全部 CPU.

當 進行負載均衡時,將從最低一級域往上看,若是能在 core 這個層級進行均衡,那最好;不然往上一級,能在socket 一級進行均衡也還湊合;最後是在 NUMA node 之間進行均衡,這是代價很是大的,由於跨 node 的內存訪問速度會下降,也許會得不償失,不多在這一層進行均衡.

這種分層的作法不只保證了均衡與性能的平衡,還提升了負載均衡的效率.

關於這方面,能夠看這篇文章:Scheduling domains [LWN.net]

11 更精確的調度時鐘(HRTICK), 2.6.25(2008年4月發佈)

CPU的週期性調度,和基於時間片的調度,是要基於時鐘中斷來觸發的.一個典型的 1000 HZ 機器,每秒鐘產生 1000 次時間中斷,每次中斷到來後,調度器會看看是否須要調度.

然而,對於調度時間粒度爲微秒(10^-6)級別的精度來講,這每秒 1000 次的粒度就顯得太粗糙了.

2.6.25引入了所謂的高清嘀噠(High Resolution Tick), 以提供更精確的調度時鐘中斷.這個功能是基於高精度時鐘(High Resolution Timer)框架,這個框架讓內核支持能夠提供納秒級別的精度的硬件時鐘(將會在時鐘子系統裏講).

12 自動 NUMA 均衡(Automatic NUMA balancing), 3.8(2013年2月發佈)

NUMA 機器一個重要特性就是不一樣 node 之間的內存訪問速度有差別,訪問本地 node 很快,訪問別的 node 則很慢.因此進程分配內存時,老是優先分配所在 node 上的內存.然而,前面說過,調度器的負載均衡是可能把一個進程從一個 node 遷移到另外一個 node 上的,這樣就形成了跨 node 的內存訪問;Linux 支持 CPU 熱插拔,當一個 CPU 下線時,它上面的進程會被遷移到別的 CPU 上,也可能出現這種狀況.

調度者和內存領域的開發者一直致力於解決這個問題.因爲兩大系統都很是複雜,找一個通用的可靠的解決方 案不容易,開發者中提出兩套解決方案,各有優劣,一直未能達成一致意見.3.8內核中,內存領域的知名黑客 Mel Gorman 基於此狀況,引入一個叫自動 NUMA 均衡的框架,以期存在的兩套解決方案能夠在此框架上進行整合; 同時,他在此框架上實現了簡單的策略:每當發現有跨 node 訪問內存的狀況時,就立刻把該內存頁面遷移到當前 node 上.

不過到 4.2 ,彷佛也沒發現以前的兩套方案有任意一個遷移到這個框架上,卻是,在前述的簡單策略上進行更多改進.

若是須要研究此功能的話,可參考如下幾篇文章:
-介紹 3.8 前兩套競爭方案的文章:A potential NUMA scheduling solution [LWN.net]
- 介紹 3.8 自動 NUMA 均衡 框架的文章:NUMA in a hurry [LWN.net]
- 介紹 3.8 後進展的兩篇文章,細節較多,建議對調度/內存代碼有研究後才研讀:
NUMA scheduling progress [LWN.net]
NUMA placement problems [LWN.net]

13 CPU 調度與節能

從 節能角度講,若是能維持更多的 CPU 處於深睡眠狀態,僅保持必要數目的 CPU 執行任務,就能更好地節約電量,這對筆記本電腦來講,尤爲重要.然而這不是一個簡單的工做,這涉及到負載均衡,調度器,節能模塊的並互,Linux 調度器中曾經有相關的代碼,但後來發現問題,在3.5, 3.6 版本中,已經把相關代碼刪除.整個問題須要從新思考.

在前不久,一個新的 patch 被提交到 Linux 內核開發郵件列表,這個問題也許有了新的眉目,到時再來更新此小節.可閱讀此文章:Steps toward power-aware scheduling [LWN.net]

========== 調度子系統 結束分割線 ==========

  • 內存管理子系統(memory management)

概述:內 存管理子系統,做爲 kernel 核心中的核心,是承接全部系統活動的舞臺,也是 Linux kernel 中最爲龐雜的子系統, 沒有之一.截止 4.2 版本,內存管理子系統(下簡稱 MM)全部平臺獨立的核心代碼(C文件和頭文件)達到11萬6千多行,這還不包括平臺相關的 C 代碼, 及一些彙編代碼;與之相比,調度子系統的平臺獨立的核心代碼才2萬8千多行.

現代操做系統的 MM 提供的一個重要功能就是爲每一個進程提供獨立的虛擬地址空間抽象,爲用戶呈現一個平坦的進程地址空間,提供安全高效的進程隔離,隱藏全部細節,使得用戶能夠簡單可移植的庫接口訪問/管理內存,大大解放程序員生產力.

在繼續往下以前,先介紹一些 Linux 內核中內存管理的基本原理和術語,方便後文討論.

- 物理地址(Physical Address): 這就是內存 DIMM 上的一個一個存儲區間的物理編址,以字節爲單位.

- 虛擬地址(Virtual Address): 技術上來說,用戶或內核用到的地址就是虛擬地址,須要 MMU (內存管理單元,一個用於支持虛擬內存的 CPU 片內機構) 翻譯爲物理地址.在 CPU 的技術規範中,可能還有虛擬地址和線性地址的區別,但在這不重要.

- NUMA(Non-Uniform Memory Access): 非 一致性內存訪問.NUMA 概念的引入是爲了解決隨着 CPU 個數的增加而出現的內存訪問瓶頸問題,非一致性內存意爲每一個 NUMA 節點都有本地內存,提供高訪問速度;也能夠訪問跨節點的內存,但要遭受較大的性能損耗.因此儘管整個系統的內存對任何進程來講都是可見的,但卻存在訪問速 度差別,這一點對內存分配/內存回收都有着很是大的影響.Linux 內核於2.5版本引入對 NUMA的支持[7].

- NUMA node(NUMA節點): NUMA 體系下,一個 node 通常是一個CPU socket(一個 socket 裏可能有多個核)及它可訪問的本地內存的總體.

- zone(內存區): 一個 NUMA node 裏的物理內存又被分爲幾個內存區(zone), 一個典型的 node 的內存區劃分以下:
linux內核版本差別

能夠看到每一個node裏,隨着物理內存地址的增長,典型地分爲三個區:

1. ZONE_DMA: 這個區的存在有歷史緣由,古老的 ISA 總線外設,它們進行 DMA操做[10] 時,只能訪問內存物理空間低 16MB 的範圍.因此故有這一區,用於給這些設備分配內存時使用.
2. ZONE_NORMAL: 這是 32位 CPU時代產物,不少內核態的內存分配都是在這個區間(用戶態內存也能夠在這部分分配,但優先在ZONE_HIGH中分配),但這部分的大小通常就只有 896 MiB, 因此比較侷限. 64位 CPU 狀況下,內存的訪問空間增大,這部分空間就增大了不少.關於爲什麼這部分區間這麼侷限,且內核態內存分配在這個區間,感興趣的能夠看我之間一個回答 [11].
3. ZONE_HIGH: 典型狀況下,這個區間覆蓋系統全部剩餘物理內存.這個區間叫作高端內存區(不是高級的意思,是地址區間高的意思). 這部分主要是用戶態和部份內核態內存分配所處的區間.

- 內存頁/頁面(page): 現 代虛擬內存管理/分配的單位是一個物理內存頁, 大小是 4096(4KB) 字節. 固然,不少 CPU 提供多種尺寸的物理內存頁支持(如 X86, 除了4KB, 還有 2MB, 1GB頁支持),但 Linux 內核中的默認頁尺寸就是 4KB.內核初始化過程當中,會對每一個物理內存頁分配一個描述符(struct page), 後文描述中可能屢次提到這個描述符,它是 MM 內部,也是 MM 與其餘子系統交互的一個接口描述符.

- 頁表(page table): 從一個虛擬地址翻譯爲物理地址時,其實就是從一個稀疏哈希表中查找的過程,這個哈希表就是頁表.

- 交換(swap): 內存緊缺時, MM 可能會把一些暫時不用的內存頁轉移到訪問速度較慢的次級存儲設備中(如磁盤, SSD), 以騰出空間,這個操做叫交換, 相應的存儲設備叫交換設備或交換空間.

- 文件緩存頁(PageCache Page): 內核會利用空閒的內存, 事先讀入一些文件頁, 以期不久的未來會用到, 從而避免在要使用時再去啓動緩慢的外設(如磁盤)讀入操做. 這些有後備存儲介質的頁面, 能夠在內存緊缺時輕鬆丟棄, 等到須要時再次從外設讀入. 典型的表明有可執行代碼, 文件系統裏的文件.

- 匿名頁(Anonymous Page):
 這種頁面的內容都是在內存中創建的,沒有後備的外設, 這些頁面在回收時不能簡單的丟棄, 須要寫入到交換設備中. 典型的表明有進程的棧, 使用 malloc() 分配的內存所在的頁等 .

-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 重要功能和時間點 -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

下文將按此目錄分析 Linux 內核中 MM 的重要功能和引入版本:

目錄:
1 內存分配
2 內存去碎片化

3 頁表管理

4 頁面回收

5 頁面寫回

6 頁面預讀
7 大內存頁支持
8 內存控制組(Memory Cgroup)支持
9 內存熱插拔支持
10 超然內存(Transcendent Memory)支持

11 非易失性內存 (NVDIMM, Non-Volatile DIMM) 支持
12 內存管理調試支持
13 雜項

-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* 正文 -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

1 內存分配
1.1 頁分配器: 夥伴分配器[12] , 古老, 具體時間難考 , 應該是生而有之. orz...

內 存頁分配器, 是 MM 的一個重大任務, 將內存頁分配給內核或用戶使用. 內核把內存頁分配粒度定爲 11 個層次, 叫作階(order). 第0階就是 2^0 個(即1個)連續物理頁面, 第 1 階就是 2^1 個(即2個)連續物理頁面, ..., 以此類推, 因此最大是一次能夠分配 2^10(= 1024) 個連續物理頁面.

因此, MM 將全部空閒的物理頁面如下列鏈表數組組織進來:
linux內核版本差別
(圖片來自Physical Page Allocation)

那 夥伴(buddy)的概念從何體現? 體如今釋放的時候, 當釋放某個頁面(組)時, MM 若是發現同一個階中若是有某個頁面(組) 跟這個要釋放的頁面(組) 是物理連續的, 那就把它們合併, 並升入下一階(如: 兩個 0 階的頁面, 合併後, 變爲連續的2頁面(組), 即一個1階頁面). 兩個頁面(組) 手拉手升階, 因此叫夥伴.

關於NUMA 支持: Linux 內核中, 每一個 zone 都有上述的鏈表數組, 從而提供精確到某個 node 的某個 zone 的夥伴分配需求.

1.2 對象分配器: 內核級別的 malloc 分配器

內 核中常見的是常常分配某種固定大小尺寸的對象, 而且對象都須要必定的初始化操做, 這個初始化操做有時比分配操做還要費時, 所以, 一個解決方法是用緩存池把這些對象管理起來, 把第一次分配做初始化; 釋放時析構爲這個初始狀態, 這樣能提升效率. 此外, 增長一個緩存池, 把不一樣大小的對象分類管理起來, 這樣能更高效地使用內存 - 試想用固定尺寸的頁分配器來分配給對象使用, 則不可避免會出現大量內部碎片. 所以, Linux 內核在頁分配器的基礎上, 實現了一個對象分配器: slab.

1.2.1 SLAB, 2.0 版本時代(1996年引入)

這 是最先引入的對象分配器, SLAB 基於頁分配器分配而來的頁面(組), 實現本身的對象緩存管理. 它提供預約尺寸的對象緩存, 也支持用戶自定義對象緩存, 維護着每一個 CPU , 每一個 NUMA node 的緩存隊列層級, 能夠提供高效的對象分配. 同時, 還支持硬件緩存對齊和着色,所謂着色, 就是把不一樣對象地址, 以緩存行對單元錯開, 從而使不一樣對象佔用不一樣的緩存行,從而提升緩存的利用率並得到更好的性能。

1.2.2 SLUB, 2.6.22(2007年7月發佈)

這 是第二個對象分配器實現. 引入這個新的實現的緣由是 SLAB 存在的一些問題. 好比 NUMA 的支持, SLAB 引入時內核還沒支持 NUMA, 所以, 一開始就沒把 NUMA 的需求放在設計理念裏, 結果致使後來的對 NUMA 的支持比較臃腫奇怪, 一個典型的問題是, SLAB 爲追蹤這些緩存, 在每一個 CPU, 每一個 node, 上都維護着對象隊列. 同時, 爲了知足 NUMA 分配的局部性, 每一個 node 上還維護着全部其餘 node 上的隊列, 這樣致使 SLAB 內部爲維護這些隊列就得花費大量的內存空間, 而且是O(n^2) 級別的. 這在大規模的 NUMA 機器上, 浪費的內存至關可觀[13]. 同時, 還有別的一些使用上的問題, 致使開發者對其不滿, 於是引入了新的實現.

SLUB 在解決了上述的問題之上, 提供與 SLAB 徹底同樣的接口, 因此用戶能夠無縫切換, 並且, 還提供了更好的調試支持. 早在幾年前, 各大發行版中的對象分配器就已經切換爲 SLUB了.

關 於性能, 前陣子爲公司的系統切換 SLUB, 作過一些性能測試, 在一臺兩個 NUMA node, 32 個邏輯 CPU , 252 GB 內存的機器上, 在相同的 workload 測試下, SLUB 綜合來講,體現出了比 SLAB 更好的性能和吞吐量.

1.2.3 SLOB, 2.6.16(2006年3月發佈)

這是第三個對象分配器, 提供一樣的接口, 它是爲適用於嵌入式小內存小機器的環境而引入的, 因此實現上很精簡, 大大減少了內存 footprint, 能在小機器上提供很不錯的性能.

1.3 連續內存分配器(CMA), 3.5(2012年7月發佈)

顧 名思義,這是一個分配連續物理內存頁面的分配器. 也許你會疑惑夥伴分配器不是也能分配連續物理頁面嗎? 誠然, 可是一個系統在運行若干時間後, 可能很難再找到一片足夠大的連續內存了, 夥伴系統在這種狀況下會分配失敗. 但連續物理內存的分配需求是剛需: 一些比較低端的 DMA 設備只能訪問連續的物理內存; 還有下面會講的透明大頁的支持, 也須要連續的物理內存.

一個解決辦法就是在系統啓動時,在內存還很充足的時候, 先預留一部分連續物理內存頁面, 留做後用. 但這有個代價, 這部份內存就沒法被做其餘使用了, 爲了可能的分配需求, 預留這麼一大塊內存, 不是一個明智的方法.

CMA 的作法也是啓動時預留, 但不一樣的是, 它容許這部份內存被正常使用, 在有連續內存分配需求時, 把這部份內存裏的頁面遷移走, 從而空出位置來做分配 .

2 內存去碎片化

前面講了運行較長時間的系統存在的內存碎片化問題, Linux 內核也不能倖免, 所以有開發者陸續提出若干種方法.

2.1 成塊回收(Lumpy Reclaim) 2.6.23引入(2007年7月), 3.5移除(2012年7月)

這 不是一個完整的解決方案, 它只是緩解這一問題. 所謂回收是指 MM 在分配內存遇到內存緊張時, 會把一部份內存頁面回收. 而成塊回收[14], 就是嘗試成塊回收目標回收頁相鄰的頁面,以造成一塊知足需求的高階連續頁塊。這種方法有其侷限性,就是成塊回收時沒有考慮被連帶回收的頁面多是「熱 頁」,即被高強度使用的頁,這對系統性能是損傷。

2.2 基於頁面可移動性的頁面聚類(Page Clustering by Page Mobility) 2.6.23(2007年7月發佈)

這 個名字是我造的, 有點拗口. 所謂可移動性, 是基於對下列事實的思考: 在去碎片化時,須要移動或回收頁面,以騰出連續的物理頁面,但可能一顆「老鼠屎就壞了整鍋粥」——因爲某個頁面沒法移動或回收,致使整個區域沒法組成一個 足夠大的連續頁面塊。這種頁面一般是內核使用的頁面,由於內核使用的頁面的地址是直接映射(即物理地址加個偏移就映射到內核空間中),這種作法不用通過頁 表翻譯,提升了效率,卻也在此時成了攔路虎。

終年致力於解決內存碎片化的內存領域黑客 Mel Gorman 觀察到這個事實, 在通過28個版本[15]的修改後, 他的解決方案進入內核.

Mel Gorman觀察到,全部使用的內存頁有三種情形:

1.容易回收的(easily reclaimable): 這種頁面能夠在系統須要時回收,好比文件緩存頁,們能夠輕易的丟棄掉而不會有問題(有須要時再從後備文件系統中讀取); 又好比一些生命週期短的內核使用的頁,如DMA緩存區。
2.難回收的(non-reclaimable): 這種頁面得內核主動釋放,很難回收,內核使用的不少內存頁就歸爲此類,好比爲模塊分配的區域,好比一些常駐內存的重要內核結構所佔的頁面。
3. 可移動的(movable): 用戶空間分配的頁面都屬於這種類型,由於用戶態的頁地址是由頁表翻譯的,移動頁後只要修改頁表映射就能夠(這也從另外一面應證了內核態的頁爲何不能移動,由於它們採起直接映射)。

所以, 他修改了夥伴分配器和分配 API, 使得在分配時告知夥伴分配器頁面的可移動性: 回收時, 把相同移動性的頁面聚類; 分配時, 根據移動性, 從相應的聚類中分配.

聚類的好處是, 結合上述的成塊回收方案, 回收頁面時,就能保證回收同一類型的; 或者在遷移頁面時(migrate page), 就能移動可移動類型的頁面,從而騰出連續的頁面塊,以知足高階的連續物理頁面分配。

關於細節, 可看我以前寫的文章:Linux內核中避免內存碎片的方法(1)

2.3 內存緊緻化(Memory Compaction) 2.6.35(2010年8月發佈)

2.2中講到頁面聚類, 它把至關可移動性的頁面彙集在一塊兒: 可移動的在一塊兒, 可回收的在一塊兒, 不可移動的也在一塊兒. 它做爲去碎片化的基礎. 而後, 利用成塊回收, 在回收時, 把可回收的一塊兒回收, 把可移動的一塊兒移動, 從而能空出大量連續物理頁面. 這個做爲去碎片化的策略.

2.6.35 裏, Mel Gorman 又實現了一種新的去碎片化的策略[16], 叫內存緊緻化. 不一樣於成塊回收回收相臨頁面, 內存緊緻化則 是更完全, 它在回收頁面時被觸發, 它會在一個 zone 裏掃描, 把已分配的頁記錄下來, 而後把全部這些頁移動到 zone 的一端, 這樣這把一個可能已經七零八落的 zone 給緊緻化成一段徹底未分配的區間和一段已經分配的區間, 這樣就又騰出大塊連續的物理頁面了.

它後來替代了成塊回收, 使得後者在3.5中被移除.

3 頁表管理

3.1 四級頁表 2.6.11(2005年3月發佈)

頁表實質上是一個虛擬地址到物理地址的映射表, 但因爲程序的局部性, 某時刻程序的某一部分才須要被映射, 換句話說, 這個映射表是至關稀疏的, 所以在內存中維護一個一維的映射表太浪費空間, 也不現實. 所以, 硬件層次支持的頁表就是一個多層次的映射表.

Linux 一開始是在一臺i386上的機器開發的, i386 的硬件頁表是2級的(頁目錄項 -> 頁表項), 因此, 一開始 Linux 支持的軟件頁表也是2級的; 後來, 爲了支持 PAE (Physical Address Extension), 擴展爲3級; 後來, 64位 CPU 的引入, 3級也不夠了, 因而, 2.6.11 引入了四級的通用頁表.

關於四級頁表是如何適配 i386 的兩級頁表的, 很簡單, 就是虛設兩級頁表. 類比下, 北京市(省)北京市海淀區東昇鎮, 就是爲了適配4級行政區規劃而引入的一種表示法. 這種通用抽象的軟件工程作法在內核中不乏例子.

關於四級頁表演進的細節, 可看我之前文章: Linux內核4級頁表的演進

3.2 延遲頁表緩存沖刷 (Lazy-TLB flushing), 極早引入, 時間難考

有 個硬件機構叫 TLB, 用來緩存頁表查尋結果, 根據程序局部性, 即將訪問的數據或代碼極可能與剛訪問過的在一個頁面, 有了 TLB 緩存, 頁表查找不少時候就大大加快了. 可是, 內核在切換進程時, 須要切換頁表, 同時 TLB 緩存也失效了, 須要沖刷掉. 內核引入的一個優化是, 當切換到內核線程時, 因爲內核線程不使用用戶態空間, 所以切換用戶態的頁表是沒必要要, 天然也不須要衝刷 TLB. 因此引入了 Lazy-TLB 模式, 以提升效率. 關於細節, 可參考[17]

4. 頁面回收

/* 從用戶角度來看, 這一節對了解 Linux 內核發展幫助不大,可跳過不讀; 但對於技術人員來講, 本節能夠展示教材理論模型到工程實現的一些思考與折衷, 還有軟件工程實踐中由簡單粗糙到複雜精細的演變過程 */

當 MM 遭遇內存分配緊張時, 會回收頁面. 頁框替換算法(Page Frame Replacement Algorithm, 下稱PFRA) 的實現好壞對性能影響很大: 若是選中了頻繁或將立刻要用的頁, 將會出現 Swap Thrashing 現象, 即剛換出的頁又要換回來, 現象就是系統響應很是慢.

4.1 加強的LRU算法 (2.6前引入, 具體時間難考)

教科書式的 PFRA 會提到要用 LRU (Least-Recently-Used) 算法, 該算法思想基於: 最近不多使用的頁, 在緊接着的將來應該也不多使用, 所以, 它能夠被看成替換掉的候選頁.

但 現實中, 要跟蹤每一個頁的使用狀況, 開銷不是通常的大, 尤爲是內存大的系統. 並且, 還有一個問題, LRU 考量的是近期的歷史, 卻沒能體現頁面的使用頻率 - 假設有一個頁面會被屢次訪問, 最近一次訪問稍久點了, 這時涌入了不少只會使用一次的頁(好比在播放電影), 那麼按照 LRU 語義, 極可能被驅逐的是前者, 而不是後者這些不會再用的頁面.

爲此, Linux 引入了兩個鏈表, 一個 active list, 一個 inactive list , 這兩個鏈表如此工做:

1. inactive list 鏈表尾的頁將做爲候選頁, 在須要時被替換出系統.
2. 對 於文件緩存頁, 當第一次被讀入時, 將置於 inactive list 鏈表頭。若是它被再次訪問, 就把它提高到 active list 鏈表尾; 不然, 隨着新的頁進入, 它會被慢慢推到 inactive list 尾巴; 若是它再一次訪問, 就把它提高到 active list 鏈表頭.
3. 對於匿名頁, 當第一次被讀入時, 將置於 active list 鏈表尾(對匿名頁的優待是由於替換它出去要寫入交換設備, 不能直接丟棄, 代價更大); 若是它被再次訪問, 就把它提高到 active list 鏈表頭,
4. 在須要換頁時, MM 會從 active 鏈表尾開始掃描, 把足夠量頁面降級到 inactive 鏈表頭, 一樣, 默認文件緩存頁會受到優待(用戶可經過 swappiness 這個用戶接口設置權重)。

如上, 上述兩個鏈表按照使用的熱度構成了四個層級:
active 頭(熱烈使用中) > active 尾 > inactive 頭 > inactive 尾(被驅逐者)

這種加強版的 LRU 同時考慮了 LRU 的語義: 更近被使用的頁在鏈表頭;
又考慮了使用頻度: 仍是考慮前面的例子, 一個頻繁訪問的頁, 它極有可能在 active 鏈表頭, 或者次一點, 在 active 鏈表尾, 此時涌入的大量一次性文件緩存頁, 只會被放在 inactive 鏈表頭, 於是它們會更優先被替換出去.

4.2 active 與 inactive 鏈表拆分, 2.6.28(2008年12月)

4.1 中描述過一個用戶可配置的接口 : swappiness. 這是一個百分比數(取值 0 -100, 默認60), 當值越靠近100, 表示更傾向替換匿名頁; 當值越靠近0, 表示更傾向替換文件緩存頁. 這在不一樣的工做負載下容許管理員手動配置.

4.1 中 規則 4)中說在須要換頁時, MM 會從 active 鏈表尾開始掃描, 若是有一臺機器的 swappiness 被設置爲 0, 意爲徹底不替換匿名頁, 則 MM 在掃描中要跳過不少匿名頁, 若是有不少匿名頁(在一臺把 swappiness設爲0的機器上極可能是這樣的), 則容易出現性能問題.

解決方法就是把鏈表拆分爲匿名頁鏈表和文件緩存頁鏈表[18][19],如今 active 鏈表分爲 active 匿名頁鏈表 和 active 文件緩存頁鏈表了;inactive 鏈表也如此。 因此, 當 swappiness 爲0時,只要掃描 active 文件緩存頁鏈表就夠了。

4.3 再拆分出被鎖頁的鏈表, 2.6.28(2008年12月)

雖然如今拆分出4個鏈表了, 但還有一個問題, 有些頁被"釘"在內存裏(好比實時算法, 或出於安全考慮, 不想含有敏感信息的內存頁被交換出去等緣由,用戶經過 mlock()等系統調用把內存頁鎖住在內存裏). 當這些頁不少時, 掃描這些頁一樣是徒勞的.

因此解決辦法是把這些頁獨立出來, 放一個獨立鏈表. 如今就有5個鏈表了, 不過有一個鏈表不會被掃描. [20][21]

4.4 讓代碼文件緩存頁多待一會, 2.6.31(2009年9月發佈)

試想, 當你在拷貝一個很是大的文件時, 你發現忽然電腦變得反應慢了, 那麼可能發生的事情是:
忽然涌入的大量文件緩存頁讓內存告急, 因而 MM 開始掃描前面說的鏈表, 若是系統的設置是傾向替換文件頁的話(swappiness 靠近0), 那麼頗有可能, 某個 C 庫代碼所在代碼要在這個內存吃緊的時間點(意味掃描 active list 會比較兇)被挑中, 給扔掉了, 那麼程序執行到了該代碼, 要把該頁從新換入, 這就是發生了前面說的 Swap Thrashing 現象了。這體驗天然就差了.

解決方法是在掃描這些在使用中的代碼文件緩存頁時, 跳過它, 讓它有多一點時間待在 active 鏈表上, 從而避免上述問題. [22][23]

4.5 工做集大小的探測, 3.15(2014年6月發佈)

一個文件緩存頁(代碼)一開始進入 inactive 鏈表表頭, 若是它沒被再次訪問, 它將被慢慢推到 inactive 鏈表表尾, 最後在回收時被回收走; 而若是有再次訪問, 它會被提高到 active 鏈表尾, 再再次訪問, 提高到 active 鏈表頭. 所以, 能夠定義一個概念: 訪問距離, 它指該頁面第一次進入內存到被踢出的間隔, 顯然至少是 inactive 鏈表的長度.

那麼問題來了: 這個 inactive 鏈表的長度得多長? 才能保護該代碼頁在第二次訪問前儘可能不被踢出, 以免 Swap Thrashing 現象.

在 這以前內核僅僅簡單保證 active 鏈表不會大於 inactive 鏈表. 若是進一步思考, 這個問題跟工做集大小相關. 所謂工做集, 就是維持系統全部活動的所需內存頁面的最小量. 若是工做集小於等於 inactive 鏈表長度, 即訪問距離, 則是安全的; 若是工做集大於 inactive 鏈表長度, 即訪問距離, 則不可避免有些頁要被踢出去.

3.15 就引入了一種算法, 它經過估算訪問距離, 來測定工做集的大小, 從而維持 inactive 鏈表在一個合適長度.[24]

5 頁面寫回

/* 從用戶角度來看, 這一節對了解 Linux 內核發展幫助不大,可跳過不讀; 但對於技術人員來講, 本節能夠展示教材理論模型到工程實現的一些思考與折衷, 還有軟件工程實踐中由簡單粗糙到複雜精細的演變過程 */

當進程改寫了文件緩存頁, 此時內存中的內容與後備存儲設備(backing device)的內容便處於不一致狀態, 此時這種頁面叫作髒頁(dirty page). 內 核會把這些髒頁寫回到後備設備中. 這裏存在一個折衷: 寫得太頻繁(好比每有一個髒頁就寫一次)會影響吞吐量; 寫得太遲(好比積累了不少個髒頁才寫回)又可能帶來不一致的問題, 假設在寫以前系統崩潰, 則這些數據將丟失, 此外, 太多的髒頁會佔據太多的可用內存. 所以, 內核採起了幾種手段來寫回:
1) 設一個後臺門檻(background threshold), 當系統髒頁數量超過這個數值, 用後臺線程寫回, 這是異步寫回.
2) 設一個全局門檻(global threshold), 這個數值比後臺門檻高. 這是以防系統忽然生成大量髒頁, 寫回跟不上, 此時系統將扼制(throttle)生成髒頁的進程, 讓其開始同步寫回.

5.1 由全局的髒頁門檻到每設備髒頁門檻 2.6.24(2008年1月發佈)

內核採起的第2個手段看起來很高明 - 扼制生成髒頁的進程, 使其中止生成, 反而開始寫回髒頁, 這一進一退中, 就能把全局的髒頁數量拉低到全局門檻下. 可是, 這存在幾個微妙的問題:

1. 有可能這大量的髒頁是要寫回某個後備設備A的, 但被扼制的進程寫的髒頁則是要寫回另外一個後備設備B的. 這樣, 一個不相干的設備的髒頁影響到了另外一個(可能很重要的)設備, 這是不公平的.
2. 還 有一個更嚴重的問題出如今棧式設備上. 所謂的棧式設備(stacked device)是指多個物理設備組成的邏輯設備, 如 LVM 或 software RAID 設備上. 操做在這些邏輯設備上的進程只能感知到這些邏輯設備. 假設某個進程生成了大量髒頁, 因而, 在邏輯設備這一層, 髒頁到達門檻了, 進程被扼制並讓其寫回髒頁, 因爲進程只能感知到邏輯設備這一層, 因此它以爲髒頁已經寫下去了. 可是, 這些髒頁分配到底下的物理設備這一層時, 可能每一個物理設備都還沒到達門檻, 那麼在這一層, 是不會真正往下寫髒頁的. 因而, 這種極端局面形成了死鎖: 邏輯設備這一層覺得髒頁寫下去了; 而物理設備這一層還在等更多的髒頁以到達寫回的門檻.

2.6.24 引入了一個新的改進[25], 就是把全局的髒頁門檻替換爲每設備的門檻. 這樣第1個問題天然就解決了. 第2個問題其實也解決了. 由於如今是每一個設備一個門檻, 因此在物理設備這一層, 這個門檻是會比以前的全局門檻低不少的, 因而出現上述問題的可能性也不存在了.

那麼問題來了, 每一個設備的門檻怎麼肯定? 設備的寫回能力有強有弱(SSD 的寫回速度比硬盤快多了), 一個合理的作法是根據當前設備的寫回速度分配給等比例的帶寬(門檻). 這種動態根據速度調整的想法在數學上就是指數衰減[26]的理念:某個量的降低速度和它的值成比例. 因此, 在這個改進裏, 做者引入了一個叫"浮動比例"的庫, 它的本質就是一個根據寫回速度進行指數衰減的級數. (這個庫跟內核具體的細節無關, 感興趣的能夠研究這個代碼: [PATCH 19/23] lib: floating proportions [LWN.net]). 而後, 使用這個庫, 就能夠"實時地"計算出每一個設備的帶寬(門檻).

5.2 引入更具體擴展性的回寫線程 2.6.32(2009年12月發佈)

Linux 內核在髒頁數量到達必定門檻時, 或者用戶在命令行輸入 sync 命令時, 會啓用後臺線程來寫回髒頁, 線程的數量會根據寫回的工做量在2個到8個之間調整. 這些寫回線程是面向髒頁的, 而不是面向後備設備的. 換句話說, 每一個回寫線程都是在認領系統全局範圍內的髒頁來寫回, 而這些髒頁是可能屬於不一樣後備設備的, 因此回寫線程不是專一於某一個設備.

不過隨着時間的推移, 這種看似靈巧的方案暴露出弊端.

1. 由 於每一個回寫線程都是能夠服務全部後備設備的, 在有多個後備設備, 且回寫工做量大時, 線程間的衝突就變得明顯了(畢竟, 一個設備同一時間內只容許一個線程寫回), 當一個設備有線程佔據, 別的線程就得等, 或者去先寫別的設備. 這種衝突對性能是有影響的.
2. 線 程寫回時, 把髒頁組成一個個寫回請求, 掛在設備的請求隊列上, 由設備去處理. 顯然,每一個設備的處理請求能力是有限的, 即隊列長度是有限的. 當一個設備的隊列被線程A佔滿, 新來的線程B就得不到請求位置了. 因爲線程是負責多個設備的, 線程B不能在這設備上乾等, 就先去忙別的, 以期這邊儘快有請求空位空出來. 但若是線程A寫回壓力大, 一直佔着請求隊列不放, 那麼A就及飢餓了, 時間過久就會出問題.

針對這種狀況, 2.6.32爲每一個後備設備引入了專屬的寫回線程[27], 換言之, 如今的寫回線程是面向設備的. 在寫回髒頁時, 系統會根據其所屬的後備設備, 派發給專門的線程去寫回, 從而避免上述問題.

5.3 動態的髒頁生成制和寫回扼算法 3.1(2011年11月發佈), 3.2(2012年1月發佈)

本節一開始說的寫回扼制算法, 其核心就是誰污染誰治理: 生成髒頁多的進程會被懲罰, 讓其中止生產, 責成其進行義務勞動, 把系統髒頁寫回. 在5.1小節裏, 已經解決了這個方案的針對於後備設備門檻的一些問題. 但還存在一些別的問題.

1. 寫回的髒頁的破碎性致使的性能問題. 破碎性這個詞是我造的, 大概的意思是, 因爲被罰的線程是同步寫回髒頁到後備設備上的. 這些髒頁在後備設備上的分佈多是很散亂的, 這就會形成頻繁的磁盤磁頭移動, 對性能影響很是大. 而 Linux 存在的一個塊層(block layer, 倒數第2個子系統會講)原本就是要解決這種問題, 而如今寫回機制是至關於繞過它了.

2. 系統根據當前可用內存情況決定一個髒頁數量門檻, 一到這個門檻值就開始扼制髒頁生成. 這種作法太粗野了點. 有時啓動一個佔內存大的程序(好比一個 kvm), 髒頁門檻就會被急劇下降, 就會致使粗暴的髒頁生成扼制.

3. 從長遠看, 整個系統生成的髒頁數量應該與全部後備設備的寫回能力相一致. 在這個方針指導下, 對於那些過分生產髒頁的進程, 給它一些限制, 扼制其生成髒頁的速度. 內核因而設置了一個定點, 在這個定點之下, 內核對進程生成髒頁的速度不作限制; 但一超過定點就開始粗暴地限制.

3.1, 3.2 版本中, 來自 Intel 中國的吳峯光博士針對上述問題, 引入了動態的髒頁生成扼制和寫回扼制算法[27][28]. 其主要核心就是, 再也不讓受罰的進程同步寫回髒頁了, 而是罰它睡覺; 至於髒頁寫回, 會委派給專門的寫回線程, 這樣就能利用塊層的合併機制, 儘量把磁盤上連續的髒頁合併再寫回, 以減小磁頭的移動時間.

至於標題中的"動態"的概念, 主要有下:

1. 決定受罰者睡多久. 他的算法中, 動態地去估算後備設備的寫回速度, 再結合當前要寫的髒頁量, 從而動態地去決定被罰者要睡的時間.
2. 平緩地修改扼制的門檻. 以前進程被罰的門檻會隨着一個重量級進程的啓動而走人驟降, 在吳峯光的算法中, 增長了對全局內存壓力的評估, 從而平滑地修改這一門檻.
3. 在進程生成髒頁的扼制方面, 吳峯光一樣採起反饋調節的作法, 針對寫回工做量和寫回速度, 平緩地(儘可能)把系統的髒頁生成控制在定點附近.


6 頁面預讀

/* 從用戶角度來看, 這一節對了解 Linux 內核發展幫助不大,可跳過不讀; 但對於技術人員來講, 本節能夠展示教材理論模型到工程實現的一些思考與折衷, 還有軟件工程實踐中由簡單粗糙到複雜精細的演變過程 */

系統在讀取文件頁時, 若是發現存在着順序讀取的模式時, 就會預先把後面的頁也讀進內存, 以期以後的訪問能夠快速地訪問到這些頁, 而不用再啓動快速的磁盤讀寫.

6.1 原始的預讀方案 (時間很早, 未可考)

一開始, 內核的預讀方案如你所想, 很簡單. 就是在內核發覺可能在作順序讀操做時, 就把後面的 128 KB 的頁面也讀進來.

6.2 按需預讀(On-demand Readahead) 2.6.23(2007年10月發佈)

這 種固定的128 KB預讀方案顯然不是最優的. 它沒有考慮系統內存使用情況和進程讀取狀況. 當內存緊張時, 過分的預讀實際上是浪費, 預讀的頁面可能還沒被訪問就被踢出去了. 還有, 進程若是訪問得兇猛的話, 且內存也足夠寬裕的話, 128KB又顯得過小家子氣了.

2.6.23 的內核引入了在這個領域耕耘許久的吳峯光的一個按需預讀的算法[29]. 所謂的按需預讀, 就是內核在讀取某頁不在內存時, 同步把頁從外設讀入內存, 而且, 若是發現是順序讀取的話, 還會把後續若干頁一塊兒讀進來, 這些預讀的頁叫預讀窗口; 當內核讀到預讀窗口裏的某一頁時, 若是發現仍是順序讀取的模式, 會再次啓動預讀, 異步地讀入下一個預讀窗口.

該算法關鍵就在於適當地決定這個預讀窗口的大小,和哪一頁作爲異步預讀的開始. 它的啓發式邏輯也很是簡單, 但取得不了錯的效果. 此外, 對於兩個進程在同一個文件上的交替預讀, 2.6.24 加強了該算法, 使其能很好地偵測這一行爲.


7 大內存頁支持

我 們知道現代操做系統都是以頁面(page)的方式管理內存的. 一開始, 頁面的大小就是4K, 在那個時代, 這是一個至關大的數目了. 因此衆多操做系統, 包括 Linux , 深深植根其中的就是一個頁面是4K大小這種認知, 儘管現代的CPU已經支持更大尺寸的頁面(X86體系能支持2MB, 1GB).

咱們知道虛擬地址的翻譯要通過頁表的翻譯, CPU爲了支持快速的翻譯操做, 引入了TLB的概念, 它本質就是一個頁表翻譯地址結果的緩存, 每次頁表翻譯後的結果會緩存其中, 下一次翻譯時會優先查看TLB, 若是存在, 則稱爲TLB hit; 不然稱爲TLB miss, 就要從訪問內存, 從頁表中翻譯. 因爲這是一個CPU內機構, 決定了它的尺寸是有限的, 好在因爲程序的局部性原理, TLB 中緩存的結果很大可能又會在近期使用.

但 是, 過去幾十年, 物理內存的大小翻了幾番, 但 TLB 空間依然侷限, 4KB大小的頁面就顯得捉襟見肘了. 當運行內存需求量大的程序時, 這樣就存在更大的機率出現 TLB miss, 從而須要訪問內存進入頁表翻譯. 此外, 訪問更多的內存, 意味着更多的缺頁中斷. 這兩方面, 都對程序性能有着顯著的影響.

7.1 HUGETLB支持 (2.6前引入)

若是能使用更大的頁 面, 則能很好地解決上述問題. 試想若是使用2MB的頁(一個頁至關於512個連續的4KB 頁面), 則所需的 TLB 表項由原來的 512個變成1個, 這將大大提升 TLB hit 的機率; 缺頁中斷也由原來的512次變爲1次, 這對性能的提高是不言而喻的.

然而, 前面也說了 Linux 對4KB大小的頁面認知是根植其中的, 想要支持更大的頁面, 須要對很是多的核心的代碼進行大改動, 這是不現實的. 因而, 爲了支持大頁面, 有了一個所謂 HUGETLB 的實現.

它的實現是在系統啓動時, 按照用戶指定需求的最大大頁個數, 每一個頁的大小. 預留如此多個數的大. . 用戶在程序中能夠使用 mmap() 系統調用或共享內存的方式訪問這些大頁, 例子網上不少, 或者參考官方文檔:hugetlbpage.txt [LWN.net] . 固然, 如今也存在一些用戶態工具, 能夠幫助用戶更便捷地使用. 具體可參考此文章: Huge pages part 2: Interfaces [LWN.net]

這一功能的主要使用者是數據庫程序.

7.2 透明大頁的支持 2.6.38(2011年3月發佈)

7.1 介紹的這種使用大頁的方式看起來是挺怪異的, 須要用戶程序作出不少修改. 並且, 內部實現中, 也須要系統預留一大部份內存. 基於此, 2.6.38 引入了一種叫透明大頁的實現[30]. 如其名字所示, 這種大頁的支持對用戶程序來講是透明的.

它的實現原理以下. 在缺頁中斷中, 內核會嘗試分配一個大頁, 若是失敗(好比找不到這麼大一片連續物理頁面), 就仍是回退到以前的作法: 分配一個小頁. 在系統內存緊張須要交換出頁面時, 因爲前面所說的根植內核的4KB頁面大小的因, MM 會透明地把大頁分割成小頁再交換出去.

用戶態程序如今能夠完成無修改就使用大頁支持了. 用戶還能夠經過 madvice() 系統調用給予內核指示, 優化內核對大頁的使用. 好比, 指示內核告知其你但願進程空間的某部分要使用大頁支持, 內核會盡量地知足你.

8 內存控制組(Memory Cgroup)支持 2.6.25(2008年4月發佈)

在 Linux輕量級虛擬化的實現 container 中(好比如今挺火的Docker, 就是基於container), 一個重要的功能就是作資源隔離. Linux 在 2.6.24中引入了cgroup(control group, 控制組)的資源隔離基礎框架(將在最後一個部分詳述), 提供了資源隔離的基礎.

在2.6.25 中, 內核在此基礎上支持了內存資源隔離, 叫內存控制組. 它使用能夠在不一樣的控制組中, 實施內存資源控制, 如分配, 內存用量, 交換等方面的控制.

9 內存熱插拔支持

內存熱插拔, 也許對於第一次據說的人來講, 以爲難以想象: 一個系統的核心組件, 爲什麼要支持熱插拔? 用處有如下幾點.

1. 大規模集羣中, 動態的物理容量增減, 能夠實現更好地支持資源集約和均衡.
2. 大規模集羣中, 物理內存出錯的機會大大增多, 內存熱插拔技術對提升高可用性相當重要.
3. 在虛擬化環境中, 客戶機(Guest OS)間的高效內存使用也對熱插拔技術提出要求

固然, 做爲一個核心組件, 內存的熱插拔對從系統固件,到軟件(操做系統)的要求, 跟普通的外設熱插拔的要求, 不可同日而語. 這也是爲何 Linux 內核對內存熱插拔的徹底支持一直到近兩年才基本完成.

總的來講, 內存熱插拔分爲兩個階段, 即物理熱插拔階段邏輯熱插拔階段:

物理熱插拔階段: 這一階段是內存條插入/拔出主板的過程. 這一過程必需要涉及到固件的支持(如 ACPI 的支持), 以及內核的相關支持, 如爲新插入的內存分配管理元數據進行管理. 咱們能夠把這一階段分別稱爲 hot-add / hot-remove.
邏輯熱插拔階段: 這一階段是從使用者視角, 啓用/關閉這部份內存. 這部分的主要從內存分配器方面作的一些準備工做. 咱們能夠把這一階段分別稱爲 online / offline.

邏 輯上來說, 內存插和拔是一個互爲逆操做, 內核應該作的事是對稱的, 可是, 拔的過程須要關注的技術難點卻比插的過程多, 由於, 從無到有容易, 從有到無麻煩:在使用的內存頁應該被妥善安置, 就如同安置拆遷戶同樣, 這是一件棘手的事情. 因此內核對 hot-remove 的徹底支持一直推遲到 2013 年.[31]

9.1 內存熱插入支持 2.6.15(2006年1月發佈)

這提供了最基本的內存熱插入支持(包括物理/邏輯階段). 注意, 此時熱拔除還不支持.

9.2 初步的內存邏輯熱拔除支持 2.6.24(2008年1月發佈)

此版本提供了部分的邏輯熱拔除階段的支持, 即 offline. Offline 時, 內核會把相關的部份內存隔離開來, 使得該部份內存不可被其餘任何人使用, 而後再把此部份內存頁, 用前面章節說過的內存頁遷移功能轉移到別的內存上. 之因此說部分支持, 是由於該工做只提供了一個 offline 的功能. 可是, 不是全部的內存頁均可以遷移的. 考慮"遷移"二字的含義, 這意味着物理內存地址會變化, 而內存熱拔除應該是對使用者透明的, 這意味着, 用戶見到的虛擬內存地址不 能變化, 因此這中間必須存在一種機制, 能夠修改這種因遷移而引發的映射變化. 全部經過頁表訪問的內存頁就沒問題了, 只要修改頁表映射便可; 可是, 內核本身用的內存, 因爲是繞過尋常的逐級頁表機制, 採用直接映射(提升了效率), 內核內存頁的虛擬地址會隨着物理內存地址變更, 所以, 這部份內存頁是沒法輕易遷移的. 因此說, 此版本的邏輯熱拔除功能只是部分完成.

注意, 此版本中, 物理熱拔除是還徹底未實現. 一句話, 此版本的熱拔除功能還不能用.

9.3 完善的內存邏輯熱拔除支持 3.8(2013年2月發佈)

針對9.2中的問題, 此版本引入了一個解決方案. 9.2 中的核心問題在於不可遷移的頁會致使內存沒法被拔除.解 決問題的思路是使可能被熱拔除的內存不包含這種不可遷移的頁. 這種信息應該在內存初始化/內存插入時被傳達, 因此, 此版本中, 引入一個 movable_node 的概念. 在此概念中, 一個被 movable_node 節點的全部內存, 在初始化/插入後, 內核確保它們之上不會被分配有不可遷移的頁, 因此當熱拔除需求到來時, 上面的內存頁均可以被遷移, 從而使內存能夠被拔除.

9.4 物理熱拔除的支持 3.9(2013年4月支持)

此版本支持了物理熱拔除, 這包括對內存管理元數據的刪除, 跟固件(如ACPI) 相關功能的實現等比較底層瑣碎的細節, 不詳談.

在完成這一步以後, 內核已經能夠提供基本的內存熱插拔支持. 值得一提的是, 內存熱插拔的工做, Fujitsu 中國這邊的內核開放者貢獻了不少 patch. 謝謝他們!

10 超然內存(Transcendent Memory)支持

超然內存(Transcendent Memory), 對不少第一次聽見這個概念的人來講, 是如此的奇怪和陌生. 這個概念是在 Linux 內核開發者社區中首次被提出的[32]. 超然內存(後文一概簡稱爲tmem)之於普通內存, 不一樣之處在於如下幾點:

1 tmem 的大小對內核來講是未知的, 它甚至是可變的; 與之相比, 普通內存在系統初始化就可被內核探測並枚舉, 而且大小是固定的(不考慮熱插拔情形).
2 tmem 能夠是穩定的, 也能夠是不穩定的, 對於後者, 這意味着, 放入其中的數據, 在以後的訪問中可能發現不見了; 與之相比, 普通內存在系統加電狀態下一直是穩定的, 你不用擔憂寫入的數據以後訪問不到(不考慮內存硬件故障問題).
3 基於以上兩個緣由, tmem 沒法被內核直接訪問, 必須經過定義良好的 API 來訪問 tmem 中的內存; 與之相比, 普通內存能夠經過內存地址被內核直接訪問.

初看之下, tmem 這三點奇異的特性, 彷佛增長了沒必要要的複雜性, 尤爲是第二條, 更是詭異無用處.
計算機界有句名言: 計算機科學領域的任何問題均可以經過增長一個間接的中間層來解決. tmem 增長的這些間接特性正是爲了解決某些問題.

考 慮虛擬化環境下, 虛擬機管理器(hypervisor) 須要管理維護各個虛擬客戶機(Guest OS)的內存使用. 在常見的使用環境中, 咱們新建一臺虛擬機時, 總要事先配置其可用的內存大小. 然而這並不十分明智, 咱們很難事先確切知道每臺虛擬機的內存需求狀況, 這不免形成有些虛擬機內存富餘, 而有些則捉襟見肘. 若是能平衡這些資源就行了. 事實上, 存在這樣的技術, hypervisor 維護一個內存池子, 其中存放的就是這些所謂的 tmem, 它能動態地平衡各個虛擬機的盈餘內存, 更高效地使用. 這是 tmem 概念提出的最初來由.

再考慮另外一種情形, 當一臺機器內存使用緊張時, 它會把一些精心選擇的內存頁面寫到交換外設中以騰出空間. 然而外設與內存存在着顯著的讀寫速度差別, 這對性能是一個不利的影響. 若是在一個超高速網絡相連的集羣中, 網絡訪問速度比外設訪問速度快, 可不能夠有別的想法? tmem 又能夠扮演一個重要的中間角色, 它能夠把集羣中全部節點的內存管理在一個池子裏, 從而動態平衡各節點的內存使用, 當某一節點內存緊張時, 寫到交換設備頁實際上是被寫到另外一節點的內存裏...

還有一種情形, 旨在提升內存使用效率.. 好比大量內容相同的頁面(全0頁面)在池子裏能夠只保留一份; 或者, tmem 能夠考慮對這些頁面進行壓縮, 從而增長有效內存的使用.

Linux 內核 從 3.X 系列開始陸續加入 tmem 相關的基礎設施支持, 並逐步加入了關於內存壓縮的功能. 進一步討論內核中的實現前, 須要對這一問題再進一步細化, 以方便討論細節.

前文說了內核須要經過 API 訪問 tmem, 那麼進一步, 能夠細化爲兩個問題.

1. 內核的哪些部份內存能夠被 tmem 管理呢? 有兩大類, 即前面提到過的文件緩存頁和匿名頁.
2. tmem 如何管理其池子中的內存. 針對前述三種情形, 有不一樣的策略. Linux 如今的主要解決方案是針對內存壓縮, 提升內存使用效率.

針對這兩個問題, 能夠把內核對於 tmem 的支持分別分爲前端後端. 前端是內核與 tmem 通信的接口; 然後端則實現 tmem 的管理策略.

10.1 前端接口之 CLEANCACHE 3.0(2011年7月發佈)

前 面章節提到過, 內核會利用空閒內存緩存後備外設中的內容, 以期在近期將要使用時不用從緩慢的外設讀取. 這些內容叫文件緩存頁. 它的特色就是隻要是頁是乾淨的(沒有被寫過), 那麼在系統須要內存時, 隨時能夠直接丟棄這些頁面以騰出空間(由於它隨時能夠從後備文件系統中讀取).

然而, 下次內核須要這些文件緩存頁時, 又得從外設讀取. 這時, tmem 能夠起做用了. 內核丟棄時,假如這些頁面被 tmem 接收並管理起來, 等內核須要的時候, tmem 把它歸還, 這樣就省去了讀寫磁盤的操做, 提升了性能. 3.0 引入的 CLEANCACHE, 做爲 tmem 前端接口, hook 進了內核丟棄這些乾淨文件頁的地方, 把這些頁面截獲了, 放進 tmem 後端管理(後方講). 從內核角度看, 這個 CLEANCACHE 就像一個魔盒的入口, 它丟棄的頁面被吸入這個魔盒, 在它須要時, 內核嘗試從這個魔盒中找, 若是找獲得, 搞定; 不然, 它再去外設讀取. 至於爲什麼被吸入魔盒的頁面爲何會找不到, 這跟前文說過的 tmem 的特性有關. 後文講 tmem 後端時再說.

10.2 前端接口之 FRONTSWAP 3.5(2012年7月發佈)

除 了文件緩存頁, 另外一大類內存頁面就是匿名頁. 在系統內存緊張時, 內核必需要把這些頁面寫出到外設的交換設備或交換分區中, 而不能簡單丟棄(由於這些頁面沒有後備文件系統). 一樣, 涉及到讀寫外設, 又有性能考量了, 此時又是 tmem 起做用的時候了.

同 樣, FRONTSWAP 這個前端接口, 正如其名字同樣, 在 內核 swap 路徑的前面, 截獲了這些頁面, 放入 tmem 後端管理. 若是後端還有空閒資源的話, 這些頁面被接收, 在內核須要這些頁面時, 再把它們吐出來; 若是後端沒有空閒資源了, 那麼內核仍是會把這些頁面按原來的走 swap 路徑寫到交換設備中.

10.3 後端之 ZCACHE (沒能進入內核主線)

講完了兩個前端接口, 接下來講後端的管理策略. 對應於 CLEANCACHE, 一開始是有一個專門的後端叫 zcache[34], 不過最後被刪除了. 它 的作法就是把這些被內核逐出的文件緩存頁壓縮, 並存放在內存中. 因此, zcache 至關於把內存頁從內存中的一個地方移到另外一個地方, 這乍一看, 感受很奇怪, 但這正是 tmem 的靈活性所在. 它容許後端有不一樣的管理策略, 好比在這個狀況下, 它把內存頁壓縮後仍然放在內存中, 這提升了內存的使用. 固然, 畢竟 zcache 會佔用一部分物理內存, 致使可用的內存減少. 所以, 這須要有一個權衡. 高效(壓縮比, 壓縮時間)的壓縮算法的使用, 從而使更多的文件頁待在內存中, 使得其帶來的避免磁盤讀寫的優點大於減小的這部份內存的代價. 不過, 也由於如此, 它的實現過於複雜, 以致最終沒能進入內核主線. 開發者在開始從新實現一個新的替代品, 不過截止至 4.2 , 尚未看到成果.

10.4後端之 ZRAM 3.14(2014年3月發佈)

FRONTSWAP 對應的一個後端叫 ZRAM. 值得一提的是, 雖然這個後端實如今 3.14 才進入內核主線, 但其實它早在 2.6.33(2010年2月發佈)時就已經進入內核的 staging 分支了, 通過4年的開發優化, 終於成功進入主線. Staging 分支[33] 是在內核源碼中的一個子目錄, 它是一個獨立的分支, 主要維護着獨立的 driver 或文件系統, 這些代碼將來可能也可能不進入主線.

ZRAM 是一個在內存中的塊設備(塊設備相對於字符設備而言, 信息存放於固定大小的塊中, 支持隨機訪問, 磁盤就是典型的塊設備, 更多將在塊層子系統中講), 所以, 內核能夠複用已有的 swap 設備設施, 把這個塊設備格式化爲 swap 設備. 所以, 被交換出去的頁面, 將經過 FRONTSWAP 前端進入到 ZRAM 這個僞 swap 設備中, 並被壓縮存放! 固然, 這個ZRAM 空間有限, 所以, 頁面可能不被 ZRAM 接受. 若是這種情形發生, 內核就回退到用真正的磁盤交換設備.

10.5 後端之 ZSWAP 3.11(2013年9月發佈)

FRONTSWAP 對應的另外一個後端叫 ZSWAP[35]. ZSWAP 的作法其實也是嘗試把內核交換出去的頁面壓縮存放到一個內存池子中. 固然, ZSWAP 空間也是有限的. 但同 ZRAM 不一樣的是, ZSWAP 會智能地把其中一些它認爲近期不會使用的頁面解壓縮, 寫回到真正的磁盤外設中. 所以, 大部分狀況下, 它能避免磁盤寫操做, 這比 ZRAM 不知高明到哪去了.

10.6 一些細節

這 一章基本說完了, 但牽涉到後端, 其實還有一些細節能夠談, 好比對於壓縮的效率的考量, 會影響到後端實現的選擇, 好比不一樣的內存頁面的壓縮效果不一樣(全0頁和某種壓縮文件佔據的內存頁的壓縮效果顯然差距很大)對壓縮算法的選擇; 壓縮後頁面的存放策略也很重要, 由於以上後端都存在特殊狀況要把頁面解壓縮寫回到磁盤外設, 寫回頁面的選擇與頁面的存放策略關係很大. 但從用戶角度講, 以上內容足以, 就很少寫了.

關於 tmem, lwn 上的兩篇文章值得關注技術細節的人一讀:
Transcendent memory in a nutshell [LWN.net]
In-kernel memory compression [LWN.net]

11 非易失性內存 (NVDIMM, Non-Volatile DIMM) 支持

計 算機的存儲層級是一個金字塔體系, 從塔尖到塔基, 訪問速度遞減, 而存儲容量遞增. 從訪問速度考量, 內存(DRAM)與磁盤(HHD)之間, 存在着顯著的差別(可達到10^5級別[38]). 所以, 基於內存的緩存技術一直都是系統軟件或數據庫軟件的重中之重. 即便近些年出現的新興的最快的基於PCIe總線的SSD, 這中間依然存在着鴻溝.

linux內核版本差別

另 一方面, 非可易失性內存也並非新鮮產物. 然而實質要麼是一塊DRAM, 後端加上一塊 NAND FLASH 閃存, 以及一個超級電容, 以在系統斷電時的提供保護; 要麼就是一塊簡單的 NAND FLASH, 提供相似 SSD 同樣的存儲特性. 全部這些, 從訪問速度上看, 都談不上真正的內存, 而且, NAND FLASH 的物理特性, 使其免不了磨損(wear out); 而且在長時間使用後, 存在寫性能降低的問題.

2015年算得上閃存技術革命年. 3D NAND FLASH 技術的創新, 以及 Intel 在醞釀的徹底不一樣於NAND 閃存技術的 3D XPoint 內存, 都將預示着填充上圖這個性能鴻溝的時刻的臨近. 它們不只能提供更在的容量(TB級別), 更快的訪問速度(3D XPoint 按 Intel 說法能提供 ~1000倍快於傳統的 NAND FLASH, 5 - 8倍慢於 DRAM 的訪問速度), 更持久的壽命.

相應的, Linux 內核也在進行相應的功能支持.

11.1 NVDIMM 支持框架: libnvdimm 4.2(2015年8月30日發佈)

2015 年4月發佈的ACPI 6.0規範[39], 定義了NVDIMM Firmware Interface Table (NFIT), 詳細地規定了 NVDIMM 的訪問模式, 接口數據規範等細節. 在 Linux 4.2 中, 內核開始支持一個叫 libnvdimm 的子系統, 它實現了 NFIT 的語義, 提供了對 NVDIMM 兩種基本訪問模式的支持, 一種即內核所稱之的 PMEM 模式, 即把 NVDIMM 設備看成持久性的內存來訪問; 另外一種則提供了塊設備模式的訪問. 開始奠基 Linux 內核對這一新興技術的支持.

11.2 DAX 4.0(2015年4月發佈)

與這一技術相關的還有另一個特性值得一提, 那就是 DAX(Direct Access, 直接訪問, X 無實義, 只是爲了酷).

傳 統的基於磁盤的文件系統, 在被訪問時, 內核總會把頁面經過前面所提的文件緩存頁(page cache)的緩存機制, 把文件系統頁從磁盤中預先加載到內存中, 以提速訪問. 而後, 對於新興的 NVDIMM 設備, 基於它的非易失特性, 內核應該能直接訪問基於此設備之上的文件系統的內容, 它使得這一拷貝到內存的操做變得沒必要要. 4.0 開始引入的 DAX 就是提供這一支持. 截至 4.3, 內核中已經有 XFS, EXT2, EXT4 這幾個文件系統實現這一特性.

12 內存管理調試支持

由 前面所述, 內存管理至關複雜, 代碼量巨大, 而它又是如此重要的一個的子系統, 因此代碼質量也要求很是高. 另外一方面, 系統各個組件都是內存管理子系統的使用者, 而若是缺少合理有效的約束, 不正當的內存使用(如內存泄露, 內存覆寫)都將引發系統的崩潰, 以致於數據損壞. 基於此, 內存管理子系統引入了一些調試支持工具, 方便開發者/用戶追蹤,調試內存管理及內存使用中的問題. 本章介紹內存管理子系統中幾個重要的調試工具.

12.1 頁分配的調試支持 2.5(2003年7月以後發佈)

前 面提到過, 內核本身用的內存, 因爲是繞過尋常的逐級頁表機制, 採用直接映射(提升了效率), 即虛擬地址與頁面實際的物理地址存在着一一線性映射的關係. 另外一方面, 內核使用的內存又是出於各類重要管理目的, 好比驅動, 模塊, 文件系統, 甚至 SLAB 子系統也是構建於頁分配器之上. 以上二個事實意味着, 相鄰的頁, 可能被用於徹底不一樣的目的, 而這兩個頁因爲是直接映射, 它們的虛擬地址也是連續的. 若是某個使用者子系統的編碼有 bug, 那麼它的對其內存頁的寫操做形成對相鄰頁的覆寫可能性至關大, 又或者不當心讀了一個相鄰頁的數據. 這些操做可能不必定立刻引發問題, 而是在以後的某個地方纔觸發, 致使數據損壞乃至系統崩潰.

爲此, 2.5中, 針對 Intel 的 i386 平臺, 內核引入了一個 CONFIG_DEBUG_PAGEALLOC 開關, 它在頁分配的路徑上插入鉤子, 並利用 i386 CPU 能夠對頁屬性進行修改的特性, 經過修改未分配的頁的頁表項的屬性, 把該頁置爲"隱藏". 所以, 一旦不當心訪問該頁(讀或寫), 都將引發處理器的缺頁異常, 內核將進入缺頁處理過程, 於是有了一個能夠檢查捕捉這種內存破壞問題的機會.

在2.6.30中, 又增長了對沒法做處理器級別的頁屬性修改的體系的支持. 對於這種體系, 該特性是將未分配的頁毒化(POISON), 寫入特定模式的值, 於是一旦被無心地訪問(讀或寫), 都將可能在以後的某個時間點被發現. 注意, 這種通用的方法就沒法像前面的有處理器級別支持的方法有馬上捕捉的機會.

固然, 這個特性是有性能代價的, 因此生產系統中可別用哦.

12.2 SLAB 子系統的調試支持

SLAB 做爲一個相對獨立的子模塊, 一直有本身完善的調試支持, 包括有:

- 對已分配對象寫的邊界超出的檢查
- 對未初始化對象寫的檢查
- 對內存泄漏或屢次釋放的檢查
- 對上一次分配者進行記錄的支持等

12.3 錯誤注入機制 2.6.20(2007年2月發佈)

內 核有着極強的健壯性, 能對各類錯誤異常狀況進行合理的處理. 然而, 畢竟有些錯誤實在是極小可能發生, 測試對這種小几率異常狀況的處理的代碼實在是不方便. 因此, 2.6.20中內核引入了錯誤注入機制, 其中跟 MM 相關的有兩個, 一個是對頁分配器的失敗注入, 一個是對 SLAB 對象分配器的失敗注入. 這兩個注入機制, 能夠觸發內存分配失敗, 以測試極端狀況下(如內存不足)系統的處理狀況.

12.4 KMEMCHECK - 內存非法訪問檢測工具 2.6.31(2009年9月發佈)

對 內存的非法訪問, 如訪問未分配的內存, 或訪問分配了但未初始化的內存, 或訪問了已釋放了的內存, 會引發不少讓人頭痛的問題, 好比程序因數據損壞而在某個地方莫名崩潰, 排查很是困難. 在用戶態內存檢測工具 valgrind 中, 有一個 Memcheck 插件能夠檢測此類問題. 2.6.31, Linux 內核也引進了內核態的對應工具, 叫 KMEMCHECK.

它是一個內核態工具, 檢測的是內核態的內存訪問. 主要針對如下問題:

1. 對已分配的但未初始化的頁面的訪問
2. 對 SLAB 系統中未分配的對象的訪問
3. 對 SLAB 系統中已釋放的對象的訪問

爲了實現該功能, 內核引入一個叫影子頁(shadow page)的概念, 與被檢測的正常數據頁一一相對. 這也意味着啓用該功能, 不只有速度開銷, 還有很大的內存開銷.

在分配要被追蹤的數據頁的同時, 內核還會分配等量的影子頁, 並經過數據頁的管理數據結構中的一個 shadow 字段指向該影子頁. 分配後, 數據頁的頁表中的 present 標記會被清除, 而且標記爲被 KMEMCHECK 跟蹤. 在第一次訪問時, 因爲 present 標 記被清除, 將觸發缺頁異常. 在缺頁異常處理程序中, 內核將會檢查這次訪問是否是正常. 若是發生上述的非法訪問, 內核將會記錄下該地址, 錯誤類型, 寄存器, 和棧的回溯, 並根據配置的值大小, 把該地址附近的數據頁和影子頁的對應內容, 一併保存到一個緩衝區中. 並設定一個稍後處理的軟中斷任務, 把這些內容報告給上層. 全部這些以後, 將 CPU 標誌寄存器 TF (Trap Flag)置位, 因而在缺頁異常後, CPU 又能從新訪問這條指令, 走正常的執行流程.

這裏面有個問題是, present 標記在第一次缺頁異常後將被置位, 以後若是再次訪問, KMEMCHECK 如何再次利用該機制來檢測呢? 答案是內核在上述處理後還打開了單步調試功能, 因此 CPU 接着執行下一條指令前, 又陷入了調試陷阱(debug trap, 詳情請查看 CPU 文檔), 在處理程序中, 內核又會把該頁的 present 標記會清除.

12.4 KMEMLEAK - 內存泄漏檢測工具 2.6.31(2009年9月發佈)

內存漏洞一直是 C 語言用戶面臨的一個問題, 內核開發也不例外. 2.6.31 中, 內核引入了 KMEMLEAK 工具[41], 用以檢測內存泄漏. 它採用了標記-清除的垃圾收集算法, 對經過 SLAB 子系統分配的對象, 或經過 vmalloc 接 口分配的連續虛擬地址的對象, 或分配的per-CPU對象(per-CPU對象是指每一個 CPU 有一份拷貝的全局對象, 每一個 CPU 訪問修改本地拷貝, 以提升性能)進行追蹤, 把指向對象起始的指針, 對象大小, 分配時的棧蹤影(stack trace) 保存在一個紅黑樹裏(便於以後的查找, 同時還會把對象加入一個全局鏈表中). 以後, KMEMLEAK 會啓動一個每10分鐘運行一次的內核線程, 或在用戶的指令下, 對整個內存進行掃描. 若是某個對象從其起始地址到終末地址內沒有別的指針指向它, 那麼該對象就被當成是泄漏了. KMEMLEAK 會把相關信息報告給用戶.

掃描的大體算法以下:

1. 首先會把全局鏈表中的對象加入一個所謂的白名單中, 這是全部待查對象. 而後 , 依次掃描數據區(data段, bss段), per-CPU區, 還有針對每一個 NUMA 節點的全部頁. 另外, 若是用戶有指定, 還會描掃全部線程的棧區(之因此這個不是強制掃描, 是由於棧區是函數的活動記錄, 變更迅速, 引用可能稍縱即逝). 掃描過程當中, 與以前存的紅黑樹中的對象進行比對, 一旦發現有指針指向紅黑樹中的對象, 說明該對象仍有人引用 , 沒被泄漏, 把它加入一個所謂的灰名單中.
2. 而後, 再掃描一遍灰名單, 即已經被確認有引用的對象, 找出這些對象可能引用的別的全部對象, 也加入灰名單中.
3. 最後剩下的, 在白名單中的, 就是被 KMEMLEAK 認爲是泄漏了的對象.

因爲內存的引用狀況各異, 存在不少特殊狀況, 可能存在誤報或漏報的狀況, 因此 KMEMLEAK 還提供了一些接口, 方便使用者告知 KMEMLEAK 某些對象不是泄露, 某些對象不用檢查,等等.

這個工具固然也存在着顯著的影響系統性能的問題, 因此也只是做爲調試使用.

12.5 KASan - 內核地址淨化器 4.0(2015年4月發佈)

4.0引入的這個工具[42]能夠看做是 KMEMCHECK 工具的替代器, 它的目的也是爲了檢測諸如釋放後訪問(use-after-free), 訪問越界(out-of-bouds)等非法訪問問題. 它比後者更快, 由於它利用了編譯器的 Instrument 功能, 也即編譯器會在訪問內存前插入探針, 執行用戶指定的操做, 這一般用在性能剖析中. 在內存的使用上, KASan 也比 KMEMCHECK 有優點: 相比後者1:1的內存使用 , 它只要1:1/8.

總 的來講, 它利用了 GCC 5.0的新特性, 可對內核內存進行 Instrumentaion, 編譯器能夠在訪問內存前插入指令, 從而檢測該次訪問是否合法. 對比前述的 KMEMCHECK 要用到 CPU 的陷阱指令處理和單步調試功能, KASan 是在編譯時加入了探針, 所以它的性能更快.

13 雜項

這是最後一章, 講幾個 Linux 內核實現的功能, 它不是核心的內存管理所須要的, 但有了他們, 使得 Linux 內核更強大, 更好用.

13.1 KSM - 內存去重 2.6.32(2009年12月發佈)

KSM(Kernel SamePage Merging), 內存同頁合併, 又稱內存去重, 是一個提升內存頁使用效率的方法. 現代操做系統已經使用了很多共享內存的技術, 好比共享庫, 建立新進程時子進程共享父進程地址空間,等.

========== 內存管理子系統 結束分割線 ==========

  • 時間子系統(timer)
  • 中斷子系統(interrupt)
  • 同步機制子系統(synchronization)
  • 塊層(block layer)
  • 文件子系統(Linux 通用文件系統層 VFS, various fs)
  • 網絡子系統(networking)
  • 調試和追蹤子系統(debugging, tracing)
  • 虛擬化子系統(kvm)
  • 控制組(cgroup)

---
引用:
[1] Single UNIX Specification
[2] POSIX 關於調度規範的文檔: http://nicolas.navet.eu/publi/SlidesPosixKoblenz.pdf
[3]Towards Linux 2.6
[4]Linux內核發佈模式與開發組織模式(1)
[5] IBM developworks 上有一篇綜述文章,值得一讀 :Linux 調度器發展簡述
[6]CFS group scheduling [LWN.net]
[7]http://lse.sourceforge.net/numa/
[8]CFS bandwidth control [LWN.net]
[9]kernel/git/torvalds/linux.git
[10]DMA模式_百度百科
[11]進程的虛擬地址和內核中的虛擬地址有什麼關係? - 詹健宇的回答
[12]Physical Page Allocation
[13]The SLUB allocator [LWN.net]
[14]Lumpy Reclaim V3 [LWN.net]
[15]Group pages of related mobility together to reduce external fragmentation v28 [LWN.net]
[16]Memory compaction [LWN.net]
[17]kernel 3.10內核源碼分析--TLB相關--TLB概念、flush、TLB lazy模式-humjb_1983-ChinaUnix博客
[18]Toward improved page replacement [LWN.net]
[19]kernel/git/torvalds/linux.git
[20]The state of the pageout scalability patches [LWN.net]
[21]kernel/git/torvalds/linux.git
[22]Being nicer to executable pages [LWN.net]
[23]kernel/git/torvalds/linux.git
[24]Better active/inactive list balancing [LWN.net]
[25]Smarter write throttling [LWN.net]
[26]https://zh.wikipedia.org/wiki/%E6%8C%87%E6%95%B0%E8%A1%B0%E5%87%8F
[27]Flushing out pdflush [LWN.net]
[28]Dynamic writeback throttling [LWN.net]
[29]On-demand readahead [LWN.net]
[30]Transparent huge pages in 2.6.38 [LWN.net]
[31]https://events.linuxfoundation.org/sites/events/files/lcjp13_ishimatsu.pdf
[32]transcendent memory for Linux [LWN.net]
[33]linux kernel monkey log
[34]zcache: a compressed page cache [LWN.net]
[35]The zswap compressed swap cache [LWN.net]
[36]Linux-Kernel Archive: Linux 2.6.0
[37]搶佔支持的引入時間: https://www.kernel.org/pub/linux/kernel/v2.5/ChangeLog-2.5.4
[38]RAM is 100 Thousand Times Faster than Disk for Database Access
[39] http://www.uefi.org/sites/default/files/resources/ACPI_6.0.pdf
[40]Injecting faults into the kernel [LWN.net]
[41]Detecting kernel memory leaks [LWN.net]
[42]The kernel address sanitizer [LWN.net]

---8 更新日誌:
- 2015.9.12
o 完成調度器子系統的初次更新, 從早上10點開始寫,寫了近7小時, 比較累,後面更新得慢的話你們不要怪我(對手指
- 2015.9.19
o 完成內存管理子系統的前4章更新。一樣是寫了一天,內容太多,沒能寫完......
- 2015.9.21
o 完成內存管理子系統的第5章"頁面寫回"的第1小節的更新。
- 2015.9.25
o 更改一些排版和個別文字描述。接下來週末兩天繼續。
- 2015.9.26
o 完成內存管理子系統的第5, 6, 7, 8章的更新。
- 2015.10.14
o 國慶離網10來天, 未更新。 今天完成了內存管理子系統的第9章的更新。
- 2015.10.16
o 完成內存管理子系統的第10章的更新。
- 2015.11.22
o 這個月在出差和休假, 一直未更新.抱歉! 根據知友 @costa 提供的無水印圖片和考證資料, 進行了一些小更新和修正. 特此感謝 !
o 完成內存管理子系統的第11章關於 NVDIMM 內容的更新。
- 2016.1.2
o 中斷許久, 今天完成了內存管理子系統的第11章關於 調試支持內容的更新。

編輯於 昨天 18:01 107 條評論 感謝

收藏 ? 沒有幫助 ? ? 禁止轉載

linux內核版本差別
知乎用戶,RedHat前僱員, Canonical現僱員

貓貓李開希菜菜的飯飯 等人贊同

每個release具體作了什麼改動, 請看這裏:
LinuxVersions
我要開始搬運了:
2.6.39與3.0兩個版本的發佈間隔了64天. 那麼到底發生了什麼?

1. Prominent features 1.1. Btrfs: Automatic defragmentation, scrubbing, performance improvements

Automatic defragmentation

COW (copy-on-write) filesystems have many advantages, but they also have some disadvantages, for example fragmentation. Btrfs lays out the data sequentially when files are written to the disk for first time, but a COW design implies that any subsequent modification to the file must not be written on top of the old data, but be placed in a free block, which will cause fragmentation (RPM databases are a common case of this problem). Aditionally, it suffers the fragmentation problems common to all filesystems.

Btrfs already offers alternatives to fight this problem: First, it supports online defragmentation using the command "btrfs filesystem defragment". Second, it has a mount option, -o nodatacow, that disables COW for data. Now btrfs adds a third option, the -o autodefrag mount option. This mechanism detects small random writes into files and queues them up for an automatic defrag process, so the filesystem will defragment itself while it's used. It isn't suited to virtualization or big database workloads yet, but works well for smaller files such as rpm, SQLite or bdb databases. Code: (commit)

Scrub

"Scrubbing" is the process of checking the integrity of the data in the filesystem. This initial implementation of scrubbing will check the checksums of all the extents in the filesystem. If an error occurs (checksum or IO error), a good copy is searched for. If one is found, the bad copy will be rewritten. Code: (commit 12)

Other improvements

-File creation/deletion speedup: The performance of file creation and deletion on btrfs was very poor. The reason is that for each creation or deletion, btrfs must do a lot of b+ tree insertions, such as inode item, directory name item, directory name index and so on. Now btrfs can do some delayed b+ tree insertions or deletions, which allows to batch these modifications. Microbenchmarks of file creation have been speed up by ~15%, and file deletion by ~20%. Code: (commit)

-Do not flush csum items of unchanged file data: speeds up fsync. A sysbench workload doing "random write + fsync" went from 112.75 requests/sec to 1216 requests/sec. Code: (commit)

-Quasi-round-robin for space allocation in multidevice setups: the chunk allocator currently always allocates space on the devices in the same order. This leads to a very uneven distribution, especially with RAID1 or RAID10 and an uneven number of devices. Now Btrfs always sorts the devices before allocating, and allocates the stripes on the devices with the most available space. Code: (commit)

1.2. sendmmsg(): batching of sendmsg() calls

Recvmsg() and sendmsg() are the syscalls used to receive/send data to the network. In 2.6.33, Linux added recvmmsg(), a syscall that allows to receive in a single call data that would need multiple recvmsg() calls, improving throughput and latency for a number of scenarios. Now, a equivalent sendmmsg() syscall has been added. A microbenchmark saw a 20% improvement in throughput on UDP send and 30% on raw socket send

Code: (commit)

1.3. XEN dom0 support

Finally, Linux has got Xen dom0 support

1.4. Cleancache

Recommended LWN article: Cleancache and Frontswap

Cleancache is an optional feature that can potentially increases page cache performance. It could be described as a memcached-like system, but for cache memory pages. It provides memory storage not directly accessible or addressable by the kernel, and it does not guarantee that the data will not vanish. It can be used by virtualization software to improve memory handling for guests, but it can also be useful to implement things like a compressed cache.

Code: (commit)(commit)

1.5. Berkeley Packet Filter just-in-time filtering

Recommended LWN article: A JIT for packet filters

The Berkeley Packet Filter filtering capabilities, used by tools like libpcap/tcpdump, are normally handled by an interpreter. This release adds a simple JIT that generates native code when filter is loaded in memory (something already done by other OSes, like FreeBSD). Admin need to enable this feature writting "1" to /proc/sys/net/core/bpf_jit_enable

Code: (commit)

1.6. Wake on WLAN support

Wake on Wireless is a feature to allow the system to go into a low-power state (e.g. ACPI S3 suspend) while the wireless NIC remains active and does varying things for the host, e.g. staying connected to an AP or searching for networks. The 802.11 stack has added support for it.

Code: (commit 12)

1.7. Unprivileged ICMP_ECHO messages

Recommended LWN article: ICMP sockets

This release makes it possible to send ICMP_ECHO messages (ping) and receive the corresponding ICMP_ECHOREPLY messages without any special privileges, similar to what is implemented in Mac OS X. In other words, the patch makes it possible to implement setuid-less and CAP_NET_RAW-less /bin/ping. Initially this functionality was written for Linux 2.4.32, but unfortunately it was never made public. The new functionality is disabled by default, and is enabled at bootup by supporting Linux distributions, optionally with restriction to a group or a group range.

Code: (commit)

1.8. setns() syscall: better namespace handling

Recommended LWN article: Namespace file descriptors

Linux supports different namespaces for many of the resources its handles; for example, lightweight forms of virtualization such as containers or systemd-nspaw show to the virtualized processes a virtual PID different from the real PID. The same thing can be done with the filesystem directory structure, network resources, IPC, etc. The only way to set different namespace configurations was using different flags in the clone() syscall, but that system didn't do things like allow to one processes to access to other process' namespace. The setns() syscall solves that problem-

Code: (commit 123456)

1.9. Alarm-timers

Recommended LWN article: Waking systems from suspend

Alarm-timers are a hybrid style timer, similar to high-resolution timers, but when the system is suspended, the RTC device is set to fire and wake the system for when the soonest alarm-timer expires. The concept for Alarm-timers was inspired by the Android Alarm driver, and the interface to userland uses the POSIX clock and timers interface, using two new clockids:CLOCK_REALTIME_ALARM and CLOCK_BOOTTIME_ALARM.

Code: (commit 12)

2. Driver and architecture-specific changes

All the driver and architecture-specific changes can be found in the Linux_3.0_DriverArch page

3. VFS

  • Cache xattr security drop check for write: benchmarking on btrfs showed that a major scaling bottleneck on large systems on btrfs is currently the xattr lookup on every write, which causes an additional tree walk, hitting some per file system locks and quite bad scalability. This is also a problem in ext4, where it hits the global mbcache lock. Caching this check solves the problem (commit)

4. Process scheduler

  • Increase SCHED_LOAD_SCALE resolution: With this extra resolution, the scheduler can handle deeper cgroup hiearchies and do better shares distribution and load balancing on larger systems (especially for low weight task groups) (commit)(commit)
  • Move the second half of ttwu() to the remote CPU: avoids having to take rq->lock and doing the task enqueue remotely, saving lots on cacheline transfers. A semaphore benchmark goes from 647278 worker burns per second to 816715 (commit)
  • Next buddy hint on sleep and preempt path: a worst-case benchmark consisting of 2 tbench client processes with 2 threads each running on a single CPU changed from 105.84 MB/sec to 112.42 MB/sec (commit)

5. Memory management

  • Make mmu_gather preempemtible (commit)
  • Batch activate_page() calls to reduce zone->lru_lock contention (commit)
  • tmpfs: implement generic xattr support (commit)
  • Memory cgroup controller:
    • Add memory.numastat API for NUMA statistics (commit)
    • Add the pagefault count into memcg stats (commit)
    • Reclaim memory from nodes in round-robin order (commit)
    • Remove the deprecated noswapaccount kernel parameter (commit)

6. Networking

  • Allow setting the network namespace by fd (commit)
  • Wireless
    • Add the ability to advertise possible interface combinations (commit)
    • Add support for scheduled scans (commit)
    • Add userspace authentication flag to mesh setup (commit)
    • New notification to discover mesh peer candidates. (commit)
  • Allow ethtool to set interface in loopback mode. (commit)
  • Allow no-cache copy from user on transmit (commit)
  • ipset: SCTP, UDPLITE support added (commit)
  • sctp: implement socket option SCTP_GET_ASSOC_ID_LIST (commit), implement event notification SCTP_SENDER_DRY_EVENT (commit)
  • bridge: allow creating bridge devices with netlink (commit), allow creating/deleting fdb entries via netlink (commit)
  • batman-adv: multi vlan support for bridge loop detection (commit)
  • pkt_sched: QFQ - quick fair queue scheduler (commit)
  • RDMA: Add netlink infrastructure that allows for registration of RDMA clients (commit)

7. File systems

BLOCK LAYER

  • Submit discard bio in batches in blkdev_issue_discard() - makes discarding data faster (commit)

EXT4

CIFS

  • Add support for mounting Windows 2008 DFS shares (commit)
  • Convert cifs_writepages to use async writes (commit)(commit)
  • Add rwpidforward mount option that enables a mode when CIFS forwards pid of a process who opened a file to any read and write operation (commit)

OCFS2

NILFS2

XFS

8. Crypto

  • caam - Add support for the Freescale SEC4/CAAM (commit)
  • padlock - Add SHA-1/256 module for VIA Nano (commit)
  • s390: add System z hardware support for CTR mode (commit), add System z hardware support for GHASH (commit), add System z hardware support for XTS mode (commit)
  • s5p-sss - add S5PV210 advanced crypto engine support (commit)

9. Virtualization

  • User-mode Linux: add earlyprintk support (commit), add ucast Ethernet transport (commit)
  • xen: add blkback support (commit)

10. Security

  • Allow the application of capability limits to usermode helpers (commit)
  • SELinux
    • add /sys/fs/selinux mount point to put selinuxfs (commit)
    • Make SELinux cache VFS RCU walks safe (improves VFS performance) (commit)

11. Tracing/profiling

  • perf stat: Add -d -d and -d -d -d options to show more CPU events (commit)(commit)
  • perf stat: Add --sync/-S option (commit)

12. Various core changes

  • rcu: priority boosting for TREE_PREEMPT_RCU (commit)
  • ulimit: raise default hard ulimit on number of files to 4096 (commit)
  • cgroups
    • remove the Namespace cgroup subsystem. It has been replaced by a compatibility flag 'clone_children', where a newly created cgroup will copy the parent cgroup values. The userspace has to manually create a cgroup and add a task to the 'tasks' file (commit)
    • Make 'procs' file writable (commit)
  • kbuild: implement several W= levels (commit)
  • PM/Hibernate: Add sysfs knob to control size of memory for drivers (commit)
  • posix-timers: RCU conversion (commit)
  • coredump: add support for exe_file in core name (commit)

3.19到4.0間隔了63天, 期間究竟發生了什麼?:

1. Prominent features 1.1. Arbitrary version change

This release increases the version to 4.0. This switch from 3.x to 4.0 version numbers is, however, entirely meaningless and it should not be associated to any important changes in the kernel. This release could have been 3.20, but Linus Torvalds just got tired of the old number, made a poll, and changed it. Yes, it is frivolous. The less you think about it, the better.

1.2. Live patching

This release introduces "livepatch", a feature for live patching the kernel code, aimed primarily at systems who want to get security updates without needing to reboot. This feature has been born as result of merging kgraft and kpatch, two attempts by SuSE and Red Hat that where started to replace the now propietary ksplice. It's relatively simple and minimalistic, as it's making use of existing kernel infrastructure (namely ftrace) as much as possible. It's also self-contained and it doesn't hook itself in any other kernel subsystems.

In this release livepatch is not feature complete, yet it provides a basic infrastructure for function "live patching" (i.e. code redirection), including API for kernel modules containing the actual patches, and API/ABI for userspace to be able to operate on the patches (look up what patches are applied, enable/disable them, etc). Most CVEs should be safe to apply this way. Only the x86 architecture is supported in this release, others will follow.

For more details see the merge commit

Sample live patching module: commit

Code commit

1.3. DAX - Direct Access, for persistent memory storage

Before being read by programs, files are usually first copied from the disk to the kernel caches, kept in RAM. But the possible advent of persistent non-volatile memory that would be also be used as disk changes radically the way the kernel deals with this process: the kernel cache would become unnecesary overhead.

Linux has had, in fact, support for this kind of setups since 2.6.13. But the code wasn't maintaned and only supported ext2. In this release, Linux adds DAX (Direct Access, the X is for eXciting). DAX removes the extra copy incurred by the buffer by performing reads and writes directly to the persistent-memory storage device. For file mappings, the storage device is mapped directly into userspace. Support for ext4 has been added.

Recommended LWN article: Supporting filesystems in persistent memory

Code: commitcommitcommitcommitcommitcommitcommitcommitcommitcommitcommitcommitcommit

1.4. kasan, kernel address sanitizer

Kernel Address sanitizer (KASan) is a dynamic memory error detector. It provides fast and comprehensive solution for finding use-after-free and out-of-bounds bugs. Linux already has the kmemcheck feature, but unlike kmemcheck, KASan uses compile-time instrumentation, which makes it significantly faster than kmemcheck.

The main idea of KASAN is to use shadow memory to record whether each byte of memory is safe to access or not, and use compiler's instrumentation to check the shadow memory on each memory access. Address sanitizer uses 1/8 of the memory addressable in kernel for shadow memory and uses direct mapping with a scale and offset to translate a memory address to its corresponding shadow address.

Code: commitcommitcommitcommitcommit

1.5. "lazytime" option for better update of file timestamps

Unix filesystems keep track of information about files, such as the last time a file was accessed or modified. Keeping track of this information is very expensive, specially the time when a file was accessed ("atime"), which encourages many people to disable it with the mount option "noatime". To alleviate this problem, the "relatime" mount option was added, the atime is only updated if the previous value is earlier than the modification time, or if the file was last accessed more than 24 hours ago. This behaviour, however, breaks some programs that rely on accurate access time tracking to work, and it's also against the POSIX standard.

In this release, Linux adds another alternative: "lazytime". Lazytime causes access, modified and changed time updates to only be made in the cache. The times will only be written to the disk if the inode needs to be updated anyway for some non-time related change, if fsync(), syncfs() or sync() are called, or just before an undeleted inode is evicted from memory. This is POSIX compliant, while at the same time improving the performance.

Recommended LWN article: Introducing lazytime

Code: commitcommitcommit

1.6. Multiple lower layers in overlayfs

In overlayfs, multiple lower layers can now be given using the the colon (":") as a separator character between the directory names. For example:

  • mount -t overlay overlay -olowerdir=/lower1:/lower2:/lower3 /merged

The specified lower directories will be stacked beginning from the rightmost one and going left. In the above example lower1 will be the top, lower2 the middle and lower3 the bottom layer. "upperdir=" and "workdir=" may be omitted, in that case the overlay will be read-only.

Code: commitcommit

1.7. Support Parallel NFS server, default to NFS v4.2

Parallel NFS (pNFS) is a part of the NFS v4.1 standard that allows compute clients to access storage devices directly and in parallel. The pNFS architecture eliminates the scalability and performance issues associated with NFS servers deployed today. This is achieved by the separation of data and metadata, and moving the metadata server out of the data path.

This release adds support for pNFS server, and drivers for the block layout with XFS support to use XFS filesystems as a block layout target, and the flexfiles layout.

Also, in this release the NFS server defaults to NFS v4.2.

Code: commitcommitcommitcommitcommitcommit

1.8. dm-crypt scalability improvements

This release significantly increases the dm-crypt CPU scalability performance thanks to changes that enable effective use of an unbound workqueue across all available CPUs. A large battery of tests were performed to validate these changes, summary of results is available here

Merge: commit

2. File systems

  • XFS
    • Adds support for sys_renameat2() commit
    • Remove deprecated sysctls xfsbufd_centisecs and age_buffer_centisecs commit
  • EXT4
    • Support "readonly" filesystem flag to mark a FS image as read-only, tunable with tune2fs. It prevents the kernel and e2fsprogs from changing the image commit
  • Btrfs
    • Add code to support file creation time commit
  • NFSv4.1
    • Allow parallel LOCK/LOCKU calls commit
    • Allow parallel OPEN/OPEN_DOWNGRADE/CLOSE commit
  • UBIFS
    • Add security.* XATTR support for the UBIFS commit
    • Add xattr support for symlinks commit
  • OCFS2
    • Add a mount option journal_async_commit on ocfs2 filesystem. When this feature is opened, journal commit block can be written to disk without waiting for descriptor blocks, which can improve journal commit performance. Using the fs_mark benchmark, using journal_async_commit shows a 50% improvement commit
    • Currently in case of append O_DIRECT write (block not allocated yet), ocfs2 will fall back to buffered I/O. This has some disadvantages. In this version, the direct I/O write doesn't fallback to buffer I/O write any more because the allocate blocks are enabled in direct I/O now commitcommitcommit
  • F2FS
    • Introduce a batched trim commit
    • Support "norecovery" mount option, which is mostly same as "disable_roll_forward". The only difference is that "norecovery" should be activated with read-only mount option. This can be used when user wants to check whether f2fs is mountable or not without any recovery process commit
    • Add F2FS_IOC_GETVERSION ioctl for getting i_generation from inode, after that, users can list file's generation number by using "lsattr -v commit

3. Block

  • Ported to blk-multiqueue
    • loop: Add blk-mq support, which greatly improves performance for sequential and random reads commit
    • Device-mapper commit
    • rbd commit
    • UBI commit
  • blk-multiqueue: Add support for tag allocation policies and make libata use this blk-mq tagging, instead of rolling their own commitcommit
  • UBI: Implement UBI_METAONLY, a new open mode for UBI volumes, it indicates that only meta data is being changed commit

4. Core (various)

  • pstore: Add pmsg - user-space accessible pstore object commit
  • rcu: Optionally run grace-period kthreads at real-time priority. Recent testing has shown that under heavy load, running RCU's grace-period kthreads at real-time priority can improve performance and reduce the incidence of RCU CPU stall warnings commit
  • GDB scripts for debugging the kernel. If you load vmlinux into gdb with the option enabled, the helper scripts will be automatically imported by gdb as well, and additional functions are available to analyze a Linux kernel instance. See Documentation/gdb-kernel-debugging.txt for further details commit
  • Remove CONFIG_INIT_FALLBACK commit

5. Memory management

  • cgroups: Per memory cgroup slab shrinkers commit
  • slub: optimize memory alloc/free fastpath by removing preemption on/off commit
  • Add KPF_ZERO_PAGE flag for zero_page, so that userspace processes can detect zero_page in /proc/kpageflags, and then do memory analysis more accurately commit
  • Make /dev/mem an optional device commit
  • Add support for resetting peak RSS, which can be retrieved from the VmHWM field in /proc/pid/status, by writing "5" to /proc/pid/clear_refs commit
  • Show page size in /proc//numa_maps as "kernelpagesize_kB" field to help identifying the size of pages that are backing memory areas mapped by a given task. This is specially useful to help differentiating between HUGE and GIGANTIC page backed VMAs commit
  • geneve: Add Geneve GRO support commit
  • zsmalloc: add statistics support commit
  • Incorporate read-only pages into transparent huge pages commit
  • memcontrol cgroup: Introduce the basic control files to account, partition, and limit memory using cgroups in default hierarchy mode. The old interface will be maintained, but a clearer model and improved workload performance should encourage existing users to switch over to the new one eventually commit
  • Replace remap_file_pages() syscall with emulation commit

6. Virtualization

  • KVM: Add generic support for page modification logging, a new feature in Intel "Broadwell" Xeon CPUs that speeds up dirty page tracking commit
  • vfio: Add device request interface indicating that the device should be released commit
  • vmxnet3: Make Rx ring 2 size configurable by adjusting rx-jumbo parameter of ethtool -G commit
  • virtio_net: add software timestamp support commit
  • virtio_pci: modern driver commit, add an options to disable legacy driver commitcommit

7. Cryptography

  • aesni: Add support for 192 & 256 bit keys to AES-NI RFC4106 commit
  • algif_rng: add random number generator support commit
  • octeon: add MD5 module commit
  • qat: add support for CBC(AES) ablkcipher commit

8. Security

  • SELinux : Add security hooks to the Android Binder that enable security modules such as SELinux to implement controls over Binder IPC. The security hooks include support for controlling what process can become the Binder context manager, invoke a binder transaction/IPC to another process, transfer a binder reference to another process , transfer an open file to another process. These hooks have been included in the Android kernel trees since Android 4.3 (commit).
  • SMACK: secmark support for netfilter (commit).
  • TPM 2.0 support (commits: 123).
  • Device class for TPM, sysfs files are moved from /sys/class/misc/tpmX/device/ to /sys/class/tpm/tpmX/device/ (commit).

9. Tracing & perf

  • perf mem: Enable sampling loads and stores simultaneously, it could only do one or the other before yet there was no hardware restriction preventing simultaneous collection commit
  • perf tools: Support parameterized and symbolic events. See links for documentation commitcommit
  • AMD range breakpoints support: breakpoints are extended to support address range through perf event with initial backend support for AMD extended breakpoints. For example set write breakpoint from 0x1000 to 0x1200 (0x1000 + 512): perf record -e mem:0x1000/512:w commitcommit

10. Networking

  • TCP: Add the possibility to define a per route/destination congestion control algorithm. This opens up the possibility for a machine with different links to enforce specific congestion control algorithms with optimal strategies for each of them based on their network characteristics commit
  • Mitigate TCP "ACK loop" DoS scenarios by rate-limiting outgoing duplicate ACKs sent in response to incoming "out of window" segments. For more details, see merge. Code: commitcommitcommitcommit
  • udpv6: Add lockless sendmsg() support, thus allowing multiple threads to send to a single socket more efficiently commit
  • ipv4: Automatically bring up DSA master network devices, which allows DSA slave network devices to be used as valid interfaces for e.g: NFS root booting by allowing kernel IP auto-configuration to succeed on these interfaces commit
  • ipv6: Add sysctl entry(accept_ra_mtu) to disable MTU updates from router advertisements commit
  • vxlan: Implement supports for the Group Policy VXLAN extension to provide a lightweight and simple security label mechanism across network peers based on VXLAN. It allows further mapping to a SELinux context using SECMARK, to implement ACLs directly with nftables, iptables, OVS, tc, etc commit
  • vxlan: Add support for remote checksum offload in VXLAN. It is described herecommit
  • net: openvswitch: Support masked set actions. commit
  • Infiniband: Add support for extensible query device capabilities verb to allow adding new features commit
  • Layer 2 Tunneling Protocol (l2tp): multicast notification to the registered listeners when the tunnels/sessions are created/modified/deleted commit
  • SUNRPC: Set SO_REUSEPORT socket option for TCP connections to bind multiple TCP connections to the same source address+port combination commit
  • tipc: involve namespace infrastructure commit
  • 802.15.4: introduce support for cca settings commit
  • Wireless
    • Add new GCMP, GCMP-256, CCMP-256, BIP-GMAC-128, BIP-GMAC-256, and BIP-CMAC-256 cipher suites. These new cipher suites were defined in IEEE Std 802.11ac-2013 commitcommitcommitcommitcommit
    • New NL80211_ATTR_NETNS_FD which allows to set namespace via nl80211 by fd commit
    • Support per-TID station statistics commit
    • Allow including station info in delete event commitcommit
    • Allow usermode to query wiphy specific regdom commit
  • bridge
    • offload bridge port attributes to switch ASIC if feature flag set commit
    • Support for allowing userspace to pack multiple vlans and VLAN ranges in setlink and dellink requests for improved performance commit
    • Add ability to enable TSO commit
  • Near Field Communication (NFC)
    • HCI over NCI protocol support (Some secure elements only understand HCI and thus we need to send them HCI frames) commit
    • NCI NFCEE (NFC Execution Environment, typically an embedded or external secure element) discovery and enabling/disabling support commitcommitcommitcommitcommitcommitcommit
    • NFC_EVT_TRANSACTION userspace API addition, it is sent through netlink in order for a specific application running on a secure element to notify userspace of an event commit
    • Tx timestamps are looped onto the error queue on top of an skb. This mechanism leaks packet headers to processes unless the no-payload options SOF_TIMESTAMPING_OPT_TSONLY is set. A new sysctl (tstamp_allow_data) optionally drops looped timestamp with data. This only affects processes without CAP_NET_RAW commitcommitcommit
  • Bluetooth
    • Enable LE Data Length Extension feature from Bluetooth 4.2 specification commit
    • Expose information in debugfs: Secure Simple Pairing commit, debug keys usage setting commit, hardware error code commit, remote OOB information commit
    • HCI Read Stored Link Keys commitcommit
    • HCI Delete Stored Link Key commitcommit
    • Support static address when BR/EDR has been disabled commit
  • tc: add BPF-based action. This action provides a possibility to execute custom BPF code commit
  • net: sched: Introduce connmark action commit
  • Add Transparent Ethernet Bridging GRO support commit
  • netdev: introduce new NETIF_F_HW_SWITCH_OFFLOAD feature flag for switch device offloads commit
  • netfilter: nft_compat: add ebtables support commit
    • network namespace: Add rtnl cmd to add and get peer netns ids. A user can define an id for a peer netns by providing a FD or a PID. These ids are local to the netns where it is added (i.e. valid only into this netns) commitcommit
  • openvswitch: Add support for checksums on UDP tunnels. commit
  • openvswitch: Support VXLAN Group Policy extension commit

每個release具體作了什麼改動, 請看這裏:
LinuxVersions

編輯於 2015-09-09 添加評論 感謝

收藏 ? 沒有幫助 ? ? 做者保留權利

linux內核版本差別 淘米,搞TI的IT

2.6各版本的區別也很大的

發佈於 2015-09-10 添加評論 感謝

收藏 ? 沒有幫助 ?

做者保留權利

linux內核版本差別 姚燈燈,Coder~~

namespace和cgroup吧,docker的實現就是基於這些。

發佈於 2015-09-10 添加評論 感謝

收藏 ? 沒有幫助 ?

.zm-item-answer"}" data-init="{"params": {"url_token": 35484429, "pagesize": 20, "offset": 0}, "nodename": "QuestionAnswerListV2"}">

做者保留權利

我來回答這個問題

寫回答…

我要回答

知乎是一個真實的問答社區,在這裏分享知識、經驗和看法,發現更大的世界。
使用手機或郵箱註冊

使用微信登陸 使用微博登陸 使用 QQ 登陸

758 人關注該問題

換一換

相關問題

© 2016 知乎

Similar Posts:

    • Linux內核版本發佈時間整理

      有了這個Linux內核版本發佈時間表(0.00到3.19,固然沒有包含所有的版本), 你們就能夠看看本身用的版本是什麼時候發佈的了! 或許有同窗會大吃一驚,哇(⊙0⊙)我用的版本10年前就有了啊! 發展史我也沒寫全,有時間和必要的話再寫吧! 版本號 時間 發展史 0.00 1991.2-4 兩個進程分別顯示AAABBB 0.01 1991.9 第一個正式向外公佈的Linux內核版本 0.02 1991.10.5 Linux的第一個穩定的工做版本 0.03 1991.10.5 0.10 1991.10

    • Linux內核版本識別

      從嚴格意義上講,Linux只是一個操做系統內核,它的各種發行版纔是真正的操做系統. Linux內核版本: X.Y.Z X是總版本,目前只有1,2,3三個總版本. Y爲奇數表示不穩定版,爲偶數表示穩定版 1994年3月發佈第一個內核1.0.0,1996年6月X從1躍到2,2012年1月從2躍到3 (1) 2.6.x內核 這些版本是基礎穩定版,若是發現了衝突或嚴重瑕疵,以這些版本爲基礎,新的穩定修正版本2.6.x.y就會被髮布出來. (2)2.6.x.y內核 這些版本也是穩定的,它們包含對2.6.x

    • 查詢Linux 內核版本

      查詢Linux 內核版本 今天開始學習 C 編程, 要作一份 Linux 源碼的tag 供查詢分析之用. 1, 首先就要查詢內核的版本,用以以爲當那個版本的源碼. a, > uname -a b, > cat /proc/version Note: /proc/ 目錄下爲內核的映射,這裏的信息都是內核中的,反應的是當前運行系統的信息.也爲外部瞭解系統運行狀態提供了一個接口. uname 也是讀取這裏的文件的. 2, 而後 Load source code from http://www.ker

    • 如何查看Linux內核版本及發行版本

      查看內核版本 1) cat /proc/version [root@a ~]# cat /proc/version Linux version 2.6.18-194.el5 (mockbuild@x86-007.build.bos.redhat.com) (gcc version 4.1.2 20080704 (Red Hat 4.1.2-48)) #1 SMP Tue Mar 16 21:52:43 EDT 2010 2) uname -a [root@a ~]# uname -a Linux

    • linux內核版本命名規則

      Linux內核使用三種不一樣的版本編號方式. 第一種方式用於1.0版本以前(包括1.0).第一個版本是0.01,緊接着是0.02.0.03.0.10.0.11.0.12.0.95.0.96.0.97.0.98.0.99和以後的1.0. 第二種方式用於1.0以後到2.6,數字由三部分"A.B.C",A表明主版本號,B表明次主版本號,C表明較小的末版本號.只有在內核發生很大變化時(歷史上只發生過兩次,1994年的1.0,1996年的2.0),A才變化.能夠經過數字B來判斷Linux是否穩定,

    • 查看Linux內核版本、當前使用系統版本

      1 內核信息 a. cat /proc/version b. uname -a 2 系統版本 a. cat /etc/issue 系統安裝後通常不會改變 b. lsb_release -a 內核有升級會改變 查看Linux系統位數 用這個看一下: file /bin/ls 若是顯示像這樣: /bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.0, dynamically link

    • linux內核版本多一個+號解決方法

      將 scripts/setlocalversion文件中res="$res${scm:++}"注視掉

    • Redhat系列的Linux的內核版本

      Redhat系列的Linux內核版本對照: http://dvpro.tridia.com/linuxKernelsdvpro.html

    • Linux內核設計與實現(chapter1/2)

      Linux內核簡介 Unix從一個失敗的多用戶操做系統Multics中衍生來的. Unix強大的緣由: 簡潔 幾乎全部的東西都被當作文件來對待,能夠經過相同的系統調用接口來進行調用. 由於它是由c語言編寫,因此具備強大的移植能力 進程建立迅速,且擁有獨特的fork()系統調用 具有清晰的層次化 Unix操做系統多樣的功能: 搶佔式多任務 多線程 虛擬內存 換頁 動態連接 TCP/IP網絡 Linux是類Unix系統,但它不是Unix. Linux內核是自由軟件. 操做系統和內核簡介 內核是操做系

    • 《LINUX內核設計與實現》第三週讀書筆記——第一二章

      <Linux內核設計與實現>讀書筆記--第一二章 20135301張忻 估算學習時間:共2小時 讀書:1.5 代碼:0 做業:0 博客:0.5 實際學習時間:共2.5小時 讀書:2.0 代碼:0 做業:0 博客:0.5 耗時估計的公式:Y=X+X/N ,Y=X-X/N 第一章 Linux內核簡介 1.1 Unix的歷史 因爲Unix系統設計簡潔而且在發佈時提供源代碼,因此許多其餘組織和團體都對它進了進一步的開發. Unⅸ雖然已經使用了40年,但計算機科學家仍然認爲它是現存操做系統中最強大和最優

相關文章
相關標籤/搜索