作者

彭东林

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

 /*
* Setup the initial page tables. We only setup the barest
* amount which are required to get the kernel running, which
* generally means mapping in the kernel code.
*
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*
* Returns:
* r0, r3, r5-r7 corrupted
* r4 = physical page table address
*/
__create_page_tables:
pgtbl r4, r8 @ page table address /*
* Clear the swapper page table
*/
mov r0, r4
mov r3, #
add r6, r0, #PG_DIR_SIZE
: str r3, [r0], #
str r3, [r0], #
str r3, [r0], #
str r3, [r0], #
teq r0, r6
bne 1b ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags /*
* Create identity mapping to cater for __enable_mmu.
* This identity mapping will be removed by paging_init().
*/
adr r0, __turn_mmu_on_loc
ldmia r0, {r3, r5, r6}
sub r0, r0, r3 @ virt->phys offset
add r5, r5, r0 @ phys __turn_mmu_on
add r6, r6, r0 @ phys __turn_mmu_on_end
mov r5, r5, lsr #SECTION_SHIFT
mov r6, r6, lsr #SECTION_SHIFT : orr r3, r7, r5, lsl #SECTION_SHIFT @ flags + kernel base
str r3, [r4, r5, lsl #PMD_ORDER] @ identity mapping
cmp r5, r6
addlo r5, r5, # @ next section
blo 1b /*
* Map our RAM from the start to the end of the kernel .bss section.
*/
add r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)
ldr r6, =(_end - )
orr r3, r8, r7
add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
: str r3, [r0], # << PMD_ORDER
add r3, r3, # << SECTION_SHIFT
cmp r0, r6
bls 1b /*
* Then map boot params address in r2 if specified.
* We map 2 sections in case the ATAGs/DTB crosses a section boundary.
*/
mov r0, r2, lsr #SECTION_SHIFT
movs r0, r0, lsl #SECTION_SHIFT
subne r3, r0, r8
addne r3, r3, #PAGE_OFFSET
addne r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)
orrne r6, r7, r0
strne r6, [r3], # << PMD_ORDER
addne r6, r6, # << SECTION_SHIFT
strne r6, [r3] ret lr
ENDPROC(__create_page_tables)
.ltorg
.align
__turn_mmu_on_loc:
.long .
.long __turn_mmu_on
.long __turn_mmu_on_end

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

    .macro    pgtbl, rd, phys
add \rd, \phys, #TEXT_OFFSET
sub \rd, \rd, #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
--- a/init/main.c
+++ b/init/main.c
@@ -, +, @@ asmlinkage __visible void __init start_kernel(void)
char *command_line;
char *after_dashes; +
+ unsigned int i, addr, value;
+ for (i=; i < 0x1000; i++) {
+ addr = 0xC0004000 + i*;
+ 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]\n",
+ i, __pa(addr), addr, *((int *)addr), i*(<<), i*(<<)+(<<)-,
+ ((value>>)<<), ((value>>)<<)+(<<)-);
+ }
+ }
+
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();

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

对于TQ2440,段表内容如下:

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

对于vexpress,段表内容如下:

 0x0601: hw: 0x60005804, virt: 0xc0005804, value: 0x60111c0e map: [0x60100000 ~ 0x601fffff ==> 0x60100000 ~ 0x601fffff]
0x0c00: hw: 0x60007000, virt: 0xc0007000, value: 0x60011c0e map: [0xc0000000 ~ 0xc00fffff ==> 0x60000000 ~ 0x600fffff]
0x0c01: hw: 0x60007004, virt: 0xc0007004, value: 0x60111c0e map: [0xc0100000 ~ 0xc01fffff ==> 0x60100000 ~ 0x601fffff]
0x0c02: hw: 0x60007008, virt: 0xc0007008, value: 0x60211c0e map: [0xc0200000 ~ 0xc02fffff ==> 0x60200000 ~ 0x602fffff]
0x0c03: hw: 0x6000700c, virt: 0xc000700c, value: 0x60311c0e map: [0xc0300000 ~ 0xc03fffff ==> 0x60300000 ~ 0x603fffff]
0x0c04: hw: 0x60007010, virt: 0xc0007010, value: 0x60411c0e map: [0xc0400000 ~ 0xc04fffff ==> 0x60400000 ~ 0x604fffff]
0x0c05: hw: 0x60007014, virt: 0xc0007014, value: 0x60511c0e map: [0xc0500000 ~ 0xc05fffff ==> 0x60500000 ~ 0x605fffff]
0x0c06: hw: 0x60007018, virt: 0xc0007018, value: 0x60611c0e map: [0xc0600000 ~ 0xc06fffff ==> 0x60600000 ~ 0x606fffff]
0x0c07: hw: 0x6000701c, virt: 0xc000701c, value: 0x60711c0e map: [0xc0700000 ~ 0xc07fffff ==> 0x60700000 ~ 0x607fffff]
0x0c08: hw: 0x60007020, virt: 0xc0007020, value: 0x60811c0e map: [0xc0800000 ~ 0xc08fffff ==> 0x60800000 ~ 0x608fffff]
0x0c09: hw: 0x60007024, virt: 0xc0007024, value: 0x60911c0e map: [0xc0900000 ~ 0xc09fffff ==> 0x60900000 ~ 0x609fffff]
0x0c0a: hw: 0x60007028, virt: 0xc0007028, value: 0x60a11c0e map: [0xc0a00000 ~ 0xc0afffff ==> 0x60a00000 ~ 0x60afffff]
0x0c80: hw: 0x60007200, virt: 0xc0007200, value: 0x68011c0e map: [0xc8000000 ~ 0xc80fffff ==> 0x68000000 ~ 0x680fffff]
0x0c81: hw: 0x60007204, virt: 0xc0007204, value: 0x68111c0e map: [0xc8100000 ~ 0xc81fffff ==> 0x68100000 ~ 0x681fffff]
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  ...
Image Name: Linux-4.10.+
Created: -- :: UTC
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: Bytes = 3.3 MiB
Load Address:
Entry Point:
Verifying Checksum ... OK
## Flattened Device Tree blob at
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占用的地址空间的映射图如下:

