-
struct clocksource {
-
/*
-
* Hotpath data, fits in a single cache line when the
-
* clocksource itself is cacheline aligned.
-
*/
-
cycle_t (*read)(struct clocksource *cs);
-
cycle_t cycle_last;
-
cycle_t mask;
-
u32 mult;
-
u32 shift;
-
u64 max_idle_ns;
-
u32 maxadj;
-
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
-
struct arch_clocksource_data archdata;
-
#endif
-
-
const char *name;
-
struct list_head list;
-
int rating;
-
int (*enable)(struct clocksource *cs);
-
void (*disable)(struct clocksource *cs);
-
unsigned long flags;
-
void (*suspend)(struct clocksource *cs);
-
void (*resume)(struct clocksource *cs);
-
-
/* private: */
-
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
-
/* Watchdog related data, used by the framework */
-
struct list_head wd_list;
-
cycle_t cs_last;
-
cycle_t wd_last;
-
#endif
- } ____cacheline_aligned;
- 1--99: 不适合于用作实际的时钟源,只用于启动过程或用于测试;
- 100--199:基本可用,可用作真实的时钟源,但不推荐;
- 200--299:精度较好,可用作真实的时钟源;
- 300--399:很好,精确的时钟源;
- 400--499:理想的时钟源,如有可能就必须选择它作为时钟源;
-
include/linux/acpi_pmtmr.h
-
------------------------------------------
-
#define PMTMR_TICKS_PER_SEC 3579545
-
-
drivers/clocksource/acpi_pm.c
-
---------------------------------------------
-
static struct clocksource clocksource_acpi_pm = {
-
.name = "acpi_pm",
-
.rating = 200,
-
.read = acpi_pm_read,
-
.mask = (cycle_t)ACPI_PM_MASK,
-
.mult = 0, /*to be calculated*/
-
.shift = 22,
-
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
-
-
};
-
-
dmesg output
-
------------------------
-
[ 0.664201] hpet0: 8 comparators, 64-bit 14.318180 MHz counter
-
-
arch/86/kernel/hpet.c
-
--------------------------------
-
static struct clocksource clocksource_hpet = {
-
.name = "hpet",
-
.rating = 250,
-
.read = read_hpet,
-
.mask = HPET_MASK,
-
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
-
.resume = hpet_resume_counter,
-
#ifdef CONFIG_X86_64
-
.archdata = { .vclock_mode = VCLOCK_HPET },
-
#endif
-
};
-
-
-
dmesg output:
-
-----------------------------
-
[ 0.004000] Detected 2127.727 MHz processor.
-
-
-
arch/x86/kernel/tsc.c
-
--------------------------------------
-
static struct clocksource clocksource_tsc = {
-
.name = "tsc",
-
.rating = 300,
-
.read = read_tsc,
-
.resume = resume_tsc,
-
.mask = CLOCKSOURCE_MASK(64),
-
.flags = CLOCK_SOURCE_IS_CONTINUOUS |
-
CLOCK_SOURCE_MUST_VERIFY,
-
#ifdef CONFIG_X86_64
-
.archdata = { .vclock_mode = VCLOCK_TSC },
-
#endif
- };
-
root@manu:~# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
-
tsc hpet acpi_pm
-
root@manu:~# cat /sys/devices/system/clocksource/clocksource0/current_clocksource
- tsc
我们想一下,假如我们需要给你个以一定频率输出中断的硬件,你如何计时?比如我有一个频率是1000Hz的硬件,当前时钟源计数是3500,过了一段时间,我抬头看了下时钟源计数至是5500,过去了2000cycles,我就知道了过去了2000/1000 =2 second。
- times_elapse = cycles_interval / frequency
-
static struct clocksource clocksource_tsc = {
-
.name = "tsc",
-
.rating = 300,
-
.read = read_tsc,
-
.resume = resume_tsc,
-
.mask = CLOCKSOURCE_MASK(64),
-
.flags = CLOCK_SOURCE_IS_CONTINUOUS |
-
CLOCK_SOURCE_MUST_VERIFY,
-
#ifdef CONFIG_X86_64
-
.archdata = { .vclock_mode = VCLOCK_TSC },
-
#endif
-
};
-
-
/*--------- arch/x86/kernel/tsc.c -------------------*/
-
static cycle_t read_tsc(struct clocksource *cs)
-
{
-
cycle_t ret = (cycle_t)get_cycles();
-
-
return ret >= clocksource_tsc.cycle_last ?
-
ret : clocksource_tsc.cycle_last;
-
}
-
-
-
/*------- arch/x86/include/asm/tsc.h----------------------*/
-
static inline cycles_t get_cycles(void)
-
{
-
unsigned long long ret = 0;
-
-
#ifndef CONFIG_X86_TSC
-
if (!cpu_has_tsc)
-
return 0;
-
#endif
-
rdtscll(ret);
-
-
return ret;
-
}
-
-
/*------arch/x86/include/asm/msr.h-----------------*/
-
#define rdtscll(val) \
-
((val) = __native_read_tsc())
-
-
-
static __always_inline unsigned long long __native_read_tsc(void)
-
{
-
DECLARE_ARGS(val, low, high);
-
-
asm volatile("rdtsc" : EAX_EDX_RET(val, low, high));
-
-
return EAX_EDX_VAL(val, low, high);
- }
扯了半天read这个成员变量,可以回到shift和mult了。其实shift和mult是为了解决下面这个公式的:
- times_elapse = cycles_interval / frequency
- times_elapse = cycles_interval * mult >> shift
-
/**
-
* clocksource_cyc2ns - converts clocksource cycles to nanoseconds
-
* @cycles: cycles
-
* @mult: cycle to nanosecond multiplier
-
* @shift: cycle to nanosecond divisor (power of two)
-
*
-
* Converts cycles to nanoseconds, using the given mult and shift.
-
*
-
* XXX - This could use some mult_lxl_ll() asm optimization
-
*/
-
static inline s64 clocksource_cyc2ns(cycle_t cycles, u32 mult, u32 shift)
-
{
-
return ((u64) cycles * mult) >> shift;
- }
-
void __clocksource_updatefreq_scale(struct clocksource *cs, u32 scale, u32 freq)
-
{
-
u64 sec;
-
/*
-
* Calc the maximum number of seconds which we can run before
-
* wrapping around. For clocksources which have a mask > 32bit
-
* we need to limit the max sleep time to have a good
-
* conversion precision. 10 minutes is still a reasonable
-
* amount. That results in a shift value of 24 for a
-
* clocksource with mask >= 40bit and f >= 4GHz. That maps to
-
* ~ 0.06ppm granularity for NTP. We apply the same 12.5%
-
* margin as we do in clocksource_max_deferment()
-
*/
-
sec = (cs->mask - (cs->mask >> 3));
-
do_div(sec, freq);
-
do_div(sec, scale);
-
if (!sec)
-
sec = 1;
-
else if (sec > 600 && cs->mask > UINT_MAX)
-
sec = 600;
-
-
clocks_calc_mult_shift(&cs->mult, &cs->shift, freq,
-
NSEC_PER_SEC / scale, sec * scale);
-
-
/*
-
* for clocksources that have large mults, to avoid overflow.
-
* Since mult may be adjusted by ntp, add an safety extra margin
-
*
-
*/
-
cs->maxadj = clocksource_max_adjustment(cs);
-
while ((cs->mult + cs->maxadj < cs->mult)
-
|| (cs->mult - cs->maxadj > cs->mult)) {
-
cs->mult >>= 1;
-
cs->shift--;
-
cs->maxadj = clocksource_max_adjustment(cs);
-
}
-
-
cs->max_idle_ns = clocksource_max_deferment(cs);
- }
筒子比较关心的问题是如何计算 ,精度如何,其实我不太喜欢这种计算,Kernel总是因为某些原因把代码写的很蛋疼.反正揣摩代码意图要花不少时间,收益嘛其实也不太大.如何实现我也不解释了,我以TSC为例子我评估下这种mult+shift的精度.
-
#include<stdio.h>
-
#include<stdlib.h>
-
-
typedef unsigned int u32;
-
typedef unsigned long long u64;
-
-
#define NSEC_PER_SEC 1000000000L
-
-
void
-
clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec)
-
{
-
u64 tmp;
-
u32 sft, sftacc= 32;
-
-
/*
-
* * Calculate the shift factor which is limiting the conversion
-
* * range:
-
* */
-
tmp = ((u64)maxsec * from) >> 32;
-
while (tmp) {
-
tmp >>=1;
-
sftacc--;
-
}
-
-
/*
-
* * Find the conversion shift/mult pair which has the best
-
* * accuracy and fits the maxsec conversion range:
-
* */
-
for (sft = 32; sft > 0; sft--) {
-
tmp = (u64) to << sft;
-
tmp += from / 2;
-
//do_div(tmp, from);
-
tmp = tmp/from;
-
if ((tmp >> sftacc) == 0)
-
break;
-
}
-
*mult = tmp;
-
*shift = sft;
-
}
-
-
-
int main()
-
{
-
-
u32 tsc_mult;
-
u32 tsc_shift ;
-
-
-
u32 tsc_frequency = 2127727000/1000; //TSC frequency(KHz)
-
clocks_calc_mult_shift(&tsc_mult,&tsc_shift,tsc_frequency,NSEC_PER_SEC/1000,600*1000); //NSEC_PER_SEC/1000是因为TSC的注册是clocksource_register_khz
-
-
fprintf(stderr,"mult = %d shift = %d\n",tsc_mult,tsc_shift);
-
return 0;
- }
-
mult = 7885042 shift = 24
root@manu:~/code/c/self/time# python
Python 2.7.3 (default, Apr 10 2013, 05:46:21)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> (2127727000*7885042)>>24 -
1000000045L
>>>
接下来是注册和各大clocksource PK.
各大clocksource会调用clocksource_register_khz或者clocksource_register_hz来注册.
-
HPET (arch/x86/kernel/hpet)
-
----------------------------------------
-
hpet_enable
-
|_____hpet_clocksource_register
-
|_____clocksource_register_hz
-
-
TSC (arch/x86/kernel/tsc.c)
-
----------------------------------------
-
device_initcall(init_tsc_clocksource);
-
-
init_tsc_clocksource
-
|_____clocksource_register_khz
-
-
-
ACPI_PM(drivers/cloclsource/acpi_pm.c)
-
-------------------------------------------
-
fs_initcall(init_acpi_pm_clocksource);
-
-
init_acpi_pm_clocksource
- |_____clocksource_register_hz
-
int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
-
{
-
-
/* Initialize mult/shift and max_idle_ns */
-
__clocksource_updatefreq_scale(cs, scale, freq);
-
-
/* Add clocksource to the clcoksource list */
-
mutex_lock(&clocksource_mutex);
-
clocksource_enqueue(cs);
-
clocksource_enqueue_watchdog(cs);
-
clocksource_select();
-
mutex_unlock(&clocksource_mutex);
-
return 0;
- }
clocksource_enqueue是将clocksource链入全局链表.根据的是rating,rating高的放前面.
clocksource_select会选择最好的clocksource记录在全局变量curr_clocksource,同时会通知timekeeping,切换最好的clocksource会有内核log:
-
manu@manu:~$ dmesg|grep Switching
-
[ 0.673002] Switching to clocksource hpet
- [ 1.720643] Switching to clocksource tsc
-
#define WATCHDOG_INTERVAL (HZ >> 1)
- #define WATCHDOG_THRESHOLD (NSEC_PER_SEC >> 4)
总算可以睡觉了.亲下我家小宝宝 去睡觉.
参考文献:
1 Linux时间子系统之一:clock source(时钟源)
2 Linux 3.4.61 source code.