Chinaunix首页 | 论坛 | 博客
  • 博客访问: 113864
  • 博文数量: 32
  • 博客积分: 1470
  • 博客等级: 上尉
  • 技术积分: 375
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-13 21:27
文章分类

全部博文(32)

文章存档

2011年(5)

2010年(27)

我的朋友

分类: LINUX

2010-07-07 21:47:07

外部硬件中断与操作系统的联系或者叫结合点,就是那张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.总开关,第一位。
阅读(1346) | 评论(0) | 转发(0) |
0

上一篇:8259A

下一篇:随笔

给主人留下些什么吧!~~