2.4 VxWorks内存管理——虚拟地址空间支持

内存管理并非是简单的使物理内存可被使用。通常说到内存管理,就认为是否使能MMU,更进一步地说,即是否使用虚拟地址。一般CPU都包含MMU硬件单元,但是MMU的使用与否由操作系统决定,当然使用MMU硬件单元是需要做一系列准备工作的,如系统页表的建立、配置CPU硬件控制寄存器等。虽然存在这些准备工作,这与使用MMU机制之后的优势相比也是值得的。MMU可以管理物理内存非连续的应用环境,可以对单个页面控制是否进行Cache(缓存)以及应用权限限制,可以让一个很大的程序与其他程序一同运行,而非独占物理内存或者无法运行。

是否使能MMU只是内存管理的一个重要方面,内存管理还包括:操作系统如何安排各组件(内核层次的和应用层次的)在物理内存中的布局;如何提供接口可让BSP开发人员提供内存映射的关系,因为每个平台的物理内存地址空间不同,需要根据特定平台的特殊情况定制映射关系;是否提供某种策略可让用户预留一段物理内存以供私用,如专供某个驱动存储信息等。这些都可以看做是内存管理的一部分。本节将介绍涉及内存使用以及内存管理的各个方面。

由于各处理器下内存管理的方式有些差别(如有些处理器包含MMU硬件支持,有些则不然),故下文讨论中我们以ARM处理器为例进行介绍。ARM处理器内部包含MMU硬件单元,MMU的使能通过CP15控制寄存器的相关位来进行控制。系统上电执行的初始阶段,MMU是禁止使用的,如果使用bootrom,那么在bootrom运行的整个过程中,MMU都是禁止的,一般只在VxWorks操作系统中才使能MMU。使用MMU之前,必须在内存中创建一张页表,对于ARM处理器而言,这个页表分为两个层次(实际上,为了节省内存,基本上所有的操作系统都是通过分层次的方式建立页表的),第一层次称为页目录表,第二层次称为页表。

在MMU使能的情况下,系统使用两套地址:虚拟地址和物理地址。虚拟地址是指令中使用的地址,这个地址必须经过MMU单元的转换方能输出到CPU外部总线上访问实际RAM;物理地址即实际出现在地址总线上的地址,这个地址直接输入到RAM的地址管脚,用以寻址RAM中的某个存储单元。MMU单元的转换必须要有页表的配合方能完成,这个页表一般由操作系统进行创建。具体到VxWorks下,为了适应各种平台的地址映射关系,内核提供一个内核变量供BSP开发人员使用,用以表示该平台上的地址映射关系,这个内核变量在sysLib.c文件中被初始化,变量名为sysPhysMemDesc,这个变量的初始化示例如下。

        PHYS_MEM_DESC sysPhysMemDesc [] =
        {
            /* adrs and length parameters must be page-aligned (multiples of 0x1000) */
            /* iram 16K */
            {
                  (void *)ARM_I_RAM_BASE,   /* virtual address */
                  (void *)ARM_I_RAM_BASE,   /* physical address */
                  ROUND_UP(ARM_I_RAM_SIZE, PAGE_SIZE), /* length, then initial state: */
                  VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE |
                            VM_STATE_MASK_CACHEABLE,
                  VM_STATE_VALID| VM_STATE_WRITABLE | VM_STATE_CACHEABLE
            },
            …
            /* DDRAM */
            {
                  (void *) ARM_DDR2_BASE,   /* virtual address */
                  (void *) ARM_DDR2_BASE,   /* physical address */
                  ROUND_UP (ARM_DDR2_SIZE, PAGE_SIZE), /* length, then initial state: */
                  VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE |
                            VM_STATE_MASK_CACHEABLE,
                  VM_STATE_VALID| VM_STATE_WRITABLE | VM_STATE_CACHEABLE
            }
        };

更确切地说,sysPhysMemDesc是一个结构数组,数组中每个元素都是一个PHYS_MEM_DESC结构,这个结构定义在h/vmLib.h文件中,定义如下:

        typedef struct phys_mem_desc
        {
            void *virtualAddr;
            void *physicalAddr;
            UINT len;
            UINT initialStateMask;      /* mask parameter to vmStateSet */
            UINT initialState;          /* state parameter to vmStateSet */
        } PHYS_MEM_DESC;

