分类: LINUX
2010-04-28 14:06:31
内核中时间相关的基本概念和简单应 用举例。
Linux内核中的时间来源主要 分:硬件时钟和软件时钟
硬件时钟有:
1)实时时钟RTC
实时时钟(RTC)是用电池供电的一个时钟芯片,里面保存的就是我们一般意义上的时间。 如:2009年4月26日
2)可编程间隔定时器PIT
PIT会产生周期性的时钟中断信号。Linux中在PIT(Timer)的硬件中断处理函数中对jiffies的值加1
3)时间戳记数器TSC
这里一个X86平台上有的用于记录CPU的每个时钟信号次数的寄存器。X86平台的Linux中如果时钟中断
丢失,则用这个TSC来进行校正时钟。
软件时钟有:jiffies,内核 定时器等。
内核通过时钟中断来跟踪时间流。时钟中断由定时硬件以周期性的间隔产生,这个间隔由HZ
值设定。大多数平台上默认的HZ值是50~1200之间,X86平台从2.6内核起就是1000。
对于linux-
# ifndef HZ
# define HZ 100 /* Internal kernel timer frequency */
# endif
每次时钟中断发生时,内核计数器的值加1,这
个值就是系统时钟的滴答数(此计数器的值在系统启动时初始化为0)。这个内核计数器是一个64位的变量(32位架
构上也是64位),称为“jiffies_
include/linux/jiffies.h:
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (0))
arch/arm/kernel/time.c:
u64 jiffies_64 = INITIAL_JIFFIES;
arch/arm/kernel/vmlinux.lds.S:
#ifndef __ARMEB__ //gcc –EB时指定为big endian
jiffies = jiffies_64;
#else
jiffies = jiffies_64 + 4;
#endif
如上所说,ARM平 台中HZ为100个jiffies,也就是说100jiffies=1s。
除系统定时器外,还有一个与时间有关的时钟:实时时钟(RTC),这是一个硬件时钟,用来持久存放系统时间,系统关闭后靠主板上的微型电池保持计时。系统启动时, 内核通过读取RTC来初始化Wall Time,并存放在xtime变量中,这是RTC最 主要的作用。
使用jiffies很简单,如:
t1=jiffies; //t1为运行此语句时的jiffies值
t2=jiffies+HZ; //t2为1秒之后的jiffies值
diff=(long)t2-(long)t1; //计算时间差
s=diff/HZ; //时间差转换成秒
ms=diff*1000/HZ; //时间差转换成毫秒
内核提供了相应的宏来比较时间,其实现原理就是转换成long型后相减:
#include
int time_after(unsigned long a, unsigned long b); //a比b靠后,返回真
int time_before(unsigned long a, unsigned long b); //a比b靠前,返回真
int time_after_eq(unsigned long a, unsigned long b); //a比b靠后或相等,返回真
int time_before_eq(unsigned long a, unsigned long b); //a比b靠前或相等,返回真
使用jiffies计数器的实例,如arch/arm/kernel中的irq.c中的probe_irq_on函数:
unsigned long probe_irq_on(void)
{
。。。。。。
for (delay = jiffies + HZ/10; time_before(jiffies, delay); ) //在delay前一直循环
/* min 100ms delay */ ;
。。。。。。
}
1)忙等待
上述irq.c的例子中这样使用time_before就是一个忙等待的例子。
2)ndelay,udelay,mdelay函数
这几个函数的原型定义如下:
#include
void ndelay(unsigned long nsecs); //延迟nsecs纳秒
void udelay(unsigned long usecs); //延迟usecs微秒
void mdelay(unsigned long msecs); //延迟msecs毫秒
这几个函数的实现和具体硬件相关,具体是在
用这些函数实现的延迟,至少会达到请求的时间值,但可能更长。
在linux-
ARM平台中的udelay函数实现在include/asm-arm/delay.h中,如下:
extern void __udelay(unsigned long usecs);
extern void __const_udelay(unsigned long);
#define MAX_UDELAY_MS 2
#define udelay(n) \
(__builtin_constant_p(n) ? \
/* gcc的
内建函数__builtin_constant_p用于判断n是否为编译时常数,如果n是常数,返回 1,否则返回 0。*/
((n) > (MAX_UDELAY_MS * 1000) ? __bad_udelay() : \
__const_udelay((n) * 0x68dbul)) : \
/*n> MAX_UDELAY_MS * 1000时出错返回,否则调用函数__const_udelay */
__udelay(n))
其中,__udelay和__const_udelay在arch/arm/lib/delay.S中实现,如下:
ENTRY(__udelay)
mov r2, #0x6800
orr r2, r2, #0x00db
mul r0, r2, r0
ENTRY(__const_udelay) @ 0 <= r0 <= 0x01ffffff
ldr r2, LC0
ldr r2, [r2] @ max = 0x0fffffff
mov r0, r0, lsr #11 @ max = 0x00003fff
mov r2, r2, lsr #11 @ max = 0x0003ffff
mul r0, r2, r0 @ max = 2^32-1
movs r0, r0, lsr #6
RETINSTR(moveq,pc,lr)
Note: 对ARM平台,函数中最前面4个参数通过,寄存器r0,r1,r2,r3来传递,其它的参数通过下降式满堆栈来传递。对于传递给__const_udelay函数的参数(n) * 0x68dbul),会保存在寄存器r0中。
3)msleep,ssleep函数
相关原型如下:
#include
void msleep(unsigned int msecs);
unsigned long msleep_interruptible(unsigned int msecs);
static inline void ssleep (unsigned int seconds);
其中,msleep和ssleep不可中断。
4)其它
内核中除上述的时间相关机制外,还有tasklet,工作队列,共享队列来实现任务的延迟执行。对这几种机制的说明这里暂略。
如果要在将来某个时间点调度执行某个动作,同时在该时间点到达之前不会阻塞当前进程,则可以使用内 核定时器。
一个内核定时器是一个数据结构,它告诉内核在用户定义的时间点使用用户定义的参数来执行一个用户定 义的函数。
定时器函数必须是原子的。
Note: 判断当前是否在原子操作上下文中用in_atomic();判断是否在中断上下文中用in_interrupt
();
内核定时器相关函数在include/linux/timer.h中定义:
struct timer_list {
struct list_head entry;
unsigned long expires;
//到此jiffies值时,定时器执行定义的函数
spinlock_t lock;
unsigned long magic;
void (*function)(unsigned long); //要执行的函数
unsigned long data;
//传递给function函数的参数
struct tvec_t_base_s *base;
};
static inline void init_timer(struct timer_list * timer);
static inline void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
int mod_timer(struct timer_list *timer, unsigned long expires);
在使用内核定时器时,只要初始化一个timer_list结构体,其中主要初始化L:expires、*function和data这3个就可以使用了。
以Linux-
init_timer(&motor_off_timer[dr]); //初始化定时器
motor_off_timer[dr].data = dr; //定义定时器使用的函数
motor_off_timer[dr].function = motor_off_callback; //定义定时器的时间
在同一文件的floppy_off函数中:
。。。。。。
del_timer(motor_off_timer + drive); //删除定时器
。。。。。。
add_timer(motor_off_timer
+ drive); //实际增加此定时器
1. linux-
2. Linux设备驱动程序 第三版