2023年度盤點(diǎn)|2023年Linux內(nèi)核十大技術(shù)革新功能
發(fā)布時(shí)間:2024-01-22 14:21:28
2023年,眾多Linux內(nèi)核開發(fā)者仍然在調(diào)度器、內(nèi)存管理、文件系統(tǒng)等領(lǐng)域貢獻(xiàn)著自己的idea和patch,本文從其中選取十個(gè)最典型的patchset,進(jìn)行闡述,它們是:
基于eBPF的sched_ext調(diào)度類擴(kuò)展
per-VMA lock
NUMA系統(tǒng)上kernel代碼段復(fù)制
Large folios/動(dòng)態(tài)大頁(yè)
文件系統(tǒng)large block支持
基于scope的資源管理
用代理執(zhí)行解決優(yōu)先級(jí)反轉(zhuǎn)(priority inversion)問題
延后用戶空間臨界區(qū)內(nèi)的搶占
EEVDF調(diào)度
BPF通用迭代器
下面我們一一展開。
基于eBPF的sched_ext調(diào)度類擴(kuò)展
這一patchset的開發(fā)過程,堪稱神仙打架。對(duì)壘的多方,無論是發(fā)patch的還是review patch的,都是內(nèi)核社區(qū)的頂流大神,甚至連看客都會(huì)北冥神功。他們之間直接的拼殺,刺刀見紅,毫不留情,讓凡人們見識(shí)了神仙也有性格,技術(shù)和思想的力量可以怎樣無視虛偽和矯情。
sched_ext patchset由社區(qū)鼎鼎大名的Tejun Heo發(fā)出,他是Linux內(nèi)核cgroup、KERNFS、PER-CPU MEMORY ALLOCATOR、WORKQUEUE等的maintainer。
這個(gè)patchset——sched: Implement BPF extensible scheduler class
Patchset的實(shí)際貢獻(xiàn)還包括來自Google、meta、卡內(nèi)基梅隆大學(xué)等多家主流廠商和科研院校的開發(fā)者。該patchset擴(kuò)展了一個(gè)調(diào)度class,與之前的CFS、realtime等并行,但是它允許調(diào)度行為被一個(gè)BPF程序來實(shí)現(xiàn),并聲稱有如下三大好處:
1.讓探索和實(shí)驗(yàn)變地容易: 讓新的調(diào)度策略可以快速迭代
2. 定制化調(diào)度行為:為特定應(yīng)用定制調(diào)度器(這個(gè)調(diào)度器也許不適用于通用目的)
3. 調(diào)度器快速部署: 在產(chǎn)品環(huán)境下,非侵入式地修改調(diào)度器。
新加入的sched_ext與內(nèi)核已經(jīng)存在的stop_sched_class、dl_sched_class、rt_sched_class、fair_sched_class、idle_sched_class是一種并列關(guān)系,任何一個(gè)sched_class,都需要實(shí)現(xiàn)一系列的callback函數(shù),比如:
enqueue_task:將task放入runqueue,在CFS中task會(huì)加入一個(gè)紅黑樹;
dequeue_task:將task從runqueue拿出來;
pick_next_task:調(diào)度時(shí)候選取下一個(gè)run的task,比如對(duì)于rt調(diào)度類而言就是找bitmap上第一個(gè)bit的queue里面的task;
task_tick:在調(diào)度tick發(fā)生時(shí)被調(diào)用,比如對(duì)于CFS而言,它會(huì)更新當(dāng)前運(yùn)行task的vruntime和sum_exec_runtime,并可能設(shè)置need_resched;
wakeup_preempt:一個(gè)task被喚醒的時(shí)候(也可能是調(diào)度策略或者優(yōu)先級(jí)更改,比如從其他策略調(diào)整為CFS插入CFS的runqueue的switched_to_fair),可能搶占正在運(yùn)行的task;
select_task_rq:比如fork一個(gè)新的task以及exec、wakeup等負(fù)載均衡場(chǎng)景,我們要選擇把task放到哪個(gè)CPU的runqueue上面。
Patchset定義了一組可以由eBPF程序?qū)崿F(xiàn)的callback:
并在內(nèi)核的sched_ext class的callback中,調(diào)用這一組eBPF實(shí)現(xiàn)的callback,比如ext sched_class的select_task_rq() callback調(diào)用eBPF的select_cpu() callback:
進(jìn)一步地,由于eBPF程序可以通過maps和userspace交互,實(shí)際上,調(diào)度行為也可以在userspace實(shí)現(xiàn)了,這讓內(nèi)核sched_class、eBPF的sched_ext_ops和用戶空間,實(shí)現(xiàn)了3位一體的聯(lián)動(dòng)。
比如eBPF中可以pop一個(gè)BPF_MAP_TYPE_QUEUE類型的map:
而userspace則可以u(píng)pdate_elem相關(guān)dispatch進(jìn)程的pid到這個(gè)map:
整個(gè)patchset讓Linux內(nèi)核調(diào)度器的維護(hù)者Peter Zijlstra(同時(shí)也是ATOMIC INFRASTRUCTURE、CPU HOTPLUG、FUTEX、LKMM、MMU GATHER AND TLB INVALIDATION、Perf等的維護(hù)者)所極度反感,在patchset中直接給出了NACK:
他NAK的無疑是一位大神,當(dāng)我們回眸特洛伊之戰(zhàn)中兩位偉大英雄阿喀琉斯和赫克托耳的決斗時(shí)刻,最后命運(yùn)的天平無論便向的是哪一邊,剩下的都只有悲壯。
但是之前我們?cè)趐age fault中,也是要拿mmap_sem讀鎖的,因?yàn)槲覀円膊恢纏age fault處理過程中,對(duì)應(yīng)的VMA會(huì)不會(huì)變化或者甚至消失,所以要和可能寫VMA的人排他。Page fault的處理邏輯實(shí)際是:
由于mmap_sem是整個(gè)進(jìn)程的,而一個(gè)進(jìn)程里面說不定也有成千上萬的VMA,然后大量的page fault以及其他的VMA的寫操作行為,相互競(jìng)爭(zhēng)鎖,就導(dǎo)致大量的競(jìng)爭(zhēng)延遲。其他需要持有寫鎖的地方也是非常多的,比如:brk、stack expand、munmap、remap_file_pages、exit、madvise、mprotect、mremap、mlock等。
用一個(gè)大的mmap_lock把這些寫和page fault的讀進(jìn)行保護(hù),這固然安全,但是也實(shí)在低效。我們假設(shè)一個(gè)進(jìn)程有1萬個(gè)VMA,然后我們?cè)谄渲械?個(gè)VMA上面進(jìn)行page fault,其他的9999個(gè)VMA消失不消失,變化不變化,跟我這個(gè)page fault之間其實(shí)是沒有半毛錢關(guān)系的。如果能夠在PF中不去持有mmap_lock讀鎖,而去持有一個(gè)更細(xì)粒度的,只關(guān)心本VMA的鎖,應(yīng)該是一個(gè)更好的選擇。
在處理page fault的時(shí)候,我們只需要通過持有VMA的lock,來保證這個(gè)VMA本身的穩(wěn)定:
struct vm_area_struct *lock_vma_under_rcu(struct mm_struct *mm, unsigned long address);
它的這個(gè)實(shí)現(xiàn)看起來很奇怪,因?yàn)樗玫搅藇ma->vm_lock->lock后,并不真地會(huì)一直拿著,而是馬上就放了up_write,但是它寫了一個(gè)vma->vm_lock_seq,把這個(gè)vm_lock_seq寫成了vma->vm_mm->mm_lock_seq的,而進(jìn)程級(jí)的mm_lock_seq會(huì)在mmap_lock釋放的時(shí)候自增。
但是拿讀鎖的page fault,則是在page fault的途中一直hold著vma->vm_lock->lock。lock_vma_under_rcu()會(huì)調(diào)用vma_start_read():
因?yàn)槲覀円_始VMA寫的時(shí)候把vma->vm_lock_seq寫成了進(jìn)程級(jí)的mm_lock_seq,這樣當(dāng)我們拿讀鎖的時(shí)候,如果vma->vm_lock_seq == mm->mm_lock_seq,說明VMA還在寫,我們其實(shí)也不用拿讀鎖了,per-VMA讀鎖直接失敗,讓page fault的代碼回退到去拿原先的mmap_lock就好。
由于per-VMA拿寫鎖的人總是當(dāng)場(chǎng)放寫鎖,我們其實(shí)就不用擔(dān)心忘記up_write了。這有點(diǎn)自動(dòng)化的類似后面將要提到的scope-based resource management。
值得一提是,在per-VMA lock準(zhǔn)備好之前,有些Linux內(nèi)核,比如Android采用了SPF(Speculative page faults)來處理page fault,SPF的實(shí)現(xiàn)不包含per-VMA lock,它也不拿mmap_sem,但是page fault會(huì)不拿mmap_sem投機(jī)執(zhí)行,處理過程中會(huì)邊走邊看,如果執(zhí)行過程中發(fā)現(xiàn)VMA被修改,page fault會(huì)拿mmap_sem來retry原先的page fault。這個(gè)機(jī)制我們?cè)?022年終盤點(diǎn)中也有提及。
NUMA系統(tǒng)上kernel代碼段復(fù)制
Russell King,在Linux ARM體系架構(gòu)采用device tree之前,維護(hù)著ARM Linux社區(qū)。由于當(dāng)時(shí)的arch/arm目錄充斥著大量的冗余描述硬件的代碼,在2011年TI OMAP的一次Pull request中,Linus終于忍無可忍,破口大罵“this whole ARM thing is a f*cking pain in the ass”。此后,Linaro和ARM強(qiáng)勢(shì)介入,在ARM Linux引入了device tree,開啟了一個(gè)嶄新的時(shí)代。自己的地盤被人革了命,Russell童鞋的黯然神傷無可掩飾。但是,作為大神,Russell無疑擁有無可辯駁的技術(shù)實(shí)力,這次他給我們帶來的是黯然銷魂掌arm64 kernel text replication。
在一個(gè)典型的NUMA系統(tǒng)中,跨node訪問內(nèi)存的開銷比訪問本地node的開銷大。
Large folios/動(dòng)態(tài)大頁(yè)
Large folios是社區(qū)2023的熱門話題,由于一個(gè)large folio中可以包含多個(gè)page,所以采用large folio可以減小page fault的次數(shù)(比如一個(gè)page fault中映射1個(gè)包含16個(gè)page的folio,這樣就減少了后面15次page fault)、降低LRU的維護(hù)成本(large folio整體加入LRU)、降低內(nèi)存的回收成本(large folio整體回收)等。