也正是因为Linux内核的唯一性,各个不同发布版本拥有相同的框架。Linux内核是在整个Linux系统的最底层,它负责管理硬件,运行用户程序,并保持系统整体的安全性和完整性。可以说是Linux系统的根和灵魂。图3-1中看到Linux内核还有另一层面的含义。这对于Linux内核同样适用。所以对Linux内核的剖析、学习和研究也不能独立于系统进行,而是要综合考虑应用、内核和硬件等各方面的信息和内容。这样才能更全面、深刻地理解Linux内核。......
2023-11-22
首先要明确,这里讲述的地址映射框架主要是指内核空间虚拟地址的使用和映射。mmap等用户空间的映射相关功能会在内存管理和实际的设备驱动部分再进行介绍。
1.ARM体系结构空间分配
对于地址映射框架,首先要考虑的就是体系结构。嵌入式系统通常是采用ARM的处理器,相应的地址映射框架自然就从Linux内核的ARM体系结构入手。相应的地址映射,内核大牛们已经为开发人员设计好地址空间的分布并制定了规范,相应的信息如下(摘自Documents/arm/memory.tx文件)。注意ARM体系结构和相应的Linux内核支持也是不断演进的,相应的地址映射也会随之进行一定的变化,但是通常都是增加相应的细化空间,而不是进行大的调整。
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所示。
图4-19 ARM内核地址空间映射关系
2.内核中的映射实现和相关的权限管理
地址映射的代码框架如图4-20所示。地址映射实际操作的接口是创建页表的create_mapping函数。ARM(统一地址体系结构的处理器)中无论是内存还是IO的映射最终都是通过create_mapping来实现的。而iotable_init则是提供给处理器的调用接口,用来进行IO空间的映射。
图4-20 ARM地址映射代码框架
谈到create_mapping的映射,就会涉及体系结构中不同的访问方式和权限。了解这些访问控制,对于理解内核的工作十分有帮助。
首先来看看create_mapping的传入参数的数据结构:
该结构的信息包括虚拟地址、物理地址、长度和类型。类型表示映射的种类和属性,如映射的是物理内存还是设备空间,相应的空间是否可以共享或者是否允许Cache等。具体的定义在两个文件中,内容如下:
系统中定义了这么多映射类型,最常用的是: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,具体内容如下:
这里的值都是和访问权限、属性相关的,要了解详细的内容就需要从ARM相关的设计开始入手。首先,看看两级页表项的内容,分别如图4-21和图4-22所示。
图4-21 ARM一级页表
一级页表和二级页表都会根据后两位有不同的映射形式。Linux内核会针对之前提到的不同类型采用合适的形式进行映射,一级页表存放在swapper_pg_dir开始的16KB区域内,在映射空间不满1MB的情况下才使用二级页表。对1MB空间采用section方式的只需要一次转换即可。
通过页表建立的映射关系如图4-23所示。
图4-22 ARM二级页表
图4-23 ARM页表的映射关系(www.chuimin.cn)
接下来对权限进行详细的说明,页表设置中访问权限相关的域分别为Domain、AP和APX。这里的Domain是一个索引值,具体每个Domain的权限是由CP15的C3寄存器中的每两位来设定的。权限的具体内容见表4-2。
表4-2 Domain权限具体内容
Linux内核只是用了几个Domain,对应的权限值定义在arch/arm/include/asm/domain.h中,具体内容如下:
AP和APX的属性说明见表4-3。通常使用APX=0进行设置。
表4-3 访问权限属性说明
回头看看mem_type中的几个域现在就清楚了,prot_pte是二级页表的访问控制属性,prot_l1是有二级页表情况下的一级页表访问控制属性,prot_sect是一级页表section模式访问控制属性,domain代表一级页表访问属性中的Domain,会根据需要填入到prot_l1或者prot_sect中。
Linux内核中页表属性的位域定义在arch/arm/include/asm/pgtable-hwdef.h中具体如下:
由于domain在访问权限判断中优先级高,所以它的设置就显得比较重要,Linux对不同domain设置的相关代码如下:
可见,内核在启动的初期就对这三个域的访问控制进行了设置。这里的设置主要是因为系统在内核态拥有特权级别,这样的设置可以减少权限检测。但这个设置并不是一成不变的,内核提供了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 映射类型和访问权限设置
(续)
这样整个ARM体系结构的映射框架就比较清晰了。
3.总结
Linux内核ARM体系结构下整个空间映射的数据和权限的特点为Linux在ARM体系结构下通常是使用4KB大小的页,但是对于线性映射的内核空间(low memory)以及IO空间使用1MB大小的页。内核针对不同的应用场景一共有六种不同类型页映射,分别如下:
●对于存放应用或共享库代码的页面,映射时会标记为只读,并且允许映射到多个应用的地址空间。
●对于保护可写数据的页面要标记XN位表示不可执行,用于捕获试图执行数据区的错误。
●对于用户用来映射普通文件的页面、包含stack或者heap的页面、kernel modules使用的页面、内核线性映射的页面以及vmalloc空间的页面都被标记为“normal,cache-able”。而包含stack或者heap的页面在初始分配时设计成会引发缺页异常的只读零页,只有在首次写数据引发的缺页异常中才进行真正的分配操作。注意所有的页表项都是通过内核的线性映射空间进行分配的。
●包含设备文件映射的页面由设备驱动负责映射。
●包含异常向量表的页面被标记为normal cacheable并且用户态只读。
●内核中静态和动态映射的设备存储区域需要设置为用户态不能访问。
熟悉这些类型对于理解内存管理是很有帮助的,因为这些类型也是内存页面使用的不同方式。
有关深入剖析Linux内核与设备驱动的文章
也正是因为Linux内核的唯一性,各个不同发布版本拥有相同的框架。Linux内核是在整个Linux系统的最底层,它负责管理硬件,运行用户程序,并保持系统整体的安全性和完整性。可以说是Linux系统的根和灵魂。图3-1中看到Linux内核还有另一层面的含义。这对于Linux内核同样适用。所以对Linux内核的剖析、学习和研究也不能独立于系统进行,而是要综合考虑应用、内核和硬件等各方面的信息和内容。这样才能更全面、深刻地理解Linux内核。......
2023-11-22
VFS的实现是以“一切皆是文件”为需求出发点的。要理解VFS的框架首先看一下VFS和系统的静态关系框图,如图5-1所示。从图5-1可见,VFS是用户层的直接接口,是面向用户的服务。图5-1VFS和系统静态关系图在图5-2中每个节点被使用时都会在VFS层中创建dentry,这样可以快速通过文件名进行查找和定位,Linux内核中对dentry的管理组织形式如图5-3所示。以上是系统运行时VFS对文件名的组织管理,这是VFS管理的一个方面。......
2023-11-22
1基本中断处理流程Linux内核的中断处理框架当然要满足之前提到的各种需求。对Linux内核来说,每个中断号都是通过irq_desc进行描述的,可以说irq_desc是中断处理的核心。通过这些抽象结构就可以实现整个中断处理的框架。2中断处理延时操作下面介绍系统如何解决中断中大数据量时的延时处理问题。图4-28是Linux内核中断处理流程。......
2023-11-22
考虑到电源管理的需求涉及处理器和各种设备,一方面是处理器尽可能减少功耗,另一方面是设备尽可能减少功耗。图5-30Linux电源管理各个功能从图5-30可见Linux内核的电源管理功能有与处理器相关的CPUIdle和CPUFreq,也有与设备相关的runtime pm,另外还有与整个系统待机时SLM相关的低功耗电源管理功能。下面分别对这些功能框架进行介绍。具体的驱动同样会在SoC电源管理部分进行讲解。对具体设备的电源管理实现,将在设备驱动中进行详细分析。......
2023-11-22
SPI总线框架对这两类设备进行管理,具体的框架如图7-6所示。通常的SPI驱动都是在内核实现并在内核态执行的,所以就不对spidev进行详细的分析。SPI总线框架整体的功能就是这样,接下来看看具体各部分的实现。在SPI总线框架中相应的实体是spi_board_info,内容如下:其中的主要信息是与总线信号相关的属性。......
2023-11-22
整体的frame buffer框架如图6-5所示。了解frame buffer的框架还要先从为用户提供的接口开始。图6-7可变参数中硬件信息的含义固定信息是在frame buffer的操作过程中并不发生变化的。从整体分析,frame buffer的框架很直接,主要就是直接管理实际的设备,而相关的操作与应用层直接相关。......
2023-11-22
DM 3730的I2C控制器框架如图7-4所示。图7-4引自《DM 3730芯片手册》中第2798页的框图。关于DM 3730I2C的驱动部分,主要分析相关初始化和总线传输的操作。由于I2C作为控制和获取状态信息,数据量并不大,而且相关的操作频率并不高,这样在中断中进行相关的处理,对系统的影响并不大。......
2023-11-22
从图3-2中可以理解Linux内核就是将硬件的功能抽象出来,为用户的应用程序提供各种系统服务。与用户相关的属性是与Linux内核中整体安全性相关的,这部分功能通常和硬件具体功能关系不大,而是附加在逻辑功能之上的属性。本书是以嵌入式系统为基础进行说明,所以将重点放在硬件设备的具体功能以及Linux内核在硬件之上的具体实现方面。Linux内核的整体框架如图3-3所示。图3-3清晰地展现了Linux内核的实现层次,以及应用层功能和硬件功能的对应关系。......
2023-11-22
相关推荐