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字节,可以放更多的代码在向量表里面,因此

点击(此处)折叠或打开

  1. ENTRY(vectors)
  2.     kernel_ventry 1, sync_invalid // Synchronous EL1t
  3.     kernel_ventry 1, irq_invalid // IRQ EL1t
  4.     kernel_ventry 1, fiq_invalid // FIQ EL1t
  5.     kernel_ventry 1, error_invalid // Error EL1t
  6.     kernel_ventry 1, sync // Synchronous EL1h
  7.     kernel_ventry 1, irq // IRQ EL1h
  8.     kernel_ventry 1, fiq_invalid // FIQ EL1h
  9.     kernel_ventry 1, error // Error EL1h
  10.     kernel_ventry 0, sync // Synchronous 64-bit EL0
  11.     kernel_ventry 0, irq // IRQ 64-bit EL0
  12.     kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0
  13.     kernel_ventry 0, error // Error 64-bit EL0
  14. #ifdef CONFIG_COMPAT
  15.     kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
  16.     kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0
  17.     kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0
  18.     kernel_ventry 0, error_compat, 32 // Error 32-bit EL0
  19. #else
  20.     kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0
  21.     kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0
  22.     kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0
  23.     kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0
  24. #endif
  25. 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的实现:

点击(此处)折叠或打开

  1. .macro kernel_ventry, el, label, regsize = 64
  2.     .align 7
  3.     sub sp, sp, #S_FRAME_SIZE // 将sp预留一个fram_size, 这个size 就是struct pt_regs的大小
  4. #ifdef CONFIG_VMAP_STACK
  5.     ....这里省略掉检查栈溢出的代码
  6. #endif
  7.     b el\()\el\()_\label    // 跳转到对应级别的异常处理函数, kernel_entry 1, irq为el1_irq
  8. .endm

对于向量表vectors中的kernel_ventry 1, irq ,  则 b el\()\el\()_\label跳转到el1_irq函数。 其中1表示的是从哪个异常模式产生的,比如是User->kernel就是0. , kernel->kernel就是1. 下面接着看el1_irq实现, el1_irq是内核运行期间产生了中断:

