Chinaunix首页 | 论坛 | 博客
  • 博客访问: 511348
  • 博文数量: 68
  • 博客积分: 5011
  • 博客等级: 大校
  • 技术积分: 806
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-30 22:06
文章分类
文章存档

2011年(1)

2009年(8)

2008年(59)

我的朋友

分类: LINUX

2008-09-09 16:59:15


/*--------------------------------------
  欢迎转载:http://kylinux.cublog.cn
 ---------------------------------------*/



接着上章,转入time_init_hook():
void __init time_init_hook(void)
{
     //注册中断处理函数
     setup_irq(0, &irq0);
}
Irq0定义如下:
static struct irqaction irq0  = { timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL};

对应的中断处理函数为:timer_interrupt():

/*
 * This is the same as the above, except we _also_ save the current
 * Time Stamp Counter value at the time of the timer interrupt, so that
 * we later on can estimate the time of day more exactly.
 */
irqreturn_t timer_interrupt(int irq, void *dev_id)
{
#ifdef CONFIG_X86_IO_APIC
        if (timer_ack) {
                /*
                 * Subtle, when I/O APICs are used we have to ack timer IRQ
                 * manually to reset the IRR bit for do_slow_gettimeoffset().
                 * This will also deassert NMI lines for the watchdog if run
                 * on an 82489DX-based system.
                 */
                spin_lock(&i8259A_lock);
                outb(0x0c, PIC_MASTER_OCW3);
                /* Ack the IRQ; AEOI will end it automatically. */
                inb(PIC_MASTER_POLL);
                spin_unlock(&i8259A_lock);
        }
#endif

        do_timer_interrupt_hook();

        if (MCA_bus) {
                /* The PS/2 uses level-triggered interrupts.  You can't
                turn them off, nor would you want to (any attempt to
                enable edge-triggered interrupts usually gets intercepted by a
                special hardware circuit).  Hence we have to acknowledge
                the timer interrupt.  Through some incredibly stupid
                design idea, the reset for IRQ 0 is done by setting the
                high bit of the PPI port B (0x61).  Note that some PS/2s,
                notably the 55SX, work fine if this is removed.  */

                u8 irq_v = inb_p( 0x61 );       /* read the current state */
                outb_p( irq_v|0x80, 0x61 );     /* reset the IRQ */
        }

        return IRQ_HANDLED;
}


核心处理函数为 do_timer_interrupt_hook():
 static inline void do_timer_interrupt_hook(void)                                                              
{
        global_clock_event->event_handler(global_clock_event);
}
而global_clock_event在前面提过了,中断处理函数初始化通过如下调用:
static inline void tick_set_periodic_handler(struct clock_event_device *dev,   
                                             int broadcast)
{
        dev->event_handler = tick_handle_periodic;
}


   
初始化dev->event_handler函数的过程:

         clockevents_register_device() >
                    clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev) >
                             raw_notifier_call_chain(&clockevents_chain, reason, dev) >
                                         __raw_notifier_call_chain(nh, val, v, -1, NULL) >
                                            notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls) :
                   
                                               ...
                                            ret = nb->notifier_call(nb, val, v);
 /* 注意这个参数v,指向clockevents_register_device
  * 的参数clock_event_device  *global_clock_event;
  * 而参数nb,就是&clockevents_chain
  */
   
   
    然而,这个notifier_call函数指针赋值在start_kernel() > tick_init() :
                
            clockevents_register_notifier(&tick_notifier);
   
    而tick_notifier的定义:
                
            static struct notifier_block tick_notifier = {
                    .notifier_call = tick_notify,
            };

还没有完呢,
                   tick_notify>
                           tick_check_new_device()>
                                       tick_setup-device()>
                                                   tick_setup_periodic()>
                                                           tick_set_periodic_handler()

这个dev->event_handler()处理函数终于初始化了

直接转到tick_handle_periodic():

