Hi,您好,歡迎來到西安天任軟件科技有限責任公司!

2023年度盤點|2023年Linux内核十大技術革新功能

發布時間:2024-01-22 14:21:28

2023年,衆多Linux内核開發者仍然在調度器、内存管理、文件系統等領域貢獻着自己的idea和patch,本文從其中選取十個最典型的patchset,進行闡述,它們是(shì):

  1. 基于eBPF的sched_ext調度類擴展

  2. per-VMA lock

  3. NUMA系統上kernel代碼段複制

  4. Large folios/動态大頁

  5. 文件系統large block支持

  6. 基于scope的資源管理

  7. 用代理執行解決優先級反轉(priority inversion)問題

  8. 延後用戶空間臨界區内的搶占

  9. EEVDF調度

  10. BPF通用叠代器

下面我們一一展開。

基于eBPF的sched_ext調度類擴展

這一patchset的開發過程,堪稱神仙打架。對壘的多方,無論是(shì)發patch的還是(shì)review patch的,都是(shì)内核社區的頂流大神,甚至連看客都會北冥神功。他們之間直接的拼殺,刺刀見(jiàn)紅,毫不留情,讓凡人們見(jiàn)識了神仙也有性格,技術和思想的力量可以怎樣無視虛僞和矯情。

sched_ext patchset由社區鼎鼎大名的Tejun Heo發出,他是(shì)Linux内核cgroup、KERNFS、PER-CPU MEMORY ALLOCATOR、WORKQUEUE等的maintainer。

這個patchset——sched: Implement BPF extensible scheduler class

Patchset的實際貢獻還包括來自Google、meta、卡内基梅隆大學等多家主流廠商和科研院校的開發者。該patchset擴展了一個調度class,與之前的CFS、realtime等并行,但(dàn)是(shì)它允許調度行爲被一個BPF程序來實現(xiàn),并聲稱有如下三大好處:

1.讓探索和實驗變地容易: 讓新的調度策略可以快速叠代

2. 定制化調度行爲:爲特定應用定制調度器(這個調度器也許不适用于通用目的)

3. 調度器快速部署: 在産品環境下,非侵入式地修改調度器。

新加入的sched_ext與内核已經存在的stop_sched_class、dl_sched_class、rt_sched_class、fair_sched_class、idle_sched_class是(shì)一種并列關系,任何一個sched_class,都需要實現(xiàn)一系列的callback函數,比如:


Patchset定義了一組可以由eBPF程序實現(xiàn)的callback:

并在内核的sched_ext class的callback中,調用這一組eBPF實現(xiàn)的callback,比如ext sched_class的select_task_rq() callback調用eBPF的select_cpu() callback:

進一步地,由于eBPF程序可以通過maps和userspace交互,實際上,調度行爲也可以在userspace實現(xiàn)了,這讓内核sched_class、eBPF的sched_ext_ops和用戶空間,實現(xiàn)了3位一體的聯動。

比如eBPF中可以pop一個BPF_MAP_TYPE_QUEUE類型的map:

而userspace則可以update_elem相(xiàng)關dispatch進程的pid到這個map:

整個patchset讓Linux内核調度器的維護者Peter Zijlstra(同時也是(shì)ATOMIC INFRASTRUCTURE、CPU HOTPLUG、FUTEX、LKMM、MMU GATHER AND TLB INVALIDATION、Perf等的維護者)所極度反感,在patchset中直接給出了NACK:

他NAK的無疑是(shì)一位大神,當我們回眸特洛伊之戰中兩位偉大英雄阿喀琉斯和赫克托耳的決鬥時刻,最後命運的天平無論便向的是(shì)哪一邊,剩下的都隻有悲壯。

per-VMA lock

如果Linux内核裏面有什麽鎖最臭名昭著,那麽一定是(shì)mmap_sem(後改名爲mmap_lock)。這個鎖位于mm_struct裏面,很顯然它應該是(shì)一個多線(xiàn)程共享的進程級别概念而不應該是(shì)per-VMA的概念:

但(dàn)是(shì)之前我們在page fault中,也是(shì)要拿mmap_sem讀鎖的,因爲我們也不知(zhī)道page fault處理過程中,對應的VMA會不會變化或者甚至消失,所以要和可能寫VMA的人排他。Page fault的處理邏輯實際是(shì):

由于mmap_sem是(shì)整個進程的,而一個進程裏面說不定也有成千上萬的VMA,然後大量的page fault以及其他的VMA的寫操作行爲,相(xiàng)互競争鎖,就導緻大量的競争延遲。其他需要持有寫鎖的地方也是(shì)非常多的,比如:brk、stack expand、munmap、remap_file_pages、exit、madvise、mprotect、mremap、mlock等。

