1.低分辨率定时
(1)定时器结构体描述:
struct timer_list {
struct list_head list;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long);
};
双向链表元素list:用来将多个定时器连接成一条双向循环队列。
expires:期望的定时时间,一般这个定期时间用系统启动以来的节拍数来表示。当然系统节拍数跟定时时间单位秒有转换的公式以及有相关的api。稍微理解一下就可以了。
data:在调用定时器中断服务函数会作为参数传递
函数指针function:定时器到期执行的软中断服务函数(回调函数).
(2)HZ,Tcik JIFFIES,以及通俗时间
HZ:这个是系统频率,我们平常理解的频率的概念就是这个周期性触发动作在某个时间点内有多少次。例如我们的人体每一分钟跳60,这个就是我们的心跳频率。同样linux内核的HZ类似我们人类的心跳频率,准确的说这是系统的一个心跳,每一秒钟中断了多少次为一个HZ(为了形象一点在,这里的系统中断我们称为系统心跳)。一般来说HZ值linux内核在每个版本有固定的值。一般为100,250,1000中其中一个。
Tick:系统心跳的时间间隔,也就是系统每发生一次中断的时间间隔。例如每秒钟系统的心跳是100下,那么系统的HZ就是100,那么的Tick是多少?同样类比我们的人的心跳,假如我们人的心跳每分钟60下,那么每个心跳一下需要多久?不就是1/60分钟=1秒。同样Tick 表示每个系统心跳需要多久Tick = 100/1秒=0.01秒=10ms.
得出公式:Tick = 1/HZ
JIFFIES:自系统开机以来系统的心跳数。linux系统不用我们通俗秒,分钟等时间概念来统计系统运行了多久,而是通过系统心跳跳了多少下来代表系统运行了多久(为了形象一点在,这里的系统中断我们称为系统心跳)。其实想想也是,如果来统计人活的多久的话,准确来说你的心跳跳了多久也就是你活了多久,因为人的一般来说心跳停止了人也完了,可悲的是系统可以复位重启,而人好像没有复位按键。那么假如我们有一个宝宝出生至今跳了600,那么我们想要知道他活了多久能不能知道呢?很简单一般来说我们人类每分钟心跳频率是60下,那我们就能通过很简单的数学公式知道我们的宝宝活了多长时间,很简单的公式换算如下:600/60=10(分钟)。同样我们想要知道系统运行了多长,我们只需要知道系统每秒钟心跳多少次,也就是我们上面所说的H mod_timer(&t->timer,(jiffies + msecs_to_jiffies(200) ));//重启定时器Z就能知道了。假如目前系统心跳是60000下,并且系统的心跳HZ是100,那么我想要知道系统运行的了多久就很快能知道了:t(运行时间) = 60000/100= 600(秒)。
通过上面的了解我们就能得出如下公式:
t(系统运行的时间) = JIFFIES /HZ.
(3)定时
系统定时的原理是在当前的心跳基础上加上你要定时的时间来定时,但是这个时间要转化为心跳值。同样举例,一个人想在在1分钟后他要去一个地方,那么他需要定个时间,由于没有时间表,但是他知道他自己每分钟跳60下,那么就通过把脉数自己的心跳脉冲60下也就知道一分钟定时到了,同样系统定时也是这样的原来,在目前的基础上加上定时的心跳得到一个值,等系统心跳到达这个期望的值的时候就出发定时器中断进行定时中断处理。也就是expires:期望的定时时间,这个时间是通过系统心跳来定时的。
timer.expires = (目前的时间+定时时间)
目前时间:就是我们上面说的JIFFIES
定时时间:这个就需要我们把我们需要定时的时间换算成心跳值,例如定时2秒,系统的HZ是100,也就是每个系统心跳需要Tick=10ms,那么定时2秒需要的系统心跳数等于2秒/10ms=200个心跳值。仔细看这个值的换算过程
2/(1/HZ)=200.那么我们就可以得到公式:
定时时间单位为秒的时间的心跳值=(定时时间秒数)*HZ
同样上面的定时公式为:
timer.expires = 目前的JIFFIES + (定时时间秒数)*HZ时间
上面说的是秒的转算成JIFFIES,毫秒定时怎么换算?不用担心,linux系统已经有对应api给出来了:
msecs_to_jiffies(ms)//毫秒换算成jiffies
usecs_to_jiffies(us)//微妙换算成jiffies
jiffies_to_msecs(const unsigned long j)//jiffies换算成ms
注意:注意每个心跳值的最小间隔时间,也就是最小的tick,当定时的时间小于一个Tick时间时会默认按一个Tick来算。例如当HZ为100时,也就是每个Tick=10ms,那么当msecs_to_jiffies(2)的时候,返回的值是一个Tick,也就是10ms而不是2ms。
(3定时器运用
想要运行一个定时器,一般如下:
(1)定义一个struct timer_list变量并且初始化结构体变量
struct count_timer{
atomic_t counter;
struct timer_list timer;
};
void timer_handler_func(unsigned long arg){
struct count_timer *t = (struct count_timer *)(arg);
pr_err("timer handler c_timer->counter=%d\n",c_timer->counter);
mod_timer(&t->timer,(jiffies + msecs_to_jiffies(200) ));//重启定时器
}
struct count_timer c_timer;
//初始化定时器
void init_pulse_timer(){
atomic_set(&c_timer->counter,0);
c_timer->timer.function = timer_handler_func;
c_timer->timer.data = (unsigned long)&c_timer;
c_timer->timer.expires = jiffies + msecs_to_jiffies(200);//定时200ms
init_timer(&c_timer->timer);
add_timer(&c_timer->timer);
}
注意事项:
(1)定时器中断函数在处理的时候会关闭cpu调度,所以不能在里面进行休眠阻塞。
(2)定时器中断函数是在低半部执行,中断服务函数要尽可能快速执行完,
(3)注意每个心跳值的最小间隔时间,也就是最小的tick,当定时的时间小于一个Tick时间时会默认按一个Tick来算。例如当HZ为100时,也就是每个Tick=10ms,那么当msecs_to_jiffies(2)的时候,返回的值是一个Tick,也就是10ms而不是2ms。
(4)定时器到期后执行完定时回调函数后会自动把定时器删除,不需要人为删除定时器,假如要周期性输出则可以在定时器回调函数中再次启动定时器,对应api为 mod_timer(&t->timer,(jiffies + msecs_to_jiffies(200) ));//重启定时器
(5)执行定时输出的时候要注意竞争访问,要等完一个定时器执行完的时候,其他进程才能再次访问。