http://blog.csdn.net/hongzg1982/article/details/47341881
在内核进行page初始化以及mmu配置之前,首先需要知道整个memory map。
PAGE_OFFSET
Start address of Kernel space
0xC000_0000
lowmem
Kernel direct-mapped RAM region (1:1 mapping)
Maximum 896M
HIGH_MEMORY
End address of lowmem
PAGE_OFFSET + MEMORY_SIZE
pkmap
用来把HIGHMEM page 永久映射到 kernel space
2MB (这个大小每个平台不一样)
kmap() / kunmap()
Page gap
To against out-of-bounds errors
8MB
vmalloc
vmalloc() / ioremap() space
DMA
DMA memory mapping region
Fixmap
kmap()可能会进入睡眠,所以不能用在中断上下文等地方.
所以Fixmap就是用于在中断上下文中把 highmem映射到内核空间的.
Mapping HIGHMEM pages atomically
kmap_atomic() :Fixmap在使用这个函数,所以可以在中断上下文中使用
Vector
CPU vectors are mapped here
Modules
Kernel modules inserted via insmod are placed here
16MB (14MB, if HIGHMEM is enabled)
在内核初始化的时候,上面说的lowmemory中,还需要去除一些reserved memory。这些预留的内存是供一些外设使用的。下面来看一下预留内存的去除方式以及内核怎么读取预留的。
(这里不包含具体的内存分配内容,比如slab或者buddy系统等)。
以高通平台为例,bootloader中有如下函数会负责更新device tree中的memory node
int update_device_tree() {
...
ret = fdt_path_offset(fdt, "/memory");
offset = ret;
ret = target_dev_tree_mem(fdt, offset);
...
}
“/memory”一般定义在sekeleton.dtsi,这也是为什么虽然skeleton.dtsi文件里边都是空的内容,但还是需要include这个文件的原因。
//skeleton64.dtsi
/ {
#address-cells = <2>;
#size-cells = <2>;
cpus { };
soc { };
chosen { };
aliases { };
memory { device_type = "memory"; reg = <0 0 0 0>; };
};
然后在kernel里调用如下函数来读取memory大小等赋值给memblock变量:
setup_machine_fdt(){
...
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
...
}
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
{
const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
const __be32 *reg, *endp;
int l;
/* We are scanning "memory" nodes only */
if (type == NULL) {
/*
* The longtrail doesn't have a device_type on the
* /memory node, so look for the node called /memory@0.
*/
if (!IS_ENABLED(CONFIG_PPC32) || depth != 1 || strcmp(uname, "memory@0") != 0)
return 0;
} else if (strcmp(type, "memory") != 0)
return 0;
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
return 0;
endp = reg + (l / sizeof(__be32));
pr_debug("memory scan node %s, reg size %d, data: %x %x %x %x,\n",
uname, l, reg[0], reg[1], reg[2], reg[3]);
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
base = dt_mem_next_cell(dt_root_addr_cells, ®);
size = dt_mem_next_cell(dt_root_size_cells, ®);
if (size == 0)
continue;
pr_debug(" - %llx , %llx\n", (unsigned long long)base,
(unsigned long long)size);
early_init_dt_add_memory_arch(base, size);
}
return 0;
}
在内核启动之后,
start_kernel()->setup_arch()->setup_arch()->sanity_check_meminfo()
的时候打印的memblock的内容为:
<6>[0.000000] [0:wapper:0] sanity_check_meminfo memblock.memory.cnt=2
<6>[0.000000] [0:wapper:0] pys_addr vmalloc_limit = 0xa9c00000
<6>[0.000000] [0:wapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x2fd00000
<6>[0.000000] [0:wapper:0] count = 2 , reg->base =0xb0000000 , reg->size =0x30000000
<6>[0.000000] [0:wapper:0] arm_lowmem_limit =0xa9c00000
内存分为两个CS:
CS1基地址为0x80000000,大小为0x30000000。
CS2基地址为0xb0000000,大小为0x30000000。
所以物理内存开始地址为0x800000000,总的大小为1.5GB。
但中间缺了0x2fd00000到0x30000000的3MB大小的内存,哪里去了??(应该是bootloader改的~~,预留了sec_debug相关的内存)
这段3MB里边,包含了sec_dbg的内容,但大小没有3MB这么大,其余的用作什么了还得查
<0>[0.000000] [0:swapper:0] sec_dbg_setup: str=@0xaff00008
<0>[0.000000] [0:swapper:0] sec_dbg_setup: secdbg_paddr = 0xaff00008
<0>[0.000000] [0:swapper:0] sec_dbg_setup: secdbg_size = 0x80000
之后会调用如下函数,读取memory相关的device tree内容,预留modem,audio等相关的内存:
setup_arch()->arm_memblock_init()->dma_contiguous_reserve()->dma_contiguous_early_removal_fixup()
这时打印的内容为:
<6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xa9c00000
<6>[0.000000] [0:swapper:0] cma: Found external_image__region@0, memory base 0x85500000, size 19 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found modem_adsp_region@0, memory base 0x86800000, size 88 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found pheripheral_region@0, memory base 0x8c000000, size 6 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found venus_region@0, memory base 0x8c600000, size 5 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found secure_region@0, memory base 0x00000000, size 109 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found qseecom_region@0, memory base 0x00000000, size 13 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found audio_region@0, memory base 0x00000000, size 3 MiB, limit 0xffffffff
<6>[0.000000] [0:swapper:0] cma: Found splash_region@8E000000, memory base 0x8e000000, size 20 MiB, limit 0xffffffff
读取的dts文件内容可以找到,,内容如下:
{
memory {
#address-cells = <2>;
#size-cells = <2>;
/* Additionally Reserved 6MB for TIMA and Increased the TZ app size
* by 2MB [total 8 MB ]
*/
external_image_mem: external_image__region@0 {
linux,reserve-contiguous-region;
linux,reserve-region;
linux,remove-completely;
reg = <0x0 0x85500000 0x0 0x01300000>;
label = "external_image_mem";
};
modem_adsp_mem: modem_adsp_region@0 {
linux,reserve-contiguous-region;
linux,reserve-region;
linux,remove-completely;
reg = <0x0 0x86800000 0x0 0x05800000>;
label = "modem_adsp_mem";
};
peripheral_mem: pheripheral_region@0 {
linux,reserve-contiguous-region;
linux,reserve-region;
linux,remove-completely;
reg = <0x0 0x8C000000 0x0 0x0600000>;
label = "peripheral_mem";
};
venus_mem: venus_region@0 {
linux,reserve-contiguous-region;
linux,reserve-region;
linux,remove-completely;
reg = <0x0 0x8C600000 0x0 0x0500000>;
label = "venus_mem";
};
secure_mem: secure_region@0 {
linux,reserve-contiguous-region;
reg = <0 0 0 0x6D00000>;
label = "secure_mem";
};
qseecom_mem: qseecom_region@0 {
linux,reserve-contiguous-region;
reg = <0 0 0 0xD00000>;
label = "qseecom_mem";
};
audio_mem: audio_region@0 {
linux,reserve-contiguous-region;
reg = <0 0 0 0x314000>;
label = "audio_mem";
};
cont_splash_mem: splash_region@8E000000 {
linux,reserve-contiguous-region;
linux,reserve-region;
reg = <0x0 0x8E000000 0x0 0x1400000>;
label = "cont_splash_mem";
};
};
};
之后在
setup_arch()->arm_memblock_init()->dma_contiguous_reserve()->dma_contiguous_early_removal_fixup()还会调用一次sanity_check_meminfo()函数
这时打印的内容变成了
<6>[0.000000] [0:swapper:0] pys_addr vmalloc_limit = 0xa9c00000
<6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x5500000
<6>[0.000000] [0:swapper:0] count = 2 , reg->base =0x8cb00000 , reg->size =0x23200000
<6>[0.000000] [0:swapper:0] count = 3 , reg->base =0xb0000000 , reg->size =0x30000000
<6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xb1200000
比较两次调用sanity_check_meminfo()函数打印的log,可以看到扣除的内存范围,这些里边只有external_image_mem,modem_adsp_mem,peripheral_mem,venus_mem这几个被扣除了。
后面的secure_region,qseecom_region,audio_region,splash_region哪去了??(这部分被ion memory预留!!)
以下是扣除的内容
external_image_mem: 0x85500000~0x86800000 大小为 19MB
modem_adsp_mem :0x86800000 ~0x8C000000 大小为 88MB
peripheral_mem : 0x8C000000 ~ 0x8C600000 大小为6MB
venus_mem:0x8c600000 ~ 0x8cb00000 大小为5MB
secure_mem : 0xd9000000~ 0xe0000000 大小为112MB //这个与上面的109MB相比大小被调整,为什么?
qseecom_region : 0xd8000000 ~ 0xd9000000 大小为16MB////这个与上面的109MB相比大小也被调整,为什么?
audio_mem : 0xd7c00000 大小为4MB//大小被调整
splash_region : 0x8E000000~ 0x8F400000 大小为20MB
default region :0xa9400000 ~ 0xa9c00000 大小为8MB
external_image_mem,modem_adsp_mem,peripheral_mem,venus_mem这些被扣除前后,memblock的
内容如下:
<6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x2fd00000
<6>[0.000000] [0:swapper:0] count = 2 , reg->base =0xb0000000 , reg->size =0x30000000
//第一次打印的时候是这样的,第二次打印就变成下面这样了
<6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x5500000
<6>[0.000000] [0:swapper:0] count = 2 , reg->base =0x8cb00000 , reg->size =0x23200000
<6>[0.000000] [0:swapper:0] count = 3 , reg->base =0xb0000000 , reg->size =0x30000000
<6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xb1200000
//vmalloc被cmdline设置为了340MB,所以vmalloc_limit= 0xb1200000
//(0xff000000 - 0x15400000(340MB)的值,也就是从0xff00000开始减去vmalloc大小得到的值)。
//这个值被调整完之后变成arm_lowmem_limit = 0xa9c00000。
//但第二次被sanity_check_meminfo()函数打印的时候被调整成了0xb1200000,怎么调整的??
//arm_lowmem_limit这个是最终划分Lowmemory和其他vmalloc区域的标准。
//从下面的可以看到lowmemory地址最大的区域就是0xf000000~0xf120000。最大地址就到0xf1200000,和arm_lowmem_limit是一样的。
//highmemory的开始地址是high_memory的值,大小如下:
//high_memory = __va(arm_lowmem_limit - 1) + 1;
//这个值加上VMALLOC_OFFSET即为vmalloc的开始地址
//#define VMALLOC_START ((unsigned long)high_memory + VMALLOC_OFFSET)
//VMALLOC_OFFSET一般为8MB
整个内存的示意图
<6>[0.000000] [0:swapper: 0] Memory: 1243908K/1448960K available (10539K kernel code, 1363K rwdata, 4472K rodata, 1417K init, 5844K bss, 205052K reserved, 632832K highmem)
<6>[0.000000] [0:swapper: 0] Virtual kernel memory layout:
<6>[0.000000] [0:swapper: 0] vector : 0xffff0000 - 0xffff1000 ( 4 kB)
<6>[0.000000] [0:swapper: 0] fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)
<6>[0.000000] [0:swapper: 0] arm_lowmem_limit = 0xf1200000
<6>[0.000000] [0:swapper: 0]
<6>[0.000000] [0:swapper: 0] start_phys : 0xf0000000 end_phys : 0x20000000
<6>[0.000000] [0:swapper: 0] vmalloc : 0xf1200000 - 0xff000000 ( 222 MB)
<6>[0.000000] [0:swapper: 0] lowmem : 0xf0000000 - 0xf1200000 ( 18 MB)
<6>[0.000000] [0:swapper: 0] start_phys : 0xccb00000 end_phys : 0xefd00000
<6>[0.000000] [0:swapper: 0] vmalloc : 0xefd00000 - 0xf0000000 ( 3 MB)
<6>[0.000000] [0:swapper: 0] lowmem : 0xccb00000 - 0xefd00000 ( 562 MB)
<6>[0.000000] [0:swapper: 0] start_phys : 0xc0000000 end_phys : 0xc5500000
<6>[0.000000] [0:swapper: 0] vmalloc : 0xc5500000 - 0xccb00000 ( 118 MB)
<6>[0.000000] [0:swapper: 0] lowmem : 0xc0000000 - 0xc5500000 ( 85 MB)
<6>[0.000000] [0:swapper: 0] pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
<6>[0.000000] [0:swapper: 0] modules : 0xbf000000 - 0xbfe00000 ( 14 MB)
<6>[0.000000] [0:swapper: 0] .text : 0xc0008000 - 0xc0fa8ec4 (16004 kB)
<6>[0.000000] [0:swapper: 0] .init : 0xc1000000 - 0xc1162480 (1418 kB)
<6>[0.000000] [0:swapper: 0] .data : 0xc1164000 - 0xc12b8de4 (1364 kB)
<6>[0.000000] [0:swapper: 0] .bss : 0xc12c1b3c - 0xc1876b78 (5845 kB)
contig_page_data里边node_zones的Normal和HighMem的
zone_start_pfn,spanned_pages正好对应上面的地址。
Normal:
zone_start_pfn = 0x80000000
zone_start_pfn加上spanned_pages的个数,算一下地址正好是arm_lowmem_limit的值
HighMem:
zone_start_pfn的值也是正好等于arm_lowmem_limit的值。
zone_start_pfn加上spanned_pages的值也正好等于0xE0000000。
不管是x86架构还是ARM架构,现在大部分CPU访问内存,一般通过MMU来实现虚拟内存和物理内存的转换。
以下是一个简单的示意图。(如果要详细分析的话,要看MMU分几层,每个page大小怎么配置等等!!参考ARM架构的书)
在ARM平台,二级页表和三级页表可以选择用。但目前为止没有见过三级页表的,所以略过三级页表,只看一下二级页表的。
//在/kernel/arch/arm/include/asm/pgtable.h文件里边
#ifdef CONFIG_ARM_LPAE
#include
#else
#include
#endif
设置一个page大小。这里先略过去寄存器的设置以及page大小类型等。这部分可以参考arm developer’s guide。
先看一下Linux里边在哪里定义page大小的。
//kernel/include/asm-generic/page.h文件里边
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)
//12是最常看到的4k大小的page。
以ARM二级页表为例,一级页表和二级页表的种类有两种。
//page大小为4K,按下面的组织方式都可以map最大4G的内存地址空间。
1. 一级页表是4096,二级页表是256
2. 一级页表是2048,二级页表是512
//在ARM Linux中,分别定义了PTRS_PER_PGD,PTRS_PER_PMD,PTRS_PER_PTE分别表示原本三级的页表,但如果是二级页表的话。这三个值分别定义为如下:
#define PTRS_PER_PTE 512
#define PTRS_PER_PMD 1
#define PTRS_PER_PGD 2048
//上面的值正好对应1级页表2048,二级页表512的组织方式。二级页表中,PUD,PMD没有用。
//一级页表4096,二级页表256这样的配置,就可以定义成如下:
#define PTRS_PER_PTE 256
#define PTRS_PER_PMD 1
#define PTRS_PER_PGD 4096
页表的示意图如下:
create_mapping()函数具体负责页表的生成。
//create_mapping()有几个调用路径
1. devicemaps_init()->create_mapping()
2. map_lowmem()->create_mapping()
3. iotable_init()->create_mapping()
4. debug_ll_io_init()->create_mapping()
可以看一下create_mapping()函数怎么按照物理和对应的虚拟内存,构建页表。
关于这部分的操作,将在详解函数do_page_fault()函数的时候说明~~
Linux内核进程,访问的地址都是内核范围之内的,只要做一个简单的偏移就可以在物理地址和虚拟地址之间进行转换,就不多说了。
用户进程,其page table的地址,都会保存在其task struct的mm或者active_mm的pgd中。可以根据这个地址,按照页表的分配方式来算。
从用户进程的task_struct中可以知道pgd的地址,当然页表分配方式上面已经讲了,这里是4096,256的分配方式。如果这个进程中,访问的虚拟地址是0x01206000。按照下面的方式可以算出来是0x578DB000。
按照ARM Developer’s Guide中的图,来看一下是怎么一步一步算出来的。
1. 进程数据结构: task_struct
2. 进程内存管理数据结构: mm_struct
mmap: 进程分配的所有内存的链表头
pgd: page global directory 的地址
3. 进程分配的内存,由vm_area_struct管理
vm_start and vm_end: 虚拟内存的开始地址和结束地址
下图是用户进程访问的虚拟内存通过pgd转换成物理地址的示意图,在前面已经详细讲过: