zoukankan      html  css  js  c++  java
  • Linux内存管理学习2 —— head.S中的段页表的建立

    作者

    彭东林

    pengdonglin137@163.com

    平台

    TQ2440

    Qemu+vexpress-ca9

    Linux-4.10.17

    正文

    继续分析head.S:

    此时r2存放的是设备树镜像的物理起始地址,r8是物理内存的起始地址,r9是从CP15的C0中读到的cpu id,r10是与该cpu id匹配的proc_info_list的物理地址

    TQ2440:

    r8: 0x3000_0000,r9: 0x41129200

    vexpress:

    r8: 0x6000_0000,r9: 0x414FC091

    8、第30行调用__create_page_tables建立段式页表。

    下面是__create_page_tables的定义: 其中涉及到几个宏定义:

    PG_DIR_SIZE: 0x4000

    PROCINFO_MM_MMUFLAGS:0x8

    SECTION_SHIFT:20

    PMD_ORDER:2

     1 /*
     2  * Setup the initial page tables.  We only setup the barest
     3  * amount which are required to get the kernel running, which
     4  * generally means mapping in the kernel code.
     5  *
     6  * r8 = phys_offset, r9 = cpuid, r10 = procinfo
     7  *
     8  * Returns:
     9  *  r0, r3, r5-r7 corrupted
    10  *  r4 = physical page table address
    11  */
    12 __create_page_tables:
    13     pgtbl    r4, r8                @ page table address
    14 
    15     /*
    16      * Clear the swapper page table
    17      */
    18     mov    r0, r4
    19     mov    r3, #0
    20     add    r6, r0, #PG_DIR_SIZE
    21 1:    str    r3, [r0], #4
    22     str    r3, [r0], #4
    23     str    r3, [r0], #4
    24     str    r3, [r0], #4
    25     teq    r0, r6
    26     bne    1b
    27 
    28     ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
    29 
    30     /*
    31      * Create identity mapping to cater for __enable_mmu.
    32      * This identity mapping will be removed by paging_init().
    33      */
    34     adr    r0, __turn_mmu_on_loc
    35     ldmia    r0, {r3, r5, r6}
    36     sub    r0, r0, r3            @ virt->phys offset
    37     add    r5, r5, r0            @ phys __turn_mmu_on
    38     add    r6, r6, r0            @ phys __turn_mmu_on_end
    39     mov    r5, r5, lsr #SECTION_SHIFT
    40     mov    r6, r6, lsr #SECTION_SHIFT
    41 
    42 1:    orr    r3, r7, r5, lsl #SECTION_SHIFT    @ flags + kernel base
    43     str    r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping
    44     cmp    r5, r6
    45     addlo    r5, r5, #1            @ next section
    46     blo    1b
    47 
    48     /*
    49      * Map our RAM from the start to the end of the kernel .bss section.
    50      */
    51     add    r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
    52     ldr    r6, =(_end - 1)
    53     orr    r3, r8, r7
    54     add    r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
    55 1:    str    r3, [r0], #1 << PMD_ORDER
    56     add    r3, r3, #1 << SECTION_SHIFT
    57     cmp    r0, r6
    58     bls    1b
    59 
    60     /*
    61      * Then map boot params address in r2 if specified.
    62      * We map 2 sections in case the ATAGs/DTB crosses a section boundary.
    63      */
    64     mov    r0, r2, lsr #SECTION_SHIFT
    65     movs    r0, r0, lsl #SECTION_SHIFT
    66     subne    r3, r0, r8
    67     addne    r3, r3, #PAGE_OFFSET
    68     addne    r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
    69     orrne    r6, r7, r0
    70     strne    r6, [r3], #1 << PMD_ORDER
    71     addne    r6, r6, #1 << SECTION_SHIFT
    72     strne    r6, [r3]
    73 
    74     ret    lr
    75 ENDPROC(__create_page_tables)
    76     .ltorg
    77     .align
    78 __turn_mmu_on_loc:
    79     .long    .
    80     .long    __turn_mmu_on
    81     .long    __turn_mmu_on_end

    第13行,计算段式页表在物理内存当中的起始地址,方法如下:

        .macro    pgtbl, rd, phys
        add    
    d, phys, #TEXT_OFFSET
        sub    
    d, 
    d, #PG_DIR_SIZE
        .endm

    r4 =  r8 + 0x8000 - 0x4000,通过前文知道r8存放的是物理内存的起始地址,所以对于TQ2440,r4为0x3000_4000,对于vexpress是0x6000_4000,也就是段式页表占据了kernel代码段(0x3000_8000或0x6000_8000)之前的16KB的物理内存

    第18到第26行,将段式页表的占据的那16KB的物理内存清零

    第28行,加载MMU Flags到r7中。通过之前的分析知道,r10中存放的是与cpu id匹配的proc_info_list的首地址,这里访问的是proc_info_list->__cpu_mm_mmu_flags。

    对于TQ2440,是0x00000c1e, 对于vexpress来说是0x00011c0e。下面以TQ2440为例说明。参考手册 ARM920T Technical Reference Manual 的 3.3.3 Level one descriptor

    对于段式页表,bit[1:0]是0x10,对于TQ2440的0xC1E,满足这一条件,所以是段式页表。具体段式页表的页表项的含义如下:

    每个bit位的含义如下:

     对于段式页表,只需要一级,地址转换过程如下:

    理解了段式页表地址翻译的过程,也就容易理解接下来建立段式页表的代码了。

    可以现在start_kernel的入口处将段式页表中的内容打印出来:

    diff --git a/init/main.c b/init/main.c
    index 09beb7f..fa6edd6 100644
    --- a/init/main.c
    +++ b/init/main.c
    @@ -484,6 +484,18 @@ asmlinkage __visible void __init start_kernel(void)
         char *command_line;
         char *after_dashes;
     
    +
    +    unsigned int i, addr, value;
    +    for (i=0; i < 0x1000; i++) {
    +        addr = 0xC0004000 + i*4;
    +        value = *((unsigned int *)addr);
    +        if (value) {
    +            pr_notice("0x%04x: hw: 0x%08x, virt: 0x%08x, value: 0x%08x map: [0x%08x ~ 0x%08x ==> 0x%08x ~ 0x%08x]
    ",
    +                 i, __pa(addr), addr, *((int *)addr), i*(1<<20), i*(1<<20)+(1<<20)-1,
    +                 ((value>>20)<<20), ((value>>20)<<20)+(1<<20)-1);
    +        }
    +    }
    +
         set_task_stack_end_magic(&init_task);
         smp_setup_processor_id();
         debug_objects_early_init();

    上面加的代码会将段式表中建立了映射的段表项的编号、存放每个段表项的物理地址和虚拟地址以及段表项的内容、该段表实现的虚拟地址跟物理地址之间的映射关系都打印出来。

    对于TQ2440,段表内容如下:

     1 0x0300: hw: 0x30004c00, virt: 0xc0004c00, value: 0x30000c1e map: [0x30000000 ~ 0x300fffff ==> 0x30000000 ~ 0x300fffff]
     2 0x0c00: hw: 0x30007000, virt: 0xc0007000, value: 0x30000c1e map: [0xc0000000 ~ 0xc00fffff ==> 0x30000000 ~ 0x300fffff]
     3 0x0c01: hw: 0x30007004, virt: 0xc0007004, value: 0x30100c1e map: [0xc0100000 ~ 0xc01fffff ==> 0x30100000 ~ 0x301fffff]
     4 0x0c02: hw: 0x30007008, virt: 0xc0007008, value: 0x30200c1e map: [0xc0200000 ~ 0xc02fffff ==> 0x30200000 ~ 0x302fffff]
     5 0x0c03: hw: 0x3000700c, virt: 0xc000700c, value: 0x30300c1e map: [0xc0300000 ~ 0xc03fffff ==> 0x30300000 ~ 0x303fffff]
     6 0x0c04: hw: 0x30007010, virt: 0xc0007010, value: 0x30400c1e map: [0xc0400000 ~ 0xc04fffff ==> 0x30400000 ~ 0x304fffff]
     7 0x0c05: hw: 0x30007014, virt: 0xc0007014, value: 0x30500c1e map: [0xc0500000 ~ 0xc05fffff ==> 0x30500000 ~ 0x305fffff]
     8 0x0c06: hw: 0x30007018, virt: 0xc0007018, value: 0x30600c1e map: [0xc0600000 ~ 0xc06fffff ==> 0x30600000 ~ 0x306fffff]
     9 0x0c07: hw: 0x3000701c, virt: 0xc000701c, value: 0x30700c1e map: [0xc0700000 ~ 0xc07fffff ==> 0x30700000 ~ 0x307fffff]
    10 0x0c3a: hw: 0x300070e8, virt: 0xc00070e8, value: 0x33a00c1e map: [0xc3a00000 ~ 0xc3afffff ==> 0x33a00000 ~ 0x33afffff]
    11 0x0c3b: hw: 0x300070ec, virt: 0xc00070ec, value: 0x33b00c1e map: [0xc3b00000 ~ 0xc3bfffff ==> 0x33b00000 ~ 0x33bfffff]
    12 0x0f70: hw: 0x30007dc0, virt: 0xc0007dc0, value: 0x50000c12 map: [0xf7000000 ~ 0xf70fffff ==> 0x50000000 ~ 0x500fffff]

    对于vexpress,段表内容如下:

     1 0x0601: hw: 0x60005804, virt: 0xc0005804, value: 0x60111c0e map: [0x60100000 ~ 0x601fffff ==> 0x60100000 ~ 0x601fffff]
     2 0x0c00: hw: 0x60007000, virt: 0xc0007000, value: 0x60011c0e map: [0xc0000000 ~ 0xc00fffff ==> 0x60000000 ~ 0x600fffff]
     3 0x0c01: hw: 0x60007004, virt: 0xc0007004, value: 0x60111c0e map: [0xc0100000 ~ 0xc01fffff ==> 0x60100000 ~ 0x601fffff]
     4 0x0c02: hw: 0x60007008, virt: 0xc0007008, value: 0x60211c0e map: [0xc0200000 ~ 0xc02fffff ==> 0x60200000 ~ 0x602fffff]
     5 0x0c03: hw: 0x6000700c, virt: 0xc000700c, value: 0x60311c0e map: [0xc0300000 ~ 0xc03fffff ==> 0x60300000 ~ 0x603fffff]
     6 0x0c04: hw: 0x60007010, virt: 0xc0007010, value: 0x60411c0e map: [0xc0400000 ~ 0xc04fffff ==> 0x60400000 ~ 0x604fffff]
     7 0x0c05: hw: 0x60007014, virt: 0xc0007014, value: 0x60511c0e map: [0xc0500000 ~ 0xc05fffff ==> 0x60500000 ~ 0x605fffff]
     8 0x0c06: hw: 0x60007018, virt: 0xc0007018, value: 0x60611c0e map: [0xc0600000 ~ 0xc06fffff ==> 0x60600000 ~ 0x606fffff]
     9 0x0c07: hw: 0x6000701c, virt: 0xc000701c, value: 0x60711c0e map: [0xc0700000 ~ 0xc07fffff ==> 0x60700000 ~ 0x607fffff]
    10 0x0c08: hw: 0x60007020, virt: 0xc0007020, value: 0x60811c0e map: [0xc0800000 ~ 0xc08fffff ==> 0x60800000 ~ 0x608fffff]
    11 0x0c09: hw: 0x60007024, virt: 0xc0007024, value: 0x60911c0e map: [0xc0900000 ~ 0xc09fffff ==> 0x60900000 ~ 0x609fffff]
    12 0x0c0a: hw: 0x60007028, virt: 0xc0007028, value: 0x60a11c0e map: [0xc0a00000 ~ 0xc0afffff ==> 0x60a00000 ~ 0x60afffff]
    13 0x0c80: hw: 0x60007200, virt: 0xc0007200, value: 0x68011c0e map: [0xc8000000 ~ 0xc80fffff ==> 0x68000000 ~ 0x680fffff]
    14 0x0c81: hw: 0x60007204, virt: 0xc0007204, value: 0x68111c0e map: [0xc8100000 ~ 0xc81fffff ==> 0x68100000 ~ 0x681fffff]
    15 0x0f80: hw: 0x60007e00, virt: 0xc0007e00, value: 0x10000c12 map: [0xf8000000 ~ 0xf80fffff ==> 0x10000000 ~ 0x100fffff]

    第34行到第36行,用于将__turn_mmu_on到__turn_mmu_on_end的虚拟地址转换成物理地址,然后将得到的物理地址当做虚拟地址建立页表。这样做的目的是什么呢?这两个标号之间的代码完成的任务是开启MMU,问题来了,再打开MMU那一刻,紧接着后面的地址都会被认为虚拟地址,都需要经过MMU进行转换,而在开启MMU的那条指令前后的PC是连续的,开启之前PC存放的是物理地址(0x30008000或者0x6000_8000级别的),开启之后,如果没有特殊的操作,紧邻的指令和数据的地址也是0x30008000或0x60008000级别,而此时的地址被认为是虚拟地址。如果不对这部分虚拟地址建立页表,程序里立刻出问题。

    以TQ2440为例,在上面的打印的log中的第1行就是映射__turn_mmu_on到__turn_mmu_on_end之间代码地址的段表项的信息:

    1 0x0300: hw: 0x30004c00, virt: 0xc0004c00, value: 0x30000c1e map: [0x30000000 ~ 0x300fffff ==> 0x30000000 ~ 0x300fffff]

     我们按照代码的计算方法手动计算一下该段表项:

    首先我们打开System.map文件,该文件中列出了kernel里一些符号对应的虚拟地址,我们找到__turn_mmu_on和__turn_mmu_on_end对应的虚拟地址:

    c0008200 T __turn_mmu_on
    c0008200 T _stext
    c0008218 t __turn_mmu_on_end

    然后将该虚拟地址转成对应的物理地址,可以看到这两个标号之间相差很小,在1MB以内,由于每个段表项都可以映射1MB的地址空间,所以只需要一个段表项即可。对于TQ2440,就是0x30008200和0x30008218,然后右移20位就都变成了0x300,然后再左移20位并或上0x00000c1e,就变成了0x3000_0c1e,这个就是段表项的内容,该表项的物理地址是多少呢?0x30004000 + 0x300<<2 = 0x30004C00,画出图的话:

     第51行到58行完成的任务是对0xC000_0000到_end标号之间的虚拟地址进行映射,映射到物理内存的起始地址处,查询一下System.map,可以发现标号_end的虚拟地址是0xc07404e0, 需要的段表项的个数是:(0xc07404e0 - 0xc0000000 + 0x100000)/ 0x100000 = 8

    在上面打印出来的log中下面的8个段表项就负责完成这部分的映射:

     2 0x0c00: hw: 0x30007000, virt: 0xc0007000, value: 0x30000c1e map: [0xc0000000 ~ 0xc00fffff ==> 0x30000000 ~ 0x300fffff]
     3 0x0c01: hw: 0x30007004, virt: 0xc0007004, value: 0x30100c1e map: [0xc0100000 ~ 0xc01fffff ==> 0x30100000 ~ 0x301fffff]
     4 0x0c02: hw: 0x30007008, virt: 0xc0007008, value: 0x30200c1e map: [0xc0200000 ~ 0xc02fffff ==> 0x30200000 ~ 0x302fffff]
     5 0x0c03: hw: 0x3000700c, virt: 0xc000700c, value: 0x30300c1e map: [0xc0300000 ~ 0xc03fffff ==> 0x30300000 ~ 0x303fffff]
     6 0x0c04: hw: 0x30007010, virt: 0xc0007010, value: 0x30400c1e map: [0xc0400000 ~ 0xc04fffff ==> 0x30400000 ~ 0x304fffff]
     7 0x0c05: hw: 0x30007014, virt: 0xc0007014, value: 0x30500c1e map: [0xc0500000 ~ 0xc05fffff ==> 0x30500000 ~ 0x305fffff]
     8 0x0c06: hw: 0x30007018, virt: 0xc0007018, value: 0x30600c1e map: [0xc0600000 ~ 0xc06fffff ==> 0x30600000 ~ 0x306fffff]
     9 0x0c07: hw: 0x3000701c, virt: 0xc000701c, value: 0x30700c1e map: [0xc0700000 ~ 0xc07fffff ==> 0x30700000 ~ 0x307fffff]

    我们可以拿0xC000_0000到0xC00F_FFFF映射为例,段表项的地址:(0xC000_0000 >> 18 ) + 0xC000_4000 = 0xC000_7000,段表项的内容: r8 | r7 = 0x3000_0000 | 0xC1E = 0x3000_0C1E

    对应的映射图如下:

     第64到72行做的工作是对r2指向的设备树镜像文件所在的物理地址空间镜像映射,这里kernel为其建立了两个段表项,一共可以映射2MB的空间。

    还是以TQ2440为例,对应的就是上面log中这两个:

    10 0x0c3a: hw: 0x300070e8, virt: 0xc00070e8, value: 0x33a00c1e map: [0xc3a00000 ~ 0xc3afffff ==> 0x33a00000 ~ 0x33afffff]
    11 0x0c3b: hw: 0x300070ec, virt: 0xc00070ec, value: 0x33b00c1e map: [0xc3b00000 ~ 0xc3bfffff ==> 0x33b00000 ~ 0x33bfffff]

    在TQ2440启动的时候uboot将设备树dtb加载到了物理内存0x33aa7000处:

    ## Booting kernel from Legacy Image at 30008000 ...
       Image Name:   Linux-4.10.17+
       Created:      2017-11-11  10:15:24 UTC
       Image Type:   ARM Linux Kernel Image (uncompressed)
       Data Size:    3487280 Bytes = 3.3 MiB
       Load Address: 30008000
       Entry Point:  30008000
       Verifying Checksum ... OK
    ## Flattened Device Tree blob at 32000000
       Booting using the fdt blob at 0x32000000
       Loading Kernel Image ... OK
       Loading Device Tree to 33aa7000, end 33aac168 ... OK
    
    Starting kernel ...
    
    Uncompressing Linux... done, booting the kernel.

    下面按照上面的方法,手动计算一下: 段表项的内容:(0x33aa7000 >> 20)<< 20 | 0x00000c1e = 0x33a0_0c1e   段表项的存放的地址: ((0x33aa7000 >> 20)<<20 - 0x30000000 + 0xC0000000) >> 18 + 0x3000_4000 = 0x30e8 + 0x3000_4000 = 0x3000_70e8

    此时,dtb占用的地址空间的映射图如下:

     

    未完待续。

  • 相关阅读:
    利用百度地图API实现百度地图坐标拾取
    newtonsoft.json的JObject里的JSON数据 动态
    监听微信返回按钮
    C# 如何理解如下泛型约束 class A<T>:where T:class{}
    微博数据库设计 _转
    新浪微博,腾讯微博mysql数据库主表猜想 __转
    Ferris教程学习笔记:js示例3.9 倒计时时钟
    Ferris教程学习笔记:js示例3.8 简易网页时钟
    Ferris教程学习笔记:js示例3.6 判断数字是否为两位数
    Ferris教程学习笔记:js示例3.5 页面加载后累加,自加1
  • 原文地址:https://www.cnblogs.com/pengdonglin137/p/7818922.html
Copyright © 2011-2022 走看看