内核为高精度定时器重新设计了一套软件架构,它可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动。该套架构相关文件为kernel\time\hrtimer.c和include\linux\hrtimer.h。

1、hrtimer的初始化

// kernel\time\hrtimer.c

/**
 * hrtimer_init - initialize a timer to the given clock
 * @timer:	the timer to be initialized
 * @clock_id:	the clock to be used
 * @mode:	timer mode abs/rel
 */
void hrtimer_init(struct hrtimer *timer, clockid_t clock_id,
		  enum hrtimer_mode mode)
{
	debug_init(timer, clock_id, mode);
	__hrtimer_init(timer, clock_id, mode);
}
EXPORT_SYMBOL_GPL(hrtimer_init);

通过hrtimer_init对定时器进行初始化。clock_id有多种选择,其中
CLOCK_REALTIME:可以理解为wall time,即是实际的时间。用户可以使用命令(date)或是系统调用去修改。如果使用了NTP, 也会被NTP修改。当系统休眠(suspend)时,仍然会运行的(系统恢复时,kernel去作补偿)。
CLOCK_MONOTONIC:是单调时间,即从某个时间点开始到现在过去的时间。用户不能修改这个时间,但是当系统进入休眠(suspend)时,CLOCK_MONOTONIC是不会增加的。(更改系统时间对它没有影响)
CLOCK_BOOTTIME:系统的运行时间。类似于CLOCK_MONOTONIC,区别是包含睡眠时间。当suspend时,会依然增加。
其中hrtimer_mod的可选项为

// include\linux\hrtimer.h

enum hrtimer_mode {
	HRTIMER_MODE_ABS = 0x0,		/* Time value is absolute */
	HRTIMER_MODE_REL = 0x1,		/* Time value is relative to now */
	HRTIMER_MODE_PINNED = 0x02,	/* Timer is bound to CPU */
	HRTIMER_MODE_ABS_PINNED = 0x02,
	HRTIMER_MODE_REL_PINNED = 0x03,
};

这里一般选择HRTIMER_MODE_ABS 或者HRTIMER_MODE_REL。

2、设置定时器到期回调函数

这里实现第一步初始化的定时器的function函数。即struct hrtimer中的function元素。

// include\linux\hrtimer.h

/**
 * struct hrtimer - the basic hrtimer structure
 * @node:	timerqueue node, which also manages node.expires,
 *		the absolute expiry time in the hrtimers internal
 *		representation. The time is related to the clock on
 *		which the timer is based. Is setup by adding
 *		slack to the _softexpires value. For non range timers
 *		identical to _softexpires.
 * @_softexpires: the absolute earliest expiry time of the hrtimer.
 *		The time which was given as expiry time when the timer
 *		was armed.
 * @function:	timer expiry callback function
 * @base:	pointer to the timer base (per cpu and per clock)
 * @state:	state information (See bit values above)
 * @is_rel:	Set if the timer was armed relative
 * @start_pid:  timer statistics field to store the pid of the task which
 *		started the timer
 * @start_site:	timer statistics field to store the site where the timer
 *		was started
 * @start_comm: timer statistics field to store the name of the process which
 *		started the timer
 *
 * The hrtimer structure must be initialized by hrtimer_init()
 */
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;
#ifdef CONFIG_TIMER_STATS
	int				start_pid;
	void				*start_site;
	char				start_comm[16];
#endif
};

这里需要注意该函数的返回值enum hrtimer_restart,这里有两个返回值可选,

// include\linux\hrtimer.h

/*
 * Return values for the callback function
 */
enum hrtimer_restart {
	HRTIMER_NORESTART,	/* Timer is not restarted */
	HRTIMER_RESTART,	/* Timer must be restarted */
};

HRTIMER_NORESTART:表示不会重启该定时器。
HRTIMER_RESTART:表示会重启该定时器。
根据该函数返回值的不同,定时器会有2个不同的重启用法。
1)当返回HRTIMER_NORESTART时,我们需要在合适的地方重新调用hrtimer_start去重新开始定时器。一般用于定时处理的工作比较复杂的情况,处理工作不在回调函数中执行的情况。
2)当返回HRTIMER_RESTART时,我们需要在返回之前重新设置超时时间,调用hrtimer_forward()或者hrtimer_forward_now(),把hrtimer的_softexpires和timerqueue_node.expires往后退一个interval的时间,然后函数返回HRTIMER_RESTART。

// kernel\time\hrtimer.c

