linux 启动 使能 mmummu地址映射程序右移20位什么意思

linux 内核采用页式存储管理。虚拟地址空间划分成固定大小的&页面&,由MMU在运行时将虚拟地址映射成某个物理内存页面中的地址。页式内存管理比段式内存管理有很多好处,但是由于Intel是先使用段式管理的,然后才发明了页式管理,为了兼容,i386 CPU 一律对程序中使用的地址先进行段式映射,然后才能进行页式映射,既然CPU的硬件结构是这样,linux内核也只好服从intel的选择。通过一个例子看看linux内核是怎样在i386 CPU 上进行地址映射的。
&&&&假设我们写了这么一个程序:
1 #include &stdio.h&
3 greeting()
printf("hello, world!\n");
greeting();
经过编译后,我们得到可执行代码hello。用命令:objdump -d hello来反汇编这一段二进制代码
从上面可以看出,ld给greeting()分配的地址为0x80483e4,在elf格式代码中,ld 总是从0x开始安排程序的&代码段&,对每个程序都是这样,至于程序在执行时在物理内存中的实际位置则就要由内核在为其建立内存映射时临时作出安排。
&&&&假设该程序已经在运行,整个映射机制都已经建立好,并且CPU 正在执行main()中的&call 80483e4&这条指令,要转移到虚拟地址0x上去,接下来就一步一步的走过这个地址映射过程。
&&&&首先是段式映射,由于地址0x是一个程序的入口,更重要的是在执行的过程中是由CPU 中的&指令计数器&EIP所指向的,所以在代码段中,因此,i386 CPU使用代码段寄存器CS的当前值来作为段式映射的&选择码&,也就是用它作为在段描述表中的下标。
&&&&接下来看看CS的内容,内核建立一个进程时都要将其段寄存器设置好(?),代码在include/asm-i386/processor.h 中:
&&&&可以看到,除了CS被设置成USER_CS外,其他的所有段寄存器都被设置成USER_DS。就是说,虽然Intel 的意图是将一个进程的映像分成代码段、数据段和堆栈段,linux 内核中堆栈段和数据段式不分的。
&&&&再来看看USER_CS和USER_DS到底是什么
也就是说,linux 内核中只是用四种不同的段寄存器值,两种用于内核本身,两种用于所有的进程。现在我们将这四种数值用二进制展开并与段寄存器的格式对照:
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Index&&&&&&&&&&&&& &TI& &RPL&&&&&&&&&&&&&&&& & & &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
__KERNEL_CS & &0x10&&&&&&&& &0001 &0 &| &0 &|&0 &0
__KERNEL_DS&&&&0x18&&&&&&&& &0001 &1& |& 0 &|&0 &0
__USER_CS&&&& &0x23&&&&&&&& &0010 &0&&|& 0 &|&1 &1
__USER_DS&&&&& 0x2B&&&&&&&& &0010& 1&&|& 0 &|&1 &1
经过和段寄存器的格式对照:
__KERNEL_CS &&&&&&index = 2 & & & & & & &TI = 0 & & & & & & &RPL = 0
__KERNEL_DS & & &&index = 3 & & & & & & &TI = 0 & & & & & & &RPL = 0
__USER_CS & & & &&index = 4 & & & & & & &TI = 0 & & & & & & &RPL = 3
__USER_DS & & & &&index = 5 & & & & & & &TI = 0 & & & & & & &RPL = 3
TI 都是0说明用的都是GDT,RPL只用了0和3,内核为0级,用户为3级
我们上面写的程序显然不属于内核,所以在进程的用户空间中运行,内核在调度该进程进入运行时,把CS设置成__USER_CS,即0x23,所以,CPU 以4为下标在全局描述表GDT中招对应的段描述项。
&&&&GDT内容如下:
把4个段描述项的内容按二进制展开,和段描述项的定义对照,可以得出以下结论:
每个段都是从0地址开始的整个4GB空间,虚地址到线性地址的映射保持不变。所以,linux 内核设计的段式映射机制把地址0x 映射到了自身,现在作为线性地址出现了,下面进入页式映射过程:
&&&&每个进程都有其自身的页面目录PGD,指向这个目录的指针保持在每个进程的mm_struct数据结构中,每当调度一个进程进入运行的时候,内核都要为即将运行的进程设置好控制寄存器CR3,而MMU的硬件则总是从CR3中取得指向当前页面目录的指针。不过,CPU 在执行程序时使用的虚存地址,而MMU硬件在进行映射时所用的则是物理地址,这是在switch_mm()函数中完成的
1 static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk, unsigned cpu)
asm volatile("movl %0, %%cr3" : : "r" (__pa(next-&pgd)));
当我们程序中要转移到地址0x80483e4去的时候,进程正在运行中,CR3早已设置好,指向我们这个进程的页面目录了,先将线性地址展开:
&&&&&&&&00 11
&&&&&&&&&32 & & & & & & & & & &72 & & & & & & & & 996
已CR3中的内容指向的地址为基地址,以32为下标就找到了目录项,取得到的目录项中的高20位然后加上12个0就得到了该页面表的指针。同理再以刚刚找到的页面表指针为基地址,以72为下标,找到页表项,然后取高20位,再加上996即是greeting()函数的物理地址。
阅读(...) 评论()ARM MMU地址重映射
第2页_Linux编程_Linux公社-Linux系统门户网站
你好,游客
ARM MMU地址重映射
来源:Linux社区&
作者:Linux
三、启动过程中的堆栈初始化释疑
堆与栈:对于ARM,堆是向上生长的,栈是向下生长的。
局部变量占用栈(stack)空间(但其初始化值为数据,占用RO空间);
程序中动态申请的如malloc()和new函数申请的内存空间占用堆(heap)空间。
&&&&&以下讨论不使用semihosting机制&&&&&
因此,在转入C应用程序前,必须要为C程序准备堆栈空间。根据具体的目标平台的存储器资源,要对堆栈的初始化函数__user_initial_stackheap( )进行移植,主要是正确设置堆(heap)和栈(stack)的地址。它可以使用C或ARM汇编语言来编写,并至少返回堆基址(保存在R0中),栈基址(保存在R1)可选。因而一个简单的汇编语言编写的__user_initial_stackheap( )函数如下:
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, =0x20000 ;heap base
LDR R1, =0x40000 ;stack base, optional
MOV PC, R14
该函数的C语言实现可见参考资料[6]P158页。
注意,如果在工程中没有自定义这个函数,那么缺省情况下,编译器/链接器会把|Image$$ZI$$Limit|作为堆(heap)的基址(即把heap和stack区放置在ZI区域的上方,这也被认为是标准的实现[7])。但是,如果使用scatter文件实现分散加载机制,链接器并不生成符号|Image$$ZI$$Limit|,这时就必须自己重新实现__user_initial_stackheap( )函数并且设置好堆基址和栈顶,否则链接时会报错。
堆栈区还分为单区模型和双区模型,在双区模型中,还必须设置堆栈限制[4,6,7]。
关于重定义__user_initial_stackheap( )函数时几点要注意的地方:一是不要使用超过96字节的stack,二是不要影响到R12(IP,用作进程间调用的暂存寄存器),三是按规则返回参数值(R0:heap base;R1:stack base;R2:heap limit;R3:stack limit),四是让堆区保持8字节对齐[6]。
在启动代码中,还要对各个处理器模式的栈指针进行初始化。这个问题很容易与上面谈到的__user_initial_stackheap()函数的作用相混淆。可从以下几点来加以说明:
(1)在嵌入式应用中,启动代码分为两个部分:一是系统的初始化,包括中断向量表的建立、时钟、存储系统初始化、关键I/O口初始化、各处理器模式下的栈指针初始化等;二是应用程序初始化(或说C库函数初始化),包括RW段的搬移和ZI段的清零、C应用程序堆栈区的建立(__user_initial_stackheap()函数初始化堆栈指针)等。
从这个意义上说,两者并没有直接关系。
(2)但两者并不是没有联系的。以单区模型的堆栈区为例,由于栈是向下生长的,堆是向上生长的,系统模式的栈指针(与用户模式相同,共用一个R13寄存器来描述)实际上定义了用户模式下单区模型堆栈区的上限,而__user_initial_stackheap()函数中指定的heap基址则成为该堆栈区的下限。
因此,如果之前已经对系统模式(用户模式)的栈指针进行了初始化,则在重定义__user_initial_stackheap()函数时,就不需要重新定义stack base了。
四、启动代码的内容和初始化顺序探讨
前面已经指出,启动代码包括系统初始化以及应用程序运行环境的初始化两个部分,完成初始化后,就可以呼叫用户主程序了。参考资料[1]、[3]和[5]等都对两个部分的内容以及过程列出了非常清晰但又简单明了的步骤,这对于初学者来说稍微有点抽象。
如果不需要使用MMU进行地址重映射,那么,结合网上可以搜集的示例boot代码以及分析文档,加上自己动手移植和调试,也是比较容易理解的。如果是使用处理器自带的Remap控制寄存器来进行地址重映射,网上也有相关的代码,例如网友twentyone的boot代码【4510 bootloader的实现与分析(附源代码)】就非常清楚,另外,在《ARM学习报告》系列文章中也对其有详细的分析。
对于在启动过程中要使用MMU进行地址重映射的系统初始化顺序,在《使用AXD调试MMU地址映射程序手记(二)》一文中给出了一个参考步骤,并做了一定的说明。通过进一步参考权威资料,这里,对系统初始化顺序作了小的改进与修正如下:
①禁止所有中断&②初始化时钟&③初始化存储器&④初始化各模式下的栈指针&⑤初始化GPIO&⑥拷贝映像文件到SDRAM&⑦建立地址重映射表&⑧使能MMU&⑨应用程序初始化(RW&ZI区)&⑩使能异常中断&⑾呼叫主程序(dummyOS)。
主要对使能异常中断和应用程序初始化的顺序做了调整,即先进行应用程序的初始化,再使能异常中断,可参考[3]和[10]。
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
【参考资料】
[1]《ARM体系结构与编程》,杜春雷,清华大学出版社,2003年2月[2]《ARM嵌入式系统开发&&软件设计与优化》,沈建华译,2005年5月[3]《基于ARM的嵌入式系统程序开发要点》,费浙平,2003年8月[4]《RealView编译工具开发者指南》,ARM Limited,2003年1月[5]《ADS Developer Guide》,ARM Limited,2001年11月[6]《ADS Compilers and Libraries Guide》,ARM Limited,2001年11月[7]《ADS Linker and Utilities Guide》,ARM Limited,2001年11月[8]《MAP文件认识初步》,JOHNNY LEE[9]《堆与栈的区别》,未知网友[10]《使用ADS1.2进行嵌入式软件开发》,ARM Limited2
相关资讯 & & &
   同意评论声明
   发表
