1、前言
上一篇我们谈到了timer,在详细分析hrtimer的实现之前,我们先追根溯源来谈一下clockevent。先抛开clockevent这个概念,如果硬件要支持定时功能,那么硬件必然要能够支持产生定时时间,通过异步中断的方式通知CPU,你当然不可能让CPU一直去轮询时钟,看时间是不是快到了对吧,linux对能够产生异步事件的clock进行了软件抽象clock event。那么实际上clock event和clock source是硬件时钟的一体两面,clock source提供了读取cycle,计算时间相关的功能,而clock event则提供了产生时间事件相关的功能。
2、clock_event_device数据结构
我们看一下Linux内核是如何用clock_event_device来抽象产生系统时间事件(中断)的设备的,如下:
struct clock_event_device {void (*event_handler)(struct clock_event_device *);int (*set_next_event)(unsigned long evt, struct clock_event_device *);int (*set_next_ktime)(ktime_t expires, struct clock_event_device *);ktime_t next_event;u64 max_delta_ns;u64 min_delta_ns;u32 mult;u32 shift;enum clock_event_state state_use_accessors;unsigned int features;unsigned long retries;int (*set_state_periodic)(struct clock_event_device *);int (*set_state_oneshot)(struct clock_event_device *);int (*set_state_oneshot_stopped)(struct clock_event_device *);int (*set_state_shutdown)(struct clock_event_device *);int (*tick_resume)(struct clock_event_device *);void (*broadcast)(const struct cpumask *mask);void (*suspend)(struct clock_event_device *);void (*resume)(struct clock_event_device *);unsigned long min_delta_ticks;unsigned long max_delta_ticks;const char *name;int rating;int irq;int bound_on;const struct cpumask *cpumask;struct list_head list;struct module *owner;
} ____cacheline_aligned;
clock_event_device肯定要基于中断,我们先看中断相关的成员
irq:使用的中断号
features:中断模式,硬件支持的中断能力,包括:
# define CLOCK_EVT_FEAT_PERIODIC 0x000001
# define CLOCK_EVT_FEAT_ONESHOT 0x000002
# define CLOCK_EVT_FEAT_KTIME 0x000004
# define CLOCK_EVT_FEAT_C3STOP 0x000008
# define CLOCK_EVT_FEAT_DUMMY 0x000010
# define CLOCK_EVT_FEAT_DYNIRQ 0x000020
# define CLOCK_EVT_FEAT_PERCPU 0x000040
# define CLOCK_EVT_FEAT_HRTIMER 0x000080
CLOCK_EVT_FEAT_PERIODIC:能够产生周期性event,比如每隔1s产生1个event
CLOCK_EVT_FEAT_ONESHOT :具备产生oneshot类型的event能力,即单次event
CLOCK_EVT_FEAT_KTIME :可以直接使用ktime产生event,即到达某个ktime产生event,有些硬件时钟确实有这样的能力,就比如在clocksource中说的,read函数返回的是time,而不是counter
CLOCK_EVT_FEAT_C3STOP :CPU的sleep state叫做C-states,有C1/C2等,即CPU进入某个深度睡眠状态的时候,停止了local timer的运作
CLOCK_EVT_FEAT_PERCPU :是不是percpu的clock event
CLOCK_EVT_FEAT_DYNIRQ:表示该定时事件设备可以设定CPU亲缘性,也就是可以指定到期后触发某个特定CPU的中断
CLOCK_EVT_FEAT_HRTIMER:表示该定时事件设备实际上是有高分辨率定时器模拟出来的
与feature强相关的还有state_use_accessors,clock event支持上述feature,但是具体工作在哪一个状态,用state_use_accessors描述,
enum clock_event_state {
CLOCK_EVT_STATE_DETACHED,
CLOCK_EVT_STATE_SHUTDOWN,
CLOCK_EVT_STATE_PERIODIC,
CLOCK_EVT_STATE_ONESHOT,
CLOCK_EVT_STATE_ONESHOT_STOPPED,
};
都比较好理解,就不解释了。同时,也可以看到工作状态,通过set_state_xxx这样的接口实现。
event_handler:这里的event_handler并不是中断处理函数,而是在中断处理函数中调用的callback
相应的就有next_event,set_next_event,set_next_ktime相关成员,和设置下一次触发时间相关
除了这些与中断相关的成员,还有
cpumask:指定了这个定时事件设备所服务的CPU号,系统中高精度定时事件设备一般都是每个CPU核私有的。
list:系统中所有的定时事件设备实例都会保存在全局链表clockevent_devices中
mult、shift,rating与clock source相同,其他略过。
3、clock_event_device的注册
注册函数:clockevents_config_and_register
在使用clockevents_config_and_register注册设备之前,先要 准备好clock_event_device结构体数据。
void clockevents_config_and_register(struct clock_event_device *dev,u32 freq, unsigned long min_delta,unsigned long max_delta)
{dev->min_delta_ticks = min_delta;dev->max_delta_ticks = max_delta;clockevents_config(dev, freq);clockevents_register_device(dev);
}
实际调用了clockevents_config和clockevents_register_device,完成设备配置和注册
clockevents_config主要用来设置对应的mult和shift,clockevents_register_device如下:
void clockevents_register_device(struct clock_event_device *dev)
{unsigned long flags;/* Initialize state to DETACHED */clockevent_set_state(dev, CLOCK_EVT_STATE_DETACHED);if (!dev->cpumask) {WARN_ON(num_possible_cpus() > 1);dev->cpumask = cpumask_of(smp_processor_id());}if (dev->cpumask == cpu_all_mask) {WARN(1, "%s cpumask == cpu_all_mask, using cpu_possible_mask instead\n",dev->name);dev->cpumask = cpu_possible_mask;}raw_spin_lock_irqsave(&clockevents_lock, flags);list_add(&dev->list, &clockevent_devices);tick_check_new_device(dev);clockevents_notify_released();raw_spin_unlock_irqrestore(&clockevents_lock, flags);
}
首先修改了注册设备的状态为:CLOCK_EVT_STATE_DETACHED
再次检查了设备绑定的cpumask,随后list_add将设备加入全局设备链表clockevent_devices,与时钟源注册类似clocksource_list
tick_check_new_device用来检查当前的定时设备是否可以成为新的tick设备,回想一下,之前clocksource注册的时候是不是也有类似的操作,判断新注册的时钟源是否可以替代现有时钟源,这里的逻辑也是类似的。如果新设备更适合作为tick设备(想一想,系统时间是和clocksource绑定的,而系统tick是和clock_event_device绑定的),那么就会调用clockevents_exchange_device。
clockevents_exchange_device的实现看下一节
4、更换系统clock_event_device
当有新的定时事件设备加入内核后,有可能会切换当前tick设备使用的定时事件设备,
这是在函数clockevents_exchange_device中实现的
void clockevents_exchange_device(struct clock_event_device *old,struct clock_event_device *new)
{/** Caller releases a clock event device. We queue it into the* released list and do a notify add later.*/if (old) {module_put(old->owner);clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED);list_del(&old->list);list_add(&old->list, &clockevents_released);}if (new) {BUG_ON(!clockevent_state_detached(new));clockevents_shutdown(new);}
}
clockevents_switch_state将设备状态切换为CLOCK_EVT_STATE_DETACHED
list_del将事件设备从clockevent_devices全局链表删除,加入clockevents_released链表
这个函数是在本地中断关闭并且获得自旋锁的情况下调用的。功能其实很简单,主要就是把被替换的老设备从原有的clockevent_devices全局链表中删除,并加入clockevents_released全局链表中,于此同时,把新替换的设备加入clockevent_devices全局链表中,当然还要更新设备的状态。新加入的设备的初始状态必须是CLOCK_EVT_STATE_DETACHED。
5、配置clock event device触发参数
int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,bool force)
{unsigned long long clc;int64_t delta;int rc;if (WARN_ON_ONCE(expires < 0))return -ETIME;dev->next_event = expires;if (clockevent_state_shutdown(dev))return 0;/* We must be in ONESHOT state here */WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n",clockevent_get_state(dev));/* Shortcut for clockevent devices that can deal with ktime. */if (dev->features & CLOCK_EVT_FEAT_KTIME)return dev->set_next_ktime(expires, dev);delta = ktime_to_ns(ktime_sub(expires, ktime_get()));if (delta <= 0)return force ? clockevents_program_min_delta(dev) : -ETIME;delta = min(delta, (int64_t) dev->max_delta_ns);delta = max(delta, (int64_t) dev->min_delta_ns);clc = ((unsigned long long) delta * dev->mult) >> dev->shift;rc = dev->set_next_event((unsigned long) clc, dev);return (rc && force) ? clockevents_program_min_delta(dev) : rc;
}
dev指向具体的clock event device,expires参数是设定下一次产生event的时间点,force参数控制在expires设定异常的时候(例如设定在一个过去的时间点上产生event)该函数 的行为,一种是出错返回,另外一种还是进行event的产生,只是设定一个最小的delta。
(1)如果chip driver支持使用ktime的设定(这需要硬件支持,设定了CLOCK_EVT_FEAT_KTIME flag的那些clock event device才支持哦),事情会比较简单,直接调用set_next_ktime就搞定了。
(2)对于一个“正常”的clock event device,需要转换成cycle这样的单位。不过在转换成cycle之前,需要先将ktime格式的时间(传入的expires参数就是这样的格式)转换成纳秒这样的时间单位。
(3)delta小于0意味着用户设定的时间点已经是过去的一个时间点,如果强制产生event的话,那么事不宜迟,要立刻产生event,这需要调用clockevents_program_min_delta函数,代码如下:
6、总结
了解了上面的内容就可以知道内核中timer相关的底层机制了,关于tick和timer的内容,见后面的文章,本篇主要内容参考Linux时间子系统之(十六):clockevent