為了忘卻的紀念——2022 Linux 核心十大技術革新功能 | 年終盤點

本文來自 CSDN 重磅策劃的《2022 年技術年度盤點》欄目。2022 年,智慧技術變革留下了深刻的腳印,各行各業數字化升級催生了更多新需求。過去一年,亦是機遇與挑戰並存的一年。

在本篇文章中,長期奮鬥在一線的 Linux 核心開發者、經典書籍《Linux 設備驅動開發詳解》作者宋寶華老師為大家解剖 2022 年 Linux 核心開發的十大革新技術功能,紀念這平凡而又不凡的 Linux 核心之旅。

作者 | 宋寶華 責編 | 夢依丹

滾滾長江東逝水,浪花淘盡英雄。在浩瀚的宇宙星河中,波捲雲詭的 2022 年,縱使無數渺小如塵埃般人類個體的命運伴隨著群體宏大敘事的轉折變遷而風雨飄搖,也只是再平淡不過的一年。很多年以後,人們會忘卻了 2022 年的人和事。然而,Linux 核心在 2022 年發生的重大事件,仍然在若干年後會潛移默化地影響著無數人,時間會消磨一切,唯有文字和程式碼可以永恆。

2023 年的農曆新春即將到來,註定將是一個幾家焰火幾家憂傷的春節,人類的悲歡總是不盡相同。而卑微如筆者,也唯有在一個遙遠的地球角落,寫一些文字來紀念這一年平凡而不凡的 Linux 核心。

這一年,Linus Torvalds 迎來了他生命的第 54 年,好友 dog250 前些天發朋友圈說,Linus 可能最多還能管 Linux 核心 36 年,一些公司甚至以在核心社區提交補丁作為 KPI,所以核心可能會越來越臃腫,36 年後的 Linux,必將成為一坨屎。我勸他說,我們也不一定能再活 36 年,超那份心幹什麼呢?

竊以為,Linux 核心,將以頑強的生命力,活過使用 Linux 的很多碼農,直到下一個革命性作業系統的出現。前提是,Linux 核心仍然總體是技術和社區驅動的,不會成為某一特定商業公司挾天子以令諸侯的籌碼,更不能成為無數人追名逐利的名利場。

2022 年,Linux 核心共發佈了 5.16、5.17、5.18、5.19 和 6.1 五個版本。社區以頑強的驅動力激發核心的活力,海外友商,以及我國華為、阿里、騰訊、位元組等頭部企業的聯合努力,一眾新 feature 加入核心,本文一一盤點 Linux 核心 2022 年十大技術革新。這個榜單,為筆者個人之理解,如有意見雷同,純屬巧合;如有不同意見,則純屬意外。

首先列一下提綱形成一個總體的認識:

  • Multi-generational LRU

  • 核心排程器 cluster 排程支持及 AMD ZEN 的 LLC imbalance

  • 基於 DAMON 的 LRU-lists Sorting

  • 基於 DAMON 以及 per-memcg 的 proactive reclaim

  • maple tree

  • MADV_COLLAPSE、futex_waitv 等 API

  • Memory tiering 的 demotion/promotion 一系列最佳化

  • Rust 和 C11

  • 熱鬧非凡的 BPF

  • 龍芯新架構 LoongArch 引入 5.19 核心主線

故事的大幕正式拉開……

故事的大幕正式拉開......

Multi-generational LRU

Least Recently Used (LRU)是計算機體系架構的一個永恆經典話題,只要cache 比 memory 貴和快,memory 比 disk 貴和快,人們都需要在房價和房子大之間找到一個平衡。Linux 核心,以 LRU 演算法管理記憶體和磁碟 I/O 之間的替換。它把 LRU 分為 File 和 anon 頁,爾後 File 和 anon 頁各自有自己的 active 和 inactive list,活躍的頁面從 inactive 升級到 active list,冷卻的頁面從 active 頁面降級到 inactive list,直至足夠冷卻,排在了 list 的尾部,被核心回收。

這個演算法的主體邏輯維持了多年,雖性能欠佳卻異常堅強。Google 的 Zhao Yu 童鞋,打破固有局勢,向 Linux 核心社區貢獻了 Multi-generational LRU(MGLRU)。

我們來看看 MGLRU 的底層邏輯是什麼。作為一個 LRU 演算法,我們任何時候其實有 2 個方面的訴求:一是選擇出被回收頁面的精準度,被回收的頁面最好是未來較長時間不使用的頁面;二是演算法成本,如果演算法的成本特別高,哪怕回收地特別精準,實際效果也會非常差,不如來個不那麼精準的。

實際上,Linux 核心的 LRU 在演算法成本上可能極高,一個簡單的事實是,現代 CPU 通常會在頁表 Page Table Entry(PTE)中含有一個 reference bit,如果硬體訪問過這一頁,這個 bit 會被硬體自動設置,這個 PTE 對應的 page 會被核心識別為 young(青春年少)。在 LRU 演算法的 active、inactive 變遷,以及頁面回收時,我們是通過掃描 PTE 的 reference bit 來決定頁面是否 young 的。但是,當一個頁面被許多進程共享的時候,比如 100 個進程,我們則需要通過所謂的反向對映技術,找到這 100 個進程的 100 張頁表,來一一讀取 PTE 的 reference bit:

