Linux内存管理15(基于6.1内核)

Linux内存管理15(基于6.1内核)---kmalloc分配物理内存与高端内存映射

一、概述


在 Linux 6.1 内核中,kmalloc 是一个用于分配内核空间内存的函数。它用于从内核的内存池中分配一块指定大小的内存块。要理解 kmalloc 如何分配物理内存和高端内存,我们需要了解几个概念,包括内存区域、内存分配器和 kmalloc 的工作原理。

1. 内存区域的划分

在 Linux 内核中,物理内存的管理分为多个区域,主要包括以下几类:

  • 低端内存(Low Memory):通常指的是低于某个特定地址的物理内存,这部分内存是可以直接映射到虚拟地址空间的,通常是指 0-896MB 的物理内存(根据具体架构而定)。

  • 高端内存(High Memory):通常是指物理地址大于低端内存的部分。例如,在 x86 架构上,高端内存通常指的是大于 896MB 的物理内存。这些内存不能直接映射到内核的虚拟地址空间,需要通过特定的映射机制才能访问。

  • 直接内存(Direct Memory):这是指低端内存区域,它可以直接映射到内核的虚拟地址空间。

  • 高端内存映射(High Memory Mapping):对于高端内存,不能直接映射到内核的虚拟地址空间,内核需要借助页表的映射或者使用高端内存映射机制(如 kmapkmap_atomic)来访问这部分内存。

2. kmalloc 和内存分配

kmalloc 是 Linux 内核中用来分配内存的一个函数,它在内存池中寻找合适大小的内存块。如果分配请求的大小较小,kmalloc 会优先从低端内存中分配,因为低端内存是直接映射的,效率较高。

低端内存分配:

  • 对于请求的内存大小在低端内存允许的范围内,kmalloc 会直接从低端内存分配,并返回一个虚拟地址。
  • 内核通过 kmalloc() 分配的内存块,通常是通过 __get_free_pages() 来申请的,这种方式是直接映射到内核虚拟地址空间的。

高端内存分配:

  • 当分配请求的内存超出了低端内存的范围,kmalloc 会尝试从高端内存中分配内存。由于高端内存不能直接映射到内核虚拟地址空间,因此需要通过 kmap()kmap_atomic() 等方法将其映射到内核虚拟地址空间。

    • kmap():用于将高端内存的一页映射到内核的虚拟地址空间。这是一个慢速的过程,因为每次需要进行映射。
    • kmap_atomic():用于将高端内存的一页映射到内核的虚拟地址空间,但它只会持续非常短的时间(通常是为了避免内核中断)。这种方法比 kmap() 更高效,但也更具限制性。

3. 高端内存与低端内存的区别

  • 低端内存:能够直接映射到内核的虚拟地址空间,因此可以直接访问,效率较高。

  • 高端内存:不能直接映射,需要通过临时映射来访问。由于高端内存的管理更复杂,访问这些内存的性能会受到一些影响,尤其是在高并发的情况下。

4. 内存分配器和 kmalloc

kmalloc 使用的是内核的 SLAB 内存分配器,后者分为几个区域,其中包括低端内存和高端内存的管理。内核通过 kmalloc 来分配内存块,具体分配策略会根据请求的内存大小、内存池的可用性以及当前系统的内存状态来决定。

  • 对于小于某个阈值的内存请求,kmalloc 会尽量使用低端内存。
  • 对于较大的内存请求,可能会需要从高端内存中分配,甚至可能导致分配失败(如果系统内存不足)。

5. 内存映射与分页

内核在进行内存分配时,尤其是涉及高端内存时,会借助分页机制。内存分配并不意味着直接返回一个物理地址,而是返回一个虚拟地址,这个虚拟地址通过页表映射到实际的物理内存。

对于高端内存,由于其物理内存地址较高,不会直接映射到内核虚拟地址空间,需要进行额外的映射处理:

  • 使用 kmapkmap_atomic 将高端内存的物理页面临时映射到内核虚拟地址空间。
  • 内核通过页表对物理地址和虚拟地址进行映射,保证在访问时可以正确映射到物理内存。

