kernel feature collection

Fault injection

http://lwn.net/Articles/209257/html

The framework can cause memory allocation failures at two levels: in the slab allocator (where it affects kmalloc() and most other small-object allocations) and at the page allocator level (where it affects everything, eventually).

 

Page ownerlinux

http://lwn.net/Articles/121656/程序員

 

The kernel lock validator

http://lwn.net/Articles/185666/算法

 

The "too small to fail" memory-allocation rule

http://lwn.net/Articles/627419/編程

 

     內核開發者長久以來被告知,除了少數狀況外,在系統沒有足夠的資源的狀況下嘗試申請內存可能會失敗。結果,在設計好的代碼裏面,每次調用內存分配函數,好比kmalloc(),vmalloc()或__get_free_pages()都伴隨着一些當心設計的錯誤處理代碼。可是,內存管理系統的實際實現可能跟上面說的不同。這種不一樣可能致使一些不幸的運行時行爲,可是修改它可能使狀況變得更糟。
     關於這個問題的討論出如今Tetsuo Handa發表了一個關於如何解決某個出現的特定問題的疑問。事情的大致過程是這樣的:緩存

1. 一個當前佔用相對來講比較少內存的進程引用了一個XFS文件系統操做,這個操做須要申請內存。
2. 內存管理子系統嘗試知足內存申請的需求,可是發現沒有足夠的內存。而後內存子系統首先嚐試直接重聲明(direct reclaim)內存(強制一部分頁換出內存),若是尚未釋放足夠須要的內存,則繼續調用OOM killer。
3. OOM killer挑選進程並殺死它。
4. 被殺死的進程在退出的時候必須執行一些在同一個XFS文件系統上的操做。這些操做須要獲取一些鎖,而這些鎖正被引發這個有問題的內存分配的進程(1中的進程)所佔用。而後,系統死鎖。

     換句話說,申請內存的進程不能進行下去是由於它正等待分配調用返回。這個調用須要有足夠的空閒內存纔會返回,而空閒內存須要經過其餘進程退出才能釋放。同時,OOM killer也在等待這個進程退出才能繼續(可能)選擇其餘進程退出。可是這個進程不能退出,由於它須要一些被申請內存的進程佔用的鎖。系統鎖住了,而後系統的全部者開始認真的考慮切換到某個版本的BSD系統了。安全

     當被問到這個問題時,XFS maintainer Dave Chinner很快回復說爲何內存管理代碼會調用OOM killer而不是返回內存分配失敗。他說XFS的代碼能很好的處理內存分配失敗。對他來講,返回內存分配失敗可能比隨機殺死一個進程並可能鎖住系統更好。而後,內存管理系統的maintainer說:數據結構

「(這是由於)有一個不成文的規定:low-order(<=PAGE_ALLOC_COSTLY_ORDER)的GFP_KERNEL內存分配不會失敗。這是一個好久之前的決定,如今修改它會很困難。」

Dave懷疑的回覆說:架構

「咱們老是被告知內存分配不保證成功,曾經,除非__GFP_NOFAIL標誌被設置,可是這已通過時了,沒人再被容許使用它了。
不少代碼依賴內存分配來繼續運行或在低內存的狀況下的系統上失敗。頁緩存就是其中之一,這也就意味這全部的文件系統也存在這種依賴。咱們不是顯式的要求內存分配失敗,咱們只是但願內存分配失敗出如今低內存的狀況下。咱們在過去的15年裏一直這樣設計和coding的。」

     「too small to fail」分配對大多數內核來講是指少於或等於八個連續頁的分配,這(八個頁)相對比較大。沒人確切的知道這條規則是什麼時候進入內核的,它在Git以前就被引入了。Johannes Weiner解釋說,當時的想法是這樣的,若是這麼小的內存分配都不能被知足,那麼系統將變得不可用;也不存在除了調用OOM killer以外其餘可行的替代方法。這多是正確的,可是鎖住一個內核正在處理內存分配錯誤的系統也會致使系統不可用。app

     討論中提到的一個方法是給特定的申請增長__GFP_NORETRY標誌。這個標誌會致使在資源不足的狀況下即便小的內存分配申請也會失敗。可是,就像Dave提到的,用__GFP_NORETRY去修復潛在的死鎖請求就是一個「打狗熊」的遊戲,總會有更多的熊熊,而且他們會取得最終的勝利。
      最終的方法可能就是移除「too small to fail」規則並讓內存分配函數按照大多數內核開發者想的那樣去運行。Johannes的信息裏包含了一個採用這種方法的patch:若是直接重聲明沒有釋聽任何內存,它就會讓無休止的重聲明循環退出(並給內存分配申請返回失敗)。可是,就像他提到的,通過如此長時間的嘗試後order-0分配都不能知足,這太可怕了。
     說它可怕是有兩個緣由,一個是否是全部的內核開發者都認真檢查每次的內存分配並考慮合適的恢復路徑。但更糟的是,由於小內存分配不會失敗,內核中幾乎全部的上千的的錯誤恢復路徑歷來沒有執行過。開發者可使用內核中的「fault injection」框架來測試這些代碼,可是事實上,幾乎沒有開發者會這樣作。因此這些錯誤恢復路徑代碼不只僅是未被使用到,並且至關大一部分在第一時間都沒被測試過。
     若是這個不成文的「too small to fail」規則被移除了,全部的這些錯誤恢復代碼就會第一次被執行。某種程度上,內核將會增長上千行未經測試的而且只會在罕見的系統已出錯的狀況下運行的代碼。毫無疑問,這將會致使不少bug和潛在的安全問題。
      這讓內存管理開發者陷入兩難的境地。讓內存分配代碼按照建議的那樣運行會給內核引入不少難以調試的問題。可是,「too small to fail」也有它本身的缺點;同時,愈來愈複雜的內核鎖住可能讓狀況變得更糟;它也會浪費至關長的開發時間來開發從不會執行的錯誤恢復代碼。即便如此,在如此晚的時間點上引入low-order內存分配失敗可能太可怕了,即便長期來看可能讓內核變得更好。

 

