hz:上述间隔由hz的值设定,hz是一个与体系结构相关的常数linux
计数器:发生中断一次,计数器加一,这个计数器的值(只有)在系统引导时被初始化为0缓存
jiffies变量:unsigned long 型变量,要么与jiffies_64相同,要么取其低32位安全
使用jiffies计数器数据结构
包含在<linux/jiffies.h>中,可是一般只需使用<linux/sched.h>,前者会自动包含并发
jiffies与jiffies_64均应被看作只读变量app
jiffies变量应被声明为volatile异步
使用举例:函数
#include<linux/jiffies.h>性能 unsigned long j,stamp_1,stamp_half,stamp_n;atom j=jiffies; //read the current value stamp_1=j+HZ; //1second in the future stamp_half=j+HZ/2; //0.5second in the future stamp_n=j+n*HZ/1000; // n milliseconds |
比较缓存值(例如上述的stamp_1)与当前值:
#include<linux/jiffies.h> int time_after(unsigned long a,unsigned long b); int time_before(unsigned long a,unsigned long b); int time_after_eq(unsigned long a,unsigned long b); int time _before_eq(unsigned long a,unsigned long b); |
上述几个宏会将计数器值转换为signed long,相减,而后比较结果。若是须要以安全的方式计算两个jiffies实例之间的差,以下:
diff = (long) t2 - (long) t1;
而经过下面的方法,可将两个jiffies的差转换为毫秒值:
msec = diff *1000/HZ;
用户空间和内核空间的时间表述方法的转换:
用户空间方法:timeval,timespec
内核空间方法:jiffies
#include<linux/time.h> struct timespec { 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); |
读取64为jiffies:jiffies_64
#include<linux/jiffies.h>
u64 get_jiffies_64(void);
处理器特定的寄存器
若是须要精度很高的计时,jiffies已不可知足须要,这时就引入了一种技术就是CPU包含一个随时钟周期不断递增的计数寄存器。这是完成高分辨率计时任务的惟一可靠途径。
1.无论该寄存器是否置0,咱们都强烈建议不要重置它。
2.TSC:这是一个64位寄存器,记录CPU的时钟周期数,从内核空间和用户空间均可以读取它。
<asm/msr.h>
如下宏是与体系结构相关的,上述头文件是x86专用头文件
rdtsc(low32,high32);
rdtscl(low32);
rdtscll(var64);
第一个宏原子性的把64位变量读到两个32位的变量中。
第二个读取低32位,废弃高32位。
第三个把64值读到一个long long型变量中。
举例:
下面代码完成测量指令自身运行时间
unsigned long ini,end; rdtscl(ini); rdtscl(end); printk("time lapse:%li\n",end-ini); |
现提供一个与体系结构无关的函数,能够替代rdtsc
<linux/timex.h>
cycles_t get_cycles(void);
在各类平台上均可以使用这个函数,在没有时钟周期计数寄存器的平台上它老是返回0。cycles_t类型是能装入读取值的合适的无符号类型。
获取当前时间
jiffies用来测量时间间隔
墙钟时间-->jiffies时间:
#include<linux/time.h>
unsigned long mktime(unsigned int year,unsigned int month,
unsigned int day, unsigned int hour,
unsigned int minute,unsigned int second);
为了处理绝对时间, <linux/time.h> 导出了 do_gettimeofday 函数,它填充一个指向 struct timeval 的指针变量。绝对时间也可来自 xtime 变量,一个 struct timespec 值,为了原子地访问它,内核提供了函数 current_kernel_time。它们的精确度由硬件决定,原型是:
|
以上两个函数在ARM平台都是经过 xtime 变量获得数据的。
全局变量xtime:它是一个timeval结构类型的变量,用来表示当前时间距UNIX时间基准1970-01-01 00:00:00的相对秒数值。
结构timeval是Linux内核表示时间的一种格式(Linux内核对时间的表示有多种格式,每种格式都有不一样的时间精度),其时间精度是微秒。该结构是内核表示时间时最经常使用的一种格式,它定义在头文件include/linux/time.h中,以下所示:
struct timeval {
time_t tv_sec; /* seconds */
SUSEconds_t tv_usec; /* microseconds */
};
其中,成员tv_sec表示当前时间距UNIX时间基准的秒数值,而成员tv_usec则表示一秒以内的微秒值,且1000000>tv_usec>=0。
Linux内核经过timeval结构类型的全局变量xtime来维持当前时间,该变量定义在kernel/timer.c文件中,以下所示:
/* The current time */
volatile struct timeval xtime __attribute__ ((aligned (16)));
可是,全局变量xtime所维持的当前时间一般是供用户来检索和设 置的,而其余内核模块一般不多使用它(其余内核模块用得最多的是jiffies),所以对xtime的更新并非一项紧迫的任务,因此这一工做一般被延迟 到时钟中断的底半部(bottom half)中来进行。因为bottom half的执行时间带有不肯定性,所以为了记住内核上一次更新xtime是何时,Linux内核定义了一个相似于jiffies的全局变量 wall_jiffies,来保存内核上一次更新xtime时的jiffies值。时钟中断的底半部分每一次更新xtime的时侯都会将 wall_jiffies更新为当时的jiffies值。全局变量wall_jiffies定义在kernel/timer.c文件中:
/* jiffies at the most recent update of wall time */
unsigned long wall_jiffies;
延迟
长延迟
忙等待
若想延迟执行若干个时钟嘀哒,精度要求不高。最容易的( 尽管不推荐 ) 实现是一个监视 jiffy 计数器的循环。这种忙等待实现的代码以下:
|
对 cpu_relex 的调用将以体系相关的方式执行,在许多系统中它根本不作任何事,这个方法应当明确地避免。对于ARM体系来讲:
|
也就是说在ARM上运行忙等待至关于:
|
这种忙等待严重地下降了系统性能。若是未配置内核为抢占式, 这个循环在延时期间彻底锁住了处理器,计算机直到时间 j1 到时会彻底死掉。若是运行一个可抢占的内核时会改善一点,可是忙等待在可抢占系统中仍然是浪费资源的。更糟的是, 当进入循环时若是中断碰巧被禁止, jiffies 将不会被更新, 而且 while 条件永远保持真,运行一个抢占的内核也不会有帮助, 惟一的解决方法是重启。
让出处理器
忙等待加剧了系统负载,必须找出一个更好的技术:不须要CPU时释放CPU 。 这可经过调用schedule函数实现(在 <linux/sched.h> 中声明):
|
在计算机空闲时运行空闲任务(进程号 0, 因为历史缘由也称为swapper)可减轻处理器工做负载、下降温度、增长寿命。
超时
实现延迟的最好方法应该是让内核为咱们完成相应的工做。
(1)若驱动使用一个等待队列来等待某些其余事件,并想确保它在一个特定时间段内运行,可以使用:
|
(2)为了实现进程在超时到期时被唤醒而又不等待特定事件(避免声明和使用一个多余的等待队列头),内核提供了 schedule_timeout 函数:
|
短延迟
当一个设备驱动须要处理硬件的延迟(latency潜伏期), 涉及到的延时一般最多几个毫秒,在这个状况下, 不该依靠时钟嘀哒,而是内核函数 ndelay, udelay和 mdelay ,他们分别延后执行指定的纳秒数, 微秒数或者毫秒数,定义在 <asm/delay.h>,原型以下:
|
重要的是记住这 3 个延时函数是忙等待; 其余任务在时间流失时不能运行。每一个体系都实现 udelay, 可是其余的函数可能未定义; 若是它们没有定义, <linux/delay.h> 提供一个缺省的基于 udelay 的版本。在全部的状况中, 得到的延时至少是要求的值, 但可能更多。udelay 的实现使用一个软件循环, 它基于在启动时计算的处理器速度和使用整数变量 loos_per_jiffy肯定循环次数。
为避免在循环计算中整数溢出, 传递给udelay 和 ndelay的值有一个上限,若是你的模块没法加载和显示一个未解决的符号:__bad_udelay, 这意味着你调用 udleay时使用太大的参数。
做为一个通用的规则:若试图延时几千纳秒, 应使用 udelay 而不是 ndelay; 相似地, 毫秒规模的延时应当使用 mdelay 完成而不是一个更细粒度的函数。
有另外一个方法得到毫秒(和更长)延时而不用涉及到忙等待的方法是使用如下函数(在<linux/delay.h> 中声明):
|
若可以容忍比请求的更长的延时,应使用 schedule_timeout, msleep 或 ssleep。
内核定时器
当须要调度一个之后发生的动做, 而在到达该时间点时不阻塞当前进程, 则可以使用内核定时器。内核定时器用来调度一个函数在未来一个特定的时间(基于时钟嘀哒)执行,从而可完成各种任务。
内核定时器是一个数据结构, 它告诉内核在一个用户定义的时间点使用用户定义的参数执行一个用户定义的函数,函数位于 <linux/timer.h> 和 kernel/timer.c 。被调度运行的函数几乎肯定不会在注册它们的进程在运行时运行,而是异步运行。实际上, 内核定时器一般被做为一个"软件中断"的结果而实现。当在进程上下文以外(即在中断上下文)中运行程序时, 必须遵照下列规则:
(1)不容许访问用户空间;
(2)current 指针在原子态没有意义;
(3)不能进行睡眠或者调度. 例如:调用 kmalloc(..., GFP_KERNEL) 是非法的,信号量也不能使用由于它们可能睡眠。
经过调用函数 in_interrupt()可以告知是否它在中断上下文中运行,无需参数并若是处理器当前在中断上下文运行就返回非零。
经过调用函数 in_atomic()可以告知调度是否被禁止,若调度被禁止返回非零; 调度被禁止包含硬件和软件中断上下文以及任何持有自旋锁的时候。
在后一种状况, current 多是有效的,可是访问用户空间是被禁止的,由于它能致使调度发生. 当使用 in_interrupt()时,都应考虑是否真正该使用的是 in_atomic 。他们都在 <asm/hardirq.h> 中声明。
内核定时器的另外一个重要特性是任务能够注册它自己在后面时间从新运行,由于每一个 timer_list 结构都会在运行前从激活的定时器链表中去链接,所以可以当即链入其余的链表。一个从新注册它本身的定时器一直运行在同一个 CPU.
即使在一个单处理器系统,定时器是一个潜在的态源,这是异步运行直接结果。所以任何被定时器函数访问的数据结构应当经过原子类型或自旋锁被保护,避免并发访问。
定时器 API
内核提供给驱动许多函数来声明、注册以及删除内核定时器:
|
内核定时器的实现《LDD3》介绍的比较笼统,之后看《ULK3》的时候再细细研究。
一个内核定时器还远未完善,由于它受到 jitter 、硬件中断,还有其余定时器和其余异步任务的影响。虽然一个简单数字 I/O关联的定时器对简单任务是足够的,但不合适在工业环境中的生产系统,对于这样的任务,你将最可能须要实时内核扩展(RT-Linux).