天天看點

阿裡核心月報2014年3月

目前linux核心急需的一項功能是線上打更新檔的特性。此前被oracle收購的ksplice一度是linux上唯一的解決方案。但是在被oracle收購後,ksplice就閉源了,并且成為了oracle linux的一項商業特性。而目前可以拿到的最新版本的ksplice仍然僅僅停留在0.19上,而可以直接使用的核心版本則是2.6.26。對于更新的核心版本則還無法使用。

在目前的實際生産環境中,不論是企業應用還是網際網路公司,對于核心bug的修複仍然停留在原始的安排停機,更新核心,重新開機服務的方式上。這一方式帶來的後果就是停機時造成的服務中斷。而通過主備切換方式進行停機也有可能造成部分資料的損失和服務的短時間中斷。總之,隻要你需要停機,就存在服務中斷的風險。而這對于企業使用者、網際網路公司來說屬于不能接受或應該盡量避免的。想必是看到了這一點,redhat和suse兩家公司分别啟動了一個關于核心熱打更新檔的項目:kpatch和kgraft。

根據目前看到的文獻資料,這兩個項目解決問題的方案是十分接近的,即通過linux核心已有的ftrace機制來對有問題的函數進行替換。目前ftrace通過在函數調用過程中插入斷點(int 3)的方式來實作相應的功能。而kpatch和kgraft則将這一斷點替換為一個長跳轉,進而将有問題的函數替換為新的功能正常的函數。在實作這一功能的過程中,這兩個項目的差別是替換時如何解決新舊代碼不一緻的問題。kpatch的解決方法是通過stop_machine()加以解決的;而kgraft則是通過類似rcu的方式來更新舊代碼。此外的一個差別是生成帶有更新檔的核心子產品的方式。kpatch是基于patch的方式來生成核心子產品的;而kgraft則可以自動通過源代碼來生成帶有新更新檔的核心子產品。

目前這兩個項目均可以在網上擷取到源代碼。kpatch,kgraft。有興趣的讀者可以一探究竟。

系統調用的設計絕不簡單。當開發者們設計一個接口的時候,經常有一些邊角的情況沒有被考慮到。涉及檔案系統的系統調用似乎 特别容易出現這種問題,因為檔案系統實作的複雜性和多樣性意味着當一個開發者想建立一個新的檔案操作的時候,他将不得不考 慮許多特例。在fallocate()系統調用的推薦添加的讨論中能看到一些這樣的特例。

fallocate()是關于一個檔案内空間配置設定的系統調用。它的初始目的是允許一個應用在寫這些檔案之前給它們配置設定空間塊。這種預 配置設定確定了在試着往那寫資料之前已經有了可用的磁盤空間;它也有助于檔案系統實作在磁盤上更有效地分布被配置設定的空間。後來 添加了falloc_fl_punch_hole檔案操作,用來去配置設定檔案裡的塊空間,留一個檔案空洞。

在二月份,namjae jeon推薦了一個新的fallocate()操作falloc_fl_collapse_range,這個草案包括了針對于ext4和xfs文 件系統的實作。它像打洞操作一樣删除了檔案資料,但又有一個不同,它沒有留一個洞在檔案裡,而是移動檔案中受影響區間之後 的所有資料到該區間的開始位置,整體上縮短檔案大小。這種操作的直接使用者将是視訊編輯應用,它能使用這種操作快速而有效地 視訊檔案中的一段内容。假如被删除的範圍是塊對齊(這對于一些檔案系統是個必須的前提條件),這種删除可能受改變檔案區間 映射圖所影響,不需要任何實際的資料複制。考慮到那些包含視訊資料的檔案可能是大的,我們不難了解一個有效的裁剪操作為什 麼是有吸引力的。

是以對于這種操作将會出現什麼樣的問題呢?首先一個問題将可能是與mmap()系統調用的互動,這種操作将映射一個檔案到一個進 程的位址空間。被推薦的草案是通過從page cache中删除從被受影響區間到檔案結尾的所有頁面來實作的。髒頁首先被寫回磁盤。 這種方法阻止了那些已經通過映射被寫入的資料的丢失,而且也有利于節省了那些一旦操作完成而超出了檔案結尾的記憶體頁面。這 應該不是個大問題,正如dave chinner指出的,使用這種操作的應用通常不會通路被映射過的檔案。除此之外,受這種操作檔案 影響的應用同樣不能處理沒有這種操作的其他修改。

但是,正如hugh dickins提醒的,tmpfs檔案系統會有相關的問題,所有的檔案存在在page cache,看起來非常像是一個記憶體映 射。既然page cache是個後備存儲,從page cache裡删除所有的檔案不可能會有個好的結果。是以在tmpfs能支援collapse操作 之前,還有許多與page cache相關的工作需要做。hugh不确定tmpfs是否需要這種操作,但他認為為tmpfs解決page cache問題 同樣很可能能為其他檔案系統帶來更穩健的實作。

hugh也想知道單向的collapse操作是否應該被設計成雙向的。

andrew morton更進一步,建議一個簡單的“從一個地方移動一些塊到另外一個地方”的系統調用可能是個更好的想法。但dave不 太看好這個建議,他擔心可能會引入更多地複雜性和困難的極端情況。

andrew不同意,他說更通用的接口更受歡迎,問題能被克服;但沒有其他人支援他這種觀點。是以,機會是,這種操作将局限于那 些超出檔案的崩潰的塊組;也許以後添加一個單獨的插入操作,應該是個有意思的用例。

同時,有一個其他的行為上的問題要解答;假如從檔案中删除的區間到達了檔案結尾将會發生什麼?對于這種場景,目前的更新檔集 傳回einval,一個想法是建議調用truncate()。ted ts'o問這種操作是否應該之間變成truncate()調用,但dave是反對這種想 法的,他說一個包含了檔案結尾的崩潰操作很可能是有缺陷的,在這種情況下,最好傳回一個錯誤。

假如允許崩潰操作包含檔案結尾,很明顯将也會有一些有趣的安全問題出現。檔案系統能配置設定檔案尾後的塊。fallocate()肯定能 用顯式地請求這種行為。檔案系統基本上沒有零化這些塊;代替地,它們被保持不可通路以緻無論包含了什麼樣的不穩定資料都不能 被讀取。沒有太多的考慮,這種允許區間超出檔案結尾的崩潰操作最終會暴露那些資料,特别是假如在中間它被中斷(也許被一個系 統崩潰)。dave不認可這種為檔案系統開發者設定陷阱的行為,他更喜歡從一開始杜絕這種帶有風險的操作,特别是既然還沒有任何 真實的需求來支援它。

是以,所有讨論的最後結果是falloc_fl_collapse_range操作将很可能基本原封不動地進入核心。它将不會包含所有的一些開發者 希望看到的功能,而是支援一個有助于加速一類應用的特性。從長期來看,這是否是足夠的依然需要時間的檢驗。系統調用api設計是 難的。但是,假如在将來需要一些其他的特性,能采用某種相容的方法來建立一些新的falloc_fl指令。

在已簽名子產品中複用“tainted kernel”标志會給在未簽名子產品中使用tracepoint造成麻煩,這個問題比較容易解決,但其實也有一些阻力,另外核心hacker們也沒有興趣幫助linux核心代碼外的子產品解決問題。

核心的子產品加載機制已有近20年的曆史(1995年自1.2版本引入),但直到最近才引入加密簽名的驗證機制:核心隻加載被認可的子產品。redhat核心早就支援了這個特性。kernel的編譯者可以指定用于簽名的密鑰;私有密鑰被儲存或被丢棄,公有密鑰則被編譯到核心裡。

有幾個核心參數可以影響到子產品簽名:config_module_sig控制是否使能簽名檢查。 config_module_sig_force 決定是否所有的子產品都需要簽名。如果config_module_sig_force沒有被開啟(并且沒有設定啟動參數module.sig_enforce ),那麼沒有簽名或簽名不比對的子產品也可以被加載。此時,核心被标記為“tainted”。 如果使用者使用modprobe –force去加載與核心版本不比對的子產品,核心也會設定“taint”标記。強制加載核心非常危險,由于與核心的記憶體布局不一緻,很可能會導緻核心crash掉。而且此類的bug報告肯定會被核心開發者們忽略掉。

但是,加載一個未簽名子產品并不一定會導緻crash,是以并不是很有必要使用“taint_forced_module”。tracepoint機制不支援被強制加載的module是因為在不比對的子產品中使用tracepoint很容易挂掉核心。tracepoint允許taint_crap與 taint_oot_module,但是如果有其它任何一個“taint”标記,子產品中的tracepoint是預設被關掉的。 mathieu desnoyers釋出了一個rfc patch意圖改變此現狀。該patch引入了“taint_unsigned_module”用以标記是否有此類module被加載。并且允許tracepoint支援這種“taint”類型。ingo molnar立即給了nak,他不希望外部子產品影響kernel的穩定。

但是,有一些發行版的kernel已經打開了簽名檢查,但是允許通過module.sig_enforce指定是否真的使用。發行版的密鑰存在kernel image中。嚴格的檢查隻允許發行版自己的子產品被加載。這嚴重限制了使用者對子產品的使用。

子產品子系統的維護者rusty russell也沒有被此項功能打動。他還在進一步尋找使用案例。

steven rostedt指出,如果沒有子產品被加載,我們為什麼還要設定forced_module标記。他也經常收到使用者們釋出的“在未簽名子產品中不能使用tracepoint”的bug報告。

johannes berg最終提供了russell一直在尋找的使用案例。berg還提供了使用未簽名核心的另一個原因:重kernel.org的wiki上backport核心子產品用以支援發行版提供者不支援的硬體驅動。

berg的使用案例足以使russell同意将此patch加入他的pending tree。我們也将會在3.15版本中看到desnoyers的最終的patch版本。屆時,kernel将會區分兩種不同的taint,使用者也能夠在他們加載的子產品中使用tracepoint,無論是否簽名。

