首页 理论教育地址映射框架简介-深入剖析Linux内核与设备驱动

地址映射框架简介-深入剖析Linux内核与设备驱动

【摘要】:首先要明确,这里讲述的地址映射框架主要是指内核空间虚拟地址的使用和映射。嵌入式系统通常是采用ARM的处理器,相应的地址映射框架自然就从Linux内核的ARM体系结构入手。图4-19ARM内核地址空间映射关系2.内核中的映射实现和相关的权限管理地址映射的代码框架如图4-20所示。图4-20ARM地址映射代码框架谈到create_mapping的映射,就会涉及体系结构中不同的访问方式和权限。

首先要明确,这里讲述的地址映射框架主要是指内核空间虚拟地址的使用和映射。mmap等用户空间的映射相关功能会在内存管理和实际的设备驱动部分再进行介绍。

1.ARM体系结构空间分配

对于地址映射框架,首先要考虑的就是体系结构。嵌入式系统通常是采用ARM的处理器,相应的地址映射框架自然就从Linux内核的ARM体系结构入手。相应的地址映射,内核大牛们已经为开发人员设计好地址空间的分布并制定了规范,相应的信息如下(摘自Documents/arm/memory.tx文件)。注意ARM体系结构和相应的Linux内核支持也是不断演进的,相应的地址映射也会随之进行一定的变化,但是通常都是增加相应的细化空间,而不是进行大的调整。

978-7-111-49426-3-Chapter04-80.jpg

978-7-111-49426-3-Chapter04-81.jpg

32位的ARM系统,整个的地址空间只有4GB,其中还要为用户应用保留大部分的地址空间,所以通常来说32位体系结构会保留1GB左右的地址空间给内核使用。对于这1GB的空间,向量表的位置是不允许移动的。ARM体系结构中向量表可以是低地址或者高地址两种模式,通常处理器都是使用高地址。其他的空间则根据需要进行设计。

从映射说明文档中可见,在ARM体系结构中,地址空间分配满足各种需求。其中包括加速内核地址换算(PAGE_OFFSET到high_memory-1)的内存区域lowmem,该区域是线性映射(linear mapping)区域;VMALLOC_START至VMALLOC_END-1的区域用于离散页的分配,可减少碎片,增加内存的使用率,另外这部分地址空间在新的内核中还涵盖了设备IO地址空间(ARM是统一地址的体系结构),设备IO地址需要在启动时在该空间中保留一部分静态空间,作为后续的IO地址映射使用;超过地址空间的内存可以通过定义HIGH- MEM功能使用,PKMAP_BASE至PAGE_OFFSET-1的空间进行临时的映射和使用;其他还有为设备特别保留的空间等。可见在整个地址空间分配上已经考虑得十分周详。当然上述的空间不都是必须使用的,还是要按照实际的需要进行配置,通常情况下ARM体系结构下的地址映射关系如图4-19所示。

978-7-111-49426-3-Chapter04-82.jpg

图4-19 ARM内核地址空间映射关系

2.内核中的映射实现和相关的权限管理

地址映射的代码框架如图4-20所示。地址映射实际操作的接口是创建页表的create_mapping函数。ARM(统一地址体系结构的处理器)中无论是内存还是IO的映射最终都是通过create_mapping来实现的。而iotable_init则是提供给处理器的调用接口,用来进行IO空间的映射。

978-7-111-49426-3-Chapter04-83.jpg

图4-20 ARM地址映射代码框架

谈到create_mapping的映射,就会涉及体系结构中不同的访问方式和权限。了解这些访问控制,对于理解内核的工作十分有帮助。

首先来看看create_mapping的传入参数的数据结构:

978-7-111-49426-3-Chapter04-84.jpg

该结构的信息包括虚拟地址、物理地址、长度和类型。类型表示映射的种类和属性,如映射的是物理内存还是设备空间,相应的空间是否可以共享或者是否允许Cache等。具体的定义在两个文件中,内容如下:

978-7-111-49426-3-Chapter04-85.jpg

978-7-111-49426-3-Chapter04-86.jpg

系统中定义了这么多映射类型,最常用的是:MT_MEMORY,对应内存映射;MT_DE-VICE,对应于通过ioremap的IO设备映射;MT_ROM,对应于ROM;MT_LOW_VECTORS,对应0地址开始的向量;MT_HIGH_VECTORS,对应高地址开始的向量,通常设备都是采用高地址映射。对于向量的映射是由配置CONFIG_VECTORS_BASE的值来决定的。

create_mapping的调用必然通过这些类型形成真正的页表属性才是有意义的。在相应的内核代码中type=&mem_types[md->type];来将逻辑的类型转变为实际的映射页表的值。相应的type类型是mem_type,具体内容如下:

978-7-111-49426-3-Chapter04-87.jpg

这里的值都是和访问权限、属性相关的,要了解详细的内容就需要从ARM相关的设计开始入手。首先,看看两级页表项的内容,分别如图4-21和图4-22所示。

978-7-111-49426-3-Chapter04-88.jpg

图4-21 ARM一级页表

