linux内核学习10.1:Linux内核ARM7架构异常中断向量表

参考:https://www.cnblogs.com/douzi2/p/5112743.html

当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断处理程序处执行。在异常中断处理程序执行完成后,程序返回到发生中断的指令的下一条指令处执行。

说明

Kernel版本:4.14.111
ARM处理器,Contex-A7
在《ARM体系结构与编程》第9章中说到,ARM 中有个概念叫做“异常中断”,也就是包括外部中断在内的各种异常。显然,ARM体系的“异常中断”概念更加接近MIPS体系中的“异常”概念。
在这里插入图片描述

思考

  1. 首先中断向量是什么,中断向量表又是什么.
  2. ARM中异常中断的种类
  3. 向量中断和非向量中断
  4. 内核在哪里定义中断向量表
  5. 异常来时,中断跳转过程
  6. 为什么异常中断向量表必须设置在从0地址开始
  7. FIQ相比IRQ,FIQ称为快速中断,IRQ称为普通中断。FIQ为什么比IRQ快

1. 首先中断向量是什么,中断向量表又是什么

  • 中断向量
    中断服务程序的入口地址。在某些计算机中,中断向量的位置存放一条跳转到中断服务程序入口地址的跳转指令。
    来存放中断向量(共256个),称这一片内存区为中断向量表,地址范围是0~3FFH

  • 中断向量地址:
    存储中断向量的存储单元地址

  • 中断向量:
    中断向量的集合,按中断类型号从小到大的顺序存放到存储器的某一区域内,这个存放中断向量的存储区叫做中断向量表,即中断服务程序入口地址表。

CPU是根据中断号获取中断向量值,即对应中断服务程序的入口地址值。因此为了让CPU由中断号查找到对应的中断向量,就需要在内存中建立一张查询表

2. ARM中异常中断的种类

说明
Kernel版本:4.14.111
ARM处理器,Contex-A7
在《ARM体系结构与编程》第9章中说到,ARM 中有个概念叫做“异常中断”,也就是包括外部中断在内的各种异常。显然,ARM体系的“异常中断”概念更加接近MIPS体系中的“异常”概念。
在这里插入图片描述
ARM异常中断向量表
在这里插入图片描述
ARM的异常中断向量表可以是高端向量表,也可以是低端向量表,两者取其一。区别是基地址不同。高端向量是ARM架构可选配置,可以通过硬件外部输入管脚来配置是低端向量还是高端向量,不能通过指令来改变向量的位置,但如果ARM芯片内部有标准ARM协处理器,那么协处理器CP15的寄存器C1的bit13可以用来切换低端和高端向量地址,等于0时为低端向量,等于1时为高端向量。
  Linux内核分用户空间、内核空间,通常32位处理器,用户空间0-3G,内核空间3-4G,所以Linux内核使用高端向量表。

ARM的一个非常重要的寄存器——CPSR程序状态寄存器
cpsr这个寄存器用来设定进入哪种模式

3. 向量中断和非向量中断

向量中断 非向量中断
    向量者,矢量也,即指方向,门路。
    向量中断------由硬件提供中断服务程序入口地址;
    非向量中断------由软件件提供中断服务程序入口地址;

推荐向量中断就是不同的中断有不同的入口地址,非向量中断就只有一个入口地址,进去了再判断中断标志来识别具体是哪个中断。向量中断实时性好,非向量中断简单

  • 向量中断模式用于RESET、NMI(非屏蔽中断)、异常处理。当向量中断产生时,控制器直接将PC赋值,如跳到0x0000000d处,而在0x0000000d地址处通常放置ISR服务程序地址LDR PC, =ISR_HANDLER。——代码都是现成的
  • 非向量中断模式,有一个寄存器标识位,跳转到统一的函数地址,此函数通过判别寄存器标识位和优先级关系进行中断处理。向量中断模式是当CPU读取位于0x18处的IRQ中断指令的时候,系统自动读取对应于该中断源确定地址上的指令取代0x18处的指令,通过跳转指令系统就直接跳转到对应地址函数中,节省了中断处理时间提高了中断处理速度。例如 ADC 中断的向量地址为0xC0,则在0xC0处放如下代码:ldr PC,=HandlerADC 当ADC中断产生的时候系统会自动跳转到HandlerADC函数中处理中断。

对于ARM,ARM没有NMI,外部中断走的是非向量中断,即走注册的中断服务例程ISP,这个是驱动工程师自己注册的request_irq的irq_handle

但是无论是非向量中断还是向量中断,都会走到__vectors_start,然后根据中断类型走到对于的分类,例如IRQ就是走到了__vectors_start的vendor_irq,然后最终走到了irq_desc(中断例程描述符表),里面就定义了irqaction,irqaction链表指向了irq_handler_t中断服务程序,即reques_irq注册的中断服务程序