VxWorks内核初始化过程将依据sysPhysMemDesc变量构建系统页表。对于一般通用操作系统(如Linux),其页表的构建是动态的,即在操作系统初始化过程中,只对操作系统本身代码已使用的内存空间进行页表项的构建,而其他内存空间用做动态内存在以后使用的过程中不断地对系统页表项进行添加。

更进一步地说,每个用户进程都有自己的一份页表,这份页表中对于内核代码部分的映射都是相同的,只在自己的地址空间所对应的页表项上映射到的物理页表才有差别。在进程进行调度时,系统页表的更换作为调度的一部分完成。所以,在Linux下所有的用户进程可以使用完全相同的虚拟地址0x80000000,程序运行前都被链接到这个相同的虚拟地址处。只是在映射到物理内存时,才分配到不同的物理地址处,由于内核代码在操作系统初始化过程中已被复制到固定的物理内存中,故所有的用户进程对内核代码的映射都是一致的,且提供给用户的内核代码一般都是可重入的(reenterable),所以各进程对于内核代码的调用都不存在问题。

注意

通用操作系统中一个可运行的用户程序一般都是通过链接的,如Linux下的.out文件、Windows下的.exe文件,这些文件中的代码都具有实际运行时使用的绝对虚拟地址。

VxWorks下使用MMU时虽然也存在页表表示的映射关系,但是其工作机制却从根本上不同于通用操作系统。首先,VxWorks运行的所有任务(进程)都使用相同的页表,换句话说,VxWorks操作系统中只有一张页表,所有的任务都使用这张页表;其次,也是非常关键的一点,VxWorks运行的文件都是未链接的,在程序运行之前,都需通过loadModule函数的即时动态链接。

Tornado环境下编译的.out文件实际上也是一个未链接文件,或者更准确地说,是一个可链接的文件,如此不同的程序经过loadModule函数被动态链接到不同的物理地址处,才将各个任务的地址空间区分开来而不造成冲突。如此就引起另一个问题,即VxWorks的内存分配都是针对物理内存直接进行的,而在使能MMU的情况下,访问内存使用的是虚拟地址,这就表示物理地址被直接用做了虚拟地址。这个关系决定了系统页表中虚拟地址到物理地址的映射必须是相同的,即虚拟地址必须等于物理地址,否则整个系统将无法正常工作。所以,在VxWorks下使用MMU和使用系统页表并非是实现通常操作系统下灵活的映射机制,而更多的是提供面向页的Cache机制和权限控制机制。

实际上,VxWorks下的地址映射关系是非常固定的,在系统初始化过程中,这个系统页表就被完全建立,在建立完成后,基本不存在改变系统页表项的操作(虽然VxWorks支持动态添加页表项),在这一点上完全不同于通用操作系统。

我们可以对VxWorks下内存映射机制进行总结。

① VxWorks下整个系统只有一张页表,所有的任务都在使用这张页表。

② VxWorks系统页表在初始化过程中就被完全创建,其后基本不会改变该页表,虽然VxWorks支持动态改变页表项。这同时也表示BSP开发任务必须仔细编写sysPhysMemDesc变量定义,对其后操作系统程序以及所有要运行的用户程序需要访问的地址都必须进行映射,包括主内存、所有外设的寄存器地址区域。

③ 在页表中,所有的物理内存都被进行了映射,但并不表示对应的物理内存已被使用,内存的使用与否由VxWorks内核另外的机制表示。

④ VxWorks下虽然使用MMU和页表,但是使用的虚拟地址实际上就是物理地址,这是由VxWorks的内存分配方式决定的。VxWorks下分配内存函数返回的地址直接就是物理内存地址,这个返回的物理内存地址被直接用于访问物理内存,即物理地址被直接用做虚拟地址,这就从根本上决定了页表项中的内容必须是虚拟地址等于物理地址。

⑤ VxWorks所有程序的运行都要经过VxWorks内核的重新动态链接操作,这个链接由loadModule及其同族函数完成,包括Tornado环境下编译生成的.out文件也是要经过VxWorks内核的重新链接才被执行的,且生成的.out文件本身就是被设计为可被重新链接的。

⑥ VxWorks下使用MMU更多的是提供面向页(一页一般为4KB)的Cache机制和权限控制机制,而非提供虚拟地址和物理地址之间灵活的映射关系。

内存管理的另一个重要方面是如何安排VxWorks内核以及各种信息在内存中的布局。如图2-2所示是ARM处理器平台下物理内存的布局示意图。注意:不同版本的VxWorks内核内存布局存在差别,图2-2中所示为VxWorks 5.5内核在ARM系统结构下对应的布局。