/**
 * hrtimer_forward - forward the timer expiry
 * @timer:	hrtimer to forward
 * @now:	forward past this time
 * @interval:	the interval to forward
 *
 * Forward the timer expiry so it will expire in the future.
 * Returns the number of overruns.
 *
 * Can be safely called from the callback function of @timer. If
 * called from other contexts @timer must neither be enqueued nor
 * running the callback and the caller needs to take care of
 * serialization.
 *
 * Note: This only updates the timer expiry value and does not requeue
 * the timer.
 */
u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
{
	u64 orun = 1;
	ktime_t delta;

	delta = ktime_sub(now, hrtimer_get_expires(timer));

	if (delta.tv64 < 0)
		return 0;

	if (WARN_ON(timer->state & HRTIMER_STATE_ENQUEUED))
		return 0;

	if (interval.tv64 < hrtimer_resolution)
		interval.tv64 = hrtimer_resolution;

	if (unlikely(delta.tv64 >= interval.tv64)) {
		s64 incr = ktime_to_ns(interval);

		orun = ktime_divns(delta, incr);
		hrtimer_add_expires_ns(timer, incr * orun);
		if (hrtimer_get_expires_tv64(timer) > now.tv64)
			return orun;
		/*
		 * This (and the ktime_add() below) is the
		 * correction for exact:
		 */
		orun++;
	}
	hrtimer_add_expires(timer, interval);

	return orun;
}
EXPORT_SYMBOL_GPL(hrtimer_forward);

/**
 * hrtimer_forward_now - forward the timer expiry so it expires after now
 * @timer:	hrtimer to forward
 * @interval:	the interval to forward
 *
 * Forward the timer expiry so it will expire after the current time
 * of the hrtimer clock base. Returns the number of overruns.
 *
 * Can be safely called from the callback function of @timer. If
 * called from other contexts @timer must neither be enqueued nor
 * running the callback and the caller needs to take care of
 * serialization.
 *
 * Note: This only updates the timer expiry value and does not requeue
 * the timer.
 */
static inline u64 hrtimer_forward_now(struct hrtimer *timer,
				      ktime_t interval)
{
	return hrtimer_forward(timer, timer->base->get_time(), interval);
}

3、开始定时器

// kernel\time\hrtimer.c

**
 * hrtimer_start_range_ns - (re)start an hrtimer on the current CPU
 * @timer:	the timer to be added
 * @tim:	expiry time
 * @delta_ns:	"slack" range for the timer
 * @mode:	expiry mode: absolute (HRTIMER_MODE_ABS) or
 *		relative (HRTIMER_MODE_REL)
 */
void hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
			    u64 delta_ns, const enum hrtimer_mode mode)
{
	struct hrtimer_clock_base *base, *new_base;
	unsigned long flags;
	int leftmost;

	base = lock_hrtimer_base(timer, &flags);

	/* Remove an active timer from the queue: */
	remove_hrtimer(timer, base, true);

	if (mode & HRTIMER_MODE_REL)
		tim = ktime_add_safe(tim, base->get_time());

	tim = hrtimer_update_lowres(timer, tim, mode);

	hrtimer_set_expires_range_ns(timer, tim, delta_ns);

	/* Switch the timer base, if necessary: */
	new_base = switch_hrtimer_base(timer, base, mode & HRTIMER_MODE_PINNED);

	timer_stats_hrtimer_set_start_info(timer);

#ifdef CONFIG_HISI_CPU_ISOLATION
	/* Update pinned state */
	timer->state &= ~HRTIMER_STATE_PINNED;
	if (mode & HRTIMER_MODE_PINNED)
		timer->state |= HRTIMER_STATE_PINNED;
#endif

	leftmost = enqueue_hrtimer(timer, new_base);
	if (!leftmost)
		goto unlock;

	if (!hrtimer_is_hres_active(timer)) {
		/*
		 * Kick to reschedule the next tick to handle the new timer
		 * on dynticks target.
		 */
		if (new_base->cpu_base->nohz_active)
			wake_up_nohz_cpu(new_base->cpu_base->cpu);
	} else {
		hrtimer_reprogram(timer, new_base);
	}
unlock:
	unlock_hrtimer_base(timer, &flags);
}
EXPORT_SYMBOL_GPL(hrtimer_start_range_ns);
// include\linux\hrtimer.h

/* Basic timer operations: */
extern void hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
				   u64 range_ns, const enum hrtimer_mode mode);

/**
 * hrtimer_start - (re)start an hrtimer on the current CPU
 * @timer:	the timer to be added
 * @tim:	expiry time
 * @mode:	expiry mode: absolute (HRTIMER_MODE_ABS) or
 *		relative (HRTIMER_MODE_REL)
 */
static inline void hrtimer_start(struct hrtimer *timer, ktime_t tim,
				 const enum hrtimer_mode mode)
{
	hrtimer_start_range_ns(timer, tim, 0, mode);
}