void tick_handle_periodic(struct clock_event_device *dev)
{
        int cpu = smp_processor_id();
        ktime_t next;

        tick_periodic(cpu);

        if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
                return;
        /*
         * Setup the next period for devices, which do not have
         * periodic mode:
         */
        next = ktime_add(dev->next_event, tick_period);
        for (;;) {
                if (!clockevents_program_event(dev, next, ktime_get()))
                        return;
                tick_periodic(cpu);
                next = ktime_add(next, tick_period);
        }
}
其中tick_periodic调用就是以前的一系列更新操作,包括更新进程时间片等等.
static void tick_periodic(int cpu)
{
        if (tick_do_timer_cpu == cpu) {
                write_seqlock(&xtime_lock);

                /* Keep track of the next tick event */
                tick_next_period = ktime_add(tick_next_period, tick_period);

                do_timer(1);
                write_sequnlock(&xtime_lock);
        }
    //更新当前运行进程的与时钟相关的信息
        update_process_times(user_mode(get_irq_regs()));
        profile_tick(CPU_PROFILING);
}


我们忽略选择编译部份,转到do_timer()
void do_timer(unsigned long ticks)                                             
{
    // 更新jiffies计数.jiffies_64与jiffies在链接的时候,实际是指向同一个区域
        jiffies_64 += ticks;
        ////更新当前时间.xtime的更新
        update_times(ticks);
}

Update_process_times()代码如下:
void update_process_times(int user_tick)
{
        struct task_struct *p = current;
        int cpu = smp_processor_id();
   
    //这里判断时钟中断发生用户空间,还是发生在内核模式,然后计数值加1
        /* Note: this timer irq context must be accounted for as well. */
        if (user_tick)
                account_user_time(p, jiffies_to_cputime(1));
        else
                account_system_time(p, HARDIRQ_OFFSET, jiffies_to_cputime(1));
        //激活时间软中断
        run_local_timers();
        if (rcu_pending(cpu))
                rcu_check_callbacks(cpu, user_tick);
        //减少时间片。
        scheduler_tick();
        run_posix_cpu_timers(p);


run_local_timers()
void run_local_timers(void)
{
     raise_softirq(TIMER_SOFTIRQ);
}
而该中断的处理函数__run_timers():

static inline void __run_timers(tvec_base_t *base)
{
        struct timer_list *timer;

        spin_lock_irq(&base->lock);
        /*这里进入定时器处理循环,利用系统全局jiffies与定时器基准jiffies进行对比,如果前者大,则表明某些定时器进行处理了,否则表示所有的 定时器都没有超时.因为CPU可能关闭中断,引起时钟中断信号丢失.可能jiffies要大base->timer_jiffies
        while (time_after_eq(jiffies, base->timer_jiffies)) {
            //定义并初始化一个链表
                struct list_head work_list;
                struct list_head *head = &work_list;
                int index = base->timer_jiffies & TVR_MASK;

                /*
                 * Cascade timers:
                 */
                
                 //当index == 0时,说明已经循环了一个周期
                 //则将tv2填充tv1.如果tv2为空,则用tv3填充tv2.依次类推......
                if (!index &&
                        (!cascade(base, &base->tv2, INDEX(0))) &&
                                (!cascade(base, &base->tv3, INDEX(1))) &&
                                        !cascade(base, &base->tv4, INDEX(2)))
                        cascade(base, &base->tv5, INDEX(3));
                       
                //更新base->timer_jiffies
                ++base->timer_jiffies;
               
                //将base->tv1.vec项移至work_list.并将base->tv1.vec置空
                list_replace_init(base->tv1.vec + index, &work_list);
               
                /*如果当前找到的时间数组对应的列表不为空,则表明该列表上串连的所有定时器都已经超时,循环调用每个定时器的处理
                函数,并将其从列表中删除,直到列表为空为止。*/
                while (!list_empty(head)) {
                        void (*fn)(unsigned long);
                        unsigned long data;
            //遍历链表中的每一项.运行它所对应的函数,并将定时器从链表上脱落
           
                        timer = list_first_entry(head, struct timer_list,entry);
                        fn = timer->function;
                        data = timer->data;

                        timer_stats_account_timer(timer);

                        set_running_timer(base, timer);
                        detach_timer(timer, 1);
                        spin_unlock_irq(&base->lock);
                        {
                                int preempt_count = preempt_count();
                                fn(data);
                                if (preempt_count != preempt_count()) {
                                        printk(KERN_WARNING "huh, entered %p "
                                               "with preempt_count %08x, exited"
                                               " with %08x?\n",
                                               fn, preempt_count,
                                               preempt_count());
                                        BUG();
                                }
                        }
                        spin_lock_irq(&base->lock);
                }
        }
        set_running_timer(base, NULL);
        spin_unlock_irq(&base->lock);
}


