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
/*
* 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中的段页表的建立的更多相关文章
- Linux内存管理学习1 —— head.S中的段页表的建立
作者 彭东林 pengdonglin137@163.com 平台 TQ2440 Qemu+vexpress-ca9 Linux-4.10.17 概述 在Linux自解压完毕后,开始执行arch/arm ...
- Linux内存管理学习3 —— head.S中的段页表的建立
作者 彭东林 pengdonglin137@163.com 平台 TQ2440 Qemu+vexpress-ca9 Linux-4.10.17 正文 继续分析head.S: ldr r13, =__m ...
- Linux内存管理学习资料
下面是Linux内存管理学习的一些资料. 博客 mlock() and mlockall() system calls. All about Linux swap space 逆向映射的演进 Linu ...
- Linux内存管理学习笔记 转
https://yq.aliyun.com/articles/11192?spm=0.0.0.0.hq1MsD 随着要维护的服务器增多,遇到的各种稀奇古怪的问题也会增多,要想彻底解决这些“小”问题往往 ...
- Linux内存管理学习笔记——内存寻址
最近开始想稍微深入一点地学习Linux内核,主要参考内容是<深入理解Linux内核>和<深入理解Linux内核架构>以及源码,经验有限,只能分析出有限的内容,看完这遍以后再更深 ...
- 郝健: Linux内存管理学习笔记-第1节课【转】
本文转载自:https://blog.csdn.net/juS3Ve/article/details/80035751 摘要 MMU与分页机制 内存区域(内存分ZONE) LinuxBuddy分配算法 ...
- 郝健: Linux内存管理学习笔记-第2节课【转】
本文转载自:https://blog.csdn.net/juS3Ve/article/details/80035753 摘要 slab./proc/slabinfo和slabtop 用户空间mallo ...
- Linux内存管理学习笔记--物理内存分配
http://blog.chinaunix.net/uid-20321537-id-3466022.html
- C++内存管理学习笔记(7)
/****************************************************************/ /* 学习是合作和分享式的! /* Auth ...
随机推荐
- 【工具】用命令行与Python使用YARA规则
1.前言 YARA是一款旨在帮助恶意软件研究人员识别和分类恶意软件样本的开源工具,使用YARA可以基于文本或二进制模式创建恶意软件家族描述与匹配信息.现在已经被多家公司所运用于自身的产品. 2.YAR ...
- mysql5.7主从复制--在线变更复制类型【转】
这里说一下关于如何在线变更复制类型(日志复制到全局事物复制),参考课程:mysql5.7复制实战 先决条件 (1)集群中所有的服务器版本均高于5.7.6(2)集群中所有的服务器gtid_mod ...
- screen命令记录
1.screen -x 进入 2.ctrl+a+n 下一个 3.ctrl+a+p 上一个任务 4.ctrl+a+d 退出 5.ctrl+c 结束任务 其他 screen -ls 所有任务 screen ...
- [Android]使用 Eclipse 给 APK 签名时遇到的两个问题及解决办法
问题 今天用 APK 反编译工具看了一下自己项目生成的 APK 文件,发现代码并没有混淆,于是设置了用 ProGuard 混淆代码,可是混淆是必须在非 Debug 模式才会生效的,即使你是以 Rele ...
- 输出联系变化的数字seq
主要作用:输出联系变化的数字格式:Seq 分割符号 开始 间隔 结束开始默认是1,间隔默认是1,分隔符默认回车一位是结束,两位首末,三位首间隔末,没有四位,开始可以是负数主要参数:-f 指定格式打印- ...
- navicate连接Linux下mysql慢,卡,以及mysql相关查询,授权
方法,网上的办法是在my.ini的“[mysqld]”下面加入一行“skip-name-resolve”,就像这样: 然后保存并重启mysql服务即可. service mysqld restart ...
- SQL Server 4
一.视图 1.创建视图 1)选中数据库中的表中的视图处,右键选择新建视图,即: 2)在弹出“添加表”对话框中,单击“表”标签,选择要添加的表,点击添加,即: 3)选中要建立联系的列名的复选框,然后拖动 ...
- centos redis 自动重启
配置init脚本 对于Centos,有一份https://gist.github.com/1335694 经过修改,如下: ########################## PATH=/usr/l ...
- Pomelo分布式游戏服务器框架
Pomelo介绍&入门 目录 前言&介绍 安装Pomelo 创建项目并启动 创建项目 项目结构说明 启动 测试连接 聊天服务器 新建gate和chat服务器 配置master.json ...
- 【LOJ】 #6012. 「网络流 24 题」分配问题
题解 又写了一遍KM算法,这题刚好是把最大最小KM拼在一起写的,感觉比较有记录价值 感觉KM始终不熟啊QAQ 算法流程大抵如下,原理就是每次我们通过减少最少的匹配量达成最大匹配,所以获得的一定是最大价 ...