外部硬件中断与操作系统的联系或者叫结合点,就是那张IDT表(中断描述符表)。
在arch/x86/kernel/traps_32.c中定义的(这里仅讨论32位的X86架构的代码)
/*
* The IDT has to be page-aligned to simplify the Pentium
* F0 0F bug workaround.. We have a special link segment
* for this.
*/
struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, };
初始化pic(可编程中断控制器)的时候,要分别为pic的IRQ线(中断请求线)(8259A有15个引脚,APIC有24个IRQ引脚)指定中断号。
中断号的作用,就跟IDT表有关了,IDT表在内核中的形式是256个8字节的大数组,编号从0到255,对应中断号,IRQ0对应于32号中断,那么当IRQ有中断来的时候,通知cpu,然后cpu通过IDT表,找到第32项的8个字节,这8个字节里面有个部分是一个函数的地址,然后就执行这个函数。
内核初始化的时候,用LIDT命令载入这个IDT载入到IDTR中。
idt_descr:
.word IDT_ENTRIES*8-1 # idt contains 256 entries
.long idt_table
中断描述表的前32项都是intel保留的,用于内核自己内部用,从33开始,是外部中断用。对于外部中断的处理函数,是个函数数组interrupt[15],每个处理函数几乎都一样,区别就是每个函数最开始,压入的是中断号,比如说IRQ0的处理函数interrupt[0]函数,开始是pushl #FFFFFFFF,也就是0的反码,而IRQ1对应的处理函数的第一句代码是pushl #FFFFFFFE,然后就都是JMP common_interrupt,
包含common_interrupt的是汇编代码,在arch/x86/kernel/entry_32.S中,这个文件很重要,包含了以下的内容
/*
* entry.S contains the system-call and fault low-level handling routines.
* This also contains the timer-interrupt handler, as well as all interrupts
* and faults that can result in a task-switch.
*
* NOTE: This code handles signal-recognition, which happens every time
* after a timer-interrupt and after each system call
*/
common_interrupt:
SAVE_ALL
TRACE_IRQS_OFF
movl %esp,%eax//传参数,因为do_IRQ函数是fastcall的,即用寄存器穿参数
call do_IRQ
jmp ret_from_intr
common_interrupt的开始SAVE_ALL,然后call do_IRQ,进入了do_IRQ处理函数,参数中有个是压入的中断号,所以,在do_IRQ中就根据中断号的不同来处理中断。
内核为每个中断在内核中构造了一个结构体,用来修饰中断,这个结构体
struct irq_desc {
irq_flow_handler_t handle_irq;
struct irq_chip *chip;
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned int irqs_unhandled;
unsigned long last_unhandled; /* Aging timer for unhandled count */
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_t affinity;
unsigned int cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
} ____cacheline_internodealigned_in_smp;
在do_IRQ中,就是用中断号来获取其相应的irq_desc结构体的。
fastcall unsigned int do_IRQ(struct pt_regs *regs)
{
struct pt_regs *old_regs;
/* high bit used in ret_from_ code */
int irq = ~regs->orig_eax;//取中断号
struct irq_desc *desc = irq_desc + irq;//根据中断号找到对应的irq_desc结构体
if (unlikely((unsigned)irq >= NR_IRQS)) {
printk(KERN_EMERG "%s: cannot handle IRQ %d\n",
__FUNCTION__, irq);
BUG();
}
old_regs = set_irq_regs(regs);
irq_enter();//进入irq处理,关键就是设置preempt_count,关调度,关软中断
desc->handle_irq(irq, desc);//真正的处理中断过程
irq_exit();//离开irq处理,关键就是还原preempt_count,开调度,开软中断,然后执行softirq
set_irq_regs(old_regs);
return 1;
}
对于handle_irq函数,x86用的是handle_level_irq函数,比较简单,但是现在的趋势是使用generic_handle_irq,是个很复杂的函数,这里仅仅讨论handle_level_irq,又叫做水平触发的中断处理函数,当然还有边沿触发的中断处理函数。
/**
* handle_level_irq - Level type irq handler
* @irq: the interrupt number
* @desc: the interrupt description structure for this irq
*
* Level type interrupts are active as long as the hardware line has
* the active level. This may require to mask the interrupt and unmask
* it after the associated handler has acknowledged the device, so the
* interrupt line is back to inactive.
*/
void fastcall
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
unsigned int cpu = smp_processor_id();
struct irqaction *action;
irqreturn_t action_ret;
spin_lock(&desc->lock);//对irq_desc使用的自旋锁
mask_ack_irq(desc, irq);//屏蔽并且相应irq中断线
if (unlikely(desc->status & IRQ_INPROGRESS))
goto out_unlock;//如果当前的desc的状态时正在处理中,那么退出
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
kstat_cpu(cpu).irqs[irq]++;
/*
* If its disabled or no action available
* keep it masked and get out of here
*/
action = desc->action;
if (unlikely(!action || (desc->status & IRQ_DISABLED)))
goto out_unlock;
desc->status |= IRQ_INPROGRESS;//在处理前,将desc的状态为置位正在处理中
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action);//真正处理desc的函数
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
spin_lock(&desc->lock);
desc->status &= ~IRQ_INPROGRESS;//将正在处理状态位还原
if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
desc->chip->unmask(irq);//解开屏蔽的中断线
out_unlock:
spin_unlock(&desc->lock);
}
现在已经在中断上下文了,而且是关中断状态。
/**
* handle_IRQ_event - irq action chain handler
* @irq: the interrupt number
* @action: the interrupt action chain for this irq
*
* Handles the action chain of an irq event
*/
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
handle_dynamic_tick(action);
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();//如果允许的话,这里是sti开中断
do {//遍历desc的action的处理函数
ret = action->handler(irq, action->dev_id);
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;
} while (action);
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();上面可能开中断,到了这里,无论如何要cli再关中断一次
return retval;
}
所以,在中断处理过程中,是否开中断(允许中断嵌套),要看desc的aciton的flags标志位中的IRQF_DISABLE标志位是否未置位。
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();//软中断处理函数
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (!in_interrupt() && idle_cpu(smp_processor_id()) && !need_resched())
tick_nohz_stop_sched_tick();
#endif
preempt_enable_no_resched();//空函数
}
in_interrupt()函数,其实也是检察preempt_count是否为0,为0则处于非中断环境处理中,就可以执行软中断了,软中断执行的前提就是,必须允许抢断或者调度。
thread_info中的preempt_count变量貌似很有意思,涉及了很多事情。1,是否允许调度。2,是否允许软中断。3,是否允许bh的执行。
#define PREEMPT_OFFSET (1UL << PREEMPT_SHIFT)//抢占(调度)计数
#define SOFTIRQ_OFFSET (1UL << SOFTIRQ_SHIFT)//软中断计数
#define HARDIRQ_OFFSET (1UL << HARDIRQ_SHIFT)//硬中断计数
/*
* We put the hardirq and softirq counter into the preemption
* counter. The bitmask has the following meaning:
*
* - bits 0-7 are the preemption count (max preemption depth: 256)
* - bits 8-15 are the softirq count (max # of softirqs: 256)
*
* The hardirq count can be overridden per architecture, the default is:
*
* - bits 16-27 are the hardirq count (max # of hardirqs: 4096)
* - ( bit 28 is the PREEMPT_ACTIVE flag. )
*
* PREEMPT_MASK: 0x000000ff
* SOFTIRQ_MASK: 0x0000ff00
* HARDIRQ_MASK: 0x0fff0000
*/
这里可以看出,preempt_count的关键作用,就是对于调度和软中断的开关允许。
是否允许执行软中断,看的是in_interrupt(),
#define in_interrupt() (irq_count())
#define irq_count() (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK))
所以说,是否允许执行软中断,看的是当前是否有硬中断在执行,是否已经有软中断在执行,如果都没有,才会真正执行软中断。这里不涉及调度。
当从内核态返回到用户态的时候,要检查是否进行调度,而调度要看两个条件:
1.preempt_count是否为0
2.rescheduled是否置位
ret_from_exception:
preempt_stop(CLBR_ANY)
ret_from_intr:
GET_THREAD_INFO(%ebp)
check_userspace:
movl PT_EFLAGS(%esp), %eax # mix EFLAGS and CS
movb PT_CS(%esp), %al
andl $(VM_MASK | 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
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
END(ret_from_exception)
#ifdef CONFIG_PREEMPT
ENTRY(resume_kernel)
DISABLE_INTERRUPTS(CLBR_ANY)
cmpl $0,TI_preempt_count(%ebp) # non-zero preempt_count ?
jnz restore_nocheck
need_resched:
movl TI_flags(%ebp), %ecx # need_resched set ?
testb $_TIF_NEED_RESCHED, %cl
jz restore_all
testl $IF_MASK,PT_EFLAGS(%esp) # interrupts off (exception path) ?
jz restore_all
call preempt_schedule_irq
jmp need_resched
END(resume_kernel)
#endif
CFI_ENDPROC
检查preempt_count的时候,是统一检查是否为0,也就是说,有4个条件限制,可能不能够进行调度。
1.preempt_disable()
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
#define inc_preempt_count() add_preempt_count(1)
会在preempt_enable中释放
2.add_preempt_count(HARDIRQ_OFFSET)
是在irq_enter()中调用的,记录进入硬件中断处理的次数(计数),
会在irq_exit()中释放
3.__local_bh_disable((unsigned long)__builtin_return_address(0));
在do_softirq中调用的,
static inline void __local_bh_disable(unsigned long ip)
{
add_preempt_count(SOFTIRQ_OFFSET);
barrier();
}
4.总开关,第一位。