在我們進入記憶體回收路徑時,通常系統的空閒記憶體已經很少。而我們為了進行回收,還需要以上漫長複雜的過程,這個時候我們往往會感受到系統的卡頓。如果我們能夠根據頁面回收時候的成本,進行有針對性的回收決策,則可能進一步最佳化 LRU。比如,典型的,透過 syscall 訪問的檔案 pagecache,可能沒有被任何進程 map,掃描的時候可能就不需要上述的反向對映過程,成本就可能比檔案中被廣泛對映的程式碼段之類低很多。

再者,核心 LRU 演算法,在精度上可能也有問題,比如,file 和 anon 是兩個單獨的 list,我們可以在 file 和 anon 內部進行 page 熱度的排序,但是很難對比和評估 file 與 anon 頁面之間的熱度。

MGLRU 拋棄了 active、inactive 兩種不同類型 list 的概念,取而代之的是 2 個新概念:Generation 和 Tier。

MGLRU 最年輕的一代是 max_seq,其對於 anon 和 file 來說,總是相等的;最老的一代,則區分為:min_seq[anon] 和 min_seq[file]。因為記憶體回收的時候,允許 file 比 anon 更激進地多回收一代,所以 anon 和 file 的 min_seq 不一定完全相等。

MGLRU 為 file 和 anon 頁面維護了 4 代 LRU list,不再有 active 和 inactive 的「類 2 代」概念。通過 min_seq 和 max_seq 的增加來更新代。相關的細節比較複雜,我們用最簡單的圖示來推演下 MGLRU 的工作。

假設我們觀察的起點,min_seq=100,max_seq=103,分別裝有a,b,c,d,e,f,h,i 這 8 個 page:

現在我們把 b 訪問一下,那麼 b 可以被 promote 到 max_seq=103 的一代:

這個時候,我們進行記憶體回收,min_seq=100 的 page a 可能就被回收了,當然,min_seq 就變成了 101。

這個過程一直是動態的,隨著不斷有新的 page 進入,隨著核心記憶體的不斷回收,過了一會,它可能就變成這樣了:

b 和 i 目前還在 max_seq 最新代 108,證明過去有段時間裡面它們被訪問地比較頻繁啊!

MGLRU 可以限制 anon 和 file 的 min_seq 的代差,實際很好地平衡了 anon 和 file 的回收。

前面我們提到,mapped 的 page 的回收成本遠大於僅僅通過 syscall 進入 page cache 的 page。而且通常應用程序對 syscall 的 I/O 可能會精心設計,哪怕 page cache 扔掉再次 load 回來可能也不會太慢。比如讓 syscall I/O 與其他過程非同步並行;但是,對於運行過程中,突然 page fault 發生的 map 頁面的同步讀入,application 通常沒有那麼精心的準備。MGLRU 的另外一個概念 tier,實際是為了虐待 syscall 引入的 page 而善待被對映的 page。Tier 是 generation 內部的概念。代表檔案 I/O 頁的訪問頻度,如果檔案頁被訪問 n 次,則其 tier 為 log2(n)。注意這個次數只在 sys_read、sys_write 的時候統計,故通過 system call 只被 read() 了一次的 page、只 map 訪問的 page,tier 都是 0;通過 syscall 讀寫 2 次的 page,tier 是1。file page 首次通過 syscall read 進入的話,會進入 min_seq。而任何時候,基於 page fault 進入的 file 頁(也就是被 map 到 PTE),都會直接進入 max_seq。

Tier 會基於一個 PID(Proportional–Integral–Derivative Control)反饋系統,根據 workingset 的 refault 情況,將 Tier(n) 的 refault 情況與 Tier(0-1) 的進行比對,計算出一個 tier index 作為參考,當 page 的訪問次數的對數(log)超過這個 tier index,則可被升級到下一代 min_seq + 1(還是不升級到 max_seq 有木有)。MGLRU 之前的核心 LRU,會在頁面第 2 次被 syscall 訪問的時候,activate 一個 page 進入 active,而 Tier 在此 access 路徑上不做這樣的事情,syscall 的 page 僅僅可能在訪問次數頻繁的情況下,升一代而已。

另外,原先核心 LRU 的設計,通常會通過二次嘗試法, mapped 的 page 也是一次訪問進入 inactive,二次訪問再進入 active(除非是程式碼段、或被多個進程同時訪問到了、或之前回收的頁面再進來因 workingset 保護以避免 thrashing,可以一次性升級);這避免了把只訪問一次的 page 提升到 active 難以回收的狀態,可能有些 workload 可能就是把某些記憶體只訪問一次呢?

但是,MGLRU 對於 mapped 的 page 升級,都是一次性升級到 max_seq 最新代。對此,Zhao Yu 同學的解釋是,Google 大量的資料採集顯示,剛訪問的記憶體對於使用者體驗來說是最重要的,這也不無道理。比如你在系統上打開個微信,微信訪問了 mapped 的一些記憶體,這個時候微信是最重要的,直接給他頂到最高代而不是類似原 LRU 先放 inactive(這樣它有更多可能性被替換),可以避免其他後臺的任務需要記憶體的時候,碰巧把微信的 page 替換出去。當然,MGLRU 這樣的「活在當下」的做法,對於特定的 workload 可能確實是有不良的副作用的。

