Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3612
  • 博文数量: 4
  • 博客积分: 120
  • 博客等级: 入伍新兵
  • 技术积分: 50
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-03 02:44
文章分类
文章存档

2011年(1)

2009年(2)

2008年(1)

我的朋友
最近访客

分类: LINUX

2009-03-09 22:30:31

    首先看一下中断发生时硬件和软件的大致处理过程,然后再来对每一步做稍微详细的介绍。
    1.当中断发生时,cpu control unit会截获这一事件,并做一些基本的保留,同时disable local interrupt,表示不再响应中断。这里涉及到control unit保存的内容和来读取相应idt、gdt的内容来验证中断源的合法性和interrupt handler的指令地址,接下来执行的第一条软件指令便是interrupt handler。
    2.Interrupt handler的首要动作便是save all,保存一切有可能会被中断处理程序用到的寄存器值,然后执行函数do_irq,do_irq调用irq_enter增加preemt_count的 Hardirq counter数值。做一些与8k内核堆栈相关的动作,然后调用__do_irq。
    3.__do_irq的首要任务便是acknowledge apic并且mask掉产生中断的线,使得该线在中断处理完成之前不再接收再次中断。此时local interrupt也是disable的(由1中的control unit关闭),接下来调用isr。
    4.在执行isr之前,首先根据该isr的要求,开启或关闭local interrupt,一般都是开启local interrupt,然后依次调用isr。除非注册isr(request_irq)的时候特别用flag指明必须local interrupt disable,否则一般的isr都是运行在产生中断的那根线disable掉和local interrupt enable的情况下,执行完成之后并关闭local interrupt。
    5.退到上层的__do_irq,该函数会调用pic的end函数,使得对应那根线(irq line)的中断开启。
    6.退到上层的do_irq,此时会调用irq_exit,在irq_exit中首先减少preemt_count中的Hardirq counter,然后调用了softirq.可知softirq是运行在全部的中断线和local interrupt enable的情况下,意味着此时内核已经可以再次响应同样的中断。
    7.完成之后。。。。。。。。。。。。。调用iret指令。
    8.iret指令使得control unit恢复先前保存的所有东西,enable local interrupt。
   上述第一点中,中断源的合法性比较重要,那么硬件上有哪些机制保证了该中断源的合法性呢?要了解此我们先来看一下当中断发生时,control unit做了哪些动作。首先它会根据中断源和idtr一起来找到相应的idt中的某个entry,并从该entry当中取出segment seletor,由此seletor再加上gdtr寄存器,就可以找到该interrupt handler的segment descriptor,由于该segment descriptor当中有个DPL,它表示了中断处理程序应该在哪个级别下运行,一般都是在0的level即kernel态下运行interrupt handler。因此如果发现当前进程的cs中的低两位数值比中断处理程序的DPL还小(数值越小,level也高,kernel的数值为0),那么就直 接出现异常,因为不可能会有某个进程它的运行级别会被interrupt handler还低。经过这部确认之后,cs和eip分别被赋值成IDT当中的segment selector和offset,做完这一步意味着下一个执行的指令便是interrupt handler中的指令了。

    第二步当中的interrupt handler的整条命令如下:
