从源码分析arm64中断与GIC-400

写在前面,本文将一步步从手册到源码,深入解析 ARMv8 架构下异常发生、被检测、再到被处理的全过程。相比于冗长的文字描述,代码往往更具说服力。因此,本文将结合 手册理论 + 代码实践,力求通过 可复现的代码 直观展示整个异常处理流程。

文中所有代码均由本人编写并经过测试,如果发现 bug 或者有更优的实现方式,欢迎指正与交流~

参考资料:Linux4.10 《arm64体系结构编程与实践》

本文以树莓派4b(armv8)来实现,4b支持两种

  • 传统的中断控制器
  • gic-400 但是使用的qemu和实际的板子都是默认支持gic-400的,所以主要是借助gic-400实现中断的功能

异常处理

相关寄存器

  • PSTATE 就是cpu状态
    • DAIF 调试异常 SError(系统异常) IRQ(中断) FIQ(快速中断)
  • esr_elx 用来保存返回地址
  • spsr_elx 用来保存对应级别的PSTATE
  • elr_elx 用来保存异常的原因

处理异常时自动发生的事

CPU捕获到异常时

  1. 将PSTATE保存到对应的SPSR_ELx中
  2. 返回地址保存到ELR_ELx中
  3. PSTATE DAIF关闭
  4. 如果是同步异常把原因写入ESR_ELx,如果是中断,原因保存在GIC-400的寄存器中
  5. 切换SP到对应的SP_ELx中
  6. 跳转到中断向量表里
  7. 执行对应的处理函数

CPU处理完异常执行eret,会

  1. ELR中恢复PC
  2. SPSR中恢复PSTATE (DAIF也会变) alt 从源码分析arm64中断与GIC

中断向量表

alt

可见通过保存到对应异常的vbar中,CPU就可以在对应级别是发生异常进入中断向量表中 由于内核目前一直在EL1阶段,所以在el1的初始化函数el1_entry

el1_entry:
    // 加入向量表
    adr x0, vectors
    msr vbar_el1, x0

其中vector的实现是参考linux的实现

    .macro kernel_ventry, el, label, regsize=64
    .align 7
    sub sp, sp, #S_FRAME_SIZE
    b el\()\el\()_\label
    .endm

    .pushsection ".entry.text", "ax"
    .align 11
ENTRY(vectors)
	kernel_ventry	1, sync_invalid			// Synchronous EL1t
	kernel_ventry	1, irq_invalid			// IRQ EL1t
	kernel_ventry	1, fiq_invalid			// FIQ EL1t
	kernel_ventry	1, error_invalid		// Error EL1t

	kernel_ventry	1, sync_invalid				// Synchronous EL1h
	kernel_ventry	1, irq                      // IRQ EL1h
	kernel_ventry	1, fiq_invalid			// FIQ EL1h
	kernel_ventry	1, error_invalid			// Error EL1h

	kernel_ventry	0, sync_invalid				// Synchronous 64-bit EL0
	kernel_ventry	0, irq_invalid				// IRQ 64-bit EL0
	kernel_ventry	0, fiq_invalid			// FIQ 64-bit EL0
	kernel_ventry	0, error_invalid			// Error 64-bit EL0

	kernel_ventry	0, sync_invalid, 32		// Synchronous 32-bit EL0
	kernel_ventry	0, irq_invalid, 32		// IRQ 32-bit EL0
	kernel_ventry	0, fiq_invalid, 32		// FIQ 32-bit EL0
	kernel_ventry	0, error_invalid, 32		// Error 32-bit EL0
END(vectors)

kernel_ventry 值得说一下 el\()\el\()_\label\()表示一个符号的结尾,el 然后是结尾\() ,接着\el 这个就是传入宏第一个的参数,然后是结尾\(),然后是_,在跟着\label也就是第二个参数

vectors 就是个位置,依次放着各个异常的入口,只要依次实现这些入口函数,CPU发生异常的时候就会进入对应的处理函 数。

el0_sync_invalid为例,现在先忽略kernel_entry,这个是用来现场保护的,最后会到bad_mode中进行异常的处理

/*
 * Invalid mode handlers
 */
	.macro	inv_entry, el, reason, regsize = 64
	bl kernel_entry
	mov	x0, sp
	mov	x1, #\reason
	mrs	x2, esr_el1
	b	bad_mode
	.endm
	
el0_sync_invalid:
	inv_entry 0, BAD_SYNC
ENDPROC(el0_sync_invalid)

中断现场保护和恢复

这时候就不得不说中断发生时的现场保护了 与异常不同最后需要恢复现场

el1_irq:
    bl kernel_entry
    bl irq_handle
    bl kernel_exit
ENDPROC(el1_irq)

一个线框的定义是

struct pt_regs {
	struct {
		u64 regs[31];
		u64 sp;
		u64 pc;
		u64 pstate;
	};
	u64 orig_x0;
#ifdef __AARCH64EB__
	u32 unused2;
	s32 syscallno;
#else
	s32 syscallno;
	u32 unused2;
#endif

	u64 orig_addr_limit;
	u64 unused; // maintain 16 byte alignment
	u64 stackframe[2];
};

主要就是32个寄存器和SP、PC、PSTATE,所以只要按照这个顺序依次保存就可以

