Linux内存初始化

1.进程内存描述符struct mm_struct

一个进程的内存信息通过struct mm_struct描述和管理,我们称其为进程描述符
一个进程的虚拟地址空间主要由两个数据结来描述。
一个是最高层次的:mm_struct,一个是较高层次的:vm_area_structs。
最高层次的mm_struct结构描述了一个进程的整个虚拟地址空间。
较高层次的结构vm_area_truct描述了虚拟地址空间的一个区间(简称虚拟区)。
每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个指向该进程的结构。可以说,mm_struct结构是对整个用户空间的描述。

2.启动

Linux内核启动于init/main.c中的start_kernel函数,内存系统也启动于此。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
......
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
setup_arch(&command_line);
/*
* Set up the the initial canary ASAP:
*/
boot_init_stack_canary();
mm_init_cpumask(&init_mm);
setup_command_line(command_line);
......
build_all_zonelists(NULL, NULL);
page_alloc_init();
......
/*
* These use large bootmem allocations and must precede
* kmem_cache_init()
*/
setup_log_buf(0);
pidhash_init();
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_init();
......
}

setup_arch函数中会调用bootmem_init来初始化启动内存:
arch/arm64/kernel/setup.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void __init setup_arch(char **cmdline_p)
{
pr_info("Boot CPU: AArch64 Processor [%08x]n", read_cpuid_id());

sprintf(init_utsname()->machine, UTS_MACHINE); // 初始化内核线程的内存描述符
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
......
paging_init(); //初始化分页系统,主要是设置页表,初始化zone memory maps以及设置zero page。

acpi_table_upgrade();

/* Parse the ACPI tables for possible boot-time configuration */
acpi_boot_table_init();

if (acpi_disabled)
unflatten_device_tree();

bootmem_init(); //bootmem初始化
......
}

mm_init_cpumask(&init_mm)操作的参数init_mm是一个mm_struct结构。
init_mm是内核线程的mm_struct数据结构,或内核线程的映射,这是因为在这个映射中不包含任何用户空间的映射。
系统中所有的内核线程都共享同一个映射,那就是init_mm。特别的,内核线程init的mm_struct数据结构就是init_mm。而mm_struct结构中,则有个指针pgd指向它的首层页面目录。
系统中只有一个init_mm结构对象,他在mm/init-mm.c中申明。

1
2
3
4
5
6
7
8
9
大专栏  Linux内存初始化ne">10
11
12
13
14
struct mm_struct init_mm = {
.mm_rb = RB_ROOT,
#ifdef CONFIG_SPECULATIVE_PAGE_FAULT
.mm_rb_lock = __RW_LOCK_UNLOCKED(init_mm.mm_rb_lock),
#endif
.pgd = swapper_pg_dir,
.mm_users = ATOMIC_INIT(2),
.mm_count = ATOMIC_INIT(1),
.mmap_sem = __RWSEM_INITIALIZER(init_mm.mmap_sem),
.page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
.mmlist = LIST_HEAD_INIT(init_mm.mmlist),
.user_ns = &init_user_ns,
INIT_MM_CONTEXT(init_mm)
};

函数mm_init_cpumask是围绕这个结构体对象执行的,它清理内核线程的内存描述符中cpu_vm_mask_var字段,将其置零。

1
2
3
4
5
6
7
static inline void mm_init_cpumask(struct mm_struct *mm)
{
#ifdef CONFIG_CPUMASK_OFFSTACK
mm->cpu_vm_mask_var = &mm->cpumask_allocation;
#endif
cpumask_clear(mm->cpu_vm_mask_var);
}

下面通过build_all_zonelists 构建zonelist。
为什么需要zonelist?
之前bootmem_init初始化的时候,已经初始化了内存节点的zone成员,该成员是一个struct zone数组,存放该内存节点的zone信息。
对于uma系统来说,这已经够了,因为uma系统只有一个本地内存节点,所有zone的信息都存放在本地内存节点的zone成员中。
对于numa系统来说,除了本地内存节点,还可以存在一个或多个远端内存节点,本地内存节点的zone成员并不会存放远端内存节点的zone信息。所以,这里引入zonelist的概念,本地内存节点和远端内存节点的zone统一挂在zonelist链表上。
什么是pageset?
每一个cpu会维护一个boot_pageset成员,这是一个struct per_cpu_pageset结构体,用于描述每个cpu维护的page集合。该函数中只会对pageset做一些最简单的初始化
下面是build_all_zonelists的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
build_all_zonelists
|----->set_zonelist_order();
| |----->current_zonelist_order = ZONELIST_ORDER_ZONE;
| | 对于uma系统来说,仅存在一个内存节点,所以按zone来排序;对于numa系统来说,可以按node来排序,也可以按zone来排序
|----->build_all_zonelists_init();
| |----->__build_all_zonelists(NULL);
| | |----->pg_data_t *pgdat = NODE_DATA(nid);
| | |----->build_zonelists(pgdat);
| | | |----->zonelist = &pgdat->node_zonelists[ZONELIST_FALLBACK];
| | | |----->j = build_zonelists_node(pgdat, zonelist, 0);
| | | | 遍历该内存节点所有的zone,将所有的zone挂到该内存节点的node_zonelists上去
| | | | |----->zoneref_set_zone(zone, &zonelist->_zonerefs[nr_zones++]);
| | | | |----->return nr_zones;
| | | | | 传进来的nr_zones加上挂到node_zonelists上的zone之后,返回
| | | |----->zonelist->_zonerefs[j].zone = NULL;
| | | |----->zonelist->_zonerefs[j].zone_idx = 0;
| | | | 标记node_zonelists上zone的结束
| | | |----->zonelist->_zonerefs[j].zone = NULL;
| | | |----->zonelist->_zonerefs[j].zone_idx = 0;
| | |----->setup_pageset(&per_cpu(boot_pageset, cpu), 0);
| | | 遍历每一个cpu,初始化其pageset
| | | |----->pageset_init(p);
| | | | |----->pcp->count = 0;
| | | | | page数量初始化为0
| | | | |----->INIT_LIST_HEAD(&pcp->lists[migratetype]);
| | | | | 每一种迁移类型对于一个链表,这里初始化所有迁移类型对应的链表
| | | |----->pageset_set_batch(p, batch);
| | | | |----->pageset_update(&p->pcp, 6 * batch, max(1UL, 1 * batch));
| | | | | |----->pcp->batch = 1;
| | | | | | chunk size初始化为1
| | | | | |----->pcp->high = high;
| | | | | | page上限初始化为0
| | | | | |----->pcp->batch = batch;
| | | | | | chunk size初始化为1

接下来page_alloc_init

猜你喜欢

转载自www.cnblogs.com/liuzhongrong/p/12390097.html