当前位置:   article > 正文

内核定时任务timer_list

内核模块定时任务怎么做

    使用定时器任务,可以让内核在将来的一个指定时刻执行一段指定的代码。内核定时器相关的接口在linux/timer.h文件中。

    本文将会先介绍定时任务的使用,然后在此基础上了解其内部的实现逻辑。

一、定时任务结构体表示:

  1. struct timer_list {
  2.     struct list_head entry;  //用于链接到内核定时器链表中                                                                                                             
  3.     unsigned long expires;  //定时任务过期时间
  4.     void (*function)(unsigned long); //定时任务的工作函数
  5.     unsigned long data; //定时任务工作函数参数
  6.     struct tvec_base *base; //定时任务关联的内核定时器
  7. #ifdef CONFIG_TIMER_STATS
  8.     void *start_site;
  9.     char start_comm[16];
  10.     int start_pid;
  11. #endif
  12. #ifdef CONFIG_LOCKDEP
  13.     struct lockdep_map lockdep_map;
  14. #endif
  15. };

二、定时任务相关的接口:

1. 初始化定时任务

  1. #define TIMER_INITIALIZER(_function, _expires, _data) {     \
  2.         .entry = { .prev = TIMER_ENTRY_STATIC },    \
  3.         .function = (_function),            \
  4.         .expires = (_expires),              \
  5.         .data = (_data),                \
  6.         .base = &boot_tvec_bases,           \
  7.         __TIMER_LOCKDEP_MAP_INITIALIZER(        \
  8.             __FILE__ ":" __stringify(__LINE__)) \
  9.     }
  10.     
  11. #define DEFINE_TIMER(_name, _function, _expires, _data)     \
  12.     struct timer_list _name =               \
  13.         TIMER_INITIALIZER(_function, _expires, _data)
  14.      
  15. #define setup_timer(timer, fn, data)                    \
  16.     do {                                \
  17.         static struct lock_class_key __key;         \
  18.         setup_timer_key((timer), #timer, &__key, (fn), (data));\
  19.     } while (0)

`    主要是完成定时任务的成员初始化,这里要注意一下.base = &boot_tvec_bases;boot_tvec_bases是内核在初始化的时候创建好的。

    其实过期时间expires在初始化的时候设置,一般是没有什么意义的,通常都是在注册定时器任务的时候才设置过期时间。

2.  注册定时任务:

void add_timer(struct timer_list *timer);

    当一个定时任务注册到内核的定时器列表后,就会处于激活状态。这里要注意的是:注册的定时任务在只会被执行一次,因为在执行的时候会将其从定时器链表中移除,如果需要实现每隔一段时间就执行,则需要在其定时任务函数中再次注册,才能再次被执行。

3. 注销定时任务:

  1. int del_timer(struct timer_list * timer);
  2. int del_timer_sync(struct timer_list *timer);

    有可能在注销定时任务的时候,此时的定时任务正在被执行中,那么调用del_timer_sync()就会等待任务被执行完毕后再注销。

4. 修改定时任务的过期时间

    当调用add_timer()函数将定时任务注册后,定时任务就处于激活的状态,此时如果需要修改过期时间,则必须通过如下接口来完成:

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

5. 判断定时任务的状态:

  1. static inline int timer_pending(const struct timer_list * timer)
  2. {
  3.     return timer->entry.next != NULL;
  4. }


    看完上面的接口介绍之后,再看一个简单的例子:

  1. #include <linux/module.h>
  2. #include <linux/timer.h>
  3. #include <linux/delay.h>
  4. #define ENTER() printk(KERN_DEBUG "%s() Enter", __func__)
  5. #define EXIT() printk(KERN_DEBUG "%s() Exit", __func__)
  6. #define ERR(fmt, args...) printk(KERN_ERR "%s()-%d: " fmt "\n", __func__, __LINE__, ##args)
  7. #define DBG(fmt, args...) printk(KERN_DEBUG "%s()-%d: " fmt "\n", __func__, __LINE__, ##args)
  8. struct test_timer {
  9.     struct timer_list t;
  10.     unsigned long nums;
  11. };
  12. static void my_timer_func(unsigned long data)
  13. {
  14.     struct test_timer *timer = (struct test_timer *)data;
  15.     
  16.     DBG("nums: %lu", timer->nums--);
  17.     if (timer->nums > 0) {
  18.         mod_timer(&timer->t, timer->t.expires + HZ); //再次注册定时任务
  19.     }
  20. }
  21. static struct test_timer my_timer;
  22. static int __init timer_demo_init(void)
  23. {
  24.     setup_timer(&my_timer.t, my_timer_func, (unsigned long)&my_timer);
  25.     my_timer.nums = 30;
  26.     msleep_interruptible(2000);
  27.     DBG("before mod_timer");
  28.     mod_timer(&my_timer.t, jiffies + 2 * HZ);
  29.     DBG("success");
  30.     return 0;
  31. }
  32. static void __exit timer_demo_exit(void)
  33. {
  34.     ENTER();
  35.     while (my_timer.nums > 0) {
  36.         DBG("waiting my_timer exit");
  37.         msleep_interruptible(1000);
  38.     }
  39.     EXIT();
  40. }
  41. MODULE_LICENSE("GPL");
  42. module_init(timer_demo_init);
  43. module_exit(timer_demo_exit);

三、定时任务的注册:

    接下来,分析一下内核是如何管理我们注册的定时任务的,首先从add_timer()开始:

  1. void add_timer(struct timer_list *timer)                                                                                                  
  2. {
  3.     BUG_ON(timer_pending(timer));
  4.     mod_timer(timer, timer->expires);
  5. }

    这里可以看出,我们调用add_timer()和调用mod_timer()进行注册,是一样的。

  1. int mod_timer(struct timer_list *timer, unsigned long expires)                                                                            
  2. {
  3.     /*
  4.      * This is a common optimization triggered by the
  5.      * networking code - if the timer is re-modified
  6.      * to be the same thing then just return:
  7.      */
  8.     if (timer_pending(timer) && timer->expires == expires)
  9.         return 1;
  10.     return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
  11. }

    先判断下定时任务是否已经处于激活状态,如果已经处于激活状态,则直接返回,避免重复注册,否则调用__mod_timer():

  1. static inline int __mod_timer(struct timer_list *timer, unsigned long expires,
  2.                         bool pending_only, int pinned)
  3. {
  4.     struct tvec_base *base, *new_base;
  5.     unsigned long flags;
  6.     int ret = 0 , cpu;
  7.     timer_stats_timer_set_start_info(timer);
  8.     BUG_ON(!timer->function);
  9.     base = lock_timer_base(timer, &flags);
  10.     /*如果timer_list已经处于激活状态,则先将其从链表中移除:detach_timer()*/
  11.     if (timer_pending(timer)) {
  12.         detach_timer(timer, 0);
  13.         if (timer->expires == base->next_timer &&
  14.             !tbase_get_deferrable(timer->base))
  15.             base->next_timer = base->timer_jiffies;
  16.         ret = 1;
  17.     } else {
  18.         if (pending_only)
  19.             goto out_unlock;
  20.     }
  21.     debug_activate(timer, expires);
  22.     new_base = __get_cpu_var(tvec_bases);
  23.     cpu = smp_processor_id();
  24. #if defined(CONFIG_NO_HZ) && defined(CONFIG_SMP)
  25.     if (!pinned && get_sysctl_timer_migration() && idle_cpu(cpu)) {
  26.         int preferred_cpu = get_nohz_load_balancer();
  27.         if (preferred_cpu >= 0)
  28.             cpu = preferred_cpu;
  29.     }
  30. #endif
  31.     new_base = per_cpu(tvec_bases, cpu);
  32.     if (base != new_base) {
  33.         /*
  34.          * We are trying to schedule the timer on the local CPU.
  35.          * However we can't change timer's base while it is running,
  36.          * otherwise del_timer_sync() can't detect that the timer's
  37.          * handler yet has not finished. This also guarantees that
  38.          * the timer is serialized wrt itself.
  39.          */
  40.         if (likely(base->running_timer != timer)) {
  41.             /* See the comment in lock_timer_base() */
  42.             timer_set_base(timer, NULL);
  43.             spin_unlock(&base->lock);
  44.             base = new_base; 
  45.             spin_lock(&base->lock);
  46.             timer_set_base(timer, base);
  47.         }
  48.     }
  49.     timer->expires = expires;
  50.     if (time_before(timer->expires, base->next_timer) &&
  51.         !tbase_get_deferrable(timer->base))
  52.         base->next_timer = timer->expires;
  53.     internal_add_timer(base, timer);
  54. out_unlock:
  55.     spin_unlock_irqrestore(&base->lock, flags);
  56.     return ret;
  57. }

    最终调用internal_add_timer()完成注册:

  1. static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
  2. {
  3.     unsigned long expires = timer->expires;
  4.     unsigned long idx = expires - base->timer_jiffies;
  5.     struct list_head *vec;
  6.     /* 根据过期时间选择合适的的定时器链表 */
  7.     if (idx < TVR_SIZE) {
  8.         int i = expires & TVR_MASK;
  9.         vec = base->tv1.vec + i;
  10.     } else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
  11.         int i = (expires >> TVR_BITS) & TVN_MASK;
  12.         vec = base->tv2.vec + i;
  13.     } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
  14.         int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
  15.         vec = base->tv3.vec + i;
  16.     } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
  17.         int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
  18.         vec = base->tv4.vec + i;
  19.     } else if ((signed long) idx < 0) {
  20.         /*
  21.          * Can happen if you add a timer with expires == jiffies,
  22.          * or you set a timer to go off in the past
  23.          */
  24.         vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
  25.     } else {
  26.         int i;
  27.         /* If the timeout is larger than 0xffffffff on 64-bit
  28.          * architectures then we use the maximum timeout:
  29.          */
  30.         if (idx > 0xffffffffUL) {
  31.             idx = 0xffffffffUL;
  32.             expires = idx + base->timer_jiffies;
  33.         }
  34.         i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
  35.         vec = base->tv5.vec + i;
  36.     }
  37.     /*
  38.      * Timers are FIFO:
  39.      */
  40.     list_add_tail(&timer->entry, vec); /*添加到定时器链表尾部*/
  41. }

    这里需要补充说明一下struct tvsec_base结构体,看完之后就大致清楚是怎么管理的了:

  1. /*
  2.  * per-CPU timer vector definitions:
  3.  */
  4. #define TVN_BITS (CONFIG_BASE_SMALL ? 4 : 6)
  5. #define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)
  6. #define TVN_SIZE (1 << TVN_BITS)
  7. #define TVR_SIZE (1 << TVR_BITS)                                                                                                          
  8. #define TVN_MASK (TVN_SIZE - 1)
  9. #define TVR_MASK (TVR_SIZE - 1)
  10. struct tvec {
  11.     struct list_head vec[TVN_SIZE];
  12. };
  13. struct tvec_root {
  14.     struct list_head vec[TVR_SIZE];
  15. };
  16. struct tvec_base {                                                                                                                        
  17.     spinlock_t lock;
  18.     struct timer_list *running_timer; //保存正在运行的定时任务
  19.     unsigned long timer_jiffies;
  20.     unsigned long next_timer;
  21.     struct tvec_root tv1;
  22.     struct tvec tv2;
  23.     struct tvec tv3;
  24.     struct tvec tv4;
  25.     struct tvec tv5;
  26. } ____cacheline_aligned;

    每一个CPU都会包含一个struct tvsec_base类型的对象,用于存储注册到每个CPU上的定时任务。看完这个结构体,可以发现包含有5个链表数组,分别用于存储不同过期时间的定时任务,分布如下:

    过期时间在0 ~ (1<<8)   -->  tv1, 具体在tv1.vec数组的哪个链表,则是通过掩码来确定,即:  过期时间   &   ((1 << 8) - 1)

    过期时间在(1 << 8) ~ (1 << (8+6))  --> tv2, 具体在tv2.vec数组的哪个链表,则是通过掩码来确定,即:  (过期时间 -(1 << 8))   &   ((1<<6)  - 1)

    过期时间在(1 << (8+6)) ~  (1 << (8+2*6))  -->  tv3,具体在tv3.vec数组的哪个链表,也是通过掩码确定,即:  (过期时间 - (1 << (8+1*6)))    &   ((1<<6) - 1)

    过期时间在(1 << (8 + 6*2)) ~ (1 << (8 + 3*6))  -->  tv4, 具体在tv4.vec数组的哪个链表,也是通过掩码确定,即:  (过期时间 - (1 << (8+2*6))   &    ((1 << 6)- 1)

    如果过期时间超过(1 << (8 + 3 * 6))  -->  tv5, 具体在tv5.vec数组的哪个链表,也是通过掩码确定,即: (过期时间 - ((1 << (8+3*6))    &    ((1 << 6) - 1)

    之所以要分成5个数组,就是为了提高效率,因为当有中断发生,就会触发内核去检查是否存在过期的定时任务需要执行,如果把所有的链表都去遍历,那么显然效率会很低下,所以内核每次只会去检查tv1.sec数组上的链表是否存在需要执行的定期任务。具体是怎么执行的,下面会有分析。这里暂时可以理解为注册一个定时任务,就是将此定时任务保存到本地CPU上的定时器的某个链表中。


四、定时任务的执行:

    定时器的执行,是在软中断中执行的,是在一个原子上下文环境中,即不允许定时任务发生睡眠等待。

    在内核初始化的时候,会调用init_timers()注册软中断:

  1. void __init init_timers(void)
  2. {
  3.     int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
  4.                 (void *)(long)smp_processor_id());
  5.     init_timer_stats();
  6.     BUG_ON(err == NOTIFY_BAD);
  7.     register_cpu_notifier(&timers_nb);                                                                                                    
  8.     open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
  9. }

    调用open_softirq()函数注册定时器的软中断,处理函数为run_timer_softirq。软中断是由软件模拟的中断,大部分情况下软中断会在irq_exit阶段被执行,在irq_exit阶段没被处理完的软中断,会在守护进程ksoftirqd中执行。这里暂时不深究软中断的实现原理,暂时认为中断发生之后,会触发定时器软中断的处理函数run_timer_softirq的执行。

  1. /*
  2.  * This function runs timers and the timer-tq in bottom half context.
  3.  */
  4. static void run_timer_softirq(struct softirq_action *h)                                                                                   
  5. {
  6.     struct tvec_base *base = __get_cpu_var(tvec_bases);
  7.     perf_event_do_pending();
  8.     hrtimer_run_pending();
  9.     // 判断是否有超时,jiffies >= base->timer_jiffies则表示有超时,有定时任务需要执行。
  10.     if (time_after_eq(jiffies, base->timer_jiffies))
  11.         __run_timers(base);
  12. }
  13. /**
  14.  * __run_timers - run all expired timers (if any) on this CPU.
  15.  * @base: the timer vector to be processed.
  16.  *
  17.  * This function cascades all vectors and executes all expired timer
  18.  * vectors.
  19.  */
  20. static inline void __run_timers(struct tvec_base *base)
  21. {
  22.     struct timer_list *timer;
  23.     spin_lock_irq(&base->lock);
  24.     while (time_after_eq(jiffies, base->timer_jiffies)) {
  25.         struct list_head work_list;
  26.         struct list_head *head = &work_list;
  27.         int index = base->timer_jiffies & TVR_MASK;
  28.         /*
  29.          * Cascade timers:
  30.          */
  31.          // 寻找已经超时的定时任务链表,并将超时的链表上的定时任务移动到上一级的链表
  32.         if (!index &&
  33.             (!cascade(base, &base->tv2, INDEX(0))) &&
  34.                 (!cascade(base, &base->tv3, INDEX(1))) &&
  35.                     !cascade(base, &base->tv4, INDEX(2)))
  36.             cascade(base, &base->tv5, INDEX(3));
  37.         ++base->timer_jiffies;
  38.         
  39.         list_replace_init(base->tv1.vec + index, &work_list);
  40.         while (!list_empty(head)) {
  41.             void (*fn)(unsigned long);
  42.             unsigned long data;
  43.             timer = list_first_entry(head, struct timer_list,entry);
  44.             fn = timer->function; // 定时任务函数
  45.             data = timer->data;
  46.             timer_stats_account_timer(timer);
  47.             set_running_timer(base, timer);
  48.             detach_timer(timer, 1);
  49.             spin_unlock_irq(&base->lock);
  50.             {
  51.                 int preempt_count = preempt_count();
  52.                 
  53. #ifdef CONFIG_LOCKDEP
  54.                 /*
  55.                  * It is permissible to free the timer from
  56.                  * inside the function that is called from
  57.                  * it, this we need to take into account for
  58.                  * lockdep too. To avoid bogus "held lock
  59.                  * freed" warnings as well as problems when
  60.                  * looking into timer->lockdep_map, make a
  61.                  * copy and use that here.
  62.                  */
  63.                 struct lockdep_map lockdep_map = timer->lockdep_map;
  64. #endif
  65.                 /*
  66.                  * Couple the lock chain with the lock chain at
  67.                  * del_timer_sync() by acquiring the lock_map
  68.                  * around the fn() call here and in
  69.                  * del_timer_sync().
  70.                  */
  71.                 lock_map_acquire(&lockdep_map);
  72.                 trace_timer_expire_entry(timer);
  73.                 fn(data); // 执行定时任务函数
  74.                 trace_timer_expire_exit(timer);
  75.                 lock_map_release(&lockdep_map);
  76.                 if (preempt_count != preempt_count()) {
  77.                     printk(KERN_ERR "huh, entered %p "
  78.                            "with preempt_count %08x, exited"
  79.                            " with %08x?\n",
  80.                            fn, preempt_count,
  81.                            preempt_count());
  82.                     BUG();
  83.                 }
  84.             }
  85.             spin_lock_irq(&base->lock);
  86.         }
  87.     }
  88.     set_running_timer(base, NULL);
  89.     spin_unlock_irq(&base->lock);
  90. }

    这段代码的逻辑比较复杂,我也还不能完全理解,不过从上面来看,就是把已经超时的链表取出到work_list,然后依次执行work_list上的定时任务。

    在代码的前面部分,有一段是重新调整定时任务链表的操作:

  1. int index = base->timer_jiffies & TVR_MASK;
  2.         /*
  3.          * Cascade timers:
  4.          */
  5.         if (!index &&
  6.             (!cascade(base, &base->tv2, INDEX(0))) &&
  7.                 (!cascade(base, &base->tv3, INDEX(1))) &&
  8.                     !cascade(base, &base->tv4, INDEX(2)))
  9.             cascade(base, &base->tv5, INDEX(3));
  10.         
  11.         ++base->timer_jiffies;

    这里要先看一下INDEX宏和cascade()函数:

  1. static int cascade(struct tvec_base *base, struct tvec *tv, int index)
  2. {
  3.     /* cascade all the timers from tv up one level */
  4.     struct timer_list *timer, *tmp;                                                                                                       
  5.     struct list_head tv_list;
  6.     
  7.     list_replace_init(tv->vec + index, &tv_list);
  8.     
  9.     /*
  10.      * We are removing _all_ timers from the list, so we
  11.      * don't have to detach them individually.
  12.      */
  13.     list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
  14.         BUG_ON(tbase_get_base(timer->base) != base);
  15.         internal_add_timer(base, timer);
  16.     }
  17.     
  18.     return index;
  19. }
  20. #define INDEX(N) ((base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK)

    可以看出INDEX宏是根据定时器的过期时间来得到其所在数组的索引,而cascade()函数就是将此索引对应的链表取出,然后将此链表上的每一个定时任务从新加入到定时器中。

转载于:https://my.oschina.net/kaedehao/blog/631725

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/183451
推荐阅读
相关标签
  

闽ICP备14008679号