閱讀860 返回首頁    go 阿裏雲 go 技術社區[雲棲]


阿裏內核月報2015年03月

Virtual filesystem layer changes, past and future

LSF/MM 2015峰會上,虛擬文件係統也吸引了足夠的目光。LSF/MM 2015峰會上,虛擬文件係統也吸引了足夠的目光。

首先是一些需要繼續期待的變化。一個是替代mount()係統調用的工作,Al做了一些但是還沒能發出來review。另一個是revoke()係統調用,也隻有框架。

進展比較大的是iov_iter接口的轉變,Al計劃到4.1版本完成aio_read()和aio_write()裏麵用iov_iter。還有其他一些地方也需要轉變,不過大的問題已經沒有了。網絡協議棧的iov_iter的變化在過去一年已經完成了,sendpages()路徑上還沒改,不過應該也沒什麼障礙。 splice()係統調用改起來就麻煩一點,唯一的問題是用戶態FUSE文件係統模塊,希望做零拷貝IO,從splice() buffer直接到page cacha裏。splice()最早被引入內核的時候,這種“page stealing”是為了優化的效果而設計的。不過後來發現有很多問題,比如一個也被直接塞到page cache裏文件係統會出問題。於是Nick Piggin在07年把這個特性去掉了,後來一直沒加回來。FUSE文件係統做零拷貝就沒問題,也一直沒去掉,所以現在隻有FUSE沒有轉成用iov_iter了。Al的想法是全麵恢複零拷貝,似乎有點麻煩。splice_read()今年就會用上iov_iter。 需要從iovec改成iov_iter的地方還有很多啊,一點一點來吧。

待續。。。

Overlayfs issues and experiences

在今年的LSF/MM峰會上David Howells和Mike Snitzer有一個session,主題是關於overlayfs目前存在的一些問題。Howells抱怨說overlayfs對一些安全技術目前支持不夠(比如SELinux),由於overlayfs的設計原因,任何一個文件在overlayfs上有3個inode,隻讀層有一個,可寫層有一個,overlayfs自己還有一個,這個帶來的問題就是不知道到底應該使用哪個inode作為安全信息的來源,不過似乎這些問題目前都已經搞定了。

另外兩個問題是關於文件鎖file locking和fanotify。當一個在底層已經被鎖住的文件在上層被寫入的時候(這個時候上層要創建新文件),這個鎖是否需要傳遞上去?如果剛好這個文件對應有兩個上層文件,那是都應該上傳麼(fanotify也存在類似的問題)?。Al Viro同時指出,一個被隻讀打開的文件和一個被可寫打開的文件在overlayfs中會有不同的inode號,這個可能會給很多應用造成困擾。對於這個問題,Bottomley認為我們需要先搞清楚用戶到底關心什麼,雖然一些posix語義在overlayfs中被破壞了,但是用戶真的關心麼?(個人感覺這個真的是一個好問題,如果沒人關心,我們為啥還要去費力的優化呢)

由於docker的大熱以及docker準備用overlayfs,這個話題也被引申了出來。docker嚐試過btrfs,但是很失敗,而一些基於dm的方案目前看也不適合,因為docker希望Go程序可以一次編譯到處運行,因此需要靜態連接很多庫,但是udev不提供靜態庫(這個似乎不是一個技術問題)。。。所以docker真正希望切換到overlayfs的一個重要原因是在很多容器之間共享相同的page cache(這也是淘寶最初選擇overlayfs的一個重要原因),有人提到KSM似乎可以解決,但是KSM目前的實現還要根據內存的內容hash來搞定,這個實現還是有很多overhead的,所以目前看overlayfs似乎是一個不錯的方案。

帶緩存的異步磁盤IO

帶緩存的異步IO,或者說帶page cache的異步IO,或者說buffer aio,真的已經是一個萬年老話題了。各種競爭方案層出不窮,除了在preadv/pwritev上做文章的,還有通過syslets去解決的方案,但沒有一個被mainline接受。

按照社區的傳統,往往到最後我們會看見一個對現有體係改動最小,實現的功能也最少(所以引出的反對意見也最少)的方案先被接受,在buffer aio這個事情上這個規律再次應驗了。

首先介紹一下背景,大家都知道Linux native aio必須是Direct IO,這給應用場景帶來了很多不便。第一,Direct IO對對齊有額外要求(有幾個使用Direct IO的人知道嚴格來說起始地址並不是按照512B對齊就萬事大吉了,正確的要求是需要和底層塊設備的logical block size對齊,而logical block size並不總是512B,比如cdrom就是2kb,其他一些虛擬塊設備也有能力在代碼裏自己隨意定義非512b的logical block size。因此最安全的做法是在發起IO前調用BLKSSZGET這個ioctl去查詢一下才知道。把這些詭異的細節甩給用戶去關心其實非常危險);第二,由於失去了page cache支持,一個嚴肅的應用往往需要自己實現用戶態緩存層來保證性能,比如MySQL;第三,對於不想自己實現緩存層,一定需要page cache支持的應用來說,就隻能用線程池來做並發IO,這樣做不僅延遲高了,更大的問題是多線程之間的同步不是那麼好寫的,比起單線程做異步提交和異步完成,前者更容易有bug,對應用程序員的要求也更高。因此很久以來就有人唿籲應該支持帶page cache的aio。

