linux 定時器原理

內核定時器:
    unsigned long timeout = jiffies + (x * HZ);
    while(1) {
        // Check the condition.
        // Take a schedule.
        if (time_after(jiffies, timeout)) {
            printk("Timeout\n");
            break;
        }
    }
轉換到秒:    
s = (jiffies - last_jiffies)/HZ;
jiffies(約50天溢出)爲jiffies_64的後32位,所以直接讀取jiffies_64不具有原子性,使用get_jiffies_64,
函數原理:[平臺爲32位則須要保護讀取,不然直接讀取]
順序鎖:        讀讀/讀寫併發,寫寫互斥
讀寫自旋鎖:    讀讀併發,讀寫/寫寫互斥
自旋鎖:        不容許任何操做併發
u64 get_jiffies_64(void)
{
    unsigned long seq;
    u64 ret;
    do {
        seq = read_seqbegin(&xtime_lock);
        ret = jiffies_64;
    } while (read_seqretry(&xtime_lock, seq)); // 若讀的過程當中發生寫,則重讀
    return ret;
}
這裏涉及一個順序鎖的讀寫規則:
讀不會更改lock的seq,寫會++,這裏就會發現到值被寫覆蓋,因而從新讀。

write_seqlock(&lock);
...... //寫操做代碼
write_sequnlock(&lock);
順序鎖的使用場景是必須默認保持寫互斥後,才能使用順序鎖.


睡眠延時:
這種睡眠再調度的精度要低於jiffies的精度:
schedule_timeout(xx);
函數原理:
    expire = timeout + jiffies;            // 超時截至時間
    setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
    __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
    schedule();
    del_singleshot_timer_sync(&timer);
    destroy_timer_on_stack(&timer);
    timeout = expire - jiffies;
 out:
    return timeout < 0 ? 0 : timeout;
    
從這裏看出,這個函數其實是基於timer來實現的

標準的延時調度接口 timer_list:[若是須要循環調度,則在timer_func中遞歸init/add]
struct timer_list my_timer;
init_timer(&my_timer);
my_timer.expire = jiffies + n*HZ;
my_timer.function = timer_func;
my_timer.data = func_parameter;
add_timer(&my_timer);


短延時(基於指令級忙等,不基於jiffy機制的方法):
mdelay/udelay/ndelay
基於一個全局的變量:loops_per_jiffy,變量初始化位於:
calibrate_delay()
基本原理是,先從4096 以*2的倍數找到第一個範圍 4096*x < t < 4096*2x
而後逐漸開始細化,從4096*x 開始,逐漸遞增4096*x>>2(而不是減半),直到到達對應的精度要求
10000000
11000000
10100000
....

static inline void __udelay(unsigned long usecs)
{
    unsigned long loops_per_usec;
    loops_per_usec = (loops_per_jiffy * HZ) / 1000000;        //一秒中可以執行的指令數目/1000000(ms)
    __delay(usecs * loops_per_usec);                        //Delay 幾毫秒的能夠執行的指令
}


注:rdtsc這個指令是獲得CPU自啓動之後的運行週期,不適合超線程和多核CPU

牆上時鐘:RTC
static struct timeval curr_time;
do_gettimeofday(&curr_time);


timer_list原理:
初始化timer時,首先取一個cpu變量【timer在哪一個cpu註冊,就在哪一個cpu觸發】
做爲本cpu上全部timer的控制結構,根據超時程度將timers進行分級管理,其中base->timer_jiffies爲最短的那個計時器的時間:
0 - 1<<8       tv1  index = expires
  - 1<<(8+6)   tv2    index = expires >> 8()
  - 1<<(8+2*6) tv3  index = expires >> 8+6()
  - 1<<(8+3*6) tv4  index = expires >> 8+2*6()
  ...          tv5  [不是直接根據索引來決定在數組的地方,由於數組的地方是有限的]
 

在tv2中,須要把 2^8 - 2^14之間的timers均勻放到一個2^6的數組中,只能2^8對齊,每一個數組鏈中最多放置2^8個timers.
日後類推..


tv1
1  2  3  4  5  6 ....    vec
a1 b1 c1 d1 e1 f1
a2 b2 c2
a3    c3
a4
最後一步,添加timer到目標隊列的尾部.

timer_list執行調度:
初始化:init_timers_cpu,主要是分配cpu變量,(除了啓動cpu0是固定的靜態空間),後再初始化tv1-tv5全部的timer header.
調用:這裏從時鐘irq中開始執行,增長jiffies.
run_timer_softirq(timer.c)
   ->  __run_timers
   
在函數update_process_times調用run_local_timers後觸發軟中斷:
raise_softirq(TIMER_SOFTIRQ);  

遍歷過程:
static inline void __run_timers(struct tvec_base *base)
{
    struct timer_list *timer;

    spin_lock_irq(&base->lock);
    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;                    //取其索引

        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;                                        //下一個處理的列表
        list_replace_init(base->tv1.vec + index, &work_list);        //清空這個列表,並處理
        while (!list_empty(head)) {                                    //遍歷這個列表下的全部timer
            void (*fn)(unsigned long);
            unsigned long data;
            bool irqsafe;
            timer = list_first_entry(head, struct timer_list,entry);//取出timer
            fn = timer->function;
            data = timer->data;
            irqsafe = tbase_get_irqsafe(timer->base);

            timer_stats_account_timer(timer);

            base->running_timer = timer;
            detach_expired_timer(timer, base);                        //timer脫鏈

            if (irqsafe) {
                spin_unlock(&base->lock);
                call_timer_fn(timer, fn, data);                        //調用實際的函數
                spin_lock(&base->lock);
            } else {
                spin_unlock_irq(&base->lock);
                call_timer_fn(timer, fn, data);
                spin_lock_irq(&base->lock);
            }
        }
    }
    base->running_timer = NULL;
    spin_unlock_irq(&base->lock);
}

核心的降級處理函數:
#define INDEX(N) ((base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK)

static int cascade(struct tvec_base *base, struct tvec *tv, int index)
{
    struct timer_list *timer, *tmp;
    struct list_head tv_list;
    list_replace_init(tv->vec + index, &tv_list);            //獲取
    list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
        __internal_add_timer(base, timer);
    }
    return index;
}

index=0 說明當前的tv1已經爲空,這個時候base->timer_jiffies應該已經 >256, INDEX(N)的做用就是減去基數獲取實際所在的
鏈表位置,在tv2中timer_jiffies逐漸增長,每次取tv2的一個數組鏈表而後釋放到tv1中(256),逐漸釋放,當tv2結束時,同理從tv3
釋放到tv2.數組

相關文章
相關標籤/搜索