Linux系统中ARM体系的内存分页认识

来源:互联网 发布:游戏编程基础 编辑:程序博客网 时间:2024/04/29 05:22

Linux系统下ARM芯片内存分页

的一个认知文档,

阅读本文前认为读者了解MMU

 

目 录

1. 概述... 5

2. LINUX的内存分页管理... 5

3. ARM的分页模式... 5

4. 内存分页相关的数据结构... 5

5. 重要的系统函数调用... 5

6.物理内存的定制... 5

7.虚拟空间到物理空间的映射... 5

8.几个相关问题的讨论... 5


一. 概述

内存管理是Linux系统的一个极其重要部分,涉及到虚拟-物理地址映射和寻址,内存分页,内存换页,内核和用户进程的内存分配,内存文件系统等许多内容,本文仅对Linux系统ARM体系结构的内存分页,映射寻址的认识作一个描述。

二.Linux支持的分页管理

为支持多种平台处理器的虚拟内存管理,Linux(2.6.1)版本以后采用了4级分页虚拟地址映射(此版本前采用3级分页映射)的模式,可满足64位CPU的寻址要求。不过,ARM9的MMU只支持两级页表地址转换,且两级能满足32位CPU的存储管理需求,因此ARM体系只使用linux四级中的两级分页。Linux四级分页模式如下图:

clip_image002

第一级:

全局页目录表——对应于代码中的PGD (见代码pgtable.h),系统运行时这个页表的首地址存放于ARM协处理器CP15的寄存器C2中,C2寄存器是和堆栈指针SP,程序指针PC同等重要的寄存器,在进行任务(进程)切换时,没有虚拟内存的操作系统只切换SP和PC,有虚拟内存的操作系统增加切换C2,即每个进程都有自己的独立虚拟空间,也就有独立的全局页目录表PGD。

第2、3、4级

这三级行为基本相同,程序中分别缩写为

PUD——页上级目录

PMD——页中间目录

PTE——页表(最末级)

当系统访问一个32位虚拟地址时,假设cache未命中且TLB(页地址快表)中未存有这个地址页的情况下,MMU将全局页目录存放PGD的地址读出来,取虚拟地址的前若干位(32-宏PGDIR_SHIFT)在该表中进行索引,索引到的32位整数即是下一级页表PUD的首地址,一直索引到最末级页表PTE,PTE中对应的索引项内容(32位整数)前若干位为虚拟地址对应的物理页地址,页内偏移量为虚拟地址的后若干位,这样即可以得到一个32位的物理地址。

PTE索引项的后若干位,记录了本页的访问属性和访问权限,读写标志,访问标志等,以便MMU进行控制。

Linux中的宏PAGE_SHIFT, PMD_SHIFT,PUD_SHIFT,PGDIR_SHIFT分别定义了这四级分页各自占用的字段长度。(../arch/arm/include/asm/pgtable.h))

三.ARM9体系MMU支持的分页模式

ARM存储体系支持的页的大小有几种——1M,64KB,4KB,1KB,支持的二级页表大小有两种:粗粒度和细粒度。在linux中,ARM采用了粗粒度页表4K页的模式如下图,其中一级索引地址有效位为11位,二级索引有效位为9位,页内偏移量为12位:

clip_image004

4KB的页大小决定了虚拟地址的低12bit留作偏移地址用。也决定了二级页描述符的低12位作为用户标志用,4K的页大小还决定了虚拟地址空间最多可以映射出(4GB/4KB=1024×1024)个页。程序中下列宏用于定义页的大小:(arch/arm/include/asm/page.h)

#define PAGE_SHIFT 12

#define PAGE_SIZE (1UL << PAGE_SHIFT)

由上可知二级页表加页内偏移的总长度为12+9=21,因此程序中定义中间页表PMD的位数为:(arch/arm/include/asm/pgtable.h)

#define PMD_SHIFT 21

