深圳市住房建设与保障局官方网站,大连网站建设酷网科技,wordpress调用目录,能进封禁网站的手机浏览器1、前言 上一篇我们谈到了timer#xff0c;在详细分析hrtimer的实现之前#xff0c;我们先追根溯源来谈一下clockevent。先抛开clockevent这个概念#xff0c;如果硬件要支持定时功能#xff0c;那么硬件必然要能够支持产生定时时间#xff0c;通过异步中断的方式通知CPU在详细分析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_accessorsclock 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_eventset_next_eventset_next_ktime相关成员和设置下一次触发时间相关
除了这些与中断相关的成员还有
cpumask指定了这个定时事件设备所服务的CPU号系统中高精度定时事件设备一般都是每个CPU核私有的。
list系统中所有的定时事件设备实例都会保存在全局链表clockevent_devices中
mult、shiftrating与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和shiftclockevents_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 deviceexpires参数是设定下一次产生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参数就是这样的格式转换成纳秒这样的时间单位。
3delta小于0意味着用户设定的时间点已经是过去的一个时间点如果强制产生event的话那么事不宜迟要立刻产生event这需要调用clockevents_program_min_delta函数代码如下
6、总结
了解了上面的内容就可以知道内核中timer相关的底层机制了关于tick和timer的内容见后面的文章本篇主要内容参考Linux时间子系统之十六clockevent