kmalloc分配物理記憶體與高端記憶體映射--Linux記憶體管理(十八)

来源:https://www.cnblogs.com/linhaostudy/archive/2018/12/27/10183797.html
-Advertisement-
Play Games

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個用途。

  1. 虛擬記憶體中連續、但物理記憶體中不連續的記憶體區,可以在vmalloc區域分配. 該機制通常用於用戶過程, 內核自身會試圖儘力避免非連續的物理地址。內核通常會成功,因為大部分大的記憶體塊都在啟動時分配給內核,那時記憶體的碎片尚不嚴重。但在已經運行了很長時間的系統上, 在內核需要物理記憶體時, 就可能出現可用空間不連續的情況. 此類情況, 主要出現在動態載入模塊時.
  2. 持久映射用於將高端記憶體域中的非持久頁映射到內核中
  3. 固定映射是與物理地址空間中的固定頁關聯的虛擬地址空間項,但具體關聯的頁幀可以自由選擇. 它與通過固定公式與物理記憶體關聯的直接映射頁相反,虛擬固定映射地址與物理記憶體位置之間的關聯可以自行定義,關聯建立後內核總是會註意到的.

在這裡有兩個預處理器符號很重要 __VMALLOC_RESERVE設置了vmalloc區域的長度, 而MAXMEM則表示內核可以直接定址的物理記憶體的最大可能數量.

  1. 直接映射區

線性空間中從3G開始最大896M的區間, 為直接記憶體映射區,該區域的線性地址和物理地址存線上性轉換關係:線性地址=3G+物理地址。

  1. 動態記憶體映射區

該區域由內核函數vmalloc來分配, 特點是 : 線性空間連續, 但是對應的物理空間不一定連續. vmalloc分配的線性地址所對應的物理頁可能處於低端記憶體, 也可能處於高端記憶體.

  1. 永久記憶體映射區

該區域可訪問高端記憶體. 訪問方法是使用alloc_page(_GFP_HIGHMEM)分配高端記憶體頁或者使用kmap函數將分配到的高端記憶體映射到該區域.

  1. 固定映射區

該區域和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聲明與定義

kmalloc定義 kzalloc定義 kfree定義
tools/virtio/linux/kernel.h?v=4.7, line 46 tools/virtio/linux/kernel.h?v=4.7, line 52 tools/virtio/linux/kernel.h?v=4.7, line 60
include/linux/slab.h, line 466 include/linux/slab.h?v=4.7, line 620 mm/slob.c?v=4.7, line 484
mm/slub.c?v=4.7, line 3645
mm/slab.c?v=4.7, line 3853

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出現了幾次,因為它可能位於不同的地方.

與記憶體域修飾符相反, 這些額外的標誌並不限制從哪個物理記憶體段分配記憶體, 但確實可以改變分配器的行為. 例如, 它們可以修改查找空閑記憶體時的積極程度.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Apache伺服器可以配置多個虛擬主機,從而為多個站點提供服務,其實質就是根據不同的策略,訪問不同的文件系統目錄。apache提供3種虛擬主機的策略,1.基於埠的虛擬主機;2.基於功能變數名稱的虛擬主機;3.基於IP的虛擬主機。 ...
  • 文件系統: 以UNIX系統V文件系統為例: 磁碟分為區,每個分區都有自己的文件系統: ​ i節點是固定長度的記錄項,包含了文件的相關信息。目錄項包含文件名和i節點號。stat結構中除文件名和i節點編號之外的數據都包含在i節點中 在進行文件索引的時候會首先找到目錄項,根據目錄項的i節點號找到i節點,i ...
  • linux預設大部分埠的是關閉的。而我們在開發、部署環境時,需要用到大量的服務,如mysql、tomcat、redis、zk等,需要開放指定的埠號。 以mysql埠3306為例 首先編輯伺服器的埠開放配置 在編輯器加入以下代碼,其中3306可以根據需要開放的埠進行替換 添加後的效果如下: ...
  • 最近搭建測試伺服器,訪問網站查看報錯日誌出現如下錯誤: Fatal error: Call to undefined function json_decode() 出現該問題原因是安裝PHP時沒有安裝json擴展所致。 1、首先進入php安裝包的ext中的json目錄,如下圖所示: 2、執行phpi ...
  • 為什麼要有進程優先順序?這似乎不用過多的解釋,畢竟自從多任務操作系統誕生以來,進程執行占用cpu的能力就是一個必須要可以人為控制的事情。因為有的進程相對重要,而有的進程則沒那麼重要。 本文作者:鄒立巍 Linux系統技術專家。目前在騰訊SNG社交網路運營部 計算資源平臺組,負責內部私有雲平臺的建設和架 ...
  • 最初的2小時,你會愛上Docker,對原理和使用流程有個最基本的理解,避免滿世界無頭蒼蠅式找資料。本人反對暴風驟雨式多管齊下狂轟濫炸的學習方式,提倡迭代學習法,就是先知道怎麼玩,有個感性認識,再深入學習高級用法,深層原理,一輪輪迭代。堅決反對一上來就搞幾百頁厚的東西把人腦子弄亂。 Docker是什麼 ...
  • 一.Logrotate工具介紹 Logrotate是一個日誌文件管理工具,它是Linux預設自帶的一個日誌切割工具。用來把舊文件輪轉、壓縮、刪除,並且創建新的日誌文件。我們可以根據日誌文件的大小、天數等來轉儲,便於對日誌文件管理,一般都是通過cron計劃任務來完成的,讓日誌切割實現按小時分割,按天分 ...
  • 該按鍵庫使用 C 語言編寫,驅動與應用程式解耦,便於靈活應用,比如用戶可以方便地在應用層增加按鍵中斷、處理按鍵功耗、定義按鍵事件處理方式,而無需修改 FlexibleButton 庫中的代碼。另外,使用 C 語言標準庫 API 編寫,也使得該按鍵庫可以無縫相容任意的處理器平臺,並且支持任意 OS 和... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...