子曰:“居上不宽,为礼不敬,临丧不哀,吾何以观之哉?”《论语》:八佾篇
硬件架构相关篇为:
- v65.01 鸿蒙内核源码分析(CPU历史) | 正在制作中 ...
- v66.03 鸿蒙内核源码分析(ARM架构) | ARMv7 & Cortex(A|R|M)
- v67.01 鸿蒙内核源码分析(协处理器) | CPU的好帮手
- v68.05 鸿蒙内核源码分析(工作模式) | 羡慕韦小宝老婆多
- v69.06 鸿蒙内核源码分析(寄存器) | 真牛把世界玩出花来了
- v70.03 鸿蒙内核源码分析(多核管理) | 真正并发的基础
- v71.05 鸿蒙内核源码分析(中断概念) | 海公公的日常工作
- v72.04 鸿蒙内核源码分析(中断管理) | 没中断太可怕
- v73.01 鸿蒙内核源码分析(移值适配) | 正在制作中 ...
本篇很重要,对CP15
协处理所有16
个寄存器一一介绍,可能是全网介绍CP15
最全面的一篇,鸿蒙内核的汇编部分(尤其开机启动)中会使用,熟练掌握后看汇编代码将如虎添翼。
协处理器
协处理器 (co-processor) 顾名思义是协助主处理器完成工作,例如浮点、图像、音频处理这一类外围工作。角色相当于老板的助理/秘书,咱皇上身边的人,专干些咱皇上又不好出面的脏活累活,您可别小看了这个角色,权利不大但能力大,是能通天的人,而且老板越大,身边这样的人还不止一个。
在 arm
的协处理器设计中,最多可以支持 16
个协处理器,通常被命名为 cp0
~cp15
,本篇主要说第16
号协处理器 cp15
CP15
关于 cp15
详细介绍见于《ARM体系结构参考手册》的 B3.17。 cp15
一共有 16
个寄存器32
位的寄存器,其编号为C0 ~ C15
,用来控制cache
、TCM
和存储器管理。cp15
寄存器都是复合功能寄存器,不同功能对应不同的内存实体,全由访问指令的参数来决定,对于 armv7
架构而言,A
系列和 R
系列是统一设计的,A
系列带有 MMU
相关的控制,而 R
系列带有 MPU
相关控制,针对不同的功能需要做区分,同时又因为协处理器 cp15
只支持 16
个寄存器,而需要支持的功能较多,所以通过同一寄存器不同功能的方式来满足需求。
mcr | mrc 指令
armv7 中对于协处理器的访问,CP15
的寄存器只能被MRC
和MCR
(Move to Coprocessor from ARM Register )指令访问。MCR
表示将 arm
核心寄存器中的值的写到 cp15
寄存器中,MRC
从 cp15
寄存器中读到 arm
核心寄存器中,大部分指令都需要在 PL1
以及更高的特权级下才能正常执行,这是因为 cp15
协处理器大多都涉及到系统和内存的设置,user
模式没有操作权限,user
模式仅能访问 cp15
中有限的几个寄存器比如:ISB、DSB、DMB、TPIDRURW、TPIDRURO 寄存器。
从 `cp**` 寄存器中读到 `arm` 核心寄存器中
MRC<cond> <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
- cond: 指令后缀,表示条件执行,关于条件执行可以参考 arm状态寄存器
- coproc:协处理器的名称,cp0~cp15 分别对应名称 p0~p15
- opc1:对于 cp15 而言,这一个参数一般为0。
- Rt:arm 的通用寄存器
- CRn:与 arm 核心寄存器交换数据的核心寄存器名,c0~c15
- CRm:需要额外操作的协处理器的寄存器名,c0~c15,针对多种功能的 cp15 寄存器,需要使用 CRm 和 opc2 来确定 CRn 对应哪个寄存器实体。
- opc2:可选,与 CRm搭配使用,同样是决定多功能寄存器中指定实体。
啥玩意,太抽象没看懂,后面直接上内核代码就懂了,先看16个寄存器的功能介绍表
c0 寄存器
c0 寄存器提供处理器和特征识别 ,内核宏定义为,可参考图理解
/*!
* Identification registers (c0) | c0 - 身份寄存器
*/
/*! Main ID Register | 主ID寄存器 */
/*! Multiprocessor Affinity Register | 多处理器关联寄存器给每个CPU制定一个逻辑地址*/
/*! Cache Size ID Registers | 缓存大小ID寄存器*/
/*! Cache Level ID Register | 缓存登记ID寄存器*/
/*! Virtualization Processor ID Register | 虚拟化处理器ID寄存器*/
/*! Virtualization Multiprocessor ID Register | 虚拟化多处理器ID寄存器*/
c1 寄存器
c1 为系统控制寄存器
/*!
* System control registers (c1) | c1 - 系统控制寄存器 各种控制位(可读写)
*/
/*! System Control Register | 系统控制寄存器*/
/*! Auxiliary Control Register | 辅助控制寄存器*/
/*! Coprocessor Access Control Register | 协处理器访问控制寄存器*/
/// 读取CP15的系统控制寄存器到 R0寄存器
STATIC INLINE UINT32 OsArmReadSctlr(VOID)
{
UINT32 val;
__asm__ volatile("mrc p15, 0, %0, c1,c0,0" : "=r"(val));
return val;
}
/// R0寄存器写入CP15的系统控制寄存器
STATIC INLINE VOID OsArmWriteSctlr(UINT32 val)
{
__asm__ volatile("mcr p15, 0, %0, c1,c0,0" ::"r"(val));
__asm__ volatile("isb" ::: "memory");
}
解读
- 从图中找到
c1-0-c0-0
行,后边的备注是SCTLR, System Control Register系统控制寄存器,其操作模式是支持Read/Write -
%0
表示r0寄存器,注意这个寄存器是CPU
的寄存器,: "=r"(val) 意思向编译器声明,会修改R0
寄存器的值,改之前提前打好招呼,都是绅士文明人。其实编译器的功能是非常强大的,不仅仅是大家普遍认为的只是编译代码的工具而已。OsArmReadSctlr
的含义就是读取CP15的系统控制寄存器到R0寄存器。 -
volatile
的意思还告诉编译器,不要去优化这段代码,原封不动的生成目标指令。 - "isb" ::: "memory" 还是告诉编译器内存的内容要被更改了,需要无效所有
Cache
,并访问实际的内容,而不是Cache!
- CRn|CRm|opc2是一套组合拳,
c7-0-c10-4
c7-0-c10-5
都表示不同的功能含义
c2、c3 寄存器
/*!
* Memory protection and control registers (c2 & c3) | c2 - 传说中的TTB寄存器,主要是给MMU使用 c3 - 域访问控制位
*/
/*! Translation Table Base Register 0 | 转换表基地址寄存器0*/
/*! Translation Table Base Register 1 | 转换表基地址寄存器1*/
/*! Translation Table Base Control Register | 转换表基地址控制寄存器*/
/*! Domain Access Control Register | 域访问控制寄存器*/
看段代码
STATIC INLINE UINT32 OsArmReadTtbr0(VOID)
{
UINT32 val;
__asm__ volatile("mrc p15, 0, %0, c2,c0,0" : "=r"(val));
return val;
}
STATIC INLINE VOID OsArmWriteTtbr0(UINT32 val)
{
__asm__ volatile("mcr p15, 0, %0, c2,c0,0" ::"r"(val));
__asm__ volatile("isb" ::: "memory");
}
STATIC INLINE UINT32 OsArmReadTtbr1(VOID)
{
UINT32 val;
__asm__ volatile("mrc p15, 0, %0, c2,c0,1" : "=r"(val));
return val;
}
STATIC INLINE VOID OsArmWriteTtbr1(UINT32 val)
{
__asm__ volatile("mcr p15, 0, %0, c2,c0,1" ::"r"(val));
__asm__ volatile("isb" ::: "memory");
}
c2
寄存器负责存页表的基地址,即一级映射描述符表的基地址。还记得吗?每个进程的页表都是独立的!c2
值一变,当前使用的页表就发生了变化,页表变化意味着虚拟地址和物理地址的映射关系发生了变化。那么什么情况下会修改里面的值呢?很容易想到只有在进程切换时发生的mmu
上下文切换,直接看代码吧!
/// mmu 上下文切换
VOID LOS_ArchMmuContextSwitch(LosArchMmu *archMmu)
{
UINT32 ttbr;
UINT32 ttbcr = OsArmReadTtbcr();//读取TTB寄存器的状态值
if (archMmu) {
ttbr = MMU_TTBRx_FLAGS | (archMmu->physTtb);//进程TTB物理地址值
/* enable TTBR0 */
ttbcr &= ~MMU_DESCRIPTOR_TTBCR_PD0;//使能TTBR0
} else {
ttbr = 0;
/* disable TTBR0 */
ttbcr |= MMU_DESCRIPTOR_TTBCR_PD0;
}
/* from armv7a arm B3.10.4, we should do synchronization changes of ASID and TTBR. */
OsArmWriteContextidr(LOS_GetKVmSpace()->archMmu.asid);//这里先把asid切到内核空间的ID
ISB; //指令必须同步 ,清楚流水线中未执行指令
OsArmWriteTtbr0(ttbr);//通过r0寄存器将进程页面基址写入TTB
ISB; //指令必须同步
OsArmWriteTtbcr(ttbcr);//写入TTB状态位
ISB; //指令必须同步
if (archMmu) {
OsArmWriteContextidr(archMmu->asid);//通过R0寄存器写入进程标识符至C13寄存器
ISB;
}
}
至于具体内核哪些地方会触发到 mmu
的切换,可前往翻看 (进程切换篇)
c4 寄存器
c4 没有用于任何 ARMv7 实现,这么不待见4,难道原因跟中国人一样觉得数字不吉利 ,但老师教的老外是不喜欢 13 啊 , 但c13确很重要
c5 c6 寄存器
c5和c6寄存器提供内存系统故障报告。此外,c6还提供了MPU区域寄存器。这一类寄存器在软件排错时可以提供非常大的帮助,比如通过 DFSR(数据状态寄存器)、IFSR(指令状态寄存器) 的 status bits 可以查到系统 abort 类型,内核中的缺页异常就是通过该寄存器传递异常地址,从而分配页面的。
/*!
* Memory system fault registers (c5 & c6) | c5 - 内存失效状态 c6 - 内存失效地址
*/
/*! Data Fault Status Register | 数据故障状态寄存器 */
/*! Instruction Fault Status Register | 指令故障状态寄存器*/
/*! Data Fault Address Register | 数据故障地址寄存器*/
/*! Instruction Fault Address Register | 指令错误地址寄存器*/
c7 寄存器
c7寄存器提供高速缓存维护操作和内存屏障操作。
c8 寄存器
c8 寄存器提供 TLB 维护功能
TLB
是硬件上的一个cache
,因为页表一般都很大,并且存放在内存中,所以处理器引入MMU
后,读取指令、数据需要访问两次内存:首先通过查询页表得到物理地址,然后访问该物理地址读取指令、数据。为了减少因为MMU导致的处理器性能下降,引入了TLB
,可翻译为“地址转换后援缓冲器”,也可简称为“快表”。简单地说,TLB
就是页表的Cache
,其中存储了当前最可能被访问到的页表项,其内容是部分页表项的一个副本。只有在TLB
无法完成地址翻译任务时,才会到内存中查询页表,这样就减少了页表查询导致的处理器性能下降。详细看
照着图说吧,步骤是这样的。
- 1. 图中的
page table
的基地址就是上面TTB
寄存器值,整个page table
非常大,有多大接下来会讲,所以只能存在内存里,TTB
中只是存一个开始位置而已。
- 虚拟地址是程序的地址逻辑地址,也就是喂给
CPU
的地址,必须经过MMU
的转换后变成物理内存才能取到真正的指令和数据。
- 3.
TLB
是page table
的迷你版,MMU
先从TLB
里找物理页,找不到了再从page table
中找,从page table
中找到后会放入TLB
中,注意这一步非常非常的关键。因为page table
是属于进程的会有很多个,而TLB
只有一个,不放入就会出现多个进程的page table
都映射到了同一个物理页框而不自知。一个物理页同时只能被一个page table
所映射。但除了TLB
的唯一性外,要做到不错乱还需要了一个东西,就是进程在映射层面的唯一标识符 -asid
,具体可前往翻看(进程切换篇)有详细说明。
c9 寄存器
c9 寄存器主要为 cache、分之预测 和 tcm 保留功能,这些保留功能由处理的实现决定
c10 寄存器
c10 寄存器主要提供内存重映射和 TLB 控制功能
c11 寄存器
c11 寄存器主要提供 TCM 和 DMA 的保留功能,这些保留功能由处理的实现决定
c12 寄存器
c12 安全扩展寄存器
c13 寄存器
c13 寄存器提供进程、上下文以及线程ID处理功能
/*!
* Process, context and thread ID registers (c13) | c13 - 进程标识符
*/
/*! FCSE Process ID Register | FCSE(Fast Context Switch Extension,快速上下文切换)进程ID寄存器 位于CPU和MMU之间*/
/*! Context ID Register | 上下文ID寄存器*/
/*! User Read/Write Thread ID Register | 用户读/写线程ID寄存器*/
/*! User Read-Only Thread ID Register | 用户只读写线程ID寄存器*/
/*! PL1 only Thread ID Register | 仅PL1线程ID寄存器*/
c14 寄存器
c14 寄存器提供通用定时器扩展的保留功能
c15 寄存器
ARMv7 保留 c15 用于实现定义的目的,并且不对 c15 编码的使用施加任何限制。 意思就是可以将他当通用寄存器来使用 语法: c15 0-7 c0-c15 0-7
百文说内核 | 抓住主脉络
- 百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切。
- 与代码需不断
debug
一样,文章内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx
代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。 - 百文在 < 鸿蒙研究站 | 开源中国 | 博客园 | 51cto | csdn | 知乎 | 掘金 > 站点发布,鸿蒙研究站 | weharmonyos中回复百文可方便阅读。
按功能模块:
- 基础知识 >>双向链表 |内核概念 |源码结构 |地址空间 |计时单位 |宏的使用 |钩子框架 |位图管理 |POSIX |main函数 |
- 进程管理 >>调度故事 |进程控制块 |进程空间 |线性区 |红黑树 |进程管理 |Fork进程 |进程回收 |Shell编辑 |Shell解析 |
- 任务管理 >>任务控制块 |并发并行 |就绪队列 |调度机制 |任务管理 |用栈方式 |软件定时器 |控制台 |远程登录 |协议栈 |
- 内存管理 >>内存规则 |物理内存 |虚拟内存 |虚实映射 |页表管理 |静态分配 |TLFS算法 |内存池管理 |原子操作 |圆整对齐 |
- 通讯机制 >>通讯总览 |自旋锁 |互斥锁 |快锁使用 |快锁实现 |读写锁 |信号量 |事件机制 |信号生产 |信号消费 |消息队列 |消息封装 |消息映射 |共享内存 |
- 文件系统 >>文件概念 |文件故事 |索引节点 |VFS |文件句柄 |根文件系统 |挂载机制 |管道文件 |文件映射 |写时拷贝 |
- 硬件架构 >>CPU历史 |ARM架构 |协处理器 |工作模式 |寄存器 |多核管理 |中断概念 |中断管理 |移值适配 |
- 内核汇编 >>汇编指令 |汇编基础 |汇编传参 |可变参数 |开机启动 |进程切换 |任务切换 |中断切换 |异常接管 |缺页中断 |
- 编译运行 >>编译过程 |编译构建 |GN语法 |忍者无敌 |ELF格式 |ELF解析 |静态链接 |重定位 |动态链接 |进程映像 |应用启动 |系统调用 |VDSO |
- 调测工具 >>模块监控 |日志跟踪 |系统安全 |测试用例 |
- 前因后果 >>总目录 |源码注释 |静态站点 |参考文档 |
百万注源码 | 处处扣细节
- 百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。
- < gitee | github | coding | gitcode > 四大码仓推送 | 同步官方源码,鸿蒙研究站 | weharmonyos 中回复 百万 可方便阅读。
据说喜欢点赞分享的,后来都成了大神。:-)