很久以来对于中断处理没有系统化理解和分析,总是有点好似明白感觉,现在进行从头整理一番。
1.中断处理过程总体介绍
中断处理过程,非常简单
1).中断准备(计算中断向量,保存寄存器),
2). 中断处理
3). 中断处理完成返回
代码如下:
addl $-0x80,(%esp)
SAVE_ALL
call do_IRQ
jmp ret_from_intr
2.中断处理点详解
1).保存中断环境
当系统运行过程中, 发生中断,中断处理程序需要保存进程上下文寄存器值。原因是这些内容属入运行中进程,当中断处理完成后,某个时间点进程继续运行时,需要恢复保存进程上下文寄存器值。
x86架构保存寄存器代码:SAVE_ALL
x86架构恢复寄存器代码:RESTORE_REGS 4
2).中断处理与嵌套
中断嵌套是指中断系统正在执行一个中断服务时,有另一个优先级更高的中断提出中断请求,这时会暂时终止当前正在执行的级别较低的中断源的服务程序,去处理级别更高的中断源,待处理完毕,再返回到被中断了的中断服务程序继续执行,这个过程就是中断嵌套。
对于Linux不支持中断嵌套,个人观点是对于不同类型中断是可以嵌套的,而对于同种类型的中断,是不可以嵌套执行的。 具体分析如下:
中断屏蔽分为中断控制器屏蔽中断和cpu屏蔽中断,两者区别如下:
1. 中断控制器屏蔽中断,中断将丢失
x86架构Linux系统:对于同种类型的中断,是不可以嵌套执行的,通过中断控制器屏蔽中断。
代码如下:
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
mask_ack_irq(desc, irq);
action_ret = handle_IRQ_event(irq, action);
desc->chip->unmask(irq);
}
2.cpu屏蔽中断,中断将被记录,并存放在请求寄存器irr中,cpu开启中断后,中断将响应。
对于x86架构,当发生中断时,进入中断门后,处理器会自动清除IF 标志,禁掉(屏蔽)中断, 不响应任何中断。
对于Linux系统来说, 对于标志不为IRQF_DISABLED中断, 系统进入中断处理函数之前,设置IF 标志,响应中断。可见关闭全部中断是否一直执行下去,取决中断设备类型,影响中断响应程度,从而影响系统交互性。
当中断处理程序完成后,再次禁掉(屏蔽)中断,准备中断返回
具体代码如下:
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();
do {
trace_irq_handler_entry(irq, action);
ret = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, ret);
retval |= ret;
action = action->next;
} while (action);
local_irq_disable();
return retval;
}
3).中断上下文下半部分
对于系统不支持中断上下睡眠,主要原因影响系统的实时性,降低系统吞吐量。Linux系统把中断划分为上下半部分,对于下半部分代码要退后执行的,下半部分关键地方在于允许响应所有中断,目的也是为了提供系统响应及时性。此外自行触发的中断,本次不会执行,本次返回前不会执行,等待下次中断返回时候执行。
下半部分退后执行时机之一,代码如下:
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
preempt_enable_no_resched();
}
asmlinkage void __do_softirq(void)
{
local_irq_enable(); //响应所有中断
h = softirq_vec;
do {
if (pending & 1) {
h->action(h);
rcu_bh_qs(cpu);
}
h++;
pending >>= 1;
} while (pending);
local_irq_disable();//屏蔽所有中断响应
_local_bh_enable();
}
4).中断返回
中断返回其实一句代码iret, 但是中断处理返回前,在关闭中断上下情况下,已经发生需要事情,在返回之前需要对其做些处理, 究竟要做那些事情呢?需要根据发生中断时上下文环境,在内核态还是用户态来进行返回处理。
如果内核态并且不启用内核抢占,恢复保存寄存器,返回继续执行中断内核代码。
如果内核态并且启用抢占,判断是否允许抢占(计数是否为0),如果允许抢占并且有需要调度进程,执行内核抢占下进程调度,处理完成后,返回继续执行中断内核代码。
如果是用户态发生中断,没有要出来事情,恢复保存寄存器,返回继续执行中断用户态代码。
如果是用户态发生中断,并且在中断处理过程中有需要处理信号,有需要调度进程, 那么先去处理处理这些事情,处理完成后,恢复保存寄存器,返回继续执行中断用户态代码。
代码如下:
ret_from_intr:
GET_THREAD_INFO(%ebp)
check_userspace:
movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CS
movb PT_CS(%esp), %al
andl $(X86_EFLAGS_VM | SEGMENT_RPL_MASK), %eax
cmpl $USER_RPL, %eax
jb resume_kernel # not returning to v8086 or userspace
ENTRY(resume_userspace)
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work to be done on
# int/exception return?
jne work_pending
jmp restore_all
work_pending:
testb $_TIF_NEED_RESCHED, %cl
jz work_notifysig
work_resched:
call schedule
LOCKDEP_SYS_EXIT
DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt
# setting need_resched or sigpending
# between sampling and the iret
TRACE_IRQS_OFF
movl TI_flags(%ebp), %ecx
andl $_TIF_WORK_MASK, %ecx # is there any work to be done other
# than syscall tracing?
jz restore_all
testb $_TIF_NEED_RESCHED, %cl
jnz work_resched
work_notifysig: # deal with pending signals and
# notify-resume requests
#ifdef CONFIG_VM86
testl $X86_EFLAGS_VM, PT_EFLAGS(%esp)
movl %esp, %eax
jne work_notifysig_v86 # returning to kernel-space or
# vm86-space
xorl %edx, %edx
call do_notify_resume
jmp resume_userspace_sig
ALIGN
work_notifysig_v86:
pushl %ecx # save ti_flags for do_notify_resume
CFI_ADJUST_CFA_OFFSET 4
call save_v86_state # %eax contains pt_regs pointer
popl %ecx
CFI_ADJUST_CFA_OFFSET -4
movl %eax, %esp
#else
movl %esp, %eax
#endif
xorl %edx, %edx
call do_notify_resume
jmp resume_userspace_sig
END(work_pending)
#ifdef CONFIG_PREEMPT
ENTRY(resume_kernel)
DISABLE_INTERRUPTS(CLBR_ANY)
cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ?
jnz restore_all
need_resched:
movl TI_flags(%ebp), %ecx # need_resched set ?
testb $_TIF_NEED_RESCHED, %cl
jz restore_all
testl $X86_EFLAGS_IF,PT_EFLAGS(%esp) # interrupts off (exception path) ?
jz restore_all
call preempt_schedule_irq
jmp need_resched
END(resume_kernel)
#endif
restore_all:
TRACE_IRQS_IRET
restore_all_notrace:
movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS
# Warning: PT_OLDSS(%esp) contains the wrong/random values if we
# are returning to the kernel.
# See comments in process.c:copy_thread() for details.
movb PT_OLDSS(%esp), %ah
movb PT_CS(%esp), %al
andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax
cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax
CFI_REMEMBER_STATE
je ldt_ss # returning to user-space with LDT SS
restore_nocheck:
RESTORE_REGS 4 # skip orig_eax/error_code
CFI_ADJUST_CFA_OFFSET -4
irq_return:
INTERRUPT_RETURN