/*首先声明下,这是本人阅读韦东山老师后的一些学习记录和笔记*/
1. 对异常概念的理解
异常就是可以打断CPU正常运行的事件,比如,外部中断、未定义的指令、软中断等。当这些异常发生时,就打断CPU的正常运行,跳到相应的异常处理程序去处理这些异常要求的一些操作。
2. Linux内核中断处理流程
基于Linux-2.6.29.4
1、Linux内核中异常向量表的拷贝
/*分析代码*/
内核启动时将异常向量表从物理地址0x00000000拷贝到0xffff0000的虚拟地址中去;
其中异常向量在arch/arm/kernel/entry-armv.S中定义
中断向量表
.globl __vectors_start
__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
.globl __vectors_end
__vectors_end:
注:内核启动时调用start_kernel()函数,在start_kernel()中又调用了大量的函数。
/*在init/main.c中*/
asmlinkagevoid __init start_kernel(void)
{
.....................................
/*
*Interrupts are still disabled. Do necessary setups, then
-
enablethem
-
调用setup_arch(),在该函数中调用了early_trap_init()
*/
setup_arch(&command_line);
mm_init_owner(&init_mm,&init_task);
setup_command_line(command_line);
.....................
sched_init();
.....................
init_IRQ();
/*
中断的初始化(init_IRQ函数),完成相关体系结构的设置
*/
/*Do the rest non-__init'ed, we're now alive */
rest_init();
}
函数setup_arch()在arch/arm/kernel/setup.c中
void__init setup_arch(char **cmdline_p)
{
.........................
memcpy(boot_command_line,from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1]= '\0';
parse_cmdline(cmdline_p,from);
paging_init(mdesc);
request_standard_resources(&meminfo,mdesc);
.............................
...............................
early_trap_init();
/*
就是early_trap_init()完成了异常向量表的拷贝
而early_trap_init()在arch/arm/kernel/trap.c中定义
,代码如下:
*/
}
void__init early_trap_init(void)
{
........................................
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void*)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void*)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void*)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
...............................
/*
其中vectors为0xffff0000,而__vectors_start~__vectors_end之间的代码就是向量表
__stubs_start~__stubs_end为重新定位跳转位置
*/
}
2、Linux内核中中断处理流程
首先,内核启动时完成中断初始化框架的流程
在start_kernel()中调用init_IRQ(),该函数在arch/arm/kernel/irq.c中被定义,被用来初始化中断的处理框架,设置各种中断的默认处理函数。当发生中断时,中断总入口函数asm_do_IRQ()就可以调用这些函数作进一步处理。
void__init init_IRQ(void)
{
intirq;
for(irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status|= IRQ_NOREQUEST | IRQ_NOPROBE;
#ifdefCONFIG_SMP
bad_irq_desc.affinity= CPU_MASK_ALL;
bad_irq_desc.cpu= smp_processor_id();
#endif
/*用来处理相关硬件体系结构的中断初始化函数*/
init_arch_irq();
}
其中init_arch_irq()被定义为
void(*init_arch_irq)(void) __initdata = NULL;
它的作用如下,
void__init setup_arch(char **cmdline_p)
{
structmachine_desc *mdesc;
。。。。。。。。。。
setup_processor();
mdesc= setup_machine(machine_arch_type);
machine_name= mdesc->name;
memcpy(boot_command_line,from, COMMAND_LINE_SIZE);
boot_command_line[COMMAND_LINE_SIZE-1]= '\0';
parse_cmdline(cmdline_p,from);
paging_init(mdesc);
request_standard_resources(&meminfo,mdesc);
cpu_init();
/*
* Set up various architecture-specific pointers
*/
init_arch_irq= mdesc->init_irq;
system_timer= mdesc->timer;
init_machine= mdesc->init_machine;
early_trap_init();
}
/*
machine_desc结构体在arch/arm/include/asm/mach/arch.h定义
*/
structmachine_desc {
/*
* Note! The first four elements are used
* by assembler code in head.S, head-common.S
*/
unsignedint nr; /* architecture number */
unsignedint phys_io; /* start of physical io */
unsignedint io_pg_offst; /* byte offset for io
* page tabe entry */
constchar *name; /* architecture name */
unsignedlong boot_params; /* tagged list */
unsignedint video_start; /* start of video RAM */
unsignedint video_end; /* end of video RAM */
unsignedint reserve_lp0 :1; /* never has lp0 */
unsignedint reserve_lp1 :1; /* never has lp1 */
unsignedint reserve_lp2 :1; /* never has lp2 */
unsignedint soft_reboot :1; /* soft reboot */
void (*fixup)(structmachine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/*IO mapping function */
void (*init_irq)(void);
structsys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
/*setup_machine的定义*/
static structmachine_desc * __init setup_machine(unsignedint nr)
{
structmachine_desc *list;
/*
* locate machine in the list of supported machines.
*/
list= lookup_machine_type(nr);
if(!list) {
printk("Machineconfiguration botched (nr %d), unable "
"to continue.\n", nr);
while(1);
}
printk("Machine:%s\n", list->name);
returnlist;
}
/*lookup_machine_type在arch/arm/kernel/head-common.S中定义*/
ENTRY(lookup_machine_type)
stmfd sp!,{r4 - r6, lr}
mov r1,r0
bl __lookup_machine_type
mov r0,r5
ldmfd sp!,{r4 - r6, pc}
ENDPROC(lookup_machine_type)
/*
*Lookupmachine architecture in the linker-build list of architectures.
*Note that we can't use the absolute addresses for the __arch_info
*lists since we aren't running with the MMU on (and therefore, we are
*not in the correct address space). We have to calculate the offset.
*
* r1 = machine architecture number
*Returns:
* r3, r4, r6 corrupted
* r5 = mach_info pointer in physical address space
*/
__lookup_machine_type:
adr r3,3b
ldmia r3,{r4, r5, r6}
sub r3,r3, r4 @ get offset between virt&phys
add r5,r5, r3 @ convert virt addresses to
add r6,r6, r3 @ physical address space
1: ldr r3,[r5, #MACHINFO_TYPE] @ get machine type
teq r3,r1 @ matches loader number?
beq 2f @found
add r5,r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5,r6
blo 1b
mov r5,#0 @ unknown machine
2: mov pc,lr
ENDPROC(__lookup_machine_type)
接下来是内核完成启动后响应中断的流程
当CPU被中断时,强制跳转到异常中断向量表中的
b vector_fiq+ stubs_offset
如vector_irq这些是宏,定义在 vector_stub irq,IRQ_MODE, 4
vector_stub irq,IRQ_MODE, 4
这两句对应
.macro vector_stub,irq,IRQ_MODE,correction=4
.align 5
vector_irq:
.if4
sub lr,lr, #4 计算返回地址
.endif
@
@Save r0, lr_<exception> (parent PC) and spsr_<exception>
@(parent CPSR)
@保存被中断时CPU的现场
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继续执行跳转
ENDPROC(vector_irq)
.endm
执行完上述语句后,跳转到
vector_stub irq,IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
如果是用户态发生中断时,跳转到__irq_usr地址去执行
.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
/*
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
*/
kuser_cmpxchg_check
#ifdefCONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
get_thread_infotsk
#ifdefCONFIG_PREEMPT
ldr r8,[tsk, #TI_PREEMPT] @ get preempt count
add r7,r8, #1 @ increment it
str r7,[tsk, #TI_PREEMPT]
#endif
/*
irq_handler
#ifdefCONFIG_PREEMPT
ldr r0,[tsk, #TI_PREEMPT]
str r8,[tsk, #TI_PREEMPT]
teq r0,r7
strne r0,[r0, -r0]
#endif
#ifdefCONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif
mov why,#0
b ret_to_user
ENDPROC(__irq_usr)
.ltorg
.align 5
irq_handler就是中断的处理函数
/*
*Interrupt handling. Preserves r7, r8, r9
*/
.macro irq_handler
get_irqnr_preambler5, lr
1: get_irqnr_and_baser0, r6, r5, lr
movne r1,sp
@
@routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr,1b
bne asm_do_IRQ
/*
最终调用asm_do_irq()这个中断处理函数
*/
***位于irq.c
asmlinkagevoid __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
structpt_regs *old_regs = set_irq_regs(regs);
irq_enter();
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if(irq >= NR_IRQS)
handle_bad_irq(irq,&bad_irq_desc);
else
generic_handle_irq(irq);
/*AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
irq_desc定义在include/linux/irq.h中
structirq_desc {
unsignedint irq;
#ifdefCONFIG_SPARSE_IRQ
structtimer_rand_state *timer_rand_state;
unsignedint *kstat_irqs;
#ifdef CONFIG_INTR_REMAP
structirq_2_iommu *irq_2_iommu;
#endif
#endif
irq_flow_handler_t handle_irq;
structirq_chip *chip;
structmsi_desc *msi_desc;
void *handler_data;
void *chip_data;
structirqaction *action; /* IRQ action list */
unsignedint status; /* IRQ status */
unsignedint depth; /* nested irq disables */
unsignedint wake_depth; /* nested wake enables */
unsignedint irq_count; /* For detecting broken IRQs */
unsignedlong last_unhandled; /* Aging timer for unhandled count */
unsignedint irqs_unhandled;
spinlock_t lock;
#ifdefCONFIG_SMP
cpumask_t affinity;
unsignedint cpu;
#endif
#ifdefCONFIG_GENERIC_PENDING_IRQ
cpumask_t pending_mask;
#endif
#ifdefCONFIG_PROC_FS
structproc_dir_entry *dir;
#endif
constchar *name;
}
定义在include/linux/irq.h
staticinline void generic_handle_irq(unsigned int irq)
{
generic_handle_irq_desc(irq,irq_to_desc(irq));
}
staticinline void generic_handle_irq_desc(unsigned int irq, struct irq_desc*desc)
{
#ifdefCONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
desc->handle_irq(irq,desc);
#else
if(likely(desc->handle_irq))
desc->handle_irq(irq,desc);
else
__do_IRQ(irq);
#endif
}
最终处理的函数为handle_irq,为用户自定义。。。