ARM 汇编启动代码详解:从中断向量表到中断处理

ARM 汇编启动代码详解:从中断向量表到中断处理

引言

在嵌入式系统开发中,ARM 处理器(如 Cortex-A 系列)的启动代码是系统初始化和运行的基础。启动代码通常包括中断向量表的创建、初始化硬件状态(如关闭缓存和 MMU)、设置栈指针以及处理各种中断(如 IRQ、FIQ 等)。本文将详细解析一段典型的 ARM 汇编启动代码,涵盖 _start 函数、中断向量表、复位处理程序(Reset_Handler)以及 IRQ 中断处理程序(IRQ_Handler)。代码参考了 ARM Cortex-A(armV7)编程手册和 Cortex-A7 技术参考手册,确保内容准确且实用。


1. 代码概述

以下是完整代码的结构:

.global _start  				/* 全局标号 *//** 描述:_start函数,首先是中断向量表的创建* 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)* 		 	ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)*/
_start:ldr pc, =Reset_Handler        /* 复位中断 */    ldr pc, =Undefined_Handler    /* 未定义中断 */    ldr pc, =SVC_Handler          /* SVC(Supervisor)中断 */    ldr pc, =PrefAbort_Handler    /* 预取终止中断 */    ldr pc, =DataAbort_Handler    /* 数据终止中断 */    ldr pc, =NotUsed_Handler      /* 未使用中断 */    ldr pc, =IRQ_Handler          /* IRQ中断 */    ldr pc, =FIQ_Handler          /* FIQ(快速中断)未定义中断 *//* 复位中断 */    
Reset_Handler:cpsid i                       /* 关闭全局中断 *//* 关闭 I、D Cache 和 MMU */mrc p15, 0, r0, c1, c0, 0     /* 读取 CP15 的 C1 寄存器到 R0 中 */    bic r0, r0, #(0x1 << 12)      /* 清除 C1 寄存器的 bit12 位 (I 位),关闭 I Cache */    bic r0, r0, #(0x1 << 2)       /* 清除 C1 寄存器的 bit2 (C 位),关闭 D Cache */    bic r0, r0, #0x2              /* 清除 C1 寄存器的 bit1 (A 位),关闭对齐 */    bic r0, r0, #(0x1 << 11)      /* 清除 C1 寄存器的 bit11 (Z 位),关闭分支预测 */    bic r0, r0, #0x1              /* 清除 C1 寄存器的 bit0 (M 位),关闭 MMU */    mcr p15, 0, r0, c1, c0, 0     /* 将 r0 寄存器中的值写入到 CP15 的 C1 寄存器中 */#if 0/* 汇编版本设置中断向量表偏移 */ldr r0, =0X87800000dsbisbmcr p15, 0, r0, c12, c0, 0dsbisb
#endif/* 设置各个模式下的栈指针 *//* 进入 IRQ 模式 */mrs r0, cpsrbic r0, r0, #0x1f             /* 将 r0 寄存器中的低 5 位清零,也就是 cpsr 的 M0~M4 */    orr r0, r0, #0x12             /* r0 或上 0x12, 表示使用 IRQ 模式 */    msr cpsr, r0                  /* 将 r0 的数据写入到 cpsr_c 中 */    ldr sp, =0x80600000           /* 设置 IRQ 模式下的栈首地址为 0X80600000, 大小为 2MB *//* 进入 SYS 模式 */mrs r0, cpsrbic r0, r0, #0x1f             /* 将 r0 寄存器中的低 5 位清零 */    orr r0, r0, #0x1f             /* r0 或上 0x1f, 表示使用 SYS 模式 */    msr cpsr, r0                  /* 将 r0 的数据写入到 cpsr_c 中 */    ldr sp, =0x80400000           /* 设置 SYS 模式下的栈首地址为 0X80400000, 大小为 2MB *//* 进入 SVC 模式 */mrs r0, cpsrbic r0, r0, #0x1f             /* 将 r0 寄存器中的低 5 位清零 */    orr r0, r0, #0x13             /* r0 或上 0x13, 表示使用 SVC 模式 */    msr cpsr, r0                  /* 将 r0 的数据写入到 cpsr_c 中 */    ldr sp, =0X80200000           /* 设置 SVC 模式下的栈首地址为 0X80200000, 大小为 2MB */cpsie i                       /* 打开全局中断 */#if 0/* 使能 IRQ 中断 */mrs r0, cpsr                  /* 读取 cpsr 寄存器值到 r0 中 */    bic r0, r0, #0x80            /* 将 r0 寄存器中 bit7 清零,也就是 CPSR 中的 I 位清零,表示允许 IRQ 中断 */    msr cpsr, r0                 /* 将 r0 重新写入到 cpsr 中 */    
#endifb main                        /* 跳转到 main 函数 *//* 未定义中断 */
Undefined_Handler:ldr r0, =Undefined_Handlerbx r0/* SVC 中断 */
SVC_Handler:ldr r0, =SVC_Handlerbx r0/* 预取终止中断 */
PrefAbort_Handler:ldr r0, =PrefAbort_Handler    bx r0/* 数据终止中断 */
DataAbort_Handler:ldr r0, =DataAbort_Handlerbx r0/* 未使用的中断 */
NotUsed_Handler:ldr r0, =NotUsed_Handlerbx r0/* IRQ 中断!重点!!!!! */
IRQ_Handler:push {lr}                    /* 保存 lr 地址 */    push {r0-r3, r12}           /* 保存 r0-r3,r12 寄存器 */mrs r0, spsr                /* 读取 spsr 寄存器 */    push {r0}                   /* 保存 spsr 寄存器 */mrc p15, 4, r1, c15, c0, 0 /* 从 CP15 的 C0 寄存器内的值到 R1 寄存器中 */                            add r1, r1, #0X2000         /* GIC 基地址加 0X2000,也就是 GIC 的 CPU 接口端基地址 */    ldr r0, [r1, #0XC]          /* GIC 的 CPU 接口端基地址加 0X0C 就是 GICC_IAR 寄存器 */    push {r0, r1}               /* 保存 r0, r1 */cps #0x13                   /* 进入 SVC 模式,允许其他中断再次进去 */push {lr}                   /* 保存 SVC 模式的 lr 寄存器 */    ldr r2, =system_irqhandler  /* 加载 C 语言中断处理函数到 r2 寄存器中 */    blx r2                      /* 运行 C 语言中断处理函数,带有一个参数,保存在 R0 寄存器中 */pop {lr}                    /* 执行完 C 语言中断服务函数,lr 出栈 */    cps #0x12                   /* 进入 IRQ 模式 */    pop {r0, r1}                str r0, [r1, #0X10]         /* 中断执行完成,写 EOIR */pop {r0}                    msr spsr_cxsf, r0           /* 恢复 spsr */pop {r0-r3, r12}            /* r0-r3, r12 出栈 */    pop {lr}                    /* lr 出栈 */    subs pc, lr, #4             /* 将 lr-4 赋给 pc *//* FIQ 中断 */
FIQ_Handler:ldr r0, =FIQ_Handler    bx r0