MGLRU 有一個非常有特色的地方,它在通過反向對映查看 PTE 的 reference bit 時,會利用空間局部性原理,look-around 特定 PTE 周圍的幾十個 PTE,比如下圖目標黑色 PTE 周圍的紅色 PTE:

簡單來說,我們既然通過反向對映好不容易找到了那個進程的 PTE,那麼訪問這個 PTE 周圍的 PTE 是比較快的,我們就一起看,看完發現這個周圍的 PTE 是 young 的,則升級它們到 max_seq。簡單來說,你在武漢,你大伯父在上海,你坐高鐵去上海看你大伯父。但是你二伯父正好也是在上海,你肯定把你二伯父也一起看一看。何必不看呢?人已經到上海了,下次再看二伯父,不是還是要通過反向對映,坐高鐵從武漢到上海?這一 look-around 的創意,顯然極大降低了我們前面提到了回收時 rmap 查詢 page young 的開銷。

MGLRU 還有一些有意思的最佳化,比如正向掃描進程頁表進一步利用空間局部性減小 rmap 的開銷;基於時間的 thrashing 阻止(防止 page 反覆回收和因為重新 access 進入 LRU)等。

核心排程器 Cluster 排程支持及 AMD ZEN 的 LLC imbalance

Cluster 排程器,這一由筆者提出和主要參與,海思 Jonathan Cameron,Yicong Yang 和 Intel Tim Chen 等童鞋聯合開發的排程器 feature 合入 2022 年一月發佈的 5.16 核心,被列入為 5.16 「prominent features」之一。

我們簡單地介紹一下這個工作的背景。Linux 核心有較好的 last level cache(通常是 L3 cache)意識,在排程器的負載均衡場景,它可以把任務鋪開到不同的 LLC domain,從而讓任務享有更多的 cache 資源,並減小 cache 之間的 contention。比如系統有 8 個 CPU,每 4 個共享一個 LLC,運行微信和支付寶兩個任務,下面的運行方式可能較為理想,微信和支付寶在 2 個分開的 domain 裡面運行,有利於最大化它們各自能拿到的系統資源。

但是在任務喚醒的場景,則通常希望藉助同 LLC domain 內 cache 的臨近優勢,讓任務喚醒後更平滑獲得 cache 遷移。比如 CPU A 上面的支付寶睡眠了,我們從 CPU B 上某任務喚醒它:

理論上,支付寶繼續在 A 上面跑會比較好,因為睡眠之前的 cache 之類可能還在。但是 A CPU 可能很忙,支付寶在 A 上面跑,可能要等很久,這個時候我們可以優先考慮 B,C,D,它們與 A 鄰近,相對而言的 cache 遷移成本低,而不是考慮把被喚醒的支付寶放置於遙遠的 E,F,G,H。注意,實際的 wake_affine() 處理比這個複雜地多,這只是一個簡化模型。

但是,Linux 的排程器,並不考慮 LLC 之前的可能的 L2 或者中間階段 cache。假設一個系統有 4 個 CPU,全部共享一個 LLC,但是每 2 個共享一個 L2 cache。由於排程器沒有 LLC 以外的 cache 概念,核心會認為 4 個 CPU 對等,則負載均衡的結果完全可能是:

這樣沒有關聯的支付寶和微信運行在同樣一個 L2 上面的 cache contention 較大。Cluster scheduler,旨在讓排程器意識到 LLC 之前的 cache 級別或其他拓撲級別,從而有針對性地進行負載均衡和 wake_affine 處理。

上面同樣的 4 個 CPU,增加 cluster scheduler 後,負載均衡的結果會是:

Cluster 排程器,會對帶有中間階段 cache/拓撲級別的處理器排程帶來性能提升,典型應用於 ARM Kunpeng920、X86 Jacobsville 等處理器平臺。

同樣基於 cache 拓撲,對排程進行最佳化的,還有合入 5.18 核心的「Better process scheduling performance on AMD Zen」,這一貢獻來自於大名鼎鼎的 Mel Gorman,他調整了同一個 NUMA 節點內,多個 LLC domain 的排程不平衡。如下圖,來自 Intel X86 的處理器通常是一個 NUMA 節點共享一個 LLC 的。

排程器在進行排程的是,任務會在兩個不同的 NUMA 節點間進行負載均衡。從而導致 task 在 NUMA 間遷移。但是,允許兩個 NUMA 間少量的不平衡,則可以避免這種遷移過於頻繁,尤其在負載比較輕的情況下。這類似一個人月薪一萬,另外一個人月薪九千五,給這兩個人搞平均,意義不大,最後兩個人都不開心。月薪一萬和月薪一千的人,搞一搞還是可以的。

AMD ZEN 的體系架構與 Intel 的處理器有點不一樣,它在同一個 NUMA 內部,也是 LLC 有多個的:

