void __init build_all_zonelists(void)
{
int i;
for(i = 0 ; i < numnodes ; i++)
build_zonelists(NODE_DATA(i));//NODE_DATA(i)宏定义可以找到i号内存node对应的discontig_node_data[i]
printk("Built %i zonelists/n", numnodes);
}
build_zonelists()函数调用分两种请款,我们来浅析一下吧。
支持非统一内存访问(即配置了CONFIG_NUMA选项,该选项一般用于多处理器系统)
static void __init build_zonelists(pg_data_t *pgdat)
{
int i, j, k, node, local_node;
int prev_node, load;
struct zonelist *zonelist;//先要介绍一个结构体--struct zonelist. struct zonelist {
//struct zone *zones[MAX_NUMNODES * MAX_NR_ZONES + 1]; };
//其实就是就是一个指针数组,这个数组里面的每个成员都是一个struct zone,最多可以容纳整个系统的
//所有页区数。
DECLARE_BITMAP(used_mask, MAX_NUMNODES);//这个宏定义其实就是申明一个used_mask[1]这样的数组。
/* initialize zonelists */
for (i = 0; i < GFP_ZONETYPES; i++) {
zonelist = pgdat->node_zonelists + i;//node_zonelists页区列表结构数组,其实就是上面介绍的zonelist结构类型。
//这个列表数组其实就只是有三个元素,分别是我们说的3类页区。可以看出这里只
//是清零初始化。
memset(zonelist, 0, sizeof(*zonelist));
zonelist->zones[0] = NULL;
}
local_node = pgdat->node_id;
load = numnodes;
prev_node = local_node;
bitmap_zero(used_mask, MAX_NUMNODES);//这里是使16个node对应的used_mask[0]这个32位的相应位为0
while ((node = find_next_best_node(local_node, used_mask)) >= 0) {//对于find_next_best_node()函数的分析如下:
static int __init find_next_best_node(int node, void *used_node_mask)
{
int i, n, val;
int min_val = INT_MAX;
int best_node = -1;
for (i = 0; i < numnodes; i++) {
cpumask_t tmp;//这里是为每个处理器设置一位,来标记是否使用过,tmp里面的每一位都代表一个cpu。
n = (node+i)%numnodes;//求余,一上来肯定是以本次node开始的。
if (test_bit(n, used_node_mask))//因为是初始化函数调用的,所以都没有被使用过的。这里会跳过。
continue;
if (!test_bit(node, used_node_mask)) {//这里会执行。
best_node = node;
break;//找到没有被使用过的node就跳出上面的for循环。
val = node_distance(node, n);//如果发现的node不是从本次node开始的,val=1,否则就是0
tmp = node_to_cpumask(n);//由于这里注明是多个cpu的,所以如果是哪个node被使用了,对应的cpu也要标注一下,对应位变化
if (!cpus_empty(tmp))
val += PENALTY_FOR_NODE_WITH_CPUS;
if (val < min_val) {
min_val = val;
best_node = n;
}
}//上面涉及多个cpu时如何选取合适的node,本人现在未能理解。希望以后可以回来完善。
if (best_node >= 0)
set_bit(best_node, used_node_mask);//确实找到所需的node,这样在相应位置上1,表示这个node被使用了。
return best_node;
}//我们回到刚才的while,代码如下:
if (node_distance(local_node, node) !=node_distance(local_node, prev_node))
node_load[node] += load;
prev_node = node;
load--;//重点在后面拉。
for (i = 0; i < GFP_ZONETYPES; i++) {//开始为这个node的成员node_zonelist[]进行设置了。
zonelist = pgdat->node_zonelists + i;
for (j = 0; zonelist->zones[j] != NULL; j++);//寻找到一个不指向空的struct zone的结构体,记录这个J。
k = ZONE_NORMAL;
if (i & __GFP_HIGHMEM)//node_zonelist[2]存放highMem类型页区的strcut zone结构体
k = ZONE_HIGHMEM;
if (i & __GFP_DMA)//node_zonrlist[1]存放dma类型页区的strcut zone结构体
k = ZONE_DMA;
j = build_zonelists_node(NODE_DATA(node), zonelist, j, k);//可以看出第一次调用此node时,把本次node各类页
//区对应的struct zone链接到各类node_zonelist[]上
//等下一次while的时候,我们就把下个node的各类页区
//加载上一次的后面。
zonelist->zones[j] = NULL;//每次都使其尾部指向null。以便下次利用。
下种情况是内存统一的,个人认为,如果只有一个cpu的话,就是使用一个统一的内存。我们用arm的大多数是使用下面这种。
static void __init build_zonelists(pg_data_t *pgdat)
{
int i, j, k, node, local_node;
local_node = pgdat->node_id;
for (i = 0; i < GFP_ZONETYPES; i++) {
struct zonelist *zonelist;
zonelist = pgdat->node_zonelists + i;
memset(zonelist, 0, sizeof(*zonelist));
j = 0;
k = ZONE_NORMAL;
if (i & __GFP_HIGHMEM)
k = ZONE_HIGHMEM;
if (i & __GFP_DMA)
k = ZONE_DMA;
j = build_zonelists_node(pgdat, zonelist, j, k);//上面的容易理解,这里不多说了。
for (node = local_node + 1; node < numnodes; node++)
j = build_zonelists_node(NODE_DATA(node), zonelist, j, k);//这里把本次node后面的node都往上加。
for (node = 0; node < local_node; node++)
j = build_zonelists_node(NODE_DATA(node), zonelist, j, k);//把前面的node也往上加。
zonelist->zones[j] = NULL;//结尾处附上NULL
}//可以看到,我们在哪个内存node都可以访问到其他node不同类型页区的struct zoon结构数据,靠的就是我们这里的主角node_zonelist。
在bootmem_init初始化的时候,已经初始化了
内存
节点的zone成员,该成员是
struct
zone数组,存放该
内存
节点的zone信息。在
linux
的
内存
管理中,分几个阶段进行抽象,用数据结构来管理。先用结点集合管理
内存
,然后用zone管理结点,再用页的管理zone。此时使用的数据结构分别为pglist_data、zone、page结构体,本章的主要是来
分析
内核是如何完成zonelist的初始化。
1. 数据结构
在结点的pglist_data数据结构中有一个node_zone_list[]类型的st
为什么需要zonelist?
之前bootmem_init初始化的时候,已经初始化了
内存
节点的zone成员,该成员是一个
struct
zone数组,存放该
内存
节点的zone信息。
对于uma系统来说,这已经够了,因为uma系统只有一个本地
内存
节点,所有zone的信息都存放在本地
内存
节点的zone成员中。
对于numa系统来说,除了本地
内存
节点,还可以存在一个或多个远端内...
关键
函数
1.__
build
_all_zonelists()
2.nr_free_pagecache_pages()__
build
_all_zonelists(void *data):
对于UMA系统这个
函数
执行的子
函数
主要有以下几个:(CONFIG_HAVE_MEMORYLESS_NODES 没有配置)
for_each_online_node(nid) {//循环找到系统的所有节点
build
_zone_zonelists() 初始化备用
内存
域链表
build
_zone_zonelists() 建立管理结点及其
内存
域所需的数据结构,即node_zonelists数组。type
struct
pglist_data{
struct
zonelists node_zonelists[MAX_ZONELISTS];
}pg_da
细心的读者可能会发现bootmem流程中每个节点strcut pglist_data构体
中的
strcut zonelist node_zonelists[]成员还未进行初始化。实际上这些操作是通过start_kernel
函数
中的
build
_all_zonlists
函数
来完成的。在
分析
该
函数
前可通过下面链接的博客了解一下备选节点的概念:http://blog.chinaunix.net/uid-30282771-id-5171166.html
build
_all_zonlists
函数
主要是为node创建一个内
1. 前言
本专题文章承接之前《kernel启动流程_head.S的执行》专题文章,我们知道在head.S执行过程中保存了bootloader传递的启动参数、启动模式以及FDT地址等,创建了内核空间的页表,最后为init进程初始化好了堆栈,并跳转到start_kernel执行。
本文重点介绍start_kernel的
build
_all_zonelists的主要流程.
kernel版本:5.10
平台:arm64
2. pg_data_t
先来重点看下pg_data_t结构体,有一段注释
On NUMA m
1. 今日内容(第二阶段(二)–初始化备用
内存
域列表zonelists)
我们之前讲了在memblock完成之后,
内存
初始化开始进入第二阶段, 第二阶段是一个漫长的过程, 它执行了一系列复杂的操作, 从体系结构相关信息的初始化慢慢向上层展开, 其主要执行了如下操作
特定于体系结构的设置
在完成了基础的
内存
结点和
内存
域的初始化工作以后, 我们必须克服一些硬件的特殊设置
在初始化
内存
的结点和
内存
区...
内存
管理子系统是
linux
内核最核心最重要的一部分,内核的其他部分都需要在
内存
管理子系统的基础上运行。而对其初始化是了解整个
内存
管理子系统的基础。对相关数据结构的初始化是从全局启动例程start_kernel开始的。本文详细描述了从bootloader跳转到
linux
内核
内存
管理子系统初始化期间所做的操作,从而来加深对
内存
管理子系统知识的理解和掌握。
内核的入口是stext,这是在arch/ar
初始化后zonelists:
m1 == node1 movable ; h1 == node1 highmen; n1 == node1 normal; d32.1 == node1 DMA32; d1 == node1 DMA
m2 == node2 movable; .........
ZONELIST_ORDER_NODE:pgdat->node_zonelists[0]->_zonerefs[0 ~ node * node->node_zones] = m1 h1 n1
一、伙伴系统原理
1. 伙伴关系
定义:由一个母实体分成的两个各方面属性一致的两个子实体,这两个子实体就处于伙伴关系。在操作系统
分配
内存
的过程中,一个
内存
块常常被分成两个大小相等的
内存
块,这两个大小相等的
内存
块就处于伙伴关系。它满足 3 个条件 :
两个块具有相同大小记为 2^K 它们的物理地址是连续的 从同一个大块中拆分出来
不坑爹不坑娘:
Linux中的内存分配和释放之kmem_cache_alloc()函数分析
运动码农: