概述
以下内容描述的都是linux内核下的知识点
什么是linux内核定时器?
内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于 <linux/timer.h> 和 kernel/timer.c 文件中。
被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则:
1) 没有 current 指针、不允许访问用户空间。因为没有进程上下文,相关代码和被中断的进程没有任何联系。
2) 不能执行休眠(或可能引起休眠的函数)和调度。
3) 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。
内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。
在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。
什么是hrtimer?
在聊高精度定时器(hrtimer)之前,我们先聊一下普通软件定时器(timer)。
普通软件定时器,也称作低分辨率定时器,所谓低分辨率定时器,是指这种定时器的计时单位基于jiffies值的计数,也就是说,它的精度只有1/HZ,假如你的内核配置的HZ是1000,那意味着系统中的低分辨率定时器的精度就是1ms。
用途:低分辨率定时器几乎是为“超时”而设计的,并为此对它进行了大量的优化,对于这些以“超时”未目的而使用定时器,它们大多数期望在超时到来之前获得正确的结果,然后删除定时器,精确时间并不是它们主要的目的,例如网络通信、设备IO等等。
简单的低精度定时器使用例子:
struct timer_list timer;
......
init_timer(&timer);
timer.function = _function;
timer.expires = _expires;
timer.data = _data;
add_timer(&timer);
mod_timer(&timer, jiffies+50);
del_timer(&timer);
高精度定时器,随着内核的不断演进,硬件也在不断地发展,系统中的定时器硬件的精度也越来越高,这也给高分辨率定时器的出现创造了条件。内核从2.6.16开始加入了高精度定时器架构。高精度定时器可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动,例如多媒体应用,音频设备的驱动程序等等。
hrtimer如何运转?
hrtimer的实现需要一定的硬件基础,依赖于timekeeper和clock_event_device。
hrtimer系统需要通过timekeeper获取当前的时间,计算与到期时间的差值,并根据该差值,设定该cpu的tick_device(clock_event_device)的下一次的到期时间,时间一到,在clock_event_device的事件回调函数中处理到期的hrtimer。
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock,
enum hrtimer_mode mode);
int hrtimer_start(struct hrtimer *timer, ktime_t tim,
const enum hrtimer_mode mode);
nt hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
unsigned long range_ns, const enum hrtimer_mode mode);
int hrtimer_cancel(struct hrtimer *timer);
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval);
static inline u64 hrtimer_forward_now(struct hrtimer *timer,ktime_t interval)
hrtimer的到期处理
高精度定时器系统有3个入口可以对到期定时器进行处理,它们分别是:
没有切换到高精度模式时,在每个jiffie的tick事件中断中进行查询和处理;
在HRTIMER_SOFTIRQ软中断中进行查询和处理;
切换到高精度模式后,在每个clock_event_device的到期事件中断中进行查询和处理;
如何切换到高精度模式?
hrtimer_run_pending
tick_check_oneshot_change
ts->check_clocks
ts->nohz_mode
hrtimer_is_hres_enabled
hrtimer_switch_to_hres
hrtimer的原理是什么?如何实现高精度定时?
以硬件定时器为基础,设计一套hrtimer架构,每一个内核都有自己的hrtimer,各自维护。
原理:高精度定时器通过timekeeper获取当前的时间,计算与到期时间的差值,并根据该差值,设定该cpu的tick_device(clock_event_device)的下一次的到期时间,时间一到,在clock_event_device的事件回调函数中处理到期的hrtimer。
如何实现:理解了高精度时间的原理后,就可以理解如何实现了,首先肯定是需要硬件支持,然后需要内核配置hrtimer,然后添加高精度定时器。并不一定能实现高精度。
内核hrtimer的配置?
CONFIG_RTC_HCTOSYS
CONFIG_NO_HZ
CONFIG_HIGH_RES_TIMERS
hrTimer软件架构
struct hrtimer {
struct timerqueue_node node;
ktime_t _softexpires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
unsigned long state;
......
};
定时器的到期时间用ktime_t来表示,_softexpires字段记录了时间,定时器一旦到期,function字段指定的回调函数会被调用,该函数的返回值为一个枚举值,它决定了该hrtimer是否需要被重新激活:
enum hrtimer_restart {
HRTIMER_NORESTART, /* Timer is not restarted */
HRTIMER_RESTART, /* Timer must be restarted */
};
state字段用于表示hrtimer当前的状态,有几下几种位组合:
#define HRTIMER_STATE_INACTIVE 0x00 // 定时器未激活
#define HRTIMER_STATE_ENQUEUED 0x01 // 定时器已经被排入红黑树中
#define HRTIMER_STATE_CALLBACK 0x02 // 定时器的回调函数正在被调用
#define HRTIMER_STATE_MIGRATE 0x04 // 定时器正在CPU之间做迁移
hrtimer的到期时间可以基于以下几种时间基准系统:
enum hrtimer_base_type {
HRTIMER_BASE_MONOTONIC, // 单调递增的monotonic时间,不包含休眠时间
HRTIMER_BASE_REALTIME, // 平常使用的墙上真实时间
HRTIMER_BASE_BOOTTIME, // 单调递增的boottime,包含休眠时间
HRTIMER_MAX_CLOCK_BASES, // 用于后续数组的定义
};
和低分辨率定时器一样,处于效率和上锁的考虑,每个cpu单独管理属于自己的hrtimer,为此,专门定义了一个结构hrtimer_cpu_base:
struct hrtimer_cpu_base {
......
struct hrtimer_clock_base clock_base[HRTIMER_MAX_CLOCK_BASES];
};
其中,clock_base数组为每种时间基准系统都定义了一个hrtimer_clock_base结构,它的定义如下:
struct hrtimer_clock_base {
struct hrtimer_cpu_base *cpu_base; // 指向所属cpu的hrtimer_cpu_base结构
......
struct timerqueue_head active; // 红黑树,包含了所有使用该时间基准系统的hrtimer
ktime_t resolution; // 时间基准系统的分辨率
ktime_t (*get_time)(void); // 获取该基准系统的时间函数
ktime_t softirq_time;// 当用jiffies
ktime_t offset; //
};
active字段是一个timerqueue_head结构,它实际上是对rbtree的进一步封装:
struct timerqueue_node {
struct rb_node node; // 红黑树的节点
ktime_t expires; // 该节点代表队hrtimer的到期时间,与hrtimer结构中的_softexpires稍有不同
};
struct timerqueue_head {
struct rb_root head; // 红黑树的根节点
struct timerqueue_node *next; // 该红黑树中最早到期的节点,也就是最左下的节点
};
timerqueue_head结构在红黑树的基础上,增加了一个next字段,用于保存树中最先到期的定时器节点,实际上就是树的最左下方的节点,有了next字段,当到期事件到来时,系统不必遍历整个红黑树,只要取出next字段对应的节点进行处理即可。timerqueue_node用于表示一个hrtimer节点,它在标准红黑树节点rb_node的基础上增加了expires字段,该字段和hrtimer中的_softexpires字段一起,设定了hrtimer的到期时间的一个范围,hrtimer可以在hrtimer._softexpires至timerqueue_node.expires之间的任何时刻到期,我们也称timerqueue_node.expires为硬过期时间(hard),意思很明显:到了此时刻,定时器一定会到期,有了这个范围可以选择,定时器系统可以让范围接近的多个定时器在同一时刻同时到期,这种设计可以降低进程频繁地被hrtimer进行唤醒。经过以上的讨论,我们可以得出以下的图示,它表明了每个cpu上的hrtimer是如何被组织在一起的:
为什么hrtimer使用了红黑树架构?
对于高分辨率定时器,我们期望它们的数据结构至少具备如下调节:(稳定且快速查找能力、快速插入和删除定时器能力、排序功能)
内核的开发者考察了多种数据结构,例如基数树、哈希表等等,最终他们选择了红黑树(rbtree)来组织hrtimer,红黑树已经以库的形式存在于内核中,
并被成功地使用在内存管理子系统和文件系统中,随着系统的运行,hrtimer不停地被创建和销毁,新的hrtimer按顺序被插入到红黑树中,
树的最左边的节点就是最快到期的定时器,内核用一个hrtimer结构来表示一个高精度定时器。
hrtimer如何运转?
高精度定时器系统有3个入口可以对到期定时器进行处理,它们分别是:
没有切换到高精度模式时,在每个jiffie的tick事件中断中进行查询和处理;
在HRTIMER_SOFTIRQ软中断中进行查询和处理;
切换到高精度模式后,在每个clock_event_device的到期事件中断中进行查询和处理;
如何实现让hrtimer定时时间小于1ms?关于hrtimer定时器精度
只要硬件支持,内核支持,就可以实现定时时间小于1ms。否则hrtimer会按照时钟TICK调用高精度定时器。
问题描述:
首先kernel中已经打开了high resolution Timer support,
驱动程序中hetimer初始化,设置hrtimer定时时间为60us,但实际示波器检测发现定时时间为1ms(1000/HZ).
在linux上可通过命令来查看调度器的精度。
cat /proc/timer_list | grep .resolution
可以尝试在驱动中添加打印(具体查看下可能是什么原因导致的高精度定时器无法使用):
int tick_check_oneshot_change(int allow_nohz)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);
if (!test_and_clear_bit(0, &ts->check_clocks))
return 0;
printk("%s(%d) ts->check_clocks=%dn", __func__, __LINE__, ts->check_clocks);
if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
return 0;
printk("%s(%d) ts->nohz_mode=%dn", __func__, __LINE__, ts->nohz_mode);
if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
return 0;
printk("%s(%d)allow_nohz=%dn", __func__, __LINE__, allow_nohz);
if (!allow_nohz)
return 1;
printk("%s(%d)n", __func__, __LINE__);
tick_nohz_switch_to_nohz();
return 0;
}
最后
以上就是不安柚子为你收集整理的操作系统 -- Linux 高精度定时器 Hrtimer的全部内容,希望文章能够帮你解决操作系统 -- Linux 高精度定时器 Hrtimer所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复