linux程序的虛拟位址空間被核心分割成多個虛拟記憶體區域(vma),每個vma描述了這個位址空間的一些屬性,比如存儲後端,通路控制等。一個mmap()調用建立一個空間映射,這個空間在核心中就用vma來描述,但是将一個可執行檔案加載到記憶體,需要建立好幾個vma。我們可以通過/proc/pid/maps來檢視程序的vma清單。在記憶體管理子系統中,通過一個虛拟位址找到對應的vma是一個非常頻繁的操作。比如,每一次缺頁中斷都會觸發這個操作。是以不出大家所料,這個核心操作已經是高度優化過的。可能會令一些讀者意外的是這個操作還有優化提升的空間。

核心用紅黑樹來儲存程序的vma,紅黑樹可以保證很好的線性查找效率。紅黑樹有自己的優點,比如擴充性強,能支援大幾百個vma還有很好的查找效率。但是每一次查找都得從樹頂周遊到葉子節點。為了在某些情況下避免這種開銷,核心毫不意外的用了一個極簡的緩存政策,儲存上一次查找的結果。這種簡單的緩存效果還不錯,對于大多數應用基本上都有50%或以上的命中率。

但是davidlohr bueso同學認為核心可以做得更好。他覺得一個緩存項太少了,多一個肯定效果會好一些。在去年11月的時候,他送出了一個更新檔,就是增加了一個緩存項,指向具有最大空間的vma。背後的理論很直白,空間越大被查找的機率就越大。多了這個緩存項,一些應用的命中率确實從50%上升到60%了。這是一個不大不小的優化,但是沒有進入核心主幹。很大的一個原因就是linus跳出來說,“這個更新檔讓我很生氣。”當然,一般有品味的大神對你說這話,除了看得起你,肯定是有後話的。linus說了一堆為什麼這麼做沒有品味的原因,最重要的是提出了他自己認可的解決方法,而且一針見血。大意就是要優化就要視野要更寬廣,目前vma查找對于多線程應用來說是很不友好的,一個程序才一個緩存項,是以當務之急是把per-process的緩存變成per-thread的。

davidlohr同學聆聽教誨之後,奮發圖強,經過幾次疊代,送出了一套很有希望進入主幹的更新檔。該更新檔主要就是把per-process的緩存項變成了per-thread的。優化效果喜人,對于單線程和多線程的應用都有幫助。比如說系統啟動,主要都是單線程操作,命中率從51%提升到73%。多線程應用,比如大家都以為無法提升的核心編譯,命中率從75%提升到88%。實際的多線程應用,效果可能更好。比如通過觀察模拟多線程網頁伺服器負載的ebizzy,命中率驚人的從1%提高到了99.97%。

有了以上的測試資料,沒有任何理由把這個更新檔阻擋在核心的門外。大概在3.15核心,大家可以受享到這個vma查找優化了。

h. peter anvin同學在2014年的lsf會上主持了一個簡短的讨論會,向大家問了一個簡單的問題:你認為硬體特别是cpu的哪些改進可以簡化核心記憶體管理的工作?雖然hpa不保證這個問題會對硬體帶來真正的改進,但他說能保證把話帶回英特爾。

第一個抱怨更多的是跟底層硬體聯系緊密的代碼:rik van riel提到powerpc架構沒有類似x86上tlb的頁表緩存的flush操作。其它的架構(比如sparc)也有類似的限制。這使得抽象層的共用代碼部分很難寫。他希望硬體方面能有一些改進,讓硬體無關的代碼能很友善的更新頁表。

對于x86平台,peter zijlstra希望能友善的讓部分頁表失效。另外一個很受期待的請求是支援64kb的頁。目前隻支援4kb和2mb大小的頁。

mel gorman希望硬體提供一種對記憶體頁快速的填零操作。目前對于2mb記憶體頁填零是一個耗時的操作,是以這個特性可以提高記憶體大頁的效率。有人建議可以考慮在核心空閑時做填零的操作,但是christoph lameter說他已經這樣嘗試過了,沒有什麼效果。

另外一個請求是提供一個效率更高更快的iret實作。這将大大提高缺頁中斷的效率,因為它通過iret傳回到使用者層。

期間有人讨論了一下處理器之間發送消息的開銷,還有希望一個mwait指令讓對于使用者層的某些應用可能會有幫助。對于處理器未來的變化,讓我們拭目以待。

spinlock是一種簡單的鎖機制,當某個bit被清除時鎖是available的。一個線程需要申請該鎖時,通過調用原子操作compare-and-swap指令設定那個bit。如果鎖不是available的,線程就會一直spinning。最近幾年spinlock開始變得複雜起來,2008年加入了ticket spinlock用于實作公平性,2013年又加入了新特性用于更好的支援虛拟化。

目前spinlock還存在一個比較嚴重的問題就是cache-line bouncing:每次嘗試擷取鎖的時候都需要将這個鎖對應的cache line挪到local cpu上。對于鎖競争特别嚴重的場景下,這個問題對性能影響很大。之前有過一組patch嘗試解決這個問題,但是最終沒有被merge

tim chen送出了一組patch用于解決這個問題,基于一篇1992年的論文實作的"mcs lock"。其思想是将spinlock擴充為一個per-cpu的結構,能夠很好的消除cache-line bouncing問題。

3.15的tip tree裡面,mcs lock定義如下:

使用的時候首先需要定義一個unlocked的mcs lock(locked位為0),這個相當于是一個單連結清單的tail記錄頭,next記錄的是鎖的最末尾一項。當有一個新mcs_spinlock鎖加入時,新鎖的next指向null(mcs lock的next為null時)或者mcs lock的next的next,mcs lock的next指向新鎖。 當一個cpu1嘗試拿鎖的時候,需要先提供一個本地的mcs_spinlock,如果鎖是available的,那麼本地mcs_spinlock的locked為0,設定mcs lock的locked為1,本地mcs_spinlock的next設定為mcs lock的next,mcs lock的next設定為本地的mcs_spinlock。如果cpu2也嘗試拿鎖,那麼也需要提供一個cpu2本地的mcs_spinlock,mcs lock記錄的tail修改為cpu2的mcs_spinlock,cpu1的next設定為cpu2.

當cpu1鎖釋放之後會修改cpu2對應的locked為0,cpu2就能拿鎖成功。這樣cpu2上面拿鎖spinning的時候讀取的都是本地的locked值,同時也能夠保證按照cpu拿鎖的順序保證擷取鎖的順序,實作公平性。

mcs lock目前僅僅是用在實作mutex上,并沒有替換之前的ticket spinlock。主要原因是ticket spinlock隻需要32bit,但是mcs lock不止,由于spinlock大量嵌入在核心的資料結構中,部分類似struct page這種資料結構不能夠容許size的增大。為此需要使用一些别的變通方法來實作。

目前看有可能被合并的是peter zijlstra提供的一組qspinlock patch。這組patch中每個cpu會配置設定一個包含四個mcs_spinlock的數組。需要四個mcs_spinlock的原因是由于軟中斷、硬中斷、不可屏蔽中斷導緻的單個cpu上面會有多次擷取同一個鎖的可能。

這組patch中,qspinlock是32bit的,也就解決了之前mcs lock替換ticket spinlock導緻size增大的問題。

aim7的測試結果看部分workload有一小點的性能退化,但是其他workload有1~2%的性能提升(對底層鎖的優化方面,這是一個很好的結果)。disk相關的benchmark測試性能提升甚至達到116%,這個benchmark在vfs和ext4檔案系統代碼路徑上存在很嚴重的鎖競争。

目前kernel的"shrinker"接口是記憶體管理子系統用來通知其他子系統發生了記憶體緊張,并促使他們盡可能地釋放一些記憶體,也有各種努力嘗試增加一個類似的機制來允許核心通知使用者态來收縮記憶體,但是基本都太複雜而不易合并進記憶體管理代碼。但是大家沒有停止嘗試,最近就有兩個相關的patch。這兩個patchset都基于一個新的名詞volatile range,它是指程序位址空間中的一段資料可再生的記憶體區域。在記憶體緊張時,核心可以直接回收這種區域裡的記憶體,程序在以後需要的時候再重新生成;在記憶體充裕時,該區域的記憶體不會被回收,程式也可随時通路這些資料。這些patch的最初動機是為了替換android上的ashmem機制,當然也可能有其他潛在使用者。

之前有很多版本的volatile range實作,有的是基于posix_fadvise()系統調用,有的是基于fallocate(),也有一些基于madvise()。john stultz的實作則增加了一個新的系統調用:

vrange()操作的範圍是從start位址開始的,長度為length的空間。如果mode是vrange_volatile,這段區域将被設定成volatile;如果mode是vrange_nonvolatile,這段區域的volatile将會被去掉,但是這個過程中有些volatile頁可能已經被回收了,此時purged會被設定成一個非0值來辨別記憶體區域的資料已經不可用了,而如果purged被設定了0則表示資料仍可用。如果程序繼續通路volatile range的記憶體且通路了已經被回收的頁,它就會收到sigbus信号來表明頁已經不在了。這個版本的patch還有另外一個地方不同于其他方式:它隻對匿名頁有效,而以前的版本可以工作在tmpfs下。隻滿足匿名頁使得patch很簡單也更容易被review和merge,但是也有一個很大的缺點:不能工作在tmpfs下也就無法替換ashmem。目前的計劃是先在基本patch實作上達成一緻再考慮實作更複雜的檔案接口。vrange()内部是工作在vma層次上,挂在一個vma上的所有頁要麼是volatile的要麼不是,調用vrange()時可能會發生vma的拆分和合并。因為不用周遊range裡的每一個頁,是以vrange()速度非常快。

另外一個實作方法是minchan kim的madv_free patchset,他擴充了現有的madvise()調用:

