linux内存管理 (四) 3 内存管理机制 第一阶段 迈向 第二阶段的过程

第一阶段是个时间点 : 基本堆栈建立完成
第二阶段是个时间点 : 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个过程,其中 45 过程最重要

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 (&params);                                                                                              
	setup_revision_tag (&params);                                                                                 
	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 的修改
  • ok6410-A 实际情况
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
			    	// 1.确定本设备物理内存的各个node各个bank中到底有没有高端内存,根据是否存在高端内存决定每个bank的highmem成员值
			    	// 2.将那些部分重叠highmem区域的内存区域分开,大大简化了以后的工作
			    	// 3.赋值 lowmem_limit,所有region中非高端内存的end的最大值,并 将此值赋值 memblock.current_limit
			    	// 4.检查cache 是否支持高端内存(如果cache_is_vipt_aliasing,则不支持高端内存),计算 普通内存和可用的高端内存 数量 为 meminfo.nr_banks.
			    	// 判断有高端内存(highmem)的依据 : __va(bank->start) >= vmalloc_min || __va(bank->start) < (void *)PAGE_OFFSET
			    	// vmalloc_min = VMALLOC_END - SZ_128M = 0xF4000000UL - 0x08000000 = EC00 0000
			    	// PAGE_OFFSET = 0xC000 0000
	         				___________                 	_______________
			    	//      高端内存	  |    	非高端内存  		|     高端内存
			    					0xC000 0000					0xEC00 0000
			    	// highmem 用于 vmalloc

	// 总之做以下事情  
	// 1. 赋值 memblock.current_limit
	// 2. 根据 是否 包括 highmem 对 meminfo.bank 进行 recipe , 可能会 增加 meminfo.bank的数量
	// 3. 赋值 meminfo.nr_banks
	// 4. 根据 包括 highmem  && cache 组织方式不为 VIPT aliasing , 赋值 meminfo.nr_banks 为 1
	

struct membank {                                                                    
    phys_addr_t start;                                                              
    unsigned long size;                                                             
    unsigned int highmem;                                                           
};                                                                                  
                                                                                    
struct meminfo {                                                                    
    int nr_banks;                                                                   
    struct membank bank[NR_BANKS];                                                  
}; 

  • ok6410-A 实际情况
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);
    	// 1. 对 meminfo.bank 进行排序(根据bank[i].start对应的页帧号码排序)
    	// 2. 对 memblock 全局变量赋值
    	// 3. 调用 memblock_add->memblock_add_region 把 meminfo 中的内存信息 添加 到 memblock.memory 中
    	// 4. 调用 memblock_reserve->memblock_add_region 把 以下 中的内存信息 添加 到 memblock.reserved 中
    	 		// 4.1 kernel : start : __pa(_stext)  size : _end - _stext
    	 		// 4.2 initrd : phys_initrd_start, phys_initrd_size
    	 		// 4.3 swapper_pg_dir : __pa(swapper_pg_dir), PTRS_PER_PGD * sizeof(pgd_t)
    	// 5. 至此,内存已经注册完了,赋值memblock.memory_size
    	// 6. dump(展示,打印) 出 MEMBLOCK 的配置

	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;    /* Updated by memblock_analyze() */                 
    struct memblock_type memory;                                                 
    struct memblock_type reserved;                                               
};


struct memblock_type {                                                              
    unsigned long cnt;  /* number of regions */                                     
    unsigned long max;  /* size of the allocated array */                           
    struct memblock_region *regions;                                                
};

struct memblock_region {                                                            
    phys_addr_t base;                                                               
    phys_addr_t size;                                                               
};

  • ok6410-A 实际情况
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
			    // 1. (之前建立的)段表物理地址 5000 7000 - 5000 7024               ,关系: 5000 0000 - 5090 0000 : C000 0000- C090 0000
			    // 2. 段表物理地址 5000 4000 - 5000 4000 + 5FF*4       ,关系: x         - x         : 0000 0000- C000 0000
			    // 3. 段表物理地址 5000 4000+608*4 - 5000 4000 + 7A0*4 ,关系: x         - x         : C100 0000- F400 0000
		3. 向页表空间写数据(创建映射关系)
			map_lowmem
			// 根据 memblock 中的每个 memory region, create_mapping
			// 综上,共创建 64个一级表,64*1024个二级表.
			// 一级表范围在 0xC000 4600 - 0xC000 4600 + 64*4 
			// 二级表范围在 memblock_alloc 申请出来的空间,该空间在直接映射空间.(C000 0000 - C090 0000)
			// 目的是能够通过 虚拟地址 访问 物理地址(5000 0000 - 5800 0000)
			devicemaps_init
			// 做 machine vectors 的 create_mapping
	           // 创建1个一级页表,在 C010 3FF0
	           // 创建8个二级页表
	        
	        // 目的是 将 虚拟地址 0xffff0000 与 物理地址建立联系
	        // 能够通过 虚拟地址 访问 vectors_page
	        // 但此时 该 物理地址 空间内还没填充 vectors_page
			
			// memblock 中包括 低端内存和高端内存(vmalloc)
			// 这个过程 做了 低端内存的映射 , 采用的是 线性映射
			// 也做了 高端内存的映射,采用 高端内存映射技术2:非连续内存映射
			kmap_init
			// 初始化了一级表
			// 申请了二级表的空间,位置在 pkmap_page_table
			//  pkmap_page_table 保存额 PKMAP_BASE 对应的页表项地址,在 该地址之后,还有512-1或1024-1个页表项用于内存映射
			// 采用 高端内存映射技术2:永久内存映射
			zero_page
			// 申请了二级表的空间,位置在 zero_page, 对应的 struct page 地址empty_zero_page 
			// empty_zero_page is a special page that is used for  zero-initialized data and COW

			当一个进程第一次对一个页进行读操作时,而且该页不在内存中时,kernel应该给进程分配一个新的页帧,更安全的做法是分配一个filled with zero的页帧给进程,这样才能保证别的进程的信息不会被新的进程给读取
			所以linux中保留的一个这样filled with zero的页帧,叫zero page,当这种情况发生时,系统就将zero page填入页表中,并标记为不可写。
			当进程要对zero page进行写操作时则
