Linux ARMv8 异常向量表
http://blog.chinaunix.net/uid-69947851-id-5830546.html
本章接着《Linux内核启动》部分讲解,我们知道了在进入start_kernel之前,通过指令adr_l x8, vectors;msr vbar_el1, x8设置了异常向量表,那么异常向量表的结构是怎么样的呢?在armv8中,每个异常的 向量地址不再是4字节,而是0x80字节,可以放更多的代码在向量表里面,因此
点击(此处)折叠或打开
- 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 // Synchronous EL1h
- kernel_ventry 1, irq // IRQ EL1h
- kernel_ventry 1, fiq_invalid // FIQ EL1h
- kernel_ventry 1, error // Error EL1h
- kernel_ventry 0, sync // Synchronous 64-bit EL0
- kernel_ventry 0, irq // IRQ 64-bit EL0
- kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0
- kernel_ventry 0, error // Error 64-bit EL0
- #ifdef CONFIG_COMPAT
- kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
- kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
- kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
- kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
- #else
- 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
- #endif
- END(vectors)
从上图可以了解到一条kernel_ventry 为一个异常,但是kernel_ventry的展开需要对齐到0x80,不够的部分用nop填充。通过上图,我们可以知道armv8有4张向量表,每张向量表有4中异常:同步异常、irq异常、fiq异常、系统错误异常,而4张表分别对应:
1、发生中断时,异常等级不发生变化,并且不管怎么异常模式,sp只用SP_EL0
2、发生中断时,异常等级不发生变化,并且sp用对应异常私有的SP_ELx
3、发生中断时,异常等级发生变化,这种情况一般是用户态向内核态发生迁移,当前表示64位用户态向64位内核态发生迁移
4、发生中断时,异常等级发生变化,这种情况一般是用户态向内核态发生迁移,当前表示32位用户态向64位/32位内核发生迁移
下面我们来看看kernel_ventry的实现:
点击(此处)折叠或打开
- .macro kernel_ventry, el, label, regsize = 64
- .align 7
- sub sp, sp, #S_FRAME_SIZE // 将sp预留一个fram_size, 这个size 就是struct pt_regs的大小
- #ifdef CONFIG_VMAP_STACK
- ....这里省略掉检查栈溢出的代码
- #endif
- b el\()\el\()_\label // 跳转到对应级别的异常处理函数, kernel_entry 1, irq为el1_irq
- .endm
对于向量表vectors中的kernel_ventry 1, irq , 则 b el\()\el\()_\label跳转到el1_irq函数。 其中1表示的是从哪个异常模式产生的,比如是User->kernel就是0. , kernel->kernel就是1. 下面接着看el1_irq实现, el1_irq是内核运行期间产生了中断:
点击(此处)折叠或打开
- el1_irq:
- kernel_entry 1 // 跟进入C函数需要压栈的道理一样, 这里进入内核空间,需要保存寄存器到pt_regs,也就是前面kernel_ventry sp预留的空间当中。
- enable_da_f
- irq_handler // 中断处理函数
- #ifdef CONFIG_PREEMPT
- ldr w24, [tsk, #TSK_TI_PREEMPT] // get preempt count
- cbnz w24, 1f // preempt count != 0
- ldr x0, [tsk, #TSK_TI_FLAGS] // get flags
- tbz x0, #TIF_NEED_RESCHED, 1f // needs rescheduling?
- bl el1_preempt // 支持内核抢占,会在这里判断是否需要调度到新的进程。
- 1:
- #endif
- kernel_exit 1 // 这里是kernel_entry 1的逆向过程,弹出栈,就是还原寄存器
- ENDPROC(el1_irq)
- el1_preempt: // 内核抢占
- mov x24, lr // 保存lr寄存器
- 1: bl preempt_schedule_irq // irq en/disable is done inside
- ldr x0, [tsk, #TSK_TI_FLAGS] //获取新进程的标志TI_FLAGS
- tbnz x0, #TIF_NEED_RESCHED, 1b // 如果还有需要调度的进程,继续抢占
- ret x24
下面看看kernel_entry的实现:
点击(此处)折叠或打开
- .macro kernel_entry, el, regsize = 64
- /* 这里用stp指令将x0-x29保存到预留的栈中,保存顺序为下面结构体顺序
- struct pt_regs {
- union {
- struct user_pt_regs user_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];
- }
- */
- stp x0, x1, [sp, #16 * 0] ->pt_regs.regs[0] 和pt_regs.regs[1]
- 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]
- //如果el为0 表示从用户态产生的异常
- .if \el == 0
- clear_gp_regs // 清除 x0-x29寄存器
- mrs x21, sp_el0 // 将用户态的sp指针保存到x21寄存器
- ldr_this_cpu tsk, __entry_task, x20 // 从当前per_cpu读取当前的task_struct地址
- ldr x19, [tsk, #TSK_TI_FLAGS] // 获取task->flag标记
- disable_step_tsk x19, x20 // exceptions when scheduling.
- .else
- // 从内核状态产生的异常
- add x21, sp, #S_FRAME_SIZE // X21保存压入pt_regs数据之前的栈地址,也就是异常时,内核的栈地址
- get_thread_info tsk // 这里是从sp_el0从获取到当前task_struct结构,在启动篇看到,内核状态的时候,sp_el0用于保存内核的task_struct结构,用户态的时候, 这个sp_el0是用户态的sp
- /* 保存task's original addr_limit 然后设置USER_DS */
- ldr x20, [tsk, #TSK_TI_ADDR_LIMIT]
- str x20, [sp, #S_ORIG_ADDR_LIMIT]
- mov x20, #USER_DS
- str x20, [tsk, #TSK_TI_ADDR_LIMIT]
- .endif /* \el == 0 */
- // x22保存异常地址
- mrs x22, elr_el1
- // x23保存程序状态寄存器
- mrs x23, spsr_el1
- stp lr, x21, [sp, #S_LR] // 将lr和sp保存到pt_regs->x[30], pt_rets->sp
- // 如果发生在el1模式,则将x29和异常地址保存到pt_regs->stackframe
- .if \el == 0
- stp xzr, xzr, [sp, #S_STACKFRAME]
- .else
- stp x29, x22, [sp, #S_STACKFRAME]
- .endif
- add x29, sp, #S_STACKFRAME
- stp x22, x23, [sp, #S_PC] // 将异常和程序状态 保存到pt_regs->pstate和pt__regs->pc
- // 如果是el0->el1发了变迁, 那么将当前的task_struct给sp_el0保存
- .if \el == 0
- msr sp_el0, tsk
- .endif
- /*
- * Registers that may be useful after this macro is invoked:
- *
- * x21 - aborted SP
- * x22 - aborted PC
- * x23 - aborted PSTATE
- */
- .endm
irq_handler为中断实现函数,具体实现如下:
点击(此处)折叠或打开
- .macro irq_handler
- ldr_l x1, handle_arch_irq // 将handle_arch_irq的地址放入x1寄存器
- mov x0, sp
- irq_stack_entry // 中断入口, 这里主要是切换成中断栈
- blr x1 // 跳转到handle_arch_irq函数运行,这个函数是gic驱动加载的时候设置的,否则是invilid
- irq_stack_exit
- .endm
- //C语言设置回调函数
- int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
- {
- if (handle_arch_irq)
- return -EBUSY;
- handle_arch_irq = handle_irq;
- return 0;
- }
点击(此处)折叠或打开
- .macro irq_stack_entry
- mov x19, sp // 保存当前sp到x19
- /*
- * 判断当前栈是不是中断栈, 如果是任务栈,就从per_cpu中读取中断栈地址,并切换到中断栈
- */
- ldr x25, [tsk, TSK_STACK]
- eor x25, x25, x19
- and x25, x25, #~(THREAD_SIZE - 1)
- cbnz x25, 9998f
- ldr_this_cpu x25, irq_stack_ptr, x26 // 读取per_cpu的irq_stack_ptr
- mov x26, #IRQ_STACK_SIZE
- add x26, x25, x26
- /* 切换到中断栈 */
- mov sp, x26
- 9998:
- .endm
如果是用户态发生中断异常,则进入el0_irq, 实现如下:
点击(此处)折叠或打开
- el0_irq:
- kernel_entry 0 // 和el1_irq一样,只是这传入的是0表示 用户态发生异常
- enable_da_f
- ct_user_exit
- irq_handler // 中断回调
- b ret_to_user // 中断返回
- ENDPROC(el0_irq)
点击(此处)折叠或打开
- work_pending:
- mov x0, sp // 'regs'
- bl do_notify_resume // 用户抢占调度和处理信号
- ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for single-step
- b finish_ret_to_user
- ret_to_user:
- disable_daif
- ldr x1, [tsk, #TSK_TI_FLAGS]
- and x2, x1, #_TIF_WORK_MASK
- cbnz x2, work_pending // 判断是否有信号或者任务挂起
- finish_ret_to_user:
- enable_step_tsk x1, x2
- kernel_exit 0 // 恢复栈
- ENDPROC(ret_to_user)
do_notify_resume函数用于用户抢占和信号处理, 实现大概如下:
点击(此处)折叠或打开
- asmlinkage void do_notify_resume(struct pt_regs *regs,
- unsigned long thread_flags)
- {
- trace_hardirqs_off();
- do {
- /* Check valid user FS if needed */
- addr_limit_user_check();
- if (thread_flags & _TIF_NEED_RESCHED) {
- /* Unmask Debug and SError for the next task */
- local_daif_restore(DAIF_PROCCTX_NOIRQ);
- schedule(); // 重新调度新的进程
- } else {
- local_daif_restore(DAIF_PROCCTX);
- if (thread_flags & _TIF_UPROBE)
- uprobe_notify_resume(regs);
- if (thread_flags & _TIF_SIGPENDING)
- do_signal(regs); // 信号处理
- if (thread_flags & _TIF_NOTIFY_RESUME) {
- clear_thread_flag(TIF_NOTIFY_RESUME);
- tracehook_notify_resume(regs); // 工作队列
- rseq_handle_notify_resume(NULL, regs);
- }
- if (thread_flags & _TIF_FOREIGN_FPSTATE)
- fpsimd_restore_current_state();
- }
- local_daif_mask();
- thread_flags = READ_ONCE(current_thread_info()->flags);
- } while (thread_flags & _TIF_WORK_MASK);
- }
至于el1_sync同步异常(包含系统调用,数据异常,指令异常等)这里就不多做说明了, 原理是一样的,如
1、数据异常:el0/1_sync->el1_da->do_mem_abort->do_page_fault.
2、系统调用:el0_sync->el0_svc->el0_svc_handler->el0_svc_common(__NR_syscalls, sys_call_table)->invoke_syscall
相关文章:
Linux ARMv8 异常向量表
http://blog.chinaunix.net/uid-69947851-id-5830546.html 本章接着《Linux内核启动》部分讲解,我们知道了在进入start_kernel之前,通过指令adr_l x8, vectors;msr vbar_el1, x8设置了异常向量表,那么异常向量表的结构是怎么样…...

C++基类和派生类的内存分配,多态的实现
目录 基类和派生类的内存分配基类和派生类的成员归属多态的实现 基类和派生类的内存分配 类包括成员变量(data member)和成员函数(member function)。 成员变量分为静态数据(static data)和非静态数据&…...
C/C++基础
C 二进制 问题:二进制怎么表示整数、小数、正数、负数,如何存储?加减乘除怎么运算(见文章《计算机加减乘除本质》)? 变量 c定义一个变量的时候,需要事先定义变量大小和变量类型。 //有符号…...

MySQL基础练习题
数据表介绍 --1.学生表 Student(SId,Sname,Sage,Ssex) --SId 学生编号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别 --2.课程表 Course(CId,Cname,TId) --CId 课程编号,Cname 课程名称,TId 教师编号 --3.教师表 Teacher(TId,Tname) --TId 教师编号,Tname 教师姓名 --4.成绩…...
【C语言学习笔记 --- 动态内存管理】
C语言程序设计笔记---029 C语言之动态内存管理1、介绍动态内存管理2、动态内存函数的介绍2.1、malloc和free函数2.2、calloc函数2.3、realloc函数 3、动态内存管理过程中,一些常见的错误3.1、对NULL指针的解引用操作3.2、对动态内存开辟的空间的越界访问3.3、对非动…...

Nougat来了,能否成为pdf格式转换的新神器?
Nougat来了,能否成为pdf格式转换的新神器? 论文链接:https://arxiv.org/pdf/2308.13418v1.pdf 项目地址:https://github.com/facebookresearch/nougat What happened?🤨 科学知识主要存储在书籍和科学期…...

C++文件和流
到目前为止,我们已经使用了 iostream 标准库,它提供了 cin 和 cout 方法分别用于从标准输入读取流和向标准输出写入流。 本教程介绍如何从文件读取流和向文件写入流。这就需要用到 C 中另一个标准库 fstream,它定义了三个新的数据类型&#x…...
代码随想录算法训练营第23期day31|贪心算法理论基础、455.分发饼干、376. 摆动序列、53. 最大子序和
目录 一、贪心算法理论基础 二、(leetcode 455)分发饼干 三、(leetcode 376)摆动序列 四、(leetcode 53)最大子序和 一、贪心算法理论基础 1.什么是贪心 贪心的本质是选择每一阶段的局部最优…...
mdadm命令详解及实验过程
mdadm命令详解及实验过程 ⼀.概念 mdadm是multiple devices admin的简称,它是Linux下的⼀款标准的软件 RAID 管理⼯具,作者是Neil Brown ⼆.特点 mdadm能够诊断、监控和收集详细的阵列信息 mdadm是⼀个单独集成化的程序⽽不是⼀些分散程序的集合&#…...

推荐几个程序员必逛的个人技术博客网站
1、美团技术团队 地 址: 美团技术团队简 介:美团技术团队的博客,干货满满。推荐指数:⭐⭐⭐⭐⭐ 2、阮一峰的网络日志 地 址: 阮一峰的个人网站 - Ruan YiFengs Personal Website简 介:大神阮一峰,博客风格真正…...

Python桌面应用之XX学院水卡报表查询系统(Tkinter+cx_Oracle)
一、功能样式 Python桌面应用之XX学院水卡报表查询系统功能: 连接Oracle数据库,查询XX学院水卡操作总明细报表,汇总数据报表,个人明细报表,进行预览并且支持导出报表 1.总明细报表样式 2.汇总明细样式 3.个人明细…...

ubuntu 中使用Qt连接MMSQl,报错libqsqlodbc.so: undefined symbol: SQLAllocHandle
Qt4.8.7的源码编译出来的libqsqlodbc.so,在使用时报错libqsqlodbc.so: undefined symbol: SQLAllocHandle,需要在编译libqsqlodbc.so 的项目pro文件加上LIBS -L/usr/local/lib -lodbc。 这里的路径根据自己的实际情况填写。 编辑: 使用uni…...
笔试,猴子吃香蕉,多线程写法
package demo;import java.util.concurrent.CountDownLatch;/*** description: 猴子吃香蕉* author: wxm* create: 2023-10-23 14:01**/ public class Main {public static void main(String[] args) throws InterruptedException {Monkey[] m new Monkey[3];Resource r new …...

安装docker ,更换docker版本
docker dockerd & containerd Dockerd(Docker 守护进程)在其底层使用 Containerd 来管理容器。Containerd 是一个开源的容器运行时管理器,由 Docker 公司于2017年开发并开源,它负责实际的容器生命周期管理。 以下是 Docker 守…...

英语小作文写作模板及步骤(1)
...

编写hello驱动程序
hello的驱动编写 编写驱动程序的步骤 1.确定主设备号,也可以让内核分配 2.定义自己的 file_operations 结构体 3.实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构 体 4.把 file_operations 结构体告诉内核:regist…...
ZYNQ中断例程
GPIO 中断系统初始化流程: 第一步:初始化 cpu 的异常处理功能 第二步:初始化中断控制器 第三步:向 CPU 注册异常处理回调函数; 第四步:将中断控制器中的对应中断 ID 的中断与中断控制器相连接 第五步:设置 …...

常用linux命令 linux_cmd_sheet
查看文件大小 ls -al 显示每个文件的kb大小 查看系统日志 dmesg -T | tail 在 top 命令中,RES 和 VIRT(或者 total-vm)是用来表示进程内存使用的两个不同指标,它们之间有以下区别: RES(Resident Set Size…...

【proteus】8086 写一个汇编程序并调试
参考书籍:微机原理与接口技术——基于8086和Proteus仿真(第3版)p103-105,p119-122. 参考程序是p70,例4-1 在上一篇的基础上: 创建项目和汇编文件 写一个汇编程序并编译 双击8086的元件图: …...
大数据之LibrA数据库常见术语(四)
Failover 指当某个节点出现故障时,自动切换到备节点上的过程。反之,从备节点上切换回来的过程称为Failback。 Freeze 在事务ID耗尽时由AutoVacuum Worker进程自动执行的操作。FusionInsight LibrA会把事务ID记在行头,在一个事务取得一行时&…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...

快速排序算法改进:随机快排-荷兰国旗划分详解
随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...