Chinaunix首页 | 论坛 | 博客
  • 博客访问: 85621
  • 博文数量: 11
  • 博客积分: 386
  • 博客等级: 一等列兵
  • 技术积分: 240
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-02 17:11
文章分类

全部博文(11)

文章存档

2012年(11)

我的朋友

分类: LINUX

2012-09-15 23:09:35

     System timer(系统时钟)是一个硬件外设,并且可以设定它的频率,定期给CPU发送中断信号。

Dynamic timer或者kernel timer(通常所说的定时器)是软件的,用于定时,比如过一些时间之后发生某个事件,但dynamic timer的实现是依赖于system timer的。

 

TickHZ

两个system timer中断之间的间隔是一个tick,一秒钟有多少个tick称为HZ。通常HZ的大小为100或者1000(通常linux的时钟分辨率也是毫秒级)。系统每秒钟产生HZ个时钟中断。

HZ(也就是系统时钟)可以配置,尽管越高值代表着更高的时钟分辨率,但是伴随而来的弊端就是增大系统开销(因为有了更多的中断),而且会增加系统功耗(系统功耗和时钟频率很有关系)。用户空间有相应的USER_HZ可以使用。

 

Jiffies

内核会对system timer中断进行计数,在系统启动的时候,计数值为0Jiffies就是这么一个计数值,它是一个全局变量。在32位的平台上,它其实是另一个变量jiffies_64的低32位,jiffies_64每过一个tick递增1。之所以jiffies_64要是64位的原因是防止溢出,只要HZ设置的合理,长达几十年的时间里jiffies_64都不会溢出。而在64位平台上,jiffies_64 jiffies是一样的。32位平台上更为广泛的使用jiffies是因为访问它是原子化的,而访问jiffies_64则不一定。

很多时候,使用jiffies来衡量某个操作是否超时,比如:

  1. unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */ //-- 时间点1

  2. /* do some work ... */

  3. /* then see whether we took too long */
  4. if (timeout > jiffies) { // -- 时间点2
  5. /* we did not time out, good ... */
  6. } else {
  7. /* we timed out, error ... */
  8. }//时间点1的jiffies和时间点2的jiffies是不一样的
     请注意,用于和jiffies比较的变量,其数据类型定义为unsigned long

若是考虑到溢出的风险,使用大于符号“>”不够理想,系统提供了专门的比较函数:


  1. if (time_before(jiffies, timeout)) {
  2. /* we did not time out, good ... */
  3. } else {
  4. /* we timed out, error ... */
  5. }
    为了避免溢出,time_before的内部实现其实使用了有符号数:

diff = (long)t2 - (long)t1;//两个时间点的差异,不管有没有溢出,都可以比较

获取jiffies_64的方法是:

u64 get_jiffies_64(void);

 

精确计时

Jiffies的时间分辨率不高(毫秒级),若需要精确的比较两个时间点,可以利用CPU本身的寄存器。CPU的工作频率很高(MHz级别),现代CPU都有一个计数寄存器,以CPU时钟的时间频率自我累加,所以分辨率非常高,可以用于需要计时精确的场合:

  1. unsigned long ini, end;
  2. rdtscl(ini); rdtscl(end);
  3. printk("time lapse: %li\n", end - ini);
    rdtscl就是访问CPU本身的计数寄存器(counter),是平***立的,在MIPS平台上是:

#define rdtscl(dest) \

__asm__ __volatile__("mfc0 %0,$9; nop" : "=r" (dest))

 

Wall time

一般存在两个跟时钟有关的硬件,一个是前面提到的系统时钟(system timer),另外一个是RTCreal-time clock)。RTC在系统掉电时维护时间(wall time)的更新,在系统启动时,从RTC中读取当前时间,之后通过system timer来维护时间,并周期性更新至RTC

系统中有两种数据类型来描述wall time,一个是timeval,另一个是timespec,前者用秒和毫秒描述,后者用秒和纳秒描述,它们与jiffies之间可以互相转换:

unsigned long timespec_to_jiffies(struct timespec *value);

void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);

unsigned long timeval_to_jiffies(struct timeval *value);

void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);

获得当前的wall time的方法有:

do_gettimeofday() — timeval描述

current_kernel_time() — timespec描述

 

系统时钟ISR

system timer的作用是提供周期性中断,在system timer的中断处理程序中主要处理这么一些工作:

更新jiffies_64wall time

进程调度——用于减少处于运行状态进程的timeslice count,当count0时,need_resched标志置位,于是就开始调度新的进程。

