天天看點

《Linux核心設計與實作》定時器和時間管理節拍硬時鐘和定時器時鐘中斷處理程式延時執行

節拍

系統定時器以某種頻率自行觸發,這個頻率是靜态預處理定義的的,稱為節拍率,也稱為HZ(赫茲)。

HZ值在不同的體系結構不同。

x86體系結構中HZ預設值是100(include\asm-generic\param.h),是以x86上時鐘中斷的頻率就是100Hz,即每秒觸發100次中斷。

#ifndef HZ
#define HZ 100
#endif
           

jiffies用來記錄自系統啟動以來産生的節拍的綜述(include\linux\jiffies.h),還有個64位的版本jiffies_64。

extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
           

jiffies一秒内增加的值就是HZ。

jiffies是32位的,是以可能存在溢出的情況。

通過幾個宏可以正确處理溢出導緻的回繞:

#define time_after(a,b)     \
    (typecheck(unsigned long, a) && \
     typecheck(unsigned long, b) && \
     ((long)(b) - (long)(a) < 0))
#define time_before(a,b)    time_after(b,a)
#define time_after_eq(a,b)  \
    (typecheck(unsigned long, a) && \
     typecheck(unsigned long, b) && \
     ((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)
           

硬時鐘和定時器

實時時鐘(RTC)用來持久存放系統時間。

核心通過讀取RTC來初始化牆上時間,該時間存放在xtime變量(include\linux\time.h):

#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC
struct timespec {
    __kernel_time_t tv_sec;         /* seconds */
    long        tv_nsec;        /* nanoseconds */
};
#endif
extern struct timespec xtime;
           

tv_sec以秒位機關,存放着自19700101以來經過的時間;

tv_nsec記錄自上一秒開始經過的ns數;

x86會周期性地将目前時間值存回RTC。

系統定時器提供一種周期性觸發中斷的機制。

x86下還有APIC時鐘和時間戳計數(TSC)。

定時器是管理核心流逝時間的基礎。

定時器并不是周期性執行,它在逾時後自行撤銷。

定時器有結構體time_list表示:

struct timer_list {
    struct list_head entry;
    unsigned long expires;
    void (*function)(unsigned long);
    unsigned long data;
    struct tvec_base *base;
#ifdef CONFIG_TIMER_STATS
    void *start_site;
    char start_comm[16];
    int start_pid;
#endif
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};
           

定時器定義:

#define DEFINE_TIMER(_name, _function, _expires, _data)     \
    struct timer_list _name =               \
        TIMER_INITIALIZER(_function, _expires, _data)
           

定時器初始化:

#define init_timer(timer)                       \
    do {                                \
        static struct lock_class_key __key;         \
        init_timer_key((timer), #timer, &__key);        \
    } while (0)
#define init_timer(timer)\
    init_timer_key((timer), NULL, NULL)
           

激活定時器:

extern void add_timer(struct timer_list *timer);
           

更改定時器:

extern int mod_timer(struct timer_list *timer, unsigned long expires);
           

停止定時器:

extern int del_timer(struct timer_list * timer);
           

對于多處理,定制定時器最好使用如下的:

#ifdef CONFIG_SMP
  extern int del_timer_sync(struct timer_list *timer);
           

核心在時鐘中斷發生之後執行定時器,定時器作為軟中斷(TIMER_SOFTIRQ)在下半部上下文中執行。

時鐘中斷處理程式

時鐘中斷處理程式分為兩個部分,一部分與體系架構有關,另一部分與體系架構無關。

前一部分:

  • 獲得xtime_lock鎖(是一個seq鎖),以便對方為jiffies_64和牆上時間xtime進行保護;
  • 需要時應答或重新設定系統時鐘;
  • 周期性地使用牆上時間更新實時時鐘;
  • 調用體系架構無關的時鐘例程tick_periodic();

後一部分就是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);
}
           

大緻的操作:

  • 給jiffies_64變量增加1;
  • 更新資源消耗的統計值;
  • 執行已經到期的動态定時器;
  • 執行sheduler_tick()函數;
  • 更新牆上時間,該時間存放在xtime變量中;
  • 計算平均負載值;

延時執行

延時有很多種方法:

忙等待:

static void
fore200e_spin(int msecs)
{
    unsigned long timeout = jiffies + msecs_to_jiffies(msecs);
    while (time_before(jiffies, timeout));
}
           

代碼等待時允許核心重新排程更重要的任務:

    for (;;) {
        smcr = ioread16(dev->base + SMCR);
        /*
         * Don't bother checking ACKE here, this and the reset
         * are handled in highlander_i2c_wait_xfer_done() when
         * waiting for the ACK.
         */
        if (smcr & SMCR_IRIC)
            return;
        if (time_after(jiffies, timeout))
            break;
        cpu_relax();
        cond_resched();
    }
           

cond_resched()函數将排程一個新程式投入運作。它不能在中斷上下文中使用,隻能在程序上下文中使用。

短延時:

udelay(100);
           

睡眠到指定時間:

set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(HZ);
           

該方法不能保證睡眠時間正好等于指定的延時時間,隻能盡量接近。

繼續閱讀