尊重网上道德,遵守中华人民共和国的各项有关法律法规
承担一切因您的行为而直接或间接导致的民事或刑事法律责任
本站管理人员有权保留或删除其管辖留言中的任意内容
本站有权在网站内转载或引用您的评论
参与本评论即表明您已经阅读并接受上述条款mmu以及页表 linuxkernel(2) - CSDN博客
mmu以及页表 linuxkernel(2)
对于mmu的作用,参看第一篇的介绍
这里讲linux kenel的mmu和页表
部分内容图片参考&http://blog.csdn.net/luckyapple1028/article/details/ &非常不错的blog
对于1M段大小的虚拟地址和物理地址转换,arm1176计算方式如下
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:
pgtbl r4, r8
@ page table address
* Clear the swapper page table
mov r0, r4
mov r3, #0
add r6, r0, #PG_DIR_SIZE
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
.macro pgtbl, rd, phys
add \rd, \phys, #TEXT_OFFSET
sub \rd, \rd, #PG_DIR_SIZE
这里明确说明建立的页表只是为了kernel能够运行,因此只映射kernel code.
输入的时候 r8是物理地址偏移(0) r9是cpuid r10是procinfo
返回值r4是页表基地址(0x4000)
下面来看这个操作过程
pgtbl r4,r8被展开为
add r4,r8,#TEXT_OFFSET&表示内核起始地址相对于RAM地址的偏移值&(定义在arch/arm/Makefile中 TEXT_OFFSET := $(textofs-y) &textofs-y:= 0x&)
sub r4,r4,#PG_DIR_SIZE 表示页目录的大小。
我这里变成
ADD & & R4, R8, #0x8000
SUB & & R4, R4, #0x4000
0x4000是页目录的大小为16KB
0x8000表示内核起始地址相对于RAM地址的偏移值&
r4=r8+0x0
得到r4等于物理页表的起始地址
后面就是循环清空这一块地址 从r4-&r4+0x4000。全部清0
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
从proc结构中取出mm_mmuflags标记放到r7中(0xc0e)
__turn_mmu_on_loc:
.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: 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, #1
@ next section
接下来先将 R5左移20位 &然后或上r7,也就是0X 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.
add r0, r4, #PAGE_OFFSET && (SECTION_SHIFT - PMD_ORDER)
ldr r6, =(_end - 1)
orr r3, r8, r7
add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)
1: str r3, [r0], #1 && PMD_ORDER
add r3, r3, #1 && SECTION_SHIFT
cmp r0, r6
在我的反汇编代码中变成了
.head.text:C000811C
R0, R4, #0x3000
.head.text:C0008120
R6, =0xC08F269F
.head.text:C0008124
R3, R8, R7
.head.text:C0008128
R6, R4, R6,LSR#18
.head.text:C000812C
.head.text:C000812C loc_C000812C
CODE XREF: __create_page_tables+7Cj
.head.text:C000812C
R3, [R0],#4
.head.text:C0008130
R3, R3, #0x100000
.head.text:C0008134
.head.text:C0008138
loc_C000812C
PAGE_OFFSET=0XC0000000&
SECTION_SHIFT=20&
PMD_ORDER=2
首先将PAGE_OFFSET(0xc0008000)右移20-2 ,再加上r4(页表物理基地址)得到内核起始链接地址对应页表项的物理地址,保存到r0中
r0等于0x7000 &
将.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+(0xc)*4=pagetable+0xc&&2=pagetable+0xc0008000&&(20-2)=0xxc
以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.
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], #1 && PMD_ORDER
addne r6, r6, #1 && SECTION_SHIFT
strne r6, [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, r0
mov r3, r3, lsr #SECTION_SHIFT
mov r3, r3, lsl #PMD_ORDER
add r0, r4, r3
mov r3, r7, lsr #SECTION_SHIFT
ldr r7, [r10, #PROCINFO_IO_MMUFLAGS] @ io_mmuflags
orr r3, r7, r3, lsl #SECTION_SHIFT
orr r3, r3, #PMD_SECT_XN
str r3, [r0], #4
对于我的树莓派addruart的定义是
.macro addruart, rp, rv, tmp
ldr \rp, =UART0_BASE
ldr \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
#define UART0_BASE
(BCM2708_PERI_BASE + 0x201000) /* Uart 0 */
BCM2708外设的基地址是 0X, UART0口的基地址是 0X201000
所以只要映射 物理地址r7=0X 到虚拟地址r3=IO_ADDRESS(0x)=(0xF2201000)
IO的映射公式 是先取物理地址paddr的低28bit得到v1 ,取得最高位加到v1的[27:24] bit上,最后加上0xf0000000(映射到其他合适的虚拟地址应该也可以???)
首先r3=r3&&(20-2) 得到描述页表项相对于页表基地址的偏移。
r4是页表基地址 r0=r4+r3, 得到r0就是对应的描述页表项的物理地址。
接下来构造页表描述项
再把物理地址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了。
本文已收录于以下专栏:
相关文章推荐
LinuxDeviceDrive...
LinuxDeviceDrive...
LinuxDeviceDrive...
LinuxDeviceDrive...
LinuxDeviceDrive...
LinuxDeviceDrive...
本文整理了ARM Linxu启动流程的第二阶段——start_kernel前启动阶段(汇编部分),内核版本为3.12.35。我以手上的树莓派b(ARM11)为平台示例来分析Linux内核在自解压后到跳...
再介绍pageing_init之前,我们了解几个定义
pte_t 页表项
pmd_t 页中间目录项
pud_t 页上级目录
pgd_t 页全局目录项
我的arm平台
#define PMD_SHIFT...
他的最新文章
讲师:吴岸城
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)文档分类:
下载后只包含 1 个 DOC 格式的文档,没有任何的图纸或源代码,
下载前请先预览,预览内容跟原文是一样的,在线预览图片经过高度压缩,下载原文更清晰。
您的浏览器不支持进度条
下载文档到电脑,查找使用更方便
还剩?页未读,继续阅读
播放器加载中,请稍候...
该用户其他文档
下载所得到的文件列表ARM MMU L1 L2页表计算说明介绍.doc
文档介绍:
第 1页共 47页 ARM 的存储管理单元 MMU 知识点介绍................................................................................................................................................................................................... 1 ARM MMU 页表设置计算........................................................................................................................................................................................................................... 9 ARM 的存储管理单元 MMU 知识点介绍由于 ARM 的MMU 知识体系比较庞大,只总结必须知道了解的 MMU 知识点。(一) MMU 内存管理单元的作用(1)虚拟存储空间到物理存储空间的映射(2)存储器访问权限控制(3)设置虚拟存储空间的缓冲特性(二) MMU 的地址变换的内存块单元(1)段单元:按大小为 1M的内存块单元为单位进行的虚拟地址物理地址之间的变换。第 2页共 47页(2)大页单元:按大小为 64K 的内存块单元为单位进行的虚拟地址物理地址之间的变换。(3)小页单元:按大小为 4K的内存块单元为单位进行的虚拟地址物理地址之间的变换。(4)极小页单元:按大小为 1K的内存块单元为单位进行的虚拟地址物理地址之间的变换。 Linux 在最初的汇编代码中使用段单元的映射机制来实现 MMU 的开启。当 Linux 系统启动之后采用的是小页单元的映射机制,因为我们知道 Linux 把物理内存和虚拟内存的管理是按页来管理的,每个页大小为 4k字节。(三) MMU 的地址变换方式(1 )一级页表地址映射第 3页共 47页一级页表地址段映射,是指按 1M大小的内存块单元进行的地址映射,查找的过程就是找到页表基地址和当前需要转化的虚拟地址的高 12位为索引的页目录项,由于每个目录项都是 4字节对齐的,所以应该为:页表基地址+虚拟地址高 12位X4,从上图中可看出第 4页共 47页一级描述符的地址总是 4字节对齐的,即后两位为 0.一级描述符地址中存放的是一级描述符,一级描述符的格式定义如下: 映射实例: 题目:把内存的地址从 0xx200000 空间映射成虚拟地址 0xcxc0200000 的地址空间. (第一步)根据图 1 的介绍,我们先把内存虚拟地址右移 20位: Table index =(0xc0100000&& 20) =0xc01 (第二步)我们打算用内存地址 0x0 的内存空间内作为存放页表的内存地址。所以把 0x4000 写到 CP15: C2寄存器。注意 0x4000 正好是 16k 对齐。所以这里的一级描述符地址为: 一级描述符地址: (0x4000 + (0xc01 && 2)) = 0x4000 + 0x3004 = 0x7004 ( 第三步)我们根据需要把该虚拟地址映射到实际的物理地址的需求,来构建一级页表描述符: 第 5页共 47页根据图 2,我们为了取得被映射的物理地址基地址,需要将物理地址右移20位: Sectionbase address = (0x100000 && 20) = 0x1 AP,Domain, C,B 属性在实例中不做设置。所以最终的一级页表描述符为: `1cb10 ’=0b 一级页表描述符= (0x1 && 20) + 0x12 =0x100000 + 0x12 = 0x100012 然后把这个 0x100012 (一级页表描述符)写到一级描述符地址 0x7004 指向的内存中去。这样当我们需要访问虚拟内存地址 0xc0100000 的时候, CPU 需要做的事情就是读取 C2寄存器,找到页表基地址 0x4000 ,然后根据 0xc0100000 && 20的index 索引找到一级描述符基地址(0x4000 + (0xc01 &&2)) ,然后读取这个地址里面的一级描述符: 0x100012 , 得到段映射的物理基地址 0x100012 && 20, 然后再与虚拟地址的低 20 位相加,得到对应的物理地址 0x100000. 第 6页共 47页(2 )二级页表地址映射第 7页共 47页二级页表的映射查找过程其实就是,找到页表基地址,然后找到一级描述符,然后通过一级描述符找到二级页表的基地址,然后再找到二级页表的描述符,通过二级页表描述符来找到对应的物理地址基地址。二级描述符结构: 二级描述符的说明可以参考一级描述符的描述。第 8页共 47页(3 )一级,二级页表地址映射的过程图: 目录项和页面表都是存放在内存中的表。虚拟地址和物理地址的转化就是查找这些表的过程。第 9页共 47页 ARM MMU 页表设置计算内存管理单元简称 MMU ,它负责虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查。MMU 使得每个用户进程拥有自己独立的地址空间,并通过内存访问权限的检查保护每个进程所用的内存不被其他进程破坏。重点就在于地址映射:页表的结构与建立、映射的过程。 1、S3C0 MMU 地址变换过程 1)地址的分类一个程序在运行之前,没有必要全部装入内存,仅需要将那些要运行的部分先装入内存,其余部分在用到时从磁盘载入,当内存不足时,再将暂时不用的部分调出到磁盘。这使得大程序可以在较小的内存空间中运行,也使得内存中可以同时装入更多的程序并发执行,这样的存储器一般称为虚拟存储器。虚拟地址最终需要转换为物理地址才能读写实际的数据,通过将虚拟地址空间和物理空间划分为同样大小的空间(段或页) ,然后两个空间建立映射关系。由于虚拟地址空间远大于物理地址,可能多块虚拟地址空间映射到同一块物理地址空间,或者有些虚拟地址空间没有映射到具体的物理地址空间上去(使用到时再映射)。 ARM cpu 地址转换涉及三种地址:虚拟地址( VA,Virtual Address )、变换后的虚拟地址( MVA ,Modified Virtual Address )、物理地址( PA,Physical Address ) 没有启动 MMU 时, CPU 核心, cache ,MMU ,外设等所有部件使用的都是物理地址。启动 MMU 后,CPU 核心对外发出虚拟地址 VA;VA被转换为 MVA 供cache ,MMU 使用,在这里 MVA 被转换成 PA;最后使用 PA读取实际设备①CPU 核心看到和用到的只是虚拟地址 VA,至于 VA如果去1
内容来自淘豆网转载请标明出处.

我要回帖

更多关于 linux mmu初始化 的文章

 

随机推荐