比如每 8 個 CPU 一個 LLC(以上拓撲僅為示意圖,與真實 CPU 數量等不一定一致)。這樣 LLC 成為一個比 NUMA 更小的需要考慮不平衡的 domain,因為任務在 LLC 之間遷移的開銷應該也是比較大的。Mel 的提交「sched/fair: Adjust the allowed NUMA imbalance when SD_NUMA spans multiple LLCs」,允許了這種 NUMA 內部,LLC 之間的少量不均衡,從而減小不必要的任務遷移,通過排程最佳化提升了 AMD ZEN 上 workload 的運行性能。

基於 DAMON 的 LRU-lists Sorting

DAMON 是來自 amazon SeongJae Park 童鞋的心血之作,是 Linux 核心的一個 data access monitoring 框架。LRU-lists Sorting 是 DAMON 框架下的一項工作。

Linux 核心的 LRU 演算法,相對來說是缺乏進取心的,是在記憶體相對緊張的時候才開始運行的。前面說的 active、inactive list,基本在記憶體相對寬裕的情況下,沒有人去造訪。我們都清楚,Linux 的記憶體回收是水位驅動的:

達到 low 水位的時候,kswapd 開始非同步回收記憶體;達到 min 水位的時候,進程被堵住進行 direct reclamation 同步回收記憶體。

在回收的時候,我們掃描 inactive list 尾部的一個個 page,但是仍然需要通過反向對映讀這些 page 的 PTE 的 reference bit,如果這些 page 曾經被訪問,我們還是不能回收它(它們可能回到 inactive 或者 active 的頭部);只有紅色的那些老 page,正好由於掃描前沒有被訪問,才被回收。所以,回收成本高,回收效率低。

這是極有可能發生的,考慮到記憶體壓力到來前,LRU 演算法在 Linux 核心基本是一個躺平的狀態。累積到 inactive 尾部的 page 的 PTE,可能由於長期沒有被掃描到,其狀態並不能反映頁面實時的情況。上圖中,掃描了 7 頁,僅回收了 3 頁,這個效率是很低的。

基於 DAMON 的 LRU-lists Sorting 這個項目的 idea,其實是在記憶體壓力到來前,進行進取型地 LRU 排序,先把熱頁找出來,往 LRU 的頭部提升,這樣 LRU 的尾部相對容易是冷頁,從而提升回收效率,比如下圖中,我們得到 5 個被回收的紅球。

DAMON 的 LRU-lists Sorting 毫無疑問提供了一個極好的 idea,但是筆者對它的實際效果是持懷疑態度的。這主要源於,DAMON 採用的是類似大海里找飛機殘骸的方式來尋找熱頁的。這個方式並不靠譜。

DAMON 把一片龐大的物理記憶體區域進行分割,然後在每一片分割的記憶體區域隨機找一個地址去訪問,如果它在這個地址上發現這個頁面的 PTE 是有 reference bit 的,則進一步把這個 PTE 所在的區域進行繼續分割縮小,盯著這個位置看。

簡單來說,有一架飛機在茫茫的太平洋上空炸了,殘骸可能分佈在太平洋某處方圓數十公里的範圍。於是你派出 100 個搜尋隊,然後把太平洋分成 100 塊。每個搜救隊,隨機在自己分得的區域找個點去搜尋,如果它找到了一個小殘骸,它可以進一步圍繞這個殘骸的周圍密集搜尋。這樣把搜尋範圍逐步縮小,找到這架飛機全部的殘骸甚至黑匣子。

這個方式用在核心裡面並不靠譜,原因有二:

  1. 核心的實體地址空間太大了,茫茫太平洋隨機打點,可能要打很多次才能找到一點殘骸;

  2. 由於 Linux 核心普遍採用虛擬地址到實體地址的轉換,核心的實體地址空間不太具備空間局部性。

問題 1 比較好理解,問題 2 理解 MMU 工作原理的童鞋應該很容易想明白。在進程的虛擬地址空間可能存在較好的空間局部性,但是連續的虛擬地址在實體地址上的對映往往是不連續的。所以不能簡單假定你在實體地址 100MB 這個位置找到了一點殘骸,就可以在 100MB 周圍找到其他的殘骸。

有鑑於此,在下基本認為這個工作本身應該是沒有效果,但是它本身給出的想法,卻具有極強的指引意義,指引我們不斷努力求索更好的實現方式。

基於 DAMON 以及 per-memcg 的 proactive reclaim

基於 DAMON 的 proactive reclaim 也是 DAMON 框架下的一項工作,它仍然採用類似前文的大海里找飛機殘骸的原理,進取型地在記憶體壓力到來前回收一部分冷頁,從而減緩記憶體壓力的到來。

類似的工作,還有來自於 Google Shakeel Butt 和 Yosry Ahmed 童鞋的 per-memcg proactive reclaim。這項工作為核心增加一個新的使用者態接口,比如你在特定的 memcg 裡面「echo 10M > memory.reclaim」就可以觸發這個 memcg 裡面的記憶體回收工作。當把靈活性交給使用者態的時候,使用者態可以借用 refault 和 PSI 的反饋機制,靈活地進行回收控制,也可更早地對 LRU 進行排序。

運行 echo “1G” > memory.reclaim 的話,意味著我們期許回收 1G 的記憶體。

這項工作簡單快捷,它的實現也是異常簡單,簡單地調用 try_to_free_mem_cgroup_pages() 去進行 memcg 的 LRU list 掃描和回收:

