zoukankan      html  css  js  c++  java
  • linux 定时器原理

    内核定时器:
        unsigned long timeout = jiffies + (x * HZ);
        while(1) {
            // Check the condition.
            // Take a schedule.
            if (time_after(jiffies, timeout)) {
                printk("Timeout ");
                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.

  • 相关阅读:
    swift2.2当中的inout参数的使用
    Swift的7大误区
    Swift 设计指南之 编程规范
    我为什么用 SQLite 和 FMDB 而不用 Core Data
    ios学习笔记——代理设计模式
    ios学习笔记——UIImagePickerController
    ios学习笔记——保存图片到相册
    KVC中setValuesForKeysWithDictionary: (转载)
    ios学习笔记——GCD简介
    ios学习笔记——操作队列NSOperation的基本操作
  • 原文地址:https://www.cnblogs.com/wenhuisun/p/3158625.html
Copyright © 2011-2022 走看看