Some ado about zero

http://lwn.net/Articles/340370/

     計算機使用不少零(不少使用零的場合)。在編程事業的早期,我工做在一臺有一個特殊的包含零的硬件寄存器的機器;這臺機器上的程序員知道他們可使用他們須要的零而不用擔憂系統跑飛。同時,在上個世紀,linux內核提供了一個充滿零的頁。在X86架構上它叫作empty_zero_page,它甚至能被模塊使用。有趣的是,這個特殊的頁不像在2.6.24以前的內核那樣被普遍使用了,可是這種狀況可能將要改變。
     在一切運行良好的之前,當內核須要一個充滿零的頁時,它會使用這個零頁。因此,好比,若是一個進程在一個它從未使用過的頁上發生了讀錯誤,內核就會簡單的映射這個零頁到那個地址。固然,寫時複製也會用到,若是進程接下來要修改這個頁,它就會獲得它本身的一份複製。可是推遲新的零頁的建立有助於保存零,並防止內核跑飛(溢出?run out)。同時,它也能節省內存,減小緩存壓力,消除清除頁的需求。
     2007年的內存管理改動增長了對零頁的引用計數功能。在多處理器機器上這會有問題。因爲全部的處理器共享同一個零頁(per-CPU差別是不可能的),它們也都管理相同的引用計數。這會致使緩存行波動,顯著的性能影響等嚴重的問題。做爲迴應,Nick Piggin評估了不少可能的修復,包括避免對零頁的引用計數的特殊技巧或增長per-CPU零頁。但合併的補丁卻只是簡單的清除了大多數對零頁的使用。這種修改是這樣解釋的:

爲匿名讀錯誤映射零頁是一個錯誤的優化:若是一個應用是性能相關的,它不會在新內存上發生太多的讀錯誤,或者至少它很快就會向那塊內存寫入數據。若是是緩存或者內存使用相關的應用,那麼它不會使用到大量的零頁(應該使用一個更緊湊的零的表示)。

     當時,對於這個補丁存在一些擔憂。Linus首先抱怨了致使問題的改變並擔憂:

內核一直(差很少從第一天開始)存在零頁。這就意味着若是有些應用依賴於它,那麼我一點也不奇怪。我就曾經寫過依賴於它的測試程序,也許人們也寫過其餘代碼,而這些代碼必須工做在一個提供了只讀零頁的內核上。

     儘管有擔心,Linus仍是在2.6.24版內核合併了這個補丁來看看會不會出現問題。接下來的18個月都沒出現問題,大部分人都把零頁忘了。可是在6月的早些時候,Julian Phillips報告了一個他發現的問題:

