原文:Linux内存管理:ARM
Memory Layout以及mmu配置

在内核进行page初始化以及mmu配置之前,首先需要知道整个memory map。

1. ARM Memory Layout

  1. PAGE_OFFSET 

    Start address of Kernel space 

    0xC000_0000

  2. lowmem 

    Kernel direct-mapped RAM region (1:1 mapping) 

    Maximum 896M

  3. HIGH_MEMORY 

    End address of lowmem 

    PAGE_OFFSET + MEMORY_SIZE

  4. pkmap 

    用来把HIGHMEM page 永久映射到 kernel space 

    2MB (这个大小每个平台不一样) 

    kmap() / kunmap()

  5. Page gap 

    To against out-of-bounds errors 

    8MB

  6. vmalloc 

    vmalloc() / ioremap() space

  7. DMA 

    DMA memory mapping region

  8. Fixmap 

    kmap()可能会进入睡眠,所以不能用在中断上下文等地方. 

    所以Fixmap就是用于在中断上下文中把 highmem映射到内核空间的. 

    Mapping HIGHMEM pages atomically 

    kmap_atomic() :Fixmap在使用这个函数,所以可以在中断上下文中使用

  9. Vector 

    CPU vectors are mapped here

  10. Modules 

    Kernel modules inserted via insmod are placed here 

    16MB (14MB, if HIGHMEM is enabled)

在内核初始化的时候,上面说的lowmemory中,还需要去除一些reserved memory。这些预留的内存是供一些外设使用的。下面来看一下预留内存的去除方式以及内核怎么读取预留的。 

(这里不包含具体的内存分配内容,比如slab或者buddy系统等)。

2. 在bootloader判断物理内核地址范围之后,会修改相应的device tree节点。

以高通平台为例,bootloader中有如下函数会负责更新device tree中的memory node

int update_device_tree() {
...
ret = fdt_path_offset(fdt, "/memory");
offset = ret;
ret = target_dev_tree_mem(fdt, offset);
...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

“/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>; };
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后在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, &reg);
size = dt_mem_next_cell(dt_root_size_cells, &reg); 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

3. 内核读到device tree节点之后,会把所有的内存范围保留在memblock里边。然后去掉所有预先保留的内存(比如高通msm平台预留给modem的内存等)。把内核分成lowmemory和highmemory等

在内核启动之后,

start_kernel()->setup_arch()->setup_arch()->sanity_check_meminfo()
  • 1
  • 1

的时候打印的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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

内存分为两个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
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

之后会调用如下函数,读取memory相关的device tree内容,预留modem,audio等相关的内存:

setup_arch()->arm_memblock_init()->dma_contiguous_reserve()->dma_contiguous_early_removal_fixup()
  • 1
  • 1

这时打印的内容为:

<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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

读取的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";
};
};
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

之后在

setup_arch()->arm_memblock_init()->dma_contiguous_reserve()->dma_contiguous_early_removal_fixup()还会调用一次sanity_check_meminfo()函数
  • 1
  • 1

这时打印的内容变成了

<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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

比较两次调用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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

整个内存的示意图 

<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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4. 根据上述处理之后,内核得到可用的内存大小以及范围。然后通过mmu配置等,做内存分页(paging)。

不管是x86架构还是ARM架构,现在大部分CPU访问内存,一般通过MMU来实现虚拟内存和物理内存的转换。 

以下是一个简单的示意图。(如果要详细分析的话,要看MMU分几层,每个page大小怎么配置等等!!参考ARM架构的书) 

在ARM平台,二级页表和三级页表可以选择用。但目前为止没有见过三级页表的,所以略过三级页表,只看一下二级页表的。

//在/kernel/arch/arm/include/asm/pgtable.h文件里边
#ifdef CONFIG_ARM_LPAE
#include <asm/pgtable-3level.h>
#else
#include <asm/pgtable-2level.h>
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

设置一个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。
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

以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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

页表的示意图如下:

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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

