节拍率:Hz
- 系统定时器频率是通过静态预处理定义的,在系统启动时按照Hz值对硬件进行设置
- 内核在<asm/param.h>文件中定义Hz
jiffies
- 全局变量jiffies用来记录自系统以来产生的节拍总数,启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序就会增加该变量的值
- jiffies定义文件<linux/jiffies.h>中:extern unsigned long volatile jiffies
- jiffies变量时无符号长整型,在32位体系结构上是32位,在64位体系结构上是64位
//在<linux/jiffies.h>中定义
extern unsigned long volatile jiffies;
//第二变量也定义在<linux/jiffies.h>中
extern u64 jiffies_64;
//ld链接脚本用于连接内核映像(x86位于arch/x86/kernel/vmlinux.lds.S)
//然后jiffies_64变量的初值覆盖jiffies变量
jiffies = jiffies_64;
- jiffies取整个64位jiffies_64变量的低32位,时间管理代码使用整个64位,避免整个64位的溢出
- 访问jiffies的代码仅读取jiffies_64的低32位,通过get_jiffies_64()函数,可以读取整个64位数值
- 在64位体系结构上,jiffies_64和jiffies指的是同一个变量
#include <linux/jiffies.h>
//为了防止jiffies的回绕,即当jiffies_64或jiffies变量的值超过最大范围就会产生溢出
//其中unknown参数通常是jiffies,known参数是需要对比的值
//当时间unknown超过指定的known时,返回真,否则返回假
#define time_after(unknown, known) ((long)(known) - (long)(unknown) < 0)
//当时间unknown没有超过指定的known时,返回真,否则返回假
#define time_before(unknown, known) ((long)(unknown) - (long)(known) < 0)
//当时间unknown超过或等于指定的known时,返回真,否则返回假
#define time_after_eq(unknown, known) ((long)(unknown) - (long)(known) >= 0)
//当时间unknown没有超过或等于指定的known时,返回真,否则返回假
#define time_before_eq(unknown, known) ((long)(known) - (long)(unknown) >= 0)
案例
unsigned long timeout = jiffies + HZ/2; //0.5秒后超时
if (time_before(jiffies, timeout)) {
//没有超时
} else {
//超时
}
- 内核定义USER_HZ代表用户空间看到的HZ值,内核可以使用函数jiffies_to_clock_t()(定义kernel/time.c)将一个由HZ表示的节拍数转换成一个由USER_HZ表示的节拍计数
实际时间(RTC时间)
- 当前实际时间定义在文件kernel/time/timekeeping.c中:struct timespec xtime
- timespec数据结构定义在文件<linux/time.h>中
#include <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锁
write_seqlock(xtime_lock);
/*更新xtime*/
write_sequnlock(xtime_lock);
- 读取xtime时也需要使用read_seqbegin()和read_seqretry()函数
//下面代码实际为函数do_gettimeofday()的部分代码
unsigned long seq;
unsigned long lost;
do {
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));
- 从用户空间读取实时时间的接口是gettimeofday(),在内核中对应系统调用sys_gettimeofday(),定义于kernel/time.c
asmlinkage long sys_gettimeofday(struct timeval *tv, struct timezone *tz)
{
struct timeval ktv;
if (likely(tv)) {
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;
}
- 内核实现了time()系统调用,但是gettimeofday几乎完全取代time()函数
- C库页提供一些实时时间的相关库调用,如ftime()、ctime()
- 系统调用settimeofday()设置当前时间,需要具有CAP_SYS_TIME权限
定时器
- 定时器并不周期运行,在超时后就自行撤销
- 动态定时器不断地创建和撤销,而且运行次数也不受限制
使用定时器
#include <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;//定时器内部值,用户不要使用
};
//创建定时器
struct timer_list timer;
//定时器初始化,初始化定时器数据结构的内部值
timer.expires = jiffies + delay;//超时时间,以节拍为单位的绝对计数值
timer.data = 0; //data参数,可以利用一个处理函数注册多个定时器
timer.function = timer_handler;
init_timer(&timer);
//激活定时器
add_timer(&timer);
//改变已经激活的定时器超时时间,该函数也可以操作已经初始化,但未激活的定时器
//如果定时器未激活,mod_timer()会激活
//如果调用定时器未被激活,该函数返回0,否则返回1
//不管何种情况,一旦mod_timer()函数返回,定时器都将被激活且设置新定时值
mod_timer(&timer, jiffies + new_delay);
//在定时器超时前停止定时器
//被激活或未被激活的定时器都可以使用该函数
//如果定时器未被激活,返回0,否则返回1
//不需要为已经超时的定时器调用该函数,因为它们会自动删除
del_timer(&timer);
//需要删除定时器时需要等待可能在其他处理器上运行的定时器处理程序都退出,则该调用del_timer_sync()函数
//del_timer_sync()函数不能在中断上下文中使用
del_timer_sync(&timer);
延迟执行
#include <linux/delay.h>
#include <asm/delay.h>
void udelay(unsigned long usecs);
void ndelay(unsigned long nsecs);
void mdelay(unsigned long msecs);
- schedule_timeout()函数让需要延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行(只能尽量使睡眠时间接近指定的延迟时间)
- schedule_timeout()的唯一参数是延迟的相对时间,单位为jiffies
- schedule_timeout()需要调度程序,所有调用该函数必须保证能够睡眠,即代码必须处于进程上下文中,并且不能持有锁
//将任务设置为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE
set_current_state(TASK_INTERRUPTIBLE);
//s秒后唤醒
schedule_timeout(s * HZ);
schedule_timeout()的实现
signed long schedule_timeout(signed long timeout)
{
timer_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 = expire;
timer.data = (unsigned long) current;
timer.function = process_timerout;
add_timer(&timer);
schedule();
del_timer_sync(&timer);
timeout = expire - jiffies;
out:
return timeout < 0 ? 0 : timeout;
}
}
void process_timeout(unsigned long data)
{
wake_up_process((task_t *)data);
}
- 该函数使用一个定时器timer,然后设置超时时间timeout,设置超时执行函数process_timeout()
- 接着激活定时器而且调用schedule(),因为任务被标识TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE,所有调度程序不会再选择该任务投入运行,而选择其他新任务运行
- process_timeout()将任务设置为TASK_RUNNING状态,然后将其投入运行队列
- 当任务重新调度时,将返回代码进入睡眠前位置继续执行(正好调用schedule()后),如果任务提前被唤醒(比如收到信息),那么定时器被撤销,process_timeout()函数返回剩余时间