Chinaunix首页 | 论坛 | 博客
  • 博客访问: 662308
  • 博文数量: 255
  • 博客积分: 5000
  • 博客等级: 大校
  • 技术积分: 2811
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-09 13:29
个人简介

IT业行者,行者无疆

文章分类

全部博文(255)

文章存档

2011年(121)

2010年(134)

我的朋友

分类: 嵌入式

2010-10-16 14:22:57

嵌入式Linux应用开发完全手册读书笔记(内存管理单元MMU)

本章目标:

n         了解虚拟地址和物理地址的关系

n         掌握如何通过设置MMU来控制虚拟地址到物理地址的转化

n         了解MMU的内存访问权限机制

n         了解TLBCacheWrite buffer的原理,使用时的注意事项

n         通过实例深刻掌握上述要点

 

u     S3C2410/S3C2440 MMU特性

       内存管理单元MMU负责虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查。现代的多用户多进程操作系统通过MMU使得各个用户都拥有自己独立的地址空间:地址映射功能使得各进程拥有“看起来”一样的地址空间,而内存访问权限的检查可以保护每个进程所用的内存不会被其他进程破坏。

       S3C2410/S3C2440MMU有如下特征:

1)        ARM V4兼容的映射长度、域、访问权限检查机制。

2)        4种映射长度:段(1MB)、大页(64KB)、小页(4KB)、极小页(1KB)。

3)        对每个段都可以设置访问权限。

4)        大页、小页的每个子页(sub-page,即被映射页的1/4)都可以单独设置访问权限。

5)        硬件实现16个域。

6)        指令TLB(含64个条目)、数据TLB(含64个条目)。

7)        硬件访问页表(地址映射、权限检查由硬件自动进行)。

8)        TLB中条目的替换采用round-robin算法(也称cyclic算法)。

9)        可以使无效整个TLB

10)     可以单独使无效某个TLB条目。

11)     可以在TLB中锁定某个条目,指令TLB、数据TLB互相独立。

       本章重点在于地址映射:页表的结构与建立、映射的过程。对于访问权限、TLBCache只作粗略介绍。

 

u       地址的分类

       ARM CPU上的地址转换过程涉及3个概念:虚拟地址(VAVirtual Address),变换后的虚拟地址(MVAModified Virtual Address),物理地址(PAPhysical Address)。

       没有启动MMU时,CPU核、cacheMMU、外设等所有部件使用的都是物理地址。

       启动MMU后,CPU核对外发出虚拟地址VAVA被转换为MVAcacheMMU使用,在这里MVA被转换为PA;最后使用PA读写实际设备。(S3C2410/S3C2440内部寄存器或外接的设备):

1)        CPU核看到的、用到的只是虚拟地址VA,至于VA如何最终落实到物理地址PA上,CPU      核是不理会的。

2)        cachesMMU也是看不见VA的,它们利用由MVA转换得到PA

3)        实际设备看不到VAMVA,读写它们时使用的是物理地址PA

       MVA是除CPU核外的其他部分看见的虚拟地址。

       如果VA < 32M,需要使用进程标识号PID(通过读CP15C13获得)来转换为MVAVAMVAR 的转换方法如下(这是硬件自动完成的):

       if(VA < 32M) then

              MVA =VA | (PID << 25)               //VA < 32M

       else

              MVA = VA                                  //VA >= 32M

       利用PID生成MVA的目的是为了减少切换进程时的代价:不使用MVA而直接使用VA 的话,当两个进程所用的虚拟地址空间(VA)有重叠时,在切换进程时为了把重叠的VA映射到不同的PA上去,需要重建页表、使无效cachesTLSS等,代价非常大。使用MVA后,进程切换就省事多了:假设两个进程12运行时的VA都是0-32M-1),但是它们的MVA并不重叠,分别是0x02000000-0x03ffffff0x04000000-0x05ffffff,这样就不必进行重建页表等操作了。下面说到底的虚拟地址,若没有特别指明,就是指MVA

u       虚拟地址到物理地址的转换过程

       将一个虚拟地址转换为物理地址,一般有两个办法:用一个确定的数学公式进行转换或用表格存储虚拟地址对应的物理地址。这类表格称为页表(Page table),页表由一个个条目(Entry)组成,每个条目存储了一段虚拟地址对应的物理地址及其访问权限,或者下一级页表。

       ARM CPU中使用第二种方法,S3C2410/S3C2440最多会用到两级页表:以段(Section1MB)的方式进行转换时只用到一级页表,以页(Page)的方式进行转换时用到两级页表。页的大小有3种:大页(64KB)、小页(4KB)、极小页(1KB)。条目也称为描述符(Descriptor),有:段描述符、大页描述符、小页描述符、极小页描述符——它们保存段、大页、小页和极小页的起始物理地址;粗页表描述符、细页表描述符——它们保存二级页表的物理地址。

       大概的转换过程如下,请参考图7.3、图7.4(图7.3是通用的转换过程,图7.4是针对ARM CPU细化的转换过程):