這次2015年的LSF上,Milosz Tanski帶來的buffer aio解決方法隻能勉強稱為一個workaround。他加入了一個新的名為preadv2的係統調用 ,這個係統調用的形狀很像preadv,隻是增加了一個標誌用來指示非阻塞,從語義上來說它其實就是標準的NON_BLOCKING標誌而已— 進去後看看要的數據是不是都在page cache裏,如果都在,就拷出來,完成;如果不全在,把相應的請求都發射給磁盤,但不用等這些請求返回,直接給用戶返回EAGAIN — 隻不過原先在Linux上NON_BLOCKING標誌不能針對磁盤文件的fd使用(你可以用,但不會有任何效果)。這裏算是又把這個語義實現完整了。

那麼應用要如何使用這個“buffer aio”呢?Tanski建議的方法是這樣,你還是要基於原先的多線程版本改,但在打算把任務扔到IO線程池裏之前,先用一個單獨的線程 — 不妨管這個線程叫查詢者線程 — 去走preadv2嚐試一下非阻塞讀,如果運氣好這些請求都被page cache滿足了,那麼你就不用再管你的IO線程池了,可以直接往下走拿著這些數據去做別的事情了。

那麼這個東西和我想去讀之前,直接用現有的接口readahead() + fincore()去查一下數據在不在page cache裏又有什麼區別呢?Tanski說區別在於fincore不能確保它告訴你存在的數據,當你後邊用的時候,數據還能保證在那裏。這是顯然的,內核不可能允許一個係統調用帶著對page cache裏某些頁的引用計數返回,不然如果你以後一直不來讀,這個頁要什麼時候才能釋放?因此有一定概率你後邊去讀時,仍然會觸發同步IO,阻塞住查詢者線程。老實說,我覺得這個說法很牽強,因為發生這種事情的概率太小了

Tanski說他已經在自己的一些內部項目上使用這個新係統調用做了改造,成效斐然。比如在一個架在ceph上邊的列存儲數據庫上,延遲降低了23%等等。

上邊說的都是讀,那寫的時候要怎麼辦呢?因為page cache的寫一般情況下本來就是異步的,實際上要實現上邊說的這種所謂“buffer aio”,對於寫操作這一側基本不需要修改。

參加這次討論的人對這個patch沒有太多反對意見,我們有望在4.1內核裏見到它被合並進來。其實如果你覺得這麼做會有用,並不需要等待新內核,完全可以在自己目前的環境中使用readahead() + fincore() 去達到類似的功能。注意使用老發行版的用戶,比如rhel5,在man read ahead時會發現它說“readahead() blocks until the specified data has been read.”,這純粹是因為老發行版帶的man早已過時了,查看最新的man projects頁麵,你會發現這句話已經被刪除掉了。

另一個需要注意的點是,不管是Linux native aio還是這裏說的preadv2(),或者是readahead(),仍然都有可能因為需要讀取文件係統元數據而發起同步IO並阻塞。

memcgroup相關問題

mem cgroup經常是LSF上討論的熱點,但這次已經不是了,這可能預示著memcg主要的問題都解決得差不多了。

早先memcg隻能跟蹤、限製、並且回收用戶內存,也就是說page cache加匿名頁。對內核自己占用的內存無能為力,就算是它代表用戶去申請的,也沒有辦法限製它。後來加入了對內核內存的追蹤,memcg就可以統計、限製這一類內存了,但仍然沒有辦法回收它們,隻是說達到一定上限後就不再分配而已。再後來Vladimir Davydov在這上做了大量工作,對一些很常用又可以回收的結構,比如dentry和inode,實現了per-memcgroup的LRU,這樣針對它們的回收也可以工作了。

現在討論的主要問題集中在是否應該把“這個memcgroup的內核側總共允許使用多少內存”這樣一個開頭暴露給用戶,還是改變memory.limit_in_bytes的語義,讓它表示“用戶進程加它的內核側一共允許使用多少內存”。討論這個問題顯然是有意義的,因為對於多數普通用戶來說,對內核使用多少內存是合理的這個事情根本沒有概念,也很難估算出來,而針對總量限製後用戶就可以像之前使用係統虛擬化方案時指定一個虛擬機的內存那樣指定一個mem cgroup的內存了。但暴露出來單獨的內核內存使用上限也有它的好處,比如你可以通過這個接口間接限製最大可以創建的進程數量等等,PeterZ說很多用戶都想要這個特性,但可能單獨實現它更好些,通過memcg來實現太隱晦了。具體的行動需要開發者們再去更多地收集用戶需求後決定。

另外,開發者們在LSF上也研究了一下memcg現在還有哪塊大塊的內核內存沒有跟蹤到,看起來頁表本身占用的內存值得關注,下一輪的開發應該會解決這個問題