用一個大的mmap_lock把這些寫和page fault的讀進行保護,這固然安全,但(dàn)是(shì)也實在低效。我們假設一個進程有1萬個VMA,然後我們在其中的1個VMA上面進行page fault,其他的9999個VMA消失不消失,變化不變化,跟我這個page fault之間其實是(shì)沒有半毛錢關系的。如果能夠在PF中不去(qù)持有mmap_lock讀鎖,而去(qù)持有一個更細粒度的,隻關心本VMA的鎖,應該是(shì)一個更好的選擇。

在處理page fault的時候,我們隻需要通過持有VMA的lock,來保證這個VMA本身的穩定:

struct vm_area_struct *lock_vma_under_rcu(struct mm_struct *mm,            unsigned long address);

每個VMA裏面實際增加了一個vma_lock(裏面含有一個讀寫鎖):

在page fault裏面我們持有讀鎖,在其他要寫某個VMA的場景,我們要持有寫鎖(前提條件是(shì)我們也必須持有了進程級的mmap_lock):

它的這個實現(xiàn)看起來很奇怪,因爲它拿到了vma->vm_lock->lock後,并不真地會一直拿着,而是(shì)馬上就放(fàng)了up_write,但(dàn)是(shì)它寫了一個vma->vm_lock_seq,把這個vm_lock_seq寫成了vma->vm_mm->mm_lock_seq的,而進程級的mm_lock_seq會在mmap_lock釋放(fàng)的時候自增。

但(dàn)是(shì)拿讀鎖的page fault,則是(shì)在page fault的途中一直hold着vma->vm_lock->lock。lock_vma_under_rcu()會調用vma_start_read():

因爲我們要開始VMA寫的時候把vma->vm_lock_seq寫成了進程級的mm_lock_seq,這樣當我們拿讀鎖的時候,如果vma->vm_lock_seq == mm->mm_lock_seq,說明VMA還在寫,我們其實也不用拿讀鎖了,per-VMA讀鎖直接失敗,讓page fault的代碼回退到去(qù)拿原先的mmap_lock就好。

由于per-VMA拿寫鎖的人總是(shì)當場放(fàng)寫鎖,我們其實就不用擔心忘記up_write了。這有點自動化的類似後面将要提到的scope-based resource management。

值得一提是(shì),在per-VMA lock準備好之前,有些Linux内核,比如Android采用了SPF(Speculative page faults)來處理page fault,SPF的實現(xiàn)不包含per-VMA lock,它也不拿mmap_sem,但(dàn)是(shì)page fault會不拿mmap_sem投機執行,處理過程中會邊走邊看,如果執行過程中發現(xiàn)VMA被修改,page fault會拿mmap_sem來retry原先的page fault。這個機制我們在2022年終盤點中也有提及。

NUMA系統上kernel代碼段複制


Russell King,在Linux ARM體系架構采用device tree之前,維護着ARM Linux社區。由于當時的arch/arm目錄充斥着大量的冗餘描述硬件的代碼,在2011年TI OMAP的一次Pull request中,Linus終于忍無可忍,破口大罵“this whole ARM thing is a f*cking pain in the ass”。此後,Linaro和ARM強勢介入,在ARM Linux引入了device tree,開啓了一個嶄新的時代。自己的地盤被人革了命,Russell童鞋的黯然神傷無可掩飾。但(dàn)是(shì),作爲大神,Russell無疑擁有無可辯駁的技術實力,這次他給我們帶來的是(shì)黯然銷魂掌arm64 kernel text replication。

在一個典型的NUMA系統中,跨node訪問内存的開銷比訪問本地node的開銷大。

于是(shì)從軟件層面,我們傾向于讓本node的CPU訪問本node的内存,對于數據段而言,通過内存綁定、NUMA balance等方法可以可以實現(xiàn)這個目的。
但(dàn)是(shì),Russell瞄準的是(shì)内核的代碼段,衆所周知(zhī),内核代碼段在整個内存隻有一份拷貝,假設這份拷貝位于node 0 memory,那麽對于node1,node 2, node3這些CPU而言,它們其實都是(shì)訪問遠端的内存來執行内核代碼,這顯然是(shì)有耗損的。
Russell的這個patchset ——arm64 kernel text replication
鏈接:
https://lore.kernel.org/linux-arm-kernel/ZMKNYEkM7YnrDtOt@shell.armlinux.org.uk/
讓kernel的代碼段(也可以包含隻讀的數據段)在node0, node1, node2和node3各自擁有自己的拷貝,從而實現(xiàn)近距離(lí)内存訪問。

Large folios/動态大頁

Large folios是(shì)社區2023的熱門話(huà)題,由于一個large folio中可以包含多個page,所以采用large folio可以減小page fault的次數(比如一個page fault中映射1個包含16個page的folio,這樣就減少了後面15次page fault)、降低LRU的維護成本(large folio整體加入LRU)、降低内存的回收成本(large folio整體回收)等。

上一篇:21%程序員(yuán)感覺到失業危機、AI崗成HR招聘難題,調查了13000名開發者後揭曉2024年技術招聘現(xiàn)狀!
下一篇:軟件工程--------就業與編程語言的多樣性選擇