而对于其他类型的异常,如RESET,硬件已经做好了现成的处理code,根据向量表直接到达代码的地址,然后执行。区别于非向量的IRQ是统一的地址,进入这个地址后,再由中断例程描述符表区分中断

4. 内核在哪里定义中断向量表

在vmlinux.lds.S描述了__vectors_start的起始位置,从0xffff0000开始

	/*
	 * The vectors and stubs are relocatable code, and the
	 * only thing that matters is their relative offsets
	 */
	__vectors_start = .;
	.vectors 0xffff0000 : AT(__vectors_start) {			(1)
		*(.vectors)
	}
	. = __vectors_start + SIZEOF(.vectors);
	__vectors_end = .;

	__stubs_start = .;
	.stubs ADDR(.vectors) + 0x1000 : AT(__stubs_start) {
		*(.stubs)										(2)
	}
	. = __stubs_start + SIZEOF(.stubs);
	__stubs_end = .;

1)链接时,将.vectors段内容链接到虚拟地址0xffff0000地址。(这里我理解为在vmlinux镜像中.vectors段连续,夹在__vectors_start和__vectors_end 中间,但是链接的虚拟地址指向0xffff0000)
2)同上。
  arch/arm/kernel/entry-armv.S 中.vectors段保存了异常向量表。

	.section .vectors, "ax", %progbits
.L__vectors_start:
	W(b)	vector_rst
	W(b)	vector_und
	W(ldr)	pc, .L__vectors_start + 0x1000
	W(b)	vector_pabt
	W(b)	vector_dabt
	W(b)	vector_addrexcptn
	W(b)	vector_irq
	W(b)	vector_fiq

(软件中的向量表和硬件中的offset定义的一致性)
在这里插入图片描述
上面是中断向量表,而具体的向量是在vector_stub中,例如,vector_irq

/*
 * Interrupt dispatcher
 */
    vector_stub irq, IRQ_MODE, 4   
 
     .long   __irq_usr               @  0  (USR_26 / USR_32)
    .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
    .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
    .long   __irq_svc               @  3  (SVC_26 / SVC_32)
    .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

5. 异常来时,中断跳转过程

参考:http://blog.chinaunix.net/uid-29045944-id-3968667.html

根据异常向量表,有异常的时候就可以跳转了;但是跳到哪里呢?

上面说到,当有IRQ中断(我们在7种异常中断类型举IRQ为例)时,CPU根据中断向量表进入vector_irq,vector_irq的描述在vector_stub中,假如IRQ中断时在用户usr模式,则进入
__irq_usr,__irq_usr

__irq_usr:
	usr_entry    @保存中断上下文
	kuser_cmpxchg_check
	irq_handler  @调用中断处理程序
	get_thread_info tsk @获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk(r9)(在entry-header.S中定义)
	mov	why, #0
	b	ret_to_user_from_irq @中断处理完成,恢复中断上下文并返回中断产生的位置
 UNWIND(.fnend		)
ENDPROC(__irq_usr)

如果发生中断前处于核心态则进入__irq_svc,其定义如下

__irq_svc:
	svc_entry  @保存中断上下文
	irq_handler  @调用中断处理程序

#ifdef CONFIG_PREEMPT
	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  @如果不等于0,说明发生内核抢占,需要重新调度。
#endif

	svc_exit r5, irq = 1			@恢复中断上下文
 UNWIND(.fnend		)
ENDPROC(__irq_svc)

上面代码中的usr_entry和svc_entry是一个宏定义,主要用于保护中断上下文到栈中
保存中断上下文后则进入中断处理程序——irq_handler,定义在arch/arm/kernel/entry_armv.S文件中:

#ifdef CONFIG_MULTI_IRQ_HANDLER
	ldr	r1, =handle_arch_irq
	mov	r0, sp
	badr	lr, 9997f
	ldr	pc, [r1]
#else
	arch_irq_handler_default
#endif

这里32位走arch_irq_handler_default,64位走handle_arch_irq

(1 )arch_irq_handler_default:

arch_irq_handler_default ——>asm_do_IRQ()—>handle_IRQ()—>__handle_domain_irq()—>generic_handle_irq()—>调用用户request_irq注册的中断处理函数

(2)handle_arch_irq :
handle_arch_irq---->set_handle_irq---->gic_handle_irq---->handle_domain_irq()---->__handle_domain_irq()---->generic_handle_irq()---->generic_handle_irq_desc()

2种最终都调用到了desc->handle_irq(desc)

(kernel/include/linux/irqdesc.h)
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc);  // 这里真正调用到,用户request_irq注册的中断处理函数
}

