mmu以及页表 linuxkernel(2)
来源:互联网 发布:欧洲人 知乎 编辑:程序博客网 时间:2024/04/29 14:49
对于mmu的作用,参看第一篇的介绍
这里讲linux kenel的mmu和页表
部分内容图片参考 http://blog.csdn.net/luckyapple1028/article/details/45287617 非常不错的blog
对于1M段大小的虚拟地址和物理地址转换,arm1176计算方式如下
虚拟地址的[31:20]位存放一级页表的入口index,[19:0]位存放段偏移;
从TTBR(translation table base register,协处理器CP15中的一个寄存器,用于存放一级页表的基址)寄存器中获取一级页表的基址;
一级页表基址+ VA[31:20] = 该虚拟地址对应的页表描述符的入口地址;
页表描述符的[31:20]位为该虚拟地址对应的物理段基址;
物理段基址+ VA[19:0]段偏移= 物理地址
linux有两次页表处理
第一次是在arch/arm/kernel/head.s里面
第二次是是在start_kernel以后
还有其他的一些io地址映射
内核解压到了0x8000处,并且从0x8000开始执行
第一次在arch/arm/kernel/head.s
/* * 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 = page table (see ARCH_PGD_SHIFT in asm/memory.h) */__create_page_tables:pgtblr4, r8@ page table address/* * Clear the swapper page table */movr0, r4movr3, #0addr6, r0, #PG_DIR_SIZE1:strr3, [r0], #4strr3, [r0], #4strr3, [r0], #4strr3, [r0], #4teqr0, r6bne1b
其中pgtbl
.macropgtbl, rd, physadd\rd, \phys, #TEXT_OFFSETsub\rd, \rd, #PG_DIR_SIZE.endm
这里明确说明建立的页表只是为了kernel能够运行,因此只映射kernel code.
输入的时候 r8是物理地址偏移(0) r9是cpuid r10是procinfo
返回值r4是页表基地址(0x4000)
下面来看这个操作过程
add r4,r8,#TEXT_OFFSET 表示内核起始地址相对于RAM地址的偏移值 (定义在arch/arm/Makefile中 TEXT_OFFSET := $(textofs-y) textofs-y:= 0x00008000 )
sub r4,r4,#PG_DIR_SIZE 表示页目录的大小。
我这里变成
ADD R4, R8, #0x8000
SUB R4, R4, #0x4000
0x4000是页目录的大小为16KB
0x8000表示内核起始地址相对于RAM地址的偏移值
r4=r8+0x8000-0x4000
得到r4等于物理页表的起始地址
后面就是循环清空这一块地址 从r4->r4+0x4000。全部清0
ldrr7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags/* * Create identity mapping to cater for __enable_mmu. * This identity mapping will be removed by paging_init(). */adrr0, __turn_mmu_on_locldmiar0, {r3, r5, r6}subr0, r0, r3@ virt->phys offsetaddr5, r5, r0@ phys __turn_mmu_onaddr6, r6, r0@ phys __turn_mmu_on_endmovr5, r5, lsr #SECTION_SHIFTmovr6, r6, lsr #SECTION_SHIFT
从proc结构中取出mm_mmuflags标记放到r7中(0xc0e)
__turn_mmu_on_loc:.long..long__turn_mmu_on.long__turn_mmu_on_end
将__trun_mmu_on_loc地址存放到r0中 也就是r0指向这个3个long字节的数据第一个 r0=0x8168
从r0中取出数据来放到r3 r5 r6中 adr是取得运行时的相对地址。此时r0是相对地址
第一个.表示当前位置 也就是__trun_mmu_on_loc的值=r3(0xc0008168),第二个是__turn_mmu_on函数的起始地址=r5(0xc0008200),第三个是__turn_mmu_on函数的结束地址=r6(0xc0008220)这些地址都是虚拟地址,是0XC0000000起头的地址
sub r0,r0,r3 r0=r0-r3 相对的运行地址r0-绝对的0XC000XXX地址r3得到两者之间的偏移
然后 r5和r6这两个绝对地址0XC0000XXX 都加上这个偏移。就得到了当前物理内存中的__turn_mmu_on(0x8200)和__turn_mmu_on_end(0x8220)的地址
然后将r5 r6的物理地址都右移20位,这样r5和r6里面保存的就是__trun_mmu_on和__trun_mmu_on_end的物理基地址的索引。(r5=r6=0)
比如R5=0X123ABCDE。按照1M分段。那么0x123就是[31:20]的基地址。0xABCDE就是段内偏移。
R5右移20位后变成了0X123
1:orrr3, r7, r5, lsl #SECTION_SHIFT@ flags + kernel basestrr3, [r4, r5, lsl #PMD_ORDER]@ identity mappingcmpr5, r6addlor5, r5, #1@ next sectionblo1b
接下来先将 R5左移20位 然后或上r7,也就是0X12300000 or r7,此时r5还是没有变,依然是0X123。也就是描述符标记,形成了一个4字节的页表描述符(0xc0e),放到R3当中。
str r3 ,[r4,r5, lsl #2]等同于r4[r5*4]=r3。也就是将这个描述符放到相应的位置上去。循环直到R6停止。
到这里为止
turn_mmu_on到turn_mmu_end的代码所属的物理位置的页表描述符已经设置好了
这样就做到了虚拟地址和物理地址一一映射。因为要保证执行完turn_mmu_on以后,这部分代码依然是一一映射的,开启完毕以后即使在虚拟地址上也可以执行后续的代码
实际上我的树莓派中R5=0因此就是 r4[0]=0xc0e如下图
接下来是映射内核从开始映射到末尾.bss段
/* * Map our RAM from the start to the end of the kernel .bss section. */addr0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)ldrr6, =(_end - 1)orrr3, r8, r7addr6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)1:strr3, [r0], #1 << PMD_ORDERaddr3, r3, #1 << SECTION_SHIFTcmpr0, r6bls1b
在我的反汇编代码中变成了
.head.text:C000811C ADD R0, R4, #0x3000.head.text:C0008120 LDR R6, =0xC08F269F.head.text:C0008124 ORR R3, R8, R7.head.text:C0008128 ADD R6, R4, R6,LSR#18.head.text:C000812C.head.text:C000812C loc_C000812C ; CODE XREF: __create_page_tables+7Cj.head.text:C000812C STR R3, [R0],#4.head.text:C0008130 ADD R3, R3, #0x100000.head.text:C0008134 CMP R0, R6.head.text:C0008138 BLS loc_C000812C
PAGE_OFFSET=0XC0000000
SECTION_SHIFT=20
PMD_ORDER=2
首先将PAGE_OFFSET(0xc0008000)右移20-2 ,再加上r4(页表物理基地址)得到内核起始链接地址对应页表项的物理地址,保存到r0中
r0等于0x7000
r4=0x4000
将.end结束地址放到R6当中
将R7|R8的值保存到 R3中。
然后计算内核结束虚拟地址对应应页表项的物理地址保存到r6中
每一次循环都会将r3增加一个1<<SECTION_SHIFT 的大小,也就是增加1MB将描述符填充到页表对应的位置里面
r4=pagetable=0x4000
为什么要用 addr>>(20-2)+pagetable 得到内核起始地址和结束地址在pagetable对应的页表项地址呢?
这个-2是因为一个页表项占用4字节,也就是左移两位,相当于乘以4
比如要计算0XC0008000 先取得高12bit 0xC00,这个就是索引。访问的时候就是访问pagetable[0XC00]项,每一项四个字节,
pagetable[0xc00]=pagetable+(0xc0008000>>20)*4=pagetable+0xc0008000>>20<<2=pagetable+0xc0008000>>(20-2)=0x4000+0xc0008000>>18
以R3为基准,每次增加1MB大小。
比如 R3=0X00000C0E pagetable[0xc00]=0X00000C0E
R3=R3+0X100000
R3=0x00100C0E pagetable[0xc00]=0X00100C0E
R3=R3+0X100000
R3=0x00200C0E pagetable[0xc00]=0X00200C0E
pagetable[0xc00] 开始处的内存如下图
到这里为止。我们映射了内核代码部分,也映射了turn_mmu_on部分
注意到页表地址0x7000里面是0x00000c0e 前面的0x4000里面也是0x00000c0e。那么就表示,这两个虚拟地址都映射到了同一个物理地址。
因此我们访问0xc0008000 和访问0x8000 访问的是同一块物理地址。
接下来就是映射参数地址了。r2 atag或者DTB
/* * Then map boot params address in r2 if specified. * We map 2 sections in case the ATAGs/DTB crosses a section boundary. */movr0, r2, lsr #SECTION_SHIFTmovsr0, r0, lsl #SECTION_SHIFTsubner3, r0, r8addner3, r3, #PAGE_OFFSETaddner3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)orrner6, r7, r0strner6, [r3], #1 << PMD_ORDERaddner6, r6, #1 << SECTION_SHIFTstrner6, [r3]
首先得到物理地址r2的高12bit。其实就是r0=r2&0xfff00000
如果r0是0,后面就不再映射了,这里我的r0是0,所以后续的映射都没有执行。
因为我的r2是0x100,恰好和内核起始地址0x8000在同一个段内,所以在映射内核的时候就顺带映射了r2。
(疑问?如我我r2=0x100 内核的起始地址在0x100800,不在同一个段内那不是就没有映射了吗?)。
这个疑问暂留。
接下来一般就是return 了。
但是为了我们能够在start_kernel之前实现串口打印。通常可以通过配置CONFIG_DEBUG_LL实现
make menuconfig ---> Kernel hacking ---> 选中:Kernel debugging。
当选中Kernel debugging后,才能看见Kernel low-level debugging functions. 选中即可
所以在return之前可以映射一下串口地址。
/* * Map in IO space for serial debugging. * This allows debug messages to be output * via a serial console before paging_init. */addruart r7, r3, r0movr3, r3, lsr #SECTION_SHIFTmovr3, r3, lsl #PMD_ORDERaddr0, r4, r3movr3, r7, lsr #SECTION_SHIFTldrr7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflagsorrr3, r7, r3, lsl #SECTION_SHIFTorrr3, r3, #PMD_SECT_XNstrr3, [r0], #4
对于我的树莓派addruart的定义是
.macroaddruart, rp, rv, tmpldr\rp, =UART0_BASEldr\rv, =IO_ADDRESS(UART0_BASE).endm其中addruart实现的宏就是 r7存放UART需要映射的物理地址,R3存放映射后的虚拟地址,r0是一个临时变量,可供自由使用
并且在arm\mach-bcm2708\include\mach\platform.h里面找到了相关的定义
/* macros to get at IO space when running virtually */#define IO_ADDRESS(x)(((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000)#define BCM2708_PERI_BASE 0x20000000#define UART0_BASE (BCM2708_PERI_BASE + 0x201000)/* Uart 0 */
BCM2708外设的基地址是 0X20000000, UART0口的基地址是 0X201000
所以只要映射 物理地址r7=0X20201000 到虚拟地址r3=IO_ADDRESS(0x20201000)=(0xF2201000)
IO的映射公式 是先取物理地址paddr的低28bit得到v1 ,取得最高位加到v1的[27:24] bit上,最后加上0xf0000000(映射到其他合适的虚拟地址应该也可以???)
首先r3=r3>>(20-2) 得到描述页表项相对于页表基地址的偏移。
r4是页表基地址 r0=r4+r3, 得到r0就是对应的描述页表项的物理地址。
接下来构造页表描述项
r3=r7>>20
再把物理地址r7右移20位放到R3中;
r7=io_mmu_flags
取出io_mmu_flags到r7中
r3=r3<<20 | r7
再或上另外一个标记PMD_SECT_XN
得到最终的页表项描述符 r3
然后把r3存储到r0地址中.(再把r0自增4 r0=r0+4,这个r0没有必要再+4了。没什么作用)
这样访问F2XXXXX的时候就会访问IO地址0X2XXXXXX了。
- mmu以及页表 linuxkernel(2)
- mmu以及页表 UBOOT(1)
- ARM MMU 创建页表
- ARM MMU页表框架
- ARM MMU页表框架
- MMU工作原理以及S3C2440的MMU
- MMU工作原理以及S3C2440的MMU
- MMU工作原理以及S3C2440的MMU
- 关于MMU的页表的理解 (转)
- MMU结构以及工作原理(作者:wogoyixikexie@gliet)
- ARM920T协处理器以及MMU的分析(一)
- ARM920T协处理器以及MMU的分析(二)
- ARM920T协处理器以及MMU的分析(三)
- LinuxKernel 入侵式双向链表的设计,分析,使用
- LinuxKernel中文版
- linuxkernel map
- MMU结构以及工作原理
- mmu以及cache入门详解
- MYSQL的八大缺陷
- Sublime Text 3鼠标选中时背景色的修改方法
- 窄带物联网(NB-IoT)深入了解
- 聪聪工作室---插入日期数据小技巧switch-case
- 二维码开源库zbar、zxing使用心得
- mmu以及页表 linuxkernel(2)
- PPT个人学习笔记(三)——模版背景的设计
- 高并发架构处理方法
- 转一篇比较详细介绍FatFs文件系统移植的文章
- PrestoDB 大数据查询引擎
- 设置动画结束的监听事件
- Caffe源码(十一):io.cpp 分析
- Objective-c runtime方法替换引发的死循环
- 巧克力