二、kmallc & kfree分配释放连续的物理内存


kmalloc和kzalloc

kmalloc函数与用户空间的malloc一族函数非常类似, 只不过它多了一个flags参数, kmalloc函数是一个简单的接口, 用它可以获取以字节为单位的一块内核内存。

如果你需要整个页, 那么前面讨论的页分配接口是更好的选择。 但是, 对大多数内核分配来说, kmalloc接口用的更多, 同时内核也提供了kzalloc该接口在kmalloc的基础上会将分配的内存清0。

这两个函数返回一个指向内存块的指针, 其内存块至少要有size大小. 所分配的内存区在物理上是连续的. 在出错时, 它返回NULL. 除非没有足够的内存可用, 否则内核总能分配成功. 在对kmalloc调用之后, 你必须检查返回的是不是NULL, 如果是, 要适当处理错误。

kfree释放内存

kmalloc的另一端就是kfree, 用于释放分配的内存。

kmalloc定义

kzalloc定义

kfree定义

tools/virtio/linux/kernel.h

tools/virtio/linux/kernel.h

tools/virtio/linux/kernel.h

include/linux/slab.h

include/linux/slab.h

mm/slob.c

mm/slub.c

mm/slab.c

三、分配掩码(gfp_mask标志)


3.1 分配掩码


前述所有函数中强制使用的mask参数,到底是什么语义?

我们知道Linux将内存划分为内存域. 内核提供了所谓的内存域修饰符(zone modifier)(在掩码的最低4个比特位定义), 来指定从哪个内存域分配所需的页。

内核使用宏的方式定义了这些掩码, 一个掩码的定义被划分为3个部分进行定义, include/linux/gfp_types.h, 因此后面#define __GFP_BITS_SHIFT (28)

3.2 掩码分类


Linux中这些掩码标志gfp_mask分为3种类型 :

类型描述
区描述都符内核把物理内存分为多个区, 每个区用于不同的目的, 区描述符指明到底从这些区中的哪一区进行分配
行为修饰符表示内核应该如何分配所需的内存. 在某些特定情况下, 只能使用某些特定的方法分配内存
类型标志组合了行为修饰符和区描述符, 将这些可能用到的组合归纳为不同类型

3.3 内核中掩码的定义


1 内核中的定义方式


 include/linux/gfp_types.h

/* 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
#define ___GFP_RECLAIMABLE	0x10u
#define ___GFP_HIGH		0x20u
#define ___GFP_IO		0x40u
#define ___GFP_FS		0x80u
#define ___GFP_ZERO		0x100u

/*
 * 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)

#define GFP_DMA		__GFP_DMA
#define GFP_DMA32	__GFP_DMA32

其中GFP缩写的意思为获取空闲页(get free page), __GFP_MOVABLE不表示物理内存域, 但通知内核应在特殊的虚拟内存域ZONE_MOVABLE进行相应的分配.

2 定义掩码位


我们首先来看第一部分, 内核源代码中定义include/linux/gfp_types.h掩码信息.

/* 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
#define ___GFP_RECLAIMABLE	0x10u
#define ___GFP_HIGH		0x20u
#define ___GFP_IO		0x40u
#define ___GFP_FS		0x80u
#define ___GFP_ZERO		0x100u
/* 0x200u unused */
#define ___GFP_DIRECT_RECLAIM	0x400u
#define ___GFP_KSWAPD_RECLAIM	0x800u
#define ___GFP_WRITE		0x1000u
#define ___GFP_NOWARN		0x2000u
#define ___GFP_RETRY_MAYFAIL	0x4000u
#define ___GFP_NOFAIL		0x8000u
#define ___GFP_NORETRY		0x10000u
#define ___GFP_MEMALLOC		0x20000u
#define ___GFP_COMP		0x40000u
#define ___GFP_NOMEMALLOC	0x80000u
#define ___GFP_HARDWALL		0x100000u
#define ___GFP_THISNODE		0x200000u
#define ___GFP_ACCOUNT		0x400000u
#define ___GFP_ZEROTAGS		0x800000u
#ifdef CONFIG_KASAN_HW_TAGS
#define ___GFP_SKIP_ZERO	0x1000000u
#define ___GFP_SKIP_KASAN	0x2000000u
#else
#define ___GFP_SKIP_ZERO	0
#define ___GFP_SKIP_KASAN	0
#endif
#ifdef CONFIG_LOCKDEP
#define ___GFP_NOLOCKDEP	0x4000000u
#else
#define ___GFP_NOLOCKDEP	0
#endif
#ifdef CONFIG_KFENCE
#define ___GFP_NOKFENCE		0x8000000u
#else
#define ___GFP_NOKFENCE		0
#endif
/* If the above are modified, __GFP_BITS_SHIFT may need updating */