pushl $n-256
jmp common_interrupt
common_interrupt:
SAVE_ALL
movl %esp,%eax
call do_IRQ
jmp ret_from_intr
       其实存在于idt的地址指向的指令便是上述的前两条语句,接下来就是统一地保存所有cpu的寄存器,以免丢失。然后就调用函数do_IRQ来执行。 do_IRQ如上述所说,它基本就是增加硬中断的数值,然后直接调用__do_irq,__do_irq基本会通知中断控制器已经收到中断信号,告诉它将 中断线的信号取消,一般此时会中断控制器会mask掉该跳线的中断。接下来__do_irq会调用ISR也即驱动程序利用函数 request_irq函数注册进去的中断处理历程。一般来说,在执行中断处理历程的时候,kernel会根据driver传进来的flag决定是否开启 local的中断,一般来说都是开启的,但是这里需要我们注意的是对于8259A这个中断控制芯片来说,此时尽管cpu local的中断是enabled,但是8259A上产生中断的哪条线还是disable的,意味着这条线上的中断不会被硬件觉察到。对于intel IO apic还没有研究过。由此可以看出,一般driver的中断处理历程都是运行local interrupt enable的情况下进行的,因此会产生中断嵌套之说。当__do_irq执行完ISR之后,并enable8259A上的哪条中断线,意味着将接收该线 上产生的中断。到此,__do_irq算基本完成,退到上层函数do_irq,do_irq会执行irq_exit,在该函数中会减掉由 irq_enter增加的硬中断数值,接下来就判断是否是中断context,即如果函数in_interrupt返回false,且有pending 的软中断,那么就去执行软中断,即中断的底半步。关于linux中断的上半步和底半步就不详细说明了,这里只说明整个流程是怎么走的。
     kernel在系统初始化的时候注册了6个不同的softirq,分别为HI_SOFTIRQ,TIMER_SOFTIRQ(内核定时器的实现就是 依赖这个softirq),NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,BLOCK_SOFTIRQ,TASKLET_SOFTIRQ (tasklet的实现就是依赖于这个softirq),SCHED_SOFTIRQ,RCU_SOFTIRQ。。。。。当中断处理程序执行到 irq_exit函数,在该函数中调用invoke_softirq即__do_softirq,正是在这个函数里,kernel对每个有pending 的softirq执行起其相应的函数。那么哪个softirq pending是谁来设置的呢?可以参考函数raise_softirq_irqoff,这个函数传进来的参数就是上述6个softirq的值。 softirq会在每次中断返回时会被检查是否要调用,到真正执行softirq的各个函数时,in_interrupt()值一定不为真。(由于 softirq不能嵌套执行,因此它在执行之前会local_bh_disable来提升preemt_count的softirq counter值,使得下次softirq在in_interrupt()的时候直接返回。那么在什么情况下会发生softirq的中断嵌套呢?因为 softirq的时候所有中断都是打开的,因此有可能新的interrupt handler里面又注册了这个softirq,等到嵌套的中断退出时(此时preemt_count中的hardirq counter 为0,请参考do_irq分析),又会调到softirq,此时就实现了softirq的中断嵌套。
     这里将tasklet(即软中断中的一种)做个简单的解析,使用过tasklet来帮助我们driver中的实现中断底半步的兄弟都知道,一般我们都是通过如下两个步骤来实现:
tasklet_init
tasklet_schedule。
我们来看看上述两个函数的代码实现:
void tasklet_init(struct tasklet_struct *t,
                  void (*func)(unsigned long), unsigned long data)
{
        t->next = NULL;
        t->state = 0;
        atomic_set(&t->count, 0);
        t->func = func;
        t->data = data;
}
tasklet_init很简单,它只是将一个tasklet_struct赋值成想要的函数和参数,为该tasklet被运行做了一些初始化工作。
再来看
static inline void tasklet_schedule(struct tasklet_struct *t)
{
        if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
                __tasklet_schedule(t);
}
void __tasklet_schedule(struct tasklet_struct *t)
{
        unsigned long flags;

        local_irq_save(flags);
        t->next = NULL;
        *__get_cpu_var(tasklet_vec).tail = t;
        __get_cpu_var(tasklet_vec).tail = &(t->next);
        raise_softirq_irqoff(TASKLET_SOFTIRQ);
        local_irq_restore(flags);
}
inline void raise_softirq_irqoff(unsigned int nr)
{
        __raise_softirq_irqoff(nr);

        /*
         * If we're in an interrupt or softirq, we're done
         * (this also catches softirq-disabled code). We will
         * actually run the softirq once we return from
         * the irq or softirq.
         *
         * Otherwise we wake up ksoftirqd to make sure we
         * schedule the softirq soon.
         */
        if (!in_interrupt())
                wakeup_softirqd();
}
#define __raise_softirq_irqoff(nr) do { or_softirq_pending(1UL << (nr)); } while (0)
由红色的代码我们可以看到函数tasklet_schedule只是将每个cpu对应的关于软中断的变量的那个关于tasklet的bit置成1,就结束了。
到此为止,我们再回想当中断处理程序完成之后,它会根据当前是否有pending的softirq,此时有,因此函数tasklet_action就会被 调用,原因是内核初始化的时候有这么一条语句:open_softirq(TASKLET_SOFTIRQ, tasklet_action);。再看函数tasklet_action:
static void tasklet_action(struct softirq_action *a)
{
        struct tasklet_struct *list;

        local_irq_disable();
        list = __get_cpu_var(tasklet_vec).head;
        __get_cpu_var(tasklet_vec).head = NULL;
        __get_cpu_var(tasklet_vec).tail = &__get_cpu_var(tasklet_vec).head;
        local_irq_enable();

        while (list) {
                struct tasklet_struct *t = list;

                list = list->next;

                if (tasklet_trylock(t)) {
                        if (!atomic_read(&t->count)) {
                                if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
                                        BUG();
                                t->func(t->data);
                                tasklet_unlock(t);
                                continue;
                        }
                        tasklet_unlock(t);
                }

                local_irq_disable();
                t->next = NULL;
                *__get_cpu_var(tasklet_vec).tail = t;
                __get_cpu_var(tasklet_vec).tail = &(t->next);
                __raise_softirq_irqoff(TASKLET_SOFTIRQ);
                local_irq_enable();
        }
}
红色的代码就是我们通过函数tasklet_init注册进去的函数。

 

 

附:

中断处理机制中,上半部和下半部的概念显得较为重要。其中下半部的处理机制主要有softirqtaskletworkqueue。中断机制同时涉及中断嵌套概念,由于在kernel代码中,只有page fault这个exception可能发生,如果其他的exception会发生,那说明kernel存在bug。且interrupt handler当中是没有page fault这样的代码,因此exception不会抢占interrupt,相反,interrupt handler可以抢占exceptioninterrupt handler。同时需要关注的是,在interrupt context当中不能睡眠,答案还有待确认。

在中断处理当中,涉及到一个叫抢占式kernel的概念。什么是抢占式kernel,即当一个进程由于需要内核服务而进入到内核时,如果此时由于某种原因其他比它高级别的进程进入了可执行状态,那么当前进程即使在内核态也会被激活的进程抢占。这就是抢占式内核和非抢占式内核的主要区别,非抢占式内核对于一个执行在kernel态的进程,除非是主动释放cpu,否则只有执行到它要返回到用户态的时才会进行进程切换。

    当中断发生时,cpu control unit将进行一系列的保存现场工作,最后一步便是直接将对应idt当中的指令下给cseip,这些动作全部由硬件来执行,第一条得到执行的软件指令便是interrupt handler

    1.此时interrupt handler已经执行在local interrupt disable的情况下,关闭local interrupt是有control unit自动完成的。

    2.interrupt handler执行一些save动作之后并会调用do_irq函数执行,do_irqack pic,让它停止发送某条线上的信号,同时也mask掉该线的中断能力。

interrupt handler执行的时候到底是该irq线上的中断被disable掉还是全部的中断被disable掉?

It is crucial to understand why to defer work, and when exactly to defer it. You want to limit the amount of work you perform in an interrupt handler because interrupt handlers run with the current interrupt line disabled on all processors. Worse, handlers that register with SA_INTERRUPT run with all local interrupts disabled (plus the local interrupt line globally disabled).

自旋锁抢占被禁止且如果该自旋锁会在interrupt handler或者底半步调用的话,那么必须用spin_lock_irq(bh)来获取自旋锁。

阅读(381) | 评论(0) | 转发(0) |
0

上一篇:纯粹的夜猫

下一篇:同步机制的使用

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