天天看點

核心代碼閱讀(18) - 時鐘中斷的上半部do_timer和下半部timer_bh時鐘中斷機制

淩晨1點正是看代碼的好時間,自勉。

核心代碼閱讀(17) 中分析了如何借助softirq機制實作Top Half和Bottom Half的。時鐘中斷不僅是核心的心跳,而且很好的诠釋了softirq機制是如何把始終中斷分成上下兩個部分。

時鐘中斷機制

時鐘中斷很重要,是核心的心跳。

進入核心态的途徑:中斷發生,異常,系統調用。

程序的排程隻能在核心态運作,如果使用者态的程序進入了死循環,而此時一直都沒有中斷,異常,系統調用,CPU豈不是一直進入不了核心執行?

時鐘中斷就是惟一可以預測的中斷源。

時鐘中斷的初始化

asmlinkage void __init start_kernel(void)
    {
        char * command_line;
        unsigned long mempages;
        extern char saved_command_line[];
        lock_kernel();
        printk(linux_banner);
        setup_arch(&command_line);
        printk("Kernel command line: %s\n", saved_command_line);
        parse_options(command_line);
        trap_init();
        init_IRQ();
        sched_init();
        time_init();
        softirq_init();
    }      
1) time_init 就是時鐘中斷初始化函數。      

time_init

void __init time_init(void)
    {
        extern int x86_udelay_tsc;
        
        xtime.tv_sec = get_cmos_time();
        xtime.tv_usec = 0;
         dodgy_tsc();
         
        if (cpu_has_tsc) {
                unsigned long tsc_quotient = calibrate_tsc();
                if (tsc_quotient) {
                        fast_gettimeoffset_quotient = tsc_quotient;
                        use_tsc = 1;
                        x86_udelay_tsc = 1;
    #ifndef do_gettimeoffset
                        do_gettimeoffset = do_fast_gettimeoffset;
    #endif
                        do_get_fast_time = do_gettimeofday;
                        /* report CPU clock rate in Hz.
                         * The formula is (10^6 * 2^32) / (2^32 * 1 / (clocks/us)) =
                         * clock/second. Our precision is about 100 ppm.
                         */
                        {        unsigned long eax=0, edx=1000;
                                __asm__("divl %2"
                                       :"=a" (cpu_khz), "=d" (edx)
                                       :"r" (tsc_quotient),
                                "0" (eax), "1" (edx));
                                printk("Detected %lu.%03lu MHz processor.\n", cpu_khz / 1000, cpu_khz % 1000);
                        }
                }
        }
        setup_irq(0, &irq0);
    }      
1) xtime.tv_sec = get_cmos_time();
   xtime.tv_usec = 0;
   系統時鐘涉及到兩個全局變量:
   a) struct time_val xtime;
      struct timeval {
          time_t        tv_sec;
          suseconds_t        tv_usec;
      };
     xtime紀錄了從曆史上某個時刻開始的絕對時間,數值來自一個CMOS晶片,精确到秒。get_cmos_time擷取秒并初始化全局變量xtime。
   b) jiffies
     記錄了從開機以來時鐘中斷發生的次數。
2) setup_irq(0, &irq0);
   設定中斷向量号0的服務子程式為irq0.
   static struct irqaction irq0  = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};
   是以,時鐘中斷的服務程式是 timer_interrupt。      

時鐘中斷服務子程式timer_interrupt

static void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
    {
        int count;
        write_lock(&xtime_lock);
 
        do_timer_interrupt(irq, NULL, regs);
        write_unlock(&xtime_lock);
    }      
1) timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
   調用中斷服務子程式的第一個參數是裝置的id,第二個參數就是通過SAVE_ALL或者 jmp error_code 構造的寄存器現場。
2) write_lock(&xtime_lock);
   全局變量xtime需要上鎖。      

do_timer_interrupt

static inline void do_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
    {
        do_timer(regs);
        if (!user_mode(regs))
                x86_do_profile(regs->eip);
    }      
1) x86_do_profile(regs->eip);
   統計資訊。      

時鐘中斷上半段do_timer

