首先我们回顾一下上一篇文章,arm32汇编有以下特点
- 32bit指令
- 4GB地址空间
- 16个32bit通用寄存器
- 哈弗架构,取数据和取指令分别、并发的访问内存
- 有单独的load/store指令
- 默认为小端字节序(高地址保存高字节,数字顺序)可以支持大端模式(低地址保存高字节,字符串阅读顺序)
处理器模式
1. 受限的应用程序视角
上节我们了解到,从一个应用程序的角度看,arm有16个32位寄存器r0-r15和1个当前程序状态寄存器CPSR,也被称为APSR
- r15是pc,程序计数寄存器,指向当前cpu运行的指令的地址,顺序执行时,每完成一个指令加4(4×8=32bit),写入r15会让cpu立即跳转。
- r0-r14都可以作为通用寄存器来使用。传统上,
- r14是lr,链接寄存器,在使用bl指令函数调用时会将caller的地址储存在lr上。
- r13是sp,栈指针
- r12是ip,Intra-Procedure-call
- r11是fp,frame pointer
- r10是sl,stack limit
- r9是sb,static base,存储data段的基地址
- r0-r3作为参数和返回值寄存器,当C语言参数小于等于4个的时候,为了加快速度,参数不会push入栈,直接通过寄存器传递。
这17个寄存器构成了arm程序的上下文context,当需要进程切换时,在调度进程前,我们至少要将这17个寄存器保存到内存中。
2. 操作系统视角
操作系统程序视角来看,这只是庞大系统的冰山一角。ARMv6有如下9种模式(部分cpu实现了Hypervisor),用户程序仅仅运行于user模式。因此,ARMv6实际上寄存器有43个
寄存器在各种运行模式下有相应的banking,标注为阴影的寄存器就是banking。除了user和sys模式共享同一套寄存器,在不同模式下,访问同一个寄存器其实并不在同一个地方。在user模式下访问r13实际是r13_usr,在svc模式下访问r13实际是r13_svc。有了banking,在模式切换时不需要保存和恢复sp和lr寄存器,架构会自动处理。
fiq快速中断模式为了更快响应中断切换模式,banking寄存器最多。
特定模式下的banking只能在特定模式下才能访问。但所有特权模式都可以访问user模式的寄存器。使用^配合stm和ldm指令即可,例如
1 | STMFD r13, {sp, lr}^ //把sp_usr和lr_usr压入栈 |
每种模式(除sys)都多一个spsr寄存器,用来保存模式切换之前的cpsr状态
我们并不需要关心所有的模式,sys模式和fiq模式只有在某些实时系统的特定功能才用到。cpu data prefetch出错会陷入abt模式,cpu拿到未定义undefined指令时会进入und模式。但我们至少需要详细了解下面三种模式
USR 受限的应用程序模式,不能访问系统硬件、寄存器
IRQ 中断后进入该模式
SVC 特权模式,系统启动后为svc模式,此模式拥有最高权限,swi指令产生的软中断也会导致进入该模式
依靠硬件,操作系统可以实现上下文切换,进程隔离,权限控制,进程管理等功能。操作系统依赖于cpu架构。并不是每一种cpu上都可以实现所有操作系统功能,一些嵌入式架构就是例外,cortex-M3核心使用了双堆栈,MSP和PSP,主栈和任务栈,方便实时多任务操作系统的实现,但它的mpu只实现了mmu的一个子集,不具备虚拟内存的功能。
系统控制协处理器
ARMv6处理器支持连接片上协处理器的外部接口,
- CP10 VFP 控制协处理器
- CP11 VFP控制协处理器
- CP14 Debug和ETM
- CP15 系统控制协处理器
其中CP15为系统控制协处理器,用来控制和提供系统信息,主要功能有
- 系统配置
- cache管理
- TCM管理(紧耦合内存)
- MMU管理
- DMA管理
- 系统性能检测
- 有两个指令MCR、MRC可以操作CP15的寄存器
1 | MCR{cond} P15, <Op1>, <Rd>, <CRn>, <CRm>, <Op2> // load core registers to cp15 |
CRn 指定CP15的寄存器
Op1、Op2和CRm 指定特定的操作
异常
1. 处理器模式切换
前面介绍了处理器有9种工作模式,这些模式是怎样切换的?
我们知道cpsr分成了四个域cxsf
- cpsr[ 7: 0] c = control
- cpsr[15: 8] x = extension
- cpsr[23:16] s = status
- cpsr[31:24] f = flags
1 | MSR cpsr_c, r2 |
通过寄存器后缀语法可以只修改特定的域,防止误操作
特权模式到受限模式直接改cpsr寄存器的Mode bits即可
- b10000 USER Mode
- b10011 SVC Mode
- b10010 IRQ Mode
1 | MOV r0, #10000 // user mode |
从受限模式到特权模式的切换则复杂一些,在user模式下处理器没有权限直接操作Mode bits。切换发生在异常和中断。
2. 异常处理
当在程序正常执行,遇到异常不得不暂停处理,这就是中断。比如计时器timer到达预定时间、uart收到了消息、访问非法内存、swi系统调用,等等(但arm架构div0不会产生异常)
ARMv6 中断概览
地址 | 中断向量 |
---|---|
vector+0x00 | Reset |
vector+0x04 | Undefined instruction |
vector+0x08 | Software Interrupt exception |
vector+0x0c | Prefetch Abort |
vector+0x10 | Data Abort |
vector+0x14 | Interrupt request (IRQ) exception |
vector+0x18 | Fast Interrupt Request (FIQ) exception |
vector+0x1c | Secure Monitor Call Exception |
ARMv6中断向量vector
系统boot成功后,0x00000000是默认的vector地址,CP15的control寄存器V bit可以启用high vector,地址是4G内存空间中的最末尾1MB,0xffff0000
1 | MRC P15, 0, r0, c1, c0, 0 // 读取 Control 寄存器 |
还可以通过c12中断向量基地址寄存器Vector Base Address Register直接指定vector地址
1 | LDR r0, =vectors // 伪指令,获取vector符号地址 |
3. 进入和退出异常
在处理异常之前,首先保存当前处理器的状态,这样在当处理程序routine结束之后,原程序可以恢复。
系统会自动帮你操作
- 保存下一条指令的地址(pc+4或者pc+8)到lr
- 复制cpsr到相应的spsr
- 设置cpsr mode bit到相应的处理器模式
- pc跳转到中断相应的地址
- 处理器自动关闭interrupt/abort flags,以防异常重叠到一起无法管理
当程序处理完异常,想要回到之前的处理器上下文,该怎么做呢
- 恢复lr到pc
- 目标寄存器设置为pc,在算术逻辑指令加上S后缀
1 | MOVS pc, r14_svc // 从svc恢复,跳过swi指令 |
系统通过S后缀和目标寄存器pc得知要返回正常的user程序,会自动将spsr恢复到cpsr中。
内存管理
ARMv6硬件支持虚拟内存,mmu也是通过cp15进行控制。可以支持4KB 64KB 1MB 16MB粒度的page,大多数32位操作系统都是两级页表管理4KB页。
section和page
地址翻译从最左边开始,翻译表基地址Translation table base由TTBR寄存器控制
1 | mov r0, #0 |
ARMv6还有另一个TTBR1,官方建议用两套页表,TTBR0用来用户多任务切换,TTBR1由操作系统和IO使用。这一行为由TTBCR的N bits控制,默认N=0,只启用TTBR0可兼容ARMv5
TTBR0指向的是第一级页表,由虚拟地址高12bits索引VA[31:20],每一个页描述符占32bits,4个字节。一个比较省心的做法是一次性分配2^12*4=16KB内存(0x0000-0x3fff)来保存整个一级页表。
first_level_descriptor
根据最后两个bits,页描述符共有5种
- Translation fault 无效页
- Coarse page table 细粒度页表,即二级页表,可以管理4KB 16KB 64KB页
- Section 段 1MB
- Super Section 超级段 16MB
- Reserved 系统保留,同无效页
总结
很多教程在刻意回避底层架构与操作系统之间的关联,只讲简单的应用程序,ARM核心本身就是一座大山,本文粗略介绍了ARMv6底层架构和相应的汇编操作,帮助入门者有更全面的认识。另外,ARMv6是个非常成熟的架构,其中有很多细节和机制帮助完成故障恢复failover、性能performace、权限permission control。例如barrier、cache、TLB等等,这些内容也同样值得深入探讨研究。
参考
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
扫描二维码,分享此文章