Operating System - Linux High Precision Timer Hrtimer

The following content describes the knowledge points under the linux kernel

 

What is the linux kernel timer?
    The kernel timer is a mechanism used by the kernel to control the execution of a function at a certain point in the future (based on jiffies), and its implementation is located in the <linux/timer.h> and kernel/timer.c files.
    The scheduled function must be executed asynchronously. It is similar to a "software interrupt" and is in a non-process context, so the scheduling function must comply with the following rules:
        1) There is no current pointer and access to user space is not allowed. Because there is no process context, the relevant code has no connection with the interrupted process.
        2) Sleep (or functions that may cause sleep) and scheduling cannot be performed.
        3) Any data structure that is accessed should be protected against concurrent access to prevent race conditions.
    After the kernel timer's scheduling function runs once, it will no longer be run (equivalent to automatic logout), but it can be run periodically by rescheduling itself in the scheduled function.
    In the SMP system, the scheduling function always runs on the same CPU that registered it, in order to obtain the locality of the cache as much as possible.

 

What is hrtimer?
    Before talking about high-precision timers (hrtimer), let's talk about ordinary software timers (timer).
    Ordinary software timer, also called low-resolution timer, so-called low-resolution timer, refers to the counting unit of this timer based on the jiffies value, that is, its accuracy is only 1/HZ, if you The HZ of the kernel configuration is 1000, which means that the accuracy of the low-resolution timer in the system is 1ms.
    Purpose: The low-resolution timer is almost designed for "timeout", and a lot of optimizations have been made for it. For these timers used for "timeout" for no purpose, most of them expect to get it before the timeout arrives The correct result, and then delete the timer, accurate time is not their main purpose, such as network communication, device IO, etc.
    Simple low-precision timer usage example:

		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);

High-precision timers, with the continuous evolution of the core, the hardware is constantly developing, and the precision of the timer hardware in the system is getting higher and higher, which also creates conditions for the emergence of high-resolution timers. The high-precision timer architecture has been added to the kernel since 2.6.16. High-precision timers can provide us with nanosecond timing accuracy to meet applications or kernel drivers that have an urgent need for precise time, such as multimedia applications, audio device drivers, and so on.

 

How does hrtimer work?
    The implementation of hrtimer requires a certain hardware foundation, which depends on timekeeper and clock_event_device.
    The hrtimer system needs to obtain the current time through timekeeper, calculate the difference with the expiration time, and set the next expiration time of the tick_device (clock_event_device) of the cpu according to the difference. Once the time is up, the event in clock_event_device The expired hrtimer is processed in the callback function.

	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 expiration processing
    precision timer system has three inlets expiration timer may be processed, they are:
        no highly accurate switching mode, and a query processing of each jiffie tick interrupt event;
        the Query and process in the HRTIMER_SOFTIRQ soft interrupt;
        after switching to high-precision mode, query and process in the expiration event interrupt of each clock_event_device;

 

How to switch to high precision mode?
 

    hrtimer_run_pending
        tick_check_oneshot_change
            ts->check_clocks
            ts->nohz_mode
            hrtimer_is_hres_enabled
        hrtimer_switch_to_hres

 

What is the principle of hrtimer? How to achieve high-precision timing?
    Based on the hardware timer, a set of hrtimer architecture is designed, and each core has its own hrtimer, which is maintained separately.
    Principle: The high-precision timer obtains the current time through timekeeper, calculates the difference with the expiration time, and sets the next expiration time of the tick_device (clock_event_device) of the cpu according to the difference. Once the time is up, The expired hrtimer is processed in the event callback function of clock_event_device.
    How to implement: After understanding the principle of high-precision time, you can understand how to implement it. First of all, it must be supported by hardware, then the kernel needs to be configured with hrtimer, and then a high-precision timer is added. It is not always possible to achieve high accuracy.

 

The configuration of the kernel hrtimer?

    CONFIG_RTC_HCTOSYS
    CONFIG_NO_HZ
    CONFIG_HIGH_RES_TIMERS

 

hrTimer software architecture

	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是如何被组织在一起的:

 

Why does hrtimer use a red-black tree architecture?
    For high-resolution timers, we expect their data structure to have at least the following adjustments: (stable and fast search capability, fast insertion and deletion timer capability, sorting function)
    The developers of the kernel have examined a variety of data structures, such as radix tree , Hash table, etc., in the end they chose a red-black tree (rbtree) to organize hrtimer. The red-black tree has existed in the kernel in the form of a library and has
    been successfully used in the memory management subsystem and file system. As the system runs, hrtimer is constantly being created and destroyed. New hrtimer is inserted into the red-black tree in order
    . The leftmost node of the tree is the fastest expiring timer. The kernel uses an hrtimer structure to represent one High-precision timer.

 

How does hrtimer work?
    The high-precision timer system has 3 entries to process expired timers. They are: when
        not switched to high-precision mode, query and process in each jiffie tick event interrupt;
        in HRTIMER_SOFTIRQ soft interrupt Query and processing;
        after switching to high-precision mode, query and process in each clock_event_device expiration event interrupt;

 

How to make hrtimer timing time less than 1ms? Regarding the accuracy of the hrtimer timer,
    as long as the hardware supports it and the kernel supports it, the timing time can be less than 1ms. Otherwise, hrtimer will call the high-precision timer according to the clock TICK.

 

Problem description:
    First, the high resolution Timer support has been turned on in the kernel, the
    hetimer in the driver is initialized, and the hrtimer timing time is set to 60us, but the actual oscilloscope test finds that the timing time is 1ms (1000/HZ).
    
You can view it by command on linux The accuracy of the scheduler.

cat /proc/timer_list | grep .resolution

You can try to add printing to the driver (see what may cause the high-precision timer to be unusable):

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;
}

 

Guess you like

Origin blog.csdn.net/Ivan804638781/article/details/97112603