可以看一下create_mapping()函数怎么按照物理和对应的虚拟内存,构建页表。

  1. Present flag: 表示pte指向的page地址已经存在于内存当中。linux内核中pte_present()函数来检查present flag。
  2. Accessed flag : Set each time the paging unit addresses the corresponding page frame. This flag may be 

    used by the operating system when selecting pages to be swapped out. The paging unit 

    never resets this flag; this must be done by the operating system
  3. Dirty flag : Applies only to the Page Table entries. It is set each time a write operation is performed on 

    the page frame. As with the Accessed flag, Dirty may be used by the operating system 

    when selecting pages to be swapped out. The paging unit never resets this flag; this must 

    be done by the operating system.
  4. Read/Write flag : Contains the access right (Read/Write or Read) of the page or of the Page Table (see the 

    section “Hardware Protection Scheme” later in this chapter)
  5. User/Supervisor flag : Contains the privilege level required to access the page or Page Table (see the later section 

    “Hardware Protection Scheme”).
  6. PCD and PWT flags : Controls the way the page or Page Table is handled by the hardware cache (see the section 

    “Hardware Cache” later in this chapter).
  7. Page Size flag : Applies only to Page Directory entries. If it is set, the entry refers to a 2 MB- or 4 MB-long 

    page frame (see the following sections).
  8. Global flag : Applies only to Page Table entries. This flag was introduced in the Pentium Pro to prevent 

    frequently used pages from being flushed from the TLB cache (see the section “Translation 

    Lookaside Buffers (TLB)” later in this chapter). It works only if the Page Global Enable (PGE) 

    flag of register cr4 is set.

关于这部分的操作,将在详解函数do_page_fault()函数的时候说明~~

下面举一个例子看一下某个task访问某个虚拟地址是怎么一步一步转成物理地址的。

Linux内核进程,访问的地址都是内核范围之内的,只要做一个简单的偏移就可以在物理地址和虚拟地址之间进行转换,就不多说了。 

用户进程,其page table的地址,都会保存在其task struct的mm或者active_mm的pgd中。可以根据这个地址,按照页表的分配方式来算。 

从用户进程的task_struct中可以知道pgd的地址,当然页表分配方式上面已经讲了,这里是4096,256的分配方式。如果这个进程中,访问的虚拟地址是0x01206000。按照下面的方式可以算出来是0x578DB000。 

按照ARM Developer’s Guide中的图,来看一下是怎么一步一步算出来的。

  1. 虚拟地址是:0x01206000
  2. Translation table base addre就是pgd的地址,也就是一级页表的地址(保存在协处理器CP15:C2中),从上面的task_struct->active_mm->pgd可以看到就是0xDD318000
  3. 虚拟地址0x01206000 * 0xFFF000000 ,这个是取虚拟地址前面12bit,然后右移20位,就是0x12,等于18。这个值要乘以4,加上pgd地址。因为第一级页表有4096个,页表的每一个项是4个字节,所以就要乘以4。故,要取的地址就是0xDD318048。这个地址里边的值就是0x53C6381。这个值乘以0xFFFFFF00就是第二级页表的基地址0x53C6300。
  4. 取0x01206000虚拟地址的中间8bit,右移8位,然后乘以4,加到上面算出来的二级页表基地址0x53C6300这个上面去。算出来的值就是0x53C6318。这个地址的值是0x578DBC7F。
  5. 0x578DBC7F * 0xFFFFF000 加上虚拟地址*0x00000FFF的值,就是0x578DB000。这个就是最终要访问的物理地址。

用户进程的内存管理

 

1. 进程数据结构: task_struct 

2. 进程内存管理数据结构: mm_struct 

mmap: 进程分配的所有内存的链表头 

pgd: page global directory 的地址 

3. 进程分配的内存,由vm_area_struct管理 

vm_start and vm_end: 虚拟内存的开始地址和结束地址

下图是用户进程访问的虚拟内存通过pgd转换成物理地址的示意图,在前面已经详细讲过: 