在下非常認同它的這個簡單快捷,核心只提供機制,把策略交給使用者態。

maple tree

maple tree

下面閃亮登場的是來自 Oracle 大神 Matthew Wilcox 和 Liam Howlett 童鞋的火紅楓葉樹!現實生活中的楓葉樹美地讓人沉醉,核心裡的楓葉樹,程式碼讀地你欲哭無淚,整個 patchset 有 70 個 patch。

核心的進程 VMA 原本保存於一個被修改過的紅黑樹,受到位於 mm_struct 內的 mmap_sem(5.8 後更名為 mmap_lock)保護。mmap_sem 是 Linux 核心臭名昭著的大坑,這個鎖的競爭也成為核心最重要的性能瓶頸之一。歷史上,人們付出了無數的努力來降低這個鎖的競爭,這裡面最著名的莫過於在 page fault 的處理中,採用 SPF(Speculative page faults),儘可能避免去拿 mmap_sem 這個鎖。簡單來說,SPF 採用馬伊琍童鞋「且行且珍惜」的方式,邊走邊看,先不拿 mmap_sem,但是每走一個關鍵步驟,就查詢是否其他地方有並存的 VMA 以及 PTE 頁表修改,如果有這種併發的過程,再考慮讓 page fault 重新 retry,獲取 mmap_sem。比如充滿了這種「是否蒼天變了心」的判斷:

Maple tree 的理念不太一樣,它從維護 VMA 的資料結構層面,進行根本性的革命。Maple tree 是一種 B-tree,支持 lockless 和 RCU 設計,針對 non-overlapping 的 range-based 的業務場景,目前已經替換維護核心 VMA 的 rbtree,因為一個進程的地址空間 mm_struct 顯然包含多個 VMA,而且這些 VMA 是不會 overlapping。

遺憾地是,6.1 採用 maple tree 後的性能提升僅限於 cache miss 的減少。由於 maple tree 的葉子和非葉子節點上,都可以承載較多的分支因子(branch factor),故 maple tree 要比 rbtree 簡短很多,所以更加 cache 友好:

進一步地刪除 mmap_lock、RCU 無鎖化設計,仍然在計劃中,開發目標是:採用 RCU 模式訪問 maple tree,讀者不會堵住寫者,同一時刻只能有一個單一的寫者,讀者在碰到 stale data 的情況下,re-walk 獲取 VMA,我們期待那一天的早日到來。

MADV_COLLAPSE、futex_waitv 等 API

透明大頁(THP)的性能一直因為透明大頁本身生命週期的不確定性而變幻莫測。首先,Linux 核心往往要經過一個 collapse 過程,把小頁塌縮(collapse)為大頁:

這個 collapse 過程是由核心後臺執行緒 khugepaged 來非同步完成的,但是這個過程非常不靠譜:

  1. 何時會 collapse 不知道;

  2. 是否值得 collapse 不太明確,比如你費了吃奶的力氣給我塌縮成了大頁,但是我進程又自己 free 了這個 2MB 記憶體或者 unmap,一切功夫都白費了。

我們能不能把這個事情變地靠譜和具有確定性呢?如果我的使用者進程明確地知道我需要大頁來降低 TLB miss,而且我這個大頁會長期存在,我告訴你現在就給我 collapse 不香嗎?來自 Google 的 Zach OKeefe 童鞋向核心貢獻了 madvise 的 MADV_COLLAPSE API。

MADV_COLLAPSE 的語義比較直接,當我對你這 2MB 進行了 madvise 的暗示後,哪怕這個區域還有頁面沒有駐留,我會給你強制 fault in 或者 swap in(這有點 mlock 和 gup 的味道了);如果你有部分區域還沒有對映,我給你把這部分初始化為 0,整個過程是同步完成的。

這裡,我們回過頭來看前文提到的 memory.reclaim 這個 feature,與 MADV_COLLAPSE 這個 feature 有些驚人的相似,就是核心經常不是萬能的,把靈活性有時候要交給使用者態,作為一個「架構師」,咱架構到了嗎?

來自 collabora 的 André Almeida 童鞋,向核心社區貢獻了一個新的系統調用:futex_waitv(),它實際被號稱為 futex2 工作的一部分。

這個系統調用,允許使用者態一次性等待多個 futex(fast user-space locking )鎖,如果任何一個 futex 鎖 ready 了,阻塞就返回,有那麼一點 select 和 epoll 的味道。這為使用者態的底層鎖的庫實現提供了支撐,模擬了 Windows 的 WaitForMultipleObjects() 行為,它可較好地替代 scalability 不太好的eventfd,從而提升遊戲的幀率。

Memory tiering 的

Memory tiering 的

demotion/promotion一系列最佳化

Memory tiering 是核心開發的熱門領域之一,伺服器為了追求性能、容量、成本之間的平衡(房子要大,房價要便宜),可能會配置分級的記憶體系統:

而那些單獨的 DRAM 以外的 memory,比如 PMEM,可以成為單獨的 cpu-less 的 NUMA 節點。比如如下硬體拓撲下 NUMA2-NUMA3,是兩片單獨的 PMEM。