未完待续。

Linux内存管理学习2 —— head.S中的段页表的建立的更多相关文章

  1. Linux内存管理学习1 —— head.S中的段页表的建立

    作者 彭东林 pengdonglin137@163.com 平台 TQ2440 Qemu+vexpress-ca9 Linux-4.10.17 概述 在Linux自解压完毕后,开始执行arch/arm ...

  2. Linux内存管理学习3 —— head.S中的段页表的建立

    作者 彭东林 pengdonglin137@163.com 平台 TQ2440 Qemu+vexpress-ca9 Linux-4.10.17 正文 继续分析head.S: ldr r13, =__m ...

  3. Linux内存管理学习资料

    下面是Linux内存管理学习的一些资料. 博客 mlock() and mlockall() system calls. All about Linux swap space 逆向映射的演进 Linu ...

  4. Linux内存管理学习笔记 转

    https://yq.aliyun.com/articles/11192?spm=0.0.0.0.hq1MsD 随着要维护的服务器增多,遇到的各种稀奇古怪的问题也会增多,要想彻底解决这些“小”问题往往 ...

  5. Linux内存管理学习笔记——内存寻址

    最近开始想稍微深入一点地学习Linux内核,主要参考内容是<深入理解Linux内核>和<深入理解Linux内核架构>以及源码,经验有限,只能分析出有限的内容,看完这遍以后再更深 ...

  6. 郝健: Linux内存管理学习笔记-第1节课【转】

    本文转载自:https://blog.csdn.net/juS3Ve/article/details/80035751 摘要 MMU与分页机制 内存区域(内存分ZONE) LinuxBuddy分配算法 ...

  7. 郝健: Linux内存管理学习笔记-第2节课【转】

    本文转载自:https://blog.csdn.net/juS3Ve/article/details/80035753 摘要 slab./proc/slabinfo和slabtop 用户空间mallo ...

  8. Linux内存管理学习笔记--物理内存分配

    http://blog.chinaunix.net/uid-20321537-id-3466022.html

  9. C++内存管理学习笔记(7)

    /****************************************************************/ /*            学习是合作和分享式的! /* Auth ...

随机推荐

  1. 为何gpio_to_irq不能静态使用?【转】

    之前在调试传感器模块的时候发现,在结构体声明的时候irq成员使用gpio_to_irq会报错,而动态使用的话就没有问题.就对gpio_to_irq为什么不能静态使用产生了疑问.恰巧最近又有朋友遇到了同 ...

  2. RESET MASTER和RESET SLAVE使用场景和说明【转】

    [前言]在配置主从的时候经常会用到这两个语句,刚开始的时候还不清楚这两个语句的使用特性和使用场景. 经过测试整理了以下文档,希望能对大家有所帮助: [一]RESET MASTER参数 功能说明:删除所 ...

  3. oracle数据库如何创建用户和角色,并给其赋权?

    一.创建用户并赋予权限 1.创建用户 create user wangxiangyu identified by wangxiangyu; 2.赋权 grant dba to wangxiangyu; ...

  4. springcloud Zuul中异常处理细节

    Spring Cloud Zuul对异常的处理整体来说还是比较方便的,流程也比较清晰,只是由于Spring Cloud发展较快,各个版本之间有差异,导致有的小伙伴在寻找这方面的资料的时候经常云里雾里, ...

  5. Select查询语句1

    一.语法结构 select[all|distinct]select_list from table_name[join join_condition] where search_condition g ...

  6. Git(四)Git的分支管理

    一. 创建合并分支原理 在我们每次的提交,Git都把它们串成一条时间线,这条时间线就是一个分支.截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支.HEAD指针严格来说不是指 ...

  7. 【LOJ】#2569. 「APIO2016」最大差分

    题解 第一个子任务直接询问最大最小,每次可以问出来两个,再最大最小-1再问两个,最多问\(\frac{N + 1}{2}\)次就还原出了序列 第二个子任务由于差分肯定会大于等于\(\lceil \fr ...

  8. Python 之 Module Not Found Error: No module named 'openpyxl'

    我在学习Python的过程中,计划将取到的数据保存到Excel文件中,使用 df.to_csv('D:/PythonWorkSpace/TestData/test.xlsx') 总是报错 Module ...

  9. 关于Sublime text3 配置及插件整理

    Package Control组件在线安装 按Ctrl+`调出console(注:避免热键冲突) 粘贴以下代码到命令行并回车: import urllib.request,os; pf = 'Pack ...

  10. vim/sed/awk/grep等文件批处理总结

    Vim相关操作 1.基础 * 和 # 对对当前光标所在的单词进行搜索 %匹配括号移动,包括 (, {, [ K 查看man手册 ga 查看ascii值 g CTRL-G 统计字数,使用Visual模式 ...