madvise()的advice辨別了希望核心采取的行為,madv_dontneed告訴核心馬上回收指定記憶體區域的頁,而madv_free則将這些頁辨別為延遲回收。當核心記憶體緊張時,這些頁将會被優先回收,如果應用程式在頁回收後又再次通路,核心将會傳回一個新的并設定為0的頁。而如果核心記憶體充裕時,辨別為madv_free的頁會仍然存在,後續的通路會清掉延遲釋放的标志位并正常讀取原來的資料,是以應用程式不檢查頁的資料,就無法知道頁的資料是否已經被丢棄。

madv_free更傾向于在使用者态記憶體配置設定器中發揮作用,當應用程式釋放了一組頁,配置設定器将會使用madv_free告訴核心。如果應用程式很快在同一段位址空間又配置設定了記憶體,它就會使用原來的頁,而不需要先釋放原來的頁在配置設定并清零新的頁。簡而言之,madv_free代表"我不再關心這段位址空間裡的資料,但是我可能會再次使用這段位址空間"。另外,bsd已經支援madv_free,而不像vrange()隻是linux的特性,這顯然會提高程式的可移植性。

目前這兩種方法都沒有收到很多的review,但是在2014 lsfmm上會有一個相關的讨論。

随着linux上的各種container解決方案不斷成熟,主流發行版開發者們也開始行動起來了-他們開始構思一個完全由container組成的運作時環境(還記得docker嗎?)實作這偉大計劃的其中一個需求是:負責這些containers的資源管理、生命周期管理的中控守護程序需要知道和自己通信的各個程序各屬于哪個cgroup。vivek goyal最近送出了一個實作此功能的patch,通過給domain socket添加新的指令字來讓通信的雙方知道對方在哪個組裡。這patch代碼非常簡單,但仍然不出意外地引起了一場郵件清單上的大讨論。

這個patch的大概想法是這樣的:給domain socket的getsockopt()系統調用添加一個叫做so_peercgroup的指令字,每次打開這個socket的程序調用這個指令字,就給它傳回對端所在的組 - 精确地說是連接配接建立時對端所在的組,因為它後邊可能被移到别的組裡了,對于開發者們考慮的典型場景來說,傳回連接配接建立時的情況就已足夠。

主要的反對意見來自andy lutomirski,他的抱怨真是多,但核心思想主要是一條:“一個位于cgroup控制組中的程序根本不應該知道它自己在組裡!”而vivek的patch需要組内程序配合才能工作,遠不僅僅是“知道”自己在某個組裡。他提出了替代解決方案

首先,我們可以給每個cgroup組配上一個user namespace,然後就可以通過現有的so_peercred + scm_credentials獲得socket對端的程序的uid的映射,進而間接地推斷出它在哪個cgroup組裡。vivek反對這麼做,主要原因是user namespace還遠未成熟,simo sorce也表示這麼做不現實,不會在近期考慮給docker加上user namespace支援。

其次,andy認為我們可以在/proc/{pid}/ns下增加接口,把各個程序的cgroup組資訊輸出在那兒(這聽起來似乎是最簡單易行的辦法了),然而simo認為這樣做易用性太差,某個程序可能占用了一個pid然後死掉,然後有新程序又占用了這個pid,同一個pid已經換人了,這種情況下使用者态程式很難感覺到。這一類的race一直不好解決,不然我們直接添加一個/proc/{pid}/cgroup就萬事大吉了。

andy在讨論過程中也向simo提了一個小問題:既然添加這個指令字主要是為了給docker用,而docker是同時使用了namespace和cgroup兩種機制的,那麼是不是說docker可以跨network namespace使用domain socket了?比如,在根組裡建立一個叫/mysocket的domain socket,再把它bind mount到container裡邊的同名路徑下,就可以跨container通信了,這樣做現在可以嗎?應該可以嗎?這個問題在工作中也曾困擾過淘寶核心組,留給親愛的讀者們做思考題好了 :p

總之,這個線索讨論到最後誰也沒有說服誰,我們相信随着unix标準中不存在的新特性不斷進入linux核心,這樣的讨論還會越來越多。核心開發者們不禁開始思考這樣一個事情:當我們向核心加入一個新特性的時候,到底應該在多大範圍内勸說使用者使用它們?到底應該在多大範圍内和我們已有的特性結合在一起?畢竟如果我們加入一個新特性,又非常不相信它,以至于我們自己都不願意使用,那真是非常奇怪的事情。

大家應該對linux上的oom killer不陌生,當使用了過多記憶體且swap都被用完後經常就會發生。linux kernel發現無法回收上來任何記憶體後它就會選擇kill掉一個程序,而這可能是運作幾年之久的web浏覽器,媒體播放器或者x window環境,導緻一下子丢失了很多工作。有時候這種行為可能是有用的,比如殺掉了一個正在記憶體洩漏的程式,使得其他程式可以繼續正常運作,但是大多數時候它都會在沒有任何通知的情況下讓你丢失重要的東西。google的david rientjes最近提出了一套patchset使得即将oom時可以給程式發送通知并自己采取行動,比如選擇犧牲哪個程序,檢查是否發生了記憶體洩漏或者儲存一些資訊用于debug。目前的oom政策是找到并殺死使用記憶體最多的程序,我們可以改變/proc//oom_score_adj的值對使用的記憶體量進行權重并影響oom的決策,但是也僅限于此,我們無法把自己實作oom kill的行為告知核心,例如我們不能選擇殺死一個新生成的程式而不是跑了一年的web伺服器,也不能選擇一個優先級最低的程式。

目前有兩種類型的oom行為:全局oom-整個系統記憶體緊張時發生和memcg oom-memcg記憶體使用量達到限制,使用者态oom機制可以同時處理這兩種類型的oom。

memcg是使一組程序跑在一個cgroup裡并對總的記憶體使用量整體監控,可以用它來防止使用的總記憶體超過某一限制,是以提供了一個有效的記憶體隔離機制。當memcg的使用量超過限制且無法回收時,memcg就會觸發oom,這其中分為兩個階段:頁配置設定階段即頁配置設定器配置設定空閑記憶體頁的過程,和記賬階段即memcg統計記憶體使用量的過程。如果頁配置設定階段失敗,這意味着系統整體記憶體緊張,如果頁配置設定成功但記賬失敗,就說明memcg内部記憶體緊張。如果要使用memcg,首先要确認核心編譯了該功能,确認方法如下:

可以看出核心開啟了memcg,接下來确認是否挂載:

也已經挂載了。如果沒有,可通過如下指令挂載:

根挂載點本身就是root memcg,它控制系統上的所有程序,可以通過mkdir來建立子memcg。每一個memcg都有以下四個控制檔案:

david rientjes增加了另外一個控制檔案:

當memcg記憶體使用達到限制且核心無法從它以及它的子組中回收任何記憶體時,基本上oom就要發生了。預設情況下核心将會在它的程序(或它的子組裡的程序)中選擇一個使用記憶體最多的并kill掉,但是我們也可以關掉memcg oom:

但是關掉它以後,所有申請記憶體的程序都可能會處于死鎖狀态直到有記憶體被釋放。這個行為看起來沒有什麼作用,但是如果使用者态注冊了memcg oom通知機制,情況就變了。程序可以通過eventfd()來注冊通知:

之後程序采取:

read()調用将一直被阻塞直到要發生memcg oom的時候,這時相關程序将會被喚醒而不需要一直去主動查詢記憶體狀态。

但是即使程序被喚醒并知道了即将發生oom,它也什麼都做不了,因為基本所有可能的操作都會配置設定記憶體。如果該程序是個shell,因為無法配置設定記憶體運作ps,ls或者cat tasks都會被阻塞住。是以一個明顯的問題是:即使使用者态想kill掉一個程式,但是它卻無法擷取所有的程序清單,這是不是很無奈? 使用者态oom處理機制的目的就是把責任轉移到使用者态,并允許使用者決定後續的操作,而這隻有在記憶體預留後才有可能。他增加的memory.oom_reserve_in_bytes接口就是可以被程序在oom時超額使用的記憶體,但是該程序隻能是注冊了oom通知的程序。例如:

那麼向memory.oom_control注冊了eventfd()的memcg程序就可以超額使用32m記憶體,這可以允許使用者做一些有意義的事:讀檔案,檢查記憶體使用情況,儲存memcg程序清單等。如果可以通過其他方式釋放一些記憶體,使用者态oom處理也不一定要kill掉一個程序。它也可以隻是儲存一些目前資訊例如memcg記憶體狀态,程序使用記憶體等以便之後debug,并重新開啟memory.oom_control來打開oom killer。當有記憶體預留時,寫memory.oom_control就可以成功。

如果整個系統記憶體緊張,那麼就沒有預留記憶體可供memcg使用,包括root memcg也無法允許程序配置設定記憶體,因為核心在頁配置設定階段就會失敗而不是記賬階段。對于這種情況我們需要另外一種預留機制:記憶體管理系統需要單獨為使用者态oom處理機制預留一部分記憶體,使得它可以在oom時繼續配置設定。目前已經有了per-zone的預留:sysctl min_free_kbytes可以為重要的申請預留一部分記憶體,例如需要回收的程序或者正在退出的程序。使用者态oom處理的預留記憶體是min_free_kbytes預留的一個子集。

如果核心決定自己kill掉一個程序,那這些預留也沒什麼意義。預設下系統全局的oom killer不能被關閉,而這組patchset允許我們像關閉memcg oom一樣關閉全局oom,這是通過root memcg的memory.oom_control接口實作的。他增加了一個flag pf_oom_handler來允許等待oom通知的程序可以使用預留記憶體,如果程序屬于根組,那麼核心允許它在per-zone的min水位之下繼續配置設定。這個flag使用在頁配置設定器的slow path中,也不會産生什麼性能影響,對于無關程序就隻是多了一條判斷而已。這個設計的另一個重要方面是統一了全局oom和memcg oom處理的接口,使用者态oom處理也不需要根據它是屬于根組還是子組而改變使用方法。

