文章目录

  • 1 页表缓存
    • 1.1 TLB表项格式
    • 1.2 TLB管理
    • 1.3 地址空间标识符
    • 1.4 虚拟机标识符
  • 重要:本系列文章内容摘自<Linux内核深度解析>基于ARM64架构的Linux4.x内核一书,作者余华兵。系列文章主要用于记录Linux内核的大部分机制及参数的总结说明

1 页表缓存

处理器的内存管理单元(Memory Management Unit,MMU)负责把虚拟地址转换成物理地址,为了改进虚拟地址到物理地址的转换速度,避免每次转换都需要查询内存中的页表,处理器厂商在内存管理单元里面增加了一个称为TLB(Translation Lookaside Buffer)的高速缓存,TLB直译为转换后备缓冲区,意译为页表缓存。

页表缓存用来缓存最近使用过的页表项,有些处理器使用两级页表缓存:第一级TLB分为指令TLB和数据TLB,好处是取指令和取数据可以并行执行;第二级TLB是统一TLB(Unified TLB),即指令和数据共用的TLB。

1.1 TLB表项格式

不同处理器架构的TLB表项的格式不同。ARM64处理器的每条TLB表项不仅包含虚拟地址和物理地址,也包含属性:内存类型、缓存策略、访问权限、地址空间标识符(Address Space Identifier,ASID)和虚拟机标识符(Virtual Machine Identifier,VMID)。地址空间标识符区分不同进程的页表项,虚拟机标识符区分不同虚拟机的页表项。

1.2 TLB管理

如果内核修改了可能缓存在TLB里面的页表项,那么内核必须负责使旧的TLB表项失效,内核定义了每种处理器架构必须实现的函数,如下所示:

函  数 说  明
void flush_tlb_all(void); 使所有TLB表项失效
void flush_tlb_mm(struct mm_struct *mm); 使指定用户地址空间的所有TLB表项失效,参数mm是进程的内存描述符
void flush_tlb_range(struct vm_area_struct *vma, unsigned long start, unsigned long end); 使指定用户地址空间的某个范围的TLB表项失效参数vma是虚拟内存区域,start是起始地址,end是结束地址(不包括)
void flush_tlb_page(struct vm_area_struct *vma, unsigned long uaddr); 使指定用户地址空间里面的指定虚拟页的TLB表项失效参数vma是虚拟内存区域,uaddr是一个虚拟页中的任意虚拟地址
void flush_tlb_kernel_range(unsigned long start, unsigned long end); 使内核的某个虚拟地址范围的TLB表项失效参数start是起始地址,end是结束地址(不包括)
void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep); 修改页表项以后把页表项设置到页表缓存由软件管理页表缓存的处理器必须实现该函数,例如MIPS处理器ARM64处理器的内存管理单元可以访问内存中的页表,把页表项复制到页表缓存,所以ARM64架构的函数update_mmu_cache什么都不用做
void tlb_migrate_finish(struct mm_struct *mm); 内核把进程从一个处理器迁移到另一个处理器以后,调用该函数以更新页表缓存或上下文特定信息

当TLB没有命中的时候,ARM64处理器的内存管理单元自动遍历内存中的页表,把页表项复制到TLB,不需要软件把页表项写到TLB,所以ARM64架构没有提供写TLB的指令。

ARM64架构提供了一条TLB失效指令:

TLBI <type><level>{IS} {, <Xt>}

(1)字段的常见选项如下:
1)ALL:所有表项。
2)VMALL:当前虚拟机的阶段1的所有表项,即表项的VMID是当前虚拟机的VMID。虚拟机里面运行的客户操作系统的虚拟地址转换成物理地址分两个阶段:第1阶段把虚拟地址转换成中间物理地址,第2阶段把中间物理地址转换成物理地址。
3)VMALLS12:当前虚拟机的阶段1和阶段2的所有表项。
4)ASID:匹配寄存器Xt指定的ASID的表项。
5)VA:匹配寄存器Xt指定的虚拟地址和ASID的表项。
6)VAA:匹配寄存器Xt指定的虚拟地址并且ASID可以是任意值的表项。

(2)字段指定异常级别,取值如下:
1)E1:异常级别1。
2)E2:异常级别2。
3)E3:异常级别3。
(3)字段IS表示内部共享(Inner Shareable),即多个核共享。如果不使用字段IS,表示非共享,只被一个核使用。在SMP系统中,如果指令TLBI不携带字段IS,仅仅使当前核的TLB表项失效;如果指令TLBI携带字段IS,表示使所有核的TLB表项失效。
(4)字段Xt是X0~X30中的任何一个寄存器。
例如ARM64内核实现了函数flush_tlb_all,用来使所有核的所有TLB表项失效,其代码如下:

