Changchun Master Li

[RPi bring up] 深入树莓派内部,arm汇编语言精粹(下)

2023-03-31

首先我们回顾一下上一篇文章,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入栈,直接通过寄存器传递。

arm用户寄存器

这17个寄存器构成了arm程序的上下文context,当需要进程切换时,在调度进程前,我们至少要将这17个寄存器保存到内存中。

2. 操作系统视角

操作系统程序视角来看,这只是庞大系统的冰山一角。ARMv6有如下9种模式(部分cpu实现了Hypervisor),用户程序仅仅运行于user模式。因此,ARMv6实际上寄存器有43个

arm寄存器

寄存器在各种运行模式下有相应的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
2
MCR{cond} P15, <Op1>, <Rd>, <CRn>, <CRm>, <Op2>  // load  core registers to cp15
MRC{cond} P15, <Op1>, <Rd>, <CRn>, <CRm>, <Op2> // store cp15 to the core registers

CRn 指定CP15的寄存器
Op1、Op2和CRm 指定特定的操作

异常

1. 处理器模式切换

前面介绍了处理器有9种工作模式,这些模式是怎样切换的?

cpsr

我们知道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
2
3
4
5
MOV     r0, #10000              // user mode
MRS r2, cpsr // 读取cpsr到r2寄存器
BIC r2, r2, #1f // Bit Clear, r2最低5个bit清零
ORR r2, r2, r0 // 设置user mode
MSR cpsr, r2 // 写回cpsr

从受限模式到特权模式的切换则复杂一些,在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
2
3
MRC P15, 0, r0, c1, c0, 0    // 读取 Control 寄存器
MOV r1, #0x00002000 // 设置V bit,High exception vectors
MCR p15, 0, r0, c1, c0, 0 // 写回 Control 寄存器

还可以通过c12中断向量基地址寄存器Vector Base Address Register直接指定vector地址

1
2
LDR r0, =vectors             // 伪指令,获取vector符号地址
MCR p15, 0, r0, c12, c0, 0 // 写入c12

3. 进入和退出异常

在处理异常之前,首先保存当前处理器的状态,这样在当处理程序routine结束之后,原程序可以恢复。

系统会自动帮你操作

  1. 保存下一条指令的地址(pc+4或者pc+8)到lr
  2. 复制cpsr到相应的spsr
  3. 设置cpsr mode bit到相应的处理器模式
  4. pc跳转到中断相应的地址
  5. 处理器自动关闭interrupt/abort flags,以防异常重叠到一起无法管理

当程序处理完异常,想要回到之前的处理器上下文,该怎么做呢

  1. 恢复lr到pc
  2. 目标寄存器设置为pc,在算术逻辑指令加上S后缀
1
2
MOVS pc, r14_svc     // 从svc恢复,跳过swi指令
SUBS pc, r14_irq, #4 // 从irq恢复,需要减4执行陷入异常时的指令

系统通过S后缀和目标寄存器pc得知要返回正常的user程序,会自动将spsr恢复到cpsr中。

内存管理

ARMv6硬件支持虚拟内存,mmu也是通过cp15进行控制。可以支持4KB 64KB 1MB 16MB粒度的page,大多数32位操作系统都是两级页表管理4KB页。

page_translation

section和page

地址翻译从最左边开始,翻译表基地址Translation table base由TTBR寄存器控制

1
2
mov r0, #0
MCR p15, 0, r0, c2, c0 // 设置TTBR0 = 0x0

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等等,这些内容也同样值得深入探讨研究。

参考

ARM1176JZFS ddi0301
ARMv7ARM ddi0406

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章