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分配的核心