Filesystem support for SMR devices

今年的LSF/MM上,Hannes Reinecke和Adrian Palmer分別介紹了介紹了他們在SMR設備上的工作。前者主要是讓塊設備層支持SMR設備,後者的主要工作圍繞讓Ext4文件係統支持SMR設備展開。SMR設備主要的特點是在某些區域允許隨機寫入,而在另外一些區域僅允許順序寫入,Reinecke的工作就是讓塊設備能夠遵循這一規範。由於SMR設備不允許跨區域IO,所以Reinecke使用紅黑樹來記錄SMR設備上每個區域的狀態,這些信息並不是在磁盤掛載上的時候就獲取到的,而是在使用時進行查詢。目前的規範中,並沒有規定SMR設備上每個區域的大小一定要相等,盡管目前的設備廠商都還是使用相同大小的區域,但未來會怎樣誰都說不好。Ted Ts'o建議對於區域大小不相等的設備的支持還是需要的,同時他也指出目前采用的延遲加載區域信息的做法可能會造成磁盤性能的下降。

Reinecke指出目前存在的另外一個問題是IO調度器的亂序問題。由於SMR設備在某些區域隻能繼續進行順序寫,否則就會返回IO錯誤。對於這種情況,使用nop IO調度器可以在一定程度上解決這個問題,因為nop調度器僅對連續的IO請求進行合並。對於亂序寫入請求,Reinecke指出可以將這些寫請求重新插入IO調度器的隊尾來解決。但是Dave Chinner指出從文件係統層麵看,文件係統是期望寫請求按照發送順序進行下發的,如果要保證這一行為,就需要將對SMR硬盤寫入操作串行化。而Ted提出了另一個更寬泛的問題,是否需要讓一個普通的文件係統針對SMR硬盤進行優化。Chinner的觀點是SMR設備的問題還是讓固件來解決的好(但是,LSF/MM剛結束,Dave Chinner就在xfs郵件列表中發了一封郵件來說明他對於xfs支持SMR硬盤的想法)。

Adrian Palmer隨後介紹了他的工作。Adrian的想法是借助Ext4的block group來保存SMR設備上區域的相關信息,包括區域大小、寫指針位置等等。他首先要解決的問題就是目前SMR設備的區域大小為256MB,而默認塊大小為4K情況下,Ext4 block group隻能表示128M的磁盤空間。因此,他需要增大塊的大小,而且這個數值需要能夠改變來支持以後更大的區域大小(解決方法很簡單:bigalloc特性);此外他還需要解決O_DIRECT IO、寫請求亂序等其他問題。Ted說他的一個實習生在修改Ext4的日誌係統以使得Ext4的日誌在寫入時對SMR設備更加友好。Dave Chinner則更關心fsck的問題。當fsck過程中需要對一個已經寫入的數據進行覆蓋時,麻煩就來了。Ted說隻能進行256MB的RMW(read-modify-write)來解決。Dave Chinner指出,最好的方法是硬盤廠商提供一個新的write allocate接口,寫入數據的時候直接返回邏輯塊號,這樣磁盤就可以決定數據放到哪裏了,而不用操作係統管理這件事了。當然這個想法基本屬於YY,什麼時候能出來完全沒有時間點。

A rough patch for live patching

Linux Kernel 4.0中最重要的新特性應該就是內核熱升級(live patching)了,即在不停機的情況下修複一個正在運行的Linux Kernel中的問題。當然,4.0版本中合並的代碼僅僅是這一特性中最基本的一些代碼,真正的核心代碼還遠沒有達到能夠合並進入主線的狀態,並且現在看起來,想合並起來有點兒困難。

4.0代碼中目前已經合並的代碼是kaptch和kGraft項目的公共部分,即對於插入的熱升級內核模塊的添加、刪除等管理接口。目前代碼中缺少的最重要的組件是一致性模型的相關代碼,即如何判斷當前內核所處的狀態是否可以安全的將補丁打上的代碼。已有的兩個項目:kpatch和kGraft在一致性模型上的分歧完全是不可調和的。

kpatch使用的一致性模型是通過調用stop_machine()做停機檢查所有進程的棧信息,確保需要修複的函數沒有被運行再打上補丁;kGraft則使用了一種類似RCU的模型,即每個進程都從“舊宇宙”進入到“新宇宙”之後,問題函數才會被替換掉。

上述兩種一致性模型都各自有自己的優缺點,Josh Poimboeuf嚐試將上麵兩種模型進行合並,他的做法是保留了kGraft中的新舊宇宙模型,同時通過檢查所有進程的棧信息來加速切換過程。理論上講這一方案利用了兩種方案的有點,但是:

Peter Zijlstra提出了他對棧檢查的反對,他認為:“目前檢查棧的方法是用來進行debug的,用這個方法來確保內核的完整性是不可靠的”。Ingo Molnar則反駁道:“100%準確太難實現了。目前檢查棧的代碼中有很多問題,隻有在你跑到的時候才暴露出來。”這就等於說用戶為了避免宕機而進行熱升級,結果在熱升級的時候用戶有可能真的宕機。但是這似乎就是目前的正式狀況。Ingo隨後也提出了自己對於一致性模型的想法,讓所有進程跑到一個一致的沉默狀態,這個狀態不會影響打補丁,然後把補丁打上。

