Linux时间子系统之四(2):定时器…

4. tick_device

当内核没有配置成支持高精度定时器时,系统的tick由tick_device产生,tick_device其实是clock_event_device的简单封装,它内嵌了一个clock_event_device指针和它的工作模式:
[cpp]  viewplain copy
  1. struct tick_device  
  2.     struct clock_event_device *evtdev;  
  3.     enum tick_device_mode mode;  
  4. };  
在kernel/time/tick-common.c中,定义了一个per-cpu的tick_device全局变量,tick_cpu_device:
[cpp]  viewplain copy
  1.   
  2. DEFINE_PER_CPU(struct tick_device, tick_cpu_device);  
前面曾经说过,当machine的代码为每个cpu注册clock_event_device时,通知回调函数tick_notify会被调用,进而进入tick_check_new_device函数,下面让我们看看该函数如何工作,首先,该函数先判断注册的clock_event_device是否可用于本cpu,然后从per-cpu变量中取出本cpu的tick_device:
[cpp]  viewplain copy
  1. static int tick_check_new_device(struct clock_event_device *newdev)  
  2.  
  3.         ......  
  4.     cpu smp_processor_id();  
  5.     if (!cpumask_test_cpu(cpu, newdev->cpumask))  
  6.         goto out_bc;  
  7.   
  8.     td &per_cpu(tick_cpu_device, cpu);  
  9.     curdev td->evtdev;  
如果不是本地clock_event_device,会做进一步的判断:如果不能把irq绑定到本cpu,则放弃处理,如果本cpu已经有了一个本地clock_event_device,也放弃处理:

[cpp]  viewplain copy
  1. if (!cpumask_equal(newdev->cpumask, cpumask_of(cpu)))  
  2.                ......  
  3.     if (!irq_can_set_affinity(newdev->irq))  
  4.         goto out_bc;  
  5.                ......  
  6.     if (curdev && cpumask_equal(curdev->cpumask, cpumask_of(cpu)))  
  7.         goto out_bc;  
  8.  
反之,如果本cpu已经有了一个clock_event_device,则根据是否支持单触发模式和它的rating值,决定是否替换原来旧的clock_event_device:

[cpp]  viewplain copy
  1. if (curdev)  
  2.     if ((curdev->features CLOCK_EVT_FEAT_ONESHOT) &&  
  3.         !(newdev->features CLOCK_EVT_FEAT_ONESHOT))  
  4.         goto out_bc;  // 新的不支持单触发,但旧的支持,所以不能替换  
  5.     if (curdev->rating >= newdev->rating)  
  6.         goto out_bc;  // 旧的比新的精度高,不能替换  
  7.  
在这些判断都通过之后,说明或者来cpu还没有绑定tick_device,或者是新的更好,需要替换:
[cpp]  viewplain copy
  1. if (tick_is_broadcast_device(curdev))  
  2.     clockevents_shutdown(curdev);  
  3.     curdev NULL;  
  4.  
  5. clockevents_exchange_device(curdev, newdev);  
  6. tick_setup_device(td, newdev, cpu, cpumask_of(cpu));  
上面的tick_setup_device函数负责重新绑定当前cpu的tick_device和新注册的clock_event_device,如果发现是当前cpu第一次注册tick_device,就把它设置为TICKDEV_MODE_PERIODIC模式,如果是替换旧的tick_device,则根据新的tick_device的特性,设置为TICKDEV_MODE_PERIODIC或TICKDEV_MODE_ONESHOT模式。可见,在系统的启动阶段,tick_device是工作在周期触发模式的,直到框架层在合适的时机,才会开启单触发模式,以便支持NO_HZ和HRTIMER。

5. tick事件的处理--最简单的情况

clock_event_device最基本的应用就是实现tick_device,然后给系统定期地产生tick事件,通用时间框架对clock_event_device和tick_device的处理相当复杂,因为涉及配置项:CONFIG_NO_HZ和CONFIG_HIGH_RES_TIMERS的组合,两个配置项就有4种组合,这四种组合的处理都有所不同,所以这里我先只讨论最简单的情况:
  • CONFIG_NO_HZ == 0;
  • CONFIG_HIGH_RES_TIMERS == 0;
在这种配置模式下,我们回到上一节的tick_setup_device函数的最后:
[cpp]  viewplain copy
  1. if (td->mode == TICKDEV_MODE_PERIODIC)  
  2.     tick_setup_periodic(newdev, 0);  
  3. else  
  4.     tick_setup_oneshot(newdev, handler, next_event);  
因为启动期间,第一个注册的tick_device必然工作在TICKDEV_MODE_PERIODIC模式,所以tick_setup_periodic会设置clock_event_device的事件回调字段event_handler为tick_handle_periodic,工作一段时间后,就算有新的支持TICKDEV_MODE_ONESHOT模式的clock_event_device需要替换,再次进入tick_setup_device函数,tick_setup_oneshot的handler参数也是之前设置的tick_handle_periodic函数,所以我们考察tick_handle_periodic即可:
[cpp]  viewplain copy
  1. void tick_handle_periodic(struct clock_event_device *dev)  
  2.  
  3.     int cpu smp_processor_id();  
  4.     ktime_t next;  
  5.   
  6.     tick_periodic(cpu);  
  7.   
  8.     if (dev->mode != CLOCK_EVT_MODE_ONESHOT)  
  9.         return 
  10.   
  11.     next ktime_add(dev->next_event, tick_period);  
  12.     for (;;)  
  13.         if (!clockevents_program_event(dev, next, false))  
  14.             return 
  15.         if (timekeeping_valid_for_hres())  
  16.             tick_periodic(cpu);  
  17.         next ktime_add(next, tick_period);  
  18.      
  19.  
该函数首先调用tick_periodic函数,完成tick事件的所有处理,如果是周期触发模式,处理结束,如果工作在单触发模式,则计算并设置下一次的触发时刻,这里用了一个循环,是为了防止当该函数被调用时,clock_event_device中的计时实际上已经经过了不止一个tick周期,这时候,tick_periodic可能被多次调用,使得jiffies和时间可以被正确地更新。tick_periodic的代码如下:
[cpp]  viewplain copy
  1. static void tick_periodic(int cpu)  
  2.  
  3.     if (tick_do_timer_cpu == cpu)  
  4.         write_seqlock(&xtime_lock);  
  5.   
  6.           
  7.         tick_next_period ktime_add(tick_next_period, tick_period);  
  8.   
  9.         do_timer(1);  
  10.         write_sequnlock(&xtime_lock);  
  11.      
  12.   
  13.     update_process_times(user_mode(get_irq_regs()));  
  14.     profile_tick(CPU_PROFILING);  
  15.  
如果当前cpu负责更新时间,则通过do_timer进行以下操作:
  • 更新jiffies_64变量;
  • 更新墙上时钟;
  • 每10个tick,更新一次cpu的负载信息;
调用update_peocess_times,完成以下事情:
  • 更新进程的时间统计信息;
  • 触发TIMER_SOFTIRQ软件中断,以便系统处理传统的低分辨率定时器;
  • 检查rcu的callback;
  • 通过scheduler_tick触发调度系统进行进程统计和调度工作;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值