Linux内核---42.arm 内存初始化2
2016-07-09 10:21
351 查看
几个名词:
Page Global Directory (PGD)
Page Upper Directory (PUD)
Page Middle Directory (PMD)
Page Table (PTE)
Page directory 页目录
Page table entry 页表项
Page Frame Number--> PFN
1. paging_init
setup_arch
--> paging_init
void __init paging_init(struct machine_desc *mdesc)
{
void *zero_page;
//设置memblock.current_limit = lowmem_limit
memblock_set_current_limit(lowmem_limit);
build_mem_type_table(); //1.设置memblock.current_limit
prepare_page_table(); //2.初始化pgd并清除pgd中所有项除映射实际内存地址的项
map_lowmem(); //3.映射lowmem部分,即实际的内存部分
devicemaps_init(mdesc); //4.设置memblock.current_limit
kmap_init(); //
空
top_pmd = pmd_off_k(0xffff0000);
/* allocate the zero page. */
zero_page = early_alloc(PAGE_SIZE);
bootmem_init(); //5.设置memblock.current_limit
empty_zero_page = virt_to_page(zero_page);
__flush_dcache_page(NULL, empty_zero_page);
}
2. page_table初始化
setup_arch
--> paging_init
--> prepare_page_table
这儿是要把pgd初始化:
初始化的过程并不是把pgd中所有的项全部清0,而是清除pgd中表示虚拟地址0-2048的项,
然后空出表示2048-(2048+mem_size)的项,
再清除表示(2048+mem_size - 4096M)的项.
static inline void prepare_page_table(void)
{
//MODULES_VADDR=0x7f000000=2032M, PGDIR_SIZE=0x200000=2048K=2M
//清除0-2032M (0x0-0x7f000000)的页目录表
for (addr = 0; addr < MODULES_VADDR; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
//清除2032M-2048M (0x7f000000-0x80000000)的页目录表//清除2032M-2048M的页目录表
for ( ; addr < PAGE_OFFSET; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
//memory.regions[0].base=0x60000000, size=0x8000000
end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
if (end >= lowmem_limit)
end = lowmem_limit;
//清除(2176M-3968M)0x88000000-0xf8000000处的页目录表 (2176-2048=128M)
for (addr = __phys_to_virt(end); addr < VMALLOC_END; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
}
其中一个pmd可以表示1M的内存空间
A:这个pmd_clear函数为什么要把pmdp的下一个地址即pmdp[1]清除呢?
#define pmd_clear(pmdp) \
do { \
pmdp[0] = __pmd(0); \
pmdp[1] = __pmd(0); \
clean_pmd_entry(pmdp); \
} while (0)
要理解这个需要知道两点:
a. 因为一个pud可以表示2M的内存即上面的PGDIR_SIZE,而一个pmd可表示1M的内存
b. 这儿的pud和pmd的基地址是一样的,即清pmd时也就是清pud
B: 宏的定义
//下面这两个只是去掉第二个参数,没有多大意义
#define pmd_offset(dir, addr) ((pmd_t *)(dir))
#define pud_offset(pgd, start) (pgd)
//pgd_offset_k 就是取得mm->pgd+index的地址
#define pgd_offset_k(addr) pgd_offset(&init_mm, addr)
#define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr))
#define pgd_index(addr) ((addr) >> PGDIR_SHIFT)
3.映射lowmem部分,即实际的内存部分
setup_arch
--> paging_init
--> map_lowmem
映射的过程说白了其实很简单:
假设要将phy_addr=0x60000000 size=0x08000000的内存映射到virtual_addr=0x80000000处
a. 通过pgd_offset查出virtual_addr在pgd中的地址
b. 将phy_addr写到pgd中去
将虚拟地址virtual_addr=0x80000000 转为物理地址的方法是:
a. 通过pgd_offset查出virtual_addr在pgd中的地址
b. 读出pgd中的内容就是物理地址
static void __init map_lowmem(void)
{
struct memblock_region *reg;
for_each_memblock(memory, reg) {
//start=0x60000000, end=0x68000000
phys_addr_t start = reg->base;
phys_addr_t end = start + reg->size;
struct map_desc map;
if (end > lowmem_limit)
end = lowmem_limit;
if (start >= end)
break;
map.pfn = __phys_to_pfn(start); //map.pfn=0x60000
map.virtual = __phys_to_virt(start); //把实际内存0x60000000映射到0x80000000处
map.length = end - start; //映射的长度是0x08000000
map.type = MT_MEMORY; //映射的类型是MEMORY
create_mapping(&map); //映射的主函数
}
}
3.1 对lowmem部分进行映射
setup_arch
--> paging_init
--> map_lowmem
--> create_mapping
static void __init create_mapping(struct map_desc *md)
{
unsigned long addr, length, end;
phys_addr_t phys;
const struct mem_type *type;
pgd_t *pgd;
type = &mem_types[md->type];
addr = md->virtual & PAGE_MASK; //addr=0x80000000
phys = __pfn_to_phys(md->pfn); //phys=0x60000000
length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK)); //length=0x08000000
pgd = pgd_offset_k(addr); //pgd=0x80006000,pgd是虚地址addr所在页目录表的地址
end = addr + length; //end=0x88000000
do {
unsigned long next = pgd_addr_end(addr, end); //下一个页目录表所映射的地址
alloc_init_pud(pgd, addr, next, phys, type);
phys += next - addr;
addr = next;
} while (pgd++, addr != end);
}
3.1.1 映射
setup_arch
--> paging_init
--> map_lowmem
--> create_mapping
--> alloc_init_pud
pgd 页目录表的基地址
addr 要映射的虚地址
phy 要映射的虚地址的物理地址
end 要映射的虚地址+偏移(PUD_SIZE)
static void alloc_init_pud(pgd_t *pgd, unsigned
long addr, unsigned long end,
unsigned long phys, const struct mem_type *type)
{
pud_t *pud = pud_offset(pgd, addr); //虚地址addr在页目录表的地址
unsigned long next;
do {
next = pud_addr_end(addr, end); //next=addr+PUD_SIZE
alloc_init_section(pud, addr, next, phys, type);
phys += next - addr;
} while (pud++, addr = next, addr != end);
}
3.1.1.1 真正的映射过程
setup_arch
--> paging_init
--> map_lowmem
--> create_mapping
--> alloc_init_pud
--> alloc_init_section
#ifndef pud_addr_end
#define pud_addr_end(addr, end) \
({ unsigned long __boundary = ((addr) + PUD_SIZE) & PUD_MASK; \
(__boundary - 1 < (end) - 1)? __boundary: (end); \
})
#endif
pud: 虚地址addr在页目录表的地址
addr: 要映射的虚地址
end: addr+PUD_SIZE
phys: 虚地址addr的物理地址
static void __init alloc_init_section(pud_t *pud, unsigned long addr,
unsigned long end, phys_addr_t phys, const struct
mem_type *type)
{
//#define pmd_offset(dir, addr) ((pmd_t *)(dir))
pmd_t *pmd = pmd_offset(pud, addr);
if (((addr | end | phys) & ~SECTION_MASK) == 0) {
pmd_t *p = pmd;
if (addr & SECTION_SIZE)
pmd++;
//SECTION_SIZE=1<<20而PUD_SIZE=1<<21,所以下面要执行两次
do {
//真正的映射这就这一句,很简单嘛:
//把要映射的地址加上标志填充到pgd中
*pmd = __pmd(phys | type->prot_sect);
phys += SECTION_SIZE;
} while (pmd++, addr += SECTION_SIZE, addr != end);
flush_pmd_entry(p);
} else { //接下来会用到,不过map_lowmem时不执行
alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);
}
}
4. devicemaps_init
会调用memblock_alloc_base从物理内存的顶端分配一些内存
static void __init devicemaps_init(struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
//没有具体看early_alloc干了什么,不过从打印信息看,它的作用是:
//在物理地址的最高处分配了一个page的内存: vectors_page=0x87fff000
vectors_page = early_alloc(PAGE_SIZE); //为中断向量分配一个page的内存
//清除pgd映射了虚拟地址VAMLLOC_END-4G的项
//addr每次步进2M,直到溢出addr=0才结束
for (addr = VMALLOC_END; addr; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
map.pfn = __phys_to_pfn(virt_to_phys(vectors_page));
map.virtual = 0xffff0000; //把中断向量表映射到0xffff0000处
map.length = PAGE_SIZE; //中断向量表要映射的长度是1个page
4K
map.type = MT_HIGH_VECTORS; //映射的类型是
MT_HIGH_VECTORS
create_mapping(&map);
if (mdesc->map_io)
mdesc->map_io();
local_flush_tlb_all();
flush_cache_all();
}
5. bootmem_init
setup_arch
--> paging_init
--> bootmem_init
void __init bootmem_init(void)
{
unsigned long min, max_low, max_high;
max_low = max_high = 0;
//内存开始0x60000000-0x68000000,其页帧号的起始0x60000-0x68000
//页帧号就是把地址 >> PAGE_SHIFT
find_limits(&min, &max_low, &max_high); //5.1
获取页帧号的起始地址
arm_bootmem_init(min, max_low); //5.2
初始化bootmem
arm_memory_present(); //空
sparse_init(); //空
arm_bootmem_free(min, max_low, max_high); //5.3
初始化内存结点
high_memory = __va(((phys_addr_t)max_low << PAGE_SHIFT) - 1) + 1;
max_low_pfn = max_low - PHYS_PFN_OFFSET;
max_pfn = max_high - PHYS_PFN_OFFSET; //将一些值保存在全局变量中
}
5.1 find_limits 获取页帧号的起始地址
setup_arch
--> paging_init
--> bootmem_init
-->
find_limits
static void __init find_limits(unsigned long *min, unsigned
long *max_low, unsigned long *max_high)
{
struct meminfo *mi = &meminfo;
int i;
*min = -1UL;
*max_low = *max_high = 0;
for_each_bank (i, mi) {
struct membank *bank = &mi->bank[i];
unsigned long start, end;
//函数bank_pfn_start即把地址bank >>
PAGE_SHIFT
start = bank_pfn_start(bank); //内存开始bank_start=0x60000000,转为页帧号start=0x60000
end = bank_pfn_end(bank); //内存结束bank_end=0x68000000,转为页帧号end=0x68000
if (*min > start)
*min = start;
if (*max_high < end)
*max_high = end;
if (bank->highmem)
continue;
if (*max_low < end)
*max_low = end;
} //最后min=0x60000, max_low=0x68000, max_high=0x68000
}
5.2 初始化bootmem
setup_arch
--> paging_init
--> bootmem_init
--> arm_bootmem_init
static void __init arm_bootmem_init(unsigned long start_pfn,
unsigned long end_pfn)
{
struct memblock_region *reg;
unsigned int boot_pages;
phys_addr_t bitmap;
pg_data_t *pgdat;
//页帧号的起始:start_pfn=0x60000, end_pfn=0x68000
//所以共有页0x8000个,可以用0x8000/8=0x1000个Byte的位图来表示
//0x1000正好需要1个page来表示
boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn); //5.2.1
//为位图分配内存:
物理地址bitmap=0x67ffa000,
//这儿为什么是0x67ffa000呢?因为memblock_alloc_base这个函数先从物理地址的结束0x68000000处开始分配内存,
//然后依次递减,这个过程好像类似于压栈
bitmap = memblock_alloc_base(boot_pages << PAGE_SHIFT, L1_CACHE_BYTES, __pfn_to_phys(end_pfn));
node_set_online(0); //空函数
pgdat = NODE_DATA(0); //获取pglist_data结构体contig_page_data的地址
init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn); //5.2.2初始化内存位图
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;
//对物理内存在位图中置0,代表free
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的memory在内存位图中置1
reserve_bootmem(__pfn_to_phys(start), (end - start) << PAGE_SHIFT, BOOTMEM_DEFAULT);
}
}
5.2.1 计算出整个内存位图所占的字节数
setup_arch
--> paging_init
--> bootmem_init
--> arm_bootmem_init
--> bootmem_bootmap_pages
unsigned long __init bootmem_bootmap_pages(unsigned long pages)
{
unsigned long bytes = bootmap_bytes(pages);
return PAGE_ALIGN(bytes) >> PAGE_SHIFT; //将表示整个位图需要的字节数,转为page
}
static unsigned long __init bootmap_bytes(unsigned long pages)
{
unsigned long bytes = (pages + 7) / 8; //位图中1个page占1位,那么表示整个位图需要的字节数
return ALIGN(bytes, sizeof(long));
}
5.2.2 初始化内存位图
setup_arch
--> paging_init
--> bootmem_init
--> arm_bootmem_init
--> init_bootmem_node
--> init_bootmem_core
init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn);
pgdat 是 获取pglist_data结构体contig_page_data的地址
freepfn 是内存位图的页帧
start_pfn与end_pfn是内存的起始页帧
unsigned long __init init_bootmem_node(pg_data_t *pgdat, unsigned
long freepfn,
unsigned long startpfn, unsigned long endpfn)
{
return init_bootmem_core(pgdat->bdata, freepfn, startpfn, endpfn);
}
static unsigned long __init init_bootmem_core(bootmem_data_t *bdata,
unsigned long mapstart, unsigned long start, unsigned long end)
{
unsigned long mapsize;
mminit_validate_memmodel_limits(&start, &end); //空函数
bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart)); //将内存位图的首地址由物理地址转为虚地址
bdata->node_min_pfn = start; //
bdata->node_low_pfn = end; //
link_bootmem(bdata); //链表
mapsize = bootmap_bytes(end - start); //上次计算的时储存整个位图需要的页数,这次只计算位图的大小size=0x1000
memset(bdata->node_bootmem_map, 0xff, mapsize); //将整个位图初始化为0xff
return mapsize;
}
list_add_tail只执行这一句
static void __init link_bootmem(bootmem_data_t *bdata)
{
struct list_head *iter;
list_for_each(iter, &bdata_list) {
bootmem_data_t *ent;
ent = list_entry(iter, bootmem_data_t, list);
if (bdata->node_min_pfn < ent->node_min_pfn)
break;
}
list_add_tail(&bdata->list, iter); //只执行了这一句
}
5.3 初始化内存结点
setup_arch
--> paging_init
--> bootmem_init
--> arm_bootmem_free
static void __init arm_bootmem_free(unsigned long min, unsigned long max_low, unsigned
long max_high)
{
//min=0x60000, max_low=0x68000, max_high=0x68000, MAX_NR_ZONES=2
unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
struct memblock_region *reg;
memset(zone_size, 0, sizeof(zone_size));
zone_size[0] = max_low - min;
//zone_size[0]=0x8000
memcpy(zhole_size, zone_size, sizeof(zhole_size));
for_each_memblock(memory, reg) {
unsigned long start = memblock_region_memory_base_pfn(reg); //start=0x60000
unsigned long end = memblock_region_memory_end_pfn(reg); //end=0x68000
if (start < max_low) {
unsigned long low_end = min(end, max_low);
zhole_size[0] -= low_end - start; //zhole_size[0]=0x0,说明没有留下空洞hole
}
}
free_area_init_node(0, zone_size, min, zhole_size); //初始化结点中所有内存区
}
void __paginginit free_area_init_node(int nid, unsigned
long *zones_size,
unsigned long node_start_pfn, unsigned long *zholes_size)
{
pg_data_t *pgdat = NODE_DATA(nid);
//contig_page_data的基地址
pgdat->node_id = nid;
pgdat->node_start_pfn = node_start_pfn;
calculate_node_totalpages(pgdat, zones_size, zholes_size);
alloc_node_mem_map(pgdat);
free_area_init_core(pgdat, zones_size, zholes_size);
}
分析不下去了,需要再好好看一下--> memblock_alloc_base 等
Page Global Directory (PGD)
Page Upper Directory (PUD)
Page Middle Directory (PMD)
Page Table (PTE)
Page directory 页目录
Page table entry 页表项
Page Frame Number--> PFN
1. paging_init
setup_arch
--> paging_init
void __init paging_init(struct machine_desc *mdesc)
{
void *zero_page;
//设置memblock.current_limit = lowmem_limit
memblock_set_current_limit(lowmem_limit);
build_mem_type_table(); //1.设置memblock.current_limit
prepare_page_table(); //2.初始化pgd并清除pgd中所有项除映射实际内存地址的项
map_lowmem(); //3.映射lowmem部分,即实际的内存部分
devicemaps_init(mdesc); //4.设置memblock.current_limit
kmap_init(); //
空
top_pmd = pmd_off_k(0xffff0000);
/* allocate the zero page. */
zero_page = early_alloc(PAGE_SIZE);
bootmem_init(); //5.设置memblock.current_limit
empty_zero_page = virt_to_page(zero_page);
__flush_dcache_page(NULL, empty_zero_page);
}
2. page_table初始化
setup_arch
--> paging_init
--> prepare_page_table
这儿是要把pgd初始化:
初始化的过程并不是把pgd中所有的项全部清0,而是清除pgd中表示虚拟地址0-2048的项,
然后空出表示2048-(2048+mem_size)的项,
再清除表示(2048+mem_size - 4096M)的项.
static inline void prepare_page_table(void)
{
//MODULES_VADDR=0x7f000000=2032M, PGDIR_SIZE=0x200000=2048K=2M
//清除0-2032M (0x0-0x7f000000)的页目录表
for (addr = 0; addr < MODULES_VADDR; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
//清除2032M-2048M (0x7f000000-0x80000000)的页目录表//清除2032M-2048M的页目录表
for ( ; addr < PAGE_OFFSET; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
//memory.regions[0].base=0x60000000, size=0x8000000
end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
if (end >= lowmem_limit)
end = lowmem_limit;
//清除(2176M-3968M)0x88000000-0xf8000000处的页目录表 (2176-2048=128M)
for (addr = __phys_to_virt(end); addr < VMALLOC_END; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
}
其中一个pmd可以表示1M的内存空间
A:这个pmd_clear函数为什么要把pmdp的下一个地址即pmdp[1]清除呢?
#define pmd_clear(pmdp) \
do { \
pmdp[0] = __pmd(0); \
pmdp[1] = __pmd(0); \
clean_pmd_entry(pmdp); \
} while (0)
要理解这个需要知道两点:
a. 因为一个pud可以表示2M的内存即上面的PGDIR_SIZE,而一个pmd可表示1M的内存
b. 这儿的pud和pmd的基地址是一样的,即清pmd时也就是清pud
B: 宏的定义
//下面这两个只是去掉第二个参数,没有多大意义
#define pmd_offset(dir, addr) ((pmd_t *)(dir))
#define pud_offset(pgd, start) (pgd)
//pgd_offset_k 就是取得mm->pgd+index的地址
#define pgd_offset_k(addr) pgd_offset(&init_mm, addr)
#define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr))
#define pgd_index(addr) ((addr) >> PGDIR_SHIFT)
3.映射lowmem部分,即实际的内存部分
setup_arch
--> paging_init
--> map_lowmem
映射的过程说白了其实很简单:
假设要将phy_addr=0x60000000 size=0x08000000的内存映射到virtual_addr=0x80000000处
a. 通过pgd_offset查出virtual_addr在pgd中的地址
b. 将phy_addr写到pgd中去
将虚拟地址virtual_addr=0x80000000 转为物理地址的方法是:
a. 通过pgd_offset查出virtual_addr在pgd中的地址
b. 读出pgd中的内容就是物理地址
static void __init map_lowmem(void)
{
struct memblock_region *reg;
for_each_memblock(memory, reg) {
//start=0x60000000, end=0x68000000
phys_addr_t start = reg->base;
phys_addr_t end = start + reg->size;
struct map_desc map;
if (end > lowmem_limit)
end = lowmem_limit;
if (start >= end)
break;
map.pfn = __phys_to_pfn(start); //map.pfn=0x60000
map.virtual = __phys_to_virt(start); //把实际内存0x60000000映射到0x80000000处
map.length = end - start; //映射的长度是0x08000000
map.type = MT_MEMORY; //映射的类型是MEMORY
create_mapping(&map); //映射的主函数
}
}
3.1 对lowmem部分进行映射
setup_arch
--> paging_init
--> map_lowmem
--> create_mapping
static void __init create_mapping(struct map_desc *md)
{
unsigned long addr, length, end;
phys_addr_t phys;
const struct mem_type *type;
pgd_t *pgd;
type = &mem_types[md->type];
addr = md->virtual & PAGE_MASK; //addr=0x80000000
phys = __pfn_to_phys(md->pfn); //phys=0x60000000
length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK)); //length=0x08000000
pgd = pgd_offset_k(addr); //pgd=0x80006000,pgd是虚地址addr所在页目录表的地址
end = addr + length; //end=0x88000000
do {
unsigned long next = pgd_addr_end(addr, end); //下一个页目录表所映射的地址
alloc_init_pud(pgd, addr, next, phys, type);
phys += next - addr;
addr = next;
} while (pgd++, addr != end);
}
3.1.1 映射
setup_arch
--> paging_init
--> map_lowmem
--> create_mapping
--> alloc_init_pud
pgd 页目录表的基地址
addr 要映射的虚地址
phy 要映射的虚地址的物理地址
end 要映射的虚地址+偏移(PUD_SIZE)
static void alloc_init_pud(pgd_t *pgd, unsigned
long addr, unsigned long end,
unsigned long phys, const struct mem_type *type)
{
pud_t *pud = pud_offset(pgd, addr); //虚地址addr在页目录表的地址
unsigned long next;
do {
next = pud_addr_end(addr, end); //next=addr+PUD_SIZE
alloc_init_section(pud, addr, next, phys, type);
phys += next - addr;
} while (pud++, addr = next, addr != end);
}
3.1.1.1 真正的映射过程
setup_arch
--> paging_init
--> map_lowmem
--> create_mapping
--> alloc_init_pud
--> alloc_init_section
#ifndef pud_addr_end
#define pud_addr_end(addr, end) \
({ unsigned long __boundary = ((addr) + PUD_SIZE) & PUD_MASK; \
(__boundary - 1 < (end) - 1)? __boundary: (end); \
})
#endif
pud: 虚地址addr在页目录表的地址
addr: 要映射的虚地址
end: addr+PUD_SIZE
phys: 虚地址addr的物理地址
static void __init alloc_init_section(pud_t *pud, unsigned long addr,
unsigned long end, phys_addr_t phys, const struct
mem_type *type)
{
//#define pmd_offset(dir, addr) ((pmd_t *)(dir))
pmd_t *pmd = pmd_offset(pud, addr);
if (((addr | end | phys) & ~SECTION_MASK) == 0) {
pmd_t *p = pmd;
if (addr & SECTION_SIZE)
pmd++;
//SECTION_SIZE=1<<20而PUD_SIZE=1<<21,所以下面要执行两次
do {
//真正的映射这就这一句,很简单嘛:
//把要映射的地址加上标志填充到pgd中
*pmd = __pmd(phys | type->prot_sect);
phys += SECTION_SIZE;
} while (pmd++, addr += SECTION_SIZE, addr != end);
flush_pmd_entry(p);
} else { //接下来会用到,不过map_lowmem时不执行
alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);
}
}
4. devicemaps_init
会调用memblock_alloc_base从物理内存的顶端分配一些内存
static void __init devicemaps_init(struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
//没有具体看early_alloc干了什么,不过从打印信息看,它的作用是:
//在物理地址的最高处分配了一个page的内存: vectors_page=0x87fff000
vectors_page = early_alloc(PAGE_SIZE); //为中断向量分配一个page的内存
//清除pgd映射了虚拟地址VAMLLOC_END-4G的项
//addr每次步进2M,直到溢出addr=0才结束
for (addr = VMALLOC_END; addr; addr += PGDIR_SIZE)
pmd_clear(pmd_off_k(addr));
map.pfn = __phys_to_pfn(virt_to_phys(vectors_page));
map.virtual = 0xffff0000; //把中断向量表映射到0xffff0000处
map.length = PAGE_SIZE; //中断向量表要映射的长度是1个page
4K
map.type = MT_HIGH_VECTORS; //映射的类型是
MT_HIGH_VECTORS
create_mapping(&map);
if (mdesc->map_io)
mdesc->map_io();
local_flush_tlb_all();
flush_cache_all();
}
5. bootmem_init
setup_arch
--> paging_init
--> bootmem_init
void __init bootmem_init(void)
{
unsigned long min, max_low, max_high;
max_low = max_high = 0;
//内存开始0x60000000-0x68000000,其页帧号的起始0x60000-0x68000
//页帧号就是把地址 >> PAGE_SHIFT
find_limits(&min, &max_low, &max_high); //5.1
获取页帧号的起始地址
arm_bootmem_init(min, max_low); //5.2
初始化bootmem
arm_memory_present(); //空
sparse_init(); //空
arm_bootmem_free(min, max_low, max_high); //5.3
初始化内存结点
high_memory = __va(((phys_addr_t)max_low << PAGE_SHIFT) - 1) + 1;
max_low_pfn = max_low - PHYS_PFN_OFFSET;
max_pfn = max_high - PHYS_PFN_OFFSET; //将一些值保存在全局变量中
}
5.1 find_limits 获取页帧号的起始地址
setup_arch
--> paging_init
--> bootmem_init
-->
find_limits
static void __init find_limits(unsigned long *min, unsigned
long *max_low, unsigned long *max_high)
{
struct meminfo *mi = &meminfo;
int i;
*min = -1UL;
*max_low = *max_high = 0;
for_each_bank (i, mi) {
struct membank *bank = &mi->bank[i];
unsigned long start, end;
//函数bank_pfn_start即把地址bank >>
PAGE_SHIFT
start = bank_pfn_start(bank); //内存开始bank_start=0x60000000,转为页帧号start=0x60000
end = bank_pfn_end(bank); //内存结束bank_end=0x68000000,转为页帧号end=0x68000
if (*min > start)
*min = start;
if (*max_high < end)
*max_high = end;
if (bank->highmem)
continue;
if (*max_low < end)
*max_low = end;
} //最后min=0x60000, max_low=0x68000, max_high=0x68000
}
5.2 初始化bootmem
setup_arch
--> paging_init
--> bootmem_init
--> arm_bootmem_init
static void __init arm_bootmem_init(unsigned long start_pfn,
unsigned long end_pfn)
{
struct memblock_region *reg;
unsigned int boot_pages;
phys_addr_t bitmap;
pg_data_t *pgdat;
//页帧号的起始:start_pfn=0x60000, end_pfn=0x68000
//所以共有页0x8000个,可以用0x8000/8=0x1000个Byte的位图来表示
//0x1000正好需要1个page来表示
boot_pages = bootmem_bootmap_pages(end_pfn - start_pfn); //5.2.1
//为位图分配内存:
物理地址bitmap=0x67ffa000,
//这儿为什么是0x67ffa000呢?因为memblock_alloc_base这个函数先从物理地址的结束0x68000000处开始分配内存,
//然后依次递减,这个过程好像类似于压栈
bitmap = memblock_alloc_base(boot_pages << PAGE_SHIFT, L1_CACHE_BYTES, __pfn_to_phys(end_pfn));
node_set_online(0); //空函数
pgdat = NODE_DATA(0); //获取pglist_data结构体contig_page_data的地址
init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn); //5.2.2初始化内存位图
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;
//对物理内存在位图中置0,代表free
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的memory在内存位图中置1
reserve_bootmem(__pfn_to_phys(start), (end - start) << PAGE_SHIFT, BOOTMEM_DEFAULT);
}
}
5.2.1 计算出整个内存位图所占的字节数
setup_arch
--> paging_init
--> bootmem_init
--> arm_bootmem_init
--> bootmem_bootmap_pages
unsigned long __init bootmem_bootmap_pages(unsigned long pages)
{
unsigned long bytes = bootmap_bytes(pages);
return PAGE_ALIGN(bytes) >> PAGE_SHIFT; //将表示整个位图需要的字节数,转为page
}
static unsigned long __init bootmap_bytes(unsigned long pages)
{
unsigned long bytes = (pages + 7) / 8; //位图中1个page占1位,那么表示整个位图需要的字节数
return ALIGN(bytes, sizeof(long));
}
5.2.2 初始化内存位图
setup_arch
--> paging_init
--> bootmem_init
--> arm_bootmem_init
--> init_bootmem_node
--> init_bootmem_core
init_bootmem_node(pgdat, __phys_to_pfn(bitmap), start_pfn, end_pfn);
pgdat 是 获取pglist_data结构体contig_page_data的地址
freepfn 是内存位图的页帧
start_pfn与end_pfn是内存的起始页帧
unsigned long __init init_bootmem_node(pg_data_t *pgdat, unsigned
long freepfn,
unsigned long startpfn, unsigned long endpfn)
{
return init_bootmem_core(pgdat->bdata, freepfn, startpfn, endpfn);
}
static unsigned long __init init_bootmem_core(bootmem_data_t *bdata,
unsigned long mapstart, unsigned long start, unsigned long end)
{
unsigned long mapsize;
mminit_validate_memmodel_limits(&start, &end); //空函数
bdata->node_bootmem_map = phys_to_virt(PFN_PHYS(mapstart)); //将内存位图的首地址由物理地址转为虚地址
bdata->node_min_pfn = start; //
bdata->node_low_pfn = end; //
link_bootmem(bdata); //链表
mapsize = bootmap_bytes(end - start); //上次计算的时储存整个位图需要的页数,这次只计算位图的大小size=0x1000
memset(bdata->node_bootmem_map, 0xff, mapsize); //将整个位图初始化为0xff
return mapsize;
}
list_add_tail只执行这一句
static void __init link_bootmem(bootmem_data_t *bdata)
{
struct list_head *iter;
list_for_each(iter, &bdata_list) {
bootmem_data_t *ent;
ent = list_entry(iter, bootmem_data_t, list);
if (bdata->node_min_pfn < ent->node_min_pfn)
break;
}
list_add_tail(&bdata->list, iter); //只执行了这一句
}
5.3 初始化内存结点
setup_arch
--> paging_init
--> bootmem_init
--> arm_bootmem_free
static void __init arm_bootmem_free(unsigned long min, unsigned long max_low, unsigned
long max_high)
{
//min=0x60000, max_low=0x68000, max_high=0x68000, MAX_NR_ZONES=2
unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
struct memblock_region *reg;
memset(zone_size, 0, sizeof(zone_size));
zone_size[0] = max_low - min;
//zone_size[0]=0x8000
memcpy(zhole_size, zone_size, sizeof(zhole_size));
for_each_memblock(memory, reg) {
unsigned long start = memblock_region_memory_base_pfn(reg); //start=0x60000
unsigned long end = memblock_region_memory_end_pfn(reg); //end=0x68000
if (start < max_low) {
unsigned long low_end = min(end, max_low);
zhole_size[0] -= low_end - start; //zhole_size[0]=0x0,说明没有留下空洞hole
}
}
free_area_init_node(0, zone_size, min, zhole_size); //初始化结点中所有内存区
}
void __paginginit free_area_init_node(int nid, unsigned
long *zones_size,
unsigned long node_start_pfn, unsigned long *zholes_size)
{
pg_data_t *pgdat = NODE_DATA(nid);
//contig_page_data的基地址
pgdat->node_id = nid;
pgdat->node_start_pfn = node_start_pfn;
calculate_node_totalpages(pgdat, zones_size, zholes_size);
alloc_node_mem_map(pgdat);
free_area_init_core(pgdat, zones_size, zholes_size);
}
分析不下去了,需要再好好看一下--> memblock_alloc_base 等
相关文章推荐
- [CentOS7]安装mysql遇到的问题
- Linux内核---41.arm 内存初始化
- Linux内核---40.模块加载过程分析
- Linux内核---39.ELF 结构分析
- centos更新163源并升级内核
- Linux小知识
- linux常用命令
- 在linux使用make编译ArduPilot for Pixhawk/PX4 ArduPilot 编译环境搭建
- gcc: error: elf_i386: No such file or directory
- 橙子第一篇文章
- linux菜鸟学习(一)----ls,cd,pwd,mkdir,rmdir,rm
- centos yum升级php5.3.3到最5.6.3
- 【linux GDB】linux下GDB调试器_学习笔记_003
- centos安装php扩展mssql
- centos最常用命令及终端快捷键整理
- ELF文件分析
- CentOS系统内核升级
- Linux基础-----find和重定向命令
- linux下使用eclipse编辑,链接,使用动态库的学习笔记
- Linux下(软件)标准编译安装原理