LInux内核定时器

简介

时间是继内存之后常用的资源之一。它用于执行几乎所有的事情:延迟工作、睡眠、调度、超时以及许多其他任务。

时间有两类。内核使用绝对时间来了解具体时间,也就是一天的日期和时间,而相对时间则被内核调度程序使用。对于绝对时间,有一个称为实时时钟(RTC)的硬件芯片。为了处理相对时间,内核依赖于被称作定时器的 CPU 功能(外设),从内核的角度来看,它被称为内核定时器。

内核定时器分为两种:
1.标准定时器或系统定时器
2.高精度定时器

标准定时器

标准定时器是内核定时器,它以 Jiffy 为粒度运行。

jiffy 和 HZ

Jiffy 是在<linux/jiffies.h>中声明的内核时间单位。而Jiffy的单位是HZ,它是 jiffies 在 1s 内递增的次数。每个增量被称为一个 Tick。换句话说,HZ代表 Jiffy 的大小。HZ 取决于硬件和内核版本,也决定了时钟中断触发的频率。这在某些体系结构上是可配置的,而在另一些机器上则是固定的。简言之jiffies每秒增加1 HZ,转换成时间秒的换算关系是jiffies / HZ。

标准定时器API

定时器在内核中表示为timer_list的一个实例

#include <linux/timer.h>
struct timer_list {
	struct list_head entry;
	unsigned long expires;
	struct tvec_t_base_s *base;
	void (*function)(unsigned long);
	unsigned long data;
}

expires是以jiffies为时间单位的绝对值。entry是双向链表,data是可选的,被传给回调函数。
初始化定时器的步骤如下:
(1)设置定时器:提供用户定义的回调函数和数据

void setuo_timer(struct timer_list *timer,\
			void (*function)(unsigned long),\
			unsigned long data);

也可以使用下面函数:

void init_timer(struct timer_list *timer);

(2)设置过期时间:当定时器初始化时,需要在启动回调函数之前设置它的过期时间

int mode_timer(struct timer_list *timer, unsigned long expires);

(3)释放定时器:定时器使用过后需要释放定时器

void del_timer(struct timer_list *timer);
int del_timer_sync(struct timer_list *timer);

del_timer函数是强制结束定时器,无论时间到与否。当定时时间到时返回0,时间未到时返回1;del_timer_sync函数会一直等待定时处理结束(即使是在另外一个CPU上执行),所以定时器的回调函数要注意造成死锁问题。
应该在模块清理历程中释放定时器,可以使用定时器检查函数检查定时器是否在运行:

int timer_pending(const 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 called (%ld).\n", __FUNCTION__, jiffies);
}
//模块初始化函数
static init __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 = mode_timer(&my_timer, jiffies + msecs_to_jiffies(300));
	if (retval) {
	printk("Timer firing fail\n");
	}
	return 0;
}
//模块退出函数
static void my_exit()
{
	int retval;
	//释放定时器
	retval = del_timer(my_timer);
	if (retval){
	printk("The timer is stil in use...\n");
	}
	pr_info("Timer module unloaded\n");
}
module_init(my_init);
module_exit(mt_exit);

高精度定时器(HRT)

标准定时器不够精确,单位是毫秒。不适用实时应用,为此引入高精度定时器(由内核配置中的CONFIG_HIGH_RES_TIMERS选项启用),其精度达到微妙(甚至纳秒,取决平台)。标准定时器取决于HZ,而HRT实现基于ktime。

在系统上使用HRT时,要确认内核和硬件是否支持。换句话说要用平台相关代码来访问硬件HRT。定义在<linux/hrtimer.h>。
在内核中HRT表示为hrtiimer的实例:

struct hrtimer {
	struct timer_queue_node node;
	ktime_t _softexpires;
	enum hrtimer_restart(*function) (struct hrtimer *);
}

附件:
转自:
https://blog.youkuaiyun.com/z1026544682/article/details/105650719
Linux内核时钟中断设施
linux的时钟中断需要两个全局变量,分别是xtime与jiffies。

1、xtime
一个timeval结构类型变量,是从cmos电路中取得的时间,一般是从某一历史时刻开始到现在的时间,也就是为了取得我们操作系统上显示的日期。这个就是“实时时钟”,它的精确度是微秒。获取方式是通过sys/time.h头文件里面的gettimeofday函数获取。