3 定义掩码


然后第二部分, 相对而言每一个宏又被重新定义如下include/linux/gfp_types.h

/*
* 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_types.h

区修饰符标志描述
__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_types.h

#define GFP_ZONEMASK    (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)

接着内核定义了行为修饰符

/* __GFP_WAIT表示分配内存的请求可以中断。也就是说,调度器在该请求期间可随意选择另一个过程执行,或者该请求可以被另一个更重要的事件中断. 分配器还可以在返回内存之前, 在队列上等待一个事件(相关进程会进入睡眠状态)。

行为修饰符描述
__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来表示我们所有的掩码位, 由于我们共计28个掩码位

/* Room for N __GFP_FOO bits */
#define __GFP_BITS_SHIFT (28)
#define __GFP_BITS_MASK ((__force gfp_t)((1 << __GFP_BITS_SHIFT) - 1))

可以同时指定这些分配标志, 例如:

ptr = kmalloc(size, __GFP_IO | __GFP_FS);

说明页分配器(最终会调用alloc_page)在分配时可以执行I/O, 在必要时还可以执行文件系统操作. 这就让内核有很大的自由度, 以便它尽可能找到空闲的内存来满足分配请求. 大多数分配器都会执行这些修饰符, 但一般不是这样直接指定, 而是将这些行为描述符标志进行分组, 即类型标志

4 掩码分组


最后来看第三部分, 由于这些标志几乎总是组合使用,内核作了一些分组,包含了用于各种标准情形的适当的标志. 称之为类型标志, 定义include/linux/gfp_types.h

类型标志指定所需的行为和区描述符以安城特殊类型的处理, 正因为这一点, 内核总是趋于使用正确的类型标志, 而不是一味地指定它可能用到的多种描述符。这么做既简单又不容易出错误。

如果有可能的话, 在内存管理子系统之外, 总是把下列分组之一用于内存分配。在内核源代码中, 双下划线通常用于内部数据和定义。而这些预定义的分组名没有双下划线前缀, 点从侧面验证了上述说法。

#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

/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
#define GFP_MOVABLE_SHIFT 3

编写的绝大多数代码中, 用么用到的是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), 或在你睡眠之前执行内存分配

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

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

#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_DMAZONE_DMA
__GFP_DMA & __GFP_HIGHMEMZONE_DMA
__GFP_HIGHMEMZONE_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出现了几次,因为它可能位于不同的地方。

与内存域修饰符相反, 这些额外的标志并不限制从哪个物理内存段分配内存, 但确实可以改变分配器的行为. 例如, 它们可以修改查找空闲内存时的积极程度。

四、kmalloc总结:kmalloc 与物理内存和高端内存

  • kmalloc 会首先尝试在低端内存中分配内存,低端内存直接映射到虚拟地址空间。
  • 如果内存分配请求超过了低端内存的范围,kmalloc 会尝试从高端内存中分配。
  • 高端内存不能直接映射,需要通过额外的映射操作(kmapkmap_atomic)来访问。
  • 内核通过 SLAB 分配器对低端和高端内存进行管理,优化内存的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值