但是如何定義這個狀態呢?內核線程又不能跑到內核外邊運行,長期阻塞在內核中的進程也需要被喚醒,這樣的改動量也很大。看起來似乎kGraft的新舊宇宙模型更好一些,或者最簡單暴力的方法就是不檢查,直接打補丁。當然這樣就會限製可以打的補丁的範圍。

Ingo隨後又提出了另外一個觀點,kpatch和kGraft整個路子就不對,他覺得不要在已有內核上打補丁,記錄當前內核的狀態,通過kexec啟動一個新內核,將此前記錄的狀態在新內核上恢複就好了,完全不用擔心一致性模型的問題。這個想法並不新,此前就曾經有人提出過相同的想法,並且在一些領域已經實現了內核熱升級功能,並且速度很快,10s左右就能完成。但是目前對於內核熱升級有需求的用戶都希望熱升級能夠在秒級內完成,所以還是得回到目前kpatch和kGraft的路子上來。

總之,內核熱升級項目還會繼續,但是各位想在4.1內核中看到完整功能的內核熱升級基本不太現實,所以讓我們耐心等待吧。

Reservations for must-succeed memory allocations

LSFMM 2015大會上有一個議題是討論如何在空閑內存較少的情況下必須成功地分配內存。Michal Hocko說內存管理子係統的開發者反對使用__GFP_NOFAIL標誌,因為這個標誌會試圖滿足內存申請而不管代價有多大。但是這樣會使開發者轉而在自己的代碼中使用無限重試循環來申請內存,這顯然不是解決問題之道。內核代碼裏到處是重試循環,出現bug時不僅不容易定位和解決,而且將“必須成功”的要求隱藏在了內存管理子係統之外。從內存管理開發者的角度來看,必須盡快去掉這些循環,因此Michal號召在座的開發者開始刪掉。他建議當遇到這種循環時,可以先簡單地替換成__GFP_NOFAIL標誌的申請。當這些都替換完之後,下一步就是研究如何去掉“必須成功”的申請。Michal曾經嚐試自動化地定位這些重試循環,但是使用Coccinelle後發現這個問題很難搞定。

Johannes Weiner提到他最近在嚐試改進OOM機製,但是也比較難搞定。無論OOM工作的多麼好,它始終是基於啟發式算法且總會有一些錯誤決定,而且OOM有很多錯誤處理路徑,修改後也比較難以驗證。另外OOM路徑也比較容易發生死鎖,當一個進程拿到某個鎖後再申請內存時,潛在地希望被OOM選中的進程不需要獲取這個鎖。現在就有一些跑在memcg裏的負載,這些程序都嚴重依賴同一把鎖。在這些係統上,當內存較少而發生OOM時就有可能導致整個係統死鎖。他認為不應過分依賴OOM機製,內核最好在在開始一個transaction或進入某些不能回退的路徑前保證可以申請到資源。最近有些討論提到應該建立內存預留係統,但這也有弊端,比如會浪費內存。但是可以通過標記預留係統內的頁可回收來減少浪費,內存可以被回收並重新分配出去。

James Bottomley說可以預留一個頁左右的內存,但XFS maintainer Dave Chinner說並不是這樣,比如在XFS裏創建transaction來生成一個文件時,首先要申請內存來創建inode並更新目錄,這個過程可能需要申請內存來保存和操作free-space bitmap,還有可能需要申請塊來保存目錄本身,總共下來可能需要1M的空間。這個操作一旦開始就無法回退,理論上是可以為XFS transaction設計出一套魯棒的回退機製,但可能需要數年時間而且可能會使內存需求翻倍,使得問題更加糟糕。另外一個問題是VFS在調用文件係統代碼前本身已經拿了一些鎖,設計這麼一套複雜的回退機製來避免一些corner case似乎不太值得。

在transaction執行前我們無法知道需要的確切內存量,也不可能提前將它們全部分配出來,但是我們可以預先估計最壞情況下的內存需求量並預留出來。對於一個XFS transaction來說,內存預留量是200-300KB左右,但文件係統也有可能根本就不用它們。當transaction運行時這些內存可以被拿去它用,但一旦需要就必須馬上能得到。XFS現在有一套預留係統,但是預留的是transaction日誌空間而不是內存,文件係統的並發度被可用日誌空間的量所限製,在一個有大容量日誌的繁忙係統上可以同時有7000-8000個transactions同時運行。預留係統工作的很好,可以預估出需要的日誌空間,可以把這套機製擴展到內存係統。

一些開發者提出文件係統之下的各層IO棧怎麼辦,即使文件係統知道自己需要多少,但是它不知道底層IO的需求量。Dave說這些軟件層幾年前已經改成了使用mempool,mempool本身也在改進。另外如果是文件係統疊加文件係統的使用方式可能會有些複雜,但是可以通過在底層文件係統上增加一套機製來向上層反饋最壞情況下的內存使用量來解決。

