Linux时间子系统8:clock_event_device

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

  • 13
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bluetangos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值