1.ARM64处理器中有两个页表基地址寄存器TTBR0和TTBR1,处理器如何使用它们?
在AArch64 架构中,因为地址总线位宽最多支持48位,所以VA被划分为两个空间,每个空间最多支持256TB。
- 低位的虚拟地址空间位于0x00000000_00000000到0x0000FFFF_FFFFFFFF。如果虚拟地址的最高位等于0,就使用这个虚拟地址空间,并且使用TTBR0_ELx来存放页表的基地址。
- 高位的虚拟地址空间位于0xFFFF0000_00000000到0xFFFFFFF_FFFFFFFF。如果虚拟地址的最高位等于1,就使用这个虚拟地址空间,并且使用TTBR1_ELx来存放页表的基地址。
2.请简述ARM64处理器的4级页表的映射过程,假设页面粒度为4KB,地址宽度为48位
- 1.处理器根据页表基地址控制寄存器和虚拟地址来判断使用哪个页表基地址寄存器,是TTBR0还是TTBR1。当虚拟地址第63位(简称VA[63])为1时选择TTBR1,当VA[63]为0时选择TTBR0。页表基地址寄存器中存放着1级页表(见图中的L0页表)的基地址。
- 2.处理器将VA[47:39]作为L0索引,在1级页表(L0页表)中找到页表项,1级页表有512个页表项。
- 3.2级页表的页表项中存放着3级页表(L2页表)的物理基地址。处理器以VA[29:21]作为L2索引,在3级页表(L2页表)中找到相应的项表项,3级页表有512个页表项。
- 4.3级页表的页表项中存放着4级页表(L3页表)的物理基地址。处理器以VA[20:12]作为L3索引,在4级页表(L3页表)中找到相应的项表项,4级页表有512个页表项。
- 5.4级页表的页表项里存放着4KB页面的物理基地址让,然后加上 VA[11:0],就构成了新的物理地址,因此处理器就完成了页表的查询和翻译工作。
3.在L0~L2页表项描述符中,如何判断一个页表项是块类型还是页表类型?
- L0~L2有三种页表项类型:无效类型,块类型,页表类型。
- bit[0]=0为无效类型,bit[0]=1为块类型或者页表类型。
- bit[1]=0为块类型,bit[1]=1为页表类型。
4.在ARM64 Linux内核中,用户空间和内核空间是如何划分的?
- 用户空间:0x00000000_00000000到0x0000FFFF_FFFFFFFF
- 内核空间:0xFFFF0000_00000000到0xFFFFFFF_FFFFFFFF
5.在ARM64Linux内核中,PAGE_OFFSET表示什么意思?
PAGE_OFFSET表示物理内存在内核空间里做线性映射(linear mapping)的起始地址,在ARM64的Linux内核中该值定义为0xFFFF_8000_0000_0000。Linux内核在初始化时会把物理内存全部做一次线性映射,映射到内核空间的虚拟地址上。该值定义在arch/arm64/include/asm/memory.h
#define PAGE_OFFSET (UL(0xffffffffffffffff) - \
(UL(1) << (VA_BITS - 1)) + 1)
6.KIMAGE_VADDR表示什么意思?
KIMAGE_VADDR表示内核映像文件映射到内核空间的起始虚拟地址。它的值等于MODULES_END的值,MODULE_END表示模块区域的虚拟地址的结束地址。KIMAGE_VADDR宏的值为0xFFFF_0000_1000_0000,它的定义如下:
#define KIMAGE_VADDR (MODULES_END)
#define MODULES_END (MODULES_VADDR + MODULES_VSIZE)
#define MODULES_VADDR (VA_START + KASAN_SHADOW_SIZE)
#define MODULES_VSIZE (SZ_128M)
7.TEXT_OFFSET表示什么意思?
代码段的起始地址是由KIMAGE_VADDR + TEXT_OFFSET组成的,而TEXT_OFFSET表示代码段在地址空间的相对偏移量。arch/arm64/Makefile
ifeq ($(CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET), y)
TEXT_OFFSET := $(shell awk "BEGIN {srand(); printf \"0x%06x\n\", \
int(2 * 1024 * 1024 / (2 ^ $(CONFIG_ARM64_PAGE_SHIFT)) * \
rand()) * (2 ^ $(CONFIG_ARM64_PAGE_SHIFT))}")
else
TEXT_OFFSET := 0x00080000
endif
8.内核映像文件包含哪些段?这些段的作用是什么?在Sysmtem.map 文件中它们分别使用哪些符号来表示段的开始和结束?
内核映像文件本身占据的内存空间从_text到_end,并且分为几段。
- 代码(.text)段:_text和_etext为代码段的起始与结束地址,包含了编译后的内核代码。
- init段:__init_begin和__init_end分别为init段的起始于结束地址,包含了大部分模块初始化数据。
- 数据(.data)段:_sdada和_edata分别为数据段的起始与结束地址,保存了内核的大部分变量。
- bss段:__bss_start和__bss_stop分别为bss段的开始和结束地址,包含了未初始化的或者初始化为0的全局变量和静态变量。
9.请画出ARM64Linux内核的内存布局。
- 用户空间:0x0000_0000_0000_0000~0x0000_FFFF_FFFF_FFFF,一共256TB
- 非规范区域。
- 内核空间:0xFFFF_0000_0000_0000~0xFFFF_FFFF_FFFF_FFFF,一共256TB。内核空间又做如下细分。
- Modules区域:0xFFFF_0000_0800_0000~0xFFFF_0000_1000_0000,大小为128MB。
- vmalloc区域:0xFFFF_0000_1000_0000~0xFFFF_7DFF_BFFF_0000,大小为129022GB。
- 固定映射(fixed)区域:0xFFFF_7DFF_FE7F_9000~0xFFFF_7DFFF_FEC0_0000,4124KB。
- PCI I/O区域:0xFFFF_7DFF_FEE0_0000~0xFFFF_7DFF_FFE0_0000,大小为16MB。
- vmemmap区域:0xFFFF_7E00_0000_0000~0xFFFF_8000_0000_0000,大小为2TB。
- 线性映射区:0xFFFF_8000_0000_0000~0xFFFF_FFFF_FFFF_FFFF,大小为128TB。
10._pa_symbol()宏和_pa()宏有什么区别?
- _pa_symbol()宏和_pa()宏作用都是将内核虚拟地址转换为物理地址。
- 当所有物理内存都线性映射到线性映射区时才可以使用_pa()。
- 在物理内存的线性映射没有建立好前,只能使用_pa_symbol()。
11.在物理内存还没有线性映射到内核空间时,内核映像文件映射到什么地方?
- 在物理内存还没有线性映射到内核空间时,内核映像文件需要先调用pgd_set_fixmap映射到固定映射区域。
12.在ARM Linux内核中,kimage_voffset代表什么意思呢?
当系统刚初始化时,内核映像通过块映射的方式映射到KIMAGE_VADDR + TEXT_OFFSET的虚拟地址上,因此kiamge_voffset表示内核映像虚拟地址和物理地址之间的编译量,这类似于线性映射之后的PAGE_OFFSET。
13.在ARMv8架构中,高速缓存管理的PoC和PoU有什么区别?
- 全局缓存一致性角度(Point of Coherent,PoC)系统中所有可以发起内存访问的硬件单元(如处理器,DMA设备,GPU等)都能保证观察到的某一个地址上的数据是一致的或者是相同的副本。通常PoC表示站在系统的角度来看高速缓存的一致性问题。
- 处理器缓存一致性角度(Point of Unification,PoU)表示站在处理器角度来看高速缓存的一致性问题。对于一个内部共享(inner shareable)的PoU,所有的处理器都能看到相同的内存副本。
14.在ARMv8架构中,ASID是什么意思?有什么作用?
ASID用于为每个进程分配进程地址空间标识,TLB命中查询的标准由原来的虚拟地址判断再加上ASID条件。
15.在ARMv8架构中支持哪几种内存属性?它们都有哪些特点?
分为普通内存和设备内存。
- 普通内存是弱一致性的(weekly ordered),没有额外的约束,可以提供最高的内存访问性能。通常代码段,数据段以及其他数据都会放在普通内存中。普通内存可以让处理器做很多的优化,如分值预测,数据预取,高速缓存行预取和填充,乱序加载等硬件优化。
- 设备内存:处理器访问设备内存会有很多限制,如不能进行预测访问等。设备内存是严格按照指令顺序来执行的。通常设备内存留给设备来访问。若系统中所有内存都设置为设备内存,就会有很大的副作用。
16.在ARMv8架构中,高速缓存共享属性有内部共享(inner shareable)和外部共享(outer shareable),它们有什么区别?
- 如果一个内存区域被标记为“不可共享的”,表示它只能被一个处理器访问,其他处理器不能访问这个内存区域。
- 如果一个内存区域被标记为“内部共享的”,表示它可以被多个处理器访问和共享,但是系统中其他能访问内存的硬件单元就不能访问了,如DMA,GPU。
- 如果一个内存区域被标记为“外部共享的”,表示系统中很多访问内存的单元(如DMA,GPU)都可以和处理器一样访问内存区域。
17.在ARMv8架构中,支持哪几条内存屏障指令?它们都有什么区别?
- 数据存储屏障(Data Memory Barrier,DMB)指令:仅当所有在它前面的存储器访问操作都执行完毕后,才提交在它后面的访问指令。DMB指令保证的是DMB指令之前的所有内存访问指令和DMB指令之后的所有内存访问指令的顺序。也就是说,DMB指令之后的内存访问指令不会被处理器重排在DMB指令前面。DMB指令不会保证内存访问指令在内存屏障指令之前必须完成,它仅仅保证内存屏障指令前后的内存访问指令的执行顺序。DMB指令仅仅影响内存访问指令,数据高速缓存指令以及高速缓存管理指令等,并不会影响其他指令的顺序。
- 数据同步屏障(Data Synchronization Barrier,DSB)指令:比DMB指令要更严格一些,仅当所有在它前面的访问指令都执行完毕后,才会执行在它后面的指令,即任何指令都要等待DSB指令前面的访问指令完成。位于此指令前的所有缓存,如分值预测和TLB维护操作需全部完成。
- 指令同步屏障(Instruction Synchronization Barrier,ISB)指令:比DMB和DSB指令严格,刷新流水线(flush pipeline)和预取缓冲区后,才会从高速缓存或者内存中预取ISB指令之后的指令。ISB指令通常用来保证上下文切换的效果,如ASID更改,TLB维护操作和C15寄存器的修改等。
18.加载-获取屏障原语与存储-释放屏障原语有什么区别?分别有什么作用?
- 加载-获取(load-acquire)屏障原语:含有获取屏障原语的读操作,相当于单方向后的屏障指令。所有加载-获取内存屏障指令后面的内存访问指令只能在加载-获取内存屏障指令执行后才能开始执行,并且被其他CPU观察到。普通的读和写操作可以向后越过该屏障指令,但是之后的读和写操作不能向前越过该屏障指令。
- 存储-释放(store-release)屏障原语:含有释放屏障原语的写操作,相当于单方向向前的屏障指令。只有所有存储-释放屏障原语之前的指令完成了,才能执行存储-释放屏障原语之后的指令,这样其他CPU可以观察到存储-释放屏障原语之前的指令已经执行完。普通的读和写可以向前越过存储-释放屏障指令,但是之前的读和写操作不能向后越过存储-释放屏障指令。
19.什么是一个段的加载地址和运行地址?
- 虚拟地址是运行时段所在的地址,可以理解为运行时地址。
- 加载地址是加载时section所在的地址,可以理解为加载地址。
20.从U-boot跳转到内核时,为什么指令高速缓存可以打开而数据高速缓存必须关闭?
- 数据高速缓存一定要关闭,因为在内核启动的过程中取数据的时候会先访问高速缓存,而可能高速缓存里缓存了以前U-boot的一些数据,这些数据对于内核来说是错误的。若内核拿到错误的数据,会导致内核初始化失败。
- 而指令高速缓存可以打开,是因为U-Boot和内核指令代码是不重叠的,不会再指令高速缓存中有冲突。
21.在Linux内核启动汇编代码中,为什么要建立恒等映射?
- 现代处理器大多是多级流水线架构,处理器会提前预取多条指令到流水线中。当打开MMU时,处理器已经提前预取了多条指令,并且这些指令是以物理地址来进行预取的。打开MMU的指令运行完之后,处理器的MMU功能功效,于是之前提前预取的指令会以虚拟地址来访问,到MMU中查找对应的物理地址。因此,这是为了保证处理器在开启MMU前后可以连续取指令。
22.在ARMv8架构中,在L0~L2页表项中包含了指向下一级页表的基地址,那么这个下一级页表基地址是物理地址还是虚拟地址?
- 我们知道,在使能了MMU之后,CPU直接寻址虚拟地址,而MMU负责虚拟地址到物理地址的转换和翻译工作,地址转换和翻译的依据是页表。页表项的内容是由操作系统负责填充的。如果下一级页表的基地址是虚拟地址,那么MMU还需要查询另外一个页表才能找到这个虚拟地址对应的物理地址,这样MMU就会陷入死循环,因此这里下一级页表的基地址采用的是物理地址。
23.MMU可以遍历页表,Linux内核也提供了软件遍历页表的函数,如 walk_pgd()、_create_pgd_mapping()、follow_page()等。从软件的视角, Linux 内核的pgd_t、pud_t、pmd_t 以及pte_t数据结构中并没有存储一个指向下一级页表的指针(即从CPU角度来看,CPU 访问这些数据结构时是以虚拟地址来访问的),它们是如何遍历的呢?pgd_t、pud_t、pmd_t以及pte_t数据结构是u64类型的变量。
- 在Linux内核里,物理内存会线性映射到内核空间里,偏移量为PAGE_OFFSET。在内核空间可以很方便地实现虚拟地址和物理地址映射的转换。
- Linux内提供了两个宏,其中__pa()宏用于根据内核中线性映射的虚拟地址计算对应的物理地址,而__va()宏用于根据内核线性映射中物理地址计算对应的虚拟地址。