預留係統應該由內存子係統管理,在進入transaction之前,文件係統或其他類似的模塊申請最壞情況下的內存使用量,如果內存無法被滿足,請求此時應被暫停,並對預留係統的使用者數量進行限製。對於按需缺頁機製來說有一些複雜情況:當XFS讀取所有目錄塊來為新文件尋找空間存放時,需要為它們分配內存來保存在page cache中。大多數時候這些塊都不太常用且可以被馬上回收,因此Dave認為預留係統不應把這些塊計算在內,隻應計算被pin在內存裏的量。

Johannes認為所有的預留內存應該在一個大池子裏統一管理,如果一個用戶低估了需求並超額分配了內存,這可能會破壞對所有其他用戶的保證。而Dave認為這種不確定性應該由預留計算機製來負責,計算部分可以在transaction超額使用預留內存後打印出警告信息。

slab的預留分配也有一些挑戰,現在來看每分配一個slab對象應預留一個整頁,這會大幅度增加內存需求,比如XFS的一個transaction可能需要分配多達50個slab。大部分transaction並不需要使用所有的預留內存,如果同時有大量transaction在運行,內核隻需要維護一個少於所有預留總和的池就行,但是Dave認為這種變相的內存overcommit可能最終會帶來一些問題。

Johannes擔心預留係統會增加很多複雜性,而且可能根本就沒有人想用這個特性,或者用戶都想開啟預留係統的overcommit功能來獲取內存且不影響性能。Ted Ts'o也認為對這個特性的需求不是很強烈,在實際環境裏因為低內存而引起的死鎖情況很少,但是Dave說這些複雜性可以被降到很低,畢竟XFS已經有了例子。而Ted堅持認為這個工作是為了解決少數情況下才出現的問題,而99.9999%的情況下都是正常的,我們是否需要花這麼大的代價和複雜性來解決它,Ric Wheeler也認為不應增加無關用戶的負擔,但Dave認為這些問題都是可以解決的。Ted認為即使現在有預留係統,係統管理員也很有可能將它關閉來減少對性能的影響(他預計會有5%的損失)。Dave質疑是否會有明顯的性能影響,Chris Mason也讚成在還沒有代碼的前提下,不要假設對性能的影響。Dave說如果transaction最終會要進行限流,預留係統的實際作用是將限製從transaction的中間移到了開始。James也暫時還沒有接受這個做法,他認為在低內存情況下我們總可以對付過去,但如果有了預留係統將會對請求進行限流,吞吐量應該會收到影響。關於預留係統對性能的影響,我們隻能等代碼出來以後才能有結論。Johannes在討論最後總結說,預留係統還是要做的,但是需要可配置關閉,下一步就是等社區的patch了。

Inheriting capabilities

https://lwn.net/Articles/632520/
佳澤

Memory-management testing and debugging

內存管理的問題曆來是一個難點,內存問題通常會影響正確行和性能。本文lsfmm期間相關的一些討論。

Testing:

Davidlohr Bueso在做一些mmtests benchmark相關的工作,以能夠檢測出不同內核版本之間的差異。 同時他還研究了Mosbench和Parsec,他認為這些工具裏還是有些用的測試。同時希望其他人也貢獻一些 測試到mmtests裏。Laura Abbott會提供一些針對移動係統的一組測試用例。同時,他說Scalability測試傾向於scaling up, 而移動開發者更關注scaling down的測試。 這個的討論沒有任何結論。Davidlohr 會繼續這方麵的工作。

Debugging:

Dave Jones, Sasha Levin, and Dave Hansen.發起了內存管理調試這個話題。 內存管理係統裏有很多的調試特性,但是intel的 MPX機製還沒有支持。 該機製是基於硬件的檢查並確保指針不會訪問到一個係列預定義的範圍外。 MPX在係統運行時,幾乎沒有代價,因此可以在生產係統中部署。MPX要求gcc5,並且支持MPX的硬件 還沒有真正準備就緒,因此還要可以再等一段時間。 Christoph問可否所有的對slab對象的訪問都能夠被MPX監控到。但事實上有些難度:一個內核裏有成千上萬個slab的對象, 但是mpx的硬件寄存器隻有4組。那麼當跟蹤超過4組對象時,就需要將寄存器上的信息保存和恢複。 其他人還建議MPX可以使用在內核堆棧和atomic的上下文中,以及dma操作等場景下。 Sasha建議增加內核裏的VM_BUG_ON,但是他擔心會遇到以前類似的阻力,調試代碼被排除在外。VM_BUG_ON 還有一些討論,但是沒有結論。Andrea Arcangeli, 因為他的開發時基於虛擬化係統, 質疑是否有必要增加那麼多的內存管理的tracepoints。有人反對說有些問題隻在bare-metal係統上才會出現。

KASan( kernel address sanitizer)

