在2014年lsf&mm峰會上,dave chinner和ted ts'o一起主持了一個跨越2天而占用2個時間段的會議。 這個會議的主題是,是檔案系統還是塊裝置層才是支援smr裝置的正确接口。最後,它的讨論範圍 有點超出這個主題。
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将會出現。
今年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說原則上他不反對這個使用者态接口。
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這個操作可能會導緻你的拷貝部分成功,是以使用者态程式需要處理這個問題。
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的修改将會使得事情比現在更好,是正确道路上往前的一步。
在今年的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()被幹掉。
(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的大多數議程一樣,開完會後開發者們還是什麼也定不下來,但不管怎麼說---總比不開會強。)
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認為在消除驅動的鎖方面仍有較多工作要做。
dave chinner提到了目前核心需要修改的地方,目前看似乎檔案系統以及處理分區的代碼需要修改。他還提到目前page cache代碼的假設都是能夠基于page大小做io,jan kara也提醒大家目前頁面回收算法也是基于上面這個假設,是以如果頁面大小(4k)小于sector大小,可能比較麻煩。dave提到一種解決方案是用類似irix中的chunk方案,這玩意可以處理多記憶體頁的buffer,但是他不缺定是否靠譜。另一條路子就是允許小于sector尺寸的讀寫,但是這個就不可避免的會出現讀-修改-寫回這種場景,性能就杯具了。
最後ric總結說目前裝置制造商還沒有開始推動大sector尺寸的裝置支援,是以核心社群還有時間,但是核心社群的一個基本原則就是先要搞定sector尺寸大于page尺寸的情況,後面應該就差不多了。
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拆分這樣無效率的工作了。
雖然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自己玩玩尚可,推而廣之還是不要了。可惜的是,究竟怎麼統一格式還沒有定論。
後面開始小話題滿天飛了,我摘要一下:
使用者空間檔案伺服器需要核心協助的地方蠻多的,其中之一是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也沒有定論。是以,也就沒法确定核心應該支援哪個了。
在2014年的linux foundation collaboration summit大會上,red hat的jiri olsa和lg的namhyung kim為我們介紹了perf的一些新特性。
olsa的介紹如下:
libtracepoint由steven rostedt開發,是ftrace前端的一部分。我們知道,每個kernel的tracepoint在它的format檔案裡面都有格式資訊,用以描述該tracepoint輸出資訊的格式。libraceevent可以幫助使用者空間的程式更友善地獲得這些格式資訊。
對記憶體通路行為的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都了如指掌。
在利用ftrace進行profiling時,大家都希望能看到相關的函數調用鍊。目前,我們使用了frame pointer實作了這個功能。如果frame pointer未被使用時(gcc往往會将該寄存器優化為普通寄存器),則利用libdw,在dwarf調試資訊的支援下,unwind應用程式的stack。perf已經能夠記錄使用者棧與相關的寄存器,而libdw可以完成後續的工作。libdw比libunwind要快很多。[扁鵲下一步也将考慮使用libunwind替換掉libunwind]。
另外一種快速生成調用鍊的方法是使用lbr(last branches record)資訊。lbr機制在能夠存儲被采到的那條指令所觸發的分支清單。同時包括分支的源位址與目标位址。利用這些資訊,也能夠獲得調用鍊。
允許一個事件打開或關閉另外一些事件,進而減少被記錄的資料量。原始代碼由frédéric weisbecker開發,但目前尚沒有合适的使用者接口。
目前,perf需要在每個cpu上為每個打開的事件申請一個檔案描述符。如果cpu很多,打開的事件數也很多。那麼檔案描述符很可能不夠用。目前,已經有計劃使得每個“事件組”共享同一個檔案描述符。當然,大家僅僅讨論過這個想法,還沒有代碼。
perf 的一些核心代碼被移到了tools/lib中,其它的工具也可以使用這些代碼了。
perf現在共有26個測試用例。每個commit都需要跑一遍這些測試。測試集還會日益豐富。
namhyung kim的介紹如下:
展示完整的callchain,以及callchain中的每個函數對測試結果的貢獻。在perf report的輸出結果中,增加了“children”與“self”列,用以展示callchain中每個函數的貢獻。
用以指定需要展示的資訊(dso,thread comm等等)。該參數可以與“--sort”配合使用。
在perf中支援ftrace是一個新特性。目前,僅僅支援function與function_graph。該特性利用了libtraceevents的kbuffer api來通路事件。為ftrace事件提供類似perf的行為是未來的目标。
我們可以這樣使用perf的ftrace功能:
perf ftrace record
perf ftrace report 或 perf ftrace show
perf目前能夠利用符号名與行号動态設定tracepoint。該工作由masami hiramatsu貢獻,并已經被收入upstream了。通過“--line”與“--var”便可以利用binary中的debug資訊建立uprobe檢查點。之後,perf可以對這些進行采樣。
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等。
通過共享記憶體來進行程序間通信的常用方法。這個方式簡單高效,但有一個陷阱:所有互動程序必須互相信任。也就是操作共享空間的程序必須假設對方不會做壞事,比如在你的讀的時候偷偷修改了内容還告訴你沒有修改,在不恰當的時機把空間的後端檔案大小變了導緻程序的讀寫操作觸發緻命的信号中止程式等。當然現實中,我們可以通過拷貝共享空間的資料到其它地方來避免這些問題。但這樣做十分低效和笨重。
開發者最近一直在讨論核心層面上的解決方案。三月份david herrmann同學送出了file sealing更新檔,保證一方在操作共享空間的時候對方不會做壞事。
這個方案通過fcntl()操作共享空間的檔案描述符引入了三個标志位
seal_shrink 阻止檔案被變小。
seal_grow 阻止檔案變大。
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系統都有可能用到,是以這個機制被合并的前景很好。
避免申請記憶體死鎖 有句諺語"要掙錢必須先花錢", 盡管這個悖論很容易通過創業貸款和堅持收支平衡的原則來解決。 一個相似的邏輯适用于管理像linux這樣的作業系統的記憶體: 有時候,你為了釋放記憶體,需要先申請記憶體。 這裡,同樣需要堅持原則, 盡管不小心的結果不是破産,而是死鎖。
作為linux開發曆程的一個縮影,核心如何保持其收支的平衡是很有趣的,并有助于了解如何去處理未來發生的死鎖。 讓我們從介紹1998年早期的linux 2.1.80pre3裡的__gfp_io作為一個美好起點。
核心裡的任何記憶體申請都包含一個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核心已被删除了。盡管名字一樣,但是他們是不同的标志了。
很久以前(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再次出現在核心時,跟最初的目的相比,它有一個類似的目的,但是是為了一個很重要的不同的原因。為了了解那個原因, 很有必要看看代碼裡的一個注釋:
/* 如果我們不能夠做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标志而不是一個程序标志, 程序标志可以明确地說“我持有一個檔案系統的鎖”。 像許多軟體設計一樣,這可能“在那時看來是個好主意”
這個标志最初出現在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行為的地方也被排除(禁止使用)了。 重新提煉規則,增加一個新的标志位,進而更加靈活,對性能影響更小。
在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, 進而使這個标志具有全局的屬性的想法,是個不錯的主意。
首次出現在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是遠遠不夠的。一些寫請求設計到的塊裝置,可能在順序操作中已經休眠,因而 在這個請求完成前,不可能恢複過來。
使用一個系統級别的設定來禁止這些标志位,這看起來有些小題大做。因為,隻要考慮那些順序刮起所涉及的程序就足夠了。 但這是一個簡單可靠的修複辦法。不會影響正在運作的系統。
就像挂起和恢複告訴我們,啟動不是一個很特殊的場景。進而,運作時的電源管理搞所我們,刮起也不是很特殊的場景。 如何一個塊裝置為了節省電源,在系統運作期間挂起,顯然直到他或者他依賴的裝置(如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。 但是,這樣的變化沒有明顯的好處,而且能否正常工作并安全并不是很明确,是以目前還是保持不變。
節下來有兩個地方要做進一步的修改: 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和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需要一個更加彈性的機制"把一個腳本附加到多個跟蹤點上“。 隻要兩個作者通力合作,這些都不是很困難的事情。但是兩個人溝通出現的問題,彼此誤解。 目前也沒有其它人加入進來,事情就被擱在這兒了。
ktap是基于lua語言的,lua的一些屬性在設定動态跟蹤時很有用,如associative arrays。 然而,還有一些人,更喜歡一種c風格的語言。理由是核心使用的c語言,開發者對c風格語言更容易接受。 bpf一開始使用一種受限的c語言版本,alexei已經提供把gcc和llvm(?)轉換成bpf虛拟引擎需要的後端。
而jovi并不贊同上述觀點,他認為ktap工作起來更簡單。他舉了個例子:
而ktap隻需要:
alexei承認ktap文法使用起來更簡潔。同時, 他還建議一旦确定使用哪個虛拟引擎, 使用者空間要有一些腳本語言的支援。 現在情況是,ktap裡有一些有意思的函數,作者希望能有個開發者能夠作些工作, 以便于把這些代碼合入到核心中。
@泰來
linux核心的system v共享記憶體從unix年代開始就有一個固定的限制值。雖然使用者可調大這個值,但是随着硬體的飛速發展,應用程式期待越來越大的記憶體,這個值顯然太小了。現在除了調大這個預設值,開發者開始讨論我們是否需要這個限制了。但是為了相容老的程式和避免一些不可預料的後果,簡單粗暴的去掉這個限制也不可能,是以當大家都覺得這個值太小了的時候,修複确是一件不容易的事情。
system v風格的共享記憶體廣泛用于程序間的通信,比如在多程序的資料庫中就有廣泛的應用。對于空間大小系統有兩種限制,一種是每個空間可以允許的最大值(shmmax),一種是所有共享空間加起來的最大值(shmall)。在linux系統中,shmmax預設是32mb,shall的計算公式是:
其中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限制也算得上一個不小的進步。
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塊裝置,我們僅需要處理一個遺留的死鎖。當我們發現第一個問題的時候,實際要求的改變是相當小的。下周将發出的對這個改變的了解和解釋将是更加本質的。