2014 lsfmm上有個slot讨論了使用者态oom處理的進展,也有幾個問題。sasha levin說為啥不用現成的vmpressure機制,當記憶體開始回收的時候就獲得通知而不是等到要oom時才期待使用者态處理,david說vmpressure機制跟這個不一樣,我們無法知道程序消耗記憶體的速度有多快,系統可能快速從"記憶體充裕"狀态迅速變成oom狀态,使得使用者态根本沒時間反映。另外一個焦點在于使用者态oom機制對全局oom的處理,michal hocko認為改變全局oom的處理可能從長期看很難維護,我們應該想辦法優化現有的oom機制而不是直接替掉它。但是google的一個工程師tim hockin說改變或更新核心是一個很緩慢的過程,而且很多oom政策放在核心态也不太合适,是以他們希望能把主動權交給使用者态,讓使用者态程式決定oom政策而不用更換核心。另外一個大家覺得有争議的地方是在memcg的架構下改變全局oom的處理,這個機制隻能在cgroup被編譯進核心時才能使用,但是目前仍有很多使用者不cgroup。peter zijlstra建議是否可以在/proc下為全局oom再建一個接口,這樣就可以不用依賴memcg。

目前社群對這一套patchset還是不太買帳,對之後的開發方向也沒有一緻的結論,短期内也應該不會被合并。

linux核心必須讓所有的workload都最大限度的優化,但是實際上卻并非如此(這也是阿裡核心組存在的意義,呵呵),postgresql就是這麼一個例子。在今年的lsf上postgresql的開發者robert haas, andres freund和josh berkus給核心開發者分享了一下他們的痛點。postgresql在1996年發起,并運作在很多不同的作業系統上,是以他們隻能使用程序,并用system v共享記憶體來做程序通信。另外postgresql儲存着自己的buffer cache,同時也用作業系統的buffer i/o來做磁盤資料的讀寫。這種使用方式給postgresql的使用者造成了很大的麻煩。

postgresql遇到的第一個問題是從buffer cache刷資料到磁盤。postgresql使用自己的journal模式write-ahead logging,任何修改先寫日志,當日志落盤以後,資料庫的修改才會落盤。這些邏輯都是由checkpoint程序來實作,它先寫日志再刷盤。日志的寫入量很小并且都是順序寫入,是以性能不錯,太慢現在很滿意。問題主要是出在第二步:資料庫修改的落盤。checkpoint程序會盡量安排分階段寫入以防止阻塞整個io,但是一旦它調用fsync,就會導緻所有的寫入被推到裝置的請求隊列,由于它一次寫入了太多的資料,導緻其他程序的讀請求也被block住了。

ted ts'o問了一個問題:如果我們限制住checkpoint程序所能占據的磁盤帶寬是否有意義,robert回報說這樣不行,因為當沒有競争時checkpoint程序還是希望利用整個磁盤帶寬。又有人提議用ionice(這個目前隻能用在cfq上),但是這個對fsync()發起的i/o不起作用。ric wheeler建議postgresql的開發者應該自己控制好寫入資料的速度,chris mason補充說o_datasync應該會有用,但這裡的問題是需要使用者自己知道磁盤的能力... 于是乎讨論又回到了i/o優先級。可惜很悲劇,如果使用者已經一下子發了大量的資料到裝置層并占滿了裝置隊列,現在即使程序有很高的i/o優先級也沒戲,因為隊列已經滿了。現在似乎沒啥好辦法了...

由于沒啥結論,囧,最後ted問postgresql的開發者是否有一個很容易複現的程式,這樣核心開發者可以自己來測試不同解決方案的效果。

postgresql既需要控制自己的buffer也需要用buffer i/o。 這會導緻一個明顯的問題:資料庫的某些資料會在記憶體裡面存兩份:一份子postgresql的buffer裡面,一份子作業系統的page cache裡。這會增加postgresql的記憶體使用,并降低了系統性能。大部分的重複都是可以被做掉的,比如說在postgresql裡面被修改過的buffer肯定會比核心裡面page cache的内容新,這些page cache後面會在postgresql刷buffer的時候被更新。這樣儲存這部分page cache就沒啥意思了,應該用fadvise(參數是fadv_dontneed)來通知核心移除這些記憶體。andres提到不知道什麼原因,這麼做反而會導緻一些讀的i/o,這個明顯是核心的一個bug。madvise他們也用不了,因為先要把檔案map上來太麻煩了,速度也不行。當然,這個問題也能反過來解:把核心的page cache留着,而把postgresql裡面沒修改的buffer釋放掉,這可能通過一個系統調用或者某種特殊的write操作來實作,但是這個沒有達成最終的結論。

postgresql使用者遇到的另一個問題是新核心經常導緻性能問題。比如透明大頁,這個不但沒給postgresql帶來幫助,反而顯著得降低了性能,囧。另外似乎記憶體compaction的算法雖然花費了很長時間,但是卻沒有生成很多大頁,最終把透明大頁的支援關閉掉以後整個世界清靜了。mel gorman指出如果記憶體compaction影響了性能,那就應該是個bug。而且他自己本人已經很久沒有看到關于透明大頁的bug彙報了,不過他還有一個patch可以限制compaction使用的cpu時間,這個patch目前沒有被合并,因為沒人發現compaction有問題。不過既然現在postgresql發現了問題,可能後面需要重新考慮合并這個patch。

另一個大問題是"zone reclaim"這個特性:kernel在其他zone裡面有記憶體的時候仍然僅僅在這個zone裡面回收記憶體頁。zone reclaim降低了postgresql的性能,是以已經被預設關閉。andres同時提到他經常由于這個事情去幫别人診斷系統,這個對他的收益有幫助,不過最好還是希望核心把這個解決掉。mel解釋說當時加入zone reclaim的假設是所有的程式都能在一個zone裡面,這個假設目前看沒啥道理,是以最好把這個關掉。

最後,postgresql開發者指出核心的更新很恐怖,每個核心版本的更新所導緻的性能差距沒法預期...有人提議說能否找出一個postgresql的benchmark來測試新核心,但是這個最終沒達成一緻。總得來說,核心開發者和postgresql開發者對這次會晤都很滿意。

今年的lsf summit btrfs的開發者chris mason闡述了facebook是如何使用linux核心的。他和大家分享了一些facebook核心的數字以及facebook的一些痛點。這些痛點有些和postgresql開發人員遇到的類似,其實也有好些和阿裡巴巴也類似,可見大家都是一樣的苦逼呀!

facebook的架構大概是這樣的:前面有一個web接入層,主要是cpu和網絡是瓶頸,用來處理使用者請求。後面是一個memcached層來cache mysql的查詢結果。再後面是一個存儲層,主要包括mysql, hadoop, rocksdb等。 在facebook任何人可以查閱任何代碼,facebook.com一天更新代碼兩次,是以新的代碼可以很容易被使用者用到。代碼變化如此之快,是以如果有性能或者其他的問題,核心開發人員可以通知其他開發人員"你錯了",并給出可能的解決方案。

facebook内部有很多個核心版本,最多的是基于2.6.38的核心,另外一些運作3.2 stable+250patches的版本,還有一些運作着3.10 stable+60patches的版本。這些patches大部分都集中在網絡和跟蹤子系統,還有少量的記憶體管理方面的patches。讓mson很驚訝的是facebook生産環境對錯誤的容忍性很高, 他舉了一個3.10的pipe bug,這個是一個小race,但是facebook每天會遇到500次,但是使用者卻根本感覺不到。

mason曾經在facebook内部問了很多人他們認為核心最嚴重的問題是什麼,最終有兩個候選答案:stable pages和cfq。不過btrfs也有stable pages的支援(囧),而且facebook現在還有jens axboe,cfq的開發者,呵呵!另外一個問題是buffer i/o的延遲,特别是對那些隻會追加寫的資料庫檔案。很多時候這些寫入很快,但是有時候卻非常慢,如果核心能解決這個問題就完美了。他還提到了把核心的spinlock移植到使用者态。rik van riel提到有可能posix locks可以使用自适應的lock,先自旋一陣子再進入睡眠。memcached層有一個使用者态spinlock的實作,但是mason認為這個實作和核心比起來還是太簡單了。

精細化的i/o優先級控制也是facebook的想做的地方:一些做清理工作的低優先級i/o線程往往會阻塞前景的高優先級的i/o線程。有人問mason需要什麼樣的優先級才能滿足需求,似乎聊下來facebook也就需要2個優先級:一個低一個高。這時又有人提到了ionice,不過這個解決方案隻能在cfq上用(cfq已經被facebook disable了),bottomley于是乎建議讓ionice針對所有的i/o排程器都起作用,這點mason也表示贊同,不過ted ts'o提到了一個可能的問題:回寫線程也要有ionice的設定才能讓整個環境工作。

facebook的另一個問題是關于日志的。facebook記錄了很多的日志,這些程式需要不停的調用fadvise()或者madvise()來通知核心是否掉不需要的page cache,而這是可以被優化的。van riel建議說新核心的頁面置換算法有可能會有幫助。mason解釋說其實facebook倒是不介意這些,不過不停的調用這些系統調用似乎有點多餘了。

josef bacik正在對btrfs做一些小的修改進而能夠控制buffer i/o的速率,這個對btrfs比較容易,但是其實對核心其他檔案系統來說,這個想法同樣适用。這時jan提出僅僅限制buffer i/o是不夠的,因為系統中大部分條件下都存在着很多種不同的i/o。chris對此表示同意,同時他認為這個方案雖然不完美,但至少可以部分解決問題。bottomley認為ionice實際上已經存在了,我們就應該重用它,并且如果可以讓balance_dirty_pages能夠擁有正确的ionice優先級應該是可以解決90%以上的問題的。

mason還提到目前facebook是将日志檔案儲存到了hadoop叢集裡面,但是用來搜尋日志的指令仍然是很簡單的grep,最好能有一個更好的方法來給核心日志打标記。對于這一點,bottomley的意見是mason最好把這個問題抛給linus torvalds,對這個建議,大家都隻能呵呵了。