一级页表和二级页表都会根据后两位有不同的映射形式。Linux内核会针对之前提到的不同类型采用合适的形式进行映射,一级页表存放在swapper_pg_dir开始的16KB区域内,在映射空间不满1MB的情况下才使用二级页表。对1MB空间采用section方式的只需要一次转换即可。

通过页表建立的映射关系如图4-23所示。

978-7-111-49426-3-Chapter04-89.jpg

图4-22 ARM二级页表

978-7-111-49426-3-Chapter04-90.jpg

图4-23 ARM页表的映射关系(www.chuimin.cn)

接下来对权限进行详细的说明,页表设置中访问权限相关的域分别为Domain、AP和APX。这里的Domain是一个索引值,具体每个Domain的权限是由CP15的C3寄存器中的每两位来设定的。权限的具体内容见表4-2。

表4-2 Domain权限具体内容

978-7-111-49426-3-Chapter04-91.jpg

Linux内核只是用了几个Domain,对应的权限值定义在arch/arm/include/asm/domain.h中,具体内容如下:

978-7-111-49426-3-Chapter04-92.jpg

AP和APX的属性说明见表4-3。通常使用APX=0进行设置。

表4-3 访问权限属性说明

978-7-111-49426-3-Chapter04-93.jpg

回头看看mem_type中的几个域现在就清楚了,prot_pte是二级页表的访问控制属性,prot_l1是有二级页表情况下的一级页表访问控制属性,prot_sect是一级页表section模式访问控制属性,domain代表一级页表访问属性中的Domain,会根据需要填入到prot_l1或者prot_sect中。

Linux内核中页表属性的位域定义在arch/arm/include/asm/pgtable-hwdef.h中具体如下:

978-7-111-49426-3-Chapter04-94.jpg

由于domain在访问权限判断中优先级高,所以它的设置就显得比较重要,Linux对不同domain设置的相关代码如下:

978-7-111-49426-3-Chapter04-95.jpg

可见,内核在启动的初期就对这三个域的访问控制进行了设置。这里的设置主要是因为系统在内核态拥有特权级别,这样的设置可以减少权限检测。但这个设置并不是一成不变的,内核提供了modify_domain的宏,作为修改域访问控制位的接口。内核在setup_arch中调用early_trap_init,利用该接口将DOMAIN_USER的权限位设置成DOMAIN_CLIENT。另外对于DOMAIN_KERNEL同样需要在内核执行用户态任务之前将其设置为DOMAIN_CLIENT,这样才能进行访问权限的检查。对于DOMAIN_KERNEL的修改会通过set_fs接口来执行,这主要是因为系统调用,特别是文件系统的操作,经常会调用内核空间执行相应系统调用的接口,这些接口是与体系结构无关的,所以要进行地址检查,确保操作的数据来自用户空间而不是内核空间,如果从内核直接调用就会报告异常。这时候的解决方案就是临时提高权限,等操作完成后再恢复原有的权限,这样就有set_fs接口。ARM体系结构中,由于有ldrt(无论处理器当前的权限级别都以用户级权限执行)指令,像get_user这种获得用户数据的操作,自然用ldrt更合理。这样对前述的内核态参数的访问,使用ldrt时会有同样问题,所以在ARM体系结构下,会将体系结构无关的权限升级与DOMAIN_KERNEL的权限升级结合到一起。当然平时都是在DOMAIN_CLIENT的状态下进行权限检查的。另外在任务切换时同样需要进行domain权限的转换,这就由thread_info中的cpu_domain域记录并完成。这样就对整个系统任何时候都进行了合适的domain值的设置。做这么多的工作都是因为映射本身涉及权限,权限是和安全相关的,所以要格外的小心,哪怕花费精力也是必要的。

内核中各种映射类型及访问权限相关的设置见表4-4。

表4-4 映射类型和访问权限设置

978-7-111-49426-3-Chapter04-96.jpg

(续)

978-7-111-49426-3-Chapter04-97.jpg

这样整个ARM体系结构的映射框架就比较清晰了。

3.总结

Linux内核ARM体系结构下整个空间映射的数据和权限的特点为Linux在ARM体系结构下通常是使用4KB大小的页,但是对于线性映射的内核空间(low memory)以及IO空间使用1MB大小的页。内核针对不同的应用场景一共有六种不同类型页映射,分别如下:

●对于存放应用或共享库代码的页面,映射时会标记为只读,并且允许映射到多个应用的地址空间。

●对于保护可写数据的页面要标记XN位表示不可执行,用于捕获试图执行数据区的错误。

●对于用户用来映射普通文件的页面、包含stack或者heap的页面、kernel modules使用的页面、内核线性映射的页面以及vmalloc空间的页面都被标记为“normal,cache-able”。而包含stack或者heap的页面在初始分配时设计成会引发缺页异常的只读零页,只有在首次写数据引发的缺页异常中才进行真正的分配操作。注意所有的页表项都是通过内核的线性映射空间进行分配的。

●包含设备文件映射的页面由设备驱动负责映射。

●包含异常向量表的页面被标记为normal cacheable并且用户态只读。

●内核中静态和动态映射的设备存储区域需要设置为用户态不能访问。

熟悉这些类型对于理解内存管理是很有帮助的,因为这些类型也是内存页面使用的不同方式。