KASan 最近被被合並進入了內核主線。這個工具使用“shadow memory”數組紀錄內核應該訪問的合法的內存地址。當內核訪問越界時,他會拋出一個 錯誤。 KASan的開發者 Andrey Ryabinin有一個關於關於這個工具和有待提高的地方的介紹。最初想法是使KASan能夠正確的驗證對vmalloc到的內存 的訪問 。要達到這個要求,就需要在vmalloc裏增加鉤子並創建一個動態的shadown memory數組。整個工作的跟slab內存申請的跟蹤很相似,除了 slab使用的shadow memory在係統啟動的時候就被申請到了。不出意外,這個特性很快就能實現。 有一個小問題是,內存被釋放後很快又被分配給另外一個用戶。這片內存在KASan看起來很好,但是會掩蓋如果之前用戶有訪問釋放後內存的bug, 建議是將被釋放的內存先暫時緩存一段時間再釋放,不要立即讓其他用戶使用它。但延緩釋放內存有可能會導致內存碎片增加。Andrey也不是很有 把握要不要做這個特性,並且組裏人也沒有其他新的想法。 另,通過編譯器, 檢測讀未初始化的內存,但是有大量的問題需要解決。這些問題中的一個,內存初始化是匯編語言,必須人肉去改。Andrey試圖 解決這個問題,但是發現很難實現。他擔心開發者打開這些功能後發現這問題,會放棄使用所有的功能。 另外KASan還可以用來發現data race,就像現在我們用的其他的工具一樣。但有個缺陷,就是shadown memory要使用4倍的被監視 內存. 最後一個問題是:現在我們有KASan了,還有必要維護kmemcheck工具嗎?kmemcheck是個單核的工具,速度慢且使用很麻煩。好像沒有人真正在使用它。 結論:幹掉它 :)。

Investigating a performance bottleneck

https://lwn.net/Articles/637080/
承剛

Lazytime hits a snag

https://lwn.net/Articles/634803/
承剛

Progress on persistent memory

Matthew Wilcox 在 2015.03.09 波士頓的存儲開發者大會(剛好今年也有同事參加過)的時候說,持久存儲現在基本可以拿來當主存用了,所謂 battery-backed ,在內存上加塊電池。也有說 400GB 的超大存儲也有了,不過 who knows? 從內核角度,size 不同而導致的完全要當兩種不同的設備來管理了。

Christoph Hellwig 問Wilcox ,英特爾啥時候 release 對應的驅動,說白了就是 open datasheet出來,答曰,要和 ACPI 兼容,等 ACPI 6 吧。James Bottomley 好像注意到 UEFI 裏麵有過 release出來部分的這種 spec 的流程 (譯:透露出來很正常,拿開源當廣告牌,隻是沒有 finalize,做做實驗而已)

驅動角度,有很多原型產品,現在也沒辦法統一用一樣的 driver cover, frustrating!

關於 struct page

和內存管理相關的,400GB 的設備來說,元數據估計要 6GB,太多了。(譯:這個 size 到底是當內存還是當塊設備啊?)

Wilcox 假設這種類型的設備不用 struct page 來管。另外一方麵, Boaz Harrosh 推了一些 patch,小點的,仍然用 struct page。不過貌似 Wilcox 覺得這不應該是這種設備的目標。(譯:估計以後這裏會有很多爭論)

對於大點的設備,很多特性就比較像 NAND flash 了,隻是可擦寫次數會很多 7 個0,8個0的數量級吧。而且訪問時間可能甚至超過 DRAM。

Ted Ts,Dave Chinner 也都發言了,size 不同,用途不同,就這意思,不過也很難確定個標準啥的。Wilcox 說他有一些初級的 patch, 可以 get_user_sg() 替換掉 get_user_pages(),用的是 scatter/gather list 而不是 pages, 這樣可以讓這種設備變成類似個塊設備,可以上文件係統,然後又可以用 mmap ,反正就是兩邊都粘點。然後開始討論 truncate() 用在一個被 mmap 的文件的情況,Wilcox 認為 linux 這裏處理有點問題,如果程序訪問到的內存,由於 truncate 而不再是被映射到文件了,會收到 SIGSEGV,他認為 truncate 應該阻塞等 memory unmapped。
Peter Zijlstra 認為影響大,最好弄個 flag 給 mmap,在區分是不是等待。

現在回到驅動的話題上,現在已經有很多這種類型的設備了,最好驅動能跟得上啊,總之就是覺得趕緊把驅動弄到內核裏,讓大家都用起來,不過大體上,在那之前,還有很多工作要做。(譯:就是現在沒時間表了,等等吧)

新指令

Wilcox 開始說有三條指令要增加在新的處理器中了,clflushopt 可以保證 cache-line flush,而且要比 clflush 快,還有一個 clwb ,回寫之後,仍然保持 cache line dirty。再一個就是 pcommit 也是保證 cacheline 被同步更新到內存的,而且是針對所有核心的,這點類似於於 某種 barrier,這條指令,也需要增加到我們上麵提到的設備中來(譯:畢竟可以當內存用的嘛)。

