转自:http://blog.csdn.net/xiaojsj111/article/details/14129661
以外部中断irq为例来说明,当外部硬件产生中断时,linux的处理过程。首先先说明当外部中断产生时,硬件处理器所做的工作如下:

R14_irq = address of next instruction to be executed + 4/*将寄存器lr_mode设置成返回地址,即为当前pc的值,因为pc是当前执行指令的下两条指令*/

SPSR_irq = CPSR                /*保存处理器当前状态、中断屏蔽位以及各条件标志位*/

CPSR[4:0] = 0b10010         /*设置当前程序状态寄存器CPSR中相应的位进入IRQ模式,注意cpsr是所有模式共享的*/

CPSR[5] = 0                        /*在ARM状态执行*/

/*CPSR[6] 不变*/

CPSR[7] = 1                       /*禁止正常中断*/

If high vectors configured then

PC=0xFFFF0018          /*将程序计数器(PC)值设置成该异常中断的中断向量地址,从而跳转到相应的异常中断处理程序处执行,对于ARMv7向量表普遍中断是0xFFFF0018*/

else

PC=0x00000018     /*对于低向量*/

假设在用户空间时,产生了外部硬件中断,则这个时候的指令跳转流程如下:

[cpp] view plain copy
  1. __vectors_start:---------------〉在中断向量表被拷贝后,该地址就是0xffff0000.
  2. ARM( swi SYS_ERROR0 )
  3. THUMB( svc #0 )
  4. THUMB( nop )
  5. W(b) vector_und + stubs_offset
  6. W(ldr) pc, .LCvswi + stubs_offset
  7. W(b) vector_pabt + stubs_offset
  8. W(b) vector_dabt + stubs_offset
  9. W(b) vector_addrexcptn + stubs_offset
  10. W(b)vector_irq + stubs_offset----------〉当外部中断产生时,pc直接指向这个地址。
  11. W(b) vector_fiq + stubs_offset
  12. .globl __vectors_end
下面的vector_stubirq, IRQ_MODE, 4语句,展开就是vector_irq,所以上述语句跳转到如下语句执行:
[cpp] view plain copy
  1. __stubs_start:
  2. /*
  3. * Interrupt dispatcher
  4. */
  5. vector_stub irq, IRQ_MODE, 4
  6. .long __irq_usr@  0  (USR_26 / USR_32)
  7. .long __irq_invalid@  1  (FIQ_26 / FIQ_32)
  8. .long __irq_invalid@  2  (IRQ_26 / IRQ_32)
  9. .long __irq_svc@  3  (SVC_26 / SVC_32)
  10. .long __irq_invalid@  4
  11. .long __irq_invalid@  5
  12. .long __irq_invalid@  6
  13. .long __irq_invalid@  7
  14. .long __irq_invalid@  8
  15. .long __irq_invalid@  9
  16. .long __irq_invalid@  a
  17. .long __irq_invalid@  b
  18. .long __irq_invalid@  c
  19. .long __irq_invalid@  d
  20. .long __irq_invalid@  e
  21. .long __irq_invalid@  f

vector_stubirq, IRQ_MODE, 4语句展开如下:

[cpp] view plain copy
  1. <span style="font-size:18px">/*
  2. * Vector stubs.
  3. *
  4. * This code is copied to 0xffff0200 so we can use branches in the
  5. * vectors, rather than ldr's.  Note that this code must not
  6. * exceed 0x300 bytes.
  7. *
  8. * Common stub entry macro:
  9. *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
  10. *
  11. * SP points to a minimal amount of processor-private memory, the address
  12. * of which is copied into r0 for the mode specific abort handler.
  13. */
  14. .macro  vector_stub, name, mode, correction=0
  15. .align  5
  16. vector_\name:
  17. .if \correction
  18. sub lr, lr, #\correction  //因为硬件处理器是将当前指令的下两条指令的地址存储在lr寄存器中,所以这里需要减4,让他指向被中断指令的下一条,这样当中断被恢复时,可以继续被中断的指令继续执行。
  19. .endif<span style="white-space:pre">            </span>      //需要注意的是,这个时候的lr寄存器,已经是irq模式下的私有寄存器了,在中断产生时,硬件处理器已经自动为他赋了值。
  20. @
  21. @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
  22. @ (parent CPSR)
  23. @
  24. stmia   sp, {r0, lr}        @ save r0, lr//保存r0和lr寄存器,即被中断的下一条指令
  25. mrs lr, spsr
  26. str lr, [sp, #8]        @ save spsr
  27. @
  28. @ Prepare for SVC32 mode.  IRQs remain disabled.//准备从中断模式切换到管理模式,不同的模式,对应各自不同的堆栈。
  29. @
  30. mrs r0, cpsr
  31. eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
  32. msr spsr_cxsf, r0
  33. @
  34. @ the branch table must immediately follow this code
  35. @
  36. and lr, lr, #0x0f           //获取被中断前,处理器所处的模式
  37. THUMB( adr r0, 1f          )
  38. THUMB( ldr lr, [r0, lr, lsl #2]    )
  39. mov r0, sp<span style="white-space:pre">            </span>//让r0寄存器指向中断模式下堆栈的基地址
  40. ARM(   ldr lr, [pc, lr, lsl #2]    )
  41. movs    pc, lr          @ branch to handler in SVC mode,同时将中断模式下的spsr_irq(irq私有的)赋值给cpsr(该寄存器所有模式共享)
  42. ENDPROC(vector_\name)</span>
此时中断模式下的私有栈sp的存储情况如下:(注意这个时候的sp是中断模式下的堆栈sp),并且这个时候r0寄存器中,保存有sp的指针值,由于r0已经被保存到堆栈,所以可以放心被使用

根据被中断时,处理器模式的不同,分别跳转到__irq_usr和__irq_svc两个分支。

在这里我们以__irq_usr为例来说明:
[cpp] view plain copy
  1. <span style="font-size:18px">__irq_usr:
  2. usr_entry       //进行中断前的硬件上下文的保存
  3. kuser_cmpxchg_check
  4. irq_handler
  5. get_thread_info tsk//获取被中断的用户进程或内核线程所对应的内核栈所对应的thread info结构。
  6. mov why, #0
  7. b   ret_to_user_from_irq//恢复被中断时的上下文,然后继续被中断的进程或线程的执行
  8. UNWIND(.fnend      )
  9. ENDPROC(__irq_usr)</span>
usr_entry展开如下:
[cpp] view plain copy
  1. .macro  usr_entry
  2. UNWIND(.fnstart )
  3. UNWIND(.cantunwind  )   @ don't unwind the user space
  4. sub sp, sp, #S_FRAME_SIZE   // #S_FRAME_SIZE的值为72
  5. ARM(    stmib   sp, {r1 - r12}  )      //尽管当前是处于管理模式,但由于svc和usr的r0-r12是公共的,所以相当于保存用户模式的r1-r12寄存器
  6. THUMB(  stmia   sp, {r0 - r12}  )
  7. ldmia   r0, {r3 - r5}          //将之前保存在中断模式堆栈中的r0_usr,lr,spsr分别存储到r3-r5中
  8. add r0, sp, #S_PC       @ here for interlock avoidance #S_PC=60
  9. mov r6, #-1         @  ""  ""     ""        ""
  10. str r3, [sp]        @ save the "real" r0 copied
  11. @ from the exception stack
  12. @
  13. @ We are now ready to fill in the remaining blanks on the stack:
  14. @
  15. @  r4 - lr_<exception>, already fixed up for correct return/restart
  16. @  r5 - spsr_<exception>
  17. @  r6 - orig_r0 (see pt_regs definition in ptrace.h)
  18. @
  19. @ Also, separately save sp_usr and lr_usr
  20. @
  21. stmia   r0, {r4 - r6}
  22. ARM(    stmdb   r0, {sp, lr}^           )//保存用户模式下的sp_usr,lr_usr
  23. THUMB(  store_user_sp_lr r0, r1, S_SP - S_PC    )
  24. @
  25. @ Enable the alignment trap while in kernel mode
  26. @
  27. alignment_trap r0
  28. @
  29. @ Clear FP to mark the first stack frame
  30. @
  31. zero_fp
  32. ifdef CONFIG_IRQSOFF_TRACER
  33. bl  trace_hardirqs_off
  34. endif
  35. .endm
至此,用户模式下所有的寄存器都被正确保存了,并且处理器模式中irq模式成功切换到管理模式,并且sp这个时候是指向保存r0_usr寄存器值得地方。此时的管理模式的内核栈分布如下:
需要说明的是:上图中的lr_irq即为用户模式下被中断指令的下一条指令,spsr_irq即为用户模式下被中断时的cpsr寄存器。
在这里说明下,中断时寄存器的保存是有固定的顺序的,他们顺序即如下所示:
cpsr(r16)
pc(r15)
lr(r14)
sp(r13)
r12(ip)
r11(fp)
r10
r9
r8
r7
r6
r5
r4
r3
r2
r1
r0

上图中的S_FRAME_SIZE, S_PC在arch/arm/kernel/Asm-offsets.c:中定义

DEFINE(S_FRAME_SIZE,     sizeof(struct pt_regs));

DEFINE(S_PC,         offsetof(struct pt_regs, ARM_pc));

include/asm-arm/Ptrace.h:

struct pt_regs {

long uregs[18];

};

#define ARM_pc     uregs[15]

呵呵,pt_regs中对应的就是上面栈上的18个寄存器,ARM_pc是pc寄存器存放在这个数组中的偏移。

接着看get_thread_info, 它也是个宏,用来获取当前线程的地址。他的结构体定义如下:

include/linux/Sched.h:

union thread_union {

struct thread_info thread_info;  /*线程属性*/

unsigned long stack[THREAD_SIZE/sizeof(long)];  /*栈*/

};

由它定义的线程是8K字节对齐的, 并且在这8K的最低地址处存放的就是thread_info对象,即该栈拥有者线程的对象,而get_thread_info就是通过把sp低13位清0(8K边界)来获取当前thread_info对象的地址。

arch/arm/kernel/entry-armv.S:

.macro  get_thread_info, rd

mov /rd, sp, lsr #13

mov /rd, /rd, lsl #13

.endm

调用该宏后寄存器tsk里存放的就是当前线程的地址了, tsk是哪个寄存器呢,呵呵我们在看:

arch/arm/kernel/entry-header.S:

tsk .req    r9      @ current thread_info

呵呵,tsk只是r9的别名而已, 因此这时r9里保存的就是当前线程的地址。

为了将汇编部分讲完,我们继续研究ret_to_user_from_irq函数,该函数展开后,如下:
[cpp] view plain copy
  1. <span style="font-size:18px">ENTRY(ret_to_user_from_irq)
  2. ldr r1, [tsk, #TI_FLAGS] //tsk如上所述,是r9寄存器的别名,并且是指向thread_info结构体的
  3. tst r1, #_TIF_WORK_MASK  //检测是否有待处理的任务
  4. bne work_pending
  5. no_work_pending:
  6. #if defined(CONFIG_IRQSOFF_TRACER)
  7. asm_trace_hardirqs_on
  8. #endif
  9. /* perform architecture specific actions before user return */
  10. arch_ret_to_user r1, lr    //针对arm,是dummy的
  11. restore_user_regs fast = 0, offset = 0//恢复之前用户模式时被中断时所保存的寄存器上下文
  12. ENDPROC(ret_to_user_from_irq)</span>
restore_user_regs展开如下: 
[cpp] view plain copy
  1. <span style="font-size:18px">   .macro  restore_user_regs, fast = 0, offset = 0
  2. ldr r1, [sp, #\offset + S_PSR]  @ get calling cpsr 即为被中断时,处理器的cpsr值
  3. ldr lr, [sp, #\offset + S_PC]!  @ get pc         即为被中断指令的,下一条指令
  4. msr spsr_cxsf, r1           @ save in spsr_svc  //将r1赋值给管理模式下的spsr_svc,这样在movs时,会自动将该值赋值为cpsr
  5. #if defined(CONFIG_CPU_V6)
  6. strex   r1, r2, [sp]            @ clear the exclusive monitor
  7. #elif defined(CONFIG_CPU_32v6K)
  8. clrex                   @ clear the exclusive monitor
  9. #endif
  10. .if \fast
  11. ldmdb   sp, {r1 - lr}^          @ get calling r1 - lr
  12. .else
  13. ldmdb   sp, {r0 - lr}^          @ get calling r0 - lr,将保存在内核栈中的r0到r14恢复到用户模式中的寄存器
  14. .endif
  15. mov r0, r0              @ ARMv5T and earlier require a nop
  16. @ after ldm {}^
  17. add sp, sp, #S_FRAME_SIZE - S_PC    //恢复内核栈到中断产生之前的位置。
  18. movs    pc, lr              @ return & move spsr_svc into cpsr
  19. .endm
  20. </span>

至此中断汇编部分已经全部处理完成。

最后摘录部门网上经典的问题解答:

问题1:vector_irq已经是异常、中断处理的入口函数了,为什么还要加stubs_offset?(  b    vector_irq + stubs_offset)

答:(1)内核刚启动时(head.S文件)通过设置CP15的c1寄存器已经确定了异常向量表的起始地址(例如0xffff0000),因此需要把已经写好的内核代码中的异常向量表考到0xffff0000处,只有这样在发生异常时内核才能正确的处理异常。

(2)从上面代码看出向量表和stubs(中断处理函数)都发生了搬移,如果还用b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。至于为什么搬移后的地址是vector_irq+stubs_offset,请参考我的上篇blog:linux中断系统那些事之----中断初始化过程

问题2:为什么在异常向量表中,用b指令跳转而不是用ldr绝对跳转?

答:因为使用b指令跳转比绝对跳转(ldr pc,XXXX)效率高,正因为效率高,所以把__stubs_start~__stubs_end之间的代码考到了0xffff0200起始处。

注意:

因为b跳转指令只能在+/-32MB之内跳转,所以必须拷贝到0xffff0000附近。

b指令是相对于当前PC的跳转,当汇编器看到 B 指令后会把要跳转的标签转化为相对于当前PC的偏移量写入指令码。

问题3:为什么首先进入head.S开始执行?

答:内核源代码顶层目录下的Makefile制定了vmlinux生成规则:

# vmlinux image - includingupdated kernel symbols

vmlinux: (vmlinux−lds)(vmlinux−lds)(vmlinux-init) (vmlinux−main)vmlinux.o(vmlinux−main)vmlinux.o(kallsyms.o)FORCE

其中$(vmlinux-lds)是编译连接脚本,对于ARM平台,就是arch/arm/kernel/vmlinux-lds文件。vmlinux-init也在顶层Makefile中定义:

vmlinux-init := (head−y)(head−y)(init-y)

head-y 在arch/arm/Makefile中定义:

head-y:=arch/arm/kernel/head$(MMUEX T).o arch/arm/kernel/init_task.o

ifeq ($(CONFIG_MMU),)

MMUEXT := -nommu

endif

对于有MMU的处理器,MMUEXT为空白字符串,所以arch/arm/kernel/head.O 是第一个连接的文件,而这个文件是由arch/arm/kernel/head.S编译产生成的。

综合以上分析,可以得出结论,非压缩ARM Linux内核的入口点在arch/arm/kernel/head.s中。

问题4: 中断为什么必须进入svc模式?

一个最重要原因是:

如果一个中断模式(例如从usr进入irq模式,在irq模式中)中重新允许了中断,并且在这个中断例程中使用了BL指令调用子程序,BL指令会自动将子程序返回地址保存到当前模式的sp(即r14_irq)中,这个地址随后会被在当前模式下产生的中断所破坏,因为产生中断时CPU会将当前模式的PC保存到r14_irq,这样就把刚刚保存的子程序返回地址冲掉。为了避免这种情况,中断例程应该切换到SVC或者系统模式,这样的话,BL指令可以使用r14_svc来保存子程序的返回地址。

问题5:为什么跳转表中有的用了b指令跳转,而有的用了ldr  px,xxxx?

         W(b)         vector_und+ stubs_offset

         W(ldr)      pc, .LCvswi + stubs_offset

         W(b)         vector_pabt+ stubs_offset

         W(b)         vector_dabt+ stubs_offset

         W(b)         vector_addrexcptn+ stubs_offset

         W(b)         vector_irq+ stubs_offset      

         W(b)         vector_fiq+ stubs_offset

 

.LCvswi:

         .word       vector_swi

由于系统调用异常的代码编译在其他文件中,其入口地址与异常向量相隔较远,使用b指令无法跳转过去(b指令只能相对当前pc跳转32M范围)。因此将其地址存放到LCvswi中,并从内存地址中加载其入口地址,原理与其他调用是一样的。这也就是为什么系统调用的速度稍微慢一点的原因。

问题6:为什么ARM能处理中断?

因为ARM架构的CPU有一个机制,只要中断产生了,CPU就会根据中断类型自动跳转到某个特定的地址(即中断向量表中的某个地址)。如下表所示,既是中断向量表。

 ARM中断向量表及地址

问题7:什么是High vector?

A:在Linux3.1.0,arch/arm/include/asm/system.hline121 有定义如下:

#if __LINUX_ARM_ARCH__ >=4

#define vectors_high()  (cr_alignment & CR_V)

#else

#define vectors_high()  (0)

#endif

意思就是,如果使用的ARM架构大于等于4,则定义vectors_high()=cr_alignment&CR_V,该值就等于0xffff0000

在Linux3.1.0,arch/arm/include/asm/system.hline33有定义如下:

#define CR_V   (1 << 13)       /* Vectors relocated to 0xffff0000 */

 

arm下规定,在0x00000000或0xffff0000的地址处必须存放一张跳转表。

问题8:中断向量表是如何存放到0x00000000或0xffff0000地址的?

A:Uboot执行结束后会把Linux内核拷贝到内存中开始执行,linux内核执行的第一条指令是linux/arch/arm/kernel/head.S,此文件中执行一些参数设置等操作后跳入linux/init/main.c文件的start_kernel函数,此函数调用一系列初始化函数,其中trip_init()函数实现向量表的设定操作。

本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/5321556.html,如需转载请自行联系原作者

linux中断系统那些事之----中断处理过程【转】相关推荐

  1. 韦东山 IMX6ULL和正点原子_【预习】韦东山:剥丝抽茧分析Linux中断系统中的重要数据结构...

    导语: 众所周知,目前升级版视频正在录中断系统,已经录到[Linux系统对中断处理的演进],配套文档发布后,颇受学员好评,知乎文章: https://zhuanlan.zhihu.com/p/1130 ...

  2. Linux驱动开发学习笔记【8】:Linux中断系统

    目录 一.Linux内核中断处理过程 1.1.裸机中断 1.2.linux中断 二.linux中断的上半部和下半部 2.1 软中断 2.2 tasklet 2.3 工作队列 2.4 中断线程化 三.设 ...

  3. linux系统FW升降级步骤,[Fw]初探linux中断系统(2)

    中断系统初始化的过程 用来初始化中断系统的函数位于arch/x86/kernel/irqinit.c,定义如下 void __init init_IRQ(void) { int i; /* * On ...

  4. Linux中断一网打尽(2) - IDT及中断处理的实现

    女主宣言 通过阅读本文,您可以了解到:IDT是什么,它如何被初始化,什么是门,传统系统调用是如何实现的,以及硬件中断的实现. PS:丰富的一线技术.多元化的表现形式,尽在"360云计算&qu ...

  5. utm虚拟机安装linux,UTM: 在 iOS 上安装 Windows 或 Linux 等系统及虚机安装过程

    Apps & Tweaks|Jailbreak Guide|iDevices UTM Version: 2.0.20 Repo: https://getutm.app/ Support: 11 ...

  6. arm中断保护和恢复_ARM中断处理过程

    以s3c2440 ARM9核为例: 一:s3c2440 ARM处理器特性: 1.S3C2440支持60个中断源,含子中断源: 2.ARM9采用五级流水线方式: 3.支持外部中断和内部中断: 二.s3c ...

  7. linux 中断 c语言程序,linux驱动之中断处理过程C程序部分

    当发生中断之后,linux系统在汇编阶段经过一系列跳转,最终跳转到asm_do_IRQ()函数,开始C程序阶段的处理.在汇编阶段,程序已经计算出发生中断的中断号irq,这个关键参数最终传递给asm_d ...

  8. Linux中断一网打尽(1) — 中断及其初始化

    女主宣言 通过本文您可以了解到:Linux 中断是什么,如何分类,能干什么?Linux 中断在计算机启动各阶段是如何初始化的? PS:丰富的一线技术.多元化的表现形式,尽在"360云计算&q ...

  9. linux 中断服务程序,request_irq() linux注册中断服务

    在 2.4 内核和 2.6内核中都使用 request_irq() 函数来注册中断服务函数.在 2.4 内核中,需要包含的头文件是 #include ,2.6 内核中需要包含的头文件则是 #inclu ...

最新文章

  1. Oracle的基本操作(一:子查询与常用函数)
  2. Java堆栈功能_【ThinkingInJava】35、用java实现堆栈功能
  3. jQuery 入门教程(1): 概述
  4. 全球及中国汽车卡钳行业产销需求现状与投资策略分析报告2022年版
  5. split函数python 未定义_Python之Split函数
  6. 免费赠送年终汇报总结模板福利合集
  7. EfficientDet解读
  8. kettle 查询数据库写入文件_怎么连接数据库-详解如何用kettle连接mysql数据库并导出sql查询结果...
  9. How to use price determination in Quotation scenario
  10. boost::function的用法(二)
  11. 数组算法 中部删除数据 1202
  12. 图片浏览器每次只能打开一张图片_好嗨哟!微信图片批量提取下载,我只用了3秒钟~...
  13. 解读Tom介绍的Oracle Database 12c的12个新特性
  14. 【动态规划】牛客网:礼物的最大价值(数塔问题)
  15. slice 和splice 的区别 js
  16. Docker详解(三)——Docker安装与部署
  17. 4006.Cacti集监控IBM刀片服务器温度和风扇运行状态
  18. 淘晶驰串口屏下载工程慢怎么办
  19. phpmyadmin linux 升级,phpMyAdmin 4.9.2发布下载,附主要更新内容介绍
  20. 20个网上赚钱你要知道的网站!

热门文章

  1. oracle10 exp imp 中文乱码
  2. lvs直接路由模式简单部署
  3. 逃离伪PHP程序员应该做的事
  4. Silverlight测试——利用Ranorex实现数据驱动测试
  5. windows连接linux系统telnet端口23无法连接的问题
  6. ORACLE分区表删除分区数据
  7. (转)IntelliJ Idea 的相关使用
  8. Springboot 整合 Dubbo/ZooKeeper 详解 SOA 案例
  9. zabbix企业应用之固定端口监控redis
  10. Nginx 从入门到放弃(二)