Linux内核高精度定时器(hrtimer)
内核为高精度定时器重新设计了一套软件架构,它可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动。该套架构相关文件为kernel\time\hrtimer.c和include\linux\hrtimer.h。...
内核为高精度定时器重新设计了一套软件架构,它可以为我们提供纳秒级的定时精度,以满足对精确时间有迫切需求的应用程序或内核驱动。该套架构相关文件为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)
更多推荐
所有评论(0)