宋寶華:Linux內核中用GFP_ATOMIC申請內存究竟意味着什麼?

本文目的

本文補充校正一些Linux內核開發者關於GFP_ATOMIC的認知不完整的地方,闡述GFP_ATOMIC與free內存watermark的關係,並明確什麼時候應該用GFP_ATOMIC申請內存。目錄:

1. GFP_ATOMIC vs. GFP_KERNEL

2. 內存水位,PF_MEMALLOC和GFP_ATOMIC

3. 何時使用GFP_ATOMIC(一個patch分析)

GFP_ATOMIC vs. GFP_KERNEL

我們都知道,在中斷、軟中斷、spinlock等原子上下文裏面,申請內存,應該使用GFP_ATOMIC標記,譬如內核中有大量的kmalloc/GFP_ATOMIC的例子:

對於不可睡眠的上下文,如果我們用常規的GFP_KERNEL這樣的標記去申請內存,可能引發直接的內存reclaim,從而引起睡眠,所以GFP_KERNEL這種標記只適合進程上下文調用:

GFP_KERNEL的標記可以引發直接的內存回收,從而導致進程阻塞睡眠,這在原子上下文顯然是不允許的。

#define GFP_KERNEL     \
 (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
 
 #define __GFP_RECLAIM \
 ((__force gfp_t)(___GFP_DIRECT_RECLAIM|___GFP_KSWAPD_RECLAIM)

內存水位,PF_MEMALLOC和GFP_ATOMIC

那麼GFP_ATOMIC是否僅僅意味着不能睡眠呢?檔案是否定的,GFP_ATOMIC還與內存reclaim的水位相關。下面這個圖是講述水位watermark的一個著名的圖,筆者懶得畫了,直接從網下copy過來:

在Linux中,內存有3個水位:

  • HIGH: 系統的free內存大於HIGH水位的時候,是一個相對保險的值,不需要急着做內存回收(reclaim);

  • LOW: 系統的free內存達到LOW水位的時候,啓動後臺kswapd進行內存回收,回收的目標是讓空閒內存達到HIGH水位;

  • MIN:系統應該保有的最小free內存,當空閒內存達到這個值的時候,kswapd的後臺回收可能來不及了,一般用戶在申請內存的時候,進行DIRECT RECLAIM。

min水位一般是系統自動換算的,其具體值可以從/proc看出:

# cat /proc/sys/vm/min_free_kbytes 
45056

而LOW水位一般是min*125%,HIGH 一般是min*150%。

MIN水位以下的內存,只能被緊急情況下的用戶申請到,最著名的緊急用戶莫過於PF_MEMALLOC用戶,task_struct設置了這個標記表示忽略MIN水位。比如回收內存的代碼本身也可能需要申請內存,這個時候我們應該給它無限制的申請能力。典型地,比如kswapd就設置了這個標記,這個代碼裏面的註釋也非常精彩:

如果我們不允許回收內存的代碼申請min以下的內存,則回收內存的代碼可以觸發回收內存,這樣“子子孫孫,無窮匱也”

當然,PF_MEMALLOC不是唯一的緊急用戶,GFP_ATOMIC實際也是一個“半緊急”任務:

  • 說它“緊急”,是因爲如果原子上下文申請內存失敗,往往意味着相應的中斷、軟中斷、spinlock內部的代碼就會執行失敗,而我們又不會因爲這種失敗,而去嘗試內存回收,這顯然比較慘,我們應該儘可能讓GFP_ATOMIC申請成功;

  • 說它“半”,是因爲它不至於緊急到PF_MEMALLOC這個程度,如果我們給它無限地申請到free內存爲0的權力,則會導致PF_MEMALLOC沒有內存了。想想,如果徵糧隊的人都餓死了,還怎麼去徵糧呢?

所以,內存的設計選擇是,當有人用GFP_ATOMIC申請內存的時候,允許它從MIN水位以下,申請一定數量的內存。什麼叫“一定數量”呢?就是不能讓GFP_ATOMIC導致free 內存觸底,GFP_ATOMIC還包含了高優先級的含義:

#define GFP_ATOMIC    \  
(__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)

注意這個裏面的__GFP_HIGH不是HIGHMEM高端內存的意思,而是高優先級。

當我們用GFP_ATOMIC申請內存的時候,內核的水位檢查代碼,會允許我們觸及到MIN水位以下的1/2:

那麼,“魔鬼”就是在畫紅圈的2行代碼。但是,如果我們進一步深究,會發現,GFP_ATOMIC不只是觸及1/2*min,它甚至可以觸及1/4*min,因爲GFP_ATOMIC中的__GFP_HIGH讓ALLOC_HIGH成立,而__GFP_ATOMIC讓ALLOC_HARDER成立

所以,“魔鬼”又隱藏在了gfp_to_alloc_flags()的細節裏。

一個patch的例子

在具體的工程實戰中,我們建議:

  • 原子上下文使用GFP_ATOMIC

比如在網絡設備驅動drivers/net/ethernet中,就有大量的案例

  • 在內存緊急的路徑上(比如不想睡眠,要求低延遲;或者要求內存喫緊的情況下,仍然可以從min水位以下申請內存),哪怕是進程上下文,我們也建議可以考慮使用GFP_ATOMIC

比如田濤童鞋最近在mm/zswap.c發的RFC patch:

https://lore.kernel.org/linux-mm/[email protected]/

上面2個地方,其實都是可以睡眠的進程上下文,但是我們認爲在frontendswap的路徑上,我們對延遲敏感,對swap內存過程中進一步引發內存回收也擔憂,因此,這裏哪怕是非原子上下文,我們也沒有使用GFP_KERNEL。

(END)

更多精彩,盡在"Linux閱碼場",掃描下方二維碼關注

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章