盡管3.10已經很新了,但是mason還是希望使用更新的核心。基于這個目的,他建立了一個新的層叫危險層,他移植了facebook原來打在3.10上的60個patches重新打到了最新的核心上并部署了大約1000台機器,這些機器屬于前面提到的web層,這樣他才可以收集到很多新的性能名額。說到這裡,他給大家show了一張請求響應時間圖(資料來源于最近3天抓的資料),從這張圖裡可以看出系統的平均響應時間以及10個最差的響應時間。這樣他就能登入這些機器檢視真正的問題出在什麼地方。這僅僅是一個例子,後面随着項目的進展他還會分享更多的資訊這樣才能友善診斷新核心的問題并更快的解決它們。

page cache是檔案系統和記憶體管理系統中的核心元件,它緩存了持久存儲上的大量資料,并對系統的整體性能起着關鍵作用。但目前page cache在很多方面已經暴露出了一些問題。今年的lsf/mm峰會上最先讨論的就是和page cache相關的幾個議題。

最先讨論的議題是由james bottomley提出的問題是關于32位系統上page cache不能尋址超過16tb大小的範圍。由于目前linux頁的預設大小為4kb,是以最多隻能尋址16tb的磁盤空間。對于這個問題,首先要問的就是真的需要解決它嗎?

盡管大多數核心開發者認為沒有必要解決這個問題。但是實際情況卻并不是這樣。随着嵌入式裝置的流行,特别是以arm處理器為核心的裝置的大量使用。使用者使用超過16tb空間裝置的機會大大增加。而這一問題也變得日益嚴重。

dave chinner則指出,這個問題的核心是page cache。如果使用者程式使用direct i/o來通路磁盤,由于direct i/o會跨過page cache,是以沒有尋址範圍的問題。另一個問題是udev會使用buffered i/o來映射磁盤,是以即便應用程式進行了修改,仍然不能解決這一問題。此外,相對應的使用者态程式也存在問題。比如在32位的機器上使用者無法使用fsck程式檢查超過16tb的檔案系統。是以對于32位系統尋址範圍問題的解決會涉及整個存儲應用的方方面面。

最後的一緻意見就是,這個問題不解決。

接下來dave chinner和christoph lameter主持讨論了關于大實體頁的議題,即在page cache中存放更大的實體頁。這兩個人為了解決不同的問題而同時希望能夠擴大目前的頁大小,而他們采用的解決方法也不一樣,不過最終的效果确實擴大了page cache中實體頁的大小。

christoph擔心的是他的解決方案的性能開銷。盡管更大的實體頁會減小核心在管理記憶體頁和進行i/o操作時的開銷。但是他的解決方案是在配置設定記憶體頁的時候添加一個order屬性來配置設定不同大小的記憶體頁。這樣做帶來的問題就是如何解決記憶體碎片化的問題。

對于上述記憶體碎片問題的解決方法,christoph說可以專門為大實體頁保留一些記憶體頁,但是這一做法比較“惡心”。另外一種解決方法則是允許核心移動記憶體頁來消除記憶體碎片。而且移動記憶體頁的大部分功能在目前核心中已經實作了。目前的問題是有些記憶體頁是不能被移動的,而這些不能移動的記憶體頁會破壞大實體頁。很明顯,這又是個死胡同。

記憶體頁有很多原因不能被移動。比如核心中通過slab配置設定的對象。christoph宣稱已經有更新檔解決slab中配置設定的對象不能被移動的問題。但是該更新檔還沒有被合并。此外,仍然有一些反對的聲音,比如這些對象能夠移動的前提是所有指向它的指針都已經被修改。christoph則認為這些問題都可以通過一些資料結構來加以解決。

此外,mel gorman指出上面的問題其實也還好。真正的問題是如果通過記憶體頁的移動來解決記憶體碎片的問題,那以後出現的問題很可能煽了各位核心開發者的臉。因為現在透明大頁在使用中也會遇到配置設定失敗的情況。是以問題遠沒有想象的簡單。

dave chinner希望擴大記憶體實體頁大小的原因是希望突破檔案系統塊大小4kb的限制。顯然通過連續多個實體頁是可以滿足他的需要的。這樣做的另一個好處是檔案系統基本不需要修改。但這意味着修改需要在記憶體管理代碼中完成。是以他隻不過是把檔案系統的事情拿到記憶體管理部分來做罷了。

而在擴大實體頁上,主要的限制就是通用塊i/o棧的限制。此前,單個i/o請求最大大小很長一段時間曾經困擾着快速裝置的使用者。由于這一大小太小,造成快速裝置性能上不去。盡管這一問題目前已經被解決,但是還有其他問題。由于page cache目前假定檔案系統塊大小,是以不會記錄檔案系統級别的資訊,是以檔案系統需要自己來處理塊大小和記憶體頁大小不一緻的資訊。這樣就造成了目前檔案系統磁盤塊大小不能超過記憶體頁大小的原因。

nick piggin曾經嘗試過來解決這一問題。并且從概念上也證明了其代碼是可以工作的。但是他的解決方法存在的問題是需要修改所有檔案系統。是以dave選擇另外一種方法來解決目前的問題。即不再通過page cache來維護記憶體頁和檔案系統之間的映射關系。這樣就可以解決掉目前的問題。

這一方案的一個好處是徹底廢棄buffer_head這一結構。此前buffer_head維護着記憶體頁到磁盤塊的資訊。而新的方案改用類似于extent的結構來跟蹤記憶體頁到磁盤塊的映射關系。這樣就大大簡化了此前的代碼。

另外很多開發者提出elf檔案格式的問題。因為elf格式中有很多對齊規範,而檔案系統的磁盤塊增大後可能會破壞對齊。但是這一問題除了32位系統外似乎并不嚴重。

最後的結論就是大家比較支援dave的方案。

今年lsf/mm上比較重要的一個議題就是smr硬碟。smr硬碟是下一代磁盤存儲,而它由于具有一些比較特殊的特性而對目前的檔案系統、存儲帶來了挑戰。

目前smr硬碟分為三種,即裝置管理,主機感覺和主機管理。裝置管理類型和此前傳統磁盤的使用方法一緻,由磁盤自己來對磁盤空間進行映射。但是這樣的結果會帶來性能下降(與現在flash裝置的擦出類似)。這種類型的硬碟由于和傳統磁盤在使用上沒有差別,是以不是讨論的重點。讨論的重點自然放到了主機感覺(系統需要遵循磁盤的區域來進行操作)和主機管理(主機需要主動根據磁盤的區域來進行操作)兩類上。

smr硬碟在内部會分成兩個區域,分别是可以随機讀寫的區域和隻允許順序讀寫的區域。在隻允許順序讀寫的區域,會有專門的指針來記錄目前的寫入位置。如果寫請求要求寫入的位置與目前指針位置不一緻,會導緻磁盤傳回io錯誤(主機管理)或者磁盤會将該磁盤空間重新映射到别的區域(主機感覺)。而重新映射操作會帶來很大的延遲。

目前已經定義了兩個新的scsi指令來查詢磁盤内部的空間區域和重置記錄資訊的指針。為了擷取更好的性能,smr的磁盤驅動需要配合磁盤盡量進行順序寫(磁盤内大部分空間是隻允許順序寫的空間)。如果驅動程式違反這一規範,驅動器會傳回錯誤或者進行重新映射。大部分核心開發者認為最有可能主機感覺類型的smr硬碟會更快的被廣泛使用,因為這類磁盤在目前的系統上還可以使用。

目前t10正在制定smr硬碟的相關标準和規範。不久這一規範就會被正式确定。ted指出目前就可以通過t10的網站來擷取這一草案。

随後便是對草案内容,特别是接口協定的一些讨論。目前最大的問題是大部分開發者手裡并沒有smr硬碟,是以對smr硬碟的任何評價目前都還是紙上談兵。

matthew wilcox和kent overstreet在lsf上談到了核心對persistent memory(不知道這個中文應該咋稱呼)的支援。一直以來都有傳聞說這個東東會有,現在終于我們可以自己買到了(到底哪裡有賣的?我也想買)。wilcox介紹了現在他為persistent memory做的一些支援工作并争取一下大家的建議。

persistent memory的讀寫速度和dram一樣快,并且在斷電以後資料不會丢失。為了滿足這個實體特性,wilcox寫了一個可以直接通路的塊裝置層并把它命名為dax,這個東東的設計靈感來源于核心裡面的xip(execute-in-place原地執行),因為這樣可以避免使用page cache。xip最初來源于ibm,開始的目的是為了在虛拟機之間共享可執行檔案和動态庫,後來也被用在嵌入式系統裡面來直接從rom裡面運作。既然persistent memory和記憶體一樣快,就沒啥理由要在記憶體裡面再放置一份copy了。目前,xip更像是一個起點,但是實際上還有很多工作需要做。是以wilcox重寫了它并重命名為dax。檔案系統可以通過dax驅動直接通路塊裝置并避免使用page cache,聽起來不錯,wilcox希望dax能夠merge進入主線并希望其他人能夠review代碼并給予建議。

但是目前dax還是有很多問題,比如首先現在使用者調用msync()去刷一段記憶體的時候實際上會sync整個檔案以及相應的中繼資料,這個當然不是posix的标準而且wilcox也準備去fix它。但是很顯然,這個不是僅僅修改dax就可以工作的(還需要修改msync),peter zijlstra在這時提出警告說任何修改sync的動作都有可能導緻使用者程式出現詭異的行為,因為使用者并不關心核心應該提供什麼功能,他們往往更關心的是現在核心提供了什麼功能。對于這一問題,wilcox認為社群應該修複,而不是讓使用者依賴這個看起來明顯有問題的核心行為。另外chris mason補充說他也贊成修複msync的錯誤行為,因為這個會讓所有的做檔案系統的人很happy,哈哈。

另一個比較大的問題是mmap()系統調用使用的flag map_fixed,它有兩層意思,一個是說将mmap的位址映射到指定的位址上,這個一般大家都知道,另一個隐藏的含義是說需要将mmap過程中遇到的其他頁都unmap掉...這個功能太奇葩了吧,是以wilcox建議增加一個flag map_weak,去掉第二個隐藏的功能。