图2-2 内核布局(ARM体系结构)

图2-2中几点注意:

① 某些组件的包含与否也会影响内存布局,例如,如果不包含WDB组件,那么图2-2中WDB内存池将被并入系统内存池。

② “Persistent Mem Reserved”为错误探测和报告组件预留的内存空间,这个空间的大小由PM_RESERVED_MEM表示。

③ 异常向量表(Vectors)和异常跳转指针(Exception Pointers)一般都以绝对地址0为基址。ARM处理器要求系统异常向量表在绝对地址0或者高端地址处创建,ARM处理器内部绝对地址0处集成有IRAM,故一般将异常向量表创建在这个IRAM首端(地址为0)。图2-2中没有表示出这一点,而是以LOCAL_MEM_LOCAL_ADRS为基址进行了表示,这一点是不确切的。此时就必须要求LOCAL_MEM_LOCAL_ADRS为0。除此之外,从BootLine开始都是以LOCAL_MEM_LOCAL_ADRS进行偏移是正确的。但如前文所述,这个偏移量的值根据VxWorks内核版本的不同而有所差别,但是从总体顺序上是没有变化的。

④ “initial stack”使用在usrRoot函数执行之前,usrRoot函数执行在任务上下文中,在usrRoot函数之前运行所有的代码都没有任务上下文,我们将其类比于中断上下文,“initial stack”就被这些代码使用。每个任务都具有自己独立的栈,故从usrRoot函数开始就在使用任务自身的栈,“initial stack”自此以后将不再被使用。“initial stack”大小在ARM平台下设置为512B,其栈顶就是RAM_LOW_ADRS,栈底则为(RAM_LOW_ADRS-512),当然这是对于一般“向下增长型(grows down)”栈而言的。

要使VxWorks包含内存管理相关组件,必须在configAll.h或者config.h文件中包含如下宏定义。

● INCLUDE_MMU_GLOBAL_MAP:该宏定义表示根据sysLib.c文件中sysPhysMem-Desc变量定义构建系统页表。

● INCLUDE_MMU_BASIC:该宏定义表示包含vmBaseLib API,这些API用于对系统页表的动态管理,如添加一个新的页表项或删除一个已有的页表项或改变某个页表项的状态位(如是否使能cache)。

注意

对于5.5版本的VxWorks内核,需要使用INCLUDE_MMU_FULL宏定义来进行MMU的初始化。

VxWorks操作系统初始化过程中,调用usrMmuInit函数完成MMU的配置,包括系统页表的建立以及相关硬件寄存器的配置以使能MMU。注意:如果映射的内存空间较大,在usrMmuInit函数中将执行较长的时间,内核需要分配内存,创建页表项。

内存管理是操作系统实现的一个重要方面,其决定了内存的使用效率。而内存管理的实现一般都要涉及MMU机制,不同处理器架构具有不同的MMU硬件单元,虽然从总体上说,都需要一个系统页表配合硬件MMU单元工作,但是系统页表的层次结构以及页表项的定义都不相同,不同处理器都有自己的特殊要求。从BSP移植和开发角度而言,读者只需要知道要使能MMU,必须首先在内存中创建一个系统页表,该页表表达了虚拟地址与物理地址之间的映射关系,VxWorks下这个映射关系由sysLib.c文件中sysPhysMemDesc[]数组表示,BSP开发人员只需要根据特定平台的地址空间分配来初始化这个数组即可。另外为了包含MMU内核相关组件,如usrMmuInit函数的调用,用户还必须进行相关宏的声明,如INCLUDE_MMU_FULL、INCLUDE_MMU_BASIC,具体配置选项,请用户参考文献“VxWorks kernel progammer’s guide”。

VxWorks下对MMU机制以及虚拟地址和物理地址关系处理上都比较特殊,其中最为关键的一点是VxWorks的系统页表在初始化的过程中基本上一次性完全创建,其后基本无须进行改变,这就决定了虚拟地址和物理地址之间的映射关系是固定的,且是一一对应的关系,虽然VxWorks内核提供了可动态修改系统页表项的接口函数,但是很少使用。基于VxWorks对于MMU机制的特殊使用方式,VxWorks下的文件都是可链接的文件,需用通过VxWorks自身提供的loadModule及其同族函数进行动态链接后方可执行,包括Tornado环境下编译生成的.out文件也是一个可链接文件。1