《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);

該方法不能保證睡眠時間正好等於指定的延時時間,只能儘量接近。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章