arch/arm64/include/asm/tlbflush.h
static inline void flush_tlb_all(void)
{dsb(ishst);__tlbi(vmalle1is);dsb(ish);isb();
}

把宏展开以后是:

static inline void flush_tlb_all(void)
{asm volatile("dsb ishst" : : : "memory");asm ("tlbi vmalle1is" : :);asm volatile("dsb ish" : : : "memory");asm volatile("isb" : : : "memory");
}

dsb ishst:确保屏障前面的存储指令执行完。dsb是数据同步屏障(Data Synchronization Barrier),ishst中的ish表示共享域是内部共享(inner shareable),st表示存储(store),ishst表示数据同步屏障指令对所有核的存储指令起作用。

tlbi vmalle1is:使所有核上匹配当前VMID、阶段1和异常级别1的所有TLB表项失效。

dsb ish:确保前面的TLB失效指令执行完。ish表示数据同步屏障指令对所有核起作用。

isb:isb是指令同步屏障(Instruction Synchronization Barrier),这条指令冲刷处理器的流水线,重新读取屏障指令后面的所有指令。

可以对比一下,ARM64内核实现了函数local_flush_tlb_all,用来使当前核的所有TLB表项失效,其代码如下:

arch/arm64/include/asm/tlbflush.h
static inline void local_flush_tlb_all(void)
{dsb(nshst);__tlbi(vmalle1);dsb(nsh);isb();
}

和函数flush_tlb_all的区别如下:
(1)指令dsb中的字段ish换成了nsh,nsh是非共享(non-shareable),表示数据同步屏障指令仅仅在当前核起作用。
(2)指令tlbi没有携带字段is,表示仅仅使当前核的TLB表项失效。

1.3 地址空间标识符

为了减少在进程切换时清空页表缓存的需要,ARM64处理器的页表缓存使用非全局(not global,nG)位区分内核和进程的页表项(nG位为0表示内核的页表项),使用地址空间标识符(Address Space Identifier,ASID)区分不同进程的页表项。

ARM64处理器的ASID长度是由具体实现定义的,可以选择8位或者16位,寄存器ID_AA64MMFR0_EL1(AArch64内存模型特性寄存器0,AArch64 Memory Model Feature Register 0)的字段ASIDBits存放处理器支持的ASID长度。如果具体实现支持16位ASID,那么可以使用寄存器TCR_EL1(转换控制寄存器,Translation Control Register)的AS(ASID Size)位控制实际使用的ASID长度。如果把AS位设置成0,表示使用8位ASID,否则表示使用16位ASID。

寄存器TTBR0_EL1(转换表基准寄存器0,Translation Table Base Register 0)或TTBR1_EL1都可以用来存放当前进程的ASID,寄存器TCR_EL1的A1位决定使用哪个寄存器存放当前进程的ASID,通常使用寄存器TTBR0_EL1。寄存器TTBR0_EL1的位[63:48]存放当前进程的ASID(如果使用8位ASID,那么寄存器TTBR0_EL1的位[63:56]是保留位),位[47:1]存放当前进程的页全局目录的物理地址。

在SMP系统中,ARM64架构要求ASID在处理器的所有核上是唯一的。
为了方便描述,本节假设ASID长度是8位,ASID只有256个值,其中0是保留值。可分配的ASID范围是1~255,进程的数量可能超过255,两个进程的ASID可能相同,怎么解决这个问题呢?内核引入了ASID版本号,解决方法如下。

(1)每个进程有一个64位的软件ASID,低8位存放硬件ASID,高56位存放ASID版本号。
(2)64位全局变量asid_generation的高56位保存全局ASID版本号。
(3)当进程被调度时,比较进程的ASID版本号和全局ASID版本号。如果版本号相同,那么直接使用上次分配的硬件ASID,否则需要给进程重新分配硬件ASID。

1)如果存在空闲的硬件ASID,那么选择一个分配给进程。
2)如果没有空闲的硬件ASID,那么把全局ASID版本号加1,重新从1开始分配硬件ASID,即硬件ASID从255回绕到1。因为刚分配的硬件ASID可能和某个进程的硬件ASID相同,只是ASID版本号不同,页表缓存可能包含了这个进程的页表项,所以必须把所有处理器的页表缓存清空。

引入ASID版本号的好处是:避免每次进程切换都需要清空页表缓存,只需要在硬件ASID回绕时把处理器的页表缓存清空。

内存描述符的成员context存放架构特定的内存管理上下文,数据类型是结构体mm_context_t,ARM64架构定义的结构体如下所示,成员id存放内核给进程分配的软件ASID:

