概述
问题1:在系统启动是,arm linux内核如何知道系统中有多大的内存空间?
物理内存大小定义在设备树的memory节点中,系统启动时会通过dtb解析memory节点,从而获得内存大小。
内存大小定义
在arm vexpress平台中,内存定义在vexpress-v2p-ca9.dts中。起始地址为0x60000000,大小为0x40000000(1G)。设备树语法参考链接Device Tree 详解 - 魅族内核团队
start_kernel->setup_arch->setup_machine_fdt->early_init_dt_scan_nodes->of_scan_flat_dt->early_init_dt_scan_memory
在函数early_init_dt_scan_memory会解析memory节点
/**
* early_init_dt_scan_memory - Look for an parse memory nodes
*/
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
{
..........................
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
..............................................
printk(KERN_EMERG "base %llx, size %llxn", base, size);
early_init_dt_add_memory_arch(base, size);
}
return 0;
}
输出的刚好是dts里面定义的起始地址和大小
sudo qemu-system-arm -M vexpress-a9 -m 1024M。如果-m 512M
则会输出base 60000000, size 20000000(512M)
感觉是二者取较小值
物理内存映射
page_init来完成系统分页机制的初始化工作,建立页表,从而内核可以完成虚拟地址到物理地址的映射关系
void __init paging_init(const struct machine_desc *mdesc)
{
void *zero_page;
printk(KERN_EMERG "rnpaging_init beginn");
/* 给静态全局变量mem_types赋值 */
build_mem_type_table();
prepare_page_table();//将页表项清零
map_lowmem();//初始化页表,真正创建页表,重新建立从物理地址起始点到high_mem的起始点的一一映射
/* 建立DMA映射表 */
dma_contiguous_remap();
/* 为设备IO空间和中断向量表创建页表,并刷新TLB和缓存 */
devicemaps_init(mdesc);
/* 进行永久内存映射的初始化,存储在pkmap_page_table中 */
kmap_init();
/* TCM初始化,TCM是一个固定大小的RAM,紧密地耦合至处理器内核,提供与cache相当的性能 */
tcm_init();
top_pmd = pmd_off_k(0xffff0000);
/* allocate the zero page. */
zero_page = early_alloc(PAGE_SIZE);
/* bootmem_init初始化内存管理 */
bootmem_init();
/* 分配一个0页,该页用于写时复制机制。 */
empty_zero_page = virt_to_page(zero_page);
__flush_dcache_page(NULL, empty_zero_page);
}
prepare_page_table:把页表项清零
static inline void prepare_page_table(void)
{
unsigned long addr;
phys_addr_t end;
/*
* Clear out all the mappings below the kernel image.
*/
for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE)
pmd_clear(pmd_off_k(addr));
#ifdef CONFIG_XIP_KERNEL
/* The XIP kernel is mapped in the module area -- skip over it */
addr = ((unsigned long)_etext + PMD_SIZE - 1) & PMD_MASK;
#endif
for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE)
pmd_clear(pmd_off_k(addr));
/*
* Find the end of the first block of lowmem.
*/
end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
if (end >= arm_lowmem_limit)
end = arm_lowmem_limit;
/*
* Clear out all the kernel space mappings, except for the first
* memory bank, up to the vmalloc region.
*/
for (addr = __phys_to_virt(end);
addr < VMALLOC_START; addr += PMD_SIZE)
pmd_clear(pmd_off_k(addr));
printk(KERN_EMERG "rn MODULES_VADDR %lx, PAGE_OFFSET %lx, VMALLOC_START %lxn",
(unsigned long)MODULES_VADDR, (unsigned long)PAGE_OFFSET, (unsigned long)VMALLOC_START);
}
对三段地址调用pmd_clear()清除一级页表的内容
0x0--MODULES_VADDR ;
MODULES_VADDR--PAGE_OFFSET;
arm_lowmem_limit--VMALLOC_START
arm_lowmem_limit a0000000
MODULES_VADDR 7f000000, PAGE_OFFSET 80000000, VMALLOC_START c0800000
暂时不知道这几个地址是什么意思,难道这个地址范围内都是页表??
PAGE_OFFSET:对于内核空间,给定一个虚拟地址x,其物理地址为x-PAGE_OFFSET。1、感觉这个也只是针对线性映射区吧。2、那这个不说明内核虚拟地址起始位置为PAGE_OFFSET=0x80000000(2G)??。那4G地址空间就是2:2进行划分的
map_lowmem:初始化页表
static void __init map_lowmem(void)
{
struct memblock_region *reg;
unsigned long kernel_x_start = round_down(__pa(_stext), SECTION_SIZE);
unsigned long kernel_x_end = round_up(__pa(__init_end), SECTION_SIZE);
printk(KERN_EMERG "rn arm_lowmem_limit %lx", arm_lowmem_limit);
printk(KERN_EMERG "rn kernel_x_start %lx,kernel_x_end %lx", kernel_x_start, kernel_x_end);
/* Map all the lowmem memory banks. */
for_each_memblock(memory, reg) {
phys_addr_t start = reg->base;
phys_addr_t end = start + reg->size;
struct map_desc map;
if (end > arm_lowmem_limit)
end = arm_lowmem_limit;
if (start >= end)
break;
printk(KERN_EMERG "rn start %lx, end %lx, size %lxn", start, end, reg->size);
if (end < kernel_x_start || start >= kernel_x_end) {
/* 将物理地址转为物理page number, 这里是4k为一页 */
map.pfn = __phys_to_pfn(start);
map.virtual = __phys_to_virt(start);
map.length = end - start;
map.type = MT_MEMORY_RWX;
/* create_mapping进行线性映射 */
create_mapping(&map);
} else {
/* This better cover the entire kernel */
if (start < kernel_x_start) {
map.pfn = __phys_to_pfn(start);
map.virtual = __phys_to_virt(start);
map.length = kernel_x_start - start;
map.type = MT_MEMORY_RW;
create_mapping(&map);
}
/* 映射kernel image区域 */
map.pfn = __phys_to_pfn(kernel_x_start);
map.virtual = __phys_to_virt(kernel_x_start);
map.length = kernel_x_end - kernel_x_start;
map.type = MT_MEMORY_RWX;
create_mapping(&map);
/* 书上说映射低端内存,为什么是低端内存呢 */
if (kernel_x_end < end) {
map.pfn = __phys_to_pfn(kernel_x_end);
map.virtual = __phys_to_virt(kernel_x_end);
map.length = end - kernel_x_end;
map.type = MT_MEMORY_RW;
create_mapping(&map);
}
}
}
}
只循环了一次,memblock只有一个。起始地址0x60000000, 长度0x40000000
#define for_each_memblock(memblock_type, region)
for (region = memblock.memblock_type.regions;
region < (memblock.memblock_type.regions + memblock.memblock_type.cnt);
region++)
可以看到这里的 map_lowmem函数里for_each_memblock(memory, reg)就是在使用之前memblock子系统里面的动态内存部分。在关于memblock子系统部分,关于memory_type为memory,即动态内存的部分,也只加入了一个区域,即dts里面定义的memory节点。因此这里也只循环了一次
end = start+size = 0xa0000000(这里的地址究竟是啥地址哦,虚拟地址or物理地址??).刚好是dts里面定义的memory节点。__phys_to_pfn感觉这里的地址都是物理地址。
但是物理内存只有1G,物理地址范围不是0-1G嘛?为什么会有0x60000000这种物理地址呢
我看网上说,memory节点里面的reg = <0x60000000 0x40000000>;表示物理内存起始地址,以及大小。因此物理地址范围[0x60000000,0xa0000000]。看来物理地址不一定是从0x0开始
低端内存:32位CPU能够访问4G地址空间。linux将4G地址空间为了用户和内核地址空间,一般是3:1(这个比例可以调整2:2)。内核只能访问1G的地址空间(3G-4G)。
如果物理内存大于1G.那么内核需要如何管理地址内存呢?
因此linux把内核1G的虚拟地址空间分为了两个部分。线性映射区,和非线性映射区。线性映射的物理内存,也成为了低端内存,剩下的内存被称为高端内存。线性区的内存是提前映射好了。高端内存是在使用时动态映射,使用非线性映射区去管理大于(1G,896或者说,线性映射区管理不了的高端内存)。因此,内存的前896M(物理内存)(低端内存)被一一映射,即线性映射,到了内核地址空间3G--3G+896M(虚拟地址空间)的部分,剩下的128M的虚拟地址空间,用kmap动态映射,管理超过1G的高端内存
static void __init map_lowmem(void)
{
....................
if (end < kernel_x_start || start >= kernel_x_end) {
/*不走这里*/.................
} else {
/* This better cover the entire kernel */
if (start < kernel_x_start) {
/* 也不走这里 */................................
}
/* 映射kernel image区域 */
map.pfn = __phys_to_pfn(kernel_x_start);
map.virtual = __phys_to_virt(kernel_x_start);
map.length = kernel_x_end - kernel_x_start;
map.type = MT_MEMORY_RWX;
create_mapping(&map);
/*
书上说映射低端内存,为什么是低端内存呢?
因为这部分内存是进行线性映射的,因此属于低端内存(zone_dma & zone_normal )
*/
if (kernel_x_end < end) {
map.pfn = __phys_to_pfn(kernel_x_end);
map.virtual = __phys_to_virt(kernel_x_end);
map.length = end - kernel_x_end;
map.type = MT_MEMORY_RW;
create_mapping(&map);
}
}
}
}
map_lowmem:
如上面代码所示,该函数会对两个区域进行映射
区间1:[kernel_x_start, kernel_x_end](物理地址)
物理地址:kernel_x_start(0x60000000)-- kernel_x_end (0x60600000) 大小共6M
虚拟地址:0x80000000-0x80600000
区间2:[kernel_x_end, end](物理地址)
物理地址:kernel_x_end (0x60600000)--end(0xa0000000)。共(1018M)
这里感觉和书上写的不一样。那这1G的物理内存全部都拿去做线性映射了,没有所谓的高端内存
下图里面的lowmem刚好也是1024M
书上的arm_lowmem_limit=0x8f800000,意味着低端内存大小为0x8f800000-0x60000000=760M
那高端内存还有1024-760 = 264M.
arm_lowmem_limit(是个物理地址):那这个值感觉就是低端内存的上界。高于arm_lowmem_limit都是属于高端内存。
最后
以上就是知性摩托为你收集整理的物理内存映射的全部内容,希望文章能够帮你解决物理内存映射所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复