Chinaunix首页 | 论坛 | 博客
  • 博客访问: 121423
  • 博文数量: 41
  • 博客积分: 2564
  • 博客等级: 少校
  • 技术积分: 455
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-20 19:17
文章分类

全部博文(41)

文章存档

2009年(41)

我的朋友

分类: LINUX

2009-03-30 17:48:00

Linux 2.4.35 时钟中断分析

写在前面
    这篇文章的主要目的是自己做笔记,发到博客中是为了和大家分享。本人水平有限,环境大家抓bug,哈哈。另外,本人用的代码浏览器是source insight,用过的人都知道它很好很强大,特此推荐给没有用过的朋友。

    时钟中断是在time_init()函数里面初始化的,而time_init()由start_kernel()调用。正如start_kernel()位于main.c中,它做的工作就是初始化内核的各个部分。

代码:

...

    trap_init();
    init_IRQ();
    sched_init();
    softirq_init();
    time_init();
...


   time_init()位于time.c,它主要用户初始化系统时间(从BIOS中取),和时钟中断。在这里我们关系的语句是初始化时钟中断:

...
    setup_irq(0, &irq0);
...


irq0也位于time.c,是一个irqaction结构:

...
static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};
...


irqaction结构的第一个成员就是特定中断的处理函数,在这里,我们看到时钟中断的处理函数是timer_interrput,继续往下追踪。

    timer_interrput()位于time.c,在它的倒数第二行调用了do_timer_interrput(),这是处理时钟中断的主体:

...
    do_timer_interrupt(irq, NULL, regs);
...


继续跟踪至do_timer_interrput(),在此处调用了do_timer():

...
    do_timer(regs);
...


进入do_timer,函数体很短:

void do_timer(struct pt_regs *regs)
{
    (*(unsigned long *)&jiffies)++;
#ifndef CONFIG_SMP
    /* SMP process accounting uses the local APIC timer */
    update_process_times(user_mode(regs));
#endif
    mark_bh(TIMER_BH);
    if (TQ_ACTIVE(tq_timer))
        mark_bh(TQUEUE_BH);
}


update_process_times函数跟新当前进程的时间,比如内核态时间、用户态时间等,另外,将当前进程的时间片减1,如果时间片counter为0,且调度策略不是SCHED_FIFO就把当前进程的need_resched置为1,表示可以重新调度:

...
    struct task_struct *p = current;
    int cpu = smp_processor_id(), system = user_tick ^ 1;

    update_one_process(p, user_tick, system, cpu);
    if (p->pid) {
        if (--p->counter <= 0) {
            p->counter = 0;
            /*
             * SCHED_FIFO is priority preemption, so this is
             * not the place to decide whether to reschedule a
             * SCHED_FIFO task or not - Bhavesh Davda
             */

            if (p->policy != SCHED_FIFO) {
                p->need_resched = 1;
            }
        }
...


回到,do_timer,紧接着update_process_times,执行了代码:

mark_bh(TIMER_BH);
    if (TQ_ACTIVE(tq_timer))
        mark_bh(TQUEUE_BH);


激活钟中断的下半部,表示时钟下半部可以执行;另外,如果定时任务队列不为空,则将TQUEUE_BH也激活。。自此,时钟中断的主要部分处理完毕。

    走到这里是不是表示时钟中断就完成了呢?答案是否定的。前面只完成了上半部,还有下半部,也就是TIMER_BH没做呢。要知道接下来做什么,我们要看执行完硬件中断以后会干些什么事。处理中断是在do_IRQ()中进行的,它位于irq.c。在do_IRQ的末尾,有那么两句:

...
    if (softirq_pending(cpu))
        do_softirq();
...


这两句的意思是,如果当前有软件中断被置位,则执行他们的处理函数。那么,这有和TIMER_BH有什么关系呢?这就要看看do_timer中的mark_bh(TIMER_BH)了:

static inline void mark_bh(int nr)
{
    tasklet_hi_schedule(bh_task_vec+nr);
}


函数很简单,可以知道TIMER_BH对应bh_task_vec[0]。继续跟踪下去:

static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{
    if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))
        __tasklet_hi_schedule(t);
}


还是很简单,嘿嘿。条件判断先不管,直接进入__tasklet_hi_schedule:

void fastcall __tasklet_hi_schedule(struct tasklet_struct *t)
{
    int cpu = smp_processor_id();
    unsigned long flags;

    local_irq_save(flags);
    t->next = tasklet_hi_vec[cpu].list;
    tasklet_hi_vec[cpu].list = t;
    cpu_raise_softirq(cpu, HI_SOFTIRQ);
    local_irq_restore(flags);
}


这里可以知道,mark_bh(TIMER_BH)把bh_task_vec[0]挂到tasklet_hi_vec[cpu].list上。另外,这里还有我们寻找的软中断信息:

    cpu_raise_softirq(cpu, HI_SOFTIRQ)


