Arm架构异常中断处理流程 您所在的位置:网站首页 华为人像模式效果里心形 Arm架构异常中断处理流程

Arm架构异常中断处理流程

2023-07-20 10:50| 来源: 网络整理| 查看: 265

转载:https://blog.csdn.net/eleven_xiy/article/details/71157618?spm=1001.2014.3001.5506

转载:https://www.cxymm.net/article/michaelcao1980/43309325

中断模式的stack准备

ARM处理器有多种process mode,例如user mode(用户空间的AP所处于的模式)、supervisor mode(即SVC mode,大部分的内核态代码都处于这种mode)、IRQ mode(发生中断后,处理器会切入到该mode)等。对于linux kernel,其中断处理处理过程中,ARM 处理器大部分都是处于SVC mode。但是,实际上产生中断的时候,ARM处理器实际上是进入IRQ mode,因此在进入真正的IRQ异常处理之前会有一小段IRQ mode的操作,之后会进入SVC mode进行真正的IRQ异常处理。由于IRQ mode只是一个过度,因此IRQ mode的栈很小,只有12个字节

struct stack { u32 irq[3]; u32 abt[3]; u32 und[3]; } ____cacheline_aligned; static struct stack stacks[NR_CPUS];

除了irq mode,linux kernel在处理abt mode(当发生data abort exception或者prefetch abort exception的时候进入的模式)和und mode(处理器遇到一个未定义的指令的时候进入的异常模式)的时候也是采用了相同的策略。也就是经过一个简短的abt或者und mode之后,stack切换到svc mode的栈上,这个栈就是发生异常那个时间点current thread的内核栈。anyway,在irq mode和svc mode之间总是需要一个stack保存数据,这就是中断模式的stack,系统初始化的时候,cpu_init函数中会进行中断模式stack的设定:

void notrace cpu_init(void) { unsigned int cpu = smp_processor_id();------获取CPU ID struct stack *stk = &stacks[cpu];---------获取该CPU对于的irq abt和und的stack指针 …… #ifdef CONFIG_THUMB2_KERNEL #define PLC "r"------Thumb-2下,msr指令不允许使用立即数,只能使用寄存器。 #else #define PLC "I" #endif __asm__ ( "msr cpsr_c, %1\n\t"------让CPU进入IRQ mode "add r14, %0, %2\n\t"------r14寄存器保存stk->irq "mov sp, r14\n\t"--------设定IRQ mode的stack为stk->irq "msr cpsr_c, %3\n\t" "add r14, %0, %4\n\t" "mov sp, r14\n\t"--------设定abt mode的stack为stk->abt "msr cpsr_c, %5\n\t" "add r14, %0, %6\n\t" "mov sp, r14\n\t"--------设定und mode的stack为stk->und "msr cpsr_c, %7"--------回到SVC mode :--------------------上面是code,下面的output部分是空的 : "r" (stk),----------------------对应上面代码中的%0 PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),------对应上面代码中的%1 "I" (offsetof(struct stack, irq[0])),------------对应上面代码中的%2 PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),------以此类推,下面不赘述 "I" (offsetof(struct stack, abt[0])), PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE), "I" (offsetof(struct stack, und[0])), PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE) : "r14");--------上面是input操作数列表,r14是要clobbered register列表 }

嵌入式汇编的语法格式是:asm(code : output operand list : input operand list : clobber list);大家对着上面的code就可以分开各段内容了。在input operand list中,有两种限制符(constraint),“r"或者"I”,"I"表示立即数(Immediate operands),"r"表示用通用寄存器传递参数。clobber list中有一个r14,表示在汇编代码中修改了r14的值,这些信息是编译器需要的内容

SVC模式的stack准备

我们经常说进程的用户空间和内核空间,对于一个应用程序而言,可以运行在用户空间,也可以通过系统调用进入内核空间。在用户空间,使用的是用户栈,也就是我们软件工程师编写用户空间程序的时候,保存局部变量的stack。陷入内核后,当然不能用用户栈了,这时候就需要使用到内核栈。所谓内核栈其实就是处于SVC mode时候使用的栈。

Linux kernel在创建进程(包括用户进程和内核线程)的时候都会分配一个(或者两个,和配置相关)page frame,底部是struct thread_info数据结构,顶部(高地址)就是该进程的内核栈。当进程切换的时候,整个硬件和软件的上下文都会进行切换,这里就包括了svc mode的sp寄存器的值被切换到调度算法选定的新的进程的内核栈上来。

【正文一】linux系统arm中断处理之汇编阶段

1 关闭和开启当前处理器上的本地中断,会产生中断信号,但不处理。

local_irq_disable()关闭中断指令:cpsid i;

local_irq_enable()开启中断指令:cpsie i;

关闭和开启中断,不会产生中断信号。

disable_irq/enable_irq

2 为了介绍方便介绍,先列出两个知识点。

#define ARM_cpsr uregs[16] #define ARM_pc uregs[15] #define ARM_lr uregs[14] #define ARM_sp uregs[13] #define ARM_ip uregs[12] #define ARM_fp uregs[11] #define ARM_r10 uregs[10] #define ARM_r9 uregs[9] #define ARM_r8 uregs[8] #define ARM_r7 uregs[7] #define ARM_r6 uregs[6] #define ARM_r5 uregs[5] #define ARM_r4 uregs[4] #define ARM_r3 uregs[3] #define ARM_r2 uregs[2] #define ARM_r1 uregs[1] #define ARM_r0 uregs[0] #define ARM_ORIG_r0 uregs[17]

2.3 ARM 异常处理总入口(entry-armv.s):

处理器模式 缩写 对应的M[4:0]编码 Privilegelevel User usr 10000 PL0 FIQ fiq 10001 PL1 IRQ irq 10010 PL1 Supervisor svc 10011 PL1 Monitor mon 10110 PL1 Abort abt 10111 PL1 Hyp hyp 11010 PL2 Undefined und 11011 PL1 System sys 11111 PL1 2.3 ARM 异常处理总入口(entry-armv.s): 异常向量表的准备

对于ARM处理器而言,当发生异常的时候,处理器会暂停当前指令的执行,保存现场,转而去执行对应的异常向量处的指令,当处理完该异常的时候,恢复现场,回到原来的那点去继续执行程序。系统所有的异常向量(共计8个)组成了异常向量表。向量表(vector table)的代码如下:

/*注释: 1)Arm架构异常处理向量表起始地址__vectors_start。 2)Arm架构定义7种异常包括中断、系统调用、缺页异常等,发生异常时处理器会跳转到相应入口。 3)异常向量表起始位置有cp15协处理器的控制寄存器c1的bit13 决定:v=0时,异常向量起始于0xffff0000;v=1时起始于0x0. 4)举例:当IRQ发生时跳转到0xffff0018这个虚拟地址上(内核态虚拟地址) head.s中设置了cp15寄存器器(proc-v7.s->__v7_setup()函数设置的) */ __vectors_start: W(b) vector_rst @0x0 W(b) vector_und @0x4 /* 系统调用入口点: __vectors_start + 0x1000=__stubs_start 此时pc指向系统调用异常 的处理入口:vector_swi 用户态通过swi指令产生软中断。 因为系统调用异常代码编译到其他文件,其入口地址与异常向量相隔 较远,使用b指令无法跳转过去(b指令只能相对当前pc跳转+/-32M范围) */ W(ldr) pc, __vectors_start + 0x1000 @x8 /* 取指令异常 */ W(b) vector_pabt @0x0c /* 数据异常--缺页异常 */ W(b) vector_dabt @0x10 W(b) vector_addrexcptn @0x14 /* 中断异常 */ W(b) vector_irq @0x18 W(b) vector_fiq @0x1c

对于本文而言,我们重点关注vector_irq这个exception vector。异常向量表可能被安放在两个位置上: (1)异常向量表位于0x0的地址。这种设置叫做Normal vectors或者Low vectors。 (2)异常向量表位于0xffff0000的地址。这种设置叫做high vectors 具体是low vectors还是high vectors是由ARM的一个叫做的SCTLR寄存器的第13个bit (vector bit)控制的。对于启用MMU的ARM Linux而言,系统使用了high vectors。为什么不用low vector呢?对于linux而言,0~3G的空间是用户空间,如果使用low vector,那么异常向量表在0地址,那么则是用户空间的位置,因此linux选用high vector。当然,使用Low vector也可以,这样Low vector所在的空间则属于kernel space了(也就是说,3G~4G的空间加上Low vector所占的空间属于kernel space),不过这时候要注意一点,因为所有的进程共享kernel space,而用户空间的程序经常会发生空指针访问,这时候,内存保护机制应该可以捕获这种错误(大部分的MMU都可以做到,例如:禁止userspace访问kernel space的地址空间),防止vector table被访问到。对于内核中由于程序错误导致的空指针访问,内存保护机制也需要控制vector table被修改,因此vector table所在的空间被设置成read only的。在使用了MMU之后,具体异常向量表放在那个物理地址已经不重要了,重要的是把它映射到0xffff0000的虚拟地址就OK了,具体代码如下:

static void __init devicemaps_init(const struct machine_desc *mdesc) { …… vectors = early_alloc(PAGE_SIZE * 2); -----分配两个page的物理页帧 early_trap_init(vectors); -------copy向量表以及相关help function到该区域 …… map.pfn = __phys_to_pfn(virt_to_phys(vectors)); map.virtual = 0xffff0000; map.length = PAGE_SIZE; #ifdef CONFIG_KUSER_HELPERS map.type = MT_HIGH_VECTORS; #else map.type = MT_LOW_VECTORS; #endif create_mapping(&map); ----------映射0xffff0000的那个page frame if (!vectors_high()) {---如果SCTLR.V的值设定为low vectors,那么还要映射0地址开始的memory map.virtual = 0; map.length = PAGE_SIZE * 2; map.type = MT_LOW_VECTORS; create_mapping(&map); } map.pfn += 1; map.virtual = 0xffff0000 + PAGE_SIZE; map.length = PAGE_SIZE; map.type = MT_LOW_VECTORS; create_mapping(&map); ----------映射high vecotr开始的第二个page frame …… }

为什么要分配两个page frame呢?这里vectors table和kuser helper函数(内核空间提供的函数,但是用户空间使用)占用了一个page frame,另外异常处理的stub函数占用了另外一个page frame。为什么会有stub函数呢?稍后会讲到。

在early_trap_init函数中会初始化异常向量表,具体代码如下

void __init early_trap_init(void *vectors_base) { unsigned long vectors = (unsigned long)vectors_base; extern char __stubs_start[], __stubs_end[]; extern char __vectors_start[], __vectors_end[]; unsigned i; vectors_page = vectors_base; 将整个vector table那个page frame填充成未定义的指令。起始vector table加上kuser helper函数并不能完全的充满这个page,有些缝隙。如果不这么处理,当极端情况下(程序错误或者HW的issue),CPU可能从这些缝隙中取指执行,从而导致不可知的后果。如果将这些缝隙填充未定义指令,那么CPU可以捕获这种异常。 for (i = 0; i pc) 最后从异常返回时再将pt_regs->pc加载入arm寄存器pc中,实现异常返回。 本函数只是其中一个步骤,即为将异常发生时刻lr保存到svc模式栈中(pt_regs->pc)做准备。 4 spsr是异常发生那一刻(即进入异常模式前是什么模式)的cpsr状态,如内核态下发生中断:则spsr是svc模式10111; 如用户态下发生中断,则spsr是user模式10000. 5 此时cpu正处于异常状态(如中断),此时cpsr为10010; 6 要进行真正的异常处理,需要退出异常模式进入svc模式. */ .macro vector_stub, name, mode, correction=0 .align 5 @/*强制cacheline=32byte对齐*/ vector_\name: .if \correction sub lr, lr, #\correction /*注释:需要调整返回值,对应irq异常将lr减去4,因为异常发生时,arm硬件上操作将异常的返回地址+4赋值给了lr*/ .endif @ Save r0, lr_ (parent PC) and spsr_ @ (parent CPSR) @spsr中保存异常发生时刻的cpsr ; @注意此时的栈sp是异常时(abt mode或irq mode)的栈sp和svc mode里的栈sp不同 @dabt异常时的sp只有12byte大小 ;cpu_init中初始化 @save r0, lr;将r0和lr保存到异常模式的栈上[sp]=r0;[sp+4]=lr_dabt;stmia sp,{r0, lr},没有sp!,因此sp不变 @r0也要入栈,以为r0会用作传递参数(异常状态下的sp) stmia sp, {r0, lr} @ [sp]=r0;[sp+4]=lr_dabt;stmia sp,{r0, lr},没有sp!,因此sp不变 mrs lr, spsr @ /*将spsr保存到异常模式的栈上[sp+8]=spsr*/ str lr, [sp, #8] @ [sp+8]=spsr= 异常前的cpsr @ @ Prepare for SVC32 mode. IRQs remain disabled. @ cpsr中的是模式异常模式:如中断10010;dabt10111 /* 注意: 1 dabt处理时:r0=r0^(0x17^0x13)=r0^0x4,bit3取反之后10011变为svc模式; 2 IRQ处理时:r0=10010=r0^(0x12^0x13)=r0^0x1=10011变为svc模式 */ mrs r0, cpsr eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE) msr spsr_cxsf, r0 @更新spsr中的cxsf域,全部更新,spsr存的是SVC模式 /* c 指 CPSR中的control field ( PSR[7:0]) f 指 flag field (PSR[31:24]) x 指 extend field (PSR[15:8]) s 指 status field ( PSR[23:16]) */ @ the branch table must immediately follow this code @ lr保存了发生IRQ时候的CPSR,通过and操作,可以获取CPSR.M[3:0]的 @ /* 从用户态进入异常(user mode)lr=0;从内核态进入异常(svn mode)lr=3; */ and lr, lr, #0x0f /* r0=sp; 注意: 1,此时r0中保存了异常状态下sp栈地址,这个栈上保存了r0,lr(异常返回地址),spsr(异常发生时,cpu的状态,当然异常返回时需要恢复该状态) 2,之后的函数会把r0中保存的异常模式的sp上信息,加载到svc模式下的sp栈上。异常处理返回时再将svc mode的栈加载到arm寄存器上。 */ mov r0, sp /* lr中是保存发生异常时arm的cpsr状态到spsr 1 user模式发生异常则lr=10000&0x0f;lr=pc+lr


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有