简述:
linux驱动模块,一般用jiffies、延时、时钟周期、定时器来控制时间。
本文将按照以下几点来描述:
- HZ
- jiffies
- 短延时
- 低分辨率内核定时器
- 高精度内核定时器
HZ
HZ是宏,定义在include/asm-generic/param.h中:
# define HZ CONFIG_HZ /* Internal kernel timer frequency */
CONFIG_HZ可配置的,如果配置成100,那么HZ等于100,代表系统一秒钟中断100次,周期就是10ms,即系统每10ms中断系统一次。中断系统后,系统就可以进入调度控制块,来调度进程。
更加具体情况,配置CONFIG_HZ值,太小系统有效执行时间提高了,但是,实时性降低。太大,系统开销大,有效执行时间降低,实时性提高了。平衡这点很关键,一般是100,最多的也就1000。
jiffies:
jiffies声明在include/linux/jiffies.h中:
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
jiffies_64:64位的,在32位系统和64位系统都可以用
jiffies:32位的
jiffies:如果HZ为100,那么jiffies每10ms加1。从开机系统运行后,一直这样子递增下去。
注意访问64位的jiffies_64尽量用(jiffies可以直接访问):
u64 get_jiffies_64(void);
void init_ee()
{
u64 a;
u32 b;
a = get_jiffies_64();
b = jiffies;
}
尽管我们,可以直接用jiffies来处理,但是我首先考虑用提供的接口处理。
比较接口:
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)((b) - (a)) < 0))
#define time_before(a,b) time_after(b,a)
#define time_after_eq(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)((a) - (b)) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)
time_after(a,b):a在b之后返回true,否则false
如:
unsigned long r = 0;
void rinit()
{
r = jiffies + 600;
}
void after_testr()
{
if(time_after(r,jiffies))
printk("ok");
}
rinit()先运行,after_testr()在后面,就可以判断过了的时间。
jiffies和其它时间类型的转换:
extern unsigned int jiffies_to_msecs(const unsigned long j);//jiffies转换成毫秒
extern unsigned int jiffies_to_usecs(const unsigned long j);//jiffies转换成微妙
extern unsigned long msecs_to_jiffies(const unsigned int m);//毫秒转换成对应有几个时钟滴答(jiffies量)
extern unsigned long usecs_to_jiffies(const unsigned int u);//微妙转换成对应有几个时钟滴答(jiffies量)
extern unsigned long timespec_to_jiffies(const struct timespec *value);//timespec表示的时间对应几个时钟滴答
extern void jiffies_to_timespec(const unsigned long jiffies,
struct timespec *value);//jiffies转换成timespec
extern unsigned long timeval_to_jiffies(const struct timeval *value);
extern void jiffies_to_timeval(const unsigned long jiffies,
struct timeval *value);
extern clock_t jiffies_to_clock_t(unsigned long x);
见函数名,就知道类型的转换
用timeval和timespec结构体需要包含include/linux/time/time.h
struct timespec {
__kernel_time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
可以看出timespec是用来表示纳秒级时间的,timeval是表示微妙级时间的。
在time.h里面还声明了几个与当前jiffies的转换:
//将年月日时分秒转换成jiffies
unsigned long mktime(const unsigned int year, const unsigned int mon,
const unsigned int day, const unsigned int hour,
const unsigned int min, const unsigned int sec);
//将系统当前jiffies转换成timeval结构体的形式表示
void do_gettimeofday(struct timeval *tv);
//将当前系统jiffies转换成timespec形式表示
struct timespec current_kernel_time(void);
凡是,用jiffies来计时的,精度都在是1000/HZ。
短延时:
短延时,一般需要包含头文件
#include <linux/delay.h>
部分定义是在include/asm-generic/delay.h中,但是上面的头文件已经包含了这个头文件。
不带睡眠的延时:
void ndelay(unsigned long nsecs);
void udelay(unsigned long usecs);
void mdelay(unsigned long msecs);
带睡眠的:
void msleep(unsigned int millisecs);//毫秒
unsigned long msleep_interruptible(unsigned int millisecs);//睡眠期间可被打断
void ssleep(unsigned int seconds)//秒
比上面更短的延时,可以用时钟周期,接口定义在include/asm-generic/timex.h
cycles_t get_cycles(void);
返回当前系统时钟周期数。
如:
cycles_t c;
for(c = 100+get_cycles();c > get_cycles(););
表示延时100个时钟周期。
get_cycles依赖于硬件,如果返回0,表示系统不支持。
低分辨率内核定时器:
低分辨率内核定时器,是基于jiffies来计数算时间。
包含头文件:
#include <linux/timer.h>
首先定义timer:
#define DEFINE_TIMER(_name, _function, _expires, _data) \
struct timer_list _name = \
TIMER_INITIALIZER(_function, _expires, _data)
#define init_timer(timer) \
__init_timer((timer), 0)
用宏DEFINE_TIMER定义比较,比较省事,
用init_timer麻烦点:
struct timer_list tm;
void func(unsigned long a){....};
init_timer(&tm);
tm.function = func;
tm.expires= jiffies + 500;
将定时器加入到系统启动:
void add_timer(struct timer_list *timer);
从系统中删除定时器:
int del_timer(struct timer_list * timer);
int del_timer_sync(struct timer_list *timer);
del_timer:不保证,返回后,timer已经停止。
del_timer_sync:返回后,timer肯定停止了,会等待正在运行的timer完成后,删除。
其他接口:
int mod_timer(struct timer_list *timer, unsigned long expires);//修改timer定时时间(可以代替add_timer),修改后mod_timer会把定时器加入到系统。
int timer_pending(const struct timer_list * timer);//返回1表示timer在运行中,0表示没有运行。
高精度定时器:
hrtimer高精度定时器,定时精度依赖于硬件定时器的精度。
- 1、包含头文件:
#include <linux/hrtimer>
- 2、高精度定时器使用:
- (1)定义:
struct hrtimer hrtm;
- (2)初始化:
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
enum hrtimer_mode mode);
第一个参数:定义的hrtimer定时器的指针
第二个参数:which_clock用CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_BOOTTIME其中一个,CLOCK_MONOTONIC用的最多,然后是CLOCK_REALTIME。
第三个参数:mode可是其中一个HRTIMER_MODE_REL、HRTIMER_MODE_ABS。
- (3)添加回调函数:
enum hrtimer_restart hrtm_callback(struct hrtimer *func){...}
hrtm.function = hrtm_callback;
回调函数的返回值:返回HRTIMER_NORESTART,表示定时器不自己启动;返回HRTIMER_RESTART,表示回调完成后,定时器立马自动加入系统运行。回调函数里面一定要调用hrtimer_forward_now()重置到期时间,否则会在返回HRTIMER_RESTART后,立马调用回调函数。
- (4)启动定时器:
int hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode);
第一个参数:定时器指针
第二个参数:tim定时时间(溢出时间)
第三个参数:mode同hrtimer_init第三个参数。
返回值:0表示启动成功,1表示当前定时器正在运行。
后面反复重启定时器用hrtimer_restart(),即可。
- (5)删除定时器:
int hrtimer_cancel(struct hrtimer *timer);
- (6)定时器时间:
时间用ktime_t(定义在include/linux/ktime.h)
nion ktime {
s64 tv64;
#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
struct {
# ifdef __BIG_ENDIAN
s32 sec, nsec;
# else
s32 nsec, sec;
# endif
} tv;
#endif
};
typedef union ktime ktime_t; /* Kill this */
定时时间一般用ktime.h里面声明的接口比较方便:
用得较多的,设置到期时间,secs多少秒,nsecs多少纳秒,会返回ktime_t表示的时间:
static inline ktime_t ktime_set(const long secs, const unsigned long nsecs)
- 3、配置高精度定时器
- (1)首先,看CONFIG_HIGH_RES_TIMERS定义了没有,定义了表示打开了高精度定时器。
- (2)然后,看hrtimer.c中如下:
/*
* High resolution timer enabled ?
*/
static int hrtimer_hres_enabled __read_mostly = 1;
/*
* Enable / Disable high resolution mode
*/
static int __init setup_hrtimer_hres(char *str)
{
if (!strcmp(str, "off"))
hrtimer_hres_enabled = 0;
else if (!strcmp(str, "on"))
hrtimer_hres_enabled = 1;
else
return 0;
return 1;
}
__setup("highres=", setup_hrtimer_hres);
看内核启动参数有没有带highres,带了,如果是highres=on,表示打开高精度定时器的,如果是highres=off,表示关闭高精度定时器。如果启动参数没有带highres,那么默认开启高精度定时器。
4、高精度定时器精度
hrtimer精度,是依赖于硬件定时器精度。
在Linux中,提供了一个clocksource模块、clockevent两个模块,头文件分别是clocksorce.h、clockchips.h。
硬件定时器,通过clocksource提供的接口,把定时器信息(定时器精度,读写寄存器)注册到clocksource,定时器中断服务函数注册,实现都要自己实现。(一般都是由CPU厂商实现了的)
linux已经实现了clocksource和clockevent两个模块联系起来。
hrtimer也是由系统,已经实现了将hrtimer注册到clockevent。当定时器中断产生,hrtimert就会被调用,检查如果有定时器溢出,就调用回调。
如果,高精度定时器打开的,而没有硬件定时器注册到clocksource,那么hrtimer走hrtimer_run_queues()这个函数处理注册的定时器,这种情况,hrtimer其实也是和低精度定时器一样的,是用jiffies计时的。
如果,高精度定时器打开的,有硬件定时器注册到clocksorce,那么hrtimer将hrtimer_interrupt()注册到clockevent,此时的精度,就看硬件定时器精度了。
hrtimer_interrupt()是通过kernel/tim/tick-oneshot.c中:
#ifdef CONFIG_HIGH_RES_TIMERS
/**
* tick_init_highres - switch to high resolution mode
*
* Called with interrupts disabled.
*/
int tick_init_highres(void)
{
return tick_switch_to_oneshot(hrtimer_interrupt);
}
#endif
tick_init_highres注册到clockevent的,也可以说切换到高精度定时器。
一般平台的hrtimer精度都在us级。也有纳秒的。
一种比较直接查看,hrtimer定时精度的方法就是搜索include/linux/clockchips.h结构体clock_event_device一个成员min_delta_ns,一般会在arch/arm/mach-omap1/time.c这些具体某个CPU平台里面,设置这个成员值,具体看用的什么平台,但也不全都在这样的文件里面。
硬件定时器注册和hrtimer可以参考下面的博客:
Linux hrtimer的实现
Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现