經過 Alibaba 的 Yang shi、Intel 的 Ying Huang 和 Dave Hansen 等大神童鞋的共同努力,已經可以使得主存中的記憶體在進行回收的時候,也即 shrink_page_list() 的時候被遷移到速度更慢的 PMEM,而不是被回收進行 I/O,這個過程叫做 demotion。簡單地說,目標是熱記憶體在快區(higher tier),冷記憶體在慢區(lower tier)。

Intel 大神 Ying Huang 的另外一個 patchset,合入 5.18 核心的「NUMA balancing: optimize page placement for memory tiering system」,則進一步藉助核心本身 NUMA balancing 時候記憶體在 NUMA Node 遷移的特性,把 PMEM 中的熱頁,遷移到 DRAM。

在原先的 Linux 核心中,左邊 NUMA 節點的 CPU 瘋狂訪問右邊 NUMA 節點的 DRAM 的時候,NUMA Balancing 是可以將右邊 NUMA 節點的記憶體遷移到左邊的:

但是這一 NUMA Balancing 按照不同 NUMA 的記憶體是相同類型而設計,對於如下拓撲工作地並不好:

原先核心的 NUMA Balancing 機制,在 DRAM 的 NUMA node 的剩餘 memory 低於記憶體回收的 high 水位的時候,阻止其他 node 上的記憶體向其遷移。這讓很多 PMEM 上的熱頁向 DRAM 的遷移變地不可能。

Ying Huang 童鞋在核心原有的記憶體回收 high 水位上,增加了一個 wmark_promo 的水位,比 high 水位高。在 DRAM 中,kswapd 回收到更高的 wmark_promo 的水位,把 cold pages 更多地擠壓到 PMEM,從而為 PMEM 中的 cold pages 騰挪到 DRAM 提早騰出空間也避免 DRAM 記憶體被回收機制瘋狂碾壓。為什麼選擇這個方案,在多個方案中如何選擇比對,大神在他的 commit 中描述地非常清晰:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c574bbe917036

6.1 核心合入的來自 IBM Aneesh Kumar K.V 童鞋的「mm/demotion: Memory tiers and demotion」 patchset,則允許把 memory tier 暴露給使用者態,經由使用者態進行配置。這彌補了核心態默認配置的很多不足,比如核心默認認為 cpu-less 的 memory 總是 lower tier,但是實際上它可能是 DRAM-backed 的高速記憶體;找比如默認的 demotion 路徑比較死板,總是 demote 到下一級 tier 路徑最短的 node,但是實際需求可能想要跨 socket demote 到其他更遠的 node 上面去。

Aneesh Kumar K.V 童鞋提供的 patchset,允許使用者透過設備驅動的接口, explicitly 而非 implicitly 配置相關的拓撲和 demotion 路徑。

Rust 和 C 11

Rust 和 C 11

Linux 5.18 核心,決定遷移到 C 11 標準,以代替採用了 N 年的 C 89。C 89 的標準在核心統治了那麼多年沒有動搖,唯一隻缺一根導火索了,而 Jakob Koschel 童鞋的「Proposal for speculative safe list iterator」 patchset 就是這根導火索。

原本 Jakob 只是想修復潛在的不當使用 list_for_each_entry() 而引起的 bug。在核心最常見的 API list_for_each_entry() 的使用場景:

迭代器總是定義在 list_for_each_entry() 之外的,這導致循環迭代完成後,程式碼仍然可能訪問 iterator,從而導致 bug,比如 Jakob 企圖修復的其中一個「bug」就是:

這個可能的「bug」的確有些狗血,哪怕 list_for_each_entry() 的循環沒有查到,也即沒有 break,最終指向 head 的 req 的 req->req 作為一片野記憶體,在各種記憶體排布的機緣巧合下仍然可能會等於 _req?

Jakob 猜中了這個故事的開頭,卻沒有猜中這個故事的結局,他期待他的意中人駕著五色雲彩來拯救他。而 Linus 最終選擇的決定是:我們要潛在地支持讓 iterator 可以在循環內部定義,定義在 loop 內部,外面想訪問也訪問不了,而C 89做不到:

The whole reason this kind of non-speculative bug can happen is that we historically didn’t have C99-style “declare variables in loops”. So list_for_each_entry() – and all the other ones – fundamentally always leaks the last HEAD entry out of the loop, simply because we couldn’t declare the iterator variable in the loop itself.

——Linus Torvalds

在社區另一著名大神 Arnd Bergmann 的提議下,我們看到了最終現在看到的面向 C 11 的遷移。

關於核心支持 Rust,已經有無數的討論,這裡我們不再進行贅述。一些人堅信,Linux 核心支持 Rust 絕不是出於 Linus 的浪漫主義情懷,而更多是現實需要。因為 Rust 提供的生產環境以及程式設計效率遠勝於 C,更加安全且 Rust 和 C 的性能可以做到不相上下。筆者大體認同這個觀點,但 Rust 的浪漫仍然無法解救小碼農現實的困頓。像筆者這樣的類似《中國奇譚》小豬妖的底層打工仔而且是大齡的而言,只有一句話,那就是「命苦啊」。

前些年你們忽悠說,「人生苦短,請用 Python」,我氣踹噓噓地去學 Python;Python 還沒有學會,現在又被迫要學 Rust 了。由於 Linux 還有大量類似筆者這樣的不思進取的碼農阻礙科技的進步,相信 Rust 在最終的產品化和大行其道上,還有不少路要走。

