Linux定时器

原文:正点原子Linux驱动手册

Linux内核时间管理

系统时间管理需要一个硬件定时器提供时钟 ,通常这个定时器时通用的,有利于系统移植。
像UCOS 或 FreeRTOS 一般使用 Systick 作为系统时钟源(systick 是cortex-m 内核提供的定时器,芯片厂商在使用cortex-m内核制作芯片时为了通用性也会选择它作为芯片的硬件定时器)。

同样Linux 也需要一个定时器来驱动,Linux内核就是使用它来进行一系列的时间管理操作。(具体是什么不深究)

Linux 内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序 、对于驱动编写来说最常用的是定时器。

节拍率(系统频率)

节拍率:系统每秒计数的节拍数。

硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后就周期性的产生定时中断,系统使用定时中断来计时。
中断周期性产生的频率就是系统频率,也叫做节拍率(tick rate)(有的资料也叫系统频率),单位是 Hz。 比如 1000Hz, 100Hz 等等说的就是系统节拍率。
1000Hz 就是每秒钟产生1000次中断,100Hz就是每秒钟产生100次中断。
系统节拍率是可以设置的,我们在编译 Linux 内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面:
-> Kernel Features -> Timer frequency (<choice> [=y])
在这里插入图片描述
设置好以后打开 Linux 内核源码根目录下的.config 文件中也可以发现配置后定义的系统节拍率:
在这里插入图片描述
Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。打开文件 include/asm-generic/param.h,有如下内容:

6 # undef HZ
7 # define HZ CONFIG_HZ
8 # define USER_HZ 100
9 # define CLOCKS_PER_SEC (USER_HZ)

节拍率大小的优劣

  1. 当节拍率越大的时候,发生中断的次数就越多,时间精度也就越高如果采用 100Hz 的节拍率,时间精度就是 10ms(1秒钟内发生100次中断,一次中断的计时长度就是1s/100 = 10ms),采用1000Hz 的话时间精度就是 1ms,精度提高了 10 倍。
  2. 高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担, 1000Hz 和 100Hz的系统节拍率相比,系统要花费 10 倍的“精力”去处理中断。中断服务函数占用处理器的时间增加。

jiffies

Linux 内核使用全局变量 jiffies 来记录系统从启动以来的系统节拍数(jiffies/HZ 就代表系统启动时间),系统启动的时候会将 jiffies 初始化为 0。

jiffies 定义在文件 include/linux/jiffies.h 中,定义如下:

76 extern u64 __jiffy_data jiffies_64;
77 extern unsigned long volatile __jiffy_data jiffies;

jiffies_64 和 jiffies 其实是同一个东西, jiffies_64 用于 64 位系统,而 jiffies 用于 32 位系统。为了兼容不同的硬件, jiffies 其实就是 jiffies_64 的低 32 位, jiffies_64 和 jiffies 的结构如图:
在这里插入图片描述
HZ 表示每秒的节拍数, jiffies 表示系统运行的 jiffies 节拍数,所以 jiffies/HZ 就是系统运行时间,单位为秒。

不管是 32 位还是 64 位的 jiffies,都有 溢出 的风险,溢出以后会重新从 0 开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。

假如 HZ 为最大值 1000 的时候, 32 位的 jiffies 只需要 49.7 天就发生了绕回,对于 64 位的 jiffies 来说大概需要5.8 亿年才能绕回,因此 jiffies_64 的绕回忽略不计。

处理 32 位 jiffies 的绕回显得很重要,Linux 内核提供了如下 所示的几个 API 函数来处理绕回:

time_after(unkown, known)
time_before(unkown, known) 
time_after_eq(unkown, known)
time_before_eq(unkown, known)		//unkown 通常为 jiffies, known 通常是需要对比的值。

假如计时 1s 时间,可以将1s 换算成对应的节拍数,那么就可以对比 known(jiffies) 和unkown (jiffies + 1s 的节拍数),jiffies 表示当前的节拍数,jiffies + 1s节拍数就代表1s后的节拍数。
通常情况下unkown 肯定是大于known,所以当unkown 小于known 时就是绕回了。

