一、前语
本文首要以ARM体系结构下的中止处理为例,叙述整个中止处理进程中的硬件行为和软件动作。详细整个处理进程分红三个进程来描绘:
1、第二章描绘了中止处理的预备进程
2、第三章描绘了当产生中的时分,ARM硬件的行为
3、第四章描绘了ARM的中止进入进程
4、第五章描绘了ARM的中止退出进程
本文触及的代码来自3.14内核。别的,本文留意描绘ARM指令集的内容,有些source code为了简略一些,删去了THUMB相关的代码,除此之外,有些debug相关的内容也会删去。
二、中止处理的预备进程
1、中止形式的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的值,这些信息是编译器需求的内容。
2、SVC形式的stack预备
咱们常常说进程的用户空间和内核空间,关于一个应用程序而言,能够运转在用户空间,也能够经过体系调用进入内核空间。在用户空间,运用的是用户栈,也便是咱们软件工程师编写用户空间程序的时分,保存局部变量的stack。堕入内核后,当然不能用用户栈了,这时分就需求运用到内核栈。所谓内核栈其实便是处于SVC mode时分运用的栈。
Linux kernel在创立进程(包含用户进程和内核线程)的时分都会分配一个(或许两个,和装备相关)page frame,底部是struct thread_info数据结构,顶部(高地址)便是该进程的内核栈。当进程切换的时分,整个硬件和软件的上下文都会进行切换,这儿就包含了svc mode的sp寄存器的值被切换到调度算法选定的新的进程的内核栈上来。
3、反常向量表的预备
关于ARM处理器而言,当产生反常的时分,处理器会暂停其时指令的履行,保存现场,转而去履行对应的反常向量处的指令,当处理完该反常的时分,康复现场,回到本来的那点去持续履行程序。体系悉数的反常向量(合计8个)组成了反常向量表。向量表(vector table)的代码如下:
.section .vectors, “ax”, %progbits
__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, __vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq —————————IRQ Vector
W(b) vector_fiq
关于本文而言,咱们要点重视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 frameif (!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 < PAGE_SIZE / sizeof(u32); i++)
((u32 *)vectors_base)[i] = 0xe7fddef1;复制vector table,复制stub function
memcpy((void *)vectors, __vectors_start, __vectors_end – __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end – __stubs_start);kuser_init(vectors_base); ----copy kuser helper function
flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
一旦触及代码的复制,咱们就需求关怀其编译衔接时地址(link-time address)和运转时地址(run-time address)。在kernel完结链接后,__vectors_start有了其link-time address,假如link-time address和run-time address共同,那么这段代码运转时毫无压力。可是,现在关于vector table而言,其被copy到其他的地址上(关于High vector,这是地址便是0xffff00000),也便是说,link-time address和run-time address不相同了,假如依然想要这些代码能够正确运转,那么需求这些代码是方位无关的代码。关于vector table而言,有必要要方位无关。B这个branch instruction自身便是方位无关的,它能够跳转到一个其时方位的offset。不过并非悉数的vector都是运用了branch instruction,关于软中止,其vector地址上指令是“W(ldr) pc, __vectors_start + 0x1000 ”,这条指令被编译器编译成ldr pc, [pc, #4080],这种状况下,该指令也是方位无关的,可是有个约束,offset有必要在4K的范围内,这也是为何存在stub section的原因了。
4、中止操控器的初始化
详细能够参阅GIC代码剖析。
三、ARM HW对中止事情的处理
当悉数预备好之后,一旦翻开处理器的大局中止就能够处理来自外设的各种中止事情了。
当外设(SOC内部或许外部都能够)检测到了中止事情,就会经过interrupt requestion line上的电平或许边缘(上升沿或许下降沿或许both)告诉到该外设衔接到的那个中止操控器,而中止操控器就会在多个处理器中挑选一个,并把该中止经过IRQ(或许FIQ,本文不评论FIQ的状况)分发给process。ARM处理器感知到了中止事情后,会进行下面一系列的动作:
1、修正CPSR(Current Program Status Register)寄存器中的M[4:0]。M[4:0]表明了ARM处理器其时处于的形式( processor modes)。ARM界说的mode包含:
处理器形式 | 缩写 | 对应的M[4:0]编码 | Privilege level |
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 |
一旦设定了CPSR.M,ARM处理器就会将processor mode切换到IRQ mode。
2、保存产生中止那一点的CPSR值(step 1之前的状况)和PC值
ARM处理器支撑9种processor mode,每种mode看到的ARM core register(R0~R15,合计16个)都是不同的。每种mode都是从一个包含悉数的Banked ARM core register中选取。悉数Banked ARM core register包含:
Usr | System | Hyp | Supervisor | abort | undefined | Monitor | IRQ | FIQ |
R0_usr | ||||||||
R1_usr | ||||||||
R2_usr | ||||||||
R3_usr | ||||||||
R4_usr | ||||||||
R5_usr | ||||||||
R6_usr | ||||||||
R7_usr | ||||||||
R8_usr | R8_fiq | |||||||
R9_usr | R9_fiq | |||||||
R10_usr | R10_fiq | |||||||
R11_usr | R11_fiq | |||||||
R12_usr | R12_fiq | |||||||
SP_usr | SP_hyp | SP_svc | SP_abt | SP_und | SP_mon | SP_irq | SP_fiq | |
LR_usr | LR_svc | LR_abt | LR_und | LR_mon | LR_irq | LR_fiq | ||
PC | ||||||||
CPSR | ||||||||
SPSR_hyp | SPSR_svc | SPSR_abt | SPSR_und | SPSR_mon | SPSR_irq | SPSR_fiq | ||
ELR_hyp |
在IRQ mode下,CPU看到的R0~R12寄存器、PC以及CPSR是和usr mode(userspace)或许svc mode(kernel space)是相同的。不同的是IRQ mode下,有自己的R13(SP,stack pointer)、R14(LR,link register)和SPSR(Saved Program Status Register)。
CPSR是共用的,尽管中止或许产生在usr mode(用户空间),也或许是svc mode(内核空间),不过这些信息都是体现在CPSR寄存器中。硬件会将产生中止那一刻的CPSR保存在SPSR寄存器中(由于不同的mode下有不同的SPSR寄存器,因而更精确的说应该是SPSR-irq,也便是IRQ mode中的SPSR寄存器)。
PC也是共用的,由于后续PC会被修正为irq exception vector,因而有必要保存PC值。当然,与其说保存PC值,不如说是保存回来履行的地址。关于IRQ而言,咱们希望回来地址是产生中止那一点履行指令的下一条指令。详细的回来地址保存在lr寄存器中(留意:这个lr寄存器是IRQ mode的lr寄存器,能够表明为lr_irq):
(1)关于thumb state,lr_irq = PC
(2)关于ARM state,lr_irq = PC - 4
为何要减去4?我的了解是这样的(不一定对)。由于ARM选用流水线结构,当CPU正在履行某一条指令的时分,其实取指的动作早就履行了,这时分PC值=正在履行的指令地址 + 8,如下所示:
----> 产生中止的指令
产生中止的指令+4
-PC-->产生中止的指令+8
产生中止的指令+12
一旦产生了中止,其时正在履行的指令当然要履行结束,可是现已完结取指、译码的指令则停止履行。当产生中止的指令履行结束之后,本来指向(产生中止的指令+8)的PC会持续添加4,因而产生中止后,ARM core的硬件着手处理该中止的时分,硬件现场如下图所示:
----> 产生中止的指令
产生中止的指令+4 <-------中止回来的指令是这条指令
产生中止的指令+8
-PC-->产生中止的指令+12
这时分的PC值其实是比产生中止时分的指令超前12。减去4之后,lr_irq中保存了(产生中止的指令+8)的地址。为什么HW不帮助直接减去8呢?这样,后续软件不就不必再减去4了。这儿咱们不能孤立的看待问题,实践上ARM的反常处理的硬件逻辑不仅仅处理IRQ的exception,还要处理各种exception,很惋惜,不同的exception希望的回来地址不一致,因而,硬件仅仅帮助减去4,剩下的交给软件去调整。
3、mask IRQ exception。也便是设定CPSR.I = 1
4、设定PC值为IRQ exception vector。基本上,ARM处理器的硬件就只能帮你帮到这儿了,一旦设定PC值,ARM处理器就会跳转到IRQ的exception vector地址了,后续的动作都是软件行为了。
四、怎么进入ARM中止处理
1、IRQ mode中的处理
IRQ mode的处理都在vector_irq中,vector_stub是一个宏,界说如下:
.macro vector_stub, name, mode, correction=0
.align 5vector_\name:
.if \correction
sub lr, lr, #\correction-------------(1)
.endif@
@ Save r0, lr_(parent PC) and spsr_
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr--------(2)
mrs lr, spsr
str lr, [sp, #8] @ save spsr@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr-----------------------(3)
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f---lr保存了产生IRQ时分的CPSR,经过and操作,能够获取CPSR.M[3:0]的值这时分,假如中止产生在用户空间,lr=0,假如是内核空间,lr=3
THUMB( adr r0, 1f )----依据其时PC值,获取lable 1的地址
THUMB( ldr lr, [r0, lr, lsl #2] )-lr依据其时mode,要么是__irq_usr的地址 ,要么是__irq_svc的地址
mov r0, sp------将irq mode的stack point经过r0传递给行将跳转的函数
ARM( ldr lr, [pc, lr, lsl #2] )---依据mode,给lr赋值,__irq_usr或许__irq_svc
movs pc, lr @ branch to handler in SVC mode-----(4)
ENDPROC(vector_\name).align 2
@ handler addresses follow this label
1:
.endm
(1)咱们希望在栈上保存产生中止时分的硬件现场(HW context),这儿就包含ARM的core register。上一章咱们现已了解到,当产生IRQ中止的时分,lr中保存了产生中止的PC+4,假如减去4的话,得到的便是产生中止那一点的PC值。
(2)其时是IRQ mode,SP_irq在初始化的时分现已设定(12个字节)。在irq mode的stack上,顺次保存了产生中止那一点的r0值、PC值以及CPSR值(详细操作是经过spsr进行的,其实硬件现已帮咱们保存了CPSR到SPSR中了)。为何要保存r0值?由于随后的代码要运用r0寄存器,因而咱们要把r0放到栈上,只需这样才干完彻底全康复硬件现场。
(3)不幸的IRQ mode少纵即逝,这段代码便是预备将ARM推送到SVC mode。怎么预备?其实便是修正SPSR的值,SPSR不是CPSR,不会引起processor mode的切换(究竟这一步仅仅预备罢了)。
(4)许多反常处理的代码回来的时分都是运用了stack相关的操作,这儿没有。“movs pc, lr ”指令除了字面上意思(把lr的值交给pc),还有一个隐含的操作(movs中‘s’的意义):把SPSR copy到CPSR,然后完成了形式的切换。
2、当产生中止的时分,代码运转在用户空间
Interrupt dispatcher的代码如下:
vector_stub irq, IRQ_MODE, 4 -----减去4,确保回来产生中止之后的那条指令
.long __irq_usr @ 0 (USR_26 / USR_32) <---------------------> base address + 0
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)<---------------------> base address + 12
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
这其实便是一个lookup table,依据CPSR.M[3:0]的值进行跳转(参阅上一节的代码:and lr, lr, #0x0f)。因而,该lookup table共设定了16个进口,当然只需两项有用,别离对应user mode和svc mode的跳转地址。其他进口的__irq_invalid也是十分要害的,这确保了在其形式下产生了中止,体系能够捕获到这样的过错,为debug供给有用的信息。
.align 5
__irq_usr:
usr_entry---------请参阅本章第一节(1)保存用户现场的描绘
kuser_cmpxchg_check---和本文描绘的内容无关,这些不就介绍了
irq_handler----------中心处理内容,请参阅本章第二节的描绘
get_thread_info tsk------tsk是r9,指向其时的thread info数据结构
mov why, #0--------why是r8
b ret_to_user_from_irq----中止回来,下一章会详细描绘
(1)保存产生中止时分的现场。所谓保存现场其实便是把产生中止那一刻的硬件上下文(各个寄存器)保存在了SVC mode的stack上。
.macro usr_entry
sub sp, sp, #S_FRAME_SIZE--------------A
stmib sp, {r1 – r12} -------------------Bldmia r0, {r3 – r5}--------------------C
add r0, sp, #S_PC-------------------D
mov r6, #-1----orig_r0的值str r3, [sp] ----保存中止那一刻的r0
stmia r0, {r4 – r6}--------------------E
stmdb r0, {sp, lr}^-------------------F
.endm
A:代码履行到这儿的时分,ARM处理现已切换到了SVC mode。一旦进入SVC mode,ARM处理器看到的寄存器现已产生变化,这儿的sp现已变成了sp_svc了。因而,后续的压栈操作都是压入了产生中止那一刻的进程的(或许内核线程)内核栈(svc mode栈)。详细保存多少个寄存器值?S_FRAME_SIZE现已给出了答案,这个值是18个寄存器。r0~r15再加上CPSR也只需17个罢了。先保存这个疑问,咱们稍后答复。
B:压栈首要压入了r1~r12,这儿为何不处理r0?由于r0在irq mode切到svc mode的时分被污染了,不过,原始的r0被保存的irq mode的stack上了。r13(sp)和r14(lr)需求保存吗,当然需求,稍后再保存。履行到这儿,内核栈的布局如下图所示:
stmib中的ib表明increment before,因而,在压入R1的时分,stack pointer会先添加4,重要是预留r0的方位。stmib sp, {r1 – r12}指令中的sp没有“!”的修饰符,表明压栈完结后并不会真实更新stack pointer,因而sp坚持本来的值。
C:留意,这儿r0指向了irq stack,因而,r3是中止时分的r0值,r4是中止现场的PC值,r5是中止现场的CPSR值。
D:把r0赋值为S_PC的值。依据struct pt_regs的界说(这个数据结构反响了内核栈上的保存的寄存器的摆放信息),从低地址到高地址顺次为:
ARM_r0
ARM_r1
ARM_r2
ARM_r3
ARM_r4
ARM_r5
ARM_r6
ARM_r7
ARM_r8
ARM_r9
ARM_r10
ARM_fp
ARM_ip
ARM_sp
ARM_lr
ARM_pc<---------add r0, sp, #S_PC指令使得r0指向了这个方位
ARM_cpsr
ARM_ORIG_r0
为什么要给r0赋值?因而kernel不想修正sp的值,坚持sp指向栈顶。
E:在内核栈上保存剩下的寄存器的值,依据代码,顺次是r0,PC,CPSR和orig r0。履行到这儿,内核栈的布局如下图所示:
R0,PC和CPSR来自IRQ mode的stack。实践上这段操作便是从irq stack就中止现场搬移到内核栈上。
F:内核栈上还有两个寄存器没有坚持,别离是产生中止时分sp和lr这两个寄存器。这时分,r0指向了保存PC寄存器那个地址(add r0, sp, #S_PC),stmdb r0, {sp, lr}^中的“db”是decrement before,因而,将sp和lr压入stack中的剩下的两个方位。需求留意的是,咱们保存的是产生中止那一刻(关于本节,这是其时user mode的sp和lr),指令中的“^”符号表明拜访user mode的寄存器。
(2)中心处理
irq_handler的处理有两种装备。一种是装备了CONFIG_MULTI_IRQ_HANDLER。这种状况下,linux kernel答应run time设定irq handler。假如咱们需求一个linux kernel image支撑多个渠道,这是就需求装备这个选项。别的一种是传统的linux的做法,irq_handler实践上便是arch_irq_handler_default,详细代码如下:
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp--------设定传递给machine界说的handle_arch_irq的参数
adr lr, BSYM(9997f)----设定回来地址
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm
关于状况一,machine相关代码需求设定handle_arch_irq函数指针,这儿的汇编指令只需求调用这个machine代码供给的irq handler即可(当然,要预备好参数传递和回来地址设定)。
状况二要略微杂乱一些(并且,看起来kernel中运用的越来越少),代码如下:
.macro arch_irq_handler_default
get_irqnr_preamble r6, lr
1: get_irqnr_and_base r0, r2, r6, lr
movne r1, sp
@
@ asm_do_IRQ 需求两个参数,一个是 irq number(保存在r0)
@ 另一个是 struct pt_regs *(保存在r1中)
adrne lr, BSYM(1b)-------回来地址设定为符号1,也便是说要不断的解析irq状况寄存器的内容,得到IRQ number,直到悉数的irq number处理结束
bne asm_do_IRQ
.endm
这儿的代码现已是和machine相关的代码了,咱们这儿仅仅简略描绘一下。所谓machine相关也便是说和体系中的中止操控器相关了。get_irqnr_preamble是为中止处理做预备,有些渠道底子不需求这个进程,直接界说为空即可。get_irqnr_and_base 有四个参数,别离是:r0保存了本次解析的irq number,r2是irq状况寄存器的值,r6是irq controller的base address,lr是scratch register。
3、当产生中止的时分,代码运转在内核空间
假如中止产生在内核空间,代码会跳转到__irq_svc处履行:
.align 5
__irq_svc:
svc_entry----保存产生中止那一刻的现场保存在内核栈上
irq_handler ----详细的中止处理,同user mode的处理。#ifdef CONFIG_PREEMPT--------和preempt相关的处理,本文不进行描绘
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endifsvc_exit r5, irq = 1 @ return from exception
保存现场的代码和user mode下的现场保存是相似的,因而这儿不再详细描绘,仅仅鄙人面的代码中内嵌一些注释。
.macro svc_entry, stack_hole=0
sub sp, sp, #(S_FRAME_SIZE + \stack_hole – 4)----sp指向struct pt_regs中r1的方位
stmia sp, {r1 – r12} ------寄存器入栈。ldmia r0, {r3 – r5}
add r7, sp, #S_SP – 4 ------r7指向struct pt_regs中r12的方位
mov r6, #-1 ----------orig r0设为-1
add r2, sp, #(S_FRAME_SIZE + \stack_hole – 4)----r2是发现中止那一刻stack的现场
str r3, [sp, #-4]! ----保存r0,留意有一个!,sp会加上4,这时分sp就指向栈顶的r0方位了mov r3, lr ----保存svc mode的lr到r3
stmia r7, {r2 – r6} ---------压栈,在栈上构成构成struct pt_regs
.endm
五、中止退出进程
1、中止产生在user mode下的退出进程,代码如下:
ENTRY(ret_to_user_from_irq)
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK---------------A
bne work_pending
no_work_pending:
asm_trace_hardirqs_on ------和irq flag trace相关,暂时略过/* perform architecture specific actions before user return */
arch_ret_to_user r1, lr----有些硬件渠道需求在中止回来用户空间做一些特别处理
ct_user_enter save = 0 ----和trace context相关,暂时略过restore_user_regs fast = 0, offset = 0------------B
ENDPROC(ret_to_user_from_irq)
ENDPROC(ret_to_user)
A:thread_info中的flags成员中有一些low level的标识,假如这些标识设定了就需求进行一些特别的处理,这儿检测的flag首要包含:
#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME)
这三个flag别离表明是否需求调度、是否有信号处理、回来用户空间之前是否需求调用callback函数。只需有一个flag被设定了,程序就进入work_pending这个分支。
B:从字面的意思也能够当作,这部分的代码便是将进入中止的时分保存的现场(寄存器值)康复到实践的ARM的各个寄存器中,然后彻底回来到了中止产生的那一点。详细的代码如下:
.macro restore_user_regs, fast = 0, offset = 0
ldr r1, [sp, #\offset + S_PSR] ----r1保存了pt_regs中的spsr,也便是产生中止时的CPSR
ldr lr, [sp, #\offset + S_PC]! ----lr保存了PC值,一起sp移动到了pt_regs中PC的方位
msr spsr_cxsf, r1 ---------赋值给spsr,进行回来用户空间的预备
clrex @ clear the exclusive monitor.if \fast
ldmdb sp, {r1 – lr}^ @ get calling r1 – lr
.else
ldmdb sp, {r0 – lr}^ ------将保存在内核栈上的数据保存到用户态的r0~r14寄存器
.endif
mov r0, r0 ---------NOP操作,ARMv5T之前的需求这个操作
add sp, sp, #S_FRAME_SIZE – S_PC----现场现已康复,移动svc mode的sp到本来的方位
movs pc, lr --------回来用户空间
.endm
2、中止产生在svc mode下的退出进程,代码如下:
.macro svc_exit, rpsr, irq = 0
.if \irq != 0
@ IRQs already off
.else
@ IRQs off again before pulling preserved data off the stack
disable_irq_notrace
.endif
msr spsr_cxsf, \rpsr-----将中止现场的cpsr值保存到spsr中,预备回来中止产生的现场ldmia sp, {r0 – pc}^ -----这条指令是ldm反常回来指令,这条指令除了字面上的操作,
还包含了将spsr copy到cpsr中。
.endm