#define PGDIR_SHIFT 21

由各索引表的位数可得到各表的长度:

#define PTRS_PER_PTE 512 表示每个末级页表PTE中含有512个条目(9bit)

#define PTRS_PER_PMD 1 表示中间页表PMD表等同于末级页表PTE

#define PTRS_PER_PGD 2048 表示全局页目录表中含有2048个条目(11bit)

因此概括为,ARM体系下物理内存和虚拟内存按照4KB的大小进行分页,页索引表分为两级,其中全局一级页表PGD一个,表中含有2048个条目,每个条目对应一个二级页表物理首地址。二级页表(PMD或PTE)最多2048个,每个表中含有512个条目,每个条目对应一页物理首地址。

结合起来,ARM体系在Linux系统下二级分页可简要表示如下:

clip_image006

启动PUD这一级被屏蔽使用,而PMD则等同于PTE为同一个级别,因此ARM在linux下二级分页为:

虚拟地址——> PGD转换——> PTE转换——> 物理地址


四、相关数据结构

1、页描述符,定义在 include/linux/mm_types.h文件中

struct page {,

}

每个(物理)页具有一个此类型的数据结构,保存了该页的状态信息,例如该页是属于内核还是用户,是否空闲,引用计数,是否缓存等等信息,该结构占用32字节空间,内核通过这个结构掌握一个页的信息。有多少个页,就有多少个页描述符,页描述符统一由内核保存在mem_map数组中。需要占用整个物理内存的1/128

2、页表项,页中间目录项,页上级目录项和页全局目录项的数据结构均为32bit整数:

typedef struct { unsigned long pte; } pte_t; ——页表项的类型

typedef struct { unsigned long pmd; } pmd_t; ——页中间目录项类型

typedef struct { unsigned long pgd[2]; } pgd_t; ——页全局目录项类型

3、进程描述符task_struct中的mm_struct(include/linux/sched.h),这个结构负责描述进程的存储分配,一片被进程控制的连续线性称为一个VMA(vm_area_struct),进程的线性空间中由多个VMA链表连接而成。

五、内存相关的重要的调用

pgd_alloc 分配一个新的全局页目录表,通常用在进程初始化阶段

brk ——调节进程的动态数据段大小。

_get_free_pages 获得连续的页

_alloc_pages 获得连续的页

kmalloc 从slab cache分配空间且其物理内存为连续的

vmalloc 在虚存区分配空间其虚拟内存连续,但物理内存不一定连续

shmget 分配共享存储区,用于进程间共享

mmap ,munmap 将一个文件映射到内存

六、物理内存的定义

Linux系统只需要给出物理内存的首地址和大小,即可定义物理内存给内核使用,相关宏位于/arch/arm/include/asm/memory.h中的

定义物理内存首地址:

#define PHYS_OFFSET (CONFIG_DRAM_BASE)

/*
 * Physical DRAM offset.
 */
#if defined(CONFIG_ARCH_OMAP1)
#define PHYS_OFFSETUL(0x10000000)
#elif defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3) || \
defined(CONFIG_ARCH_OMAP4)
#define PHYS_OFFSETUL(0x80000000)      //omap3530 物理地址
#endif


定义物理内存末地址:

#define END_MEM (CONFIG_DRAM_BASE + CONFIG_DRAM_SIZE)

定义物理页起始地址

#define PAGE_OFFSET (PHYS_OFFSET)

/*

define CONFIG_PAGE_OFFSET 0xC0000000    //omap3530

/*
 * PAGE_OFFSET - the virtual address of the start of the kernel image
 * TASK_SIZE - the maximum size of a user space task.
 * TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area
 */
#define PAGE_OFFSETUL(CONFIG_PAGE_OFFSET)

*/

七、从虚拟空间到物理空间的映射

虚拟空间的低3GB部分从0-0XBFFFFFFF的虚拟线性地址,用户态和内核态都可以寻址,这部分也是每个进程的独立空间。