我有一個建立了大量私有匿名映射的程序。這個程序只會向一部分映射寫入數據,可是會從全部的映射中讀取數據。
當我在一個2.6.20.7的系統上跑這個程序時,進程好像有足夠的內存來保存實際寫入的數據(以PAGE_SIZE大小爲單位)。當我在一個2.6.24.5的系統上跑它時,它使用的內存的數量一直增加直到整個映射被實際分配了內存(總大小大於物理內存致使了swapping)。我好像看到了讀時複製而不是寫時複製。

      固然,Julian看到的就是零頁被移除後的影響。在老的內核裏,數據結構中全部未寫入的頁都會被映射到零頁,一點都不使用額外的內存。而2.6.24內核,全部的這些頁每一個都有一個只包含零的物理頁,這大大增長了內存使用量。

      Hiroyuki Kamezawa報告說他在其餘地方也看到了依賴零頁的應用。他說在這些地方運行的企業級linux發行版,這些發行版尚未使用新的缺少零頁支持的內核。他擔憂這些用戶升級到新的內核後會遇到Julian碰到的問題。做爲迴應,他發佈了一個給內核恢復零頁支持的補丁。
     可是Hiroyuki的零頁支持跟之前的不太同樣。它避免了零頁的引用計數,這個改變會消除最壞的性能問題。但它也增長了一些有趣的特殊case,虛擬內存代碼須要爲零頁當心的測試這些cases;大部分cases能夠用新增長的get_user_pages_nonzero()函數處理,這個函數刪除指定範圍內的任何零頁。Linus不喜歡這些特殊的cases,認爲它們不必。做爲替代,Linus建議使用相對較新的PTE_SPECIAL標誌來標示零頁的可選實現。截止到本文發表,使用這種方法更新的補丁還沒發佈。
     首先寫出移除零頁支持補丁的Nick Piggin,不想看到內核恢復零頁支持。考慮到被影響的用戶,他問:

咱們能不能只是讓他們別用它(零頁)了?爲罕見的場景使用零頁可能很差,由於那仍會觸發錯誤並且還佔用TLB空間。使用更好的算法他們可能會看到更好的性能。

     可是若是恢復零頁支持代價不大的話,Linus仍是很樂意的。因此若是補丁工做的很好,零頁支持迴歸的可能性很大。可是否會給企業級linux用戶帶來福音還有待觀察,下一代的企業級linux可能會使用2.6.27內核。除非發行版廠商向後移植零頁補丁,不然企業級linux用戶仍會遇到目前的零頁問題。

 

 

Adding a huge zero page

http://lwn.net/Articles/517465/

     THP(透明大頁)特性容許應用程序使用目前大多數處理器支持的更大的頁而不須要管理員、開發者或用戶顯式的配置。整體來講這是一個提高性能的特性:大頁減小了系統TLB的壓力,讓內存訪問更快。由於刪除了一層頁表,它也節省了一些內存。可是,在某些條件下,THP也確實顯著增長了應用程序的內存使用量。好消息是目前有個解決方法:充滿零的頁。
     THP主要用在匿名頁--不是爲磁盤文件作備份(緩存)的頁,它們構成了進程的數據域。當一個匿名內存域被建立或擴展時,其實並無分配物理頁(無論THP是否被使能)。這是由於一個典型的程序的部分地址空間中的不少頁歷來不會被訪問到,在被證實確實須要以前分配頁會浪費至關多的時間和內存。因此內核會等到進程試圖訪問某個特定的頁而產生頁錯誤的時候纔會爲這個頁分配內存。
     可是即便這樣,仍然能夠作一個優化。新的匿名頁必須被零填滿,在之前使用者留下了的任何數據的頁上作任何其餘操做都是風險的。程序常常依賴於它們使用的內存的初始化;因爲它們知道內存一開始是填滿零的,因此它們沒有必要本身初始化內存。這些頁不少歷來就不會被寫入,它們在擁有它們的進程的整個生存期間都保持零。一旦意識到這個,咱們就不難想到能夠經過共享這些零頁來節省不少內存。一個零頁很像另一個,因此不必建立太多。
     因此,若是一個進程經過讀取實例化了一個新的頁(非大頁),內核仍然不會分配一個新的內存頁,而是映射到一個特殊頁,簡稱零頁,到進程的地址空間。因此,系統上全部進程的全部的未寫入的匿名頁,實際上都共享了一個特殊的頁。不用說,零頁是隻讀的,進程不容許修改它的值。當一個進程想要寫入零頁時,會產生一個寫保護錯誤,內核而後(最終)會分配一個真實的物理頁並把它替換到進程地址空間的合適位置。
     這種行爲很容易觀察到。正如Kirill Shutemov描述的,一個執行以下代碼的進程:

    posix_memalign((void **)&p, 2 * MB, 200 * MB);
    for (i = 0; i < 200 * MB; i+= 4096)
        assert(p[i] == 0);
    pause();