如果 unkown 超过 known 的话, time_after 函数返回真,否则返回假。如果 unkown 没有超过 known 的话 time_before 函数返回真,否则返回假。 time_after_eq 函数和 time_after 函数类似。

利用这个特点我们可以用作代码里经常使用的超时 timeout。

为了方便开发, Linux 内核提供了几个 jiffies 和 ms、 us、 ns 之间的转换函数(时间单位与节拍数之间的转换):

int jiffies_to_msecs(const unsigned long j)			//将 jiffies 类型的参数 j 分别转换为对应的毫秒、微秒、纳秒。
int jiffies_to_usecs(const unsigned long j) 
u64 jiffies_to_nsecs(const unsigned long j)

long msecs_to_jiffies(const unsigned int m)			//将毫秒、微秒、纳秒转换为 jiffies 类型。
long usecs_to_jiffies(const unsigned int u) 
unsigned long nsecs_to_jiffies(u64 n)

Linux 定时器

  1. 这里所说的 Linux定时器是软定时器, 基于系统节拍率 (HZ)系统启动总节拍数 (jiffies) 实现。
  2. jiffies 加上需要计时的时间对应的节拍数,当计时时间到达后,会进入 定时处理函数(跟中断处理函数功能相似,但是不是中断,软件定时器也不依赖中断实现)。

jiffes +(time*HZ)

  1. Linux 软件定时器每次开启只能作一次计时,再次使用需要再次开启(可以在定时处理函数中开启)。

定时器API

既然是内核提供的定时器,自然由内核提供API。

Linux 内核使用 struct timer_list 描述一个定时器:

struct timer_list {
struct list_head entry;
unsigned long expires; /* 定时器超时时间,单位是节拍数(相当于我们的定时周期) */
struct tvec_base *base;
void (*function)(unsigned long); /* 定时处理函数 */
unsigned long data; /* 要传递给 function 函数的参数 */
int slack;
};
1、初始化定时器

初始化一个定时器:

void init_timer(struct timer_list *timer)

timer: 要初始化定时器。

上面这个函数类似于复位操作,我们还是需要对定时器做一些初始化,主要是对struct timer_list成员expires,function,data 的赋值,注意expires的单位是节拍数,可用上面的函数转化。

2、 add_timer 函数

add_timer 函数用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行,函数原型如下:

void add_timer(struct timer_list *timer)

timer: 要注册的定时器。

3、del_timer

del_timer 函数用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。

在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出。

int del_timer(struct timer_list * timer)

返回值: 0,定时器还没被激活; 1,定时器已经激活。

del_timer_sync 函数

del_timer_sync 函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中。

int del_timer_sync(struct timer_list *timer)

返回值: 0,定时器还没被激活; 1,定时器已经激活。

4、mod_timer 函数

mod_timer 函数用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器! 可将此函数在定时处理中调用,就能循环定时。

int mod_timer(struct timer_list *timer, unsigned long expires)

timer: 要修改超时时间(定时值)的定时器。
expires: 修改后的超时时间。
返回值: 0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活。

定时器示例程序

1 struct timer_list timer; /* 定义定时器 */
2 
3/* 定时器回调函数 */
4 void function(unsigned long arg)
5 {
6 /*
7 * 定时器处理代码
8 */
9
10 /* 如果需要定时器周期性运行的话就使用 mod_timer
11 * 函数重新设置超时值并且启动定时器。
12 */
13 mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));
14 }
15
16 /* 初始化函数 */
17 void init(void)
18 {
19 init_timer(&timer); /* 初始化定时器 */
20
21 timer.function = function; /* 设置定时处理函数 */
22 timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
23 timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */
24
25 add_timer(&timer); /* 启动定时器 */
26 }

Linux内核短暂延时函数

有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。 Linux 内核提供了毫秒、微
秒和纳秒延时函数,这三个函数如下所示:

void ndelay(unsigned long nsecs)	/*纳秒、微秒和毫秒延时函数。*/
void udelay(unsigned long usecs) 	
void mdelay(unsigned long mseces)
  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值