上一篇:https://blog.csdn.net/qq_41345173/article/details/104344550
文章目录
一、物理内存的组织方式
自物理内存由传统的SMP(对称多处理器)组织形式转换为NUMA(非统一内存访问方式)方式,物理内存的页面就变得不连续起来了,内存模型变成了非连续内存模型,在内存访问采用NUMA方式时我们将每一个CPU与本地内存的组合叫做一个NUMA节点。见如下所示示意图:
二、从节点数据结构开始分析其构成
如下图所示,当采取NUMA方式利用内存时有多个CPU就有多个NUMA节点,而每一个NUMA节点又由多个区域组成,每个区域负责管理自己范围内的页面。当系统要求分配大块(即多页)内存时使用伙伴系统分配,而像使用malloc这样分配小块内存会使用slab分配器进行分配。
象征NUMA节点的pglist_data结构
typedef struct pglist_data {
//linux-4.13.16\include\linux\mmzone.h
struct zone node_zones[MAX_NR_ZONES]; //节点的各个分区
struct zonelist node_zonelists[MAX_ZONELISTS]; //备用节点的内存区域情况
int nr_zones;//当前节点区域的数目
struct page *node_mem_map; //象征当前节点所有物理页面的结构体数组
unsigned long node_start_pfn;//节点的起始页号
unsigned long node_present_pages; /*节点中所有物理页面数减去内存空洞页面数 */
unsigned long node_spanned_pages; /* 节点中所有的物理页面数,包括空洞*/
int node_id;//节点ID
...
} pg_data_t;
NUMA节点的组成之区域结构
struct zone {
...
struct pglist_data *zone_pgdat;
struct per_cpu_pageset __percpu *pageset; //用于区分冷热页
...
unsigned long zone_start_pfn;//起始页
unsigned long managed_pages;//伙伴系统管理的页面=
unsigned long spanned_pages;//该区域含空洞的物理页面
unsigned long present_pages;//该区域不含空洞的物理页面
...
/* free areas of different sizes */
struct free_area free_area[MAX_ORDER];//当前区域空闲块集合
区域的底层构成之页面struct page结构体
struct page是一个特别复杂的结构体,里面含有许多union结构体,之所以这样设计是因为一个页面可以用于多种使用模式,如大批量使用以用于匿名页或者内存文件映射此时我们使用伙伴系统分配,又如小批量使用分配小块内存此时使用slab allocator技术,其结构部分定义如下:
struct page {
//linux-4.13.16\include\linux\mm_types.h
unsigned long flags;
union {
struct address_space *mapping;//用于内存映射,匿名页或者映射文件时使用
void *s_mem; /* slab first object */
atomic_t compound_mapcount; /* first tail page */
};
union {
pgoff_t index; /* 映射区的偏移量 */
void *freelist; /* 空闲对象 */
};
union {
unsigned long counters;
unsigned counters;
struct {
union {
atomic_t _mapcount;
unsigned int active; /* SLAB */
struct { /* SLUB */
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
int units; /* SLOB */
。。。
三、分配页级别内存的利器——伙伴系统
伙伴系统应该是针对每一个NUMA节点的各个区域一种算法,应该是一个区域有一个伙伴系统的处理逻辑负责每一个区域吧!具体看后话验证再说,而且在struct zone中有一个结构体数组定义如下:
#define MAX_ORDER 11
struct free_area free_area[MAX_ORDER];
他就是负责管理当前区域空闲块的数组,是11个页块链表,每个块链表分别是包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页的页块,在第i个页块链表中页块中页的数目为2i。
其具体实现逻辑就是你要多少页块内核就按这个大小朝上取整至符合11个页块链表中页块大小的一个标准值分配之,若没有就继续取更大的块。这些函数逻辑可以在下面的函数中发现,咱现在去利用工具追踪它。
typedef unsigned __bitwise gfp_t;
//linux-4.13.16\include\linux\gfp.h
static inline struct page *
alloc_pages(gfp_t gfp_mask, unsigned int order)
{//伙伴系统分配算法的入口函数
return alloc_pages_current(gfp_mask, order);
}
上面这个函数是伙伴系统分配算法的入口函数,第一个参数定义可见,其实这个gfp表示希望在那个区域分配内存,在它即将调用的函数alloc_pages_current中有介绍。
//linux-4.13.16\mm\mempolicy.c
struct page *alloc_pages_current(gfp_t gfp, unsigned order)
/**
* alloc_pages_current - Allocate pages.
*
* @gfp://有如下实例
* %GFP_USER 映射到用户空间,其下类似
* %GFP_KERNEL kernel allocation,
* %GFP_HIGHMEM highmem allocation,
* %GFP_FS don't call back into a file system.
* %GFP_ATOMIC don't sleep.
* @order: 表示分配2的order次方个页
*
*/
{
struct mempolicy *pol = &default_policy;
struct page *page;
。。。//参数校验
page = __alloc_pages_nodemask(gfp, order,
policy_node(gfp, pol, numa_node_id()),
policy_nodemask(gfp, pol));//伙伴系统核心函数
return page;
}
下面看核心函数__alloc_pages_nodemask,它是核心方法最终会调用get_page_from_freelist函数寻找空闲块,在该函数内就执行一系列的流程函数最终找到合适的块并从当前链表移除,若有多余部分则挂在前面的块链表上,其具体调用逻辑如下所示:
//linux-4.13.16\mm\page_alloc.c
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
const struct alloc_context *ac)
{
。。。
page = rmqueue(ac->preferred_zoneref->zone, zone, order,
gfp_mask, alloc_flags, ac->migratetype);
}
static inline
struct page *rmqueue(struct zone *preferred_zone,
struct zone *zone, unsigned int order,
gfp_t gfp_flags, unsigned int alloc_flags,
int migratetype){。。。}
__rmqueue(zone, order, migratetype);
__rmqueue_smallest(zone, order, migratetype);
static inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
int migratetype)
{
unsigned int current_order;
struct free_area *area;
struct page *page;
/* Find a page of the appropriate size in the preferred list */
for (current_order = order; current_order < MAX_ORDER; ++current_order) {
area = &(zone->free_area[current_order]);
page = list_first_entry_or_null(&area->free_list[migratetype],
struct page, lru);
if (!page)
continue;
list_del(&page->lru);
rmv_page_order(page);
area->nr_free--;
expand(zone, page, order, current_order, area, migratetype);
set_pcppage_migratetype(page, migratetype);
return page;
}
return NULL;
}