arch/arm64/include/asm/mmu.h
typedef struct {atomic64_t   id;…
} mm_context_t;

全局变量asid_bits保存ASID长度,全局变量asid_generation的高56位保存全局ASID版本号,位图asid_map记录哪些ASID被分配。

每处理器变量active_asids保存处理器正在使用的ASID,即处理器正在执行的进程的ASID;每处理器变量reserved_asids存放保留的ASID,用来在全局ASID版本号加1时保存处理器正在执行的进程的ASID。处理器给进程分配ASID时,如果ASID分配完了,那么把全局ASID版本号加1,重新从1开始分配ASID,针对每个处理器,使用该处理器的reserved_asids保存该处理器正在执行的进程的ASID,并且把该处理器的active_asids设置为0。

active_asids为0具有特殊含义,说明全局ASID版本号变化,ASID从255回绕到1。

当全局ASID版本号加1时,每个处理器需要清空页表缓存,位图tlb_flush_pending保存需要清空页表缓存的处理器集合:

arch/arm64/mm/context.c
static u32 asid_bits;
static atomic64_t asid_generation;
static unsigned long *asid_map;static DEFINE_PER_CPU(atomic64_t, active_asids);
static DEFINE_PER_CPU(u64, reserved_asids);
static cpumask_t tlb_flush_pending;

当进程被调度时,函数check_and_switch_context负责检查是否需要给进程重新分配ASID,其代码如下:

__schedule() -> context_switch() -> switch_mm_irqs_off() -> switch_mm() -> check_and_switch_context()arch/arm64/mm/context.c
1   void check_and_switch_context(struct mm_struct *mm, unsigned int cpu){unsigned long flags;u64 asid;asid = atomic64_read(&mm->context.id);if (!((asid ^ atomic64_read(&asid_generation)) >> asid_bits)&& atomic64_xchg_relaxed(&per_cpu(active_asids, cpu), asid))goto switch_mm_fastpath;raw_spin_lock_irqsave(&cpu_asid_lock, flags);asid = atomic64_read(&mm->context.id);if ((asid ^ atomic64_read(&asid_generation)) >> asid_bits) {asid = new_context(mm, cpu);atomic64_set(&mm->context.id, asid);}if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))local_flush_tlb_all();atomic64_set(&per_cpu(active_asids, cpu), asid);raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);switch_mm_fastpath:if (!system_uses_ttbr0_pan())cpu_switch_mm(mm->pgd, mm);}

1.4 虚拟机标识符

虚拟机里面运行的客户操作系统的虚拟地址转换成物理地址分两个阶段:第 1 阶段把虚拟地址转换成中间物理地址,第 2 阶段把中间物理地址转换成物理地址。第 1 阶段转换由客户操作系统的内核控制,和非虚拟化的转换过程相同。第 2 阶段转换由虚拟机监控器控制,虚拟机监控器为每个虚拟机维护一个转换表,分配一个虚拟机标识符(Virtual Machine Identifier,VMID),寄存器VTTBR_EL2(虚拟化转换表基准寄存器,Virtualization Translation Table Base Register)存放当前虚拟机的阶段2转换表的物理地址。每个虚拟机有独立的ASID空间,页表缓存使用虚拟机标识符区分不同虚拟机的转换表项,可以避免每次虚拟机切换都要清空页表缓存,只需要在虚拟机标识符回绕时把处理器的页表缓存清空。

ARM64处理器的VMID长度是具体实现定义的,可以选择8位或者16位,寄存器ID_AA64MMFR0_EL1的字段VMIDBits存放处理器支持的VMID长度。如果具体实现支持16位VMID,可以使用寄存器VTCR_EL2(虚拟化转换控制寄存器,Virtualization Translation Control Register)的VS(VMID Size)位控制实际使用的VMID长度。如果把VS位设置成0,表示使用8位VMID,否则表示使用16位VMID。

寄存器VTTBR_EL2的位[63:48]存放正在运行的虚拟机的标识符,如果使用8位VMID,那么寄存器VTTBR_EL2的位[63:56]是保留位。

