【文章福利】小编推荐自己的Linux内核源码交流群:【点击链接加入群聊869634926】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前50名可进群领取,并额外赠送一份价值798的内核资料包(含视频教程、电子书、实战项目及代码)!
点击下方链接即可免费领取内核相关学习资料哦
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
内核资料领取:Linux内核源码技术学习路线+视频教程内核源码
基本设定
架构(32位)
约定
Byte(字节):8bits
Halfword(半字) :16 bits (2 byte)
Word(字):32bits(4 byte)
指令集
ARM指令集(32-bit)
Thumb指令集(16-bit)
Thunmb指令集(16&32bit)不考虑
工作模式
种类:七种
非特权模式(Normal:普通模式)
User(用户模式):非特权模式,大部分时候在这个模式下工作
特权模式(Privilege:特权模式)
FIQ(快速中断模式):当一个高优先级(fast)中断产生时将会进入这种模式
IRQ(普通中断模式):当一个低优先级(normal)中断产生时进入这种模式
SVC(管理模式):当复位或软中断指令执行时进入
Abt(数据访问终止模式):当存取异常时进入
und(未定义指令终止模式):当执行未定义指令进入
sys(系统模式):使用和User模式相同的寄存器的特权模式
Privilege除了System模式外,其他5种为异常模式
各种模式的切换,程序员通过代码切换,(CPSR寄存器);也可以CPU在某些情况下自动切换(中断或者按复位键)
为什么要有这么多模式?
操作系统有安全级别要求,多模式为了方便操作系统多种角色安全等级需求
ARM 处理器有 37 个 32 位长的寄存器。
1 个用作 PC(程序指针)。
1 个用作 CPSR(程序状态寄存器)。
5 个用作 SPSR(备份程序状态寄存器)。
30 个用作通用寄存器。
注意:System模式使用user模式寄存器集
CPSR程序状态寄存器
条件位
N:Negative result from ALU (ALU运算时负结果则置1)
Z:Zero result from ALU (ALU运算时零结果则置1)
C:ALU operation Carried out(进位标志位则置1)
V:ALU operation Carried out(溢出是则置1)
Mode位(理论上可以有32种模式)
实际ARM只有7种工作模式(每种模式值可查)
T位(处理器状态控制位)
T = 0:处理器处于ARM状态(默认)
T = 1:处理器处于Thumb状态
中断禁止位:
I = 1:禁止 IRQ
F = 1:禁止FIQ(快速中断)
异常:正常工作之外的流程都叫异常,中断是异常的一种。
异常会打断正在执行的工作,并且一般我们希望异常处理完成后继续回来执行原来的工作。
异常向量表
所有的CPU都有异常向量表,这是CPU设计时就设定好的,是硬件决定的。
当异常发生时,CPU会自动动作(PC跳转到异常向量处处理异常,有时伴有一些辅助动作)。
异常向量表是硬件向软件提供的处理异常的支持。
异常处理机制(处理过程)
产生异常时,ARM内核
拷贝 CPSR 到 SPSR_
设置适当的CPSR位
改变处理器状态进入 ARM 态
改变处理器模式进入相应的异常模式
设置中断禁止位禁止相应中断 (如果需要)
保存返回地址到 LR_(R14)
设置 PC 为相应的异常向量
从异常返回时
从 SPSR_恢复CPSR
从LR_恢复PC
注意:这些操作只能在 ARM 态执行。
异常处理中有一些是硬件自动做的,有一些是程序员需要自己做的。需要搞清楚哪些是需要自己做的,才知道如何写代码。 以上说的是CPU设计时提供的异常向量表,一般成为一级向量表。有些CPU为了支持多个中断,还会提供二级中断向量表,处理思路类似于这里说的一级中断向量表。
指令与伪指令(汇编)
指令:指令是CPU机器指令的助记符,经过编译后会得到一串10组成的机器码,可以由CPU读取执行。
伪指令:伪指令本质上不是指令(只是和指令一起写在代码中),它是编译器环境提供的,目的是用来指导编译过程,经过编译后伪指令最终不会生成机器码。
两种风格
ARM官方的指令风格:指令一般用大写,一般用于Windows的开发环境(ADS,MDK等)如: LDR R0, [R1]。
GNU风格:指令一般用小写字母、linux中常用。如:ldr r0, [r1]。
ARM汇编特点
LDR/STR架构
ARM采用RISC架构,CPU本身不能直接读取内存,而需要先将内存中内容加载入CPU中通用寄存器中才能被CPU处理。
ldr(load register)指令将内存内容加载入通用寄存器。
str(store register)指令将寄存器内容存入内存空间中。
ldr/str组合用来实现 ARM CPU和内存数据交换
八种寻址方式
寄存器寻址 mov r1, r2 r2的值赋值给r1
立即寻址 mov r0, #0xFF00 #后面的数值直接赋值给r0
寄存器移位寻址 mov r0, r1, lsl #3(书本无) r1中的数值左移三位,然后赋值给r0(就是乘于8)
寄存器间接寻址 ldr r1, [r2] 类似于指针,r2中存操作数的地址,[]类似于解引用
基址变址寻址 ldr r1, [r2, #4] r2为基址,r2里面的地址加4,这个地址存的值读到寄存器中
多寄存器寻址 ldmia r1!, {r2-r7, r12} r1中的8个地址读到r2-r7和r12中(类似于数值中的8个元素)
堆栈寻址 stmfd sp!, {r2-r7, lr} 将寄存器列表中的寄存器(R2到R7,lr)存入堆栈
相对寻址 beq flag
指令后缀
同一指令经常附带不同后缀,变成不同的指令。经常使用的后缀有:
B(byte)功能不变,操作长度变为8位。
H(half word)功能不变,长度变为16位。
!如果指令地址表达式中不含“!”后缀,则基址寄存器中的地址不会发生变化,指令中含有则变化,变化结果如下:基址寄存器中的值(指令执行后)=指令执行前的值+地址偏移量
注意:
“!”后缀必须紧跟在地址表达式后面,而地址表达式要有明确的地址偏移量。
“!”后缀不能用于R15(PC)的后面
当用于单个寄存器后面时,必须确性这个寄存器有隐性的偏移量,eg:“STMDB SP!,{R3,R5,R7}”此时地址基址寄存器SP的隐性偏移量是4。
S(signed)功能不变,操作数变为有符号,如 ldr ldrb ldrh ldrsb ldrsh。
S(S标志)功能不变,影响CPSR标志位,如 mov和movs movs r0, #0。指令中使用“S”后缀,指令执行后状态寄存器的条件标志位将被刷新;不使用“S”后缀时,指令执行后状态寄存器的条件标志位不会发生变化。此标志经常用于对条件进行测试,例如:是否溢出,是否进位等;根据这些变化,就可以进行一些判断,是否大于,是否相等,从而可能影响指令执行顺序。
两个S用于不同的指令,名称相同,但是在不同的指令结合却有不同的作用
条件执行后缀
**注意** - 条件后缀是否成立,不是取决于本句代码,而是取决于这句代码之前的代码运行后的结果。 - 条件后缀决定了本句代码是否被执行,而不会影响上一句和下一句代码是否被执行。
多指令流水线
为增加处理器指令流的速度,ARM使用多级流水线.,下图为3级流水线工作原理示意图。(ARM11为8级),当处理器执行简单的数据处理指令时,流水线使得平均每个时钟周期能完成 1 条指令。但 1 条指令需要 3个时钟周期来完成,因此,有 3 个时钟周期的延时( latency),但吞吐率( throughput)是每个周期一条指令。
**注意**:PC指向正被取指的指令,而非正在执行的指令
常用ARM指令
数据处理指令
数据传输指令 mov mvn
算术指令 add sub rsb adc sbc rsc
逻辑指令 and orr eor bic
比较指令 cmp cmn tst teq
乘法指令 mvl mla umull umlal smull smlal
前导零计数 clz
CPSR访问指令
mrs & msr
mrs用来读psr,msr用来写psr
CPSR寄存器比较特殊,需要专门的指令访问,这就是mrs和msr。
跳转(分支)指令
b & bl & bx
b 直接跳转(就没打开算返回)
bl branch and link,跳转前把返回地址放入lr中,以便返回,以便用于函数调用
bx跳转同时切换到ARM模式,一般用于异常处理的跳转。
访存指令
ldr/str & ldm/stm & swp
单个字/半字/字节访问 ldr/str
多字批量访问 ldm/stm
swp r1, r2, [r0] 将 r0 所指向的存储器中的字数据传输到 r1,同时将 r2中的字数据传输到 r0 所指向的内存单元。
swp r1, r1, [r0] 该指令完成将 r0 所执行的存储器中的子数据与 r1 中的字数据互换。
ARM汇编中的立即数
ARM指令都是32位,除了指令标记和操作标记外,本身只能附带很少位数的立即数。因此立即数有合法和非法之分。
合法立即数:并不是所有的 32-bit 立即数都是可以使用的合法立即数。只有那些通过将一个 8-bit 的立即数循环右移偶数位可以得到的立即数才可以在指令中使用。(了解即可)
注意:加载立即数一般采用伪指令 ldr, 编译器会自动处理非法立即数,这里了解即可,
软中断指令
swi(software interrupt)
软中断指令用来实现操作系统中系统调用(真实使用场景用处不大,仅作学习使用)
协处理器(CP15)操作指令
CP15,即通常所说的系统控制协处理器( System Control Coprocesssor)。 SoC内部另一处理核心,协助主CPU实现某些功能,被主CPU调用执行一定任务。
ARM 处理器支持 16 个协处理器。在程序执行过程中,每个协处理器忽略属于 ARM 处理器和其他协处理器的指令。当一个协处理器硬件不能执行属于它的协处理器指令时,将产生一个未定义指令异常中断,在该异常中断处理程序中,可以通过软件模拟该硬件操作。(不必深究)
mcr & mrc
mrc用于读取CP15中的寄存器
mcr用于写入CP15中的寄存器
使用方法
mcr{} p15, , , , , {}
opcode_1:对于cp15永远为0
Rd:ARM的普通寄存器
Crn:cp15的寄存器,合法值是c0~c15
Crm:cp15的寄存器,一般均设为c0
opcode_2:一般省略或为0
举例
mrc p15, 0, r0, c1, c0, 0 该指令将协处理器 p15 的寄存器中的数据传送到ARM处理器的寄存器中
mcr p15, 0, r0, c1, c0, 0 该指令将ARM处理器寄存器 r0 中的数据传送到协处理器 p15 的寄存器 c1 和 c0 中。
协处理器的学习要点
不必深究
只看一般用法,不详细区分参数细节,否则会陷入很多复杂未知中。关键在于理解,而不在于记住。
批量数据加载存储指令(LDM/STM与栈的处理)
为什么需要多寄存器访问指令
ldr/str每周期只能访问4字节内存,如果需要批量读取、写入内存时太慢,解决方案是stm/ld
stm/ldm
ldm(load register mutiple) 加载多个寄存器
stm(store register mutiple) 存储多个寄存器
举例:
stmia sp, {r0 - r12}
将r0存入sp指向的内存处(假设为0x30001000);然后地址+4(即指向0x30001004),将r1存入该地址;然后地址再+4(指向0x30001008),将r2存入该地址······直到r12内容放(0x3001030),指令完成。
一个访存周期同时完成13个寄存器的读写
八种后缀
ia(increase after)先传输,再地址+4
ib(increase before)先地址+4,再传输
da(decrease after)先传输,再地址-4
db(decrease before)先地址-4,再传输
fd(full decrease)满递减堆栈
ed(empty decrease)空递减堆栈
fa(·······) 满递增堆栈
ea(·······)空递增堆栈
四种栈
空栈:栈指针指向空位,每次存入时可以直接存入然后栈指针移动一格;而取出时需要先移动一格才能取出
满栈:栈指针指向栈中最后一格数据,每次存入时需要先移动栈指针一格再存入;取出时可以直接取出,然后再移动栈指针
增栈:栈指针移动时向地址增加的方向移动的栈
减栈:栈指针移动时向地址减小的方向移动的栈
后缀符号的作用
!
的作用
ldmia r0, {r2 - r3}
ldmia r0!, {r2 - r3}
感叹号的作用就是r0的值在ldm过程中发生的增加或者减少最后写回到r0去,也就是说ldm时会改变r0的值。
^
的作用
ldmfd sp!, {r0 - r6, pc}
ldmfd sp!, {r0 - r6, pc}^
^的作用:在目标寄存器中有pc时,会同时将spsr写入到cpsr,一般用于从异常模式返回。
总结:批量读取或写入内存时要用ldm/stm指令各种后缀以理解为主,不需记忆,最常见的是stmia
(空堆栈递增)和stmfd
(满堆栈递减)。
谨记:操作栈时使用相同的后缀(LDM/STM)就不会出错,不管是满栈还是空栈、增栈还是减栈。
伪指令的意义
伪指令不是指令,伪指令和指令的根本区别是经过编译后会不会生成机器码。
伪指令的意义在于指导编译过程。
伪指令是和具体的编译器相关的,我们使用gnu工具链,因此学习gnu环境下的汇编伪指令。
GUN汇编中的一些符号
@
用来做注释。可以在行首也可以在代码后面同一行直接跟,和C语言中//
类似。
#
做注释,一般放在行首,表示这一行都是注释而不是代码。
:
以冒号结尾的是标号。
.
点号在gnu汇编中表示当前指令的地址。
#
立即数前面要加#或 $
,表示这是个立即数。
常用GUN伪指令
.global _start @ 给_start外部链接属性
.section .text @ 指定当前段为代码段
.ascii .byte .short .long .word
.quad .float .string @ 定义数据
.align 4 @ 以16字节对齐
.balignl 16 0xabcdefgh @ 16字节对齐填充
.equ @ 类似于C中宏定义
偶尔用到的GUN伪指令
.end @标识文件结束
.include @ 头文件包含
.arm / .code32 @声明以下为arm指令
.thumb / .code16 @声明以下为thubm指令
最重要的几个伪指令
ldr 大范围的地址加载指令
adr 小范围的地址加载指令
adrl 中等范围的地址加载指令
nop 空操作
adr与ldr
adr编译时会被1条sub或add指令替代,而ldr编译时会被一条mov指令替代或者文字池方式处理;
adr总是以PC为基准来表示地址,因此指令本身和运行地址有关,可以用来检测程序当前的运行地址
在哪里
ldr加载的地址和链接时给定的地址有关,由链接脚本决定。
ARM中有一个ldr指令,还有一个ldr伪指令
一般都使用ldr伪指令而不用ldr指令