【ARM-Linux开发】Linux内存管理:ARM Memory Layout以及mmu配置的更多相关文章

  1. ARM裸机开发中内存管理库RT_HEAP的使用

    在使用arm芯片进行裸机开发的时候,很多时候都需要内存管理的功能,我们可以使用自己写的内存管理程序,也可以直接使用标准库,不过我一般比较喜欢标准库,速度快,今天就来说说在C语言环境下怎么样进行内存的动 ...

  2. Linux内核之内存管理

    Linux内核之内存管理 Linux利用的是分段+分页单元把逻辑地址转换为物理地址; RAM的某些部分永久地分配给内核, 并用来存放内核代码以及静态内核数据结构; RAM的其余部分称动态内存(dyna ...

  3. 24小时学通Linux内核之内存管理方式

    昨天分析的进程的代码让自己还在头昏目眩,脑子中这几天都是关于Linux内核的,对于自己出现的一些问题我会继续改正,希望和大家好好分享,共同进步.今天将会讲诉Linux如何追踪和管理用户空间进程的可用内 ...

  4. Linux内核笔记--内存管理之用户态进程内存分配

    内核版本:linux-2.6.11 Linux在加载一个可执行程序的时候做了种种复杂的工作,内存分配是其中非常重要的一环,作为一个linux程序员必然会想要知道这个过程到底是怎么样的,内核源码会告诉你 ...

  5. IOS开发的内存管理

    关于IOS开发的内存管理的文章已经很多了,因此系统的知识点就不写了,这里我写点平时工作遇到的疑问以及解答做个总结吧,相信也会有人遇到相同的疑问呢,欢迎学习IOS的朋友请加ios技术交流群:190956 ...

  6. IOS开发小记-内存管理

    关于IOS开发的内存管理的文章已经很多了,因此系统的知识点就不写了,这里我写点平时工作遇到的疑问以及解答做个总结吧,相信也会有人遇到相同的疑问呢,欢迎学习IOS的朋友请加ios技术交流群:190956 ...

  7. [转载]对iOS开发中内存管理的一点总结与理解

    对iOS开发中内存管理的一点总结与理解   做iOS开发也已经有两年的时间,觉得有必要沉下心去整理一些东西了,特别是一些基础的东西,虽然现在有ARC这种东西,但是我一直也没有去用过,个人觉得对内存操作 ...

  8. 高性能JAVA开发之内存管理

    这几天在找一个程序的bug,主要是java虚拟机内存溢出的问题,调研了一些java内存管理的资料,现整理如下: 一.JVM中的对象生命周期 对象的生命周期一般分为7个阶段:创建阶段,应用阶段,不可视阶 ...

  9. Linux企业级开发技术(6)——libevent企业级开发之内存管理

    默认情况下,libevent使用C库的内存管理函数在堆上分配内存.通过提供malloc.realloc和free的替代函数,可以让libevent使用其他的内存管理器.希望libevent使 用一个更 ...

随机推荐

  1. 使用JXL组件操作Excel和导出文件

    这段时间参与的项目要求做几张Excel报表,由于项目框架使用了jxl组件,所以把jxl组件的详细用法归纳总结一下.本文主要讲述了以下内容: JXL及相关工具简介 如何安装JXL JXL的基本操作 创建 ...

  2. re模块中的非贪婪匹配

    python的re模块中有贪婪匹配和非贪婪匹配之分,当使用*时会匹配零个或多个,使用+时会匹配一个或多个.当使用?在前边特殊符号前时会进行非贪婪匹配,匹配零个或者一个,今天主要讨论非贪婪匹配中存在的坑 ...

  3. Java8-Thread-No.01

    import java.util.concurrent.TimeUnit; public class Threads1 { public static void main(String[] args) ...

  4. C语言学习系列(三)C程序结构

    一.C程序结构 C 程序主要包括以下部分: 预处理器指令 函数 变量 语句 & 表达式 注释 new C program demo: #include <stdio.h> /*预处 ...

  5. 02 | 日志系统:一条SQL更新语句是如何执行的?

    前面我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块.相信你还记得,一条查询语句的执行过程一般是经过连接器.分析器.优化器.执行器等功能模块,最后到达存储引擎. 那么,一条更新语 ...

  6. 【概率论】3-4:二维分布(Bivariate Distribution)

    title: [概率论]3-4:二维分布(Bivariate Distribution) categories: Mathematic Probability keywords: Discrete J ...

  7. 顺序表应用7:最大子段和之分治递归法(SDUT 3664)

    #include <bits/stdc++.h> using namespace std; const int maxn = 50005; int num = 0; struct node ...

  8. Java学习日记——基础篇(一)常识

    JAVA简介 Java的标准 Java是一种语言,一个平台包含JavaSE.JavaEE.JavaME三个版本 JavaSE标准版(属于Java的基础部分,可以开发C/S构架的桌面应用程序) Java ...

  9. Atcoder ABC 139C

    Atcoder ABC 139C 题意: 有 $ n $ 个正方形,选择一个起始位置,使得从这个位置向右的小于等于这个正方形的高度的数量最多. 解法: 简单递推. CODE: #include< ...

  10. es搭建过程会存在的问题 针对6.x

    常见的四个基本错误 错误1 can not run elasticsearch as root 解决方案: 因为安全问题elasticsearch 不让用root用户直接运行,所以要创建新用户 第一步 ...