get_user_pages()對于persistent memory無法正常工作,原因很簡單,因為對應每個page沒有一個struct page的對象。由于persistent memory裝置容量很大以後會有很多頁,是以沒必要為每個頁浪費64bytes來儲存struct page。dave hansen正在做一個新的get_user_sg(),他的作用是像其他儲存設備一樣支援scatter-gather list(這玩意不知道咋翻譯,不過做存儲的人都懂的)類型的io。這個功能對truncate()的支援還有一些問題,因為有可能truncate和get_user_sg存在一定的race,這樣就可能看到髒資料,wilcox目前的想法也很簡單:當get_user_sg運作時阻塞truncate操作的發生,這就排除了race的條件。對于這一點,overstreet有不同的想法,最近overstreet一直在重寫direct i/o,他認為dax的mapping操作和direct i/o很類似。他目前重寫direct i/o的宗旨就是建立一個新的bio,并幹掉get_block(這個玩意确實是一個太tricky的東東),如果dax也用這個bio就可以避免和truncate的race了。這時有人質疑如果i/o是bio-based就會讓nfs/cifs很悲劇,overstreet反駁說如果我們可以讓buffer i/o架在direct i/o上面就ok了。chris mason也認為如果真搞bio based,對nfs來說工作量應該還好。overstreet也補充說新的bio僅僅是一個包含了很多page的結構體而已,言下之意也是問題不大。

這個session最後沒啥結論,可能社群還需要先去review dax和新的direct i/o的代碼然後才能再下結論。

trinity是對核心系統調用進行測試的工具。通過對核心提供随機資料,trinity已經報出了大量的核心bug。trinity的維護者dave jones在2014年的linux storage filesystem and memory management大會上解釋了記憶體管理track技術以及他如何使用此工具發現了記憶體管理子系統中的bug。

他首先介紹了al viro的想法:利用mmap()建立一段記憶體,在該段記憶體的中間unmap掉一個單獨的頁,并且将結果傳入各種系統調用中,以觀察會發生什麼。随後,他說:“全亂套了”。出現了大量的bug,記憶體管理社群需要花很多努力去修複它們。但是,他說在3.14-rc7核心中已經沒有什麼問題了,這是一件好事。

sasha levin也使用trinity發現了linux-next tree中的bug。這些bug經常會被引入mainline核心。在合并linux-next之前,必須要先測試它的穩定性了。

trinity适合發現那些無人使用的代碼段中的bug。比如huge page,page migration以及mbind系統調用。在mbind的支援下,所有的調用者都會先通過一個使用者空間的庫來檢查參數的有效性。而系統調用自身并不做此檢查。結果就發現了很多未被修複的bug。

記憶體子系統中的很多代碼目前沒有簡單的測試方法。trinity在此方面提供了一些幫助,但它在記憶體管理測試方面還處在初級階段。他今年的後續時間裡會繼續從事此工作。不過,目前trinity發現bug的速度要遠超過bug們被fix的速度。

fedora使用者報告了很多關于大頁(huge pages)的bug,它們跟trinity發現的問題是同源的。用正常手段複現這些bug很難,因為涉及java runtime及諸如此類的應用。但是dave對此的關注不夠。至少在現在,透明大頁僅僅簡單地關閉了trinity測試。

trinity對于crash的複現功能也在不斷發展中。該工具能夠記錄下他所做的一切,但是日志自身是個很重的操作,他記錄的時間與crash的時間未必一緻。很多crash是kernel内部狀态的崩潰導緻的。導緻crash的操作可能是很久以前發生的。是以,查找crash的原因比較難。

dave給記憶體管理的開發者提了2個需求。一個是任何人都能為現有的系統調用增加标記。通過此标記,他可以記錄下何時開始測試。另一個是,他希望更多的開發者能夠使用trinity。上手有點難,但這不應該是我們拒絕trinity的理由。

目前核心社群有許多通過壓縮記憶體内容來提升記憶體使用率的方案,其中zswap和zram已經進入了主線核心。這兩個實作的共同點是通過壓縮記憶體内容換取空間并最終取代swap,不同點也很明顯,zram模拟成了一個特殊的塊裝置這樣可以當作swap裝置來是使用,而zswap而是通過"frontswap"來徹底避免swap。

bob liu有一個session專門介紹zswap以及它的性能問題。zswap通過将需要交換出現的資料頁壓縮以後放置到一個特殊的有zbud記憶體管理器所管理的區域中。當zbud滿了以後,他會将頁再置換到真正的swap裝置中,這個步驟就會牽涉到資料解壓,寫資料到swap裝置兩個操作,并會顯著得降低系統的效率。為了解決這個問題,bob有幾個方案:一個是将zswap做成一個寫透的cache,任何寫入到zswap的page會被同步的寫入到真正的交換裝置,這樣經zswap的資料清除會非常簡單,但是基本上和原來的swap裝置差別不大了。另一個方案是讓zswap可以動态調整大小,這樣就可以根據需求來變大,但是即使做到這樣,我們也僅僅是推遲了問題的出現并沒有最終解決上面的問題。

bob希望社群能夠有一些建議如何來解決這些問題,可惜的是,mel gorman卻指出無論是zram還是zswap都沒有一個關于它們潛在收益的系統分析。當人們做一些性能測試的時候,他們一般會選擇specjbb,這個并不适合對zram或者zswap做測試,而核心的編譯就更不能算作對記憶體壓縮系統的測試了。是以其實作在記憶體壓縮子系統最缺乏的其實是一個合理的性能測試workload,這個才是bob他們應該關注的地方。

記憶體管理子系統作為kernel的一個核心系統,對鎖的競争非常敏感,2014 lsfmm summit中有一個由davidlohr bueso主持的slot專門讨論記憶體管理中的鎖。其中有兩個鎖的競争非常嚴重,一個是anon_vma lock,它控制對匿名虛拟位址空間的通路,另一個是i_mmap_mutex,用來保護address_space結構中的幾個域。這些鎖之前是mutex,但是由于大多是隻讀通路,是以開發者将其換成了讀寫信号量(rwsems),然而一旦需要修改這些資料結構時就會引起明顯的性能下降。某些應用程式還可能出現"rwsem stealing"的現象,即一個線程插隊搶走了另一個線程正在等待的鎖。davidlohr說應該在mutex上加上一定程度的spin等待,這樣即使一個mutex是睡眠鎖,線程也可以在擷取它時嘗試spin等待一會以期望它馬上就會被釋放。他說為rwsem的實作加上spin等待可以使所有的workloads性能提升到原來的水準,目前社群沒有人反對合并這些代碼。

對于anon_vma,一個強烈的需求是不使用睡眠鎖。rwlock可以滿足這個需求,但是rwlock有公平性問題。waiman long在queue rwlock上做了一些工作,可以解決這個問題并提升性能。peter zijlstra說他又重寫過一遍這些patch,公平性問題解決了,但是沒有合适的benchmark來評價效果,而且這些鎖在虛拟系統下仍然有一些問題,但是這并不妨礙代碼合并進upstream。但是sagi grimberg說,有些code是在拿到anon_vma lock後需要睡眠的,比如invalidate_page(),是以轉換成無睡眠鎖會帶來新的問題。infiniband和 rdma也有這類需求,它們需要在拿到鎖後進行睡眠。rik van riel 建議盡快将相關代碼合并,但是davidlohr并不認為使用睡眠鎖帶來的性能損耗很嚴重。peter說對于這些無睡眠鎖的patch應該發給linus,并詳細解釋可能引起的問題,然後由linus決定是否合并。

davidlohr也建議重新開始mmap_sem信号量的讨論。mmap_sem保護程序位址空間的很多部分,是以經常被鎖的時間很長,導緻延遲增大,而且也在鎖裡處理了太多的工作。他認為如果隻是處理位址空間的一部分區域就沒必要鎖住整個空間,也許我們應該看一下range lock是否可以解決mmap_sem鎖競争的問題。michel lespinasse認為雖然range lock可能有用,但是我們應該先解決mmap_sem鎖太長時間的問題。rik建議我們是否應該換成per-virtual-memory-area鎖,但是peter說這個方法以前試驗過,但是結果會從mmap_sem鎖競争變成另一個"big vma lock"的競争。jan kara提出了在mmap_sem鎖下mm子系統調用檔案系統代碼的一些問題,除了性能問題,這種使用方法也可能帶來lock inversion。他為了解決這個問題研究了一陣,目前已經快ready了,但是還有一些其他問題,例如page fault代碼,但解決辦法還是有的。另一個問題是調用get_user_pages(),這需要調用者先拿到mmap_sem鎖,jan把調用換成了不需要鎖的get_user_pages_fast()。這種方法在大部分情況下可以工作,但是還有一些比較難處理的情況,比如調用get_user_pages()時mmap_sem被高層代碼鎖住了,video4linux videobuf2的代碼就包含了這類難處理的使用方法。另外一個擔心是關于uprobes,它需要在它加載進記憶體的代碼段中插入斷點,這會導緻在mmap()裡調用檔案系統代碼。peter建議可以重構mmap代碼,當mmap_sem被釋放後再進行一些初始化工作。雖然這會導緻一個視窗使得程式在初始化完成之前可以通路一些映射頁,但是他認為行為正常的程式都不會在mmap傳回之前就這麼做,是以也應該沒啥問題。最後還讨論了get_user_pages()一些變種的命名問題,但是也沒有一緻性的結論。

司諾登事件之後安全問題在過去一年中得到了更多的關注。兩個關于随機數産生的核心更新檔最近引起了大家的讨論。

兩個更新檔都是關于更新用于随機數産生器(/dev/random和/dev/urandom)的熵池。這個熵池有一個估計自己熵值的計數器。任何真正可以增加熵值的操作可以提高這個熵估值,同時往這個池子裡面添加資料也可以不增加熵估值,即使是這個資料是黑客故意加進去的。