虚拟空间的高1G部分从0XC0000000到0XFFFFFFFF的虚拟地址,只有内核态的进程才能访问,这种限制由页目录和页表描述符的权限标志位决定,通过MMU自动控制。

从虚拟空间的分布,以及到物理地址的空间映射简要可见下图:

clip_image008

高1G的内核空间又分为几部分:

1、 线性地址映射区kernel logical address:从0xc0开始之后的最大896M空间,当物理内存小于896M时等于物理内存大小。这部分区域的虚拟地址和物理地址线性映射,虚拟地址和物理地址之间只差一个相同的常数,所有物理内存也在这里纳入内核统一管理。通过kmalloc在本区域内申请到的物理内存是连续的。

2、 虚拟地址映射区(vmalloc区):这一区域内的虚拟地址是连续的,但对应的物理地址可能是不连续的(分页),通过vmalloc在此区域内申请到的存储块其物理地址可能是不连续的。

3、 高端映射区:当物理内存大于896M时,超出的物理内存部分无法由内核直接寻址,通过此段区域间接寻址。

低3G的用户空间主要分为静态的代码段,只读数据段,可写数据段,共享库和动态的栈和堆。动态的栈(stack)通过其自行向下生长调整,动态的堆可通过系统调用brk进行调整,当进程堆不足时,进程向内核申请分配更多的页进行调整。

综上所述,从虚拟空间到物理空间的映射存在多种情况,同一个物理页可能被映射到多个虚拟页,如下图:kernel logical地址即上面的线性地址映射区,其映射是线性连续的不交错,kernel virtual 地址即上面的vmalloc区,其映射到物理空间是交错的不连续。而用户进程空间各种数据映射到的物理地址,是交错的不连续的,且和内核使用的物理地址区域是重叠的。下图描述了一般的情况:

clip_image009

八、几个其他和内存分页相关问题的讨论:

1、物理空间由谁控制?

物理空间统一由内核控制,在需要时分配给用户进程。

2、进程间如何隔离?

每个进程有自己的PGD(全局页目录表),因此每个进程有自己的空间映射,内核保证各进程映射使用不同的物理空间,从而隔离。

3、如何保证进程不能访问内核数据?

由页描述符的权限标志位(System/User)控制,联合ARM9上对应寄存器C1中的S,R位,虚拟地址大于0XC0000000的页,其页描述标志为只能由ARM系统模式访问。

4、虚拟内存到物理内存的映射真的能保证系统安全稳定吗?

主处理器使用的指针全部是虚拟地址,因此主处理器能访问的数据只能是映射以内的数据,从而保证了运行在主处理器中的用户任务不会访问不属于自己的数据。(基于内核对自己是信任的这个假设)

但主处理器以外的AMBA总线-Host设备除外,例如DMA控制器,其接到总线没有通过MMU,因此使用的还是物理地址。若用户进程给DMA这种控制器赋予一些非法的物理地址(对主处理器来讲是数据),则有可能冲毁物理内存的任何区域数据!!!

5、用户程序为何一般不和linux系统一起编译?

因用户程序一般都有新的进程,而进程的创建只能是复制的形式(fork或clone),因为要创建全新进程空间的需要,进程的入口函数只能通过exec启动一个二进制进程文件,而不能是一个静态编译的目标函数(因为静态编译无法使进程的主函数位于另一个独立的3G进程空间。)因此一般用户程序和各种库独立于linux编译。此问题我们在其他进程相关的认识文档中再作进一步讨论。

6、应用程序大于物理内存时是否可以执行?

可以执行。分页和换页的机制使得应用程序不必全部加载到内存,而只是加载当前使用的部分页面到内存,从而保证大于物理空间的应用程序能够运行。只是执行效率问题。

有关存储管理的其他问题本文暂不讨论,有关错误和不足望不佞指教。