運行到pause()時,只佔用了不多的內存。它申請了200MB內存,但這部份內存都用一個零頁表明了。系統照常工做。

     但當THP特性被使能後,這個進程會顯示它分配了200MB的內存。當用戶使能一項性能提高特性時,兩個數量級的內存使用量增加可不是它們但願看到的。因此,Kirill說,有些時候用戶不得不把THP關掉。
     問題很簡單:沒有大零頁。THP在任何可能的狀況下使用大頁;當一個進程在一個新頁上發生錯誤時,內核會嘗試給它提供一個大頁。因爲沒有大零頁,內核只會簡單的申請一個真實的零頁(大頁)做爲替代。這種行爲可讓進程繼續執行下去,但它也引發了大量沒必要要的內存分配。THP,換句話說,關掉了在內核內存管理子系統存在了不少年的另外一個重要優化。
     一旦問題搞明白了,解決方法也就不難了。Kirill的補丁增長了一個特殊的零填充的大頁被用做大零頁。只須要一個這樣的頁,由於THP特性只用到一個大頁大小。爲讀錯誤使用這個頁,內存佔用擴增的問題很容易就解決了。
     像往常同樣,有抱怨說:這個頁好大,若是在THP不用的時候就不分配它就行了。因此,出現了一個懶分配方案,Kirill也增長了一個引用計數,在大零頁不須要的時候會被回收。這個引用計數減慢了讀錯誤基準測試1%,因此它值不值得還有待觀察。最終,開發者得出結論:大零頁一旦被分配就放在那兒比爲引用計數付出代價會更好。畢竟,零頁出現過相似的問題。
     尚未針對這個補丁的大量評論,實現也相對比較順利。大零頁的增長帶來了明顯的大量益處,在不久的未來就會被加入到內核,3.8版看起來就不錯。

 注:(原文評論)

Q:

  Why not just keep the small zero page mapping in this case and switch to a huge (no-zero) page on the first write?

A:

     Because the whole point is to save a layer of page translations. You can't, when you do get a page fault, go back a layer and check whether all of the other pages referenced by that layer all point to the (small) zero page.
     That would kill more performance than you gain by huge pages in the first plale.

 

Transparent huge pages in 2.6.38

 http://lwn.net/Articles/423584/

     當前大多數的處理器的MMU能夠處理多種大小的頁,可是linux內核只使用其中最小的--大部分架構是4096字節。比4KB大的頁統稱爲大頁--能夠給某些負載提供更好的性能,可是這種性能提高在linux上未被充分利用。不過隨着THP特性的合入2.6.38內核,狀況開始發生改變。
     大頁能夠經過減小頁錯誤(一次錯誤一次性引入大量內存,也就是一次錯誤會映射比之前更多的內存從而減小頁錯誤)和減小虛擬地址到物理地址轉換的代價(爲得到物理地址而須要跨越的頁表的等級更少)來提高性能。但真正的優點來自徹底避免了轉換。若是處理器必須轉換一個虛擬地址,它就必須遍歷多達四級的頁表,每一級均可能是緩存冷(cache-cold)的,也就很慢了。爲了這個緣由,處理器實現了TLB來緩存轉換的結果。TLB通常很小,在我老的桌面機器上運行cpuid會顯示:

   cache and TLB information (2):
      0xb1: instruction TLB: 2M/4M, 4-way, 4/8 entries
      0xb0: instruction TLB: 4K, 4-way, 128 entries
      0x05: data TLB: 4M pages, 4-way, 32 entries

