阿裏內核月報2014年4月
Ideas for supporting shingled magnetic recording (SMR)
在2014年LSF&MM峰會上,Dave Chinner和Ted Ts'o一起主持了一個跨越2天而占用2個時間段的會議。 這個會議的主題是,是文件係統還是塊設備層才是支持SMR設備的正確接口。最後,它的討論範圍 有點超出這個主題。
Zone信息接口和緩存
Ts'o一開始就描敘了他正在處理的一個SMR設備返回的Zone信息kernel級別的C接口。SMR設備將會 報告驅動器中存在的Zones, 它們的特性(大小,僅串行,。。。)和對於每個僅串行Zone寫指針 的位置。Ts'o的想法是以緊湊的形式在內核中緩存這些信息,這樣就不需要向設備發送多個"report zones"命令。代替地,舉例說,感興趣的內核子係統能查詢Zones大小和它的寫指針的位置。
Ts'o說用戶接口是ioctl(),但James Bottomley認為sysfs接口更有意義。Chinner擔心sysfs將會有 成千的條目,Ric Wheeler提醒一個跟定的設備可能實際上有上萬的Zones.
Ts'o說他正在用的數據結構假定Zones大部分都被成組成大小相同的Zone區域。這個接口將支持其他 設備布局。Zach Brown想知道內核為什麼需要緩存這些信息,既然這也許要求探尋SCSI總線,尋找 重置寫指針命令。沒有人認為探尋總線是可靠的,但一些人認為不允許訪問裸SCSI貌似合理的。 Bottomley潑冷水Scsi sg層bypass Ts'o的緩存。
一個關於如何處理主機管理的設備的問題出現了(主機必須確保所有寫串行Zones都是串行的)。Ts'o 說他在主機感知的設備中看到了可怕的一秒延遲(主機能犯錯,轉換層將重映射非串行寫,它能導致 垃圾回收和可怕的延遲),這意味著用戶將想Linux支持主機管裏的行為。那將避免了主機感知的設備 上出現的延遲。
但是,正如Chinner指出的,在用戶空間有一些不能改變的固定布局。舉例說,mkfs零化分區末端, 並且SMR設備必須能與之工作。他是高度懷疑主機管理的設備將與Linux完全工作。今天Linux上沒有什麼 能運行在主機管裏的SMR設備上。但那些設備將可能生產起來更便宜,因此它們將是可用的,並且用戶向 支持它們。就主機管理的設備vs主機感知的設備問題輪詢了屋內設備製造者們,最後也沒有太大的結論。
Ts'o建議使用設備映射在內核中創建轉換層來支持主機管理的設備。“我們修複缺陷比供應商推出新的 固件更快”。但正如Chris Mason指出的,任何新的設備映射層對用戶可用都不會超過3年,但未來有需求 支持2種SMR設備。第一個會議在這就超時了,沒有太多實際的結論。
當Ts'o再一次撿起這個話題的時候,他向前邁出了更多。在許多場景下,塊設備正在做更多的事情。舉例 來說,SMR和帶有dm-thin的瘦provisioning.文件係統為基本的旋轉型驅動器所做的優化布局在其他場景 下是不明智的。對於SSD驅動器,轉換層和驅動器是如此的快以致文件係統不需要關心轉化層和驅動器固件 中所做的。對於SMR和其他場景,那也許不是真的,因此有必要重新思考一下文件係統層。
將文件係統一分為二
這是Chinner關於文件係統思考的關鍵。他提醒到他已經開始寫一些東西,並且對其他的建議和想法是開放的, 但他想到一些關於他思考的反饋。一個文件係統真正是由2個層組成:名字空間層和塊分配層。Linux文件 係統已經做了許多工作來為旋轉型驅動器優化塊分配,但有其他類型的設備,SMR和永久內存,它們的優化就 有些落後了。
因此,為了優化各種類型設備的塊分配,從文件係統名字空間處理中分拆出塊分配是由意義的。名字空間部分 將保持不變,所有的塊分配部分移入一個智能塊設備中,它能知道底層設備的特性並能相應地分配塊。
名字空間層希望一組分配是連續的,但塊分配層可能基於它的知識改寫這些決定。假如它正在SMR設備上寫塊, 意識到它不能在連續的位置上寫數據,它將返回附近的塊。對於旋轉型媒介,它將返回連續塊,但對於永久 內存,我們不關心,因此,它僅返回一些便利的塊。任何不支持COW的文件係統都不能為SMR做優化,因為你不 能在串行Zones中改寫數據。那意味著需要向ext4和XFS中添加COW功能。
但將文件係統一分為二意味著盤上格式能改變。名字空間層所關心的是它上麵的元數據是一致的。但Ts'o拋出 了一個問題:它與存在15年之久的基於對象的存儲有怎樣的不同?
Chinner說他不準備將像文件和inodes等東西移到塊分配層。那將僅是分配和回收塊的層。他問,為什麼在每 個文件係統中為不同種設備優化塊分配?
Chinner的想法和基於對象的存儲另一個不同是元數據與文件係統呆在一塊兒,不像基於對象的存儲那樣將它 們移到設備上。Chinner說他將不尋求分配一個能連接屬性的對象,僅創建為特定設備優化的分配器。一旦那 被實現了,在多個文件係統間共享分配器是有意義的。
Mason提醒到Chinner描述的東西非常像FusionIO DirectFS文件係統。Chinner說他並不驚奇。他尋找但沒有 發現太多關於DirectFS的文檔和過去其他人提到這些想法。它不必是新的,但他正在試著將它作為解決現存 的問題的一種方法。
Bottomley問如何得到一些我們能測試的東西。Chinner原以為需要半年的時間,但目前看來在它能工作之前, 還有許多工作要做。他問“我們應該采用這種方法?”,Wheeler認為它值得期待;它避免了重複,並且利用 新設備的優點。其他人也都表示樂觀,但他們希望Chinner當他做的時候時刻記住基於對象的存儲為什麼不 工作的原因。Chinner認為大約在6到12個月後一個POC將會出現。
Data integrity user-space interfaces
今年LSF/MM 2014上,Darrick Wong和Zach Brown主持了一個關於數據完整性(DIF)用戶態接口的討論。這一用戶態接口的相關代碼Darrick已經發補丁提交到社區了。這一接口使用DIF規範來為相關的塊數據添加必要的校驗和,以方便用戶檢查數據的損壞。
在去年的LSF/MM峰會上,Darrick也曾討論過DIF/DIX相關的話題,但是當時沒有任何代碼給出。今年,他編寫了一套相關的接口來實現此功能。James Bottomley認為提供這樣的借口給用戶會讓用戶過多的了解係統內部的信息,這樣做過於複雜了。而Martin Petersen則認為沒必要暴露底層借口給用,而是用庫函數來封裝相應的接口來方便用戶的使用。
Ted Ts'o指出目前的借口需要擴展aio中的iocb數據接口,後麵他指出了他不太喜歡這樣做的原因。而Zach則認為如果用戶需要將自己的數據放入iocb中,則必須要對這個接口做必要的修改。但這一工作需要不同用戶之間的協調。Kent Overstreet傾向於定義一個新的係統調用來完成這一工作,而不是使用已有的aio相關接口。但是Darrick則更傾向於使用已有接口。
Ted指出他對擴展iocb結構有顧慮的原因是google內部實現了一個自己的I/O調度器,而這個調度器也對iocb結構進行了修改。所以對iocb結構的修改會造成google內核代碼rebase時候的麻煩。而同時,Ted指出google也沒有打算把他們I/O調度器的相關代碼貢獻給社區的計劃。Darrick說能不能在iocb中添加一個標誌為來標識具體哪些成員被使用。看來這塊還需要更多的協調工作。不過Ted說原則上他不反對這個用戶態接口。
Copy offload
2014年的LSFMM峰會上Martin和Zach介紹了copy offload的進展,這個特性主要是在服務器的CPU和網絡不參與的條件下直接拷貝數據。Hannes和Doug同時也和大家同步了copy offload的一些額外的option。
Martin和Zach的演講題目是 "Copy Offload: Are We There Yet?"(看到這個題目,我隻能嗬嗬了,每年都炒,每年都沒啥進展)。Martin想和大家說“是的,謝謝”,但是似乎大家還是一如既往的很感興趣,所以Martin給大家介紹了一下他做的一些工作,比如基於Hanne3s的VPD(vital product data)重寫模塊,現在接口已經很簡潔,隻包含了源和目標設備以及各自的便宜量,當然還有拷貝的大小(以block為單位)。內部的實現目前是使用了SCSI XCOPY命令,因為這個目前基本被所有的大廠商支持。
如果底層存儲設備支持的很好的話,copy offload可以輕而易舉得拷貝大量數據,於是乎有人問samba的支持如何。Zach的回應是需要一個新的接口,這個接口會使用文件描述符以及文件範圍來操作。但是由於copy offload這個操作可能會導致你的拷貝部分成功,所以用戶態程序需要處理這個問題。
Error handling
Hannes Reinecke在今年的LSFMM上帶來了兩個和塊設備層錯誤處理相關的話題。第一個是分區掃描錯誤,第二個是SCSI錯誤處理路徑的徹底重構。
Reinecke已經添加了許多更加細致的錯誤碼,用於幫助診斷問題。這期間他碰到一個問題,EMC的驅動在分區掃描至磁盤末尾時返回ENOSPC。而他更希望看到的是和其他七個內核分區掃描一樣的返回ENXIO。因此Reinecke在SCSI代碼裏麵將該錯誤重新映射為EXNIO。否則的話這段代碼處理會有問題,因為ENOSPC指的是達到了上限。
Al Viro提出了他的擔憂,重新映射後的錯誤碼將會擴散到用戶態,給很多工具帶來很多困惑。Reinecke讓他確信重新映射後的錯誤碼僅會傳播到塊設備層。能夠區分實際的IO錯誤和掃描到磁盤末尾,將使得分區掃描器能夠在探測到IO錯誤之後停止掃描。
在另外一個討論上,Reinecke給出一個關於從SCSI錯誤在各個level恢複(LUN, target, bus, ...)的提議。他認為部分層麵直接reset沒有任何意義,而是應該看具體探測到的錯誤進行判斷。例如,如果target是不可達的,對LUN進行reset,或者對bus都是無意義的。而應該嚐試重新傳輸一次,假如再失敗,那麼將需要進行機器重啟。這才是一個命令超時或者返回出錯所應該走的路徑。
針對這個,底下聽眾有大量的抱怨,主要是此時並不是必須要重啟。當其中一個LUN或者target出現錯誤,其他LUN的傳輸鏈路將被破壞,即使他們處理IO正常。部分問題是源於LUN的reset命令不會time out,Reinecke說到。
但是Roland Dreier注意到一個丟失的IO可能導致整個存儲陣列需要reset,這將會花費一分多鍾處理。此外,一旦進入錯誤處理路徑,所有到該機器的有問題IO都會停止等待。在一些大存儲陣列裏麵,丟失一個包可能導致很長一段時間沒有任何IO。Reinecke爭論到命令會重試,但是承認一個嚴重的錯誤確實是會導致這種情況發生。
當然,讓事情複雜的進一步還有,存儲廠商對不同的錯誤都各自進行不同的處理。對一個廠商的錯誤恢複步驟可能和另外一個廠商的相同,也可能不同。最終,似乎大家都同意,Reinecke的修改將會使得事情比現在更好,是正確道路上往前的一步。
A revoke() update and more
在今年的LSF/MM峰會上Al Viro介紹了係統調用revoke()的最新進展。revoke()的作用就是將一個文件名對應的所有文件句柄都關閉,這樣調用進程就知道他能夠獨享這個文件名對應的文件或者設備,在這個topic中Al順便介紹了一下他對read()和write()各個變種的統一工作。
Viro開始的時候提到revoke()是他這個session裏麵最沒啥意思的,因為代碼基本已經ok,實現也很簡單,文件在打開的時候如果聲明是可以revoke的話就會增加一個引用計數,這樣如果revoke()被調用了,他就會阻塞等待直到所有打開的文件句柄都關閉(也就是說引用計數變成0),同時保證後麵的任何open都會失敗。
目前在procfs以及sysfs裏麵已經有些類似的邏輯,這些等到revoke合並以後就會被幹掉。實現revoke的一個重要前提是它不能對正常的路徑有任何的性能損耗,因為大部分應用都不會使用這個特性,目前來看poll()和mmap()還有一些問題。Viro在做這個的同時注意到內核裏麵很多代碼都有bug,囧。比如如果一個debugfs裏麵的一個文件在打開以後被刪除了,對這個打開句柄的任何read/write操作都會使內核掛掉(真的,假的,我了個去)。動態debugfs就是一個杯具,所以Viro希望revoke()在debugfs裏麵能夠工作的ok。
接下來Viro提到了他對read/readv/splice_read以及write/writev/splice_write代碼的統一工作。目前看read/readv以及write/writev已經合並得差不多了,而splice*目前看很糟糕。在理想情況下,這些變種應該在上層路徑上保持一致,一直到處理函數才有各自的區別。但現實情況是他們對數據有不同的視圖,splice*係列的函數的數據是放置在page裏麵的,而read/write則是放置在iovec裏麵,也許需要創建一個新的數據結構來包裝這些。
另一個問題是iov_iter。iov_shorten()會嚐試重新計算iovec裏麵對應的網絡包的個數,所以如果存在short read/write,iovec就會被修改。更糟糕的是,iovec的修改是和協議相關的,這個對用戶非常不友好。事實上,CIFS的一個哥們火上澆油說CIFS每次都是拷貝一份iovec因為它也不知道底層會對iovec做什麼樣的修改。Viro最後總結說iovec就應該和協議無關,所以他正在幹掉iov_shorten以及其他會縮小iovec的地方,這個可能最終會導致sendpage()被幹掉。
Thin provisioning
Thin provisioning
(Eric Sandeen、Lukáš Czerner、Mike Snitzer和Dmitry Monakhov在2014年的LSFMM上討論thin provisioning的問題。Thin provisioning是什麼意思想必不用多說,但如何翻譯倒不太一致,正式文件中居然是。。。自動精簡配置??好吧,還是叫超賣更順口些。現在的linux的標準用法已經是在dm層上用dm-thin target來實現thin provisioning了,目前dm-thin也已經相當穩定,大家今天討論的主要是一些性能問題)
Ted Ts'o:開會
Snitzer: dm-thin現在的塊分配算法對上層應用是透明的,基本就是寫時按需分配,這樣的話如果我在dm-thin數據卷上有多個volume,每個volume上邊都有進程在順序寫,它們再去讀時就不是順序的了。我想像CFQ那樣,給每個volume準備一個待提交的struct bio的有序列表,然後讓各個volume輪流去寫盤,每次寫一大片,這樣讀的時候這一大片還是順序讀。我不會把這東西搞得像電梯算法那麼複雜,主要是為了提高些locality。另外,XFS和ext4能不能向我暴露出來allocation group的邊界在哪?我可以把這當成一個hint,這樣寫IO跨越邊界時我就知道一個大塊寫已經結束了。
Joel Becker、Dave Chinner: 你要這個幹啥?你用邏輯塊號就行了。你真正想要的隻是一個hint,別去關心它到底是不是allocation group的邊界,這是文件係統的內部細節。
Ted Ts'o:總之,文件係統應該提供一個抽象的hint,用來給dm-thin判斷locality
Ted Ts'o:下一個議題~~, dm-thin必須知道哪些塊被釋放了,不然很快就入不敷出了,我看用DISCARD命令就不錯。
Roland Dreier: 多數線上係統管理員都把這東西關掉了。
Martin Petersen: 另外TRIM命令的支持也一直不太好
某人:估計目前唯一可靠的辦法是用獨立的工具做離線trim。
Snitzer:提醒大家注意一下,我們關心的不是那些硬件設備對DISCARD命令的真實支持情況到底怎麼樣,它可能支持,可能不支持,眼下咱們沒能力關心,咱們關心的僅僅是把這個命令傳到dm-thin這一層,讓dm-thin知道哪塊空間被釋放了就足夠了,甚至直接約定不往下傳給設備也行。mount時加個-o discard就足夠了。
Ted Ts'o:下一個議題~~, fallocate()是用來預分配空間的,不過目前這命令隻是在文件係統層上有效,根本影響不到塊設備層。這樣在dm-thin上造成的結果就是有可能用戶事先調用了fallocate()分配出來了空間,然後實際寫的時候遇到-ENOSPC --- 因為dm-thin那邊沒有空間了,這完全違反了fallocate()的語義。要是不把fallocate()的塊分配這事交給塊設備層,這事情解決不了。
Dave Chinner: 不可能這麼做,違反分層了。
Eric Sandeen、 Dave Chinner:提醒大家一下,文件係統沒空間後的行為很不一樣,XFS的話,如果數據已經寫到redolog裏了,它就會持續重試往數據盤裏寫,ext4和btrfs就不會這樣。一般來說,用戶程序對-ENOSPC的處理都不太好,持續地返回-ENOSPC說不定會讓用戶程序徹底瘋掉。
Dmitry Monakhov:我建議文件係統應該有一個標準化的途徑向用戶報告各種事件,包括像磁盤沒有空間了這種錯誤。比如從VFS層發個uevent上去,前幾天我提這個建議的時候,大家都表示同意,不過沒人知道該從哪開始著手做,或者什麼時候能搞定。
Ted Ts'o:。。。散會
(就像LSMM的大多數議程一樣,開完會後開發者們還是什麼也定不下來,但不管怎麼說---總比不開會強。)
Block multi-queue status
Nic Bellinger在2014 LSFMM上主持討論了block multi-queue的現狀。blkmq在3.13的時候被merge到了內核,但是隻支持virtio_block驅動且基本上可以工作,雖然到現在又有些變動,但是整體架構上是OK的。Micron的mtip32xx SSD驅動的轉換也基本完成了,目前的驅動是單隊列且共享tags,轉換後是有八條隊列,IOPS可以達到180w,和未修改的驅動一樣。它在2-socket係統上表現良好,但是在4-socket機器上有所下降。
這其中的一個問題是因為缺少tags。有人說他們替換掉了per-cpu ida後就消除了tags問題。而Matthew Wilcox也提出了tag的另一個問題:linux上的實現是每個LUN上唯一,但是規範上隻要求每target唯一。James Bottomley也說規範允許16bit的tags,而不是目前用的8bit。
Bellinger繼續介紹了他和Hellwig在給SCSI添加多隊列的工作。從2008年起就開始有人指出SCSI core在小隨機IO性能方麵的問題,這大部分是由於鎖的cache-line bounce,這使得這類IO的IOPS隻能達到250K。使用多SCSI host後可以使性能達到100w IOPS,但是會有1/3的CPU耗在spinlock爭搶。因此Bellinger使用blkmq來預分配SCSI command,sense buffer, 保護信息和request,他的初級實現不包含錯誤處理,出現的任何錯誤都會造成oops,但是可以達到170w IOPS。Hellwig實現了錯誤處理並做了merge的計劃,但是他倆在是否將faster IOPS作為默認模式上還沒有達成一致,這也會讓合並有些困難。另外,Bellinger認為驅動的轉換相對比較容易,而Bottomley認為在消除驅動的鎖方麵仍有較多工作要做。
Large-sector drives
雖然Linux kernel對4k sector的設備的支持才[剛剛出來 https://lwn.net/Articles/377895/], 2014年LSF/MM峰會上Ric Wheeler又開始招唿大家開始討論sector尺寸大於4k的設備了。Ric首先給設備製造商拋出一個問題:當時他們選擇4k sector size的原因是啥,是因為他們自願的還是因為受到內核的限製。這個問題廠商們並沒有正麵回答,不過他們的一致意見是最好在sector size上能夠有更多的靈活性,比如可以支持64k或者128k這樣的。
Dave Chinner提到了目前內核需要修改的地方,目前看似乎文件係統以及處理分區的代碼需要修改。他還提到目前page cache代碼的假設都是能夠基於page大小做IO,Jan Kara也提醒大家目前頁麵回收算法也是基於上麵這個假設,所以如果頁麵大小(4K)小於sector大小,可能比較麻煩。Dave提到一種解決方案是用類似IRIX中的chunk方案,這玩意可以處理多內存頁的buffer,但是他不缺定是否靠譜。另一條路子就是允許小於sector尺寸的讀寫,但是這個就不可避免的會出現讀-修改-寫回這種場景,性能就杯具了。
最後Ric總結說目前設備製造商還沒有開始推動大sector尺寸的設備支持,所以內核社區還有時間,但是內核社區的一個基本原則就是先要搞定Sector尺寸大於page尺寸的情況,後麵應該就差不多了。
Direct I/O status
Kent Overstreet今年在LSF/MM峰會上主持討論了Direct I/O目前的狀態。Direct I/O的作用是允許用戶跨國係統page cache直接訪問磁盤上的數據。
Kent首先提到他的biovec的相關工作已經被社區合並了。這些工作之後就可以很容易的拆分一個bio結構。剩下的一個前提準備是make_request()函數。該函數需要能夠處理任意大小的bio。
在完成了所有上述準備工作之後,內核就可以拋棄buffer_head從而直接使用bio來進行DIO操作了。這樣代碼的複雜度就可以有效地降低。同時,DIO代碼路徑上也不再需要通過buffer_head來獲取文件係統元數據信息。同時,由於make_request()函數可以處理任意大小的bio,buffered I/O路徑也可以進行相應的優化,文件係統不再需要處理bio拆分這樣無效率的工作了。
FedFS, NFS, Samba, and user-space file servers
雖然LSFMM分了三個tracks討論,但多數話題都無法將存儲和文件係統分開討論,恐怕隻有Chuck Lever和Jim Lieb牽頭的討論是個例外,他們主要提及了用戶空間文件服務器,尤其是FedFS和用戶空間NFS。
Lever首先從介紹FedFS是什麼東東開始:FedFS(Federated Filesystem)可以讓管理員把幾個完全不同的文件服務器導出的文件係統合並成一棵文件係統樹呈現給用戶。FedFS通過一種稱為referrals的概念表示“底層”文件服務器上的文件或者目錄信息。因此,FedFS文件係統實際上是樹狀組織一個referrals集合。
物理保存referrals信息的對象稱為“junctions”。不同文件係統實現junctions的方式並不一樣:Samba使用符號連接保存訪問數據所需要的元信息,而FedFS則使用帶有特殊擴展屬性的空目錄表示。雖然去年會議上對統一junctions格式的意向達到了一致,但Samba和FedFS依然各行其是。
接下來就是怎麼統一的討論了:NFS的開發者覺得空目錄方法不好,這個設計類似於Windows的reparse points概念。有人提出如果能引入一種新inode,或者至少引入一個新標誌位就完美了,但Tso反對說,這意味著需要修改所有文件係統才能支持它(作為文件係統小白,我表示個人不能理解為什麼要修改所有文件係統?!)。同樣,符號連接方法也沒有得到親們的好評:Layton解釋了為什麼不能用符號連接,而Trond Myklebust則清楚地認為符號連接的主意是too ugly和hacky,並且也限製了referral的信息隻能保存在一個頁麵上,所以如果要在一個referral上支持多個協議恐怕很困難。Myklebust總結道,符號連接,Samba自己玩玩尚可,推而廣之還是不要了。可惜的是,究竟怎麼統一格式還沒有定論。
後麵開始小話題滿天飛了,我摘要一下:
1. 用戶空間文件服務器需要內核協助的地方蠻多的,其中之一是file-private locks,Layton已經提供了一個patch,Lieb希望它可以進入3.15合並窗口。然後,就是讓glibc支持這種新型鎖。
inotify事件的過濾機製,例如,Ganesha文件係統服務器需要監聽底層文件係統的事件,但不需要其自身的事件,這用於Ganesha的緩存管理機製。同樣也有補丁了,等待合並中。
Ganesha開發者希望可以用更快的方法處理user credentials,現在每次操作都需要數次(概念上需要7次)係統調用。最後的結果是大家認為Ganesha需要一種user credentials緩存,而Tso覺得他有個招兒可以解決問題,轉私下交流了。
readdir()的增強版提議再次提上日程,看起來依然無望。
最後也是一個沒有結論的話題:目前有兩種ACL:POSIX ACL和NFS ACL,各有一批使用者,兩者的語義也不完全相同。AI Viro認為同者支持兩者過於複雜,而雖然NFS ACL的語義更豐富,但大家對NFS ACL是否能夠模擬POSIX ACL也沒有定論。所以,也就沒法確定內核應該支持哪個了。
Lots of new perf features
在2014年的Linux Foundation Collaboration Summit大會上,Red Hat的Jiri Olsa和LG的Namhyung Kim為我們介紹了Perf的一些新特性。
Olsa的介紹如下:
1)Perf利用了libtraceevent庫來分析Tracepoint的格式。
Libtracepoint由Steven Rostedt開發,是ftrace前端的一部分。我們知道,每個kernel的Tracepoint在它的format文件裏麵都有格式信息,用以描述該Tracepoint輸出信息的格式。Libraceevent可以幫助用戶空間的程序更方便地獲得這些格式信息。
2)對Intel處理器新特性的支持
2.1) 支持了RAPL(Running Average Power Limit 詳見注[1])
在RAPL的支持下,管理能能夠設置並監控係統中各種硬件域(Hardware Domain,見注[1])的能耗上限。基於RAPl,Stephane Eranian做了相應的支持,可以對不同的硬件域進行能耗相關的采樣和分析:如所有的物理Core,CPU芯片,DRAM或者內建的GPU。
2.2) 訪存行為的Profiling
對內存訪問行為的Profiling是很多開發者的共性需求,以往,perf在這方麵做的並不好,主要因為CPU沒有提供相關的接口。目前,在最新的Intel CPU的支持下,我們能夠觀察到對內存的加載與存儲操作。而且這些事件還附帶了豐富的信息:指令地址、數據地址、訪問目標(L1 Cache、本地RAM、遠端Cache…)、TLB的訪問狀況(命中、丟失…)等。同時,我們還能拿到每次訪問的開銷(weight,即此次訪存操作消耗了多少個CPU cycles)。不過,這個開銷的數值在現階段還不是很精確。但在未來的CPU中,訪存開銷的精度會得到持續改善。
Perf僅能夠記錄內存訪問信息,並不去嚐試分析它。另外一個工具“c2c(cache to cache)”能夠監測到CPU間共享的cache line。在指定地址之後,c2c能夠報告每一條cache line的狀態,比如:訪問類型(加載 or 存儲)、訪問偏移以及觸發訪問的指令。有了這些信息,開發者便能夠對每條cache line都了如指掌。
3) 對調用鏈功能的改善
在利用ftrace進行profiling時,大家都希望能看到相關的函數調用鏈。目前,我們使用了frame pointer實現了這個功能。如果frame pointer未被使用時(GCC往往會將該寄存器優化為普通寄存器),則利用libdw,在DWARF調試信息的支持下,unwind應用程序的stack。Perf已經能夠記錄用戶棧與相關的寄存器,而libdw可以完成後續的工作。Libdw比libunwind要快很多。[扁鵲下一步也將考慮使用libunwind替換掉libunwind]。
另外一種快速生成調用鏈的方法是使用LBR(Last Branches Record)信息。LBR機製在能夠存儲被采到的那條指令所觸發的分支列表。同時包括分支的源地址與目標地址。利用這些信息,也能夠獲得調用鏈。
4) 未來工作
4.1) 添加對CTF(Common Trace Format)數據格式的支持。
CTF格式當前被LTTng(Linux Trace toolkit next generation)用於數據存儲格式。
4.2) 將perf record多線程化。
4.3) 事件的相互觸發機製
允許一個事件打開或關閉另外一些事件,從而減少被記錄的數據量。原始代碼由Frédéric Weisbecker開發,但目前尚沒有合適的用戶接口。
4.4) 事件組
目前,perf需要在每個CPU上為每個打開的事件申請一個文件描述符。如果CPU很多,打開的事件數也很多。那麼文件描述符很可能不夠用。目前,已經有計劃使得每個“事件組”共享同一個文件描述符。當然,大家僅僅討論過這個想法,還沒有代碼。
4.5) Perf代碼的通用化
Perf 的一些核心代碼被移到了tools/lib中,其它的工具也可以使用這些代碼了。
4.6) 測試用例
Perf現在共有26個測試用例。每個commit都需要跑一遍這些測試。測試集還會日益豐富。
Namhyung Kim的介紹如下:
1) Perf report 的--children參數
展示完整的callchain,以及callchain中的每個函數對測試結果的貢獻。在perf report的輸出結果中,增加了“children”與“self”列,用以展示Callchain中每個函數的貢獻。
2)--field參數
用以指定需要展示的信息(dso,thread comm等等)。該參數可以與“--sort”配合使用。
3)Ftrace的支持
在perf中支持ftrace是一個新特性。目前,僅僅支持function與function_graph。該特性利用了libtraceevents的kbuffer API來訪問事件。為ftrace事件提供類似perf的行為是未來的目標。
我們可以這樣使用perf的ftrace功能:
perf ftrace record
perf ftrace report 或 perf ftrace show
4)在uprobe中增加了DWARF的支持
Perf目前能夠利用符號名與行號動態設置tracepoint。該工作由Masami Hiramatsu貢獻,並已經被收入upstream了。通過“--line”與“--var”便可以利用Binary中的debug信息創建uprobe檢查點。之後,perf可以對這些進行采樣。
5)SDT(Statically Defined Tracepoints)的支持
SDT類似於內核的Tracepoint,麵向應用程序。在支持SDT後,perf能夠訪問SDT並對SDT進行采樣。目前能夠給出應用中支持的SDT的列表,並利用uprobe訪問它們。不久之後,我們可以像訪問perf的事件那樣來訪問SDT。
注: [1] RAPL: Running Average Power Limit。IntelCPU自Sandy Bridge之後引入的新接口。通過該接口,軟件能夠監測、控製芯片的能耗,並可以獲得能耗相關的通知。RAPL為了實現細粒度的能耗控製,將硬件劃分成了不同的域,如:芯片、DRAM控製器、CPU core、Graphic uncore等。
Ref:https://lwn.net/Articles/545745/
Sealed files
通過共享內存來進行進程間通信的常用方法。這個方式簡單高效,但有一個陷阱:所有交互進程必須相互信任。也就是操作共享空間的進程必須假設對方不會做壞事,比如在你的讀的時候偷偷修改了內容還告訴你沒有修改,在不恰當的時機把空間的後端文件大小變了導致進程的讀寫操作觸發致命的信號中止程序等。當然現實中,我們可以通過拷貝共享空間的數據到其它地方來避免這些問題。但這樣做十分低效和笨重。
開發者最近一直在討論內核層麵上的解決方案。三月份David Herrmann同學提交了file sealing補丁,保證一方在操作共享空間的時候對方不會做壞事。
這個方案通過fcntl()操作共享空間的文件描述符引入了三個標誌位
1. SEAL_SHRINK 阻止文件被變小。
2. SEAL_GROW 阻止文件變大。
3. SEAL_WRITE 阻止任何寫操作(resize除外)
如果這三個標誌同時被設置,這個共享空間就變成了不可變(immutable)的空間。同時這些標誌不能作用於有可寫映射的空間,除非通過munmap()來把可寫的映射去掉。一旦空間被密封後,一方可以通過調用fcntl,傳入SHMEM_GET_SEALS來獲取對方設置的標誌。這將避免一些潛在的安全和未知因素。
目前文件密封(file seal)操作隻能作用於shmfs,通過修改write(), truncate(), mmap()路徑上的代碼實現。
有人指出引入了文件密封機製也會引入安全問題。比如說通過頻繁的去密封一個普通文件形成拒絕服務攻擊。目前文件密封機製隻做作用於shmfs,所以不會給普通文件帶來這個問題。
如果不想掛載shmfs來顯示的操作文件,這個補丁引入了一個新的係統調用:
int memfd_create(const char *name, u64 size, u64 flags);
這個係統調用將返回一個支持文件密封的句柄,然後用戶可以通過mmap()映射到共享空間。
大多數開發者都覺得這套補丁不錯,但是也有一些人覺得實現和語義有點問題。Linus大神不滿意所有人都能去密封文件,不管是不是自己創建的。建議隻有創建者才能密封文件。David同學沒有反對這個建議,說如果這樣做還能簡化實現。他覺得還可以添加一個標誌(MFD_ALLOW_SEALING)來控製是否允許別人密封。
Ted Ts'o提到文件密封可以擴大到更大的範圍,應該做vfs層中去,然後所有的文件係統都可以支持這個操作。David同學回答說,到目前為止他沒有見到任何通用文件係統對這個特性的需求用例。這個問題的討論以沒有結果而告終。
目前這個補丁已經有了一些潛在用戶,kdbus和顯卡已經Android係統都有可能用到,所以這個機製被合並的前景很好。
Avoiding memory-allocation deadlocks
避免申請內存死鎖 有句諺語"要掙錢必須先花錢", 盡管這個悖論很容易通過創業貸款和堅持收支平衡的原則來解決。 一個相似的邏輯適用於管理像linux這樣的操作係統的內存: 有時候,你為了釋放內存,需要先申請內存。 這裏,同樣需要堅持原則, 盡管不小心的結果不是破產,而是死鎖。
作為linux開發曆程的一個縮影,內核如何保持其收支的平衡是很有趣的,並有助於理解如何去處理未來發生的死鎖。 讓我們從介紹1998年早期的Linux 2.1.80pre3裏的__GFP_IO作為一個美好起點。
__GFP_IO in 2.1.80pre3
內核裏的任何內存申請都包含一個gfp_t的參數,這是一組標誌用來指導get_free_page()函數如何去定位一個空閑頁。 從2.1.80pre3,這個參數的類型從一組簡單的枚舉類型,改變為標誌位。每個標誌裏的含義跟以前的一致,但是 這是他們第一次被明確的標示。
—GFP_IO 是這些新標誌中的一個。當它被設置時, get_free_pages() 可以調用shm_swap() 把一些頁寫到交換分區。 如果shm_swap()為了完成寫操作,需要申請一些buffer_head結構體,要注意不能再設置—GFP_IO標誌了。 否則,很容易就發生死循環,進而很快耗光棧,並導致內核崩潰。
今天,內核裏的—GFP_IO是2.1.80內核引入的,原先的—GFP_IO在 2.1.116內核已被刪除了。盡管名字一樣,但是他們是不同的標誌了。
PF_MEMALLOC in 2.1.116
很久以前(1998年8月),我們還沒有像今天這樣好的修改日誌,所以需要一個操作係統考古學家去猜測之前哪些修改的原因。 我們確認之前get_free_page()使用的(每請求一個的)—GFP_IO已經沒有了,並出現了一個新的(每進程一個的)標誌PF_MEMALLOC, 來取代它避免遞歸的作用。這次改動的一個明顯的好處是它更專注於解決一個問題:遞歸是per-process的問題,因此一個per-process標誌更合適。 之前,很多申請內存的地方會不必要的避免使用—GFP_IO。現在,這些地方不用再擔心這個遞歸的問題了。
下麵的代碼注釋強調了內存分配的一個重要方麵:
*"PF_MEMALLOC" 標誌使我們避免遞歸
* 如果我們做換出的時候工作時,需要更多的內存,我們隻需要返回"success",來告訴內存分配器接受申請。
當條件滿足時, get_free_page()就會從空閑鏈裏麵拿出一頁並盡快返回這一頁。當條件不滿足時,不會僅僅釋放一頁來滿足當前請求, 而是多釋放一些頁,以節省下次的時間。這樣,就相當於重新補充了創業貸款。PF_MEMALLOC 的一個特別的結果是內存分配器不需要 特別努力地去得到大量的頁。他盡量根據它已有的去設法應對。
這意味著帶有PF_MEMALLOC的進程有可能適用最後僅有的很少的空閑內存,相比之下,其他的進程則必須換出並釋放大量內存後才能夠使用。 在當前內核裏PF_MEMALLOC的這種特性還有並且正式些。在內存分配器裏有個“水位線”的概念,如果空閑的內存總數低於特定的水位線, 分配器就會盡量釋放更多的內存,而不是返回現有的內存。跟—GFP能夠選擇不同的(最小,低,高)水位線不同,PF_MEMALLOC會忽略所有的水位線, 隻要有可用的內存,它就會返回這些內存。
PF_MEMALLOC實際上相當於說“現在要停止省錢,開始消費,否則我們將會沒有產品可賣”。結果現在PF_MEMALLOC被廣泛的使用, 而不僅僅是為了避免遞歸(盡管他有這個作用)。像nbd,網絡塊設備,iscsi_tcp和MMC卡控製器用到的幾個線程都設置了PF_MEMALLOC, 任何調用他們去寫出一頁內存(以釋放它)的時候,他們肯定能夠得到內存。
與此相比,在2.6.33,MTD驅動(它管理NANDflash並對MMC卡驅動有類似的作用)停止使用PF_MEMALLOC標誌, 並在注釋裏說是這是一種不正確的用法。關於內核裏其他使用的地方是否合理的問題已經超出了我們這篇文章要討論的深度了。
__GFP_IO in 2.2.0pre6
當—GFP_IO再次出現在內核時,跟最初的目的相比,它有一個類似的目的,但是是為了一個很重要的不同的原因。為了理解那個原因, 很有必要看看代碼裏的一個注釋:
/* 如果我們不能夠做IO,不要深入到換出的東西裏,*/
這裏的關注還要涉及到遞歸,但也會涉及到鎖,如per-inode mutex, page lock和其他各種鎖。 為了寫出一頁而調用到文件係統裏可能需要鎖。如果分配內存時,某一個鎖被阻住了,那麼避免任何可能調用到文件係統並取得相同鎖 就很重要。這些情況下,代碼千萬不要使用—GFP_IO;其他情況下,參數裏包含這個標誌是相當安全的。
PF_MEMALLOC避免了get_free_page()又遞歸調用了get_free_page(), 而—GFP_IO更通用,並且避免了 一個持有鎖的進程通過get_free_page()調用到其他也需要這個鎖的函數。這裏的風險不是像PF_MEMALLOC那樣的耗光棧, 而是死鎖.
考慮到先前—GFP_IO 的經驗並不成功,有人可能疑惑為啥使用一個GFP標誌而不是一個進程標誌, 進程標誌可以明確地說“我持有一個文件係統的鎖”。 像許多軟件設計一樣,這可能“在那時看來是個好主意”
__GFP_FS in 2.4.5.8
這個標誌最初出現在2.4.5.1裏,名字是—GFP_BUFFER ,但直到2.4.5.8裏被命名為—GFP_FS前,沒有真正正常工作。 顯然在原始設計裏有個 thinko, 這要求不僅改變一些代碼,還有改了一個新的名字。
實際上—GFP_FS從—GFP_IO裏分走了一部分功能,所以過去是一個標誌,現在又兩個標誌。 隻有三種組合可能是正確的:全沒有,全有,新的可能僅—GFP_IO被設置。 這將允許我們已經準備的buffers被寫出,但會禁止調用到文件係統裏去準備那些buffers。 I/O行為被允許,文件係統行為被禁止。
推測,—GFP_IO先前有這麼寬泛的功能,對性能有影響,在一些可能有I/O行為的地方也被排除(禁止使用)了。 重新提煉規則,增加一個新的標誌位,從而更加靈活,對性能影響更小。
PF_FSTRANS in 2.5.36
在2002年底XFS被合並進內核時,這個新的進程標誌出現。 它被用來指明一個文件係統事務正在被準備,也就是所有的寫文件係統都會被阻塞,直到這個事務被處理完畢。 這個標誌的作用是,當申請內存時,並設置了PF_FSTRANS時,—GFP_FS 就會被剔出,至少XFS的代碼產生的請求是這樣的。 其他代碼裏的請求不會受影響,但是當這個標誌設置時,其他代碼裏的內存申請很少被調到。
在2.1.116內核裏,由於用的地方少,而且修改也很容易理解,刪除一個像—GFP_IO 的標誌位是很簡單的事情。 在2.5.26,這樣的操作就困難的多了。 3.6rc1稍後的版本,NFS開始使用PF_FSTRANS 標誌。PF_FSTRANS 不是被使用在事務裏,而是被NFS用在創建並傳輸一個RPC請求到網絡上 所以,現在名字有一點不太確切。這個標誌在NFS中的作用,不是用來清除—GFP_FS標誌,而是避免在nfs_release_page裏發送一個COMMIT請求, 這在沒有設置—GFP_FS標誌是應該避免的。這樣的使用方法跟XFS的使用方法有很大的不同。也許把它重命名為PF_MEMALLOC_NOFS,並刪除GFP_FS, 從而使這個標誌具有全局的屬性的想法,是個不錯的主意。
set_gfp_allowed_mask() in 2.6.34
首次出現在2.6.31,但在2.6.34裏變得更加有意思。 gfp_allowed_mask 是一個全局的變量,它包含了一係列的GFP標誌。特別是—GFP_FS,—GFP_IO, and—GFP_WAIT (這個標誌位允許get_free_page()函數等待其他進程釋放更多的內存)有時候會通過這種機製失效。 它能夠影響更多的進程並使更多的標誌失效,這有點像PF_FSTRANS。
gfp_allowed_mask能夠在啟動的早期階段給kmalloc()函數更多的支持。在啟動的早期階段,中斷時被禁止的。任何試圖通過—GFP_WAIT或者其他標誌去申請內存,都會觸發來自lockdep checker的一個警告信息。在啟動階段,如果內存如此緊張,以至於讓 分配者不需要要去等待,那將是讓人感到很吃驚的。 所以gfp_allowed_mask 在初始化的時候去除了前麵提到的三個標誌, 並在啟動完成後,把他們再加回來。
我們這些年得到的一個經驗是啟動不是像我們想得那麼特殊:無論是掛起和恢複,還是硬件的熱拔插。 在2.6.34裏這個掩碼被擴展以覆蓋掛起和恢複。
對於內存分配引起的死鎖,刮起比啟動更有代表性。在啟動階段有大量的空閑內存,而刮起時則不是這樣,可能碰巧我們也很缺乏內存。 那麼就不是彈出個警告信息,而是真正的死鎖。掛起和重啟是順序化的進程,如設備順序進入休眠,而反向的順序醒來。 所以對塊設備來說,盡量避免使用—GFP_IO是遠遠不夠的。一些寫請求設計到的塊設備,可能在順序操作中已經休眠,因而 在這個請求完成前,不可能恢複過來。
使用一個係統級別的設置來禁止這些標誌位,這看起來有些小題大做。因為,隻要考慮那些順序刮起所涉及的進程就足夠了。 但這是一個簡單可靠的修複辦法。不會影響正在運行的係統。
PF_MEMALLOC_NOIO in 3.9-rc1
就像掛起和恢複告訴我們,啟動不是一個很特殊的場景。進而,運行時的電源管理搞所我們,刮起也不是很特殊的場景。 如何一個塊設備為了節省電源,在係統運行期間掛起,顯然直到他或者他依賴的設備(如USB控製器,PCI總線)恢複過來前, 任何將一個髒頁寫出的請求都不會得到處理。所以任何一個這樣的設備執行帶有—GFP_IO的內存請求都是不安全的。
當一個設備刮起或者恢複的時候,我們使用set_gfp_allowed_mask()來確保這一點。但是如果有多個這樣的設備同時 掛起或者恢複,我們很難處理什麼時候恢複正確的mask。所以有個patch引入了一個進程的標誌位,就像PF_FSTRANS標誌一樣, 隻禁止了—GFP_IO標誌位而不是—GFP_FS標誌位。在設置標誌時,先記錄老的值,等完成時,再恢複回去。每個設備增加了一個 memalloc_noio標誌,來指明什麼時候去設置這個標誌。他還可以被傳遞到設備樹裏的父節點。當一個帶有memalloc_noio的設備進入 電源管理的代碼的時候,PF_MEMALLOC_NOIO就會被設置。
盡管啟動的早期階段和設備的掛起和恢複大多是單線程(或者指定的線程),設置在這些線程上設置PF_MEMALLOC_NOIO和PF_FSTRANS 標誌位能夠替代set_gfp_allowed_mask。 但是,這樣的變化沒有明顯的好處,而且能否正常工作並安全並不是很明確,所以目前還是保持不變。
Patterns that emerge
節下來有兩個地方要做進一步的修改: 1。如何進一步避免“避免遞歸”。最初是個枚舉類型的值,然後是—GFP_IO,之後PF_MEMALLOC。下一步是第二版的—GFP_IO.最後會分成 兩個獨立的標誌位。 2。一個GFP標誌位是不夠的, 不是一個單獨的申請要被控製,而是給定進程的所有內存申請都要被控製。 我們使用一個進程標誌來禁止—GFP_WAIT或者一個每進程gfp_allowed_mask隻是一個時間問題嗎?
在3.6rc1,增加了一個新的標誌—GFP_MEMALLOC,以支持swap-over-NFS。 這個標誌在忽略低水位線並訪問保留內存方麵跟PF_MEMALLOC類似。 這個標誌位和每socket的sk_allocation mask, 使得某些tcp sock(nfs使用他們進行swap over)能夠訪問保留內存,從而保證swap-out成功。 顯然,GFP標誌和進程標誌,以及每設備和每socket標誌都要改。
這篇文章沒完,下周還要結合具體配置進行講解。
Ktap or BPF?
過去幾年內核一直在考慮增加一個動態的跟蹤機製. ktap和bpf是近兩年出現的兩種內核動態跟蹤的機製。 這裏的動態跟蹤機製,我個人的理解就是把報文過濾的虛擬機從用戶態轉移到內核態去做, 這樣可以提高效率。例如,減少不必要的報文copy(內核態到用戶態)。
BFP就是"Berkeley packet filter"。在各種報文分類的場合,它被廣泛的使用,如tcpdump就支持這種格式。 2011年,BPF采用了一種新的編譯器從而大大提高了速度。3.15內核也采用了這個特性。 一個重寫的BPF引擎可以為用戶空間提供一樣的虛擬指令集,在內核裏這些指令還可以被轉換為一種更 接近硬件指令的格式。跟舊的格式相比,新的格式有很多優勢,包括使用10個而不是2個64位的寄存器, 更高效的跳轉指令,一種允許從BPF程序裏調用內核函數的機製。 顯然,在BPF爭取成為內核動態跟蹤設施的虛擬引擎時,這些附加的能力進一步增強了它的優勢。
目前,如果ktap要想被內核接受,必須先和BPF虛擬引擎融合。 KTAP的作者Jovi Zhangwei(這個兄弟目前在華為)也表達了做出這樣改變的願望, 但同時他也指出了一些BPF的需要解決的缺陷, BPF不支持一些KTAP需要的特性,如訪問全局變量,timer-limited looping等。 Jovi反複的抱怨BPF的跟蹤機製,它是基於“把腳本附加到一個特定的跟蹤點上”, 而Jovi需要一個更加彈性的機製"把一個腳本附加到多個跟蹤點上“。 隻要兩個作者通力合作,這些都不是很困難的事情。但是兩個人溝通出現的問題,彼此誤解。 目前也沒有其它人加入進來,事情就被擱在這兒了。
Scripting languages
Ktap是基於lua語言的,lua的一些屬性在設置動態跟蹤時很有用,如associative arrays。 然而,還有一些人,更喜歡一種C風格的語言。理由是內核使用的C語言,開發者對C風格語言更容易接受。 BPF一開始使用一種受限的C語言版本,Alexei已經提供把GCC和LLVM(?)轉換成BPF虛擬引擎需要的後端。
而Jovi並不讚同上述觀點,他認為ktap工作起來更簡單。他舉了個例子:
void dropmon(struct bpf_context *ctx) {
void *loc;
uint64_t *drop_cnt;
loc = (void *)ctx->arg2;
drop_cnt = bpf_table_lookup(ctx, 0, &loc);
if (drop_cnt) {
__sync_fetch_and_add(drop_cnt, 1);
} else {
uint64_t init = 0;
bpf_table_update(ctx, 0, &loc, &init);
}
}
而KTAP隻需要:
var s ={}
trace skb:kfree_skb {
s[arg2] += 1
}
ALexei承認KTAP語法使用起來更簡潔。同時, 他還建議一旦確定使用哪個虛擬引擎, 用戶空間要有一些腳本語言的支持。 現在情況是,ktap裏有一些有意思的函數,作者希望能有個開發者能夠作些工作, 以便於把這些代碼合入到內核中。
Changing the default shared memory limits
@泰來
Linux內核的System V共享內存從Unix年代開始就有一個固定的限製值。雖然用戶可調大這個值,但是隨著硬件的飛速發展,應用程序期待越來越大的內存,這個值顯然太小了。現在除了調大這個默認值,開發者開始討論我們是否需要這個限製了。但是為了兼容老的程序和避免一些不可預料的後果,簡單粗暴的去掉這個限製也不可能,所以當大家都覺得這個值太小了的時候,修複確是一件不容易的事情。
System V風格的共享內存廣泛用於進程間的通信,比如在多進程的數據庫中就有廣泛的應用。對於空間大小係統有兩種限製,一種是每個空間可以允許的最大值(SHMMAX),一種是所有共享空間加起來的最大值(SHMALL)。在Linux係統中,SHMMAX默認是32MB,SHALL的計算公式是:
#define SHMALL (SHMMAX/getpagesize()*(SHMMNI/16))
其中SHMMNI是係統支持最大的空間個數--默認是4096個。SHAMAX和SHMALL可以通過sysctl調節。實際是係統還有一個宏SHMMNI,表示一個空間的最小值,默認是1個字節,這個值不能被修改。
近年來管理員一上來就調大SHMMAX幾乎成了標配的動作,因為32MB確實太小了。很多文章就建議跑一些受歡迎的程序之前,需要慣例性的調大這個值。
所以,在一些開發者當中認為調大這個值是當務之急。在3月31號的時候,Davidlohr Bueso就提交了一個補丁,把這個值修改成128MB。當然,128隻是一個隨機值,背後沒有任何意義可言。
鼎鼎大名的Andrew Morton隨後指出,這個補丁沒有解決真正的問題。這個值還是需要很多用戶去手動的調大默認值。他唿籲大家想得更遠一點:如何才能永遠的把這個問題徹底根治掉?
一個辦法就是更本不提供SHMMAX這個值給用戶,但是有人指出有時候管理員確實想設置一些限製來避免內存被濫用。Motohiro Kosaki建議看是否可以把默認值設置為0,然後用來表示無最大限製。但是後麵Spraul指出,有用例把SHMALL設置為0來表示徹底關掉共享空間這個機製。所以用0來表示無限製,不能完全走通。
後來Spraul同學搞了一個自己的版本,就是把SHMALL和SHMMAX設置為ULONG_MAX。有人指出這其實又引入了潛在的問題:如果有用戶通過N + 1的方式來設置這兩個值,那麼+1後這兩個值直接溢出了,完全變成了相反的效果。後麵針對這個方式,做了一些小的改進,但都不如人意。
默認把限製去掉,有人覺得SHM空間會有機會耗光內存。如果這個發生,將是災難性的,因為OOM機製無法釋放SHM占用的內存。解決方式是開啟shm_rmid_forced選項(也就是讓強製SHM空間和一個進程聯係在一起,所以可以通過殺死進程來釋放內存)或者選擇手動設置限製(這不是又回到了原點麼?這個補丁的目的就是避免用戶的手工設置)
即使是回到了原點,刪除古老的32MB限製也算得上一個不小的進步。
Loopback NFS: theory and practice
Linux NFS開發者一直以來就知道將一個NFS文件係統掛載到導出它的相同主機上,將會導致死鎖(有時候被稱作回環或者本地NFS掛載)。除了10年前發布的一個補丁外,有很少的努力來解決這個場景的問題因為沒有可信的用例出現。NFS實現的測試肯定能從回環掛載中受益;這很可能觸發上麵提到的補丁。打上這個補丁,其餘的死鎖的觸發需要做出一些努力,因此對測試者的建議本質上是“小心,你應該是安全的”。
對於其他的測試用例,似乎使用“bind”掛載提供了與回環NFS掛載類似的效果。簡言之:假如當你使用回環NFS掛載受傷,你不要簡單的那麼做。然而,最近一個可信的測試用例出現了,激發了關於這個問題更多的思考。它導致作者寫了一篇關於文件係統與內存管理間交互的教育性文章,並且產生了近期發布的補丁集(取代之前的嚐試)來消除大部分,甚至所有的死鎖。
一個簡單的集群文件係統
那個用例涉及使用NFS作為一個高可用性集群中的文件係統,在那,所有的主機擁有共享的對存儲訪問權限。對於集群中所有能平等地訪問存儲的節點來說,你需要某種集群文件係統,像OCFS2, Ceph, 或者GlusterFS.假如集群不需要特定高級的吞吐量並且假如係統管理員偏愛繼續使用已有技術,NFS能提供一個簡單的臨時替代方案。
理論遇到了實踐
這個場景觸發的死鎖通常涉及一係列的事件像:1)NFS服務器試著分配內存,2)內存分配器試著通過把一些內存頁經過NFS客戶端寫到文件係統,3)NFS客戶端等待NFS服務器有些進展。我的假設是這個死鎖是不可避免的因為同一個內存管理器正試著服務2個不同但競爭的用戶: NFS客戶端和NFS服務器。
一種可能的修複方法可能是在一個虛擬機中運行NFS服務器,並且給它以一個固定且被鎖住內存分配,這樣將沒有任何競爭。這將工作,但對於我們的管理員,它很難是一個簡單解決方案,很可能將會帶來為最佳性能而計算VM大小的挑戰。
似乎對__GFP_FS和PF_FSTRANS標識的操作應該能解決死鎖問題。假如我們把nfsd看作是NFS文件係統的底層,死鎖涉及到分配內存的文件係統的底層,並且觸發回寫到相同的文件係統。這準確是__GFP_FS被設計來阻止的死鎖。實際上,在所有nfsd線程中設置PF_FSTRANS標識的確修複之前很容易就碰到的死鎖。
使用__GFP_FS框架,要麼直接,要麼通過PF_FSTRANS, 最終證明作為回環NFS掛載問題的解決方案,要麼是不充分,要麼是不可信賴的。
一個關鍵的補丁
Mel Gorman在Linux 3.2中提交了一個對於這個問題很關鍵的補丁。這個補丁集改變了內存回收和文件係統回寫間的交互。
在3.2之前,內存回收通常發起它能發現的文件髒頁的回寫。在頁能被釋放前,明顯需要寫這個髒頁的內存到永久存儲上,因此似乎當尋找可釋放頁時做這些是有意義的。不幸的是,它有一些嚴重的負麵影響。
一個負麵影響是被使用的內存棧空間的數目。第二個是頁可能以不可測順序被寫出。
因此,Linux 3.2中從直接回收中刪除了回寫,把它留給kswapd或者各種文件係統回寫線程。
等待回寫
在這個補丁被應用後,直接回收將不再回寫髒的文件頁,這個延遲將也不再發生。此時,假如所有看到的髒頁正在等待擁塞的設備,我們將得到一個明顯的小延遲。這個導致了回環NFS掛載一些問題。與Linux 3.2之前存在的隱含延遲相反,清除__GFP_FS標識不能避免這個延遲。這就是為什麼使用__GFP_FS或者PF_FSTRANS標識不夠充分的原因。
從曆史中學習
這個問題是類似於被介紹中提到的那個補丁修複的10前就出現的問題。在那個場景中,問題是正在弄髒頁麵的進程將會慢下來直到一定量的髒頁被寫出。當這發生的時候,nfsd最終被阻塞直到它寫出一些頁麵,因此導致死鎖。在我們當前的場景中,延遲發生是當回收內存的時候而不是弄髒內存,並且延遲有100ms的上限,除此之外,它是一個類似的問題。
解決方案是添加一個每進程標識 PF_LESS_THROTTLE, 它僅為nfsd線程設置。在這門限的時候,進程將會慢下來,這個標識將增加門限,因此解決了死鎖。在那個補丁中,我們能看到2個重要的想法:使用一個每進程標識,和不要完全刪除限流,僅增大門限來避免死鎖。當弄髒頁麵的時候nfsd根本不限流,它將導致其他問題。
一個去,一個留
隨著在控製下的livelock,不僅對於回環NFS掛載,而且同樣潛在的對於loop塊設備,我們僅需要處理一個遺留的死鎖。當我們發現第一個問題的時候,實際要求的改變是相當小的。下周將發出的對這個改變的理解和解釋將是更加本質的。
最後更新:2017-06-06 10:01:36