中断系统的运行过程分析:
--------------------------------------------------------------------------------------------------
硬件:
--------------------------------------------------------------------------------------------------
1. 按下/松开按键
2. CPU检测到中断信号,立刻/强制跳到中断向量入口
arch\arm\kernel\entry-armv.S
b vector_irq + stubs_offset
软件:
--------------------------------------------------------------------------------------------------
1. 保存被中断的现场(保存相关寄存器)
arch\arm\kernel\entry-armv.S
vector_stub irq, IRQ_MODE, 4 //name=irq mode=IRQ_MODE correction=4
/*这段为内核中的源码,用上面的东西替换后,如后面的代码所示*/
.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_irq:
sub lr, lr, #4 /*根据arm9的5级流水线*/
@
@ 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
/*接着执行下面的*/
.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
假设是在用户态下发生了中断,执行__irq_usr
__irq_usr:
usr_entry // 保存被中断的现场
.macro usr_entry //这里是个宏,下面的代码主要工作就是保存现场
sub sp, sp, #S_FRAME_SIZE
stmib sp, {r1 - r12}
ldmia r0, {r1 - r3}
add r0, sp, #S_PC @ here for interlock avoidance
mov r4, #-1 @ "" "" "" ""
str r1, [sp] @ save the "real" r0 copied
@ from the exception stack
#if __LINUX_ARM_ARCH__ < 6 && !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)
#ifndef CONFIG_MMU
#warning "NPTL on non MMU needs fixing"
#else
@ make sure our user space atomic helper is aborted
cmp r2, #TASK_SIZE
bichs r3, r3, #PSR_Z_BIT
#endif
#endif
@
@ We are now ready to fill in the remaining blanks on the stack:
@
@ r2 - lr_<exception>, already fixed up for correct return/restart
@ r3 - spsr_<exception>
@ r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
@ Also, separately save sp_usr and lr_usr
@
stmia r0, {r2 - r4}
stmdb r0, {sp, lr}^
@
@ Enable the alignment trap while in kernel mode
@
alignment_trap r0
@
@ Clear FP to mark the first stack frame
@
zero_fp
.endm
/*上面保存完现场后,接着执行下面的代码*/
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
get_thread_info tsk
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler
#ifdef CONFIG_PREEMPT
ldr r0, [tsk, #TI_PREEMPT]
str r8, [tsk, #TI_PREEMPT]
teq r0, r7
strne r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif
mov why, #0
b ret_to_user
/*2. 接下来就是确定中断源*/
irq_handler
.macro irq_handler
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b
bne asm_do_IRQ //中断处理程序的总入口函数,传入中断号,
/*3. 确定好中断源后,接着调用对应的处理函数:*/
bne asm_do_IRQ //到这里就会进入到C语言部分,根据中断号去判断是相应的哪个数组
在asm_do_IRQ里面会执行下面的代码:
struct irq_desc *desc = irq_desc + irq; // irq_desc[irq]
desc_handle_irq(irq, desc);
desc->handle_irq(irq, desc); /* handle_edge_irq或handle_level_irq调用相应中断的中断处理入口函数,这个入口函数在初始化的时 候被指定了,如下面中断处理体系结构的初始化的过所示程分析*/
下面进入handle_edge_irq函数去看看!!!!!
--------------------------------------------------------------------------------------------------
handle_edge_irq
// 统计中断发生的次数
kstat_cpu(cpu).irqs[irq]++;
// 响应中断,比如清除中断
desc->chip->ack(irq);
// 处理:
action_ret = handle_IRQ_event(irq, action);
do {
ret = action->handler(irq, action->dev_id); /*到此真正的调用到你自己注册的中断服务程序,如buttons_irq*/
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;
} while (action);
注意:由于有些是共享中断,如:IRQ_EINT8t23, 中断8-23是共享一个中断线的,对于这种情况,上面调用的只是他们的公共的中断服务程序,在中断服务程序中还用进一步的判断具体是哪个中断,最终调用到具体的中断服务程序。
set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
desc->handle_irq = handle; // irq_desc[IRQ_EINT8t23].handle_irq = s3c_irq_demux_extint8 // 确定更具体的中断号
作用:重新计算终端号,进一步处理
进入s3c_irq_demux_extint8函数里面看看:
s3c_irq_demux_extint8
irq += (IRQ_EINT4 - 4); /*重新计算中断号*/
desc_handle_irq(irq, irq_desc + irq); /*调用到具体的中断服务程序*/
desc->handle_irq(irq, desc);
// 统计中断发生的次数
kstat_cpu(cpu).irqs[irq]++;
// 响应中断,比如清除中断
desc->chip->ack(irq);
// 处理:
action_ret = handle_IRQ_event(irq, action);
do{
ret = action->handler(irq, action->dev_id); /*到此共享中断也调用到了具体的中断服务程序了*/
action = action->next;
} while (action);
中断注册过程分析:
--------------------------------------------------------------------------------------------------
注册方法,内核给我们提供了以个注册中断的接口request_irq,下面开始分析:
int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
// 分配/设置irqaction
struct irqaction *action;
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
// 设置它
/*设置具体的中断服务程序,这个函数使我们驱动程序中要具体实现的*/
action->handler = handler;
action->flags = irqflags; /*中断标志*/
cpus_clear(action->mask);
action->name = devname;
action->next = NULL;
action->dev_id = dev_id; /*中断ID,这个有时候在驱动的中断服务程序中会用到,根据中断ID判断是哪个中断发生了*/
/*将我们辛辛苦苦构建的irqaction结构体链接进入rq_desc[irq]数组的action链表中。等待以后被调用*/
retval = setup_irq(irq, action);
free_irq:
desc = irq_desc + irq;
// 根据dev_id在链表里找到irqaction、
for (;;) {
struct irqaction **pp = p;
p = &action->next;
if (action->dev_id != dev_id)
continue;
...............................................
// 脱链
*pp = action->next;
// 如果链表空了:desc->chip->shutdown(irq);
if (!desc->action) {
desc->status |= IRQ_DISABLED;
if (desc->chip->shutdown)
desc->chip->shutdown(irq);
else
desc->chip->disable(irq);
}
...............................................
// 释放
kfree(action);
}
中断处理体系结构的初始化的过程分析:
--------------------------------------------------------------------------------------------------
由下面的内核启动过程可以看出,中断是在内核被初始化的。
在start_kernel里面调用了init_IRQ
start_kernel:
init_IRQ
init_IRQ在内核中的具体源码如下所示:
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
#ifdef CONFIG_SMP
bad_irq_desc.affinity = CPU_MASK_ALL;
bad_irq_desc.cpu = smp_processor_id();
#endif
init_arch_irq();
}
init_arch_irq()调用架构相关的中断初始化函数,对于S3C2410,S3C2440开发板,
这个函数就是S3C24XX_init_irq,此函数设置了处理函数入口(irq_desc[irq].handle_irq)
它还为所有的中断设置了芯片相关的数据机构(irq_desc[irq].chip)
arch\arm\plat-s3c24xx\irq.c
void __init s3c24xx_init_irq(void)
// IRQ_EINT4t7/IRQ_EINT8t23表示“一类中断”, linux内核首先确定发生的中断是哪个或哪类
// 设置某类中断的"处理函数(不是最终的处理函数,它细分/调用)": irq_desc[IRQ_EINT4t7].handle_irq = s3c_irq_demux_extint4t7;
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
// 设置某类中断的"处理函数(不是最终的处理函数,它细分/调用)": irq_desc[IRQ_EINT8t23].handle_irq = s3c_irq_demux_extint8;
set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
//下面是设置具体中断的handle_irq
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++)
{
irqdbf("registering irq %d (ext int)\n", irqno);
set_irq_chip(irqno, &s3c_irq_eint0t4);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++)
{
irqdbf("registering irq %d (extended s3c irq)\n", irqno);
set_irq_chip(irqno, &s3c_irqext_chip);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++)
{
irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart1);
set_irq_handler(irqno, handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++)
{
irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart2);
set_irq_handler(irqno, handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++)
{
irqdbf("registering irq %d (s3c adc irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_adc);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
内核的启动过程分析:
--------------------------------------------------------------------------------------------------
1. arch/arm/kernel/head.S
head.s中有这样一段代码:这个是内核的总入口
ENTRY(stext)
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
bl __create_page_tables
/*
* The following calls CPU specific code in a position independent
* manner. See arch/arm/mm/proc-*.S for details. r10 = base of
* xxx_proc_info structure selected by __lookup_machine_type
* above. On return, the CPU will be ready for the MMU to be
* turned on, and r0 will hold the CPU control register value.
*/
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
add pc, r10, #PROCINFO_INITFUNC
对上面一段代码的分析:
// 判断该内核是否能支持这个CPU
bl __lookup_processor_type @ r5=procinfo r9=cpuid
// 判断该内核能否支持这个单板(machine)
bl __lookup_machine_type @ r5=machinfo
// 创建页表
bl __create_page_tables
// 调用CPU相关的处理函数
add pc, r10, #PROCINFO_INITFUNC
// 上面的函数执行完后,跳到lr去(lr被设为"adr lr, __enable_mmu")
// 使能MMU
// 使能完MMU后,跳到r13去(r13被设为__mmap_switched)
b start_kernel
下面我们去看看start_kernel:
start_kernel:
// 解析bootloader传入的参数
setup_arch(&command_line);
//初始化中断,这个对上面的中断系统的运行过程分析非常有用。
init_IRQ();
接下来:
// 调用一系列的驱动程序
// 链接时,把一系列的驱动程序的函数放在一起
// 然后从头执行到尾
rest_init > kernel_init > do_basic_setup > do_initcalls
for (call = __initcall_start; call < __initcall_end; call++) {
................................
result = (*call)();
................................
}
extern initcall_t __initcall_start[], __initcall_end[];
__initcall_start = .;
.initcall.init : {
INITCALLS
}
__initcall_end = .;
#define INITCALLS \
*(.initcall0.init) \
*(.initcall0s.init) \
*(.initcall1.init) \
*(.initcall1s.init) \
*(.initcall2.init) \
*(.initcall2s.init) \
*(.initcall3.init) \
*(.initcall3s.init) \
*(.initcall4.init) \
*(.initcall4s.init) \
*(.initcall5.init) \
*(.initcall5s.init) \
*(.initcallrootfs.init) \
*(.initcall6.init) \
*(.initcall6s.init) \
*(.initcall7.init) \
*(.initcall7s.init)