generic_handle_irq_desc调用中断描述符的handle_irq回调函数。对于不同的中断类型,handle_irq回调函数可能是handle_simple_irq、handle_level_irq、handle_fasteoi_irq、handle_edge_irq、handle_edge_eoi_irq、handle_percpu_irq。

假如我们用reques_irq注册的是gpio电平触发中断,那么这里的desc->handle_irq就是handle_level_irq。(省略转换流程)

最终,handle_level_irq----> handle_irq_event(desc)-----> handle_irq_event_percpu------> res = action->handler(irq, action->dev_id); /* 调用action->handler,即request_irq 时注册的handler 函数 */

这里我们关注struct irq_desc,称为中断描述符,该数据结构保存了关于所有IRQ的中断描述符信息

struct irq_desc {
         irq_flow_handler_t       handle_irq;  //指向中断函数, 中断产生后,就会执行这个handle_irq
         struct irq_chip   *chip; //指向irq_chip结构体,用于底层的硬件访问,下面会介绍
         struct msi_desc             *msi_desc; 
         void                     *handler_data;  
         void                     *chip_data;
         struct irqaction     *action;      /* IRQ action list */   //action链表,用于中断处理函数
         unsigned int                  status;                  /* IRQ status */
         unsigned int                  depth;                  /* nested irq disables */
         unsigned int                  wake_depth;        /* nested wake enables */
         unsigned int                  irq_count;   /* For detecting broken IRQs */
         unsigned int                  irqs_unhandled;
         spinlock_t            lock;          
     ... ...
         const char            *name;              //产生中断的硬件名字
} ;

其中的成员*chip的结构体,用于底层的硬件访问, irq_chip类型如下:

struct irq_chip {
         const char   *name;
         unsigned int    (*startup)(unsigned int irq);       //启动中断 
         void            (*shutdown)(unsigned int irq);      //关闭中断
         void            (*enable)(unsigned int irq);         //使能中断
         void            (*disable)(unsigned int irq);        //禁止中断
         void            (*ack)(unsigned int irq);       //响应中断,就是清除当前中断使得可以再接收下个中断
         void            (*mask)(unsigned int irq);     //屏蔽中断源 
         void            (*mask_ack)(unsigned int irq);  //屏蔽和响应中断
         void            (*unmask)(unsigned int irq);   //开启中断源
         ... ...
     int              (*set_type)(unsigned int irq, unsigned int flow_type);  //将对应的引脚设置为中断类型的引脚
     ... ...
#ifdef CONFIG_IRQ_RELEASE_METHOD
         void            (*release)(unsigned int irq, void *dev_id);       //释放中断服务函数
#endif

};

其中的成员struct irqaction *action,主要是用来存用户注册的中断处理函数,
一个中断可以有多个处理函数 ,当一个中断有多个处理函数,说明这个是共享中断.
所谓共享中断就是一个中断的来源有很多,这些来源共享同一个引脚。
所以在irq_desc结构体中的action成员是个链表,以action为表头,若是一个以上的链表就是共享中断
irqaction结构定义如下:

struct irqaction {
         irq_handler_t handler;      //等于用户注册的中断处理函数,中断发生时就会运行这个中断处理函数
         unsigned long flags;         //中断标志,注册时设置,比如上升沿中断,下降沿中断等
         cpumask_t mask;           //中断掩码
         const char *name;          //中断名称,产生中断的硬件的名字
         void *dev_id;              //设备id
         struct irqaction *next;        //指向下一个成员
         int irq;                    //中断号,
         struct proc_dir_entry *dir;    //指向IRQn相关的/proc/irq/

};

上面3个结构体的关系如下图所示:
在这里插入图片描述

7. 为什么异常中断向量表必须设置在从0地址开始

由硬件电路决定,一般固定设置异常中断向量表在0地址开始,即复位异常触发的地址

8. FIQ相比IRQ,FIQ称为快速中断,IRQ称为普通中断。FIQ为什么比IRQ快

  1. 寄存器处理速度快。FIQ比IRQ有更多的banked寄存器,可以自动保存和恢复值。如果你FIQ中断处理程序足够用这几个独立的寄存器来运作,它就不会进行通用寄存器的压栈,这样也省了一些时间。
  2. FIQ比IRQ有更高的优先级,如果FIQ和IRQ同时产生,那么FIQ先处理。
  3. FIQ比IRQ至少少了一条跳转指令。FIQ的1C以后没有任何中断向量表了,这样可以直接在1C处放FIQ的中断处理程序,而IRQ的18只能放一条指令,然后跳转
  4. FIQ的响应比IRQ更快
  • 0
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值