[ARM&Linux]Linux下中断处理的上下文保存与切换的一些细节
2013-12-08 17:02
721 查看
我们这里讨论ARM体系下的Linux中断处理的上下文切换部分的细节。我们只讨论底层汇编处理细节,不考虑上层。
首先,Linux的中断处理程序是经过搬移的,向量表和处理程序距离很近,这是在系统初始化的时候
就完成的。这个地方我们不做深入讨论。
Linux的向量表通过MMU安排在0xffff0000的位置,向量表如下:
其中stubs_offset定义为__vectors_start + 0x200 - __stubs_start。
这个__vectors_start就是刚才的这个向量表,而__stubs_start为中断处理程序的开始地址。
向量表在trap_init函数中建立,该函数在start_kernel中较后的位置被建立。
---------------------- --- __kuser_helper_end
/ \
| |
| | --- __kuser_helper_start -
| | |
------------------------ --- __stubs_end 0xe00
|
| |
|
| --- __stubs_start -
|
| ^
|
| |
------------------------ --- __vectos_end 0x200
|
| |
|
|
vector 32字节 |
|
| |
\
/ |
---------------------- --- __vectors_start -
整个__stubs的代码如下:
对于上面的代码我们有以下几个结论:
1. Linux不支持FIQ,因为FIQ这东西是ARM特有的,为了保证代码的通用性,我们不应该使用这些特有的玩意儿。
不过上面并不是理由,最大的理由是,采用现成Linux中断处理框架的话,没法保证寄存器不被corrupt。
2. 对于中断/异常前的状态,Linux是分类处理的。如果位于内核态(svc模式下)那么执行__xxx_svc的代码。
如果位于用户态(usr模式下)那么执行__xxx_usr的代码。
我们下面来看vector_stub这个宏,gcc的宏相当强大:
上面的代码会调用__xxx_usr或__xxx_svc。为了简单起见,我们只看irq的处理。
并且假设启用了CONFIG_PREEMPT,未启用TRACE_IRQFLAGS。
我们先看__irq_usr:
注意一点,现在状态位SVC,中断未开启。
Linux的特色就是大量使用宏,这点我很讨厌。
首先是usr_entry这个宏:
经过这个宏,我们在SVC中开辟了一个空间来保存pt_regs,然后我们保存了r0到r12,现在r0指向了pt_regs中的PC,r4为-1,r2为
中断返回地址lr,r3为中断前的cpsr,r11为0。
回到__irq_usr,接下来的宏是get_thread_info tsk。
这个宏也定义在entry_head.S中。我们看看:
再次回到__irq_usr。
现在我们的情况是r7保存了当前preempt_count的值,我们知道当这个值为0的时候将发生重调度。
我们接着进入irq_handler这个宏。
依旧由很多宏构成,我们先分析从get_irq_nr_preamble r5,lr到bne asm_do_IRQ
get_irq_nr_preamble定义在和具体体系相关的文件中,一般具体体系都有个entry-macro.S
以我们内核和2440的SOC为例,这里get_irq_nr_preamble为空。
get_irqnr_and_base宏用于获得中断num,保存在r0中。
后面adrne lr,1b会将1标号的地址保存在lr中。之后调用asm_do_IRQ函数。
参数有点意思,现在r0为中断号,r1为SVC的堆栈,也就是指向struct pt_regs的r0。
asm_do_IRQ这个函数定义在体系相关的kernel/irq.c中。我们不讨论这个函数的实现,我们仅需要知道这个
函数用来处理中断。函数返回时会退到标号1处,在标号1处再次判断有没有中断产生,如果没有就退出,有的话
就继续刚才的过程。
如果是SMP平台的话,还要检查核间中断,核间中断的处理是由do_IPI实现的。
LOCAL_TIMER也是SMP平台定义的,这个我们都不用关系,我们只关心中断处理的底层过程。
下面回到__irq_usr中:
上面代码并不奇怪,最后直接调用ret_to_user,我们看看这个函数的实现:
我们一路追踪到了work_pending,我们进入这个函数看看。
这里work_resched用于上下文切换,而do_notify_resume用于实现signal。
work_resched函数直接调用schedule函数进行上下文切换。
我们这边只关心ret_slow_syscall,这个函数其实就是ret_to_user。
继续测试是否需要重调度,如果不需要的话,函数继续执行下去
首先又来了一个arch_ret_to_user的宏。这个宏是SOC相关的,比如我们用的2440这个宏就是
个空的。
至此__irq_usr就全部分析完毕,我们接下来分析__irq_svc。__irq_svc运行在SVC模式下,当前r0
为irq模式下的堆栈,堆栈情况如下:
cpsr_svc@被中断前的cpsr
lr@中断返回地址
r0@中断前的r0
此处我们仍考虑配置了内核抢占,__irq_svc全貌如下,我们分几个部分来研究:
1. svc_entry
2. get_thread_info tsk 到 irq_handler
3. irq_handler 到 svc_prempt
4. preempt_return
我们先来研究第一部分,svc_entry这个宏作用类似于usr_entry
svc_entry操作完成后,pt_regs已经保存了中断前的所有数据用于返回中断时使用。
下面我们来研究第二部分。
这一部分代码和usr_entry完全一样。下面我们来研究第三部分
我们来看看svc_preempt,这个场景是内核态任务抢占。
这一块代码都非常清晰,我们直接回到了preempt_return。
首先,Linux的中断处理程序是经过搬移的,向量表和处理程序距离很近,这是在系统初始化的时候
就完成的。这个地方我们不做深入讨论。
Linux的向量表通过MMU安排在0xffff0000的位置,向量表如下:
.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
其中stubs_offset定义为__vectors_start + 0x200 - __stubs_start。
这个__vectors_start就是刚才的这个向量表,而__stubs_start为中断处理程序的开始地址。
向量表在trap_init函数中建立,该函数在start_kernel中较后的位置被建立。
---------------------- --- __kuser_helper_end
/ \
| |
| | --- __kuser_helper_start -
| | |
------------------------ --- __stubs_end 0xe00
|
| |
|
| --- __stubs_start -
|
| ^
|
| |
------------------------ --- __vectos_end 0x200
|
| |
|
|
vector 32字节 |
|
| |
\
/ |
---------------------- --- __vectors_start -
整个__stubs的代码如下:
.globl __stubs_start __stubs_start: /* 这些都是宏,IRQ_MODE等所有宏都在ptrace.h当中 */ 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 /* 这些都是宏 */ vector_stub dabt, ABT_MODE, 8 .long __dabt_usr @ 0 (USR_26 / USR_32) .long __dabt_invalid @ 1 (FIQ_26 / FIQ_32) .long __dabt_invalid @ 2 (IRQ_26 / IRQ_32) .long __dabt_svc @ 3 (SVC_26 / SVC_32) .long __dabt_invalid @ 4 .long __dabt_invalid @ 5 .long __dabt_invalid @ 6 .long __dabt_invalid @ 7 .long __dabt_invalid @ 8 .long __dabt_invalid @ 9 .long __dabt_invalid @ a .long __dabt_invalid @ b .long __dabt_invalid @ c .long __dabt_invalid @ d .long __dabt_invalid @ e .long __dabt_invalid @ f /* 这些都是宏 */ vector_stub pabt, ABT_MODE, 4 .long __pabt_usr @ 0 (USR_26 / USR_32) .long __pabt_invalid @ 1 (FIQ_26 / FIQ_32) .long __pabt_invalid @ 2 (IRQ_26 / IRQ_32) .long __pabt_svc @ 3 (SVC_26 / SVC_32) .long __pabt_invalid @ 4 .long __pabt_invalid @ 5 .long __pabt_invalid @ 6 .long __pabt_invalid @ 7 .long __pabt_invalid @ 8 .long __pabt_invalid @ 9 .long __pabt_invalid @ a .long __pabt_invalid @ b .long __pabt_invalid @ c .long __pabt_invalid @ d .long __pabt_invalid @ e .long __pabt_invalid @ f /* 这些都是宏 */ vector_stub und, UND_MODE .long __und_usr @ 0 (USR_26 / USR_32) .long __und_invalid @ 1 (FIQ_26 / FIQ_32) .long __und_invalid @ 2 (IRQ_26 / IRQ_32) .long __und_svc @ 3 (SVC_26 / SVC_32) .long __und_invalid @ 4 .long __und_invalid @ 5 .long __und_invalid @ 6 .long __und_invalid @ 7 .long __und_invalid @ 8 .long __und_invalid @ 9 .long __und_invalid @ a .long __und_invalid @ b .long __und_invalid @ c .long __und_invalid @ d .long __und_invalid @ e .long __und_invalid @ f .align 5 /* Linux不支持FIQ */ vector_fiq: disable_fiq subs pc, lr, #4 /* 这个实际上是v7引入的hypervisor入口 */ vector_addrexcptn: b vector_addrexcptn .align 5 .LCvswi: .word vector_swi .globl __stubs_end __stubs_end:
对于上面的代码我们有以下几个结论:
1. Linux不支持FIQ,因为FIQ这东西是ARM特有的,为了保证代码的通用性,我们不应该使用这些特有的玩意儿。
不过上面并不是理由,最大的理由是,采用现成Linux中断处理框架的话,没法保证寄存器不被corrupt。
2. 对于中断/异常前的状态,Linux是分类处理的。如果位于内核态(svc模式下)那么执行__xxx_svc的代码。
如果位于用户态(usr模式下)那么执行__xxx_usr的代码。
我们下面来看vector_stub这个宏,gcc的宏相当强大:
.macro vector_stub, name, mode, correction=0 .align 5 vector_\name: /* 如果需要修正返回地址,那么修正吧 */ .if \correction sub lr, lr, #\correction .endif /* 保存r0,lr,spsr,但是sp不变,sp始终指向了r0 */ stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, [sp, #8] @ save spsr /* 将SVC模式的CPSR保存到SPSR中 */ mrs r0, cpsr eor r0, r0, #(\mode ^ SVC_MODE) msr spsr_cxsf, r0 /* 有趣的代码,lr保存的是进入中断之前的模式,根据这个模式分别让lr为__xxx_usr和__xxx_svc */ and lr, lr, #0x0f mov r0, sp ldr lr, [pc, lr, lsl #2] /* 跳转的同时将spsr写入cpsr */ movs pc, lr .endm
上面的代码会调用__xxx_usr或__xxx_svc。为了简单起见,我们只看irq的处理。
并且假设启用了CONFIG_PREEMPT,未启用TRACE_IRQFLAGS。
我们先看__irq_usr:
注意一点,现在状态位SVC,中断未开启。
.align 5 __irq_usr: usr_entry get_thread_info tsk ldr r8, [tsk, #TI_PREEMPT] add r7, r8, #1 str r7, [tsk, #TI_PREEMPT] irq_handler ldr r0, [tsk, #TI_PREEMPT] str r8, [tsk, #TI_PREEMPT] teq r0, r7 strne r0, [r0, -r0] mov why, #0 b ret_to_user .ltorg
Linux的特色就是大量使用宏,这点我很讨厌。
首先是usr_entry这个宏:
.macro usr_entry /* 定义在asm-offsets.c中,也就是struct pt_regs的大小18*4字节 */ sub sp, sp, #S_FRAME_SIZE /* 先保存r1-r12,r0保存的是IRQ状态下的堆栈指针,这个堆栈中从高到低保存的是被中断前的cpsr,中断返回地址lr,中断前的r0寄存器 */ stmib sp, {r1 - r12} /* r1 = 中断前的r0,r2 = 中断返回地址lr,r3 = 中断前的cpsr */ ldmia r0, {r1 - r3} /* PC在struct pt_regs(定义在ptrace.h中)中的偏移,应该是0x3c */ add r0, sp, #S_PC mov r4, #-1 /* 保存中断前的r0 */ str r1, [sp] /* 中断返回地址赋给PC这个空,中断前CPSR赋给CPSR这个空,中断前r0为-1,r0继续指向PC */ stmia r0, {r2 - r4} /* 将当前的sp和lr保存给lr和sp这两个空 */ stmdb r0, {sp, lr}^ /* 定义在entry_header.S中,如果没定义CONFIG_ALIGNMENT_TRAP,这边都为空,一般就是读取LCcralign的值,然后写给CP15的系统控制寄存器 */ alignment_trap r0 /* r11/fp = 0 */ zero_fp .endm
经过这个宏,我们在SVC中开辟了一个空间来保存pt_regs,然后我们保存了r0到r12,现在r0指向了pt_regs中的PC,r4为-1,r2为
中断返回地址lr,r3为中断前的cpsr,r11为0。
回到__irq_usr,接下来的宏是get_thread_info tsk。
这个宏也定义在entry_head.S中。我们看看:
tsk .req r9 @ current thread_info .macro get_thread_info, rd /* 清低13位,也就是对齐到8K,我们知道SVC状态下所有任务的thread_info都保存在连续8K内存的开头 */ mov \rd, sp, lsr #13 mov \rd, \rd, lsl #13 .endm
再次回到__irq_usr。
/* 读取thread_info中的preempt_count变量 */ ldr r8, [tsk, #TI_PREEMPT] /* preempt_count加1 */ add r7, r8, #1 str r7, [tsk, #TI_PREEMPT] /* 进入irq处理函数 */ irq_handler ...
现在我们的情况是r7保存了当前preempt_count的值,我们知道当这个值为0的时候将发生重调度。
我们接着进入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 test_for_ipi r0, r6, r5, lr movne r0, sp adrne lr, 1b bne do_IPI test_for_ltirq r0, r6, r5, lr movne r0, sp adrne lr, 1b bne do_local_timer .endm
依旧由很多宏构成,我们先分析从get_irq_nr_preamble r5,lr到bne asm_do_IRQ
get_irq_nr_preamble定义在和具体体系相关的文件中,一般具体体系都有个entry-macro.S
以我们内核和2440的SOC为例,这里get_irq_nr_preamble为空。
get_irqnr_and_base宏用于获得中断num,保存在r0中。
后面adrne lr,1b会将1标号的地址保存在lr中。之后调用asm_do_IRQ函数。
参数有点意思,现在r0为中断号,r1为SVC的堆栈,也就是指向struct pt_regs的r0。
asm_do_IRQ这个函数定义在体系相关的kernel/irq.c中。我们不讨论这个函数的实现,我们仅需要知道这个
函数用来处理中断。函数返回时会退到标号1处,在标号1处再次判断有没有中断产生,如果没有就退出,有的话
就继续刚才的过程。
如果是SMP平台的话,还要检查核间中断,核间中断的处理是由do_IPI实现的。
LOCAL_TIMER也是SMP平台定义的,这个我们都不用关系,我们只关心中断处理的底层过程。
下面回到__irq_usr中:
/* 再次读取preempt_count信息 */ ldr r0, [tsk, #TI_PREEMPT] /* aapcs-linux保证r8不会被改动,r8最早为进入中断前的preempt_count */ str r8, [tsk, #TI_PREEMPT] /* r7也不会发生改动,r7最早为r8+1 */ /* 如果r0 == r7,说明中断处理函数没有变动preempt_count */ teq r0, r7 /* 如果r0 != r7,说明中断处理函数中变动了preempt_count */ strne r0, [r0, -r0] /* why == r8 */ mov why, #0 b ret_to_user .ltorg
上面代码并不奇怪,最后直接调用ret_to_user,我们看看这个函数的实现:
ENTRY(ret_to_user) ret_slow_syscall: /* 如果中断开的话,关掉中断,有些体系压根不用实现这个,因为本身Linux自己都不是中断可嵌套的 */ disable_irq /* 获得thread_info的flags成员 */ ldr r1, [tsk, #TI_FLAGS] /* 是否有需要处理的,有的话跳到work_pending */ tst r1, #_TIF_WORK_MASK bne work_pending ...
我们一路追踪到了work_pending,我们进入这个函数看看。
work_pending: /* 判断是否要重调度 */ tst r1, #_TIF_NEED_RESCHED /* work_resched用于重调度 */ bne work_resched /* 这两个标志前面那个_TIF_NOTIFY_RESUME用来实现一个notification的机制,这个机制可以 让内核在从内核态切换到和用户态的时候执行一些操作,后者用于实现signal */ tst r1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING /* 没有设置的话直接回到前面的函数中 */ beq no_work_pending /* why == r8 */ mov r0, sp @ 'regs' mov r2, why @ 'syscall' /* 运行notification */ bl do_notify_resume /* 准备返回 */ b ret_slow_syscall @ Check work again
这里work_resched用于上下文切换,而do_notify_resume用于实现signal。
work_resched函数直接调用schedule函数进行上下文切换。
我们这边只关心ret_slow_syscall,这个函数其实就是ret_to_user。
继续测试是否需要重调度,如果不需要的话,函数继续执行下去
no_work_pending: arch_ret_to_user r1, lr /* 从上面一路下来,我们的堆栈还是平衡的,现在sp指向的是struct pt_regs的r0 */ ldr r1, [sp, #S_PSR] @ get calling cpsr /* sp 指向PC */ ldr lr, [sp, #S_PC]! @ get pc msr spsr_cxsf, r1 @ save in spsr_svc /* 全部恢复,注意只有当寄存器列表包括PC时,才将spsr赋值给cpsr,这里标识有^表示,该 指令将把r0-lr加载到用户/系统模式下的r0-lr中去。这个^不能再usr和sys模式下使用! */ ldmdb sp, {r0 - lr}^ @ get calling r1 - lr mov r0, r0 /* 堆栈恢复平衡 */ add sp, sp, #S_FRAME_SIZE - S_PC movs pc, lr @ return & move spsr_svc into cpsr
首先又来了一个arch_ret_to_user的宏。这个宏是SOC相关的,比如我们用的2440这个宏就是
个空的。
至此__irq_usr就全部分析完毕,我们接下来分析__irq_svc。__irq_svc运行在SVC模式下,当前r0
为irq模式下的堆栈,堆栈情况如下:
cpsr_svc@被中断前的cpsr
lr@中断返回地址
r0@中断前的r0
此处我们仍考虑配置了内核抢占,__irq_svc全貌如下,我们分几个部分来研究:
1. svc_entry
2. get_thread_info tsk 到 irq_handler
3. irq_handler 到 svc_prempt
4. preempt_return
.align 5 __irq_svc: svc_entry get_thread_info tsk ldr r8, [tsk, #TI_PREEMPT] @ get preempt count add r7, r8, #1 @ increment it str r7, [tsk, #TI_PREEMPT] irq_handler ldr r0, [tsk, #TI_FLAGS] @ get flags tst r0, #_TIF_NEED_RESCHED blne svc_preempt preempt_return: ldr r0, [tsk, #TI_PREEMPT] @ read preempt value str r8, [tsk, #TI_PREEMPT] @ restore preempt count teq r0, r7 strne r0, [r0, -r0] @ bug() ldr r0, [sp, #S_PSR] @ irqs are already disabled msr spsr_cxsf, r0 ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr .ltorg
我们先来研究第一部分,svc_entry这个宏作用类似于usr_entry
.macro svc_entry /* 定义在asm-offsets.c中,也就是struct pt_regs的大小18*4字节 */ sub sp, sp, #S_FRAME_SIZE /* 先保存r1-r12 */ stmib sp, {r1 - r12} /* r0为irq_sp,现在r1为中断前的r0,r2为中断返回地址,r3为中断前的cpsr */ ldmia r0, {r1 - r3} /* r5指向SP这个SLOT,具体见struct pt_regs */ add r5, sp, #S_SP mov r4, #-1 /* r0指向SVC中断前的栈 */ add r0, sp, #S_FRAME_SIZE /* 将中断前的r0保存到r0这个SLOT */ str r1, [sp] /* 保存中断前的lr */ mov r1, lr /* 分别保存-1,CPSR到CPSR,中断返回地址到PC,中断前lr到LR,中断前堆栈到sp */ stmia r5, {r0 - r4} .endm
svc_entry操作完成后,pt_regs已经保存了中断前的所有数据用于返回中断时使用。
下面我们来研究第二部分。
/* 这个没啥好说的,前面说过了,tsk为r9 */ get_thread_info tsk /* 读取当前任务抢占计数 */ ldr r8, [tsk, #TI_PREEMPT] @ get preempt count /* 抢占计数+1 */ add r7, r8, #1 @ increment it /* 保存进去,进行irq_handler */ str r7, [tsk, #TI_PREEMPT] irq_handler
这一部分代码和usr_entry完全一样。下面我们来研究第三部分
/* 读取是否需要重调度标志 */ ldr r0, [tsk, #TI_FLAGS] @ get flags /* 测试是否需要重调度 */ tst r0, #_TIF_NEED_RESCHED /* 如果需要则跳转到svc_preempt函数 */ blne svc_preempt
我们来看看svc_preempt,这个场景是内核态任务抢占。
svc_preempt: /* 中断前抢占计数是否为0 */ teq r8, #0 @ was preempt count = 0 /* irq_stat是一个irq_cpustat_t结构体,LCirq_stat保存了这个结构体的地址 */ ldreq r6, .LCirq_stat /* 非0不进行内核抢占 */ movne pc, lr @ no ldr r0, [r6, #4] @ local_irq_count ldr r1, [r6, #8] @ local_bh_count /* local_irq_count和local_bh_count都为0 */ adds r0, r0, r1 /* 不为0的话不进行上下文切换 */ movne pc, lr /* 将抢占计数清位0 */ mov r7, #0 @ preempt_schedule_irq str r7, [tsk, #TI_PREEMPT] @ expects preempt_count == 0 /* 调用preempt_schedule_irq */ 1: bl preempt_schedule_irq @ irq en/disable is done inside /* 切换到另一个任务 */ ldr r0, [tsk, #TI_FLAGS] @ get new tasks TI_FLAGS /* 测试这个任务是否需要重调度 */ tst r0, #_TIF_NEED_RESCHED /* 不需要就直接preempt_return */ beq preempt_return @ go again /* 否则继续任务切换 */ b 1b
这一块代码都非常清晰,我们直接回到了preempt_return。
preempt_return: /* 再次读抢占计数 */ ldr r0, [tsk, #TI_PREEMPT] @ read preempt value /* 还原最早的抢占计数 */ str r8, [tsk, #TI_PREEMPT] @ restore preempt count /* 抢占计数变化了么? */ teq r0, r7 /* 变化了就奇怪了 */ strne r0, [r0, -r0] @ bug() /* 读取中断前的CPSR */ ldr r0, [sp, #S_PSR] @ irqs are already disabled /* 保存到spsr */ msr spsr_cxsf, r0 /* 恢复所有寄存器,返回中断前的状态 */ ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
相关文章推荐
- linux for arm的中断处理流程[转载自:http://hi.baidu.com/wudx05/blog/item/5314935c834f4e41fbf2c0dc.html]
- 中断处理程序、中断上下文中处理延时及一些函数的调用规则(调IIC中断驱动有感)
- Linux中的中断处理 上下文
- 中断处理程序、中断上下文中处理延时及一些函数的调用规则
- arm-Linux中断处理体系结构与处理流程分析
- ARM Linux中断机制之中断的初始化
- Linux中断 - ARM中断处理过程
- 软中断网卡处理&Linux高性能外部设备处理机制
- ARM Linux中断机制之中断的初始化
- ARM BL或中断返回需要注意的一些细节问题
- ARM处理器各个模式之间是如何切换的?ARM各个模式之间切换时,上下文的保存哪些是硬件在做?哪些是操作系统在做?
- ARM Linux对中断的处理--相关数据结构
- 【ARM&Linux】按键中断驱动程序设计
- Linux的中断处理的一些说明
- 软中断网卡处理&Linux高性能外部设备处理机制&SMP
- 中断处理程序、中断上下文中处理延时及一些函数的调用规则
- ARM Linux对中断的处理--中断处理
- ARM linux的中断处理过程
- Linux定义信号的一些细节处理
- linux 中断处理(基于linux-3.7.2 arm linux)