static inline void hrtimer_start_expires(struct hrtimer *timer,
					 enum hrtimer_mode mode)
{
	u64 delta;
	ktime_t soft, hard;
	soft = hrtimer_get_softexpires(timer);
	hard = hrtimer_get_expires(timer);
	delta = ktime_to_ns(ktime_sub(hard, soft));
	hrtimer_start_range_ns(timer, soft, delta, mode);
}

static inline void hrtimer_restart(struct hrtimer *timer)
{
	hrtimer_start_expires(timer, HRTIMER_MODE_ABS);
}

其中hrtimer_start_range_ns是最基础的方法。hrtimer_start是将range_ns默认设置为0。这两个函数可以在定时器第一次启动时使用。
hrtimer_restart是将超时时间设置为上次设置的值。可以在定时器第一次启动之后使用。在使用的时候需要注意,该函数只会简单的重启定时器,使用的是timer->_softexpires和timer->node.expires,在使用之前需要修改这两个值,可以结合hrtimer_forward_now或者hrtimer_forward进行使用。
其中hrtimer_start_expires在使用的时候需要注意,由

	soft = hrtimer_get_softexpires(timer);
	hard = hrtimer_get_expires(timer);
	delta = ktime_to_ns(ktime_sub(hard, soft));
	hrtimer_start_range_ns(timer, soft, delta, mode);

可知,它是将绝对时间作为参数传入hrtimer_start_range_ns中的,所以mode应该为HRTIMER_MODE_ABS(自己的理解不知道对不对)。也是可以在定时器第一次启动之后使用。

4、取消定时器

可以使用一下两个函数。

// kernel\time\hrtimer.c

/**
 * hrtimer_try_to_cancel - try to deactivate a timer
 * @timer:	hrtimer to stop
 *
 * Returns:
 *  0 when the timer was not active
 *  1 when the timer was active
 * -1 when the timer is currently excuting the callback function and
 *    cannot be stopped
 */
int hrtimer_try_to_cancel(struct hrtimer *timer)
{
	struct hrtimer_clock_base *base;
	unsigned long flags;
	int ret = -1;

	/*
	 * Check lockless first. If the timer is not active (neither
	 * enqueued nor running the callback, nothing to do here.  The
	 * base lock does not serialize against a concurrent enqueue,
	 * so we can avoid taking it.
	 */
	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;

}
EXPORT_SYMBOL_GPL(hrtimer_try_to_cancel);

/**
 * hrtimer_cancel - cancel a timer and wait for the handler to finish.
 * @timer:	the timer to be cancelled
 *
 * Returns:
 *  0 when the timer was not active
 *  1 when the timer was active
 */
int hrtimer_cancel(struct hrtimer *timer)
{
	for (;;) {
		int ret = hrtimer_try_to_cancel(timer);

		if (ret >= 0)
			return ret;
		cpu_relax();
	}
}
EXPORT_SYMBOL_GPL(hrtimer_cancel);

如果定时器回调函数正在被执行,则hrtimer_try_to_cancel不会等待函数执行完再取消定时器,而会立刻返回-1,而hrtimer_cancel则会等待回调函数执行完取消定时器。

5、时间的转换

有上述可知,大部分传入的时间参数的都是ktime类型的,而ktime不方便我们是用,我们使用最多的是ns,us,ms等,所以内核有很多转换函数。
ns和ms转换为ktime:

// include\linux\ktime.h

static inline ktime_t ns_to_ktime(u64 ns)
static inline ktime_t ms_to_ktime(u64 ms)

ktime转换为ms、us、ns

// include\linux\ktime.h

static inline s64 ktime_to_us(const ktime_t kt)
static inline s64 ktime_to_ms(const ktime_t kt)
#define ktime_to_ns(kt)			((kt).tv64)

ktime还可以直接加减ns、us、ms

// include\linux\ktime.h

/*
 * Add a ktime_t variable and a scalar nanosecond value.
 * res = kt + nsval:
 */
#define ktime_add_ns(kt, nsval) \
		({ (ktime_t){ .tv64 = (kt).tv64 + (nsval) }; })
/*
 * Subtract a scalar nanosecod from a ktime_t variable
 * res = kt - nsval:
 */
#define ktime_sub_ns(kt, nsval) \
		({ (ktime_t){ .tv64 = (kt).tv64 - (nsval) }; })
		
static inline ktime_t ktime_add_us(const ktime_t kt, const u64 usec)
static inline ktime_t ktime_sub_us(const ktime_t kt, const u64 usec)
static inline ktime_t ktime_add_ms(const ktime_t kt, const u64 msec)
static inline ktime_t ktime_sub_ms(const ktime_t kt, const u64 msec)
Logo

更多推荐