这句的意思是将当前cpu的HI_SOFTIRQ软中断置位,也就是说它是在do_IRQ末尾要执行的东东。

    接下来看看HI_SOFTIRQ对应的处理函数是什么。软件中断是在softirq_init()中初始化的,在start_kernel()中time_init()之前调用:

void __init softirq_init()
{
    int i;

    for (i=0; i<32; i++)
        tasklet_init(bh_task_vec+i, bh_action, i);

    open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
    open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}


这里,我们很清楚的看到,HI_SOFTIRQ对应的处理函数的tasklet_hi_action,继续跟踪下去:

static void tasklet_hi_action(struct softirq_action *a)
{
    int cpu = smp_processor_id();
    struct tasklet_struct *list;

    local_irq_disable();
    list = tasklet_hi_vec[cpu].list;
    tasklet_hi_vec[cpu].list = NULL;
    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 = tasklet_hi_vec[cpu].list;
        tasklet_hi_vec[cpu].list = t;
        __cpu_raise_softirq(cpu, HI_SOFTIRQ);
        local_irq_enable();
    }
}


函数的主体是一个while循环,它遍历当前cpu的tasklet_struct链表,一一执行每个tasklet_struct注册的函数func。
    问题又来了:这和TIMER_BH有什么关系?要解决这个疑问,回到tasklet_hi_schedule()中的__tasklet_hi_schedule(),此函数接受一个tasklet_struct,并把它挂到tasklet_hi_vec[cpu].list上。也就是说,mark_bh(TIMER_BH)将TIMER_BH对应的tasklet_struct挂到tasklet_hi_vec[cpu].list上,这个tasklet_struct对应的func成员就是TIMER_BH的处理函数!至于这些tasklet_struct怎么来的,由tasklet_hi_schedule(bh_task _vec+nr)可知他们来自bh_task_vec数组。
    回忆softirq_init,里面有一个循环体:

...
for (i=0; i<32; i++)
        tasklet_init(bh_task_vec+i, bh_action, i);
...


Bingoo!原来bh_task_vec在这里初始化,从这里我们还知道bh_task_vec的长度为32。
    找到TIMER_BH的定义,在interrupt.h中:

enum {
    TIMER_BH = 0,
    TQUEUE_BH,
    DIGI_BH,
    SERIAL_BH,
    RISCOM8_BH,
    SPECIALIX_BH,
    AURORA_BH,
    ESP_BH,
    SCSI_BH,
    IMMEDIATE_BH,
    CYCLADES_BH,
    CM206_BH,
    JS_BH,
    MACSERIAL_BH,
    ISICOM_BH
};


嘿嘿,不仅知道了TIMER_BH的值为0,还知道了其它的BH的值,意外的收获哦!走到了这里,依然不知道TIMER_BH对应的处理函数是什么?浏览softirq.c,会发现有那么一个函数:

void init_bh(int nr, void (*routine)(void))
{
    bh_base[nr] = routine;
    mb();
}


看名字就能猜到它是用来初始化下半部的,查找init_bh的caller,发现一个可以的名称:sched_init。没错,就是它,在start_kernel中和time_init出现在一起,看名字也是初始化某个东西,至于它初始化的是什么,不是这里讨论的范畴。跟踪到里面,可以看到那么几句:

    init_bh(TIMER_BH, timer_bh);
    init_bh(TQUEUE_BH, tqueue_bh);
    init_bh(IMMEDIATE_BH, immediate_bh);

YES!找到我们需要的东西了,而且还有意外收获,不仅知道了TIMER_BH对应的处理函数是timer_bh,还知道了TQUEUE_BH和IMMEDIATE_BH的处理函数。
    结合init_bh可以知道,timer_bh挂在了bh_base[0]上。要彻底搞清楚timer_bh是如何与bh_task_vec联系起来的,还需回到soft_init()中的循环体和task_init()

...
for (i=0; i<32; i++)
        tasklet_init(bh_task_vec+i, bh_action, i);
...

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;
}


可以看出,bh_task_vec中每个tasklet_struct的func都与bh_action挂钩,并且将data成员设为对应的BH号。这样,tasklet_hi_action中的t->func(t->data)对应到TIMER_BH,就是bh_action(0)。继续深入bh_action():

static void bh_action(unsigned long nr)
{
    int cpu = smp_processor_id();

    if (!spin_trylock(&global_bh_lock))
        goto resched;

    if (!hardirq_trylock(cpu))
        goto resched_unlock;

    if (bh_base[nr])
        bh_base[nr]();

    hardirq_endlock(cpu);
    spin_unlock(&global_bh_lock);
    return;

resched_unlock:
    spin_unlock(&global_bh_lock);
resched:
    mark_bh(nr);
}


至此,终于真相大白了!什么?还没看到?bh_base[nr]()调用bh_action(0)就是调用bh_base[0](),也即timer_bh()。

    到这里,第一部分就结束了。回顾一下,这一部分主要分析了时钟中断的初始化以及处理函数的工作流程。要知道时钟中断的下半部,也就是timer_bh()做了什么,且听下回分解~(嘿嘿,其实现在我也还不是很清楚,边学边写~)
阅读(626) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~