处理定时器(dynamic timer),若有定时器expire了,则触发softirq来调用其handler


  1. static void tick_periodic(int cpu)
  2. {
  3.     if (tick_do_timer_cpu == cpu) {
  4.     write_seqlock(&xtime_lock);
  5.     /* Keep track of the next tick event */
  6.     tick_next_period = ktime_add(tick_next_period, tick_period);
  7.     do_timer(1);
  8.     write_sequnlock(&xtime_lock);
  9.     }

  10.     update_process_times(user_mode(get_irq_regs()));
  11.     profile_tick(CPU_PROFILING);
  12. }

  13. void do_timer(unsigned long ticks)
  14. {
  15.     jiffies_64 += ticks;
  16.     update_wall_time();
  17.     calc_global_load();
  18. }

  19. void update_process_times(int user_tick)
  20. {
  21.     struct task_struct *p = current;
  22.     int cpu = smp_processor_id();
  23.     /* Note: this timer irq context must be accounted for as well. */
  24.     account_process_tick(p, user_tick);
  25.     run_local_timers(); //触发softirq,expired dynamic timer其实通过softirq处理的
  26.     rcu_check_callbacks(cpu, user_tick);
  27.     printk_tick();
  28.     scheduler_tick();//减少进程的timerslice,若需要,还设置need_resched
  29.     run_posix_cpu_timers(p);
  30. }

定时器

在系统时钟ISR中见过run_local_timers,它触发TIMER_SOFTIRQ软中断,并处理所有expired timers

  1. void run_local_timers(void)
  2. {
  3.     hrtimer_run_queues();
  4.     raise_softirq(TIMER_SOFTIRQ); /* raise the timer softirq */
  5.     softlockup_tick();
  6. }
    定时器的数据结构为struct timer_list,使用方法为:

struct timer_list my_timer;//定义数据结构

init_timer(&my_timer);//初始化

my_timer.expires = jiffies + delay;    /* timer expires in delay ticks */

my_timer.data = 0;                     /* zero is passed to the timer handler */

my_timer.function = my_function;       /* function to run when timer expires */

add_timer(&my_timer);//激活定时器

此外,还有以下操作函数:

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

int del_timer_sync(struct timer_list *timer);

前面说的TIMER_SOFTIRQ软中断中会“处理”超时的定时器,其实指的是会调用timer_list中的function,也就是上述代码中的my_function。需要注意的是my_function是运行在中断上下文的,也就是不能sleep

定时器总是在激活它的CPU上运行。

定时器的简单实现是所有定时器都挂在一个链表上,每次系统时间中断来临之后,链表上的所有定时器的expire值都减一。之前所述,在系统时间中断的ISR中会触发softirq去处理超时的定时器,但在softirq若是将链表上的定时器都检查一边是否超时,就太过于复杂了。所以,内核将定时器按照不同的超时期限来分类,比如未来5秒超时的放在一个链表上,未来50秒超时的放在另一个链表上,这样softirq只要检查一个小巧的链表就可以了!

 

延时

1.忙等延时如果在忙等之前,内核禁用了抢占,忙等的时候系统就像死机了一样。更为值得注意的是,如果在忙等之前,系统已经禁用了中断,那么jiffies是不会被更新的,也就是忙等永远永远不会结束!

unsigned long timeout = jiffies + 10;        /* ten ticks */

while (time_before(jiffies, timeout))

;

 

2.调度延时这个方法的问题是不够精确,因为系统可能存在优先级更高的进程,等到原来的进程获得执行机会的时候,可能已经过了设定的时间了。

unsigned long delay = jiffies + 5*HZ;

while (time_before(jiffies, delay))

schedule()//或者cond_resched();//need_resched设置了,就会调度

 

3.挂起延时进程被挂起也就是处于sleep状态,直到某个事件来唤醒或者超时

long wait_event_timeout(wait_queue_head_t q, condition, long timeout);

long wait_event_interruptible_timeout(wait_queue_head_t q,condition, long timeout);

在这种情况下,没有事件(event)会唤醒进程,只能靠超时,所以有特别的函数:

set_current_state(TASK_INTERRUPTIBLE);

schedule_timeout (s * HZ);

其实,上述XXX_timeout靠的是之前所的定时器方法来实现的!

 

4.小延迟

Jiffies是毫秒级的分辨率,若需要更精准的延迟,采用如下方案:

void udelay(unsigned long usecs)

void ndelay(unsigned long nsecs)

void mdelay(unsigned long msecs)

它们依靠CPU本身的loop运算(_nop_空指令吧?)来完成。

有个BogoMIPS的概念,指的是一个jiffiesCPU可以loop运算几次,内核通过calibrate_dela()计算loops_per_jiffy变量。

阅读(1445) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~