1 内核中时间概念
时间概念对计算机来说有些模糊,事实上内核必须在硬件的帮助下才能计算和管理时间。硬件为内核提供了一个系统定时器用于计算流失的时间。系统定时器以某种频率自发触发时钟中断,该频率可以通过编程预定,称为节拍率(系统定时器的频率)。
因为节拍率对内核来说是可知的,所以内核知道连续两次时钟中断的间隔时间,这个间隔时间就是节拍。内核就是靠这种已知的时钟中断间隔来计算实际时间(绝对时间)和系统运行时间。实际时间,就是某一天的日期和时间,内核通过控制时钟中断维护实际时间,另外内核也为用户空间提供了一组系统调用以获取实际日期和实际时间。系统运行时间:就是系统启动开始所经过的时间,对用户空间和内核都很用,因为许多程序都必须清楚流失的时间,通过两次读取运行时间再计算它们的差,就可以得到相对的流逝过的时间了。
系统定时器(内核定时器)是我们接下来讨论的内容。
2 标准定时器
标准定时器是内核定时器,以jiffies为粒度运行
jiffies和HZ
jiffies是在<linux/jiffies.h> 中声明的内核时间单元。为了理解jiffies,需要引入一个新的常量HZ,注意,这是个常量,系统定时器频率(节拍率)是通过静态预处理定义的,也就是HZ(赫兹),在系统启动时按照HZ值对硬件进行设置。它是jiffies在1s内增加的次数,HZ的大小取决于硬件和内核版本,决定了时钟中断触发的频率。
全局变量jiffies用来记录自系统启动以来产生的节拍总数。启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。因为一秒内时钟中断的次数等于HZ,所以jiffies一秒内增加的值也就为HZ。
jiffies变量总是无符号长整数(unsigned long),因此,在32位体系结构上是32位,在64位体系结构上是64位。32位的jiffies变量,可能会溢出,如果频率为1000Hz,49.7天后就会溢出。而使用64位的jiffies,不会看到它溢出。为了解决这个问题,<linux/jiffies.h>引入和定义了另一个变量:
extern u64 jiffies_64;
32位系统上采用这种方式时,jiffies取整个64位jiffies_64变量的低32位。jiffies_64将指向高位。在64位平台上,jiffies = jiffies_64 。
定时器API
定时器由结构timer_list表示,定义在文件linux/timer.h中:
struct timer_list {
struct list_head entry;
unsigned long expires;
spinlock_t lock;
unsigned long magic;
void (*function)(unsigned long);
unsigned long data;
struct tvec_t_base_s *base;
};
定时器的使用很简单,你只需要执行一些初始化工作,设置一个超时时间,指定超时发生后执行的函数,然后激活定时器就可以了。指定的函数将在定时器到期时自动执行,注意定时器并不周期运行,它在超时后就自动销毁,这也是这种定时器被称为动态定时器的一个原因,动态定时器不断创建和销毁,而且它的运行次数也不受限制。
- 设置定时器
设置定时器,可以用setup_timer函数:
void setup_timer(struct timer_list *timer,void (*function)(unsigned long),unsigned long data)
也可以使用init_time函数:
init_timer(struct timer_list *timer);
setup_timer是对init_timer的包装。
- 激活定时器
函数add_timer( )根据参数struct timer_list变量的expires值将定时器插入到合适的动态定时器的链表中,并激活定时器。函数首先检测定时器是否处于挂起状态,如果挂起给出警告信息并退出,否则插入合适的定时器链表。
void add_timer(struct timer_list *timer)
- 设置过期时间
int mod_timer(struct timer_list *timer, unsigned long expires);
函数mod_timer( )主要用于更改动态定时器的到期时间,从而可更改定时器的执行顺序,相当于执行如下代码序列:
del_timer(timer);
timer->expires=expires;
add_timer(timer);
mod_timer会修改过期时间并激活定时器
- 如果需要在定时器超时前停止定时器,可以使用del_timer()函数
int del_timer(struct timer_list * timer);
int del_timer_sync(struct timer_list * timer);
函数del_timer( )返回整数,可能的取值是0和1,对于活动定时器,返回1,对于不活动定时器返回0。del_timer_sync等待定时器处理函数执行完成函数。 当del_timer返回后,可以保证的只是:定时器不会再被激活,但是在多处理器机器上定时器可能已经在其他处理器上运行了,所以删除定时器是需要等待可能在其他处理器上运行的定时器处理程序都退出,这时就要使用del_timer_sync()函数执行删除工作。 应该在模块清理例程中释放定时器,可以单独检查定时器是否正在运行:
int timer_pending(struct timer_list *timer);
这个函数检查是否有触发的定时器回调函数挂起。
标准定时器案例
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/timer.h>
static struct timer_list my_timer;
void my_timer_callback(unsigned long data)
{
printk("%s callled (%ld).\n",__FUNCTION__,jiffies );
}
static int __init my_init(void)
{
int retval;
printk("Timer module loaded\n");
setup_timer(&my_timer,my_timer_callback,0);
printk("Setup timer to fire in 300ms (%ld)\n",jiffies);
retval = mod_timer(&my_timer,jiffies+msecs_to_jiffies(300));
if(ret)
{
printk("Timer firing failed\n");
}
return 0;
}
static void my_exit(void)
{
int retval;
retval = del_timer(&my_timer);
if(retval)
{
printk("The timer is still in use..\n");
}
pr_info("Tiner module unloaded.\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
3 高精度定时器(HRT)
标准定时器不够准确,其准确度为毫秒,内核2.6.16引入了高精度定时器,其精度达到微秒(取决于平台,最高可达纳秒)。标准定时器区取决于HZ(因为它们依赖于jiffies),而HRT实现基于ktime。
在系统上使用HRT时,要确认内核和硬件支持它。换句话说,必须用于平台相关的代码来访问硬件HRT。
需要头文件:
#include <linux/hrttimer.h>
在内核中HRT表示为hrttimer的实例:
**
* struct hrtimer - the basic hrtimer structure
* @node: timerqueue node, which also manages node.expires,
* the absolute expiry time in the hrtimers internal
* representation. The time is related to the clock on
* which the timer is based. Is setup by adding
* slack to the _softexpires value. For non range timers
* identical to _softexpires.
* @_softexpires: the absolute earliest expiry time of the hrtimer.
* The time which was given as expiry time when the timer
* was armed.
* @function: timer expiry callback function
* @base: pointer to the timer base (per cpu and per clock)
* @state: state information (See bit values above)
* @is_rel: Set if the timer was armed relative
*
* The hrtimer structure must be initialized by hrtimer_init()
*/
struct hrtimer {
struct timerqueue_node node;
ktime_t _softexpires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
u8 state;
u8 is_rel;
};
HRT的初始化的步骤如下:
- 初始化hrttimer。hrttimer初始化之前,需要设置ktime,它代表持续时间。hrttimer_init初始化高精度定时
extern void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,enum hrtimer_mode mode);
- 启动hrttimer,使用hrttimer_start函数:
static inline void hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode)
mode代表到期模式,对于实际时间,它应该是HRTIMER_MODE_ABS,对于相对于现在的时间,是HRTIMER_MODE_REL
- 取消hrtimer。
extern int hrtimer_cancel(struct hrtimer *timer);
extern int hrtimer_try_to_cancel(struct hrtimer *timer);
如果定时器处于激活状态或者回调函数正在运行,hrtimer_try_to_cancel会失败,返回-1,hrtimer_cancel会等待回调函数完成。
为了防止定时器自动重启,hrtimer回调函数必须返回HRTIMER_NORESTART
高精度定时器案例
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <linux/ktime.h>
#define MS_TO_NS(x) (x * 1E6L)
static struct hrtimer hr_timer;
enum hrtimer_restart my_hrtimer_callback( struct hrtimer *timer )
{
pr_info( "my_hrtimer_callback called (%ld).\n", jiffies );
return HRTIMER_NORESTART;
}
static int hrt_init_module( void )
{
ktime_t ktime;
unsigned long delay_in_ms = 200L;
pr_info("HR Timer module installing\n");
ktime = ktime_set( 0, MS_TO_NS(delay_in_ms) );
hrtimer_init( &hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL );
hr_timer.function = &my_hrtimer_callback;
pr_info( "Starting timer to fire in %ldms (%ld)\n", \
delay_in_ms, jiffies );
hrtimer_start( &hr_timer, ktime, HRTIMER_MODE_REL );
return 0;
}
static void hrt_cleanup_module( void )
{
int ret;
ret = hrtimer_cancel( &hr_timer );
if (ret)
pr_info("The timer was still in use...\n");
pr_info("HR Timer module uninstalling\n");
return;
}
module_init(hrt_init_module);
module_exit(hrt_cleanup_module);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_DESCRIPTION("Standard timer example");
4 内核中延迟和睡眠
延迟有两种类型,取决于代码运行的上下文:原子的或非原子的。处理内核延迟要包含的头文件是#include <linux/delay.h> 。
原子上下文
原子上下文中的任务(如ISR)不能进入睡眠状态,无法进行调度。这就是原子上下文延迟必须使用循环-等待循环的原因。内核提供Xdelay系列函数,在繁忙循环中消耗足够长的时间(基于jiffies),得到所需的延迟。
ndelay(unsigned long nsecs) udelay(unsigned long usecs) mdelay(unsigned long msecs)
应该时钟使用udelay(),因为ndelay()的精度取决于硬件定时器的精度。不建议使用mdelay()。
非原子上下文
在非原子上下文中,内核提供sleep系列函数,使用那个函数取决于需要延迟多长时间。
udelay(unsigned long usecs) :基于繁忙-等待循环。如果需要睡眠数微秒(小于等于10us左右),则应该使用该函数。usleep_range(unsigned long min,unsigned long max) :依赖于hrttimer,睡眠数微秒到数毫秒(10us-20ms)时建议使用它,避免使用udelay()的繁忙-等待循环msleep(unsigned long msecs) :由jiffies传统定时器支持,对于数毫秒以上的长睡眠(10ms+)请使用该函数。
|