// 保护 pt_regs
kernel_entry:
	//开辟空间
    sub sp, sp, #S_FRAME_SIZE

    // 保存普通寄存器
    stp	x0, x1, [sp, #16 * 0]
	stp	x2, x3, [sp, #16 * 1]
	stp	x4, x5, [sp, #16 * 2]
	stp	x6, x7, [sp, #16 * 3]
	stp	x8, x9, [sp, #16 * 4]
	stp	x10, x11, [sp, #16 * 5]
	stp	x12, x13, [sp, #16 * 6]
	stp	x14, x15, [sp, #16 * 7]
	stp	x16, x17, [sp, #16 * 8]
	stp	x18, x19, [sp, #16 * 9]
	stp	x20, x21, [sp, #16 * 10]
	stp	x22, x23, [sp, #16 * 11]
	stp	x24, x25, [sp, #16 * 12]
	stp	x26, x27, [sp, #16 * 13]
	stp	x28, x29, [sp, #16 * 14]

	//保存最开始sp的位置到x21 
    add x21, sp, #S_FRAME_SIZE

    mrs x22, elr_el1
    mrs x23, spsr_el1

    stp lr, x21, [sp, #S_LR]
    stp x22, x23, [sp, #S_PC]
    ret

stp 会依次保存两个寄存器到 sp+第二个数的位置

接下来就是恢复时候

// 恢复 pt_regs
kernel_exit:
    // 先把pc 和 pstate恢复
    ldp x22, x23, [sp, #S_PC]
    msr	elr_el1, x22			// set up the return data
	msr	spsr_el1, x23

    ldp	x0, x1, [sp, #16 * 0]
	ldp	x2, x3, [sp, #16 * 1]
	ldp	x4, x5, [sp, #16 * 2]
	ldp	x6, x7, [sp, #16 * 3]
	ldp	x8, x9, [sp, #16 * 4]
	ldp	x10, x11, [sp, #16 * 5]
	ldp	x12, x13, [sp, #16 * 6]
	ldp	x14, x15, [sp, #16 * 7]
	ldp	x16, x17, [sp, #16 * 8]
	ldp	x18, x19, [sp, #16

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

1. 自我介绍:高通、Oppo(sp)、vivo(sp)、小米(ssp)、荣耀(26k*12+80k)、华子(报批中)、美团、韶音、经纬恒润、乐鑫、中兴、TP 2. 内容: 1.嵌入式学习的资料和路径 2.所有面试的题目和解答(持续更新)、对评论的快速解答 3.各种碎碎念 3.整理不易,buy me coffee☕️,为了回馈牛客和各个粉丝,文章都会先试读几天,热度过了再收录~

全部评论
哥 需不需要单独学arm架构呢
点赞 回复 分享
发布于 2025-02-25 19:54 黑龙江

相关推荐

哈啰大家 喵弟面试经验分享~bg:末9本投递:某杭州初创面试难度:地狱(因为是我第一次面试 很多都没准备)结果:秒挂11.20 初创公司面经1.看你简历中写到过实习经历,讲一下自己实习中都做了什么(说了一下实习的内容)2.看我的简历中写了MCP 你知道什么是MCP吗(不知 但其实这个实习就单纯做的数据标注和生产)3.那说说你的项目吧 派聪明项目中的东西4.ollma docker es等 都说不太认识5.说了一下jwt 组成是什么 作用6.开始redis部分 先问了redis相关的基础知识 项目中有没有redis相关的内容 (回答了zset)7.讲讲zset吧 什么底层原理 你又在项目中怎么实现的 (说了排行榜机制)8.说一下你的排行榜怎么保证加分的机制呢9.redis持久化有过了解吗 (说的aof和rdb)10.redis分布式了解过吗 (说的只了解分布式锁)11.那分布式锁的实现方式是怎么做的 为什么redis可以实现分布式锁(根本不知道)12.消息队列了解吗 rocketmq了解吗 (暂时还没看 不太了解)13.说的redis消息队列 两种模式 redis消息队列会出现什么问题 (说的会出现线程安全)14.那怎么解决这个线程安全问题 (回答的用zset来解决)15.说说mysql吧 你了解哪些mysql存储引擎? (说的innodb myisam)那innodb和其他俩的区别是什么16.innodb的锁颗粒度能分到多少呢17.事务的隔离级别 (读未 读已 可重复 串行化) 他们的优缺点18.场景 abc联合索引 ac ab都是怎么样的 (回答的都可以命中索引 他说我说的不对)19.说到了spring 讲讲bean吧 问到了bean的作用域(回答的很差) 存在哪些问题 (整个面试流程中但凡能继续深问的问题都问了这个 我不明白)20.说到了spring mvc 讲一下mvc的核心组件21.反问总结:当时觉得这个小公司要求我会的好多 但现在看来 真的挺基础 这次的面试之后 我搭配着AI 给我的实践经历总结了一下(因为这个字节实习实际上就一干脏活的 项目结束后也没给实习证明 给的是实践证明 当时报名这个项目的时候说给实习证明 被骗了 服了 但是为了找第一段寒假实习 我还是得包装一下讲一下的 现在能讲出很多)然后每天让AI按着我的简历面试我 八股我就逐渐熟悉了
文化小流氓:你的来时路会让你越来越强
查看18道真题和解析
点赞 评论 收藏
分享
评论
点赞
7
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务