Linux时间子系统
1、相关数据结构
Linux为了管理硬件时钟,主要分为两类clocksource和clockevents。
(1)clocksource
内核使用structclocksource数据结构记录时钟源所有信息,主要作为系统时间的基准,当有多个时钟源时选择最优那个,没有时钟源时默认使用基于jiffies的时钟clocksource_jiffies。内核通过一个链表clocksource_list管理所有注册的时钟源,每个时钟源定义了一个单调增加的计数器并以ns为单位。
struct clocksource结构体详细如下(include/linux/clocksource.h):
structclocksource {
cycle_t (*read)(struct clocksource *cs); //读取指定CS的cycle值(定时器当前计数值)
cycle_t cycle_last; //保存最近一次read的cycle值(其中一个重要作用翻转)
cycle_t mask; //counter是32位还是64位
u32 mult; //cycle转化为ns的乘数
u32 shift; //cycle转化为ns的除数,采用移位的方式
u64 max_idle_ns; //该时钟允许的最大空闲时间(没搞明白如何用)
u32 maxadj; //最大调整值与mult相关
#ifdefCONFIG_ARCH_CLOCKSOURCE_DATA //未用
struct arch_clocksource_data archdata;
#endif
const char *name; //时钟源名字
struct list_head list; //注册时钟源链表头
int rating; //时钟源精度值,
1--99: 不适合于用作实际的时钟源,只用于启动过程或用于测试;
100--199:基本可用,可用作真实的时钟源,但不推荐;
200--299:精度较好,可用作真实的时钟源;
300--399:很好,精确的时钟源;
400--499:理想的时钟源,如有可能就必须选择它作为时钟源;
int (*enable)(struct clocksource *cs); //使能时钟源
void (*disable)(struct clocksource *cs); //禁止时钟源
unsigned long flags; //时钟源属性,CLOCK_SOURCE_IS_CONTINUOUS连续时钟
void (*suspend)(struct clocksource *cs); //挂起时钟源
void (*resume)(struct clocksource *cs); //恢复时钟源
#ifdefCONFIG_CLOCKSOURCE_WATCHDOG //未用
/* Watchdog related data, used by theframework */
struct list_head wd_list;
cycle_t cs_last;
cycle_t wd_last;
#endif
}____cacheline_aligned;
(2)clockevents
内核使用structclock_event_device数据结构记录时钟的事件信息,包括硬件时钟中断发生时要执行的那些操作。提供了对周期性事件和单触发事件的支持。还提供了高精度定时器和动态定时器的支持。内核通过一个clockevent_devices管理所有注册的clock event设备。
本文主要讲系统时间,该结构体在头文件include/linux/clockchips.h中定义,不展开讲解。
2、内核相关初始化函数
在内核启动函数start_kernel里对时间系统进行了初始化。
(1)tick_init
tick为时钟事件设备,该函数初始化tick控制,基于clockevent框架注册一个tick分发器tick_notifier(分发回调函数:tick_notify),并加入队列clockevents_chain。
(2)init_timers
通用定时器初始化,注册timers_nb,加入到cpu_chain队列;开启定时器软中断open_softirq(TIMER_SOFTIRQ,run_timer_softirq);
(3)hrtimers_init
高精度定时器初始化,注册hrtimers_nb,加入到cpu_chain队列;如果开启高精度定时器宏,则开启高精度定时器软中断open_softirq(HRTIMER_SOFTIRQ, run_hrtimer_softirq);
(4)timekeeping_init
初始化时钟源及时间初始值,如果平台没有更好的时钟源,系统使用jiffies作为时钟源。
clock = clocksource_default_clock();
struct clocksource * __init __weakclocksource_default_clock(void)
{
return &clocksource_jiffies;
}
struct clocksource clocksource_jiffies = {
.name = "jiffies",
.rating = 1, /* lowest validrating*/
.read = jiffies_read,
.mask = 0xffffffff,/*32bits*/
.mult = NSEC_PER_JIFFY<< JIFFIES_SHIFT, /* details above */
.shift = JIFFIES_SHIFT,
};
(5)time_init
前面函数时内核通用架构,该函数为硬件时钟初始化平台相关,一般由各个平台自己实现,细节见下节。
void __inittime_init(void)
{
system_timer = machine_desc->timer;
system_timer->init();
}
3、硬件时钟(GM为例)
(1)平台注册
MACHINE_START(GM, BOARD_NAME)
.atag_offset = BOOT_PARAMETER_PA_OFFSET, //boot command line, after kernel 3.2 changeas relative address
.map_io = board_map_io,
.init_irq = board_init_irq,
.timer = &board_sys_timer,
.fixup = board_fixup_memory,
.init_machine = board_init_machine,
.restart = arch_reset,
MACHINE_END
上一节中的machine_desc就是该宏定义的具体值。
structsys_timer board_sys_timer = {
.init = board_sys_timer_init, //硬件定时器初始化函数
};
(2)定时器初始化
staticstruct fttmr010_clockevent fttmr010_0_clockevent= {
.clockevent = {
.name = "fttmr010:0",
.irq = TIMER_FTTMR010_IRQ0,
},
.base = (void __iomem *)TIMER_FTTMR010_VA_BASE,
.id = 0,
};
staticstruct fttmr010_clocksource fttmr010_1_clocksource= {
.clocksource = {
.name = "fttmr010:1",
},
.base = (void __iomem *)TIMER_FTTMR010_VA_BASE,
.id = 1,
};
staticvoid __init board_sys_timer_init(void)
{
unsigned int pclk = pmu_get_apb0_clk();
fttmr010_0_clockevent.freq = pclk;
fttmr010_clockevent_init(&fttmr010_0_clockevent);
fttmr010_1_clocksource.freq = pclk;
fttmr010_clocksource_init(&fttmr010_1_clocksource);
}
该函数主要完成clockevent和clocksource的定时器初始化,从上面可以看出timer0用于clockevent且要注册中断,timer1用于clocksource,使用APB0 clk为定时器时钟源。
(3)clockevent定时器初始化
fttmr010_clockevent_init该函数主要实现:timer0寄存器配置,中断注册,clockevent设备数据结构初始化,注册clockevent设备。
(4)clocksource定时器初始化
初始化函数源码如下:
void__init fttmr010_clocksource_init(struct fttmr010_clocksource *fttmr010)
{
struct clocksource *cs =&fttmr010->clocksource;
…
cs->rating = 300; //时钟源精度值
cs->read = fttmr010_clocksource_read;//获取计数值,系统主要调用该接口转化为系统时间
cs->mask = CLOCKSOURCE_MASK(32); //计数值32位
cs->shift = 20; //除法移位,此处写死,实际可以根据freq自动计算出来
cs->flags = CLOCK_SOURCE_IS_CONTINUOUS;
cs->mult = clocksource_hz2mult(fttmr010->freq,cs->shift); //根据shift计算出对应的mult
/* 初始化定时器1,该定时器单调递增,不产生中断 */
fttmr010_disable(fttmr010->base,fttmr010->id);
fttmr010_set_match1(fttmr010->base,fttmr010->id, 0);
fttmr010_set_match2(fttmr010->base,fttmr010->id, 0);
fttmr010_set_reload(fttmr010->base,fttmr010->id, 0xffffffff);
fttmr010_set_counter(fttmr010->base,fttmr010->id, 0xffffffff);
fttmr010_enable_noirq(fttmr010->base,fttmr010->id);
clocksource_register(cs); //注册clocksource设备
}
以下函数主要返回定时器的计数值
staticcycle_t fttmr010_clocksource_read(struct clocksource *cs)
{
struct fttmr010_clocksource *fttmr010;
cycle_t counter;
fttmr010 = container_of(cs, structfttmr010_clocksource, clocksource);
counter =fttmr010_get_counter(fttmr010->base, fttmr010->id);
return ~counter;
}
4、系统时间
1、系统时间转化流程
结合上面的硬件定时器,其基本流程图如下:
2、整个调用流程
(1)timekeeping_get_ns具体实现
staticinline s64 timekeeping_get_ns(void)
{
cycle_t cycle_now, cycle_delta;
struct clocksource *clock;
/* read clocksource: */
clock = timekeeper.clock;
cycle_now = clock->read(clock);//此处的read就为上面函数fttmr010_clocksource_read
/* calculate the delta since the lastupdate_wall_time: */
cycle_delta = (cycle_now - clock->cycle_last)& clock->mask;
/* return delta convert to nanosecondsusing ntp adjusted mult. 通过这条注释也说明通过ntp可以调整mult */
return clocksource_cyc2ns(cycle_delta,timekeeper.mult, timekeeper.shift); //将定时器timer1的cycle转化为对应的ns数
}
static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32shift)
{
return ((u64) cycles *mult) >> shift;
}
这里的mult和shift就是上面根据timer1的freq计算出来的。
T=cycles/F:F为timer1的时钟频率,但内核一般不允许浮点运算而且影响精度,因此,内核使用了另外一个变通的办法,根据时钟的频率和期望的精度事先计算出两个辅助常数mult和shift,然后使用以下公式进行cycle和t的转换:
t = (cycle * mult) >> shift;
只要我们保证以下计算无限接近F就可以保证精度
F = (1 << shift) / mult;
5、时间修正
公式:t = (cycle * mult) >> shift,误差来源:timer定时器本身精确度cycle及mult和shift的精确度。下面以GM8286为例。
freq=41500000时,得到mult:404270265/sift:24。也就是说1S=1000000000ns需要41500000个时钟周期,利用上面公式理论计算出:
t = (cycle * mult) >> shift =( 41500000 * 404270265 ) >> 24 = 999999999.85 ns。因此,每S偏差为0.15ns,一天才偏差12960ns。实际情况:GM8286每天都偏快4~5S,mult和shift是死的,只能是cycle本身不够准确导致系统偏差较大,当然也包括其他地方引入误差。
修正方法:上面说过通过shift值得到mult值,当cycle不准时通过修正mult值来达到目的:频率:41500000,每天平均快:4.67S 即每S偏差54051ns
mult =(t<<24) / cycle,t=1000000000 – 54051, cycle= cycle,最终得到如下:
mult = (999945949 << 24 ( = 16776342192259072) ) / 41500000 = 404248414
后续实现ntp功能后就可以根据偏差自动计算出该值,一般ntp一次就可以,一个设备的偏差基本固定不变,另外需要注意的就是t<<24可能超出32位范围。