Linux内核机制总结内存管理之页表缓存(十九)相关推荐

  1. Linux内核机制总结内存管理之页回收(二十三)

    文章目录 1 页回收 1.1 数据结构 1.1.1 LRU链表 1.1.2 反向映射 1.2 发起回收 1.3 计算扫描的页数 1.4 收缩活动页链表 1.5 回收不活动页 1.6 页交换 1.7 回 ...

  2. Linux内核机制总结内存管理之每处理器内存分配器(十七)

    文章目录 1 每处理器内存分配器 1.1 编程接口 1.1.1 静态每处理器变量 1.1.2 动态每处理器变量 1.1.3 访问每处理器变量 1.2 技术原理 1.2.1 确定块的参数 1.2.2 创 ...

  3. Linux内核机制总结内存管理之用户页错误文件描述符(二十八)

    文章目录 1 用户页错误文件描述符 1.1 使用方法 1.2 技术原理 重要:本系列文章内容摘自<Linux内核深度解析>基于ARM64架构的Linux4.x内核一书,作者余华兵.系列文章 ...

  4. Linux内核机制总结内存管理之连续内存分配器(二十七)

    文章目录 1 连续内存分配器 1.1 使用方法 1.2 技术原理 重要:本系列文章内容摘自<Linux内核深度解析>基于ARM64架构的Linux4.x内核一书,作者余华兵.系列文章主要用 ...

  5. Linux内核机制总结内存管理之内存耗尽杀手(二十四)

    文章目录 1 内存耗尽杀手 1.1 使用方法 1.2 技术原理 重要:本系列文章内容摘自<Linux内核深度解析>基于ARM64架构的Linux4.x内核一书,作者余华兵.系列文章主要用于 ...

  6. linux内核工程导论,Linux内核工程导论——内存管理(3)

    Linux内核工程导论--内存管理(三) 用户端内核内存参数调整 /proc/sys/vm/ (需要根据内核版本调整) 交换相关 swap_token_timeout Thisfile contain ...

  7. Linux内核初始化阶段内存管理的几种阶段

    本系列旨在讲述从引导到完全建立内存管理体系过程中,内核对内存管理所经历的几种状态.阅读本系列前,建议先阅读memblock的相关文章. 一些讲在前面的话 在很久很久以前,linux内核还是支持直接从磁 ...

  8. 详细讲解Linux内核源码内存管理(值得收藏)

    Linux的内存管理是一个非常复杂的过程,主要分成两个大的部分:内核的内存管理和进程虚拟内存.内核的内存管理是Linux内存管理的核心,所以我们先对内核的内存管理进行简介. 一.物理内存模型 物理内存 ...

  9. 《Linux内核设计与实现》读书笔记(十九)- 可移植性

    linux内核的移植性非常好, 目前的内核也支持非常多的体系结构(有20多个). 但是刚开始时, linux也只支持 intel i386 架构, 从 v1.2版开始支持 Digital Alpha, ...

最新文章

  1. 根据文法画出语法树_编译工程5:语法分析(3)
  2. nssl1338-逃亡路径【最短路计数,bfs】
  3. php使用mysql5和8的区别_mysql8.0和mysql5.7的区别是什么?
  4. 大脚导入配置选择哪个文件_IntelliJ IDEA详细图解最常用的配置,新人必备
  5. dos 退出mysql_【转】MySQL 一闪退出解决_MySQL
  6. sh/bash/csh/Tcsh/ksh/pdksh等shell本质区别
  7. mysql 在线语法检查工具_「mysql 管理工具」五大开源MySQL管理工具! - seo实验室
  8. python编程自学好学吗 ?
  9. 那些想上天的亿万富翁,开启了新的“太空竞赛”
  10. php laravel日志报错,Laravel 文档阅读:错误 日志记录
  11. 微信小程序 - 开发者账号申请流程
  12. 从零到一搭建一个属于自己的博客系统(弍)
  13. C++友元与操作符重载
  14. 小米摄像头 rtmp_如何使用外部摄像头进行Amazon Live
  15. 【元宵节】中国人民大学与加拿大女王大学金融硕士项目与你的那份“缘”
  16. IDEA设置按键提示 Ctrl+p
  17. 【猫猫的Unity Shader之旅】之玻璃材质
  18. Python之OpenGL笔记(17):键盘鼠标控制摄像机移动旋转
  19. 三极管的经典之作,你知道吗?
  20. 大学各专业计算机专属表情包,是不是每个专业都有专属表情包?

热门文章

  1. 论文阅读:SP-CIDS: Secure and Private Collaborative IDS for VANETs
  2. 2023湖北工业大学计算机考研信息汇总
  3. sap服务器查看系统日志目录,服务器怎么看操作日志
  4. C语言行列式计算--高万禄
  5. 腾讯云轻量应用服务器的端口怎么开通?
  6. spring启动加载机制spring.factories使用方法
  7. IDEA debug下调试Evaluate
  8. 解决谷歌人机验证 (reCAPTCHA) 显示错误或空白的问题 (电脑端+手机端)
  9. 石榴社区slsp7com_番石榴是重量级的图书馆,我想改变一下
  10. 202112-1 序列查询 CCF认证真题