暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

linux中的物理内存管理(二)

术道经纬 2019-07-16
545

Node

介绍完了page和zone,沿着自底向上的顺序,最后就是表示node的结构体pglist_data了。

typedef struct pglist_data {
int nr_zones;
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELIST];

unsigned long node_size;
struct page *node_mem_map;

int node_id;
unsigned long node_start_paddr;
struct pglist_data *node_next;

spinlock_t lru_lock;
...
} pg_data_t;

  • nr_zones表示这个node含有多少个zones,node_zones[]则是一个包含各个zone结构体的数组。

  • node_zonelists[]包含了2个zonelist,一个是由本node的zones组成,另一个是由从本node分配不到时可选的备用zones组成,相当于是选择了一个退路,所以叫fallback。

enum {
ZONELIST_FALLBACK, /* zonelist with fallback */
#ifdef CONFIG_NUMA
ZONELIST_NOFALLBACK, /* zonelist without fallback (__GFP_THISNODE) */
#endif
MAX_ZONELISTS
};

如果能从指定的目标node获得内存,则为NUMA hit,只能从备选node中获取,则为NUMA miss,可通过"/sys/devices/system/node/node*/numastat"查看。

  • node_size是指这个node含有多少个page frames,node_mem_map指向node中所有struct page构成的mem_map数组。

  • node_id是这个node的逻辑ID,也就是在NUMA系统中的编号。现在linux中的内存分配函数都是区别node的,靠的就是这个node_id,类似于文件描述符fd。

  • node_start_paddr(在2.6内核中被换成了node_start_pfn)是该node的起始物理地址。node_next指向由多个node构成的NUMA单向链表pgdat_list中的下一个节点。如果是UMA系统,只有一个node,则node_start_pfn为0,node_next为NULL。

你看,表示的node结构体pglist_data通过node_zones包含了它的下一级,也就是表示zone的结构体zone_struct,zone_struct又通过zone_pgdat指向包含它的node。zone_struct中zone_mem_map指向它的下一级,也就是表示page frame的struct page,按理struct page中也应该有一个元素是指向包含它的zone的,可是好像没看到对不对?并不是在那个省略号里,而是,就在flags里了,它用flags的高8位存储了它所属的zone。这再一次体现了struct page的“锱铢必较”,真是linux中节省内存的典范啊。

这时通过page flags找到page所属的node的:

static inline int page_to_nid(const struct page *page)
{
struct page *p = (struct page *)page;
return (PF_POISONED_CHECK(p)->flags >> NODES_PGSHIFT) & NODES_MASK;
}

这时通过page flags找到page所属的zone的:

static inline enum zone_type page_zonenum(const struct page *page)
{
return (page->flags >> ZONES_PGSHIFT) & ZONES_MASK;
}

static inline struct zone *page_zone(const struct page *page)
{
return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];
}

事实上,page flags的组织是这样的:

有低位的bits才是上文提到的那些表示page frame属性和状态的标志位。

在NODE和ZONE前面,还有一个SECTION,将在这篇文章中介绍。

Node和Zone的初始化

介绍完了这些核心的数据结构,来看看它们是怎么被使用的。

free_area_init_nodes()遍历系统中所有的nodes,调用free_area_init_node()依次初始化各个node。

for_each_online_node(nid) {                                    
free_area_init_node(nid, pgdat, NULL,
find_min_pfn_for_node(nid), NULL);
}

free_area_init_core()则遍历node内的所有zones并依次初始化。

for (j = 0; j < MAX_NR_ZONES; j++) {                  
struct zone *zone = pgdat->node_zones + j;
size = zone_spanned_pages_in_node(nid, j, zones_size);
realsize = size - zone_absent_pages_in_node(nid, j, zholes_size);
}

size就是上文介绍的strut zone里的spanned_pages,realsize就是present_pages。

获取物理内存

linux为获取page frame提供了两个基本函数:

struct page * alloc_pages(gfp_t gfp_mask, unsigned int order)
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

两者的参数是一模一样的,区别体现在返回值上,alloc_pages()返回的是指向第一个page的struct page的指针,__get_free_pages()返回的是第一个page映射后的虚拟地址,其实__get_free_pages()就是比alloc_pages()多了一个地址转换的工作,因为CPU直接使用的是虚拟地址,这样做也是为了给调用者提供更大的方便。

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
page = alloc_pages(gfp_mask, order);
if (page = NULL)
return (unsigned long) page_address(page);
}

调用这2个函数都会获得  个page frames,这些pages在物理地址上是连续的。之所以要求是2的n次方,这是由底层的buddy分配机制决定的。order需大于或等于0,如果只需要一个page,可设置order为0,也可以直接调用现成的alloc_page(gfp_mask)。那谁会放着alloc_pages()可以一口气分配多个page frames不用,会去调用alloc_page()一个个的分呢?那就是前面介绍到的vmalloc()啦,因为vmalloc()分配的物理内存可能是不连续的,所以不能直接使用alloc_pages()。

for (i = 0; i < area->nr_pages; i++) {
struct page *page;
if (node == NUMA_NO_NODE)
page = alloc_page(alloc_mask);
else
page = alloc_pages_node(node, alloc_mask, order);
}

kmalloc(size, flags)则是按字节分配,使用slab分配机制实现。它和alloc_pages()的共同点是都有用到一组限定了从何处获取以及如何获取空闲物理内存的GFP(Get Free Page)标志位。关于GFP的介绍,请看下文分解。


文章转载自术道经纬,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论