第一個更新檔來自于h. peter anvin。這個更新檔就是對核心添加了英特爾新指令rdseed的支援。rdseed這個指令的傳回值可以作為僞随機數産生器的種子。在啟動的時候,rdseed的4個位元組将會添加到熵池裡面,但不會增加熵估值。另外,每隔1秒也将會傳回4個位元組加入到熵池裡面。當因為熵值不夠,而讀請求阻塞在/dev/random的關鍵時刻,rdseed可以傳回64個位元組到熵池裡面。

一些人可能很難相信這樣的黑盒指令,特别是深埋在處理器裡面,能夠毫無懷疑的真正提高熵估值。畢竟沒有任何人敢确信有真正的技術屏障能避免這個指令傳回一個nsa可控的數字序列。但是這些擔憂并不影響這個更新檔進入到核心主幹。

另外一個更新檔來自于kees cook。當一個跟随機數産生器有關的硬體初始化後,核心會自動通過一些跟這個硬體有關的輸入(16個位元組)加入到熵池,但不會提高熵估值。jason cooper擔心這些輸入可能會有副作用,比如一個流氓硬體可能會對熵池初始狀态産生過大的影響。cook覺得這個擔心完全是杞人憂天,覺得自己的更新檔和核心目前通用的添加熵值的做法,比如把mac位址添加到熵池中,沒有什麼不同。另外前随機數子產品的維護者也出來頂了一下cook同學。他提到即使輸入到熵池的值有問題,核心提供了一個接口可以把這個輸入完全取消掉,回到輸入前的狀态。即使這樣,cooper同學依然不依不饒,覺得這個更新檔就是會污染熵池。最後cook丢了一段狠話來結束這個争論,大意就是添加任何值都不會減少熵值,反而隻會增加。除了性能方面的損失外,我們沒有任何理由拒絕往熵池裡面仍東西。

最後先随機數維護者herbert xu合并了這個更新檔,我們将會在3.15中看見這個更新檔。

2014 lsfmm讨論了volatile range的一些内容。volatile ranges是一段特殊的記憶體區域,該區域裡的資料可以被所屬的應用程式在需要時重新生成,如果系統記憶體緊張,核心可以從volatile range區域回收記憶體。目前最新有兩種實作方式[1],其中一個問題是我們是否需要一個新的vrange()系統調用,另外一種實作是使用了madvise()。雖然很多工程師贊同使用第二種,但是madvise()接口也有一個很大的問題:如果使用madvise()來取消一段空間的volatile辨別,它需要傳回兩種值:(1)多少記憶體被成功辨別了non-volatile (2)多少volatile頁被系統收回了。madvise()隻能有一個傳回值,是以無法做為此類接口。

當一段區域被設定成non-volatile時,這個接口需要指出具體是哪些頁被收回了麼?目前的實作隻是傳回了一個單純的布爾變量表示是否有頁被收回,hugh dickins說一些使用者想知道更詳細的資訊,但是目前看還沒有任何計劃來擴充這個接口。另外一個問題是關于page aging,當頁被辨別成volatile時,它是否需要被"aged"資訊來表明已經很長時間沒有被通路?如果有的話,核心記憶體回收時就可以優先進行回收,但是目前在這方面大家還沒有達成一緻。另外關于名稱,huge說他不喜歡vrange的命名,他更傾向于是個動詞來表明會采取什麼行動,也有人建議采用madvise2(),但是經過那些review後他也贊同應該增加一個單獨的系統調用來實作。

keith packard提出了一個使用者案例,圖像驅動經常會配置設定一大段記憶體來緩存資料,但是目前的shrinker接口并不會經常被核心調用,是以它隻能繼續保留這些記憶體的資料,他希望驅動可以也可以調用volatile range的功能。讨論的最後也提出了一些api問題,如果一個程序對volatile range區域發生了寫操作,那這段記憶體是繼續保留volatile還是自動轉換成non-volatile?一些開發者期望實作第二種行為,但是目前的作者john stultz不太願意這麼做。

目前的接口是基于記憶體的,即volatile range的描述是一個基位址和一段長度,另外一些patch版本是使用了基于檔案的接口,即用檔案的一部分來表示。android上的" ashmem"期望可以被基于檔案的接口替換,但是john認為它可以内部通過基于記憶體的接口來轉換一下。而keith有對于檔案接口的更強的需求,圖形系統通常位址空間較小,特别是在32位系統上,是以他更希望有一個基于檔案接口的api。

這個讨論最終也沒有一緻的結論,未來可能會有新的patch版本,但是具體是哪種形式也還說不好。

在測試工具上,我們還需要做些什麼?除了可用性,其實還有很多可以改善的地方。

dave chinner,檔案系統測試工具集xfstest的維護者。xfstest不僅僅支援xfs檔案系統。現在有越來越多的人在使用xfstests,并對它做出貢獻,但是仍有很多可以改進的空間。當開發者發現了檔案系統的bug時,對bug的fix通常包括一份對測試工具集的貢獻,進而使得我們以後能更好的測試此bug。

james bottomley問到xfstests具有多大的代碼覆寫率。根據redhat的測試,xfstest對xfs檔案系統的覆寫率能達到75%。ext4為65%。目前對ioctl沒有覆寫到。常用的代碼路徑則都被很好的測試了。那些不常用的代碼還缺少覆寫。

目前具有增加對裝置掉電的測試需求。dave說目前已經有一個名為“crashme”的腳本能夠應用于機器的随機重新開機。xfs具有特殊的ioctl(),它會立即切斷io流,模拟底層裝置的突然掉電。是以,并不需要模拟實體上才掉電測試,使用現有的軟體工具即可。

al viro說如果儲存設備的分區過小,某些測試可能會失敗。dave答曰:xfstest中有個機制能夠指定每個測試需要的磁盤空間。通常,最小的空間為5-10gb。此時,大多數測試都能跑起來。在另一個極端,他在一個100tb的裝置上跑了一些測試。但是,還是需要避免一些測試會占滿整個磁盤空間。雖然一些測試在上千次操作後可能會失敗,但xfstest的日志重演以及對失敗的原地複原功能還是能給debug提供一些資訊的。

matthew wilcox說,有時定位一個test失敗并不容易。dave答曰:xfstests的目的是告訴開發者有bug存在,而不是如果找到這個bug。但他會接受一些這方面的patch。

dave jones問到:在儲存設備層是否需要類似的測試用例。ric wheeler答曰:裝置廠商有他們自己的用例。但是這些用例不會公開。mike snitzer有一個針對裝置映射(device mapper)的工具集。

dave jones詢問了社群,還需要哪些種類的測試。james立即說對于性能回歸,還需要更好的測試方式。mel gorman補充到,一旦涉及到io性能,社群就完全迷茫了。不過僅有測試或許不夠,很多問題還需要更詳細的細節分析。

在3.10版本的開發中,raid5被移除了合并視窗。一些人可能很快會發現問題。那麼利用裝置映射組裝磁盤時,可能就會導緻核心出問題。開發者沒有好好的測試這段代碼。

ted ts’o建議目前很多開發者還沒有了解測試的真正意義。他希望能夠有一個向新手介紹此方面内容的方式。否則維護者必須要做那些應該由開發者做的工作。

dave說,當我們寫測試用例時,我們很難為那些尚未成型的功能寫測試。

在結尾,大家探讨了那些還沒有被好好測試的領域。比如“filesystem notification”系統調用。一些不常用的記憶體管理相關的系統調用—mremap(),remap_file_pages(),目前還沒有被測試覆寫到。覆寫numa記憶體政策的代碼也會非常有用。開發者或許最終會寫出這些測試。并且希望大家都來運作這些測試用例,并向社群報告bug。

最近幾年來,核心對程序和記憶體分布的處理一直都是許多讨論和開發的主題.最近幾個開發周期這種步調有所減緩, 但正如在2014 lsf&mm年會上主題讨論所看到的,在這個領域依然有些工作需要去做。rik van riel, peter zijlstra, and mel gorman共同主持了一個讨論,首先提出了這樣一個問題:許多代碼被合入了,下一步将會發生什麼?peter 觀察到,當代碼在核心後,很少人實際上去試着利用它來提升numa的性能。現在最需要的是使用者關于那些代碼工作得 怎麼樣和什麼被提升的回報。

davidlohr bueso說,在他的系統上,他通過手工調優能比使用自動numa特性得到更好的性能提升。rik補充道,從他所能 判斷的,在帶有4個node的系統上,這一切工作得最佳,然而在多于4個node的系統上,情況将變壞。mel問為什麼會這樣? 有一些推斷認為page hinting的開銷導緻了那,或者可能是更大的numa系統上更複雜的拓撲沒有被很好地處理。但似乎沒 有人知道真正的問題是什麼。

mel說真正了解那些numa性能缺陷需要采集大量的資料。但那些資料的采集是昂貴的,中肯點講,它破壞了負載。他很難 運作他的測試;他真正沒有找到好的辦法讓别人也來做這。似乎rik, peter和mel沒人都采用了各自的方法來測量numa性能; 關于這個領域,他們之間沒有談論太多。也就是說,一些好的建議是,每個開發能用他自己獨特的方法來發現不同的問題。

rik提醒到,當numa代碼盡力把匿名頁放在與使用它們的程序相同的node上時,page cache頁面還沒有采用這種想法。他的 問題是: 它是否應該也這麼做?目前還不清楚page cache位置性是否将會帶來更好的整體性能。mel說這個領域目前被完全 忽略了。

johannes weiner說本地于node的page cache配置設定政策也許沒有意義。假如系統盡力在本地配置設定那些頁,它将會付出排出其 他有用頁面的代碼。那時,核心是在以強制為其他頁面進行磁盤讀寫來換取本地頁面-很可能這不是個可取的辦法。目前, 頁面回收強黏連于node,是以當在其他node上的老的頁面衰退後,一些node将會有沉重地回收壓力。是以他說,縱使不是 每個地方都有記憶體壓力,也強制要求在所有node上進行一些頁面老化,這可能是個好的辦法。跨越所有node的交織page cache 頁面也許能增加記憶體的使用率和減少有效頁面的老化,縱使會導緻更多的跨node流量,也會獲得一些好處。

