第一阶段是个时间点 : 基本堆栈建立完成
第二阶段是个时间点 : bootmem 完成建立
第二阶段到 第三阶段 是个过程, 该过程中 基本堆栈管理 消亡, bootmem 完成建立
详细过程解读
迈向第二阶段,按最终目标来看,只需要关注两点内容
1. 做了哪些内存映射
2. bootmem 怎么提供的接口
但是在实现过程(linux-3.0.1 arm32)中,分为了更多步骤
---------------------------------------meminfo
1. meminfo 填充的过程
2. meminfo 检查的过程
---------------------------------------memblock
3. memblock 填充的过程
将meminfo中的内容dump到memblock
4. 内存映射的过程(清0页表,设置页表,生成零页)
清0页表
映射
low mem
0页
devicemaps
kmap
---------------------------------------bootmem
5. bootmem 初始化的过程(激活启动时的内存分配器)
将bootmem挂到全局变量上
将memblock中的内容dump到bootmem
该释放的释放,该保留的保留
6. bootmem 接口(申请,释放)调用过程
其实整个流程归结为三点
1. 分析 bootloader 传过来的内存数据.用memblock管理
2. 根据 memblock 管理的内存 ,建立映射
3. 初始化bootmem ,使 bootmem 能够提供 申请内存 释放内存 的接口
实现分为 6个过程,其中 4 和 5 过程最重要
1. meminfo 填充的过程
在启动过程中,kernel 不会探测硬件有多少内存,uboot(或其他bootloader)也不会探测硬件有多少内存
内存信息是写死在uboot(或其他bootloader)配置中的,然后经uboot通过TAG传给kernel
kernel 在解析TAG后,将内存信息放置到 struct meminfo meminfo 结构体变量中.
--------------------------------------------------------------------------------------------
u-boot-----------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
uboot 相关的内存tag :
ATAG_INITRD2
ATAG_CMDLINE
ATAG_MEM
-------------------CODE
在 uboot1.1.6/lib_arm/armlinux.c 中的 do_bootm_linux 中
bd_t *bd = gd->bd;
char *commandline = getenv ("bootargs");
setup_start_tag (bd);
params = (struct tag *) bd->bi_boot_params;
setup_serial_tag (¶ms);
setup_revision_tag (¶ms);
setup_memory_tags (bd);
// include/configs/smdk6410.h 中 #define CONFIG_NR_DRAM_BANKS 1
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
// ./board/samsung/smdk6410/smdk6410.c 中 dram_init
// gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
// gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
// include/configs/smdk6410.h 中
// #define PHYS_SDRAM_1 MEMORY_BASE_ADDRESS
// #define MEMORY_BASE_ADDRESS 0x50000000
// #define PHYS_SDRAM_1_SIZE 0x08000000 或 #define PHYS_SDRAM_1_SIZE 0x10000000
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;
params = tag_next (params);
}
setup_commandline_tag (bd, commandline);
// include/configs/smdk6410.h 中
// #define CONFIG_BOOTARGS "root=/dev/mtdblock2 rootfstype=yaffs2 init=/linuxrc console=ttySAC0,115200"
// common/env_common.c 中
// uchar default_environment[] = { "bootargs=" CONFIG_BOOTARGS "\0"
// lib_arm/armlinux.c 中
// char *commandline = getenv ("bootargs");
// setup_commandline_tag (bd, commandline);
// strcpy (params->u.cmdline.cmdline, p); // p 为 commandline
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
setup_videolfb_tag ((gd_t *) gd);
setup_end_tag (bd);
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
--------------------------------------------------------------------------------------------
kernel------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
每个物理连续的内存区域被保存为meminfo中的一个元素
也就是说在Linux使用中,整块物理内存可能是不连续的,可能其中某一中间区域是被其他cpu给使用掉了。
uboot 相关的内存tag :
ATAG_INITRD2(在解析cmdline时会被覆盖)
ATAG_CMDLINE
ATAG_MEM(在解析cmdline时会被覆盖)
-------------------CODE ATAG_MEM
start_kernel -> setup_arch -> setup_machine_tags -> parse_tags -> __tagtable(ATAG_MEM, parse_tag_mem32)
parse_tag_mem32 -> arm_add_memory -> 赋值( meminfo.nr_banks membank.bank[x].start membank.bank[x].size membank.bank[x].highmem )
__tagtable(ATAG_MEM, parse_tag_mem32);
当parse_targs找到以ATAG_MEM标识的tag后,调用parse_tag_mem32,把在uboot中填充到tag里的内存起始地址和大小填充到全局变量meminfo数组中。
-------------------CODE ATAG_INITRD2
// 注意 ,该项不是对 meminfo 的修改,而是对 memblock 的修改
start_kernel -> setup_arch -> setup_machine_tags -> parse_tags -> __tagtable(ATAG_INITRD2, parse_tag_initrd2)
__tagtable(ATAG_INITRD2, parse_tag_initrd2);
parse_tag_initrd2
phys_initrd_start = tag->u.initrd.start;
phys_initrd_size = tag->u.initrd.size;
start_kernel -> setup_arch -> arm_memblock_init-> memblock_reserve(phys_initrd_start, phys_initrd_size);
-------------------CODE ATAG_CMDLINE
1. __tagtable(ATAG_CMDLINE, parse_tag_cmdline); 中
strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
2. setup_machine_tags 中
char *from = default_command_line;
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
3. parse_early_param 中
static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
/*start_kernel -> setup_arch -> parse_early_param -> parse_early_options(tmp_cmdline)*/ -> parse_args
-> parse_one -> do_early_param
-> 解析 __setup_start 和 __setup_end 之间的 结构体 (用 __setup 和 early_param 标识)
early_param("mem", early_mem);
do_early_param -> early_mem -> arm_add_memory -> 赋值( meminfo.nr_banks membank.bank[x].start membank.bank[x].size membank.bank[x].highmem )
在内核中解析命令行中以"mem="开头的命令行,找到此命令行后,调用early_mem函数解析命令行。
格式
如果内核需要管理几段不同的内存,可以在uboot的bootarg环境变量中分别指定对应的内存段的起始地址和长度,如下所示:
mem=72M@0xe2000000 mem=128M@0xe8000000
表示内核需要管理两段不连续的内存,第一段起始地址为0xe2000000,大小为72M
另外一段起始地址为0xe8000000,大小为128M
内核通过early_mem函数分别把这两段内存写入到meminfo的两个bank中。
early_param("initrd", early_initrd);
do_early_param -> early_initrd ->
phys_initrd_start = start;
phys_initrd_size = size;
功能:
设置phys_initrd_start 和 phys_initrd_size 的值
格式
为 initrd=0xd0000000,38711808 或者 initrd=0x00800000,16M
// 注意 ,该项不是对 meminfo 的修改,而是对 memblock 的修改
parse_tag_mem32 进了一次
做了一次 arm_add_memory . 其中 mem.start:0X50000000,mem.size:0X10000000
early_initrd 没进入
parse_tag_initrd2 没进入
2. meminfo 检查的过程
start_kernel -> setup_arch -> sanity_check_meminfo
sanity_check_meminfo
___________ _______________
0xC000 0000 0xEC00 0000
struct membank {
phys_addr_t start;
unsigned long size;
unsigned int highmem;
};
struct meminfo {
int nr_banks;
struct membank bank[NR_BANKS];
};
meminfo.nr_banks = 1
meminfo.bank[0].hignmem:0
meminfo.bank[0].start:0x50000000
meminfo.bank[0].size:0x10000000
3. memblock 填充的过程
start_kernel -> setup_arch -> arm_memblock_init
这个过程会将 meminfo 保存的物理内存信息 传给了一个叫struct memblock memblock 的结构变量,后续由它来完成内存区域信息保存的责任。
他会记录
1.所有的内存
2.所有的内存中已使用的部分
1. kernel的text, code段
2. initrd
3. 页表
arm_memblock_init(&meminfo, mdesc);
memblock所管理的实际上是物理内存,CPU要使用这些物理内存,还差一步,那就是虚拟地址,物理地址映射。
stext到start_kernel过程中建立的临时页表是否依然可以沿用?
不能
前面建立的 映射关系 只有 3组 ,其中在 此时(arm_memblock_init执行后), 2组还有意义
1. kernel 的映射
2. atags 的映射
除了这些部分有物理地址 <-> 虚拟地址的 映射
还有大量的 物理地址(在ok6410上总共有128M或256M) 没有创建映射关系,所以需要创建剩余的映射关系
struct memblock {
phys_addr_t current_limit;
phys_addr_t memory_size;
struct memblock_type memory;
struct memblock_type reserved;
};
struct memblock_type {
unsigned long cnt;
unsigned long max;
struct memblock_region *regions;
};
struct memblock_region {
phys_addr_t base;
phys_addr_t size;
};
MEMBLOCK configuration:
memory size = 0x10000000 // 表示内存的总大小
memory.cnt = 0x1 // 表示总的内存由一块连续的地址空间构成
memory[0x0] [0x00000050000000-0x0000005fffffff], 0x10000000 bytes // 第一块内存的[起始地址,结束地址],大小
reserved.cnt = 0x1 // 表示有1块连续的地址空间被保留
reserved[0x0] [0x00000050004000-0x00000050aad573], 0xaa9574 bytes // 第一块被保留的内存的[起始地址,结束地址],大小
第一块保留内存 [0x00000050004000-0x00000050aad573] 由 2小块构成
第一块 : start:0x50008000,size:0xaa5574,表示内核镜像所在空间,memblock_reserve(__pa(_stext), _end - _stext);
第二块 : start:0x50004000,size:0x4000,表示内存页表所在空间,arm_mm_memblock_reserve()->memblock_reserve(__pa(swapper_pg_dir), PTRS_PER_PGD * sizeof(pgd_t));
4. 内存映射的过程
start_kernel -> setup_arch -> paging_init
stext到start_kernel过程中建立的临时页表不包括所有内存的映射.
所以需要创建所有物理内存的映射关系
paging_init
1. 准备向页表空间写入的数据
内核维护了一系列内存类型,不同的内存类型对应了不同的值.
这些值会作为页表项的一部分写到页表项中.
build_mem_type_table
2. 将页表空间清0
prepare_page_table
3. 向页表空间写数据(创建映射关系)
map_lowmem
devicemaps_init
kmap_init
zero_page
当一个进程第一次对一个页进行读操作时,而且该页不在内存中时,kernel应该给进程分配一个新的页帧,更安全的做法是分配一个filled with zero的页帧给进程,这样才能保证别的进程的信息不会被新的进程给读取
所以linux中保留的一个这样filled with zero的页帧,叫zero page,当这种情况发生时,系统就将zero page填入页表中,并标记为不可写。
当进程要对zero page进行写操作时则
copy on write 机制就会被触发。
虽然过程中有创建section的可能,但是前提是虚拟地址后20位为0,因为不满足,所以就没有创建section
建立页表
1. 所有的内存都可以通过bootmem机制来分配和释放.
2. pagging_init创建了页表, 为内核提供了一套可供内核和进程运行的虚拟运行空间
TODO
1. 校验是否为高1G空间都建立了页表
2. 思考低3G怎么建立页表
3. 思考如果小于4G怎么建立页表
4. 思考如果大于4G怎么建立页表
5. zero_page 和 kmap_init 是做什么用的
6. devicemaps_init 是做什么用的
memblock.current_limit:0x60000000
build_mem_type_table,ARRAY_SIZE(mem_types):14
1. 一级页表映射 清0情况
// 1.Clear out all the mappings below the kernel image
prepare_page_table,pmd_off_k(0):0xc0004000
prepare_page_table,pmd_off_k(200000):0xc0004008
...
prepare_page_table,pmd_off_k(bee00000):0xc0006fb8
// 2.skip over XIP kernel
prepare_page_table,pmd_off_k(bf000000):0xc0006fc0
...
prepare_page_table,pmd_off_k(bfe00000):0xc0006ff8
// 3.Clear out all the kernel space mappings, except for the first memory bank, up to the end of the vmalloc region.
prepare_page_table,pmd_off_k(d0000000):0xc0007400
...
prepare_page_table,pmd_off_k(f3e00000):0xc0007cf8
// 4.address after VMALLOC_END
devicemaps_init,pmd_off_k(f4000000):0xc0007d00
devicemaps_init,pmd_off_k(f4200000):0xc0007d08
...
devicemaps_init,pmd_off_k(ffe00000):0xc0007ff8
// 5. 可以看出 C000 0000 - D000 0000 的空间映射(即0xc0007000-0xc00073ff)没有清0
// 因为 0xC000 0000-0xD000 0000 的空间映射 (即0xc0007000-0xc00073ff) 用于 页表 和 内核的存放了
// 但只有 其中的(0xC000 4000 - C0aa d573)的空间映射(即 C000 7000 - C000 7024
) 用于 页表和内核的存放,其中的其他位置都是空闲的
// 所以这个是怎么设置的???
2.一级页表映射填充,二级页表映射填充
// 总共有 13 次 create_mapping,包括1次lowmem映射,1次vectors_page映射,11次 smdk6410_map_io 映射
1次 lowmem 映射
map_lowmem // 低端内存相关的映射 (都是一级SECTION映射,除了map_lowmem,其他都是一级二级页表映射)
md->pfn:0x50000,md->virtual:0xc0000000,md->length:0x10000000,md->type:0x9
map.pfn:0x50000
map.virtual:0xc0000000
map.length:0x10000000
map.type:0x9
1次 vectors_page 映射 // vector 初始化 请查看 early_trap_init
devicemaps_init // vectors_page相关的映射
md->pfn:0x5ffff,md->virtual:0xffff0000,md->length:0x1000,md->type:0x8
map.pfn:0x5ffff
map.virtual:0xffff0000
map.length:0x1000
map.type:0x8
11 个 paging_init-> devicemaps_init -> mdesc->map_io(smdk6410_map_io) 做的映射
smdk6410_map_io -> iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); (10次)
md->pfn:0x7e00f,md->virtual:0xf4100000,md->length:0x1000,md->type:0x0
md->pfn:0x70000,md->virtual:0xf4200000,md->length:0x1000,md->type:0x0
md->pfn:0x7f005,md->virtual:0xf5005000,md->length:0x1000,md->type:0x0
md->pfn:0x71200,md->virtual:0xf4000000,md->length:0x4000,md->type:0x0
md->pfn:0x71300,md->virtual:0xf4010000,md->length:0x4000,md->type:0x0
md->pfn:0x7f006,md->virtual:0xf4300000,md->length:0x4000,md->type:0x0
md->pfn:0x7f008,md->virtual:0xf4500000,md->length:0x1000,md->type:0x0
md->pfn:0x74108,md->virtual:0xf4600000,md->length:0x1000,md->type:0x0
md->pfn:0x7e004,md->virtual:0xf4400000,md->length:0x1000,md->type:0x0
md->pfn:0x7c100,md->virtual:0xf4700000,md->length:0x400,md->type:0x0
s3c_iodesc 结构体
.pfn = __phys_to_pfn(x) x 描述(在s3c6410数据手册中找)
S3C64XX_PA_SYSCON 0x7E00F000 System Controller
S3C64XX_PA_SROM 0x70000000 SROM SFR
S3C_PA_UART 0x7F005000 UART
S3C64XX_PA_VIC0 0x71200000 VIC0
S3C64XX_PA_VIC1 0x71300000 VIC1
S3C_PA_TIMER 0x7F006000 PWM Timer
S3C64XX_PA_GPIO 0x7F008000 GPIO
S3C64XX_PA_MODEM 0x74108000 Direct Host I/F
S3C64XX_PA_WATCHDOG 0x7E004000 Watch-Dog Timer
S3C64XX_PA_USB_HSPHY 0x7C100000 USB OTG SFR
smdk6410_map_io -> iotable_init(mach_desc, size); (1次)
md->pfn:0x77100,md->virtual:0xf5100000,md->length:0x4000,md->type:0x0
smdk6410_iodesc 结构体
.pfn = __phys_to_pfn(x) x 描述
S3C_PA_FB 0x77100000 LCD Controller
页表大概范围
一级段表大概在 0xc0007000 - 0xc00073ff
一级页表大概在 0xc0007420 - 0xc0007fff
二级页表大概在 0xcfff9000 - 0xcfffe7c0
3. 其他
pkmap page
pkmap_page_table:0xccaf8000
一级页表有填充,二级页表给出了地址0xccaf8000,但是二级 页表没有填充
top_pmd
pmd_off_k(0xffff0000) = 0xc0007ff8
zero page
LEVEL2 : PAGE:AT:0xccaf7000,Mapping ZERO
一级页表没有填充,二级页表给出了地址 0xccaf7000, 二级页表填充为0
zero_page:0xccaf7000
zero_page 的值 为 0xccaf7000
Mapping ZERO Page in PAGE : empty_zero_page:0xc0a63ee0
将zero page 纳入到 PAGE 架构中,得出 zero page 在 PAGE 架构中的 struct page 结构体变量地址(0xc0a63ee0)
对应第三阶段 (没有多大意义,暂不考虑,该项无意义)
Memory: 256MB = 256MB total
Memory: 194628k/194628k available, 67516k reserved, 0K highmem
Virtual kernel memory layout:
// 4.address after VMALLOC_END
vector : 0xffff0000 - 0xffff1000 ( 4 kB)
fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)
DMA : 0xff600000 - 0xffe00000 ( 8 MB)
// 3.Clear out all the kernel space mappings, except for the first memory bank, up to the end of the vmalloc region.
vmalloc : 0xd0800000 - 0xf4000000 ( 568 MB)
// 没清0的空间
lowmem : 0xc0000000 - 0xd0000000 ( 256 MB)
.init : 0xc0008000 - 0xc0036000 ( 184 kB)
.text : 0xc0036000 - 0xc0800888 (7979 kB)
.data : 0xc0802000 - 0xc084af50 ( 292 kB)
.bss : 0xc084af74 - 0xc0aad574 (2442 kB)
pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
// 1.Clear out all the mappings below the kernel image
// 2.skip over XIP kernel
modules : 0xbf000000 - 0xbfe00000 ( 14 MB)
5. bootmem 初始化的过程
- struct pglist_data 中的 bootmem 相关变量 和 buddy 相关变量
--------------------------------------------------------------------------------------------------------------
struct pglist_data 中的 bootmem 相关变量 和 buddy 相关变量
按道理来讲 bootmem 不应该 和 buddy 缠到一块去, 但是 内核 就是这么做了
结构体上的缠绕 : 内核在 struct pglist_data 中 放入 了 bootmem 相关变量 和 buddy 相关变量
初始化时的缠绕 : 内核在刚初始化玩 bootmem(相关变量) 就去初始化 buddy(相关变量)了
接下来 写的是 struct pglist_data 的成员
--------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------
bootmem 的建立过程
--------------------------------------------------------------------------------------------------------------
start_kernel -> setup_arch -> paging_init -> bootmem_init-> arm_bootmem_init
boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn);
bitmap = memblock_alloc_base(boot_pages << PAGE_SHIFT, L1_CACHE_BYTES,
__pfn_to_phys(end_pfn));
node_set_online(0);
pgdat = NODE_DATA(0);
init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn);
for_each_memblock(memory, reg) {
unsigned long start = memblock_region_memory_base_pfn(reg);
unsigned long end = memblock_region_memory_end_pfn(reg);
if (end >= end_pfn)
end = end_pfn;
if (start >= end)
break;
free_bootmem(__pfn_to_phys(start), (end - start) << PAGE_SHIFT);
}
for_each_memblock(reserved, reg) {
unsigned long start = memblock_region_reserved_base_pfn(reg);
unsigned long end = memblock_region_reserved_end_pfn(reg);
if (end >= end_pfn)
end = end_pfn;
if (start >= end)
break;
reserve_bootmem(__pfn_to_phys(start),
(end - start) << PAGE_SHIFT, BOOTMEM_DEFAULT);
}
--------------------------------------------------------------------------------------------------------------
至此,bootmem 已经建立完成.
--------------------------------------------------------------------------------------------------------------
// arm_bootmem_free(不包括) 之前 的 bootmem_init
min of mem :0x50000,max of low mem:0x60000,max of high mem:0x60000
需要两个 bootmem manage 0x2 pages(大小 0x00002000,初始化为0xff) 来 存储 0x1000 0000大小 的内存空间
用 memblock_alloc_base 申请出 用于 bootmem 位图 的 空间 bootmem bitmap addr : 0x5caf5000(物理地址) , 虚拟地址(0xccaf5000)
data->node_min_pfn:0x00050000, bdata->node_low_pfn;0x00060000
将 start:0x50000000,size;0x10000000 , end = 6000 0000 , free 地址 注册到 bootmem (memblock初始化完成时就有的)
将 start:0x50004000,size;0x008ca000 , end = 508C E000 , reserve 地址 注册到 bootmem(memblock初始化完成时就有的)
将 start:0x5caf5000,size;0x0350b000 , end = 6000 0000 , reserve 地址 注册到 bootmem
(memblock初始化完成时到注册时,两个时间点间调用memblock_alloc 相关函数 产生的,用于)
(1.存储 二级页表)
(2.存储 bootmem 的 位图信息)
6. bootmem 接口(申请,释放)调用过程
alloc_bootmem
alloc_bootmem_align
alloc_bootmem_nopanic
alloc_bootmem_pages
alloc_bootmem_pages_nopanic
alloc_bootmem_node
alloc_bootmem_node_nopanic
alloc_bootmem_pages_node
alloc_bootmem_pages_node_nopanic
alloc_bootmem_low
alloc_bootmem_low_pages
alloc_bootmem_low_pages_node
free_bootmem
free_bootmem_late
free_bootmem_node
alloc_bootmem
__alloc_bootmem
___alloc_bootmem
___alloc_bootmem_nopanic
list_for_each_entry(bdata, &bdata_list, list)
alloc_bootmem_core
void *region;
__reserve
test_and_set_bit
region = phys_to_virt
return region;
free_bootmem
mark_bootmem
mark_bootmem_node
__free
test_and_clear_bit
其他
- 第二阶段的内存分配器为 bootmem ,但是 建立过程 却有 memblock 的 影子 ,为什么?
因为 memblock 要去取代 bootmem,但是实际上又没有 在一个版本之内完全取代.
memblock 的代码 和 bootmem 的代码混杂到一起了.
实际上 linux-3.0.1的 bootmem 建立的过程中 memblock 只是起到了 过渡 作用, 最后 还是过渡到了 bootmem,bootmem 建立完成后,memblock 就消失了.
在过渡阶段,memblock做了两件事
1. 保存物理内存信息(之后移交给bootmem管理)
2. 根据保存的物理内存信息做映射.(映射做完就是就在那里,不用移交给bootmem)
其实 在 bootmem_init -> arm_bootmem_init -> memblock_alloc_base 用到了 memblock 分配器 来申请内存,此时申请的内存 用于 存储 bootmem 的位图信息.
- 第二阶段的内存分配器为 bootmem ,但是 建立过程 却有 buddy的 影子 ,为什么?
bootmem_init -> arm_bootmem_free 该函数调用前(不包括) 是 bootmem 建立的过程
bootmem_init -> arm_bootmem_free 该函数调用后(包括) 是 bootmem消亡(在bootmem_init函数中bootmem还没有涉及到消亡,不过正在自掘坟墓了),buddy建立的过程
总之, bootmem_init 这个函数
1.完成了 bootmem 建立的最后一步
2.开始了 buddy 的建立过程