怀着用户态定时器、内核态定时器、内核抽象硬件定时器之间的联系,经过网上查阅、代码翻阅,遂有此文。
注:使用内核源码linux-2.6.22分析的(低版本的内核分析起来更容易)。内核从2.6.16开始加入了高精度定时器架构。
用户态定时器(struct k_itimer)相关接口:timer_create、timer_settime、timer_delete等。
内核态的定时器分为低精度定时器(struct timer_list)、高精度定时器(struct hrtimer)、抽象硬件定时器(struct clock_event_device)。
低精度定时器的相关接口:init_timer、mod_timer、del_timer等。
高精度定时器的相关接口:hrtimer_init、hrtimer_cancel等。
抽象硬件定时器(struct clock_event_device)的相关接口:clockevents_register_device、clockevents_release_device等。
这里从硬件定时器(struct clock_event_device)由下往上进行梳理。
之前一直有个问题困扰着我(在下面进行了阐述):
既然内核用struct clock_event_device抽象出timer硬件设备,假设时钟频率为f,那么设置timer硬件自动重载寄存器(TIMx_ARR)为(1/HZ)/(1/f)=f/HZ的时机又是在哪呢?想着无外乎通过抽象出的结构体struct clock_event_device 设置timer硬件自动重载寄存器(TIMx_ARR)。
注 HZ:内核配置项CONFIG_HZ
一:clock_event_device & tick_device 初始化
struct clock_event_device {
const char *name;
unsigned int features;
unsigned long max_delta_ns;
unsigned long min_delta_ns;
unsigned long mult;
int shift;
int rating;
int irq;
cpumask_t cpumask;
int (*set_next_event)(unsigned long evt,
struct clock_event_device *);
void (*set_mode)(enum clock_event_mode mode,
struct clock_event_device *);
void (*event_handler)(struct clock_event_device *);
void (*broadcast)(cpumask_t mask);
struct list_head list;
enum clock_event_mode mode;
ktime_t next_event;
};
为了理解低精度定时器和高精度定时器,这里还要引入结构体struct tick_device。
struct tick_device {
struct clock_event_device *evtdev;
enum tick_device_mode mode;
};
可以看到是基于struct clock_event_device的封装。它们之间的关系借用linuxer前辈的话就是“低精度timer依赖周期性tick,而hrtimer不依赖周期性tick”。
参考链接:时间子系统 - 蜗窝科技
从低精度定时器(struct timer_list)定义的数据结构看不出来和struct clock_event_device的联系,关联处在struct tick_device发生中断时执行的中断处理函数。
struct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_t_base_s *base;
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
};
struct tick_device是基于struct clock_event_device的封装,所以就是struct clock_event_device的中断函数。要知道struct clock_event_device注册中断函数就要从内核启动后clock_event_device和tick_device的初始化说起了。
从start_kernel内核启动开始:
kernel/time/tick-common.c
static struct notifier_block tick_notifier = {
.notifier_call = tick_notify,
};
init/main.c
asmlinkage void __init start_kernel(void)
{
...
tick_init();
...
init_timers();
hrtimers_init();
...
time_init();
...
}
//函数调用栈
tick_init();
clockevents_register_notifier(&tick_notifier);
time_init();
system_timer->init();
clockevents_register_device(&clockevent_xxx);
clockevents_do_notify(CLOCK_EVT_NOTIFY_ADD, dev);
notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
nb->notifier_call(nb, val, v);
tick_check_new_device(dev);
tick_setup_device(td, newdev, cpu, cpumask);
tick_setup_periodic(newdev, 0);
tick_set_periodic_handler(dev, broadcast);
不同machine定义各自的time.c,不管怎么说都会殊途同归实现结构体struct sys_timer和函数指针init的初始化,在init函数中调用函数clockevents_register_device()注册struct clock_event_device,接着调用前面tick_init注册的tick_notifier.notifier_call,即tick_notify,根据CLOCK_EVT_NOTIFY_ADD调用tick_check_new_device函数进行tick device的初始化,如果已经已经初始化好的的tick device是否有更换更高精度clock event device的需求,这里只关注tick device(TICKDEV_MODE_PERIODIC)的初始化。
到此为止完成了clock_event_device & tick_device 初始化,但一开始的疑惑:timer硬件自动重载寄存器(TIMx_ARR)初始化为(1/HZ)/(1/f)=f/HZ的位置?
翻了下代码有三种初始化方式:
1.在不同machine下的sys_timer.init实现timer硬件自动重载寄存器(TIMx_ARR)初始化。
arch/arm/mach-davinci/time.c
static void __init davinci_timer_init(void)
{
static char err[] __initdata = KERN_ERR
"%s: can't register clocksource!\n";
/* init timer hw */
timer_init();
..
}
struct sys_timer davinci_timer = {
.init = davinci_timer_init,
};
函数调用栈:
time_init();
system_timer->init();
timer_init();
timer32_config(&timers[i]);
/* reset counter to zero, set new period */
davinci_writel(0, t->tim_reg);
davinci_writel(t->period, t->prd_reg);
2.在不同machine下的clock_event_device.set_next_event实现timer硬件自动重载寄存器(TIMx_ARR)初始化。。
arch/arm/mach-davinci/time.c
static int davinci_set_next_event(unsigned long cycles,
struct clock_event_device *evt)
{
struct timer_s *t = &timers[TID_CLOCKEVENT];
t->period = cycles;
timer32_config(t);
return 0;
}
static struct clock_event_device clockevent_davinci = {
.name = "timer0_0",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
.shift = 32,
.set_next_event = davinci_set_next_event,
.set_mode = davinci_set_mode,
};
函数调用栈:
tick_check_new_device(dev);
tick_setup_device(td, newdev, cpu, cpumask);
tick_setup_periodic(newdev, 0);
clockevents_program_event(dev, next, ktime_get())
dev->set_next_event((unsigned long) clc, dev);
timer32_config(&timers[i]);
/* reset counter to zero, set new period */
davinci_writel(0, t->tim_reg);
davinci_writel(t->period, t->prd_reg);
3.在不同machine下的clock_event_device.set_mode实现timer硬件自动重载寄存器(TIMx_ARR)初始化。
arch/arm/mach-davinci/time.c
static void davinci_set_mode(enum clock_event_mode mode,
struct clock_event_device *evt)
{
struct timer_s *t = &timers[TID_CLOCKEVENT];
switch (mode) {
case CLOCK_EVT_MODE_PERIODIC:
t->period = CLOCK_TICK_RATE / (HZ);
t->opts = TIMER_OPTS_PERIODIC;
timer32_config(t);
break;
case CLOCK_EVT_MODE_ONESHOT:
t->opts = TIMER_OPTS_ONESHOT;
break;
case CLOCK_EVT_MODE_UNUSED:
case CLOCK_EVT_MODE_SHUTDOWN:
t->opts = TIMER_OPTS_DISABLED;
break;
}
}
static struct clock_event_device clockevent_davinci = {
.name = "timer0_0",
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
.shift = 32,
.set_next_event = davinci_set_next_event,
.set_mode = davinci_set_mode,
};
函数调用栈:
tick_check_new_device(dev);
tick_setup_device(td, newdev, cpu, cpumask);
tick_setup_periodic(newdev, 0);
clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);
dev->set_mode(mode, dev);
timer32_config(t);
/* reset counter to zero, set new period */
davinci_writel(0, t->tim_reg);
davinci_writel(t->period, t->prd_reg);
第三种方式就是timer硬件自动重载寄存器(TIMx_ARR)初始化为(1/HZ)/(1/f)=f/HZ的位置
二:低精度定时器的梳理
书接上节,从低精度定时器(struct timer_list)定义的数据结构看不出来和struct clock_event_device的联系,联系在函数tick_set_periodic_handler()初始化的中断函数。
static inline void tick_set_periodic_handler(struct clock_event_device *dev,
int broadcast)
{
dev->event_handler = tick_handle_periodic;
}
上节我们说明了何时给timer硬件定时器自动重载寄存器(TIMx_ARR)的初始化,这里又指定了中断处理函数。接下来就是中断发生时中断处理函数的梳理:
arch/arm/mach-davinci/time.c
static irqreturn_t timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evt = &clockevent_davinci;
evt->event_handler(evt);
return IRQ_HANDLED;
}
kernel/time/tick-common.c
void tick_handle_periodic(struct clock_event_device *dev)
{
int cpu = smp_processor_id();
ktime_t next;
tick_periodic(cpu);
if (dev->mode != CLOCK_EVT_MODE_ONESHOT)
return;
/*
* Setup the next period for devices, which do not have
* periodic mode:
*/
next = ktime_add(dev->next_event, tick_period);
for (;;) {
if (!clockevents_program_event(dev, next, ktime_get()))
return;
tick_periodic(cpu);
next = ktime_add(next, tick_period);
}
}
函数调用栈
evt->event_handler(evt);
tick_periodic(cpu);
在函数tick_periodic()中进行低精度定时器的处理,包括但不限于唤醒软中断,在软中断中查看是否又到期的低精度定时器进行处理。详细的处理逻辑可以参考:
Linux时间子系统之五:低分辨率定时器的原理和实现-CSDN博客
三:高精度定时器的梳理
高精度定时器的数据结构如下,看起来也是没和struct clock_event_device有什么直接的联系。
struct hrtimer {
struct rb_node node;
ktime_t expires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
unsigned long state;
#ifdef CONFIG_HIGH_RES_TIMERS
enum hrtimer_cb_mode cb_mode;
struct list_head cb_entry;
#endif
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
};
高精度定时器的到期时间是被编程到该clock_event_device中(注意和低精度定时器的区别,低精度到期时间并不会影响clock_event_device的计数值,在低精度定时器下clock_event_device对应的计数值为f/HZ)。这样就能理解linuxer前辈的话(“低精度timer依赖周期性tick,而hrtimer不依赖周期性tick”)了,也能理解高精度模式下依赖HZ的tick_device的中断函数中只考虑低精度定时器而不需要考虑高精度定时器,因为高精度定时器的到期时间转换为计数值直接编程到clock_event_device中。
第一节的第2个给timer硬件自动重载寄存器(TIMx_ARR)初始化的方式就是高精度定时器的到期时间转换为计数值编程到clock_event_device。
有关高精度定时器详细的处理逻辑可以参考:
Linux时间子系统之六:高精度定时器(HRTIMER)的原理和实现_时间子系统之高精度 timer 实现-CSDN博客
四:用户态定时器
也称POSIX interval timer,通过下面内核的数据结构可以看到是对高精度定时器(struct hrtimer)的封装,关于它出现的原因,也有业界前辈帮我们总结了。
struct k_itimer {
struct list_head list; /* free/ allocate list */
spinlock_t it_lock;
clockid_t it_clock; /* which timer type */
timer_t it_id; /* timer id */
int it_overrun; /* overrun on pending signal */
int it_overrun_last; /* overrun on last delivered signal */
int it_requeue_pending; /* waiting to requeue this timer */
#define REQUEUE_PENDING 1
int it_sigev_notify; /* notify word of sigevent struct */
int it_sigev_signo; /* signo word of sigevent struct */
sigval_t it_sigev_value; /* value word of sigevent struct */
struct task_struct *it_process; /* process to send signal to */
struct sigqueue *sigq; /* signal queue entry. */
union {
struct {
struct hrtimer timer;
ktime_t interval;
} real;
struct cpu_timer_list cpu;
struct {
unsigned int clock;
unsigned int node;
unsigned long incr;
unsigned long expires;
} mmtimer;
} it;
};
五:小结
说下个人理解:高精度定时器&低精度定时器、高精度模式&低精度模式 的硬件定时器驱动频率都可高达MHZ,这里的低精度模式是指低精度定时器依赖CONFIG_HZ的tick 中断,而高精度模式下的高精度定时器就不再依赖CONFIG_HZ的tick 中断了,直接将高精度定时器的到期时间转换为计数值编程到clock_event_device。
至此linux 时间子系统脉络算是稍微有所理解了吧。