操作系统 -- Linux 高精度定时器 Hrtimer

以下内容描述的都是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=%d\n", __func__, __LINE__, ts->check_clocks);
	if (ts->nohz_mode != NOHZ_MODE_INACTIVE)
		return 0;
 printk("%s(%d) ts->nohz_mode=%d\n", __func__, __LINE__, ts->nohz_mode);
	if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())
		return 0;
 printk("%s(%d)allow_nohz=%d\n", __func__, __LINE__, allow_nohz);
	if (!allow_nohz)
		return 1;
 printk("%s(%d)\n", __func__, __LINE__);
	tick_nohz_switch_to_nohz();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Ivan804638781/article/details/97112603