Ts'o 問其他處理器咋整,Wilcox 無語了都(英特爾是我老板),其他的自己搞定吧。
Pcommit 是全局的,討論是否有必要針對每個 cpu 做,主要是有沒有必要一下 flush 針對每個 core, 或者會不會慢,目前感覺不用擔心,有需要再說吧。

錯誤處理

最後,他說到了錯誤處理,沒有狀態寄存器來表示各種錯誤類型,因為有可能當內存用啊。因為內存用的話,一旦出錯,可能導致的結果就是需要重啟了。但是如果問題存在,仍然會重啟啦(可能是內部邏輯實現)。

係統啟動的時候,也有些日誌紀錄這種錯誤,比如出錯的塊設備信息,然後文件係統可以試圖恢複,XFS 很妖要增加這個功能了,Ts'o 認為 ext4 也可支持。

但是,crash 似乎不是發現問題的好辦法,重啟對很多公司的業務來說是難以接受的,mmap 處理之後,再處理可能發生的錯誤也很難。一些建議是,在 mmap 或者 page 創建的時候,增加出錯信號,不過都需要用戶態有能力感知,並處理。

Chris Mason 說用戶期望 mmap 大文件的時候,即使裏麵有壞的頁也可以工作,聽起來不太合理,不過確實是用戶角度期待的,關於錯誤處理的話,討論了半天,沒啥結論。

Ftrace and histograms: a fork in the road

ftrace是獲取內核運行時信息的一個重要工具,但目前它輸出的數據非常原始,用戶通常都得執行額外的腳本將這些數據轉換成其它可讀的格式/形式。那麼,為什麼不把這項幾乎是必須要做的事情放到內核裏呢?所以,Tom Zanussi最近提交一個名為“hist triggers”的補丁,它生成的數據可以用於直接構造繪製直方圖。想法還是挺直觀的,例如,通過以下命令

   # echo 'hist:key=call_site:val=bytes_req' > \
           /sys/kernel/debug/tracing/events/kmem/kmalloc/trigger

就可以得到如下結果:

    # cat /sys/kernel/debug/tracing/events/kmem/kmalloc/hist
    trigger info: hist:keys=call_site:vals=bytes_req:sort=hitcount:size=2048 [active]

    call_site: 18446744071581750326 hitcount:          1  bytes_req:         24
    call_site: 18446744071583151255 hitcount:          1  bytes_req:         32
    call_site: 18446744071582443167 hitcount:          1  bytes_req:        264
    call_site: 18446744072104099935 hitcount:          2  bytes_req:        464
    call_site: 18446744071579323550 hitcount:          3  bytes_req:        168
    [...]

可以看到key和val其實對應直方圖上的兩個坐標軸。當然,一個明顯的改進點是用符號化的方法顯示調用點。

基本上沒有人對這個功能表示反對,但是ftrace的維護者Steve Rostedt指出用tracepoint生成這些數據需要額外的內存分配,但tracepoint是有可能在任何地方執行的,因此這裏有死鎖的風險。

條條大路通羅馬,另一個觀點來自於Alexei Starovoitov。他是eBPF的開發者。他覺得計算直方圖數據完全可以使用eBPF完成,這可以充分複用現有基礎設施,但同時他也指出這個方法並不是dtrace的替代品,隻是像ftrace這種基於read/write接口的輸入風格才適用(趕腳這個理由站不住腳......)。eBPF方法的確可以讓計算直方圖的邏輯脫離內核,但這也意味著ftrace的使用者原來隻需要讀寫幾個“文本文件”,而現在需要將這些指令先編譯成eBPF再輸入給內核,這個變化是否可接受還很難說。

向左轉?向右轉?再想想看:)

When real validation begins

https://lwn.net/Articles/629742/

木名

Epoll evolving

https://lwn.net/Articles/633422/

夷則

Epoll 是 Linux 下用於快速輪詢一大坨fd的一係列係統調用,雖說早在2.5開發版本中就已經有了,但還是有改進空間。現下便有兩批補丁對它們做了功能上的改進。

1. epoll 概覽

首先是 epoll 和 select()/poll() 的區別。最大的區別就是效率,epoll 在處理大量fd的時候更有性能上的優勢。簡單來說,select()/poll() 在每次輪詢間隔都會遍曆一遍要輪訓的fd,而事實上在兩次輪詢間隔中,fd其實是不怎麼變的,用不著每個fd都去過一遍。epoll 把 setup 和 waiting 兩個步驟分開了,這樣就能提高效率。使用的時候,先 epoll_create() 創建一個特定的efd,然後通過epoll_ctl() 把要操作的fd和要輪詢的事件關聯到這個efd上,然後調用epoll_wait()或者epoll_pwait()開始真正輪詢等待。

2. epoll_ctl_batch() 和 epoll_pwait1()

Fam Zheng 的 patch 引入了上麵這兩個新的係統調用。