熱鬧非凡的 BPF

熱鬧非凡的 BPF

2022 年的 Linux 社區,無疑充滿了 BPF 的聲音。這年頭,核心碼農嘴上不時時提幾次 BPF,都不好意思出來跟人打招呼了。我們看到,不僅 BPF 本身的底層在完善,各個領域掛 BPF hook 的 patchset 層出不窮。這裡我們舉 2 個栗子。

大神 Tejun Heo 在排程器裡面來深度參與 BPF,通過 BPF 擴展排程類。Tejun Heo 在 2022 年 11 月底,向核心社區發出一組 RFC,定義了一種新的排程類:ext_sched_class,允許排程策略經由 BPF 來實現。他認為,引入這個排程類,將有三方面重大好處:

  1. 快速地探索和部署排程策略的實驗;

  2. 允許使用者構造應用場景特定的排程策略;

  3. 在產線上無中斷地部署新的排程策略。

這些說法,具有相當的合理性,核心排程器,強調設計的通用性,因此很難適配特定的使用者場景。不停地發 patch 對 CFS 等排程策略進行微調,說實話,真地很蛋疼,倒不如把靈活性交給使用者自己來決定,搞贏了算他的本事,搞輸了他也得願賭服輸。RFC 上 cover letter 寫地有理有據,實為我輩學習之楷模:https://lore.kernel.org/lkml/20221130082313.3241517-1-tj@kernel.org/

這篇 cover letter 提到一個有意思的點,在 Meta,人們已經實驗性採用機器學習來決定任務的排程策略。比如,機器學習可以幫忙預測一個 task 是不是很快要 yield CPU 了,如果是,那可以攔截 task 的 migration,因為遷過去只是浪費時間沒必要,不如讓它繼續在本 CPU 上跑到 yield。比如有個人明天就要退休了,你今天還給他換個工作崗位,這就有點智障了,因為換崗位上下文切換熟悉新環境一天都不夠吧?由此可見,由 BPF 實現排程策略,也便利了 AI 的部署,創造了更大的想象空間。

社區還在火熱開發中的 BPF hooks 還有 HID-BPF,HID 是人機接口設備(Human Interface Devices),用於應付 USB 熱插拔的 mice、keyboard 以及 Bluetooth, BLE, I2C, Intel/AMD Sensors 等。

在愛爾蘭都柏林舉行的 LPC 2022 上,來自紅帽的 Benjamin Tissoires 童鞋解釋,透過 BPF,我們可以節省大量的開發時間,比如一個簡單的 HID 「report descriptor」的 quirk,在核心裡面開發需要經過 N 個步驟:識別問題、準備 patch 和測試、重新編譯記憶體、提交補丁到 LKML、patch 的 LMKL 的 review、合入子樹、合入 Linus 的樹、成為 stable 的 branch 或者 backport 到 stable 的 branch、Linux 發行版採用新核心等漫長的過程。但是提供 BPF hook 的能力,就分分鐘就可以搞定和部署了:

HID 中引入 BPF 的其他好處當然還有很多,比如在 hidraw 之外增強 tracing,提供 HID firewall 等。

BPF 為核心的向外衍生提供了無窮的可能性,夢想有多遠,你就可以走多遠,這種可能性甚至超越了子系統的界限。

龍芯新架構 LoongArch 引入 5.19 核心主線

最後壓軸的榜單,榮耀歸於龍芯。陳華才老師等以艱苦卓絕的工作,征服了 Linux 社區,將龍芯新架構 LoongArch 注入了 Linux 核心。這一點,在我國技術時時被人「卡脖子」的今天,尤其顯得彌足珍貴。

LoongArch 是一種完全不同於 Loongson 的體系架構,不同於老的 Loongson採用 MIPS 指令集,LoongArch 則是一種全新的指令集。Loongson- 3A5000 後續較新的 Loongson 晶片,就採用了這種全新的 LoongArch 指令集。

與常見的 RISC-V、ARM 等體系架構類似,LoongArch 包括 general purpose registers (GPRs), floating point registers (FPRs), vector registers (VRs) 以及 control status registers (CSRs)。LoongArch 有 4 個不同的privilege levels (PLVs) :PLV0~PLV3,核心運行於 PLV0,而使用者態運行於 PLV3。

不同於在一個現有的體系架構下增加一個新的 SoC 支持,在 Linux 核心增加一個全新體系架構支持的工作要艱鉅很多,它至少包含但不限於如下工作:

1.開機初始化和啟動

2.進程上下文保存恢復

3.exception 進入退出

4.各種 exception 的處理

5.中斷進入退出

6.中斷遮蔽和恢復 API

7.中斷控制器管理

8.cache、TLB、記憶體管理

9.spinlock、atomic 等的支持

10.系統調用支持

11.串列埠 console

12.電源管理支持

13.調試工具比如 ftrace 等的支持

14.BPF 支持

這是實打實的軟硬結合的程式設計硬功夫,來不得半點偷懶,缺陷程式碼也沒有半點倖存的空間。

到這裡,我們基礎陳述完了 2022 年核心社區這些重要的技術革新。