2. 代码功能详解

2.1 _start:程序入口和中断向量表

_start 是程序的入口点,它首先创建中断向量表。ARM 处理器在启动或发生异常时会根据向量表跳转到对应的处理程序。向量表包含 8 个条目,每个条目对应一种异常或中断:

  • 复位中断(Reset):系统上电或复位后执行。
  • 未定义中断(Undefined):执行了未定义的指令。
  • SVC 中断(Supervisor Call):软件触发系统调用。
  • 预取终止中断(Prefetch Abort):指令预取失败。
  • 数据终止中断(Data Abort):数据访问失败。
  • 未使用中断(Not Used):保留,未定义。
  • IRQ 中断(Interrupt Request):外部设备请求的中断。
  • FIQ 中断(Fast Interrupt Request):快速中断,通常用于高优先级任务。

代码使用 ldr pc, =handler 将每个处理程序的地址加载到程序计数器 pc,实现跳转。例如:

ldr pc, =Reset_Handler    /* 复位中断 */

这表示当发生复位时,处理器会跳转到 Reset_Handler 执行。


2.2 Reset_Handler:复位处理程序

Reset_Handler 是系统启动后的第一个执行函数,负责初始化硬件状态。以下是其主要步骤:

(1) 关闭全局中断
cpsid i    /* 关闭全局中断 */
  • 使用 cpsid i 关闭所有 IRQ 中断,确保初始化过程中不受干扰。
