linux驱动-时间

简述:

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)的原理和实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值