目录
逻辑移位的本质----无论左移还是右移,空出的位均补零,不保留符号信息
[arm Linux系统调用实现](#arm Linux系统调用实现)
哈佛结构
核心特征是将指令存储 和数据存储完全分离,各自使用独立的存储器和总线


arm指令格式
arm指令集采用固定长度32位的指令格式


有符号数的溢出(8bit)
无符号数的进位/借位

CPSR(当前程序状态寄存器)


ARM模式
用户模式,用户程序的工作模式,用户模式没有权限去操作其它硬件资源,只能执行处理自己的 数据;
系统模式,首先用户模式与系统模式共用同一套寄存器,系统模式为特权模式,系统模式可以直接访问硬件资源
svc模式,主要用于完成系统初始化的工作,cpu上电之后默认的工作模式,当调用swi/svc,cpu处于svc模式;
中止模式,当用户访问非法内存地址或者访问某一块不具有读写权限的内存地址时处于此工作模式;
Fiq模式 (快速中断模式),用来处 理对时间要求比较紧急的中断请求,主要用于高速数据传输及通道处理中;
IRQ模式(普通中断模式),用于处理一般的中断请求,通常在硬件产生中断信号之后自动进入IRQ模式;
arm异常类型






ARMv7架构异常向量表

arm异常的处理流程

arm寄存器

arm处理器在任何工作模式下共用一个PC寄存器(程序计数寄存器)与CPSR寄存器(当前程序状态寄存器)。

堆栈指针寄存器
栈按照栈的生长方向(递增、递减)与数据的存储方式(满栈、空栈)分为四种类型
1. 满递增栈 2. 满递减栈 3. 空递增栈 4. 空递减栈
栈顶指针(sp): 始终指向当前栈的栈顶,即最后一个入栈元素的地址(满栈)或者 下一个可用空间的地址(空栈)
递增栈:栈顶向高地址方向增长
递减栈:栈顶向低地址方向增长

push操作时处理器先减小SP值 ,然后将指定寄存器存储到SP寄存器指向的存储器地址
POP操作时处理器先将SP指向的存储器地址存储到指定寄存器中 ,然后将SP寄存器值增加。
arm模式切换流程
在ARM中,当发生异常时,处理器会自动切换到对应的异常模式,每种异常模式都有自己独立的物理寄存器,R13(SP:堆栈寄存器)、R14(LR:链接寄存器)SPSR(备份程序状态寄存器)。
当从用户模式进入IRQ模式时,保存需要保护的寄存器到IRQ模式的堆栈,步骤如下:
用户程序在用户模式下运行,使用SP_usr;
发生中断,处理器自动切换到IRQ模式,此时SP自动变为SP_irq。
保存返回地址到LR_irq (
LR_irq = 当前PC + 4
)。保存当前CPSR到SPSR_irq。
中断处理程序开始执行,使用SP_irq指向的堆栈保存寄存器(如R0-R12, LR_irq)
处理完中断后,从SP_irq的堆栈中恢复寄存器。
返回到用户模式,继续使用SP_usr。
IRQ_Handler:
; 1. 保存用户模式的寄存器(R0-R12, LR_irq保存的是返回地址)
STMFD SP!, {R0-R12, LR} ; 使用SP_irq压栈(SP_irq -= 56字节)
; 2. 处理中断(如读取定时器状态)
BL Handle_Timer_Interrupt
; 3. 恢复寄存器并返回用户模式
LDMFD SP!, {R0-R12, LR} ; 从SP_irq弹栈(SP_irq += 56字节)
SUBS PC, LR, #4 ; 返回到用户模式的下一条指令(恢复CPSR)
LDR指令、STR指令
LDR指令(Load Register)是用于从内存中加载数据到寄存器的核心指令

STR指令(Store Register)用于将寄存器中的值存储到内存中

指令流水线(三级流水线+冯诺依曼体系)
冯诺依曼体系:数据总线只有一根,取指令与取数据采用同一根数据线
三级流水线

带有访问存储器指令流水线(LDR/STR)


LDR指令
step1:访问内存------取数据------数据总线被占用------AND指令被延时(stall)
step2:数据写回寄存器------写数据------数据总线被占用------其他指令被延时
分支流水线
BL(Branch with Link) 指令核心功能是跳转到目标地址执行子程序,同时将返回地址(下一条指令地址)保存到链接寄存器LR;

B(Branch)指令的核心功能是改变程序执行流程,常用于循环控制(for/while)、条件分支(if-else) 和代码块跳转

B/BL

cpp
BL <label> #label中存储的是目标地址
跳转地址计算流程:

eg:



arm立即数编码规则
ARM立即数采用 **"8 位数值 + 4 位循环右移(Rotate Right)"**的组合编码:

arm条件码表
假设有符号数
A
和B
,比较A ≥ B
等价于判断A - B ≥ 0
设
S = A - B
,则
- 若
S ≥ 0
,则S
的符号位为 0(N=0)。- 若计算过程未溢出(V=0),则
N=0
直接表示S ≥ 0
。- 若计算过程溢出(V=1),则
N=1
才表示S ≥ 0
(溢出导致符号位反转)。
A ≥ B
的充要条件是:N 与 V 状态一致(N⊻V=0)

arm移位操作





逻辑移位vs算术移位
**逻辑移位的本质----**无论左移还是右移,空出的位均补零,不保留符号信息
算术移位的本质----右移时保留符号位(高位填充原符号位),左移与逻辑左移一致
LDM/STM指令
LDM指令:



软中断指令
软中断的本质
主动触发 :与硬件中断(由外部设备触发,如键盘、时钟)不同,软中断是程序通过显式执行指令主动发起的。
特权级切换 :在保护模式下,软中断通常用于从用户态(低特权级)切换到内核态(高特权级),以便执行受保护的操作(如访问硬件、管理内存)。

arm Linux系统调用实现
系统调用号是操作系统内核为每个系统调用分配的唯一编号!








STM指令





arm寻址方式
指令如何表达 "去哪里找数据" 或 "把结果存到哪里"?这需要一套规则 ------ 即寻址模式
寻址是确定指令中操作数位置的过程。操作数可以存储在:
寄存器(如 R0~R15)
内存(如变量、数组)
立即数(指令中直接给出的常数)


多寄存器寻址



堆栈寻址
push/pop

标签label
标签是一个符号化的地址标记,指向代码或数据在内存中的位置;
在 ARM Linux 汇编开发中,标签(Label)的分类主要基于其 作用域(可见性范围)、链接属性(符号在链接阶段的行为)以及 符号强弱性(是否允许被覆盖)可以分为 全局标签、局部标签、静态标签、弱标签。

全局标签
使用
.global
伪指令声明标签为全局,允许其他文件引用
cpp
.global 标签名 @ 声明为全局标签
标签名: @ 标签定义
汇编指令
定义全局函数
cpp
@定义一个全局函数 add_two
.global add_two @ 声明为全局
add_two:
ADD R0, R0, R1 @ R0 = R0 + R1
BX LR @ 返回
定义全局变量
cpp
.data
.global g_counter @ 声明全局变量
g_counter:
.word 0x0 @ 初始值为0
使用场景



局部标签
在 ARM 汇编中,局部标签是仅在 当前代码块或作用域内有效 的临时符号,用于简化循环、条件分支等控制逻辑,避免命名冲突 。局部标签通过 数字 + 冒号 定义,并通过
b
(backward)和f
(forward) 引用;
定义格式 :以数字开头,后跟冒号(如
1:
、2:
);作用域:仅在当前代码块(如函数或循环体)内有效;
引用方式
1b
:向后跳转(Backward),指向 最近的前一个1:
标签。
1f
:向前跳转(Forward),指向 最近的后一个1:
标签
引用规则
就近原则 :根据
b
/f
方向,寻找最近的同名标签。可重复使用 :同一代码块内可多次定义同名局部标签(如
1:
)。
使用场景
cpp
.text
.global _start
_start:
MOV R0, #5 @ 初始化 R0 = 5
loop_start: @ 全局标签(可选)
1: @ 局部标签1(循环入口)
SUBS R0, R0, #1 @ R0 = R0 - 1,更新标志位
BEQ 1f @ 若 R0 == 0,向前跳转到局部标签1(循环出口)
B 1b @ 否则,向后跳转到局部标签1(继续循环)
1: @ 局部标签1(循环出口)
MOV R7, #1 @ 退出系统调用号
SVC #0 @ 触发系统调用
弱标签
在 ARM 汇编中,弱标签(Weak Label) 是一种允许 同名符号覆盖 的标签类型。它常用于定义 默认实现 或 可选功能 ,当链接器发现同名的 强标签(Strong Label) 时,会优先使用强标签的定义,弱标签的定义则被忽略。
cpp
.weak 标签名 @ 声明为弱标签
标签名: @ 标签定义
汇编指令
弱标签的覆盖原则
强标签优先:若存在同名的强标签(通过
.global
声明),链接时使用强标签的定义。无强标签时生效:若未定义强标签,链接器使用弱标签的实现。
允许多个弱标签:若多个弱标签同名,链接器随机选择其一(通常报警告)。
使用场景



静态标签
静态标签 是仅在 当前汇编文件内有效 的符号,不可被其他文件引用。它的核心作用是 封装内部实现,避免命名冲突,提升代码模块化;
静态标签的定义

使用场景
在文件
utils.s
中定义静态函数internal_add
,仅在文件内部调用;在main.s
中尝试调用该函数将失败。



伪指令
NOP伪指令
NOP(
No Operation)伪指令 核心功能 1. 实现延时 2. 对齐指令地址

ADR伪指令
ADR伪指令 可以获取程序的运行地址,而不是链接地址

ADRL伪指令

注意: 标签地址必须与
ADRL
指令位于同一代码段内
LDR伪指令
LDR伪指令(Pseudo-Instruction)主要功能如下:
- 加载32位立即数到寄存器;
- 加载标签地址到寄存器;
LDR伪指令的两种形式


注意:#offset是当前指令地址(PC)到 文字池中目标数据地址 的偏移量


arm伪操作



汇编调用c程序
cpp
汇编调用c程序流程
@1 c文件中实现c语言函数
@2 .extern伪操作声明c函数 .extern my_add
@3.1 根据ATPCS标准,R0,R1,R2,R3用于传递参数,由于c语言函数的参数可能超过4个
@3.2 采用伪指令ldr初始化栈空间
@4 传递参数 mov r0,#0x01
@5 调用函数 BL/B my_add
cpp
@start.s文件
.text
.global _start @将_start声明为全局的
.extern my_add
_start:
@先初始化要使用的栈空间
ldr sp, =0x400
mov r0, #10
mov r1, #20
mov r2, #30
mov r3, #40
mov r5, #50
push {r5}
bl my_add
loop:
b loop
.data
.align 4 @按2^4字节对齐
.space 4096
.end
cpp
//add.c文件
int my_add(int x, int y, int z, int m, int n)
{
return x + y + z + m + n;
}
c调用汇编程序
cpp
1. 定义汇编函数my_add, 注意传递的参数、返回值满足ATCPS规则
2. 汇编文件中通过.global 把my_add声明为全局函数
3. C语言中通过extern声明外部函数my_add
bash
@start.s文件
.text
.global _start
.extern main
_start:
ldr sp,=0x400
bl main
.data
.align 4
.space 4096
loop:
b loop
.end
cpp
//main.c文件
extern int my_add(int x,int y,int z);
int main()
{
int ret=my_add(10,20,30);
return ret;
}
cpp
@ add.s文件
.arm
.text
.global my_add
my_add:
add r5,r0,r1
add r5,r5,r2
mov r0,r5 @函数的返回值通过R0传递
.end
内联汇编
内联汇编允许在高级语言(如C/C++)中直接嵌入汇编代码,内联汇编的应用场景:
- 程序中使用饱和算术运算(Saturating Arithmetic)
- 程序需要操作协处理器;
- C程序中需要操作程序状态寄存器;
饱和算术运算:
饱和算术运算是一种在数值运算中防止溢出的处理方式。当运算结果超出数据类型的表示范围时,结果会被"饱和"到该数据类型能表示的最大值或最小值;
协处理器
ARM架构通过支持协处理器来扩展处理器的功能。ARM架构的处理器支持最多16个协处理器,通常称为CP0~CP15;
CP15,提供系统控制功能,主要用于配置MMU、TLB和Cache、异常向量表的地址设置;
内联汇编的基本语法
cpp
//__asm__告知编译器不用检查后面的内容,只需要交给汇编器进行处理
__asm__ volatile (
"asm code"
: 输出操作数列表
: 输入操作数列表
: 破坏列表(Clobber List)
);
1. volatile
关键字禁止编译器优化内联汇编代码;
2. "asm code"主要用于填写汇编代码
,用\n\t
分隔多行汇编指令;
cpp
__asm__ volatile (
"add r0, r1, r2\n\t"
"add r0, r0, r3\n\t"
"mov r0, r0"
: /* 输出操作数列表 */
: /* 输入操作数列表 */
: /* 破坏列表 */
);
3. 输出操作数列表(asm->c/c++)
用于指定汇编代码执行后需要传递回 C/C++ 变量的结果,通常只能为变量;
cpp
__asm__ volatile (
"mov r0, #0xFF"
:=r(value) // 将r0的值写入value
: // 输入操作数列表
: // 破坏列表
);
每个输出操作数的格式为
"约束"(变量)
,多个操作数用逗号分隔,其中约束由两部分构成,一部分为方向修饰符(+,=,&),方向修饰符用于指定该操作数是输入还是输出,输出操作数必须包含=
(表示操作数仅用于输出等价于只写)或+
(表示操作数既是输入又是输出等价于可读可写),& 必须通过 +& 或者 =& 进行使用,另一部分为约束符(r,m),约束符用于指定操作数的存放位置,其中r 约束(寄存器约束)要求操作数存储在通用寄存器中,
m
约束(内存约束)要求操作数直接存储在内存地址中,避免通过寄存器中转;
=r : 输出到通用寄存器,操作数仅用于写入
cpp
__asm__ volatile (
"mov r0, #42"
: "=r" (result)
);
结果写入寄存器
r0
,再传递给result
;
4. 输入操作数列表(c/c++ -> asm)
用于c/c++程序向汇编代码传递数据,输入操作数的值在汇编代码中不应被修改**,** 只能读取,因此输入操作数无需方向修饰符,常用约束符 r(变量值存入通用寄存器)m(变量直接通过内存地址访问)i(立即数(整数常量)),此外用于定义输入的参数,可以是变量也可以是立即数;
cpp
int a = 10, b = 20;
__asm__ volatile (
"add r0, %1, %2\n\t" // %0, %1, ..., %n表示占位符,先按照输出操作数的顺序进行引用
//再按照输入操作数的顺序进行引用
"mov %0, r0" // result = r0
: "=r" (result) // 输出操作数: result -> %0
: "r" (a), "r" (b) // 输入操作数:a -> %1, b-> %2
: "r0" // 破环列表用于告知编译器哪些寄存器或资源被隐式修改
);
5. 破坏列表
告知编译器哪些寄存器或资源被隐式修改,常见值如下:
"cc"
:条件码寄存器(CPSR);
"memory"
:表示内存被修改(强制刷新内存缓存);
"r0"
,"r1"
:指定被破坏的寄存器;
cpp
__asm__ volatile (
"mov r0, %1\n\t"
"str r0, [%0]"
:
: "r" (addr), "r" (value)
: "r0", "memory" // 声明破坏 r0 和 memory
);