免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 4443 | 回复: 2
打印 上一主题 下一主题

Linux中断处理过程浅析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-03-30 22:37 |只看该作者 |倒序浏览
最近在看Linux中断处理的过程,写了以下几点体会,忘高手给予指点,以便我去补充,使得这个知识点对于自己更加完整。

    首先看一下中断发生时硬件和软件的大致处理过程,然后再来对每一步做稍微详细的介绍。
    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注册进去的函数。

以此类推,我们可以找到kernel timer的实现原理和其他一些软中断的执行过程。

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
2 [报告]
发表于 2009-03-31 12:19 |只看该作者
写得不错,你可以对照一下情景分析,呵呵

论坛徽章:
0
3 [报告]
发表于 2009-03-31 12:22 |只看该作者

回复 #2 dreamice 的帖子

是不是情景分析描述地更详细一些。
我要去好好仰慕一下。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP