时间管理在内核中占用很是重要的地位,内核中有大量的函数都须要基于时间驱动的,内核对相对时间和绝对时间都很是须要。linux
内核必须在硬件的帮助下才能计算和管理时间,系统定时器以某种频率自行触发(击中hitting或者射中popping)时钟中断,该频率能够经过编程预约,称做节拍率。编程
由于预编的节拍率对内核来讲是可知的,因此内核知道连续两次时钟中断的间隔时间,这个间隔时间称为节拍(tick),它等于节拍率分之一。缓存
下面是利用时间中断周期执行的工做:安全
系统定时器频率是经过静态预处理定义的,也就是HZ,在系统启动时按照HZ值对硬件进行设置。数据结构
内核在<asm/param.h>文件中定义了这个值。异步
编写内核代码时,不要认为HZ值是一个固定不变的值。 ide
提升节拍率意味着时钟中断产生得更加频繁,因此中断处理程序也会更频繁地执行。函数
节拍率越高,意味着时钟中断频率越高,意味着系统负担越重。中断处理程序占用处理器的时间越多。增长了耗电和打乱了处理器的高速缓存。工具
全局变量jiffies用来记录自系统启动以来产生的节拍的总数。启动时内核初始化为0ui
由于一秒内时钟中断的次数等于HZ,因此jiffies一秒内增长的值也就为HZ。系统运行时间以秒为单位计算,就等于jiffies/HZ。
jiffies定义于文件<linux/jiffies.h>中:
extern unsigned long volatile jiffies;
以下的使用例子:
jiffies = seconds * HZ jiffies/HZ = seconds /* jiffies和seconds相互转换 */ unsigned long time_stamp = jiffies; /* 如今 */ unsigned long next_tick = jiffies+1; /* 从如今开始1个节拍 */ unsigned long later = jiffies+5*HZ; /* 从如今开始5秒 */ unsigned long fraction = jiffies + HZ / 10; /* 从如今开始1/10秒 */
jiffies变量老是无符号长整数,在32位上是32位,在64位上是64位。由于jiffies会溢出,
jiffies_64定义在<linux/jiffies.h>中:
extern u64 jiffies_64; jiffies = jiffies_64;
若是jiffies超过最大存放范围后就会发生溢出,它的值会回绕到0。
unsgined long timeout = jiffies + HZ/2; /* 0.5秒后超过 */ /* 执行一些任务 ... */ /* 而后查看是否花的时间过长 */ if(timeout>jiffies) { /* 没有超时,很好 ... */ } else { /* 超时了,发生错误 ... */ }
内核提供给了四个宏来帮助比较节拍计数,他们能正确地处理节拍计数的回绕状况。这些宏在文件<linux/jiffies.h>中:
#define time_after(unknown, known) ((long)(known) - (long)(unknown)<0) #define time_before(unknown, known) ((long)(known) - (long)(unknown)<0) #define time_after_eq(unknown, known) ((long)(known) - (long)(unknown)>=0) #define time_before_eq(unknown, known) ((long)(known) - (long)(unknown)>=0)
宏time_after(unknown, known);当unkown超过指定known时,返回真,不然返回假。
宏time_before(unknown, known);当时间unknow 没超过指定的know时,返回真,不然返回假
后面两个宏,当两个参数相等时,才返回真。
若是改变了内核中HZ的值,用户空间中某些程序形成异常结果。内核是以节拍数/秒的形式给用户空间导出这个值的。
所以内核定义了USER_HZ来表明用户空间看到的HZ值。内核能够用函数jiffies_to_clock_t()(在kernel/time.c中)将一个HZ表示的节拍计数转换成一个由USER_HZ表示的节拍计数。
return x / ( HZ / USER_HZ);
在须要把以节拍数/秒为单位的值导出到用户空间时,须要使用上面这几个函数,好比:
unsigned long start; unsgined long total_time; start = jiffies; /* 执行一些任务 ... */ total_time = jiffies - start; printk("That took %lu ticks\n", jiffies_to_clock_t(total_time));
体系结构提供了两种设备进行计时,一种系统定时器,一种实时定时器。他们有着相同的做用和设计思路。
实时时钟(RTC)是用来持久存放系统时间的设备,即使系统关闭后,它也能够靠主板上的微型电池提供的电力保持系统的计时。
系统定时器是内核定时机制中最为重要的角色。
有些体系结构体是经过电子晶振进行分频来实现系统定时器,还有些体系结构提供一个衰减测量器。
衰减测量器设置一个初始值,该值以固定频率递减,当减到零时,触发一个中断。
时钟中断处理程序能够划分为两个部分:体系结构相关部分和体系结构无关部分。
绝大多数处理程序最低限度也都要执行以下工做:
tick_periodic()执行下面更多的工做:
上述工做分别都由单独的函数负责完成,因此tick_periodic()例程代码看起来很是简单。
static void tick_periodic(int cpu) { if(tick_do_timer_cpu == cpu) { write_seqlock(&xtime_lock); /* 记录下一节拍事件 */ tick_next_period = ktime_add(tick_next_period, tick_period); do_timer(1); write_sequnlock(&xtime_lock); } update_process_times(user_mode(get_irq_regs())); profile_tick(CPU_PROFILING); }
不少重要的操做都在do_timer()和update_process_times()函数中进行。前者承担着对jiffies_64的实际增长操做:
函数update_wall_timer根据所流逝的时间更新墙上的时钟,calc_global_load()更新系统的平均负载统计值。
void do_timer(unsigned long ticks) { jiffies_64 += ticks; update_wall_time(); calc_global_load(); }
do_timer返回时,调用update_process_times()更新所耗费的各类节拍数,经过user_tick区别话费在用户空间仍是内核空间:
void update_process_times(int user_tick) { struct task_struct *p = current; int cpu = smp_processor_id(); /* 注意:也必须对这个时钟irg的上下文说明一下缘由 */ account_process_tick(p, user_tick); run_local_timers(); rcu_check_callbacks(cpu, user_tick); printk_tick(); scheduler_tick(); run_posix_cpu_timers(p); }
account_process_tick()函数对进程的时间进行实质性更新:
void account_process_tick(struct task_struct *p, int user_tick) { cputime_t one_jiffy_scaled = cputime_to_scaled(cputime_one_jiffy); struct rq *rq = this_rq(); if(user_tick) account_user_time(p, cputime_one_jiffy, one_jiffy_scaled); else if((p != rq->idle) || (irq_count() != HARDIRQ_OFFSET)) account_system_time(p, HARDIRQ_OFFSET, cputime_one_jiffy, one_jiffy_scaled); else account_idle_time(cputime_one_jiffy); }
内核对进程进行时间计数时,是根据中断发生时处理器所处的模式进行分类统计的,它把上一个节拍所有算给了进程。
但事实上进程在上一个节拍期间,可能屡次进入和退出内核模式,并且在上一个节拍器件,该进程也不必定是惟一一个运行进程。
run_lock_timers()函数标记了一个软中断去处理全部到期的定时器。
scheduler_tick()函数负责减小当前运行进程的时间片计数值而且在须要时设置need_resched标志。
tick_periodic()函数执行完毕后返回与体系结构相关的中断处理程序,继续执行后面的工做,释放xtime_lock锁,而后退出。
以上所有工做每1/HZ秒都要发生一次,就是说x86上时钟中断处理程序每秒执行100次或者1000次。
当前实际时间(墙上时间)定义在文件kernel/time/timekeeping.c中:
struct timespec xtime;
timespec数据结构定义在文件<linux/time.h>中,形式以下:
struct timespec { _kernel_time_t tv_sec; /* 秒 */ long tv_nsec; /* ns */ };
xtime.tv_sec以秒为单位,存放着自1970年1月1日(UTC)以来通过的时间,xtime变量须要使用xtime_lock锁,它是一个seqlock锁,不是普通的自旋锁。
更新xtime首先要申请一个seqlock锁:
write_seqlock(&xtime_lock); /* 更新xtime ... */ write_sequnlock(&xtime_lock);
读取xtime时也要使用read_seqbegin()和read_seqretry()函数:
unsigned long seq; do { unsigned long lost; seq = read_seqbegin(&xtime_lock); usec = timer->get_offset(); lost = jiffies - wall_jiffies; if(lost) usec += lost * (1000000 / HZ); sec = xtime.tv_sec; usec += (xtime.tv_nsec / 1000); } while(read_seqretry(&xtime_lock, seq));
该循环不断重复,直到读者确认读取数据没有写操做介入。若是循环期间更新了xitme,read_seqretry()函数就返回无效序列号,继续循环等待。
用户空间取得的墙上时间接口是gettimeofday(),在内核中对应系统调用为sys_gettimeofday(),定义于kernel/time.c:
asmlinkage long sys_gettimeofday(struct timeval *tv, struct timezone *tz) { if(likely(tv)) { struct timeval ktv; do_gettimeofday(&ktv); if(copy_to_user(tv, &ktv, sizeof(ktv))) return -EFAULT; } if(unlikely(tz)) { if(copy_to_user(tz, &sys_tz, sizeof(sys_tz))) return -EFAULT; } return 0; }
若是用户提供的tv参数非空,do_gettimeofday()函数将被调用。它循环读取xtime操做。若是tz参数为空,将把系统时区返回用户。若是在给用户空间拷贝墙上时间或时区时发生错误,返回-EFAULT;成功返回0
gettimeodfay()函数几乎彻底取代了time()系统调用。系统调用settimeofday()来设置当前时间,须要具备CAP_SYS_TIME权能。
除了更新xtime时间之外,内核不会像用户空间程序那样频繁使用xtime。 在文件系统的实现代码中存放访问时间戳时须要使用xtime。
定时器是管理内核流逝的时间的基础。咱们须要一种工具,可以使工做在指定时间点上执行。
定时器由结构timer_list表示,定义在文件<linux/timer.h>中。
struct timer_list { struct list_head entry; /* 定时器链表的入口 */ unsigned long expires; /* 以jiffies为单位的定时器 */ void (*function)(unsigned long); /* 定时器处理函数 */ unsigned long data; /* 传给处理函数的长整型参数 */ struct tvec_t_base_s *base; /* 定时器内部值,用户不要使用 */ };
内核提供了一组与定时器相关的结构用来简化管理定时器的操做。全部接口都声明在<linux/timer.h>,大多数接口在文件kernel/timer.c中实现。
定时器的例子:
/* 建立须要先定义它 */ struct timer_list my_timer; /* 而后须要初始化它 */ init_timer(&my_timer); /* 而后填充数据结构 */ my_timer.expires = jiffies + delay; /* 定时器超时时的节拍数 */ my_timer.data = 0; /* 给定时器处理函数传入0值 */ my_timer.function = my_function; /* 定时器超时时调用的函数 */ /* 最后激活定时器 */ add_timer(&my_timer);
在填充结构体中,expires表示超时时间,data是长整型的参数,function的函数原型必须符合:
void my_timer_function(unsigned long data);
内核可能延误定时器的执行,因此不能用定时器实现任何硬实时任务。
改变指定的定时器超时时间:
mod_timer(&my_timer, jiffies+new_delay); /* 新的定时值 */
若是须要在定时器超时前中止定时器,可使用del_timer函数:
del_timer(&my_timer);
删除定时器时须要等待可能在其余处理器上运行的定时器处理程序都退出。
del_timer_sync(&my_timer);
定时器与当前执行代码是异步的,所以可能存在潜在竞争条件,下面的代码是不安全的。
del_timer(my_timer); my_timer->expires = jiffies + new_delay; add_timer(my_timer);
通常状况下del_timer_sync()函数取代del_timer()函数。
内核在时钟中断发生后执行定时器,定时器做为软中断在下半部上下文中执行。
时钟中断处理程序会执行update_process_times()函数,该函数随即调用run_local_timers()函数:
void run_local_timers(void) { hrtimer_run_queues(); raise_softirq(TIMER_SOFTIRQ); /* 执行定时器软中断 */ softlockup_tick(); }
内核将定时器按它们的超时时间划分为五组。当定时器超时时间接近时,定时器将随组一块儿下移。减小搜索超时定时器所带来的负担。
除了使用定时器或下半部机制之外,还须要其余方法来推迟执行任务。这种推迟一般发生在等待硬件完成某些工做时,并且等待的时间每每很是短。
好比,从新设置网卡的以太模式须要花费2ms,因此在设定网卡速度后,驱动程序必须至少等待2ms才能继续运行。
最简单的延迟方法是忙等待,可是仅仅在想要延迟的时间是节拍的整数倍,或者精确率要求不高时才可使用。
unsigned long time_out = jiffies + 10; /* 10个节拍 */ while(time_before(jiffies, time_out)) ; unsigned long delay = jiffies + 2*HZ; /* 2秒 */ while(time_before(jiffies, delay)) ; unsgined long delay = jiffies + 5*HZ; while(time_before(jiffies, delay)) condresched();
cond_resched()函数将调度一个新程序投入运行,但它只有在设置完need_resched标志后才能生效。
另外,延迟执行无论在那种状况下,都不该该在持有锁时或禁止中断时发生。
jiffies变量在<linux/jiffies.h>中被标记为关键字volatile,这样每次都会被从内存中读入。
有时内核代码不但须要很短暂的延迟,并且还要求延时的时间很精确。
内核提供了三个能够处理ms、ns和us级别的延时函数,定义在文件<linux/delay.h>和<asm/delay.h>中
void udelay(unsgined long usecs) void ndelay(unsigned long nsecs) void mdelay(unsigned long msecs)
通产超过1ms的范围不适用udelay()进行延迟,对于较长的延迟,mdelay()工做良好。
更理想的方法是使用schedule_timeout()函数,该方法会让须要延迟执行的任务睡眠到指定的延时时间耗尽后在从新运行。
可是睡眠时间不能保证指定的延时事件按,只能尽可能接近指定时间。用法以下:
/* 将任务设置为可中断睡眠状态 */ set_current_state(TASK_INTERRUPTIBLE); /* 小睡一会,"s"秒后唤醒 */ schedule_timeout(s*HZ);
在调用shcedule_timeout时,任务必须设置状态为TASK_INTERRUPTIBLE或者TASK_UNINTERRUPTIBLE。
signed long schedule_timeout(signed long timeout) { time_t timer; unsigned long expire; switch(timeout) { case MAX_SCHEDULE_TIMEOUT: schedule(); goto out; default: if(timeout < 0) { printk(KERN_ERR "schedule_timeout: wrong timeout " "value %lx from %p\n", timeout, __builtin_return_address(0)); current->state = TASK_RUNNING; goto out; } } expire = timeout + jiffies; init_timer(&timer); timer.expires = expires; timer.data = (unsigned long)current; timer.function = process_timeout; add_timer(&timer); schedule(); del_timer_sync(&timer); timeout = expire - jiffies; out: return timeout < 0 ? 0 : timeout; }
该函数用原始的名字timer建立了一个定时器timer,而后设置它的超时时间timeout,设置超时执行函数process_timeout();接着激活定时器并且调用schedule()。
当定时器超时时,process_timeout()函数会被调用:
void process_timeout(unsigned long data) { wake_up_process((taks_t *)data); }
该函数将任务设置为TASK_RUNNING状态,而后将其放入运行队列。