有一些抱怨,網絡套接字上程序通信應該打包成組到相同的node上,但現在這并沒有發生。似乎有點與網絡開發者脫節了, 沒有讨論它們應該如何打包成組。把面向網絡的程序移到與網卡相同的numa node也是有價值的,但目前numa代碼完全沒有 這方面的考慮。改進網絡互聯與numa分布的內建不是件容易的任務,它很可能設計到在網絡棧多層間攜帶numa資訊。

這個會議沒有達成太多的結論就結束了。似乎在numa分布這個領域依然有太多的工作要做。

記憶體壓縮是一個為了建立更大實體上連續的區域而重定位記憶體中有效頁的過程,也就是,記憶體去碎片。它在許多方面是有用的, 不僅僅是在激活大記憶體頁可用性方面;但它明顯也有一些自身的問題。在今年2014 lsf&&mm年會上,vlastimil babka舉行 了一個簡短的會議來揭示這些問題。

在vlastimil對記憶體壓縮如何工作以及相關開銷問題做了整體介紹之後,rik van riel提出了在這方面要關注2個關鍵問題: (1)能否讓記憶體壓縮代碼執行得更快?(2)當記憶體壓縮變得太昂貴的時候,它是否應該被跳過?

在過去幾年許多記憶體壓縮的缺陷被修複,但依然有一些還沒有。經常被問的問題是如何能更容易地發現他們?寫一些測試程式 來發現它們變得越來越難了。這些缺陷的出現源于在系統上以某種方式運作特定的負載。似乎沒有任何簡單的方法能将這些能 揭示問題的負載抽象成一個個測試程式。

有意思的是記憶體管理開發者們并沒有真正了解記憶體壓縮問題發生的原因。一些負載很明顯讓記憶體壓縮變得很昂貴,但為什麼是 這樣卻模糊不清,是以我們對這些問題為什麼會發生需要有更好的了解。一個方法是在核心中添加一個計數器來記錄記憶體壓縮 有明顯耗時行為的次數。當這個計數器開始增長的時候,它暗示記憶體壓縮代碼可能存在着缺陷。然後,試着弄清楚那些缺陷隐 藏在哪。

一個問題是hugepage被swapout時可能有大量0位元組的page,這會影響io性能,可能的解決方式是在swap的時候先split。

另外在page fault的時候disable huge page的申請也能夠提升一定的性能,這個問題一個解決方式是allocation挪到khugepaged處理。

再一個問題是numa帶來的:如果在一個local的node上無法申請到一個huge page,是到remote node上申請好,還是在local node上申請個小的page。matthew wilcox建議如果要在remote node上申請頁的話,應該申請small page,huge page隻能夠在local node上申請。

有一個議題是關于在記憶體碎片化嚴重時disable huge page的申請。将這部分交由khugepaged進行處理,在有資源的時候進行合并。

peter zijlstra的提議是将透明大頁先徹底移除出核心,将這部分頭痛問題放到後面再解決。andrew morton和hugh dickins都同意透明大頁使得記憶體管理變得複雜了很多,同時帶來很多問題。

透明大頁目前僅僅用于匿名頁。kirill shutemov的工作是将透明大頁應用到page cache中。這部分已經開始了1年多,一些benchmark也顯示這有重要的提升,代價是需要為page cache register加一把保護鎖。當huge page需要被split的時候将變得更加麻煩。

這個點上引入了另外一場讨論,是否需要對page cache的huge page進行split。對于匿名頁有些條件是不可避免的需要split,比如對一個頁進行copy-on-write的時候。對于page cache有些不一樣,允許一些程序将一個huge page的一部分當做small page進行map,同時其他程序把它當成一個huge page的page cache。但是這存在一個reference統計的有趣問題。

對于huge page,每個small page都對應有一個struct page結構。huge page的ref count儲存在第一個struct page結構中,其他“tail”的page對應的ref count是不使用的。這樣的話,如果一些程序隻能夠看到huge page的部分tail page,這部分tail page的ref count該記錄到什麼位置?rik建議最開始的page需要用于維護整個huge page的ref count,對于一個包含512個small page的huge page,head page的ref count被設定為512。當被split的時候,head page的ref count對應的減少。這樣核心依舊知道huge page共有多少reference。

hugh擔心這會導緻head page作為huge page一部分和作為一個small page兩種情況的混淆。rik表示這可以被解決。

andrew建議如果這使得事情更加簡單,那麼沒問題,可以進入upstream。他也指出,記憶體管理子系統設計之初就是允許page size可變的,但是沒人在這個方向上思考。将來的特性都需要以這個為最終目标去考慮。類似huge page這樣的特性進入核心就是一個錯誤。未來幾年希望在記憶體管理子系統上有一些fundamental的改變。

2014 lsfmm 繼續讨論了memcg相關問題。

在2013 summit上, michal hocko提議修改memcg裡softlimit的實作,但是他後來的嘗試都不成功,今年他換了一種方式:不再修改softlimit而是再加一個新的"low limit"。softlimit是提供當記憶體緊張時memcg裡記憶體使用的上限,如果目前記憶體充足,memcg可以使用超過softlimit限制的記憶體,一旦記憶體緊張時,記憶體回收代碼會将memcg推回至softlimit,如果記憶體依舊緊張,memcg将會被回收的更多,有可能低于softlimit,但是有的使用者不希望memcg的記憶體使用量低于某個最小值,這就是low limit的目的。如果memcg設定了該值,記憶體管理系統即使非常缺少記憶體也不會去回收該memcg,它更傾向于一種保證。

對于這一提議大家有一些意見,peter zijlstra建議直接把softlimit改成一種保證。既然現在大家都不喜歡目前softlimit的實作,為什麼不直接改掉呢?這裡有個問題是目前softlimit的預設配置是"ulimit",如果把softlimit改成了保證就需要把預設值改成0,這會改變使用者abi接口。雖然還不清楚會給使用者帶來多大的影響,但peter認為,任何使用softlimit的人已經會改變它的值。但是michal對于改變接口很緊張,他也不是唯一的一個。另外一些開發者則質疑建立這套限制機制會導緻系統進入out-of-memory狀态,他們不認為任何情況下都能為memcg保留最小量的記憶體,因為可用記憶體的總量并沒有保證,但是最後大家還是願意讓michal嘗試一下。目前softlimit的實作代碼有些搓,大家也不想再增加另一套限制機制,如果仍使用目前的softlimit,則可以增加一個"oom"标志來允許使用者使用lowlimit行為。

peter zijlstra談到了在一些情況下驅動需要配置設定"pinned"頁,即程序位址空間裡不允許被swap和cpu間migrated的頁。pinning對于rdma裡的buffer,perf子系統,video frame buffer都有用途。被pin住的頁曾經類似于被mlock()鎖住的頁,這兩種頁都在mlock limit限制下一起記賬,相當于給程序設定了可鎖記憶體的上限。但是最近改變了記賬機制,被pin住的頁和lock的頁分别進行統計,這實際上翻倍了程序可鎖記憶體的數量。在一些系統上這可能會是以導緻oom,是以peter希望能改回原來的記賬方式。但是andrew morton說這很難,因為回退回去會破壞另外一些使用者,這件事情也沒有最終的結論。

peter關心這個功能的原因是實時系統社群的開發者發現mlock()不能滿足他們的需求,将一個頁鎖住隻保證了它不會被swap,但是核心仍可以将它在ram之間遷移,而這會造成實時應用程式的延遲和軟缺頁。核心目前不會遷移被鎖住的頁,但是記憶體管理開發者保留了這個可能性,是以peter希望可以增加一套新的系統調用,mpin()和munpin(),可以将記憶體頁徹底pin住。當有了這些調用後,就可以再來看應該如何分開記賬。

程序從一個主控端遷移到另一個主控端上時,不可避免的需要将大量記憶體從一個socket發送到另外一個socket。vmsplice接口一個好處是能夠避免這其中的記憶體拷貝。但緻力于criu(checkpoint-restart in user space)項目的pavel emelyanov在使用中碰到有一些問題:

第一個問題是使用管道來移動記憶體需要申請兩個額外的fd,criu已經需要申請大量的fd,這有可能導緻open file descriptors的限制。al viro給的一個可能用于解決問題的方式是在/proc裡面找一個管道fd,以讀寫方式打開,然後關閉原有的兩個描述符。這樣能夠使得最後的fd數量減半。

當以splice_f_gift标志使用vmsplice的時候意味着指定的資料頁能夠直接在核心進行處理,而不需要進行拷貝。但實際情況卻是這些page最終還是都被拷貝了,即便這些拷貝操作并不是必須的。深入分析發現這樣做的原因是為了避免一些surprising的檔案系統的資料頁。檔案系統開發者們看起來已經達成一緻意見,對這部分page的處理工作量比較小,是以這個也許會被改變。同時也需要咨詢這段代碼最初的維護者nick piggin,看是否還有其他阻礙零拷貝的因素。

pavel的另外一個問題是發送到檔案裡面的page會進入到page cache裡面,但他期望的是直接寫入到目标檔案中。解決這個問題可以通過以o_direct方式打開用于splicing的fd。但是随之又帶來了另外一個問題:寫是同步的,導緻整個進度變慢。pavel更期望使用vmsplice時能夠異步的o_direct寫。這個在可行性上并沒有太多問題,但這部分工作做”可能不太有趣”。

最後一個問題是如何将另外一個程序的位址空間頁在不拷貝的情況下發送出去。james bottomley建議是在fork系統調用裡面的一些機制可以用來實作。程序并不會真的被fork,而是會拷貝它的一份位址空間,這樣遷移程序就能夠直接得到它的頁。這個的實作很tricky,假如能夠實作的話,将使得遷移更加高效。

繼續閱讀