一共有128個指令轉換條目和32個數據轉換條目。這麼小的緩存很容易用光,強迫CPU去執行大量的地址轉換。一個2MB的大頁須要一個TLB條目,以4KB爲單位的相同大小的內存則須要512個TLB條目。這樣,使用大頁可讓程序跑的更快也就沒什麼可奇怪的了。

     主要的內核地址空間是用大頁映射的,下降了內核代碼的TLB壓力。可是在目前的內核上,用戶空間使用大頁的惟一方法是經過2010年就存在的hugetlbfs。使用hugetlbfs須要來自應用開發者和系統管理員的大量工做,大頁必須在啓動的時候建立(這多是不正確的,原文評論也提到了:「On systems that support them, 1GB and 16GB pages must be reserved at boot, but 2MB, 4MB, and 16MB pages can be allocated any time there is contiguous space.」。同時在CentOS 6.5系統上也看到了可在系統啓動後開啓hugetlbfs,見下文),應用程序也須要顯式的映射它們。那些爲使用hugetlbfs而精巧設計的進程只限於那些確實關心和有足夠時間與hugetlbfs磨合的進程。
      當前的linux內核假定在一個給定的VMA中全部的頁都是相同大小的。爲了讓THP工做,Andrea不得不放棄了這一假定,因此,大部分補丁都是爲了在VMA中兼容混合頁大小。而後補丁用一種簡單的方法修改了頁錯誤處理函數:當一個錯誤發生時,內核嘗試分配一個大頁去知足它。若是分配成功了,大頁就會被填充(根據已存在的小頁的內容填充,其餘地方填充零),在這個新頁的地址範圍內的任何已存在的小頁就會被釋放,而後大頁被插入VMA。若是沒有可用的大頁,內核就用小頁,應用程序不會發覺這種不一樣。
      這種方案會透明的增長大頁的使用,可是它尚未解決整個問題。大頁必須是能被交換的,以避免系統很快耗盡內存。不是抱怨交換代碼不支持大頁,Andrea只是簡單的在頁須要被重聲明的時候把它拆分紅小頁。許多其餘的操做(mprotect(), mlock(), ...)也會致使大頁的拆分。
     大頁的分配依賴於大的物理上連續的內存的可用性--這是內核開發者從沒關心過的。這些物理上連續的內存只有在一些特殊的狀況下才會存在--好比一個進程在不少小頁上發生錯誤後。THP補丁想經過增長一個khugepaged內核線程來改善這種狀況。這個線程會不按期的分配大頁,若是成功了,它就掃描內存看看什麼地方能用這個大頁來替換小頁。因此,可用的大頁會很快投入使用,最大化系統上大頁的使用。
     當前的補丁只能工做在匿名頁上,把大頁集成到頁緩存的工做還沒有完成。它也只能處理一種大頁(2MB)。即便如此,咱們仍是看到了一些有用的性能提高。Mel Gorman在某些場景下的基準測試能提高高達10%左右。通常來講,性能提高尚比不上hugetlbfs,可是THP更有可能被實際使用。
     爲使用THP,應用程序不須要作任何修改,但感興趣的應用開發者能夠試着爲了THP作一些優化。一個帶MADV_HUGEPAGE標誌的madvise()調用會標示一個內存區域適合使用大頁,而MADV_NOHUGEPAGE標誌則代表大頁最好用在其餘地方(不適宜用在此處)。對想使用大頁的應用程序來講,使用posix_memalign()能幫助確保大塊分配是大頁邊界對齊的。
     系統管理員有不少能夠調整的選項,它們都在/sys/kernel/mm/transparent_hugepage下面。enabled能夠被設置成「always」(老是使用THP),「madvise」(只在MADV_HUGEPAGE標誌的VMA中使用THP)或「never」(關閉THP)。另外一個選項「defrag」,也使用相同的可選值,它控制着內核是否應該採起積極的內存壓縮來使更多的大頁可用。也有一些控制khugepaged線程的參數,詳見Documentation/vm/transhuge.txt。
     自從合入主線後,THP補丁一路坎坷。這部分代碼沒有在linux-next分支上出現過,因此當它引發主線內核編譯錯誤時驚到了一些架構maintainers。一些bug也被發現了--對於影響了如此多核心代碼的大補丁來講一點都不奇怪。這些問題都被標了出來,因此,儘管2.6.38-rc1的測試者要當心,不過到2.6.38最終發佈的時候THP應該會好不少。

 

 http://developerblog.redhat.com/2014/03/10/examining-huge-pages-or-transparent-huge-pages-performance/

There are two mechanisms available for huge pages in Linux: the hugepages and Transparent Huge Pages (THP). Explicit configuration is required for the original hugepages mechanism. The newer transparent hugepage (THP) mechanism will automatically use larger pages for dynamically allocated memory in Red Hat Enterprise Linux 6.

To determine whether the newer Transparent HugePages (THP) or the older HugePages mechanism are being used, look at the output of /proc/meminfo as below:

$ cat /proc/meminfo|grep Huge
AnonHugePages:   3049472 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB

The AnonHugePages entry lists the number of pages that the newer Transparent Huge Page mechanism currently has in use. For this machine there are 309472kB, 1489 huge pages each 2048kB in size.

In this case there are zero pages in the pool of the older hugepage mechanism as shown by HugePages_Total of 0. The HugePages_Free shows how many pages are still available for allocation, which is going to be less than or equal to HugePages_Total. The number of HugePages in use can be computed as HugePages_Total-HugePagesFree. For more information about the configuration of HugePages see Tuning and Optimizing Red Hat Enterprise Linux for Oracle 9i and 10g Databases.

 

 

 

 

(轉載本站文章請註明做者和出處 http://www.cnblogs.com/baiyw/,請勿用於任何商業用途)

相關文章
相關標籤/搜索