1 前景回顧 1.1 內核映射區 儘管vmalloc函數族可用於從高端記憶體域向內核映射頁幀(這些在內核空間中通常是無法直接看到的), 但這並不是這些函數的實際用途. 重要的是強調以下事實 : 內核提供了其他函數用於將 頁幀顯式映射到內核空間, 這些函數與vmalloc機制無關. 因此, 這就造成了混 ...
1 前景回顧
1.1 內核映射區
儘管vmalloc函數族可用於從高端記憶體域向內核映射頁幀(這些在內核空間中通常是無法直接看到的), 但這並不是這些函數的實際用途.
重要的是強調以下事實 : 內核提供了其他函數用於將ZONE_HIGHMEM
頁幀顯式映射到內核空間, 這些函數與vmalloc機制無關. 因此, 這就造成了混亂.
而在高端記憶體的頁不能永久地映射到內核地址空間. 因此, 通過alloc_pages()函數以__GFP_HIGHMEM標誌獲得的記憶體頁就不可能有邏輯地址.
在x86_32體繫結構總, 高於896MB的所有物理記憶體的範圍大都是高端記憶體, 它並不會永久地或自動映射到內核地址空間, 儘管X86處理器能夠定址物理RAM的範圍達到4GB(啟用PAE可以定址64GB), 一旦這些頁被分配, 就必須映射到內核的邏輯地址空間上. 在x86_32上, 高端地址的頁被映射到內核地址空間(即虛擬地址空間的3GB~4GB)
內核地址空間的最後128 MiB用於何種用途呢?
該部分有3個用途。
- 虛擬記憶體中連續、但物理記憶體中不連續的記憶體區,可以在vmalloc區域分配. 該機制通常用於用戶過程, 內核自身會試圖儘力避免非連續的物理地址。內核通常會成功,因為大部分大的記憶體塊都在啟動時分配給內核,那時記憶體的碎片尚不嚴重。但在已經運行了很長時間的系統上, 在內核需要物理記憶體時, 就可能出現可用空間不連續的情況. 此類情況, 主要出現在動態載入模塊時.
- 持久映射用於將高端記憶體域中的非持久頁映射到內核中
- 固定映射是與物理地址空間中的固定頁關聯的虛擬地址空間項,但具體關聯的頁幀可以自由選擇. 它與通過固定公式與物理記憶體關聯的直接映射頁相反,虛擬固定映射地址與物理記憶體位置之間的關聯可以自行定義,關聯建立後內核總是會註意到的.
在這裡有兩個預處理器符號很重要 __VMALLOC_RESERVE設置了vmalloc
區域的長度, 而MAXMEM則表示內核可以直接定址的物理記憶體的最大可能數量.
- 直接映射區
線性空間中從3G開始最大896M的區間, 為直接記憶體映射區,該區域的線性地址和物理地址存線上性轉換關係:線性地址=3G+物理地址。
- 動態記憶體映射區
該區域由內核函數vmalloc來分配, 特點是 : 線性空間連續, 但是對應的物理空間不一定連續. vmalloc分配的線性地址所對應的物理頁可能處於低端記憶體, 也可能處於高端記憶體.
- 永久記憶體映射區
該區域可訪問高端記憶體. 訪問方法是使用alloc_page(_GFP_HIGHMEM)
分配高端記憶體頁或者使用kmap函數將分配到的高端記憶體映射到該區域.
- 固定映射區
該區域和4G的頂端只有4k的隔離帶,其每個地址項都服務於特定的用途,如ACPI_BASE等。
說明
註意用戶空間當然可以使用高端記憶體,而且是正常的使用,內核在分配那些不經常使用的記憶體時,都用高端記憶體空間(如果有),所謂不經常使用是相對來說的,比如內核的一些數據結構就屬於經常使用的,而用戶的一些數據就屬於不經常使用的。用戶在啟動一個應用程式時,是需要記憶體的,而每個應用程式都有3G的線性地址,給這些地址映射頁表時就可以直接使用高端記憶體。
而且還要糾正一點的是:那128M線性地址不僅僅是用在這些地方的,如果你要載入一個設備,而這個設備需要映射其記憶體到內核中,它也需要使用這段線性地址空間來完成,否則內核就不能訪問設備上的記憶體空間了.
2 kmallc & kfree分配釋放連續的物理記憶體
kmalloc和kzalloc
kmalloc函數與用戶空間的malloc一族函數非常類似, 只不過它多了一個flags參數, kmalloc函數是一個簡單的介面, 用它可以獲取以位元組為單位的一塊內核記憶體.
如果你需要整個頁, 那麼前面討論的頁分配介面是更好的選擇. 但是, 對大多數內核分配來說, kmalloc介面用的更多, 同時內核也提供了kzalloc該介面在kmalloc的基礎上會將分配的記憶體清0. 他們定義在tools/virtio/linux/kernel.h?v=4.7, line 46
這兩個函數返回一個指向記憶體塊的指針, 其記憶體塊至少要有size大小. 所分配的記憶體區在物理上是連續的. 在出錯時, 它返回NULL. 除非沒有足夠的記憶體可用, 否則內核總能分配成功. 在對kmalloc調用之後, 你必須檢查返回的是不是NULL, 如果是, 要適當處理錯誤.
kfree釋放記憶體
kmalloc的另一端就是kfree, 用於釋放分配的記憶體, kfree聲明與定義
3 分配掩碼(gfp_mask標誌)
3.1 分配掩碼
前述所有函數中強制使用的mask參數,到底是什麼語義?
我們知道Linux將記憶體劃分為記憶體域. 內核提供了所謂的記憶體域修飾符(zone modifier)(在掩碼的最低4個比特位定義), 來指定從哪個記憶體域分配所需的頁.
內核使用巨集的方式定義了這些掩碼, 一個掩碼的定義被劃分為3個部分進行定義, 我們會逐步展開來講解, 參見include/linux/gfp.h?v=4.7, line 12~374, 共計26個掩碼信息, 因此後面__GFP_BITS_SHIFT = 26.
3.2 掩碼分類
Linux中這些掩碼標誌gfp_mask分為3種類型 :
類型 | 描述 |
---|---|
區描述都符 | 內核把物理記憶體分為多個區, 每個區用於不同的目的, 區描述符指明到底從這些區中的哪一區進行分配 |
行為修飾符 | 表示內核應該如何分配所需的記憶體. 在某些特定情況下, 只能使用某些特定的方法分配記憶體 |
類型標誌 | 組合了行為修飾符和區描述符, 將這些可能用到的組合歸納為不同類型 |
3.3 內核中掩碼的定義
3.3.1 內核中的定義方式
// http://lxr.free-electrons.com/source/include/linux/gfp.h?v=4.7
/* line 12 ~ line 44 第一部分
* 定義可掩碼所在位的信息, 每個掩碼對應一位為1
* 定義形式為 #define ___GFP_XXX 0x01u
*/
/* Plain integer GFP bitmasks. Do not use this directly. */
#define ___GFP_DMA 0x01u
#define ___GFP_HIGHMEM 0x02u
#define ___GFP_DMA32 0x04u
#define ___GFP_MOVABLE 0x08u
/* ...... */
/* line 46 ~ line 192 第二部分
* 定義掩碼和MASK信息, 第二部分的某些巨集可能是第一部分一個或者幾個的組合
* 定義形式為 #define __GFP_XXX ((__force gfp_t)___GFP_XXX)
*/
#define __GFP_DMA ((__force gfp_t)___GFP_DMA)
#define __GFP_HIGHMEM ((__force gfp_t)___GFP_HIGHMEM)
#define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32)
#define __GFP_MOVABLE ((__force gfp_t)___GFP_MOVABLE) /* ZONE_MOVABLE allowed */
#define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)
/* line 194 ~ line 260 第三部分
* 定義掩碼
* 定義形式為 #define GFP_XXX __GFP_XXX
*/
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
3.3.3 定義掩碼
然後第二部分, 相對而言每一個巨集又被重新定義如下, 參見include/linux/gfp.h?v=4.7, line 46 ~ line 192
/*
* Physical address zone modifiers (see linux/mmzone.h - low four bits)
*
* Do not put any conditional on these. If necessary modify the definitions
* without the underscores and use them consistently. The definitions here may
* be used in bit comparisons.
* 定義區描述符
*/
#define __GFP_DMA ((__force gfp_t)___GFP_DMA)
#define __GFP_HIGHMEM ((__force gfp_t)___GFP_HIGHMEM)
#define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32)
#define __GFP_MOVABLE ((__force gfp_t)___GFP_MOVABLE) /* ZONE_MOVABLE allowed */
#define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)
/*
* Page mobility and placement hints
*
* These flags provide hints about how mobile the page is. Pages with similar
* mobility are placed within the same pageblocks to minimise problems due
* to external fragmentation.
*
* __GFP_MOVABLE (also a zone modifier) indicates that the page can be
* moved by page migration during memory compaction or can be reclaimed.
*
* __GFP_RECLAIMABLE is used for slab allocations that specify
* SLAB_RECLAIM_ACCOUNT and whose pages can be freed via shrinkers.
*
* __GFP_WRITE indicates the caller intends to dirty the page. Where possible,
* these pages will be spread between local zones to avoid all the dirty
* pages being in one zone (fair zone allocation policy).
*
* __GFP_HARDWALL enforces the cpuset memory allocation policy.
*
* __GFP_THISNODE forces the allocation to be satisified from the requested
* node with no fallbacks or placement policy enforcements.
*
* __GFP_ACCOUNT causes the allocation to be accounted to kmemcg (only relevant
* to kmem allocations).
*/
#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE)
#define __GFP_WRITE ((__force gfp_t)___GFP_WRITE)
#define __GFP_HARDWALL ((__force gfp_t)___GFP_HARDWALL)
#define __GFP_THISNODE ((__force gfp_t)___GFP_THISNODE)
#define __GFP_ACCOUNT ((__force gfp_t)___GFP_ACCOUNT)
/*
* Watermark modifiers -- controls access to emergency reserves
*
* __GFP_HIGH indicates that the caller is high-priority and that granting
* the request is necessary before the system can make forward progress.
* For example, creating an IO context to clean pages.
*
* __GFP_ATOMIC indicates that the caller cannot reclaim or sleep and is
* high priority. Users are typically interrupt handlers. This may be
* used in conjunction with __GFP_HIGH
*
* __GFP_MEMALLOC allows access to all memory. This should only be used when
* the caller guarantees the allocation will allow more memory to be freed
* very shortly e.g. process exiting or swapping. Users either should
* be the MM or co-ordinating closely with the VM (e.g. swap over NFS).
*
* __GFP_NOMEMALLOC is used to explicitly forbid access to emergency reserves.
* This takes precedence over the __GFP_MEMALLOC flag if both are set.
*/
#define __GFP_ATOMIC ((__force gfp_t)___GFP_ATOMIC)
#define __GFP_HIGH ((__force gfp_t)___GFP_HIGH)
#define __GFP_MEMALLOC ((__force gfp_t)___GFP_MEMALLOC)
#define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC)
/*
* Reclaim modifiers
*
* __GFP_IO can start physical IO.
*
* __GFP_FS can call down to the low-level FS. Clearing the flag avoids the
* allocator recursing into the filesystem which might already be holding
* locks.
*
* __GFP_DIRECT_RECLAIM indicates that the caller may enter direct reclaim.
* This flag can be cleared to avoid unnecessary delays when a fallback
* option is available.
*
* __GFP_KSWAPD_RECLAIM indicates that the caller wants to wake kswapd when
* the low watermark is reached and have it reclaim pages until the high
* watermark is reached. A caller may wish to clear this flag when fallback
* options are available and the reclaim is likely to disrupt the system. The
* canonical example is THP allocation where a fallback is cheap but
* reclaim/compaction may cause indirect stalls.
*
* __GFP_RECLAIM is shorthand to allow/forbid both direct and kswapd reclaim.
*
* __GFP_REPEAT: Try hard to allocate the memory, but the allocation attempt
* _might_ fail. This depends upon the particular VM implementation.
*
* __GFP_NOFAIL: The VM implementation _must_ retry infinitely: the caller
* cannot handle allocation failures. New users should be evaluated carefully
* (and the flag should be used only when there is no reasonable failure
* policy) but it is definitely preferable to use the flag rather than
* opencode endless loop around allocator.
*
* __GFP_NORETRY: The VM implementation must not retry indefinitely and will
* return NULL when direct reclaim and memory compaction have failed to allow
* the allocation to succeed. The OOM killer is not called with the current
* implementation.
*/
#define __GFP_IO ((__force gfp_t)___GFP_IO)
#define __GFP_FS ((__force gfp_t)___GFP_FS)
#define __GFP_DIRECT_RECLAIM ((__force gfp_t)___GFP_DIRECT_RECLAIM) /* Caller can reclaim */
#define __GFP_KSWAPD_RECLAIM ((__force gfp_t)___GFP_KSWAPD_RECLAIM) /* kswapd can wake */
#define __GFP_RECLAIM ((__force gfp_t)(___GFP_DIRECT_RECLAIM|___GFP_KSWAPD_RECLAIM))
#define __GFP_REPEAT ((__force gfp_t)___GFP_REPEAT)
#define __GFP_NOFAIL ((__force gfp_t)___GFP_NOFAIL)
#define __GFP_NORETRY ((__force gfp_t)___GFP_NORETRY)
/*
* Action modifiers
*
* __GFP_COLD indicates that the caller does not expect to be used in the near
* future. Where possible, a cache-cold page will be returned.
*
* __GFP_NOWARN suppresses allocation failure reports.
*
* __GFP_COMP address compound page metadata.
*
* __GFP_ZERO returns a zeroed page on success.
*
* __GFP_NOTRACK avoids tracking with kmemcheck.
*
* __GFP_NOTRACK_FALSE_POSITIVE is an alias of __GFP_NOTRACK. It's a means of
* distinguishing in the source between false positives and allocations that
* cannot be supported (e.g. page tables).
*
* __GFP_OTHER_NODE is for allocations that are on a remote node but that
* should not be accounted for as a remote allocation in vmstat. A
* typical user would be khugepaged collapsing a huge page on a remote
* node.
*/
#define __GFP_COLD ((__force gfp_t)___GFP_COLD)
#define __GFP_NOWARN ((__force gfp_t)___GFP_NOWARN)
#define __GFP_COMP ((__force gfp_t)___GFP_COMP)
#define __GFP_ZERO ((__force gfp_t)___GFP_ZERO)
#define __GFP_NOTRACK ((__force gfp_t)___GFP_NOTRACK)
#define __GFP_NOTRACK_FALSE_POSITIVE (__GFP_NOTRACK)
#define __GFP_OTHER_NODE ((__force gfp_t)___GFP_OTHER_NODE)
/* Room for N __GFP_FOO bits */
#define __GFP_BITS_SHIFT 26
#define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))
給出的常數,其中一些很少使用,因此我不會討論。其中最重要的一些常數語義如下所示
其中在開始的位置定義了對應的區修飾符, 定義在include/linux/gfp.h?v=4.7, line 46 ~ line 57
區修飾符標誌 | 描述 |
---|---|
__GFP_DMA | 從ZONE_DMA中分配記憶體 |
__GFP_HIGHMEM | 從ZONE_HIGHMEM或ZONE_NORMAL中分配記憶體 |
__GFP_DMA32 | 從ZONE_DMA32中分配記憶體 |
__GFP_MOVABLE | 從__GFP_MOVABLE中分配記憶體 |
其次還定義了我們程式和函數中所需要的掩碼MASK的信息, 由於其中__GFP_DMA, __GFP_DMA32, __GFP_HIGHMEM, __GFP_MOVABLE是在記憶體中分別有對應的記憶體域信息, 因此我們定義了記憶體域的掩碼GFP_ZONEMASK, 參見include/linux/gfp.h?v=4.7, line 57
#define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)
接著內核定義了行為修飾符
/* __GFP_WAIT表示分配記憶體的請求可以中斷。也就是說,調度器在該請求期間可隨意選擇另一個過程執行,或者該請求可以被另一個更重要的事件中斷. 分配器還可以在返回記憶體之前, 在隊列上等待一個事件(相關進程會進入睡眠狀態).
雖然名字相似,但__GFP_HIGH與__GFP_HIGHMEM毫無關係,請不要弄混這兩者
行為修飾符 | 描述 |
---|---|
__GFP_RECLAIMABLE __GFP_MOVABLE |
是頁遷移機制所需的標誌. 顧名思義,它們分別將分配的記憶體標記為可回收的或可移動的。這影響從空閑列表的哪個子表獲取記憶體 |
__GFP_WRITE | |
__GFP_HARDWALL | 只在NUMA系統上有意義. 它限制只在分配到當前進程的各個CPU所關聯的結點分配記憶體。如果進程允許在所有CPU上運行(預設情況),該標誌是無意義的。只有進程可以運行的CPU受限時,該標誌才有效果 |
__GFP_THISNODE | 也只在NUMA系統上有意義。如果設置該比特位,則記憶體分配失敗的情況下不允許使用其他結點作為備用,需要保證在當前結點或者明確指定的結點上成功分配記憶體 |
__GFP_ACCOUNT | |
__GFP_ATOMIC | |
__GFP_HIGH | 如果請求非常重要, 則設置__GFP_HIGH,即內核急切地需要記憶體時。在分配記憶體失敗可能給內核帶來嚴重後果時(比如威脅到系統穩定性或系統崩潰), 總是會使用該標誌 |
__GFP_MEMALLOC | |
__GFP_NOMEMALLOC | |
__GFP_IO | 說明在查找空閑記憶體期間內核可以進行I/O操作. 實際上, 這意味著如果內核在記憶體分配期間換出頁, 那麼僅當設置該標誌時, 才能將選擇的頁寫入硬碟 |
__GFP_FS | 允許內核執行VFS操作. 在與VFS層有聯繫的內核子系統中必須禁用, 因為這可能引起迴圈遞歸調用. |
__GFP_DIRECT_RECLAIM | |
__GFP_KSWAPD_RECLAIM | |
__GFP_RECLAIM | |
__GFP_REPEAT | 在分配失敗後自動重試,但在嘗試若幹次之後會停止 |
__GFP_NOFAIL | 在分配失敗後一直重試,直至成功 |
__GFP_NORETRY | 在分配失敗後不重試,因此可能分配失敗 |
__GFP_COLD | 如果需要分配不在CPU高速緩存中的“冷”頁時,則設置__GFP_COLD |
__GFP_NOWARN | 在分配失敗時禁止內核故障警告。在極少數場合該標誌有用 |
__GFP_COMP | 添加混合頁元素, 在hugetlb的代碼內部使用 |
__GFP_ZERO | 在分配成功時,將返回填充位元組0的頁 |
__GFP_NOTRACK | |
__GFP_NOTRACK_FALSE_POSITIVE __GFP_NOTRACK |
|
__GFP_OTHER_NODE |
那自然還有__GFP_BITS_SHIFT來表示我們所有的掩碼位, 由於我們共計26個掩碼位
/* Room for N __GFP_FOO bits */
#define __GFP_BITS_SHIFT 26
#define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))
可以同時指定這些分配標誌, 例如
ptr = kmalloc(size, __GFP_IO | __GFP_FS);
說明頁分配器(最終會調用alloc_page)在分配時可以執行I/O, 在必要時還可以執行文件系統操作. 這就讓內核有很大的自由度, 以便它儘可能找到空閑的記憶體來滿足分配請求. 大多數分配器都會執行這些修飾符, 但一般不是這樣直接指定, 而是將這些行為描述符標誌進行分組, 即類型標誌
3.3.4 掩碼分組
最後來看第三部分, 由於這些標誌幾乎總是組合使用,內核作了一些分組,包含了用於各種標準情形的適當的標誌. 稱之為類型標誌, 定義在include/linux/gfp.h?v=4.7, lien 194 ~ line 258
類型標誌指定所需的行為和區描述符以安城特殊類型的處理, 正因為這一點, 內核總是趨於使用正確的類型標誌, 而不是一味地指定它可能用到的多種描述符. 這麼做既簡單又不容易出錯誤.
如果有可能的話, 在記憶體管理子系統之外, 總是把下列分組之一用於記憶體分配. 在內核源代碼中, 雙下劃線通常用於內部數據和定義. 而這些預定義的分組名沒有雙下劃線首碼, 點從側面驗證了上述說法.
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO (__GFP_RECLAIM)
#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO)
#define GFP_TEMPORARY (__GFP_RECLAIM | __GFP_IO | __GFP_FS | \
__GFP_RECLAIMABLE)
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE)
#define GFP_TRANSHUGE ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
__GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN) & \
~__GFP_RECLAIM)
/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
#define GFP_MOVABLE_SHIFT 3
掩碼組 | 描述 |
---|---|
GFP_ATOMIC | 用於原子分配,在任何情況下都不能中斷, 可能使用緊急分配鏈表中的記憶體, 這個標誌用在中斷處理程式, 下半部, 持有自旋鎖以及其他不能睡眠的地方 |
GFP_KERNEL | 這是一種常規的分配方式, 可能會阻塞. 這個標誌在睡眠安全時用在進程的長下文代碼中. 為了獲取調用者所需的記憶體, 內核會儘力而為. 這個標誌應該是首選標誌 |
GFP_KERNEL_ACCOUNT | |
GFP_NOWAIT | 與GFP_ATOMIC類似, 不同之處在於, 調用不會退給緊急記憶體池, 這就增加了記憶體分配失敗的可能性 |
GFP_NOIO | 這種分配可以阻塞, 但不會啟動磁碟I/O, 這個標誌在不能引發更多的磁碟I/O時阻塞I/O代碼, 這可能導致令人不愉快的遞歸 |
GFP_NOFS | 這種分配在必要時可以阻塞, 但是也可能啟動磁碟, 但是不會啟動文件系統操作, 這個標誌在你不鞥在啟動另一個文件系統操作時, 用在文件系統部分的代碼中 |
GFP_TEMPORARY | |
GFP_USER | 這是一種常規的分配方式, 可能會阻塞. 這個標誌用於為用戶空間進程分配記憶體時使用 |
GFP_DMA GFP_DMA32 |
用於分配適用於DMA的記憶體, 當前是__GFP_DMA的同義詞, GFP_DMA32也是__GFP_GMA32的同義詞 |
GFP_HIGHUSER | 是GFP_USER的一個擴展, 也用於用戶空間. 它允許分配無法直接映射的高端記憶體. 使用高端記憶體頁是沒有壞處的,因為用戶過程的地址空間總是通過非線性頁表組織的 |
GFP_HIGHUSER_MOVABLE | 用途類似於GFP_HIGHUSER,但分配將從虛擬記憶體域ZONE_MOVABLE進行 |
GFP_TRANSHUGE |
- 其中GFP_NOIO和GFP_NOFS, 分別明確禁止I/O操作和訪問VFS層, 但同時設置了__GFP_RECLAIM,因此可以被回收
- 而GFP_KERNEL和GFP_USER. 分別是內核和用戶分配的預設設置。二者的失敗不會立即威脅系統穩定性, GFP_KERNEL絕對是內核源代碼中最常使用的標誌
最後內核設置了碎片管理的可移動依據組織頁的MASK信息GFP_MOVABLE_MASK, 參見include/linux/gfp.h?v=4.7, line 262
在你編寫的絕大多數代碼中, 用麽用到的是GFP_KERNEL, 要麼是GFP_ATOMIC, 當然各個類型標誌也均有其應用場景
情形 | 相應標誌 |
---|---|
進程上下文, 可以睡眠 | 使用GFP_KERNEL |
進程上下文, 不可以睡眠 | 使用GFP_KERNEL, 在你睡眠之前或之後以GFP_KERNEL執行記憶體分配 |
中斷處理程式 | 使用GFP_ATMOIC |
軟中斷 | 使用GFP_ATMOIC |
tasklet | 使用GFP_ATMOIC |
需要用於DMA的記憶體, 可以睡眠 | 使用(GFP_DMA GFP_KERNEL) |
需要用於DMA的記憶體, 不可以睡眠 | 使用(GFP_DMA GFP_ATOMIC), 或在你睡眠之前執行記憶體分配 |
3.3.5 掩碼總結
我們從註釋中找到這樣的信息, 可以作為參考
bit result
=================
0x0 => NORMAL
0x1 => DMA or NORMAL
0x2 => HIGHMEM or NORMAL
0x3 => BAD (DMA+HIGHMEM)
0x4 => DMA32 or DMA or NORMAL
0x5 => BAD (DMA+DMA32)
0x6 => BAD (HIGHMEM+DMA32)
0x7 => BAD (HIGHMEM+DMA32+DMA)
0x8 => NORMAL (MOVABLE+0)
0x9 => DMA or NORMAL (MOVABLE+DMA)
0xa => MOVABLE (Movable is valid only if HIGHMEM is set too)
0xb => BAD (MOVABLE+HIGHMEM+DMA)
0xc => DMA32 (MOVABLE+DMA32)
0xd => BAD (MOVABLE+DMA32+DMA)
0xe => BAD (MOVABLE+DMA32+HIGHMEM)
0xf => BAD (MOVABLE+DMA32+HIGHMEM+DMA)
GFP_ZONES_SHIFT must be <= 2 on 32 bit platforms.
3.4 掩碼函數介面
很有趣的一點是,沒有__GFP_NORMAL常數,而記憶體分配的主要負擔卻落到ZONE_NORMAL記憶體域
內核考慮到這一點, 提供了一個函數gfp_zone來計算與給定分配標誌相容的最高記憶體域. 那麼記憶體分配可以從該記憶體域或更低的記憶體域進行, 該函數定義在include/linux/gfp.h?v=4.7, line 394
static inline enum zone_type gfp_zone(gfp_t flags)
{
enum zone_type z;
int bit = (__force int) (flags & GFP_ZONEMASK);
z = (GFP_ZONE_TABLE >> (bit * GFP_ZONES_SHIFT)) &
((1 << GFP_ZONES_SHIFT) - 1);
VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1);
return z;
}
其中GFP_ZONES_SHIFT的定義如下, 在include/linux/gfp.h?v=4.7, line 337
#if defined(CONFIG_ZONE_DEVICE) && (MAX_NR_ZONES-1) <= 4
/* ZONE_DEVICE is not a valid GFP zone specifier */
#define GFP_ZONES_SHIFT 2
#else
#define GFP_ZONES_SHIFT ZONES_SHIFT
#endif
#if 16 * GFP_ZONES_SHIFT > BITS_PER_LONG
#error GFP_ZONES_SHIFT too large to create GFP_ZONE_TABLE integer
#endif
由於記憶體域修飾符的解釋方式不是那麼直觀, 表3-7給出了該函數結果的一個例子, 其中DMA和DMA32記憶體域相同. 假定在下文中沒有設置__GFP_MOVABLE修飾符.
修飾符 | 掃描的記憶體域 |
---|---|
無 | ZONE_NORMAL、ZONE_DMA |
__GFP_DMA | ZONE_DMA |
__GFP_DMA & __GFP_HIGHMEM | ZONE_DMA |
__GFP_HIGHMEM | ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA |
如果__GFP_DMA和__GFP_HIGHMEM都沒有設置, 則首先掃描ZONE_NORMAL, 後面是ZONE_DMA
- 如果設置了__GFP_HIGHMEM沒有設置__GFP_DMA,則結果是從ZONE_HIGHMEM開始掃描所有3個記憶體域。
- 如果設置了__GFP_DMA,那麼__GFP_HIGHMEM設置與否沒有關係. 只有ZONE_DMA用於3種情形. 這是合理的, 因為同時使用__GFP_HIGHMEM和__GFP_DMA沒有意義. 高端記憶體從來都不適用於DMA
設置__GFP_MOVABLE不會影響內核的決策,除非它與__GFP_HIGHMEM同時指定. 在這種情況下, 會使用特殊的虛擬記憶體域ZONE_MOVABLE滿足記憶體分配請求. 對前文描述的內核的反碎片策略而言, 這種行為是必要的.
除了記憶體域修飾符之外, 掩碼中還可以設置一些標誌.
下圖中給出了掩碼的佈局,以及與各個比特位置關聯的常數. __GFP_DMA32出現了幾次,因為它可能位於不同的地方.
與記憶體域修飾符相反, 這些額外的標誌並不限制從哪個物理記憶體段分配記憶體, 但確實可以改變分配器的行為. 例如, 它們可以修改查找空閑記憶體時的積極程度.