Linux延迟和定时器管理
时间是继内存之后常用的资源之一。它用于执行几乎所有的事情:延迟工作、睡眠、调度、超时以及许多其它任务。
时间有两类:
- 绝对时间:指一天的日期和时间,内核使用绝对时间了解具体时间。有一种硬件芯片称为实时时钟(RTC)。
- 相对时间:相对时间的作用是被内核调度程序使用,为了处理相对时间,内核依赖于被称作定时器的CPU功能(外设),也称作内核定时器。
内核定时器分为两个不同的部分
- 标准定时器或系统定时器
- 高精度定时器
标准定时器
-
Jiffy和HZ
Jiffy是在***<linux/jiffies.h>***中声明的内核时间单位,HZ是Jiffies在1s内递增的次数,取决于硬件和内核版本,也决定了时钟中断触发的频率。Jiffies每个增量被称为一个Tick。
不同的系统上采用了不同类型的Jiffy变量防止溢出。
<linux/jiffies.h> extern u64 __cacheline_aligned_in_smp jiffies_64; extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies;
-
定时器API
定时器在内核中表示为timer_list的一个实例:
#include <linux/timer.h>
struct timer_list {
struct list_head entry;
unsigned long expires;
struct tvec_base *base;
void (*function)(unsigned long);
unsigned long data;
};
- expires:定时器到期时间,时间以用节拍表示。
- entry:双向链表,定时器链表入口。
- data:传递给回调函数。
- base:用来指定定时器在哪个CPU上执行。
- function:定时器到期时执行函数的地址。
初始化定时器
-
设置定时器,提供用户定义的回调函数和数据:
void setup_timer ( struct timer_list *timer, void (*function) (unsigned long), unsigned long data);
-
设置过期时间。当定时器初始化时,需要在启动回调之前设置它的过期时间:
int mod_timer( struct timer_list *timer, unsigned long expires);
-
释放定时器。定时器用过之后需要释放:
void del_timer(struct timer_list *timer); int del_timer_sync(struct timer_list *timer);
高精度定时器(HRT)
高精度定时器之所以被称为高精度,是因为它的精度能达到微妙(最高可至纳秒),而标准定时器的精度则为毫秒。标准定时器取决于HZ,而HRT实现是基于ktime。<linux/hrtimer.h>
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;
u8 is_soft;
};
初始化HRT
-
初始化hrtimer。hrtimer初始化之前,需要设置ktime,它代表持续时间:
static void __hrtimer_init(struct hrtimer *timer, clockid_t clock_id, enum hrtimer_mode mode);
-
启动hrtimer:
static inline void hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode) { hrtimer_start_range_ns(timer, tim, 0, mode); }
-
取消hrtimer。取消定时器或者查看是否可能取消它:
int hrtimer_cancel(struct hrtimer *timer)
{
for (;;) {
int ret = hrtimer_try_to_cancel(timer);
if (ret >= 0)
return ret;
cpu_relax();
}
}
int hrtimer_try_to_cancel(struct hrtimer *timer)
{
struct hrtimer_clock_base *base;
unsigned long flags;
int ret = -1;
if (!hrtimer_active(timer))
return 0;
base = lock_hrtimer_base(timer, &flags);
if (!hrtimer_callback_running(timer))
ret = remove_hrtimer(timer, base, false);
unlock_hrtimer_base(timer, &flags);
return ret;
}
内核中的延迟和睡眠
延迟有两种类型,取决于代码运行的上下文:原子的或非原子的。<linux/delay.h>
-
原子上下文
原子上下文的任务无法进入睡眠状态,无法进行调度,所以延迟必须使用繁忙-等待。
以下几个函数作用是消耗足够长的时间,达到延迟的效果:
-
ndelay (unsigned long nseus) /* 精度取决于硬件定时器的精度 */
-
udelay (unsigned long usecs)
-
mdelay (unsigned long msecs)
-
-
非原子上下文
非原子上下文中,内核提供 ***sleep[_range]***系列函数,使用哪个函数取决于需要延迟多长时间。
- udelay(unsigned long usecs):基于繁忙-等待循环。睡眠时长小于或等于10us左右建议使用该函数。
- usleep_range(unsigned long min, unsigned long max):依赖于hrtimer,睡眠数微妙到数毫秒(10ms~20ms)时建议使用。
- msleep(unsigned long msecs):由jiffies/传统定时器支持。对于数毫秒以上的长睡眠(10ms+)建议使用该函数。