硬件定时器计完一个jiffies之后,会引起硬件中断,在硬件中断服务程序中会触发软中断,在定时器软中断服务程序中会调用 __run_timers()完成定时器多级hash table的处理,并且处理定时时间到的所有timer。__run_timers算法实现描述如下:

  1、根据当前jiffes和base->timer_jiffies循环判断多级hash table扫描条件,如果满足条件,那么继续(2),否则退出循环。

  2、通过base->timer_jiffies计算得到V1 table中需要处理的索引项。并且将索引高层hash table中的具体项,将该项中的timer分散到低层table中去。

  3、增加base->timer_jiffies值,提取出V1中索引得到的定时器链表。

  4、如果该定时器链表不为空,那么依次处理链表中的定时器,处理过程为调用定时器的处理函数timer->function。


下面对scheduler_tick()函数做解析吧.
void scheduler_tick(void)
{
        int cpu = smp_processor_id();
        struct rq *rq = cpu_rq(cpu);
        struct task_struct *curr = rq->curr;
        u64 next_tick = rq->tick_timestamp + TICK_NSEC;

        spin_lock(&rq->lock);
        __update_rq_clock(rq);
        /*
         * Let rq->clock advance by at least TICK_NSEC:
         */
        if (unlikely(rq->clock < next_tick))
                rq->clock = next_tick;
        rq->tick_timestamp = rq->clock;
        update_cpu_load(rq);
        if (curr != rq->idle) /* FIXME: needed? */
                curr->sched_class->task_tick(rq, curr);
        spin_unlock(&rq->lock);

#ifdef CONFIG_SMP
        rq->idle_at_tick = idle_cpu(cpu);
        trigger_load_balance(rq, cpu);
#endif
}

scheduler_tick()函数负责减少运行进程的时间片计数值并且在需要时设置need_resched标志.该函数还负责平衡每个处理器的运行队列.


我们在do_timer()还漏掉了一个函数:
static inline void update_times(unsigned long ticks)
{
       //更新xtime
        update_wall_time();
        //统计TASK_RUNNING TASK_UNINTERRUPTIBLE进程数量
        calc_load(ticks);                                                      
}


**下张主要介绍软时钟中断,摘自网上的资料,请待更新,呵呵

参考资料:
linux内核设计与实现
http://blog.ccidnet.com/blog-htm-do-showone-uid-84561-type-blog-itemid-258809.html
http://blog.chinaunix.net/u2/61477/showart_481379.html

http://hi.baidu.com/80695073/blog/item/9c170f55be0ae5c1b745ae5b.html
http://blog.chinaunix.net/u1/51562/showart_509400.html

阅读(3164) | 评论(2) | 转发(3) |
给主人留下些什么吧!~~

longjiacheng2011-09-20 10:58:47

wenzhuW2008-12-25 15:26:08

收获不少! 请问一下:global_clock_event有多种模式,包括periodic和one shot模式等,那么内核初始化的时候是在哪里决定选择使用何种模式呢?