gfp&内存分配学习

zone type:Zone从高到底是:ZONE_MOVABLE,ZONE_HIGHMEM,ZONE_NORMAL,ZONE_DMA32,ZONE_DMA
Normal zone和Movable是自带
其他需要看是否定义了宏,宏都在out/target/product/*/obj/KERNEL_OBJ/.config里看到
伙伴系统:在每个管理区,页框被伙伴系统的部分来处理。Linux的伙伴系统解决外碎片问题,把所有的空闲页框分组为11个块链表,每个链表块分别包含大小为1,2,4,8…1024个连续的页框
在这里插入图片描述

gfp_mask是一组标志,是内存分配中的分配掩码,分配掩码分为两部分,内存域修饰符和内存分配标志
在这里插入图片描述
内存域修饰符有4种,没有zone_normal对应的修饰符,因为zone_normal是默认的内存申请类型。

#define ___GFP_DMA  0x01u
#define ___GFP_HIGHMEM  0x02u
#define ___GFP_DMA32  0x04u
#define ___GFP_MOVABLE  0x08u

举例 __GFP_DMA是16进制的1,换成2进制是0001,以此类推,分别是0010,0100,1000。对应的位置1表示可以指定相应的zone。
这4位共有16种组合,16种有可以使用的组合,有不可以使用的组合。可以使用的组合放在GFP_ZONE_TABLE里,不可食用的放在GFP_ZONE_BAD里。

16种组合对应的zone如下:低三位只能有一位置1,否则不可行。MOVABLE置1时,只有Highmem也同时置1才会指定ZONE_MOVABLE,否则指定下一个置1的ZONE。ZONE的分配顺序如下:MOVABLE,HIGHMEM,NORMAL,DMA32,DMA
举例:

  • 0x0:0000表示默认从NORMAL
  • 0x1:0001,DMA位置1,先指定DMA,如果DMA分配不到再从NORMAL分配
  • 0x2:0010,HIGHMEM置1,顺序为HIGHMEM,NORMAL
  • 0x4:0100,DMA32置1,先指定DMA32,如果DMA32分配不到DMA,NORMAL
  • 0x8:1000,MOVABLE置1,但是HIGHMEM没置1,从NORMAL分配
  • 0x9:1001,MOVABLE和DMA置1,同上不指定MOVABLE,先指定DMA,DMA分配不到再从NORMAL分配
  • 0xa:1010,MOVABLE和HIGHMEM置1,指定MOVABLE
  • 0xc:1100,MOVABLE和DMA32置1,指定DMA32
 * GFP_ZONE_TABLE is a word size bitstring that is used for looking up the
 * zone to use given the lowest 4 bits of gfp_t. Entries are ZONE_SHIFT long
 * and there are 16 of them to cover all possible combinations of
 * __GFP_DMA, __GFP_DMA32, __GFP_MOVABLE and __GFP_HIGHMEM.
 *
 * The zone fallback order is MOVABLE=>HIGHMEM=>NORMAL=>DMA32=>DMA.
 * But GFP_MOVABLE is not only a zone specifier but also an allocation
 * policy. Therefore __GFP_MOVABLE plus another zone selector is valid.
 * Only 1 bit of the lowest 3 bits (DMA,DMA32,HIGHMEM) can be set to "1".
 *
 *       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.

GFP_ZONE_TABLE存放可以使用的组合
GFP_ZONE_TABLE代码如下

#define GFP_ZONE_TABLE ( \
 (ZONE_NORMAL << 0 * GFP_ZONES_SHIFT)           \
 | (OPT_ZONE_DMA << ___GFP_DMA * GFP_ZONES_SHIFT)         \
 | (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * GFP_ZONES_SHIFT)        \
 | (OPT_ZONE_DMA32 << ___GFP_DMA32 * GFP_ZONES_SHIFT)         \
 | (ZONE_NORMAL << ___GFP_MOVABLE * GFP_ZONES_SHIFT)         \
 | (OPT_ZONE_DMA << (___GFP_MOVABLE | ___GFP_DMA) * GFP_ZONES_SHIFT)    \
 | (ZONE_MOVABLE << (___GFP_MOVABLE | ___GFP_HIGHMEM) * GFP_ZONES_SHIFT)\
 | (OPT_ZONE_DMA32 << (___GFP_MOVABLE | ___GFP_DMA32) * GFP_ZONES_SHIFT)\
)

解释:
所有zone的个数小于等于2的ZONE_SHIFT次方,说明至少需要ZONE_SHIFT个bit位来表示zone
例如有3个zone,则ZONE_SHIFT的值为2,对应zone的值是0,1,2。二进制就是00,01,02。
GFP_ZONE_TABLE对于每个可以使用的组合都给ZONE_SHIFT个bit位来存放对应的zone的值。
例如

  • 组合0x0指定ZONE_NORMAL,(ZONE_NORMAL << 0 * GFP_ZONES_SHIFT)就将ZONE_NORMAL的值写入[0,ZONE_SHIFT]
  • 组合0x1指定ZONE_DMA,(OPT_ZONE_DMA << ___GFP_DMA * GFP_ZONES_SHIFT),如果有ZONE_DMA,就将ZONE_DMA的值写入[ZONE_SHIFT,2*ZONE_SHIFT],否则写入ZONE_NORMAL
  • 组合0x2指定ZONE_HIGHMEM,(OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * GFP_ZONES_SHIFT),同上在[2ZONE_SHIFT,3ZONE_SHIFT]写入ZONE_HIGHMEM或者ZONE_NORMAL
  • 以此类推

OPT_ZONE_***是对应的可选择的ZONE的值,因为zone_type中只有ZONE_NORMAL和ZONE_MOVABLE是一定被定义的,ZONE_DMA,ZONE_DMA32,ZONE_HIGHMEM是不一定被定义的。

举例:当gfp_mask后4位为0x2时,指定ZONE的顺序是ZONE_HIGHMEM,ZONE_NORMAL。即如果ZONE_HIGHMEM被定义了就是从ZONE_NORMAL上分配,否则就从ZONE_NORMAL上分配。

对应的ZONE_SHIFT如下:MAX_NR_ZONE是ZONE的个数,ZONE_SHIFT对应ZONE的个数

#if MAX_NR_ZONES < 2
#define ZONES_SHIFT 0
#elif MAX_NR_ZONES <= 2
#define ZONES_SHIFT 1
#elif MAX_NR_ZONES <= 4
#define ZONES_SHIFT 2
#elif MAX_NR_ZONES <= 8
#define ZONES_SHIFT 3
#else
#error ZONES_SHIFT -- too many zones configured adjust calculation
#endif

对应的OPT_ZONE_***如下:可见如果对应的ZONE被定义,则OPT_ZONE_***就是对应的ZONE的值,否则都为ZONE_NORMAL的值

#ifdef CONFIG_HIGHMEM
#define OPT_ZONE_HIGHMEM ZONE_HIGHMEM
#else
#define OPT_ZONE_HIGHMEM ZONE_NORMAL
#endif

#ifdef CONFIG_ZONE_DMA
#define OPT_ZONE_DMA ZONE_DMA
#else
#define OPT_ZONE_DMA ZONE_NORMAL
#endif

#ifdef CONFIG_ZONE_DMA32
#define OPT_ZONE_DMA32 ZONE_DMA32
#else
#define OPT_ZONE_DMA32 ZONE_NORMAL
#endif

GFP_ZONE_BAD记录所有不合理的组合,将所有不合理的组合对应的位置1即可

#define GFP_ZONE_BAD ( \
 1 << (___GFP_DMA | ___GFP_HIGHMEM)          \
 | 1 << (___GFP_DMA | ___GFP_DMA32)          \
 | 1 << (___GFP_DMA32 | ___GFP_HIGHMEM)          \
 | 1 << (___GFP_DMA | ___GFP_DMA32 | ___GFP_HIGHMEM)        \
 | 1 << (___GFP_MOVABLE | ___GFP_HIGHMEM | ___GFP_DMA)        \
 | 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA)        \
 | 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_HIGHMEM)        \
 | 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA | ___GFP_HIGHMEM)  \
)

举例:

  • 0x3是个不合理的组合,1<<(__GFP_DMA|GFP_HIGHMEM),就将第3位置1
  • 以此类推

通过gfp_mask来获得对应的zone,由gfp_zone()函数实现:
在这里插入图片描述解释:
GFP_ZONEMASK定义如下:

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

flag&GFP_ZONEMASK是为了将flag其他无关的位置0。
bit值就是低4位bit值的组合所代表的值,GFP_ZONE_TABLE >> (bit * GFP_ZONE_SHIFT)表示只取GFP_ZONE_TABLE高于bitGFP_ZONE_SHIFT位的值,因为GFP_ZONE_TABLE[bitGFP_ZONE_SHIFT,(bit+1)GFP_ZONE_SHIFT]对应位上记录指定的ZONE的值。&((1 << GFP_ZONES_SHIFT) - 1)因为要将除去GFP_ZONE_TABLE[bitGFP_ZONE_SHIFT,(bit+1)*GFP_ZONE_SHIFT]其他位都置0,才能得到对应ZONE的值。

内存分配:所有函数的共同点:只能分配2的整数幂个页,因此函数参数必须指定分配阶,伙伴系统在内存中分配2^order页,内核中细粒度的分配只能借助于slab分配器,后者基于伙伴系统

内存分配函数功能
alloc_pages(gfp_mask,order)请求2^order个连续的页框,它返回第一个所分配的页框描述符的地址,分配失败返回Null,在gfp.h
alloc_page(gfp_mask)用于获得一个单独页框,order=0的情况,在gfp.h
__get_free_pages(gfp_mask,order)类似于alloc_pages(),但它返回第一个所分配页的线性地址,返回分配内存块的虚拟地址,而不是Page实例,在page_alloc.c
__get_free_page(gfp_mask)用于获得一个单独的页框
get_zeroed_page(gfp_mask)用来获得填满0的页框,返回所获取页框的线性地址,在page_alloc.c中
__get_dma_pages(gfp_mask,order)获得适用于DMA的页框,扩展名为__get_free_pages(gfp_mask
alloc_pages(gfp_t gfp_mask, unsigned int order)
{
 return alloc_pages_current(gfp_mask, order);
}
#define alloc_pages(gfp_mask, order) \
  alloc_pages_node(numa_node_id(), gfp_mask, order)
#define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)
extern unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);

在这里插入图片描述
返回一个32位的地址

extern unsigned long get_zeroed_page(gfp_t gfp_mask);
unsigned long get_zeroed_page(gfp_t gfp_mask)
{
 return __get_free_pages(gfp_mask | __GFP_ZERO, 0);
}
#define __get_dma_pages(gfp_mask, order) \
  __get_free_pages((gfp_mask) | GFP_DMA, (order))

在空闲内存无法满足分配的情况下,所有函数都返回空指针或者0
内核中除了伙伴系统函数之外,还提供其他内存管理函数,它们以伙伴系统为基础,但不属于伙伴分配器本身,这些函数是vmalloc和vmalloc_32,kmalloc

利用宏定义的函数有:alloc_page,__get_free_page,__get_dma_pages
没有使用宏的函数:
__get_free_pages:调用了alloc_pages返回的page实例需要使用辅助函数page_address转换为内存地址。

alloc_pages调用的是alloc_pages_node函数

伙伴系统的各个分配函数之间的关系
在这里插入图片描述

alloc_pages函数依赖于NUMA或者UMA架构,定义如下:
在这里插入图片描述NUMA架构调用alloc_pages_current,然后会调用__alloc_pages_nodemask
在这里插入图片描述
注释如下:对传递的参数进行了解释(暂时理解不了)

*  alloc_pages_current - Allocate pages.
 *
 * @gfp:
 *  %GFP_USER   user allocation,
 *       %GFP_KERNEL kernel allocation,
 *       %GFP_HIGHMEM highmem allocation,
 *       %GFP_FS     don't call back into a file system.
 *       %GFP_ATOMIC don't sleep.
 * @order: Power of two of allocation size in pages. 0 is a single page.
 *
 * Allocate a page from the kernel page pool.  When not in
 * interrupt context and apply the current process NUMA policy.
 * Returns NULL when no page can be allocated.
 *
 * Don't call cpuset_update_task_memory_state() unless
 * 1) it's ok to take cpuset_sem (can WAIT), and
 * 2) allocating for current task (not interrupt).

UMA架构相当于调用alloc_page_node,然后调用__alloc_pages_node函数
在这里插入图片描述
__alloc_pages_node:
在这里插入图片描述
内核先假定传递给__alloc_page_node函数的节点nid是被激活的,但是为了安全它还是先检查并警告内存结点不存在的情况,然后将信息传递给__alloc_pages,其中包含节点nid的被哟个内存域列表zonelist:
在这里插入图片描述
调用__alloc_pages_nodemask,这个函数是伙伴系统zone分配的核心
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>