点击(此处)折叠或打开

  1. el1_irq:
  2.     kernel_entry 1    // 跟进入C函数需要压栈的道理一样, 这里进入内核空间,需要保存寄存器到pt_regs,也就是前面kernel_ventry  sp预留的空间当中。
  3.     enable_da_f
  4.     irq_handler   // 中断处理函数
  5. #ifdef CONFIG_PREEMPT
  6.     ldr w24, [tsk, #TSK_TI_PREEMPT] // get preempt count
  7.     cbnz w24, 1f // preempt count != 0
  8.     ldr x0, [tsk, #TSK_TI_FLAGS] // get flags
  9.     tbz x0, #TIF_NEED_RESCHED, 1f // needs rescheduling?
  10.     bl el1_preempt     // 支持内核抢占,会在这里判断是否需要调度到新的进程。
  11. 1:
  12. #endif
  13.     kernel_exit 1  // 这里是kernel_entry 1的逆向过程,弹出栈,就是还原寄存器
  14. ENDPROC(el1_irq)
  15. el1_preempt:  // 内核抢占
  16.     mov x24, lr  // 保存lr寄存器
  17. 1:  bl  preempt_schedule_irq        // irq en/disable is done inside
  18.     ldr x0, [tsk, #TSK_TI_FLAGS]    //获取新进程的标志TI_FLAGS
  19.     tbnz    x0, #TIF_NEED_RESCHED, 1b   // 如果还有需要调度的进程,继续抢占
  20.     ret x24

下面看看kernel_entry的实现:

点击(此处)折叠或打开

  1. .macro kernel_entry, el, regsize = 64
  2.     /* 这里用stp指令将x0-x29保存到预留的栈中,保存顺序为下面结构体顺序
  3.     struct pt_regs {
  4.         union {
  5.              struct user_pt_regs user_regs;
  6.              struct {
  7.                  u64 regs[31];
  8.                  u64 sp;
  9.                  u64 pc;
  10.                  u64 pstate;
  11.              };
  12.         };
  13.      u64 orig_x0;
  14.     #ifdef __AARCH64EB__
  15.      u32 unused2;
  16.      s32 syscallno;
  17.     #else
  18.      s32 syscallno;
  19.      u32 unused2;
  20.     #endif
  21.      u64 orig_addr_limit;
  22.     u64 unused; // maintain 16 byte alignment
  23.     u64 stackframe[2];
  24.     }
  25.    */
  26.     stp x0, x1, [sp, #16 * 0]  ->pt_regs.regs[0] 和pt_regs.regs[1]
  27.     stp x2, x3, [sp, #16 * 1]  // 以此类推
  28.     stp x4, x5, [sp, #16 * 2]
  29.     stp x6, x7, [sp, #16 * 3]
  30.     stp x8, x9, [sp, #16 * 4]
  31.     stp x10, x11, [sp, #16 * 5]
  32.     stp x12, x13, [sp, #16 * 6]
  33.     stp x14, x15, [sp, #16 * 7]
  34.     stp x16, x17, [sp, #16 * 8]
  35.     stp x18, x19, [sp, #16 * 9]
  36.     stp x20, x21, [sp, #16 * 10]
  37.     stp x22, x23, [sp, #16 * 11]
  38.     stp x24, x25, [sp, #16 * 12]
  39.     stp x26, x27, [sp, #16 * 13]
  40.     stp x28, x29, [sp, #16 * 14]
  41.     //如果el为0 表示从用户态产生的异常
  42.     .if \el == 0
  43.     clear_gp_regs   // 清除 x0-x29寄存器
  44.     mrs x21, sp_el0 // 将用户态的sp指针保存到x21寄存器
  45.     ldr_this_cpu tsk, __entry_task, x20 // 从当前per_cpu读取当前的task_struct地址
  46.     ldr x19, [tsk, #TSK_TI_FLAGS] // 获取task->flag标记
  47.     disable_step_tsk x19, x20 // exceptions when scheduling.
  48.     .else
  49.     // 从内核状态产生的异常
  50.     add x21, sp, #S_FRAME_SIZE  // X21保存压入pt_regs数据之前的栈地址,也就是异常时,内核的栈地址
  51.     get_thread_info tsk   // 这里是从sp_el0从获取到当前task_struct结构,在启动篇看到,内核状态的时候,sp_el0用于保存内核的task_struct结构,用户态的时候, 这个sp_el0是用户态的sp
  52.     /* 保存task's original addr_limit 然后设置USER_DS */
  53.     ldr x20, [tsk, #TSK_TI_ADDR_LIMIT]
  54.     str x20, [sp, #S_ORIG_ADDR_LIMIT]
  55.     mov x20, #USER_DS
  56.     str x20, [tsk, #TSK_TI_ADDR_LIMIT]
  57.     .endif /* \el == 0 */
  58.     // x22保存异常地址
  59.     mrs x22, elr_el1
  60.     // x23保存程序状态寄存器
  61.     mrs x23, spsr_el1
  62.     stp lr, x21, [sp, #S_LR] // 将lr和sp保存到pt_regs->x[30], pt_rets->sp
  63.     // 如果发生在el1模式,则将x29和异常地址保存到pt_regs->stackframe
  64.     .if \el == 0
  65.     stp xzr, xzr, [sp, #S_STACKFRAME]
  66.     .else
  67.     stp x29, x22, [sp, #S_STACKFRAME]
  68.     .endif
  69.     add x29, sp, #S_STACKFRAME
  70.     stp x22, x23, [sp, #S_PC] // 将异常和程序状态 保存到pt_regs->pstate和pt__regs->pc
  71.     // 如果是el0->el1发了变迁, 那么将当前的task_struct给sp_el0保存
  72.     .if \el == 0
  73.     msr sp_el0, tsk
  74.     .endif
  75.     /*
  76.      * Registers that may be useful after this macro is invoked:
  77.      *
  78.      * x21 - aborted SP
  79.      * x22 - aborted PC
  80.      * x23 - aborted PSTATE
  81.     */
  82. .endm

irq_handler为中断实现函数,具体实现如下:

点击(此处)折叠或打开

  1. .macro irq_handler
  2.     ldr_l x1, handle_arch_irq   // 将handle_arch_irq的地址放入x1寄存器
  3.     mov x0, sp
  4.     irq_stack_entry  // 中断入口, 这里主要是切换成中断栈
  5.     blr x1          // 跳转到handle_arch_irq函数运行,这个函数是gic驱动加载的时候设置的,否则是invilid
  6.     irq_stack_exit
  7. .endm
  8. //C语言设置回调函数
  9. int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
  10. {
  11.     if (handle_arch_irq)
  12.         return -EBUSY;
  13.     handle_arch_irq = handle_irq;
  14.     return 0;
  15. }
     

点击(此处)折叠或打开

  1. .macro irq_stack_entry
  2.     mov x19, sp // 保存当前sp到x19
  3.     /*
  4.      * 判断当前栈是不是中断栈, 如果是任务栈,就从per_cpu中读取中断栈地址,并切换到中断栈
  5.      */
  6.     ldr x25, [tsk, TSK_STACK]
  7.     eor x25, x25, x19
  8.     and x25, x25, #~(THREAD_SIZE - 1)
  9.     cbnz x25, 9998f
  10.     ldr_this_cpu x25, irq_stack_ptr, x26  // 读取per_cpu的irq_stack_ptr
  11.     mov x26, #IRQ_STACK_SIZE
  12.     add x26, x25, x26
  13.     /* 切换到中断栈 */
  14.     mov sp, x26
  15. 9998:
  16. .endm

如果是用户态发生中断异常,则进入el0_irq, 实现如下:

点击(此处)折叠或打开

  1. el0_irq:
  2.     kernel_entry 0   // 和el1_irq一样,只是这传入的是0表示 用户态发生异常
  3.     enable_da_f
  4.     ct_user_exit 
  5.     irq_handler  // 中断回调
  6.     b ret_to_user  // 中断返回
  7. ENDPROC(el0_irq)

点击(此处)折叠或打开

  1. work_pending:
  2.     mov x0, sp // 'regs'
  3.     bl do_notify_resume     // 用户抢占调度和处理信号
  4.     ldr x1, [tsk, #TSK_TI_FLAGS] // re-check for single-step
  5.     b finish_ret_to_user
  6. ret_to_user:
  7.     disable_daif
  8.     ldr x1, [tsk, #TSK_TI_FLAGS]
  9.     and x2, x1, #_TIF_WORK_MASK
  10.     cbnz x2, work_pending   // 判断是否有信号或者任务挂起
  11. finish_ret_to_user:
  12.     enable_step_tsk x1, x2
  13.     kernel_exit 0           // 恢复栈
  14. ENDPROC(ret_to_user)

do_notify_resume函数用于用户抢占和信号处理, 实现大概如下:

点击(此处)折叠或打开

  1. asmlinkage void do_notify_resume(struct pt_regs *regs,
  2.                  unsigned long thread_flags)
  3. {
  4.     trace_hardirqs_off();
  5.     do {
  6.         /* Check valid user FS if needed */
  7.         addr_limit_user_check();
  8.         if (thread_flags & _TIF_NEED_RESCHED) {
  9.             /* Unmask Debug and SError for the next task */
  10.             local_daif_restore(DAIF_PROCCTX_NOIRQ);
  11.             schedule();   // 重新调度新的进程
  12.         } else {
  13.             local_daif_restore(DAIF_PROCCTX);
  14.             if (thread_flags & _TIF_UPROBE)
  15.                 uprobe_notify_resume(regs);
  16.             if (thread_flags & _TIF_SIGPENDING)
  17.                 do_signal(regs);   // 信号处理
  18.             if (thread_flags & _TIF_NOTIFY_RESUME) {
  19.                 clear_thread_flag(TIF_NOTIFY_RESUME);
  20.                 tracehook_notify_resume(regs);  // 工作队列
  21.                 rseq_handle_notify_resume(NULL, regs);
  22.             }
  23.             if (thread_flags & _TIF_FOREIGN_FPSTATE)
  24.                 fpsimd_restore_current_state();
  25.         }
  26.         local_daif_mask();
  27.         thread_flags = READ_ONCE(current_thread_info()->flags);
  28.     } while (thread_flags & _TIF_WORK_MASK);
  29. }

至于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

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

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

相关文章

Flutter视图原理之三棵树的建立过程

目录 三棵树的关系树的构建过程1.updateChild函数(element的复用)2.inflateWidget函数3.mount函数3.1 componentElement的实现3.2 RenderObjectElement的实现3.2.1 attachRenderObject函数 4.performRebuild函数 总结三棵树创建流程 三棵树的关系 Flutt…

简单的elementui倒计时按钮事件

html部分 <el-button :disabled"disableButton" style"width:35%; float: right;" click"startCountdown"><span>{{ buttonText }}</span></el-button> 初始化: disableButton: false,buttonText: 获取验证码,countdow…

WebSocket学习笔记

一篇文章理解WebSocket原理 1.HTTP协议(半双工通信)&#xff1a; HTTP是客户端向服务器发起请求&#xff0c;服务器返回响应给客户端的一种模式。 特点&#xff1a; 1.只能是客户端向服务器发起请求&#xff0c;是单向的。 2.服务器不能主动发送数据给客户端。 半双工通信…

react+ts手写cron表达式转换组件

前言 最近在写的一个分布式调度系统&#xff0c;后端同学需要让我传入cron表达式&#xff0c;给调度接口传参。我去了学习了解了cron表达式的用法&#xff0c;发现有3个通用的表达式刚好符合我们的需求&#xff1a; 需求 每天 xx 的时间&#xff1a; 0 11 20 * * ? 上面是…

Java开发树结构数据封装!

目录 源数据如下controller接口&#xff1a;service层封装:Dao接口&#xff1a;Dao层Mapper:映射实体类&#xff1a; 源数据如下 controller接口&#xff1a; RequestMapping("/UserTreeInfo")public RespBody getUserTreeInfo(Long userId) {List<MenuTreeVo>…

一文学会使用WebRTC API

WebRTC&#xff08;Web Real-Time Communication&#xff09;是一项开放标准和技术集合&#xff0c;由 W3C 和 IETF 等组织共同推动和维护&#xff0c;旨在通过Web浏览器实现实时通信和媒体流传输。WebRTC于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的…

JVS-rules中的基础与复合变量:规则引擎的心脏

JVS-rules中的“变量”概念与编程语言中的变量类似&#xff0c;但它们通常在规则系统中处理条件判断、业务结果复制场景&#xff0c;如下所示&#xff1a; 条件判断&#xff1a;在规则引擎中&#xff0c;规则通常由两个部分组成&#xff1a;条件和分支。变量用于描述条件部分中…

Arduino驱动ICG-20660L传感器(惯性测量传感器篇)

目录 1、传感器特性 2、硬件原理图 3、控制器和传感器连线图 4、驱动程序

职场题:有一件特别紧急的事,群众要办理,且联系不上领导,你怎么办?(2)

接1所写 如果无法联系上领导且有一项特别紧急的事情需要办理&#xff0c;以下是进一步的建议&#xff1a; 11. 尝试其他沟通渠道&#xff1a;除了直接联系领导外&#xff0c;尝试通过其他沟通渠道与领导取得联系。这可能包括电子邮件、即时通讯工具或其他内部通信平台。确保详…

逐字稿 | 2 MoCo 论文逐段精读【论文精读】

bryanyzhu的个人空间-bryanyzhu个人主页-哔哩哔哩视频 评价 今天我们一起来读一下 MOCO 这篇论文。 MOCO 是 CVPR 2020 的最佳论文提名&#xff0c;算是视觉领域里使用对比学习的一个里程碑式的工作。而对比学习作为从 19 年开始一直到现在视觉领域乃至整个机器学习领域里最炙…

深度学习零基础教程

代码运行软件安装&#xff1a; anaconda:一个管理环境的软件–>https://blog.csdn.net/scorn_/article/details/106591160&#xff08;可选装&#xff09; pycharm&#xff1a;一个深度学习运行环境–>https://blog.csdn.net/scorn_/article/details/106591160&#xf…

PAM从入门到精通(七)

接前一篇文章&#xff1a;PAM从入门到精通&#xff08;六&#xff09; 本文参考&#xff1a; 《The Linux-PAM Application Developers Guide》 先再来重温一下PAM系统架构&#xff1a; 更加形象的形式&#xff1a; 五、主要函数详解 5. pam_strerror 概述&#xff1a; 描述…

在vue使用class选择器和下标更改点击列表样式

如果您正在使用Vue 3的<script setup>语法&#xff0c;可以按照以下步骤在Vue中使用class和下标来更改点击项的样式&#xff1a; 首先&#xff0c;在<script setup>部分导入所需的响应式API和定义需要使用的变量。 <script setup> import { ref } from vue…

千兆光模块和万兆光模块的区别?

在网络通信领域&#xff0c;千兆光模块和万兆光模块是最为常见且广泛应用的两种光模块。不同之处在于传输速率、封装、传输距离、功耗、发射光功率、接收光功率和应用场景等。 千兆光模块的传输速率为1 Gbps&#xff0c;万兆光模块的传输速率为10 Gbps&#xff0c;这意味着万…

vue-cli脚手架创建项目时报错Error: command failed: npm install --loglevel error

项目背景 环境&#xff1a;vue-cli 5.x 在工程文件中&#xff0c;后端模块wms已经创建完成&#xff0c;现在想新建一个名为vue-web的前端模块 执行命令vue create vue-web时&#xff0c; 报错Error: command failed: npm install --loglevel error 问题分析及解决 排查过程…

ScyllaDB获4300万美元融资,NoSQL数据库市场再掀热潮!

ScyllaDB是一家成立于2012年12月的美国公司&#xff0c;总部位于加利福尼亚州桑尼维尔。作为一家数据密集型应用程序数据库供应商&#xff0c;ScyllaDB生产的NoSQL数据库兼容Apache Cassandra和Amazon DynamoDB&#xff0c;具有可靠的低延迟和10倍的吞吐量。在2023年10月17日&a…

代码随想录算法训练营第五十八天| LeetCode 583 两个字符串的删除操作、LeetCode 72 编辑距离、编辑距离总结

1 LeetCode 583 两个字符串的删除操作 题目链接&#xff1a;LeetCode 583 两个字符串的删除操作 文章讲解&#xff1a;代码随想录(programmercarl.com) 视频讲解&#xff1a;LeetCode&#xff1a;583.两个字符串的删除操作 2 LeetCode 72 编辑距离 题目链接&#xff1a;LeetCod…

JVM第十三讲:调试排错 - JVM 调优参数

调试排错 - JVM 调优参数 本文是JVM第十三讲&#xff0c;调试排错 - JVM 调优参数。对JVM涉及的常见的调优参数和垃圾回收参数进行阐述。 文章目录 调试排错 - JVM 调优参数1、Jvm参数2、垃圾回收 问题1&#xff1a;线上ECS治理问题2&#xff1a;白龙马线上服务机JVM参数配置&a…

Qt move和setGeometry的区别

move 和 setGeometry 都是用于管理窗口或小部件的位置的方法&#xff0c;通常在使用 Qt 编程时会用到。它们之间的主要区别在于&#xff1a; move 方法&#xff1a;这个方法用于设置小部件的左上角的坐标位置&#xff0c;它需要两个参数&#xff0c;即横坐标和纵坐标。使用 mov…

idea dubge 详细

目录 一、概述 二、debug操作分析 1、打断点 2、运行debug模式 3、重新执行debug 4、让程序执行到下一次断点后暂停 5、让断点处的代码再加一行代码 6、停止debug程序 7、显示所有断点 8、添加断点运行的条件 9、屏蔽所有断点 10、把光标移到当前程序运行位置 11、单步跳过 12、…