概述
要观察启动过程中的内存初始化当然要从初始化系统函数start_kernel
开始(V4.7),由于start_kernel
代码篇幅过多,这里只摘取有关于内存的部分。
asmlinkage __visible void __init start_kernel(void)
{
page_address_init();
setup_arch(&command_line);
mm_init_cpumask(&init_mm);
setup_per_cpu_areas();
build_all_zonelists(NULL, NULL);
page_alloc_init();
mm_init();
kmem_cache_init_late();
kmemleak_init();
setup_per_cpu_pageset();
}
函数 | 功能 |
---|---|
page_address_init | 初始化高端内存的映射表。高端内存即1G的内核空间中高于896M的内存。 |
setup_arch | 对内核架构进行初始化。 |
mm_init_cpumask | 初始化CPU屏蔽字 |
setup_per_cpu_areas | 设置SMP体系每个CPU使用的内存空间,同时拷贝初始化段里数据。对于非SMP体系来说这个字段为空。 |
build_all_zonelists | 建立并初始化结点和内存域的数据结构 |
page_alloc_init | 设置内存页分配通知器 |
mm_init | 建立了内核的内存分配器其中通过mem_init停用bootmem分配器并迁移到实际的内存管理器然后调用kmem_cache_init函数初始化内核内部用于小块内存区的分配器 |
kmem_cache_init_late | 在kmem_cache_init之后, 完善分配器的缓存机制, 当前3个可用的内核内存分配器slab, slob(嵌入式), slub(大型机)都会定义此函数 |
kmemleak_init | Kmemleak工作于内核态,Kmemleak 提供了一种可选的内核泄漏检测,其方法类似于跟踪内存收集器。当独立的对象没有被释放时,其报告记录在 /sys/kernel/debug/kmemleak中, Kmemcheck能够帮助定位大多数内存错误的上下文 |
setup_per_cpu_pageset | 为每CPU的高速缓存预分配页框。由于在分页情况下,每次存储器访问都要存取多级页表,这就大大降低了访问速度。所以,为了提高速度,在CPU中设置一个最近存取页面的高速缓存硬件机制,当进行存储器访问时,先检查要访问的页面是否在高速缓存中 |
启动过程中的内存管理
由于在操作系统初始化的时候,操作系统还没有内存管理的功能,只是获得了内存的一些基本信息,内存管理的一些数据结构还不存在,需要创建出来,但是创建的过程本身就是内存管理的内容,这就需要一个简单的内存管理器来负责初始化内存管理的数据结构,直到内核真正的内存管理器开始使用后再释放之前引导时期使用的内存管理器。
引导内存分配器bootmem
由于需要初始化内存管理的一些数据结构所以系统要先建立一个简单的临时内存管理系统bootmem。bootmem在没有构建完整的页分配机制之前可以用来实现简单的内存分配和释放,同时他也是系统真正的内存管理器的基础。
bootmem是基于首次适应(从空闲分区表的第一个表目起查找该表,把最先能够满足要求的空闲区分配给作业)的分配器,它的本质是位图(bitmap)。一个位代表一个页框,有多少位就代表系统有多少页框,同时1代表已用,0代表空闲。这个位图用来管理3G~3G+896M的部分,也就是可以被内核直接映射的物理内存部分。
内存分配器memblock
由于bootmem还存在一些问题,所以就引入了memblock,bootmem用于管理3G~3G+896M的启动时所需的部分物理内存,memblock却可以管理全部可用物理内存。2010 年内核 v.2.6.35 版本首次引入 memblock 补丁,在此之前使用的内存分配器是 bootmem。
struct memblock {
bool bottom_up; /* is bottom up direction? */
phys_addr_t current_limit;
struct memblock_type memory;
struct memblock_type reserved;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
struct memblock_type physmem;
#endif
};
字段 | 功能 |
---|---|
bottom_up | 在分配内存时决定是自底向上地还是自顶向下地搜寻空闲内存区间 |
current_limit | 用于限定分配的物理地址。 |
memory | 用于记录总的内存资源 |
reserved | 用于记录已经使用或者预留的内存资源 |
physmem | 用于描述硬件探查到的真实物理内存区间,一旦注册了将不能改变这个区间的大小。只能是真实大小的物理内存区域,而不能是通过内核参数指定的。 |
void __init setup_arch(char **cmdline_p)
{
memblock_reserve(__pa_symbol(_text),
(unsigned long)__bss_stop - (unsigned long)_text);
clone_pgd_range(swapper_pg_dir + KERNEL_PGD_BOUNDARY,
initial_page_table + KERNEL_PGD_BOUNDARY,
KERNEL_PGD_PTRS);
load_cr3(swapper_pg_dir);
setup_memory_map();
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = _brk_end;
code_resource.start = __pa_symbol(_text);
code_resource.end = __pa_symbol(_etext)-1;
data_resource.start = __pa_symbol(_etext);
data_resource.end = __pa_symbol(_edata)-1;
bss_resource.start = __pa_symbol(__bss_start);
bss_resource.end = __pa_symbol(__bss_stop)-1;
max_pfn = e820_end_of_ram_pfn();
if (max_pfn > (1UL<<(32 - PAGE_SHIFT)))
max_low_pfn = e820_end_of_low_ram_pfn();
else
max_low_pfn = max_pfn;
high_memory = (void *)__va(max_pfn * PAGE_SIZE - 1) + 1;
x86_init.paging.pagetable_init();
}
setup_arch
使用memblock
函数对内存页进行分配。clone_pgd_range
初始化了initial_page_table
作为全局页目录表,把它复制给swapper_pg_dir
,在这以后,swapper_pg_dir
就一直当做全局目录表使用,随后load_cr3
设置cr3,弃用以前的initial_page_table
。使用setup_memory_map
设置内存映射。init_mm.xx
完成内存描述符中内核代码段,数据段和 brk
段的初始化,xxx_resource.xxx
完成代码/数据/bss
资源的初始化。e820_end_of_ram_pfn
函数获得最后一个页帧的编号,也就是最大页面帧号max_pfn
。之后计算max_low_pfn
(低端内存的最大页面帧号),如果系统安装了超过4GB的RAM,max_low_pfn=max_pfn
。接下来通过 __va
宏计算 高端内存
中的最大页帧号,并且这个宏会根据给定的物理内存返回一个虚拟地址。x86_init.paging.pagetable_init
最终调用paging_init
进行分页管理,和初始化内存管理。
struct x86_init_ops x86_init __initdata = {
.paging = {
.pagetable_init = native_pagetable_init,
},
};
#define native_pagetable_init paging_init
初始化伙伴内存管理
内核从start_kernel
->setup_arch
完成内存分配器的初始化和设置之后,就要初始化内存管理器了。
void __init paging_init(void)
{
sparse_memory_present_with_active_regions(MAX_NUMNODES);
sparse_init();
/*
* clear the default setting with node 0
* note: don't use nodes_clear here, that is really clearing when
* numa support is not compiled in, and later node_set_state
* will not set it back.
*/
node_clear_state(0, N_MEMORY);
if (N_MEMORY != N_NORMAL_MEMORY)
node_clear_state(0, N_NORMAL_MEMORY);
zone_sizes_init();
}
sparse_memory_present_with_active_regions
函数将每个NUMA
节点的内存区域记录到mem_section
结构数组中,该结构数组包含指向struct page
。之后调用zone_sizes_init
void __init zone_sizes_init(void)
{
unsigned long max_zone_pfns[MAX_NR_ZONES];
memset(max_zone_pfns, 0, sizeof(max_zone_pfns));
#ifdef CONFIG_ZONE_DMA
max_zone_pfns[ZONE_DMA] = min(MAX_DMA_PFN, max_low_pfn);
#endif
#ifdef CONFIG_ZONE_DMA32
max_zone_pfns[ZONE_DMA32] = min(MAX_DMA32_PFN, max_low_pfn);
#endif
max_zone_pfns[ZONE_NORMAL] = max_low_pfn;
#ifdef CONFIG_HIGHMEM
max_zone_pfns[ZONE_HIGHMEM] = max_pfn;
#endif
free_area_init_nodes(max_zone_pfns);
}
该函数用来初始化zone区的大小。直到调用free_area_init_nodes
,这个函数初始化内存数据结构包括内存节点和内存域。free_area_init_nodes
函数做完之后,就已经准备好了内存管理所需要的数据结构。到此跳出setup_arch
接着执行一系列函数直到
build_all_zonelists
这个函数,这个函数将所有节点的管理区链入到了zonelist中。之后直到mm_init
()执行迁移到真实的内存管理器伙伴系统。
到这里整个启动过程内存的初始化就完成了,完成了新老内存管理器的交接,由此理清了整个内存管理系统的前世和今生。
参考资料:
https://www.cnblogs.com/linhaostudy/p/10038582.html
https://xinqiu.gitbooks.io/linux-insides-cn/content/Initialization/linux-initialization-4.html
最后
以上就是坚强毛豆为你收集整理的启动中的内存初始化的全部内容,希望文章能够帮你解决启动中的内存初始化所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复