void do_timer(struct pt_regs *regs)
    {
        (*(unsigned long *)&jiffies)++;
        mark_bh(TIMER_BH);
        if (TQ_ACTIVE(tq_timer))
                mark_bh(TQUEUE_BH);
    }      
1) (*(unsigned long *)&jiffies)++;
   jiffies加1.
2) mark_bh(TIMER_BH);
   到此時鐘中斷‘上半部’執行完畢,激活時鐘中斷的Bottom Half。      

時鐘中斷下半段 timer_bh

在sched_init中初始化了始終中斷的下半段:
    init_bh(TIMER_BH, timer_bh);
    init_bh(TQUEUE_BH, tqueue_bh);
    init_bh(IMMEDIATE_BH, immediate_bh);      
void timer_bh(void)
    {
        update_times();
        run_timer_list();
    }      
update_times
static inline void update_times(void)
    {
        unsigned long ticks;
        write_lock_irq(&xtime_lock);
        ticks = jiffies - wall_jiffies;
        if (ticks) {
                wall_jiffies += ticks;
                update_wall_time(ticks);
        }
        write_unlock_irq(&xtime_lock);
        
    }      
1) ticks = jiffies - wall_jiffies;
   wall_jiffies是牆上時間已經更新到了哪裡了。
   內插補點ticks是需要更新的心跳數。
2) update_wall_time(ticks);
   校準更新時鐘xtime
3) calc_load(ticks);
   計算load      

update_wall_time

static void update_wall_time(unsigned long ticks)
    {
        do {
                ticks--;
                update_wall_time_one_tick();
        } while (ticks);
        if (xtime.tv_usec >= 1000000) {
            xtime.tv_usec -= 1000000;
            xtime.tv_sec++;
            second_overflow();
        }
    }      
1) update_wall_time_one_tick
   校準更新時鐘xtime      

calc_load

unsigned long avenrun[3];
    static inline void calc_load(unsigned long ticks)
    {
        unsigned long active_tasks;
        static int count = LOAD_FREQ;
        count -= ticks;
        if (count < 0) {
                count += LOAD_FREQ;
                active_tasks = count_active_tasks();
                CALC_LOAD(avenrun[0], EXP_1, active_tasks);
                CALC_LOAD(avenrun[1], EXP_5, active_tasks);
                CALC_LOAD(avenrun[2], EXP_15, active_tasks);
        }
    }      
rum_timer_list
static inline void run_timer_list(void)
{
    spin_lock_irq(&timerlist_lock);
    while ((long)(jiffies - timer_jiffies) >= 0) {
            struct list_head *head, *curr;
            if (!tv1.index) {
                    int n = 1;
                    do {
                            cascade_timers(tvecs[n]);
                    } while (tvecs[n]->index == 1 && ++n < NOOF_TVECS);
            }
repeat:
            head = tv1.vec + tv1.index;
            curr = head->next;
            if (curr != head) {
                    struct timer_list *timer;
                    void (*fn)(unsigned long);
                    unsigned long data;
                    timer = list_entry(curr, struct timer_list, list);
                     fn = timer->function;
                     data= timer->data;
                    detach_timer(timer);
                    timer->list.next = timer->list.prev = NULL;
                    timer_enter(timer);
                    spin_unlock_irq(&timerlist_lock);
                    fn(data);
                    spin_lock_irq(&timerlist_lock);
                    timer_exit();
                    goto repeat;
            }
            ++timer_jiffies; 
            tv1.index = (tv1.index + 1) & TVR_MASK;
    }
    spin_unlock_irq(&timerlist_lock);
}      
1) run_timer_list(void)
       完成系統中的定時器任務。
    2) struct timer_list {
           struct list_head list;
           unsigned long expires;
           unsigned long data;
           void (*function)(unsigned long);
       };
    3) ++timer_jiffies;
       一個tick最多執行一個timer。
    4) spin_unlock_irq(&timerlist_lock);
       fn(data);
       spin_lock_irq(&timerlist_lock);
       執行一個timer過程解鎖timerlist_lock。      

繼續閱讀