1)根据给定的虚拟地址找到一级页表中的条目;

2)如果此条目是段描述符,则返回物理地址,转换结束;

3)否则如果此条目是二级页表描述符,继续利用虚拟地址在二级页表中找到下一个条目;

4)如果这第二个条目是页描述符,则返回物理地址,转换结束;

5)其他情况出错。

       7.3/7.4中的“TTB base”代表一级页表的地址,将它写入协处理器CP15的寄存器C2(称为页表基址寄存器)即可。一级页表的地址必须是16K对齐的(位[140]0)。

       下面先介绍一级页表。32CPU的虚拟地址空间达到4GB,一级页表中使用4096个描述符来表示这4GB空间——每个描述符对应1MB的虚拟地址,要么存储了它对应的1MB物理空间的起始地址,要么存储了下一级页表的地址。使用MVA[3120]来索引一级页表,得到一个描述符,每个描述符占据4字节。

       根据一级描述符的最低两位,可分为以下4种:

10b00:无效

20b01:粗页表(Coarse page table

       [3210]称为粗页表基址(Coarse page table base address),此描述符的低10位填充0后就是一个二级页表的物理地址。此二级页表含256个条目(所以大小为1KB),称为粗页表(Coarse page table)。其中每个条目表示大小为4KB的物理地址空间,所以一个粗页表表示1MB的物理地址空间。

30b10:段(Section

       [3120]称为段基址(Section base),此描述符的低20位填充0后就是一块1MB物理地址空间的起始地址。MVA[190]用来在这1MB空间中寻址。所以,描述符的位[3120]MVA[190]就构成了这个虚拟地址MVA对应的物理地址。

40b11:细页表(Fine page table

       [3112]称为细页表基址(Fine page table base address),此描述符的低12位填充0后,就是一个二级页有的物理地址。此二级页表含1024个条目(所以大小为4KB),称为细页表(Fine page table)其中每个条目表示大小为1KB的物理地址空间,所以,一个细页表表示1MB的物理地址空间。

       以大页、小页或极小页进行地址映射时,需要用到两级页表,二级页表有粗页表和细页表两种。

       根据二级描述符的最低两位,可分为以下4种情况:

10b00:无效

20b01:大页描述符。

       [3116]称为大页基址(Large page base address),此描述符的低16位填充0后就是一块64KB物理地址空间的起始地址。粗页表中每个条目只能表示4KB的物理空间,如果大页描述符保存在粗页表中,则连续16个条目都保存同一个大页描述符。类似的,细页表中每个描述符只能表示1KB的物理空间,如果大页描述符保存在细页中,则连续64个条目都保存同一个大页描述符。

30b10:小页描述符

       [3112]称为小页基址,此描述符的低12位填充0后就是一块4KB物理地址空间的起始地址。粗页表中每个条目表示4KB的物理空间,如果小页描述符保存在粗页表中,则只需要用一个条目来保存一个小页描述符。类似的,细页表中每个条目只能表示1KB的物理空间,如果小页描述符保存在细页表中,则连续4个条目都保存同一个小页描述符。

40b11:极小页描述符

       [3110]称为极小页基址,此描述符的低10位填充0后就是一块1KB物理地址空间的起始地址。极小页描述符只能保存在细页表中,用一个条目来保存一个极小页描述符。

 

u       MMU使用实例:地址映射

       本开发板SDRAM的物理地址范围处于0x30000000 - 0x33FFFFFFS3C2410/S3C2440的寄存器地址范围都处于0x48000000 - 0x5FFFFFFF。在第5章中,通过往GPBCONGPBDAT这两个寄存器的物理地址0x560000100x56000014写入特定数据来驱动4LED

       本章的实例中,将开启MMU,并将虚拟地址空间0xA0000000 - 0xA0100000映射到物理地址空间0x56000000 - 0x56100000上,这样,就可以通过操作地址0xA00000100xA0000014来达到驱动这4LED的同样的效果。

       另外,将虚拟地址空间0xB0000000 - 0xB3FFFFFF映射到物理地址空间0x30000000 - 0x33FFFFFF上,并在连接程序时将一部分代码的运行地址指定为0xB0004000(这个数值有些奇怪,看下去就会明白),看看能否使程序跳转到0xB0004000处执行。

       本章程序只使用一级页表,以段的方式进行地址映射。32CPU的虚拟地址空间达到4GB,一级页表中使用4096个描述符来表示这4GB空间(每个描述符对应1MB的虚拟地址),每个描述符占用4字节,所以一级页表占16KB。本实例使用SDRAM的开始16KB来存放一级页表,所以剩下的内存开始物理地址为0x30004000

       将程序代码分为两部分:第一部分的运行地址设为0,它用来初始化SDRAM、复制第二部分代码到SDRAM中(存放在0x30004000开始处)、设置页表、启动MMU,最后跳到SDRAM中(地址0xB0004000)去继续执行;第二部分的运行地址设为0xB0004000,它用来驱动LED

       根据上面的描述,程序流程图如下所示:

程序源代码有3个文件:head.Sinit.cleds.c

1)、head.S代码详解

head.S文件如下:

@*************************************************************************

@ Filehead.S

@ 功能:设置SDRAM,将第二部分代码复制到SDRAM,设置页表,启动MMU

@       然后跳到SDRAM继续执行

@*************************************************************************      

 

.text

.global _start

_start:

    ldr sp, =4096                       @ 设置栈指针,以下都是C函数,调用前需要                                        @ 设好栈

    bl  disable_watch_dog               @ 关闭WATCHDOG,否则CPU会不断重启

    bl  memsetup                        @ 设置存储控制器以使用SDRAM

    bl  copy_2th_to_sdram               @ 将第二部分代码复制到SDRAM

    bl  create_page_table               @ 设置页表

    bl  mmu_init                        @ 启动MMU

    ldr sp, =0xB4000000                 @ 重设栈指针,指向SDRAM顶端(使用虚拟地址)

    ldr pc, =0xB0004000                 @ 跳到SDRAM中继续执行第二部分代码

halt_loop:

    b   halt_loop

       head.S中调用的函数都在init.c中实现。

       值得注意的是,在第15行开启MMU后,无论是CPU取指还是CPU读写数据,使用的都是虚拟地址。

       在第14行设置页表时,在create_page_table函数中令head.Sinit.c程序所在内存的虚拟地址和物理地址一样,这使得head.Sinit.c中的代码在开启MMU后能够没有任何障碍地继续运行。

 

2init.c代码详解。

       init.c中的disable_watch_dogmemsetup函数实现的功能在前面两章已经讨论过,不再重复,下面列出代码方便阅读。

 

/*

 * init.c: 进行一些初始化,在Steppingstone中运行

 * 它和head.S同属第一部分程序,此时MMU未开启,使用物理地址

 */

 

/* WATCHDOG寄存器 */

#define WTCON           (*(volatile unsigned long *)0x53000000)

/* 存储控制器的寄存器起始地址 */

#define MEM_CTL_BASE    0x48000000

 

 

/*

 * 关闭WATCHDOG,否则CPU会不断重启

 */

void disable_watch_dog(void)

{

    WTCON = 0;  // 关闭WATCHDOG很简单,往这个寄存器写0即可

}

 

/*

 * 设置存储控制器以使用SDRAM

 */

void memsetup(void)

{

    /* SDRAM 13个寄存器的值 */

    unsigned long  const    mem_cfg_val[]={ 0x22011110,     //BWSCON

                                            0x00000700,     //BANKCON0

                                            0x00000700,     //BANKCON1

                                            0x00000700,     //BANKCON2

                                            0x00000700,     //BANKCON3 

                                            0x00000700,     //BANKCON4

                                            0x00000700,     //BANKCON5

                                            0x00018005,     //BANKCON6

                                            0x00018005,     //BANKCON7

                                            0x008C07A3,     //REFRESH

                                            0x000000B1,     //BANKSIZE

                                            0x00000030,     //MRSRB6

                                            0x00000030,     //MRSRB7

                                    };

    int     i = 0;

    volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;

    for(; i < 13; i++)

        p[i] = mem_cfg_val[i];

}

 

copy_2th_to_sdram函数用来将第二部分代码(即由leds.c编译得来的代码)从Steppingstone中复制到SDRAM中,在连接程序时,第二部分代码的加载地址被指定为2048,重定位地址为0xB0004000,所以系统从NAND Flash启动后,第二部分代码就存储在Steppingstone中地址2048之后,需要把它复制到0x30004000处(此时尚未开启MMU,虚拟地址0xB0004000对应的物理地址在后面设为0x30004000)。Steppingstone总大小为4KB,不妨把地址2048之后的所有数据复制到SDRAM中,所以源数据的结束地址为4096

       copy_2th_to_sdram函数的代码如下:

/*

 * 将第二部分代码复制到SDRAM

 */

void copy_2th_to_sdram(void)

{

    unsigned int *pdwSrc  = (unsigned int *)2048;

    unsigned int *pdwDest = (unsigned int *)0x30004000;

   

    while (pdwSrc < (unsigned int *)4096)

    {

        *pdwDest = *pdwSrc;

        pdwDest++;

        pdwSrc++;

    }

} 

      

阅读(872) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~