2、HZ
Linux核心每隔固定周期会发出timer interrupt (IRQ 0),HZ是用来保存每一秒有几次timer interrupts。如HZ为1000,代表每秒有1000次timer interrupts。 HZ可在编译核心时设定,可设定100、250、300或1000。核心版本预设值为250。
HZ这个值可以理解为操作系统的时钟频率,这个时钟的精度远低于硬件的时钟频率。如HZ设置为250,那么为一秒钟250次,每次为4ms,因此操作系统的时钟精度只能达到4ms。

3、Tick
Tick是HZ的倒数,意即timer interrupt每发生一次中断的时间。如HZ为250时,tick为4毫秒(millisecond)。

4、Jiffies
在<linux/jiffies.h>,定义了Jiffies(unsigned long),在linux内核中jiffies远比xtime重要。每发生一次timer interrupt,Jiffies变数会被加一。一秒内时钟中断的次数等于HZ,所以jiffies一秒内增加的值也就是HZ。
在 Linux 2.6 中,系统时钟每 1 毫秒中断一次(时钟频率,用 HZ 宏表示,定义为 1000,即每秒中断 1000 次,2.4 中定义为 100,很多应用程序也仍然沿用 100 的时钟频率),这个时间单位称为一个 jiffie。jiffies 与绝对时间之间的转换, 用两个宏来完成两种时间单位的互换:JIFFIES_TO_NS()、NS_TO_JIFFIES()。
jiffies是记录着从电脑开机到现在总共的时钟中断次数。在linux内核中jiffies远比xtime重要,那么他取决于系统的频率,单位是Hz。
这是硬件给内核提供一个系统定时器用以计算和管理时间,连续累加一年四个多月后就会溢出(假定HZ=100,1个jiffies等于1/100秒,jiffies可记录的最大秒数为 (2^32 -1)/100=42949672.95秒,约合497天或1.38年),即当取值到达最大值时继续加1,就变为了0。
因此为防止溢出需要使用这些宏函数进行:time_before()、 time_after()、time_after_eq()、time_before_eq()。因为jiffies随时钟滴答变化,不能用编译器优化 它,应取volatile值。
另外,80x86架构定义一个与jiffies相关的变数jiffies_64 ,w为64位,要等到此变数溢位可能要好几百万年。jiffies被对应至jiffies_64最低的32位元。因此,经由jiffies_64可以完全不理会溢位的问题便能取得jiffies。

5、RTC
除了系统定时器外,还有一个与时间有关的时钟:实时时钟(RTC),这是一个硬件时钟,用来持久存放系统时间,系统关闭后靠主板上的微型电池保持计时。系统启动时,内核通过读取RTC来初始化Wall Time,并存放在xtime变量中,这是RTC最主要的作用。

延迟函数问题
1、sleep
Linux下的精度为秒,是精确的,实现原理如下:

  1. 注册一个信号signal(SIGALRM,handler)。接收内核给出的一个信号。
  2. 调用alarm()函数。
  3. pause()挂起进程。

alarm是当前进程的私有定时闹钟,pause函数挂起当前进程,内核切换到其他进程运行。当alarm设置的时间到时,内核通过SIGALRM信号去处理,pause函数返回后进程继续执行。

2、usleep
精度为微妙,但实际并不能精确到微妙。通过前面给出的操作系统时钟基础设置,系统精度由HZ系统时钟频率决定,一般设置为250时精度只能到4ms,因此usleep的微妙延迟只能是至少延迟的时间,时间上操作系统进行内核到用户的切换就会花10-30ms级别的时间,因此短时间的延迟一般都会大于设定值。
另外usleep有以下的问题

  1. 在一些平台下不是线程安全,如HP-UX以及Linux usleep()会影响信号;
  2. 在很多平台,如HP-UX以及某些Linux下,参数的值必须小于1 * 1000 * 1000也就是1秒,否则该函数会报错,并且立即返回。
  3. 大部分平台的帮助文档已经明确说了,该函数是已经被舍弃的函数。

3、高精度延迟nanosleep、select
针对Linux平台下的高精度延迟,Linux2.0.x新增了nanosleep系统调用。精度虽然可以设置到纳秒,但是依然没有精确到纳秒,不过对于毫秒级别的延迟的精度已经足够高了,可以完全满足usleep无法达到的精度。但使用nanosleep应注意判断返回值和错误代码,否则容易造成cpu占用率100%。另外,nanosleep()没有usleep函数的缺点,在Solaris的多线程环境下编译器会自动把usleep()连接成nanosleep()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值