(2) 关闭缓存和 MMU
mrc p15, 0, r0, c1, c0, 0    /* 读取 CP15 的 C1 寄存器到 R0 */
bic r0, r0, #(0x1 << 12)     /* 关闭 I Cache */
bic r0, r0, #(0x1 << 2)      /* 关闭 D Cache */
bic r0, r0, #0x2             /* 关闭对齐 */
bic r0, r0, #(0x1 << 11)     /* 关闭分支预测 */
bic r0, r0, #0x1             /* 关闭 MMU */
mcr p15, 0, r0, c1, c0, 0    /* 将修改写入 CP15 C1 */
  • CP15 协处理器:CP15 负责系统配置,这里读取其控制寄存器 C1。
  • 位操作:使用 bic 清除特定位,分别关闭指令缓存(I Cache)、数据缓存(D Cache)、内存管理单元(MMU)等功能。这是“读-改-写”模式,确保初始状态干净。
(3) 设置中断向量表偏移(可选)
#if 0
ldr r0, =0X87800000
dsb
isb
mcr p15, 0, r0, c12, c0, 0
dsb
isb
#endif
  • 这部分被禁用(#if 0),用于将中断向量表基址设置为 0x87800000,常见于需要调整向量表位置的场景(如从 Flash 移动到 SRAM)。dsbisb 确保操作同步。
(4) 设置不同模式的栈指针

ARM 处理器有多种工作模式(如 IRQ、SVC、SYS),每个模式需要独立的栈。代码为 IRQ、SYS 和 SVC 模式设置栈指针:

/* 进入 IRQ 模式 */
mrs r0, cpsr
bic r0, r0, #0x1f    /* 清零模式位 */
orr r0, r0, #0x12    /* 设置为 IRQ 模式 */
msr cpsr, r0
ldr sp, =0x80600000  /* 设置栈顶为 0x80600000,大小 2MB *//* 进入 SYS 模式 */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x1f    /* 设置为 SYS 模式 */
msr cpsr, r0
ldr sp, =0x80400000  /* 设置栈顶为 0x80400000 *//* 进入 SVC 模式 */
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0x13    /* 设置为 SVC 模式 */
msr cpsr, r0
ldr sp, =0x80200000  /* 设置栈顶为 0x80200000 */
  • 模式切换:使用 mrs 读取当前程序状态寄存器(CPSR),bic 清零模式位(低 5 位),orr 设置新模式,msr 写入回 CPSR。
  • 栈设置:栈指针 sp 指向内存区域(这里是 DDR 范围 0x80000000~0x9FFFFFFF),栈向下增长,必须 4 字节对齐。
(5) 打开全局中断并跳转
cpsie i    /* 打开全局中断 */
b main     /* 跳转到 main 函数 */
  • cpsie i 重新启用中断。
  • b main 跳转到 C 语言的 main 函数,标志着初始化完成。

2.3 其他中断处理程序

除了 Reset_Handler,代码还定义了其他中断处理程序,但大多数只是简单地进入死循环:

Undefined_Handler:ldr r0, =Undefined_Handlerbx r0SVC_Handler:ldr r0, =SVC_Handlerbx r0PrefAbort_Handler:ldr r0, =PrefAbort_Handler    bx r0DataAbort_Handler:ldr r0, =DataAbort_Handlerbx r0NotUsed_Handler:ldr r0, =NotUsed_Handlerbx r0FIQ_Handler:ldr r0, =FIQ_Handler    bx r0
  • 这些处理程序使用 ldr 加载自身地址到 r0,然后 bx r0 跳转回去,形成死循环。这是一种简单处理方式,实际应用中可能需要更复杂的逻辑。

2.4 IRQ_Handler:IRQ 中断处理

IRQ_Handler 是代码中的重点,负责处理外部设备的中断。以下是详细解析:

(1) 保存上下文
push {lr}                    /* 保存返回地址 */
push {r0-r3, r12}           /* 保存通用寄存器 */
  • 保存中断前的 lr(返回地址)和 r0-r3, r12(可能被使用的寄存器)。
(2) 保存状态
mrs r0, spsr                /* 读取 spsr */
push {r0}                   /* 保存 spsr */
  • spsr 存储中断前的处理器状态,需保存以便返回。
(3) 获取 GIC 中断号
mrc p15, 4, r1, c15, c0, 0 /* 从 CP15 读取 GIC 基址 */
add r1, r1, #0X2000         /* 计算 GIC CPU 接口地址 */
ldr r0, [r1, #0XC]          /* 从 GICC_IAR 读取中断号 */
push {r0, r1}               /* 保存中断号和基址 */
  • 通过 CP15 获取 GIC 基址,偏移 0x2000 得到 CPU 接口地址,从 GICC_IAR 寄存器读取当前中断号。
(4) 模式切换和调用 C 函数
cps #0x13                   /* 切换到 SVC 模式 */
push {lr}                   /* 保存 SVC 模式下的 lr */
ldr r2, =system_irqhandler  /* 加载 C 函数地址 */
blx r2                      /* 调用 C 中断处理函数 */
  • 切换到 SVC 模式,调用 C 语言的 system_irqhandler 函数,r0 作为参数传递中断号。
(5) 清理和返回
pop {lr}                    /* 恢复 SVC 模式 lr */
cps #0x12                   /* 切换回 IRQ 模式 */
pop {r0, r1}                
str r0, [r1, #0X10]         /* 写 EOIR 标记中断结束 */
pop {r0}                    
msr spsr_cxsf, r0           /* 恢复 spsr */
pop {r0-r3, r12}            /* 恢复寄存器 */
pop {lr}                    /* 恢复 lr */
subs pc, lr, #4             /* 返回 */
  • 恢复所有状态,通知 GIC 中断处理完成(写 GICC_EOIR),返回到中断前的位置。

3. 总结与应用

3.1 关键点回顾

  • 中断向量表:定义了 8 种异常的入口,启动时跳转到 Reset_Handler
  • 复位初始化:关闭中断、缓存和 MMU,设置栈指针,跳转到 main
  • IRQ 处理:通过 GIC 获取中断号,调用 C 函数处理,恢复现场返回。

3.2 应用场景

这段代码适用于嵌入式系统(如基于 Cortex-A7 的开发板),用于启动操作系统或裸机程序。理解这些内容有助于调试硬件初始化问题、优化中断响应以及开发低级驱动。

3.3 扩展阅读

  • 参考文档:ARM Cortex-A(armV7)编程手册、Cortex-A7 技术参考手册。
  • 相关知识:CP15 协处理器、GIC 中断控制器、ARM 模式切换。

4. 附录:常见问题解答

  • 为什么关闭缓存和 MMU? 为了确保启动时硬件状态干净,避免缓存或虚拟内存的残留影响。
  • 栈指针为何向下增长? ARM 栈通常向下增长,方便压栈和出栈操作。
  • GIC 是什么? 通用中断控制器,管理多个设备的中断请求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/75881.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4.7学习总结 可变参数+集合工具类Collections+不可变集合

可变参数&#xff1a; 示例&#xff1a; public class test {public static void main(String[] args) {int sumgetSum(1,2,3,4,5,6,7,8,9,10);System.out.println(sum);}public static int getSum(int...arr){int sum0;for(int i:arr){sumi;}return sum;} } 细节&#xff1a…

2023年蓝桥杯第十四届CC++大学B组真题及代码

目录 1A&#xff1a;日期统计 解析代码_暴力_正解 2B&#xff1a;01串的熵 解析代码_暴力_正解 3C&#xff1a;冶炼金属 解析代码_暴力_正解 4D&#xff1a;飞机降落 解析代码_暴力dfs_正解 5E&#xff1a;接龙数列 解析代码_dp_正解 6F&#xff1a;岛屿个数 解析代…

rom定制系列------小米10pro机型定制解锁固件 原生安卓15批量线刷固件 操作解析与界面预览

注意;固件用于自己机型忘记密码或者手机号注销等出现设备锁 过保修期 售后无视的机型&#xff0c;勿用于非法途径 目前有粉丝联系&#xff0c;自己的机型由于手机号注销导致手机更新系统后出现设备锁界面。另外也没有解锁bl。目前无法使用手机。经过询问是小米10pro机型。根据…

信息学奥赛一本通 1861:【10NOIP提高组】关押罪犯 | 洛谷 P1525 [NOIP 2010 提高组] 关押罪犯

【题目链接】 ybt 1861&#xff1a;【10NOIP提高组】关押罪犯 洛谷 P1525 [NOIP 2010 提高组] 关押罪犯 【题目考点】 1. 图论&#xff1a;二分图 2. 二分答案 3. 种类并查集 【解题思路】 解法1&#xff1a;种类并查集 一个囚犯是一个顶点&#xff0c;一个囚犯对可以看…

我的NISP二级之路-01

目录 一.SSE-CMM系统安全工程-能力成熟度模型(Systems Security Engineering - Capability Maturity Model) 二.ISMS 即信息安全管理体系(Information Security Management System),是一种基于风险管理的、系统化的管理体系 三.Kerberos协议 1. 用户登录与 AS 请求 2…

WEB安全--内网渗透--利用Net-NTLMv2 Hash

一、前言 在前两篇文章中分析了NTLM协议中Net-NTLMv2 Hash的生成、如何捕获Net-NTLMv2 Hash&#xff0c;现在就来探讨一下在内网环境中&#xff0c;如何利用Net-NTLMv2 Hash进行渗透。 二、Net-NTLM Hash的破解 工具&#xff1a;hashcat 原理&#xff1a;利用其内部的字典对…

如何正确使用 `apiStore` 进行 API 管理

在现代前端开发中&#xff0c;API 管理是一个非常重要的环节。apiStore 是一个基于 Pinia 的状态管理工具&#xff0c;它可以帮助我们更高效地管理和调用 API。本文将详细介绍如何正确使用 apiStore&#xff0c;包括如何创建 API 配置文件、在组件中使用 apiStore 以及如何配置…

瓦片数据合并方法

影像数据 假如有两份影像数据 1.全球底层影像0-5级别如下&#xff1a; 2.局部高清影像数据级别9-14如下&#xff1a; 合并方法 将9-14文件夹复制到全球底层0-5的目录下 如下&#xff1a; 然后合并xml文件 使得Tileset设置到最高级&#xff08;包含所有级别&#xff09;&…

C++中的类和对象(上)

1 类的定义 1.1 类定义的格式 1 class为定义类的关键字&#xff0c;Stack为类的名字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后面分号不能省 略》。类体中内容称为类的成员&#xff1a;类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数…

【Tauri2】013——前端Window Event与创建Window

前言 【Tauri2】012——on_window_event函数-CSDN博客https://blog.csdn.net/qq_63401240/article/details/146909801?spm1001.2014.3001.5501 前面介绍了on_window_event&#xff0c;这个在Builder中的方法&#xff0c;里面有许多事件&#xff0c;比如Moved&#xff0c;Res…

【问题处理】webpack4升webpack5,报错Uncaught ReferrnceError: process is not defined

问题 正在做webpack4升webpack5&#xff0c;项目构建项目成功后在浏览器打开时报错 Uncaught ReferrnceError: process is not defined。 原因 webpack 5 不再自动 polyfill Node.js 的核心模块。 如果你在浏览器运行的代码中使用它&#xff0c;需要从 NPM 中安装兼容模块…

软件工程师减肥计划

一、目标设定 在 3 个月内减轻体重 5-7kg&#xff0c;改善身体代谢水平和体脂率&#xff0c;增强身体活力和精神状态&#xff0c;以更好地适应工作强度。 二、饮食调整 &#xff08;一&#xff09;基本原则 控制热量摄入&#xff0c;保证每天摄入热量低于消耗热量 500-800 …

即时访问成为降低风险的关键

云计算和软件即服务 (SaaS) 解决方案的广泛采用从根本上重塑了企业的数字格局。 不同行业的组织越来越多地利用云固有的可扩展性和成本效益来推动创新和简化运营。 这种向基于云的环境的转变也带来了一系列新的复杂安全挑战&#xff0c;需要仔细考虑并制定强有力的缓解策略。…

[环境配置] 1. 开发环境搭建

开发环境搭建 本文档将详细介绍如何搭建深度学习开发环境&#xff0c;包括 Python 环境配置、IDE 选择与配置以及虚拟环境管理。 也会介绍一下最近比较流行的 uv 工具。它是一个用 Rust 编写的极其快速的 Python 包和项目管理工具。 uv 是一个非常强大的工具&#xff0c;它可…

rust 同时处理多个异步任务,并在一个任务完成退出

use std::thread; use tokio::{sync::mpsc,time::{sleep, Duration}, };async fn check_for_one() {// 该函数会每秒打印一次 "write"loop {println!("write");sleep(Duration::from_secs(1)).await;} }async fn start_print_task() -> Result<(), (…

“群芳争艳”:CoreData 4 种方法计算最大值的效率比较(上)

概览 在 CoreData 支持的 App 中&#xff0c;一种常见操作就是计算数据库表中指定字段的最大值&#xff08;或最小值&#xff09;。就是这样一种看起来“不足挂齿”的任务&#xff0c;可能稍不留神就会“马失前蹄”。 在实际的代码中&#xff0c;我们怎样才能既迅速又简洁的…

skynet网络包库(lua-netpack.c)的作用解析

目录 网络包库&#xff08;lua-netpack.c&#xff09;的作用解析1. 数据包的分片与重组2. 网络事件处理3. 内存管理4. 数据打包与解包 动态库&#xff08;.so&#xff09;在 Lua 中的使用1. 编译为动态库2. Lua 中加载与调用(1) 加载模块(2) 核心方法(3) 使用示例 3. 注意事项 …

计科数据库第二次上机操作--实验二 表的简单查询

一、建数据库和表 1&#xff0e;启动数据库服务软件 Navicat 2&#xff0e;在 Navicat 中建立数据库 test 3. 在test数据库上建立teacher表&#xff1a; 二、基本查询 2.1 从teacher表中分别检索出教师的所有信息 SELECT * FROM teacher WHERE 教工号2000; SELECT * FROM t…

WPF依赖注入

一、IOC 在 WPF 中的原理 控制反转&#xff08;IOC&#xff09;是一种设计原则&#xff0c;它将对象的创建和依赖关系的管理从对象本身转移到外部容器&#xff08;IOC 容器&#xff09;。在传统的编程方式中&#xff0c;一个对象如果需要使用另一个对象&#xff08;即存在依赖…

【大模型深度学习】如何估算大模型需要的显存

一、模型参数量 参数量的单位 参数量指的是模型中所有权重和偏置的数量总和。在大模型中&#xff0c;参数量的单位通常以“百万”&#xff08;M&#xff09;或“亿”&#xff08;B&#xff0c;也常说十亿&#xff09;来表示。 百万&#xff08;M&#xff09;&#xff1a;表示…