總結

總結

2022 年的地球局勢詭譎莫測,而我國 Linux 社區仍像一柄利刃一樣,穿透陰霾,展現出無限的生機和活力。

華為的 Linux 核心開發者們,以無與倫比的勤奮和技術實力,多次成為核心開發貢獻榜的大滿貫贏家;郭寒軍、吳峰光等技術領袖為代表的 OpenEuler 核心社區,海納百川。

位元組以段雄春、宋牧春等為代表的火山雲引擎核心開發組,不斷給核心增加新的最佳化;在朋友圈膜拜合影上貴組諸位大神的強大氣場,令人欽佩不已。

阿里巴巴的工程師們,在核心 crypto、DAMON、virtio、erofs、RDMA、riscv、排程器、記憶體管理等模組,不斷做出新的突破。

騰訊的核心開發者們,在 KVM、io_uring、網路、perf 等模組披荊斬棘。

陳華才老師的團隊,將龍芯新架構 LoongArch 引入 5.19 核心主線。

清華的陳渝老師,仍然在低調地向青年們傳授著 OS 核心技術;而陳莉君老師和張國強老師,甚至領導和組織了第一屆全國 BPF 技術大會,眾多廠商和高校帶著他們的項目和研究參會。

秋季由清華大學、Intel、華為、阿里雲、富士通南大、迪捷軟體、騰訊雲、OPPO、字節跳動等企業發起的 2022「中國Linux核心開發者大會」在線上舉行,依舊收穫了粉絲們高漲的熱情、吸引了最廣泛的受眾。

新的一年裡,更期待我敬仰的老一輩核心大神們,李勇、吳峰光、黃瀛、時奎亮、李澤帆等大神童鞋,憑藉自身豐富的國際社區參與和一線工作的經驗,發揮領袖作用,進一步把我們的年青一代核心碼農們引入國際社區,帶到 LPC 上去,帶到其他開源論壇上去,讓更多好的 idea 成為 Linux 核心的程式碼。更期待以陳莉君老師、陳渝老師為領袖的授業力量,以及 Linux 核心之旅、核心工匠、蝸窩科技、Linux 閱碼場、Linux 中國、泰曉科技、奔跑吧等技術社區關注高質量內容,將 Linux 核心的知識傳播與普及,形成百舸爭流、百花齊放的開放學術格局。

親愛的童鞋們,2023 兔年新春的鐘聲即將敲響,滿飲了此杯,忘卻往日的悲傷徘徊,吹起嘹亮的號角,去戰鬥吧。我們戰鬥在排程器的田野,我們戰鬥在性能最佳化的山崗,我們戰鬥在記憶體管理的星辰,我們戰鬥在檔案系統的大海。用我們的核心程式碼,延伸生命的物理長度,成就傳奇。人生雖充滿無窮盡變數,我們雖控制不了苦難,但是我們仍大抵可以掌控自己的程式碼。

作者簡介:

宋寶華,長期的一線 Linux 核心開發者,工作於核心排程器、記憶體管理、ARM/ARM64 arch、設備驅動等領域,向核心提交了數百個補丁;同時也是經典書籍《Linux 設備驅動開發詳解》的作者。

參考文獻

【1】 Using Linux Kernel Memory Tiering

https://stevescargall.com/2022/06/10/using-linux-kernel-memory-tiering/

【2】kernel changelog

https://kernelnewbies.org/Linux_5.16

https://kernelnewbies.org/Linux_5.17

https://kernelnewbies.org/Linux_5.18

https://kernelnewbies.org/Linux_5.19

https://kernelnewbies.org/Linux_6.1

【3】LPC 2022 eBPF & Networking

https://lpc.events/event/16/sessions/131/#all

【4】The Maple Tree

https://lpc.events/event/4/contributions/553/attachments/362/594/2019_LPC_Maple_Tree.pdf

【5】Overview of Memory Reclaim in the Current Upstream Kernel

https://lpc.events/event/11/contributions/896/attachments/793/1493/slides-r2.pdf

相關文章

吳峰光殺進 Linux 核心

吳峰光殺進 Linux 核心

【編者按】吳峰光,Linux 核心守護者,學生時代被同學戲稱為「老神仙」,兩耳不聞窗外事,一心只搞 Linux。吳峰光的 Linux 核心之...

最強 AI ChatGPT 真要取代程式設計師?

最強 AI ChatGPT 真要取代程式設計師?

【CSDN 編者按】ChatGPT 一出,「程式設計師要失業了」、「程式設計師要下崗了」之聲不絕於耳,引得程式設計師們不由得一陣驚慌,最強 ...

狠甩macOS,2022年成Linux桌面元年!

狠甩macOS,2022年成Linux桌面元年!

【CSDN 編者按】開源熱潮一直席捲全球,諸多網際網路大廠也在爭相擁抱開源,Linux 作為一款開源作業系統也愈發受到開發者的喜愛。在今年年...

宮敏把自由軟體和 Linux 帶回中國

宮敏把自由軟體和 Linux 帶回中國

對於宮敏,在中國的開源界以及技術圈內,大家所熟知的是「中國 Linux 第一人」的稱呼,因為他用手提肩背的方式將 Linux 帶回了中國,組...