copy on write 机制就会被触发。

	虽然过程中有创建section的可能,但是前提是虚拟地址后20位为0,因为不满足,所以就没有创建section

	建立页表
		// paging_init 做完之后 我们还不能用 alloc_bootmem 安全的进行内存的分配,因为 alloc_bootmem 要去索引 bdata_list 链表, 而 在 bootmem_init 中 才将  NODE_DATA(0) 即(contig_page_data) 插入 bdata_list 中
		1. 所有的内存都可以通过bootmem机制来分配和释放.
		2. pagging_init创建了页表, 为内核提供了一套可供内核和进程运行的虚拟运行空间


TODO
	1. 校验是否为高1G空间都建立了页表
	2. 思考低3G怎么建立页表
	3. 思考如果小于4G怎么建立页表
	4. 思考如果大于4G怎么建立页表
	5. zero_page 和 kmap_init 是做什么用的
	6. devicemaps_init 是做什么用的

  • ok6410-A 实际情况
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 是通过 操作  struct pglist_data 结构体 来实现 bootmem机制的
	// 在初始化过程中,主要的工作就是 初始化 struct pglist_data contig_page_data 变量
	// contig_page_data  中的 bdata node相关 zone相关 成员
	// 初始化 bdata 成员 的时候 做了个动作 ,list_add_tail(&bdata->list, iter); // iter 为 bdata_list 链表中的节点地址 
	// static struct list_head bdata_list __initdata = LIST_HEAD_INIT(bdata_list);
	// bdata->node_bootmem_map 是 位图数据 起始地址
	// struct pglist_data 中所有的成员都要初始化
	// 1. node_zones
	// 2. bdata
	// 3. node_mem_map
  • bootmem 的建立过程
--------------------------------------------------------------------------------------------------------------
			bootmem 的建立过程 
--------------------------------------------------------------------------------------------------------------
start_kernel -> setup_arch -> paging_init -> bootmem_init-> arm_bootmem_init
	/*
	 * Allocate the bootmem bitmap page.  This must be in a region
	 * of memory which has already been mapped.
	 */
	// 计算出 位图数据大小 对应的 page 数目
	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));

	/*
	 * Initialise the bootmem allocator, handing the
	 * memory banks over to bootmem.
	 */
	// 当前 MAX_NUMNODES 为1 , node_set_online(0) 什么都不做
	node_set_online(0);
	// pgdat = &contig_page_data . contig_page_data 为 全局变量
	//struct pglist_data __refdata contig_page_data = {
	//	.bdata = &bootmem_node_data[0]
	//};
	pgdat = NODE_DATA(0);
	// pgdat 为 &contig_page_data
	// __phys_to_pfn(bitmap) 为 位图数据 对应的 pfn
	// start_pfn 为 位图数据管理的内存的 起始 pfn
	// end_pfn   为 位图数据管理的内存的 最后一个 pfn
	//
	// init_bootmem_node 要做的事 是 init_bootmem_node -> init_bootmem_core
		// A.初始化 contig_page_data->bdata 中的 成员变量
		// 		A.1. node_bootmem_map 	// bootmem 位图数据 的起始物理地址
		// 		A.2. node_min_pfn 		// bootmem 管理的 低端内存的起点
		// 		A.3. node_low_pfn			// bootmem 管理的 低端内存的终点
		// 		A.4. list 				// bootmem 可能有多个不连续的内存块,这些不连续的内存块需要用 链表链起来
		// B初始化位图数据中的每一位 为 1,表示内存不可用
	init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn);

	// 解析 memblock 中的 memory 成员变量,得到 未使用的内存信息
	// 然后 调用 free_bootmem -> mark_bootmem(start, end, 0, 0) -> __free -> test_and_clear_bit , 将位图数据的位 置0,表示内存可用
	/* Free the lowmem regions from memblock into bootmem. */
	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);
	}
	// 解析 memblock 中的 reserved 成员变量,得到 已使用的内存信息
	// 然后 reserve_bootmem mark_bootmem(start, end, 1, 0) -> __reserve -> test_and_set_bit , 将位图数据的位 置1,表示内存不可用
	/* Reserve the lowmem memblock reserved regions in bootmem. */
	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 已经建立完成.
--------------------------------------------------------------------------------------------------------------
  • ok6410-A 实际情况
// 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 的建立过程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值