在Linux内核的配置文件.config文件中可以知道Linux内核使用ARM架构CPU的异常向量基址为0xFFFF0000。
1.内核对中断的处理流程
arch/arm/kernel/entry-armv.S
__vectors_start:@异常向量
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
异常向量表,无非还是一些跳转指令。当发生irq中断,执行指令“b vector_irq + stubs_offset”,也就是跳转到vector_irq代码段继续执行。
阅读该文件时,注意到每一个异常处理时开头都有一段相似的代码,比如:
vector_stub irq, IRQ_MODE, 4
vector_stub dabt, ABT_MODE, 8
vector_stub pabt, ABT_MODE, 4
vector_stub是一段宏,展开来则是:
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction @计算返回地址
.endif
@ 保存现场
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@ 转换到管理模式
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@ 跳转指令,根据跳转表实现
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm
跳转表(以中断为例):
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
总结: 在linux内核初始化阶段,start_kernel函数(init/main.c)会调用trap_init、init_IRQ两个函数来初始化异常向量相关处理函数。并构造异常向量表__vectors_start,当发生中断时跳转至vector_irp(用宏来实现),在里面实现保存现场、计算返回地址、转换至管理模式、调用处理函数:以用户模式下的中断为例
__irq_usr:
usr_entry @将usr模式下的寄存器、中断返回地址保存到堆栈中 //保存现场
get_thread_info tsk @获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk等于r9
irq_handler @中断处理 //宏
mov why, #0
b ret_to_user @中断处理完成,返回中断产生的位置
irq_handler是一个宏,展开来是:
.macro irq_handler
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
adrne lr, 1b
bne asm_do_IRQ
.endm
进入asm_do_IRQ函数开始具体的中断处理。需要指出的是,asm_do_IRQ是中断的C语言总入口函数。
2.asm_do_IRQ函数
smlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
开始执行之后,
struct irq_desc *desc = irq_desc + irq;
从名字上可以知道,irq_desc是一个中断描述数组,以中断号为下标,
之后进入处理函数:
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
该函数所做的是分辨是哪一个中断、调用对应的处理函数、清除中断标志位
搜索handle_irq,发现handle_irq被__set_irq_handler调用过
搜索__set_irq_handler,发现__set_irq_handler被set_irq_handler调用过
搜索set_irq_handler,发现__set_irq_handler被s3c24xx_init_irq(void)调用过
中断处理 C 函数入口 “asm_do_IRQ”会调用到事先初始化好的“handle_irq”函数。
handle_irq == handle_edge_irq,将执行:
①desc->chip->ack(irq):清除中断
②handle_IRQ_event:取出action链表中的成员,执行action->handler
3.request_irq( )函数
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
用于注册用户编写的中断处理函数,里面将执行:
①分配irqaction结构
②setup_irq(irq,action):
在irq_desc(irq)->action链表里加入传入的irq、action,
在desc->chip->settype里将引脚设置为中断引脚
在desc->chip->sturtup/enable里使能中断
4.free_irq( )函数
void free_irq(unsigned int irq, void *dev_id)
用于卸载用户编写的中断服务程序。会执行出链、除能中断
写完驱动代码之后,使用命令exec /5</dev/buttons打开中断驱动即可,就是打开这个设备,定位到5处
想关闭则使用命令exec 5<&-
5.休眠与唤醒
使用休眠功能,可以将程序的CPU占有率降低至2%,大大提高了性能。
在中断服务函数里面:
ev_press = 1; /* 有按键按下 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
在驱动的read函数里面:
wait_event_interruptible(button_waitq, ev_press);
/* 返回数据给内核 */
copy_to_user(buf, &key_val, sizeof(key_val));
ev_press = 0;
按键按下后,进入中断服务函数执行一系列操作并唤醒休眠的进程,执行完毕之后程序将从休眠处继续执行。