第一個係統調用的背景是這樣的,epoll_ctl() 一次隻能增加、修改、刪除__一個__fd,所以當需要操作的fd太多的時候頻繁調用epoll_ctl()效率也是很差的。epoll_ctl_batch()從名字上就能看出是用來解決這個問題的。它的參數如下:
<source lang="cpp"> int epoll_ctl_batch(int epfd, int flags, int ncmds, struct epoll_ctl_cmd *cmds); </source>
這個很容易理解,請讀者自行理會 :-)
Fam 的另一個係統調用,用於更精細化地控製epoll_wait的過程。當前的epoll_wait是毫秒級別的輪詢精度,這個新的係統調用epoll_wait1()提供了納秒級別的精度。它還多了個沒有特定值(設為0)的flag字段,估計是為了擴展性,當前似乎沒啥用。
這個係統調用的參數如下:

<source lang="cpp">
   struct epoll_wait_params {
int clockid; struct timespec timeout; sigset_t *sigmask; size_t sigsetsize;
   };
   int epoll_pwait1(int epfd, int flags,
                    struct epoll_event *events, int maxevents,
                    struct epoll_wait_params *params);
</source>

3. 多線程優化

Jason Baron 的場景是個多線程的輪詢場景。多個線程在監控同一批fd的時候,隻要裏麵有一個fd變化了,因為所有關聯線程裏都有epoll,所以都會被喚醒,哪怕隻有其中一個線程要去處理事件。

他的解決方案就是在epoll_ctl()裏加flag參數。第一個flag是:EPOLLEXCLUSIVE,加了這個標記位之後,就隻有一個進程/線程會被喚醒了。不過這還是有問題,在Jason的場景裏,每次都是同一個進程被喚醒,這是因為在每個等待隊列頭都是同一個進程。所以Jason又加了一個flag: EPOLLROUNDROBIN,顧名思義,就是把輪詢進程改為RR輪轉方式,隊列頭的進程處理完輪詢請求之後就輪轉到隊尾,下一次就換另一個進程上來輪詢。

Jason 還自己跑了個 benchmark,在他的多進程場景裏,使用上麵的補丁,程序的執行時間降了一半。

How programs get run

https://lwn.net/Articles/630727/

無牙

Heterogeneous memory management

在2015 LSFMM內存管理的會議上,Jérôme Glisse提出異構存儲管理(HMM)。他提到現階段CPU的存儲帶寬增加緩慢,而且大部分workload並沒有達到極限帶寬,因此對帶寬的需求不是那麼迫切,而延遲才是性能的決定因素。但是,將目光投向GPU時,事情變得不一樣了。現在的GPU可支持同時運行多達10000個線程,性能不錯,運行時最大帶寬可達CPU帶寬的十倍。其中一些設計得好的GPU可以達到飽和帶寬,處理速度非常快。Jérôme Glisse則正在尋找CPU與GPU在同一塊die上的係統,兩者都可以訪問同一片內存區域。其中GPU更側重於遊戲,UI渲染等,並且占用了大部分的帶寬。

HMM則允許CPU跟GPU共享物理內存以及地址空間;更進一步地,也許可以實現一些通用設備也可訪問這些共享內存。GPU與CPU類似,具有自己的頁表,可以觸發apge faults等。整個係統的關鍵在於指定區域內存的所有權歸屬,如何避免競爭條件。為此,HMM提供了一套機製,可以在在CPU與GPU間遷移內存,保證在任一時刻隻有CPU或者GPU訪問內存。比如,某一時刻CPU需要訪問正被GPU使用的內存區域,將會觸發page fault,fault handler則會將內存控製權由GPU轉交給CPU,CPU則繼續運行。實現該功能需要保證兩側的頁表同步,通過CPU側的mmu notifier回調機製來實現。隻要任意一塊內存狀態發生改變,則執行相應的page-table invalidations操作。為了能有效運行,mmu notifier還必須支持可睡眠(該功能目前尚未支持),這也是這個patch能否被接受的關鍵點。

Andrew Morton則表示對整個係統的通用性感到擔憂。GPU發展如此迅速,也許五年以後沒有人再使用HMM功能,但是仍需要維護這份代碼。Jérôme Glisse則回應到,他相信HMM子係統可以更具有通用性,包括GPU、數字信號處理器等等。他說HMM在於提供一份完整的、對應用程序透明的GPU解決方案,而complier project可以使一些循環執行操作由GPU並行執行,這個功能實現後,GPU的使用對應用程序來說完全透明。

最後,大家討論了一下HMM一些細節上的實現。如何將匿名頁遷移給設備?將設備看成一種特殊的swap文件,fork()時,所有相關的內存首先遷移給CPU,並設置隻讀屬性。設備後續在訪問內存時觸發寫異常(類似copy-on-write),保證了原子訪問。如果能夠處理file-backed頁則更完善,不過這需要在頁緩存中創建新類型。同時這也帶來了與mmu notifier同樣的問題:文件係統部分的代碼認為頁緩存查找是原子的,而在這種情況下,是可能導致睡眠的。目前還沒有明確的解決思路...

最後更新:2017-06-07 13:01:32

  上一篇:go  送給在PHP道路上迷茫的你
  下一篇:go  怎麼學好php