linux之时间子系统(五):clocksource 模块

一、前言

和洋葱一样,软件也是有层次的,内核往往需要对形形色色的某类型的驱动进行抽象,屏蔽掉其具体的特质,获取该类驱动共同的逻辑,而又根据这些逻辑撰写该类驱动的抽象层。嵌入式系统总是会提供timer的硬件block,软件需要对timer硬件提供的功能进行抽象:linux kernel将timer类型的硬件抽象成两个组件,一是free running的counter,另外一个是指定的counter值上产生中断的能力。本文主要描述第一个组件,在内核中被称作clocksource。

二、什么是clocksource?

1、基础概念

我们先回到一个基础的问题上来:什么是时间?时间其实可以抽象成一条直线,没有起点,也没有终点(所以我们叫它timeline)。我们可以以一定的刻度来划分这条直线,例如以1秒的间隔。这样还不行,还需要一个参考点,例如在耶稣诞辰的时间定位0点。OK,大家终于可以时间的定义上统一了,例如我可以和wowo同学约定在68365287秒的时间一起去吃饭,恩,看起来不是那么方便,还是可以对以秒计算的时间进行grouping,因此有了年、月、日、时、分这些概念。

对于内核,它的timeline是怎样的呢?首先时间不是一条直线,因为硬件的计数不可能是无限制的,我们总是使用有限的bit数目的硬件来计数,因此,对于内核(更准确的说对于计算机系统),其时间更像是一个不断重复的线段,最终也是形成一个没有起点也没有终点的timeline。此外,和人不同的是,机器不喜欢grouping,它们更喜欢用一个绝对的数字来标识当前的时间(当然,机器是为人服务的,最终会在人机界面的部分转换成年、月、日、时、分、秒这样的格式)。

2、内核数据结构

所谓clock source就是用来抽象一个在指定输入频率的clock下工作的一个counter。输入频率可以确定以什么样的精度来划分timeline(假设输入counter的频率是1GHz,那么一个cycle就是1ns,也就是说timeline是用1ns来划分的,最大的精度就是1ns),counter的bit数确定了组成timeline上的“线段”的长度。内核中的struct clocksource 定义如下:

struct clocksource {
        u64                     (*read)(struct clocksource *cs);
        u64                     mask;
        u32                     mult;
        u32                     shift;
        u64                     max_idle_ns;
        u32                     maxadj;
        u32                     uncertainty_margin;
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
        struct arch_clocksource_data archdata;
#endif
        u64                     max_cycles;


        const char              *name;
        struct list_head        list;
        int                     rating;
        enum vdso_clock_mode    vdso_clock_mode;
        unsigned long           flags;

        int                     (*enable)(struct clocksource *cs);
        void                    (*disable)(struct clocksource *cs);
        void                    (*suspend)(struct clocksource *cs);
        void                    (*resume)(struct clocksource *cs);
        void                    (*mark_unstable)(struct clocksource *cs);
        void                    (*tick_stable)(struct clocksource *cs);

        /* private: */


#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
        /* Watchdog related data, used by the framework */
        struct list_head        wd_list;
        u64                     cs_last;
        u64                     wd_last;
#endif
        struct module           *owner;
};

 struct clocksource的成员分成3组,我们分别介绍:

(1)这部分的代码是和计时有关的,kernel会频繁的访问这些数据结构,因此最好把它们放到一个cacheline中,而这些计时相关的成员被放到clocksource数据结构的前面就是为了提高cache hit。一旦访问了read成员,随后的几个成员也就被加载到cacheline中,从而提高的性能。对于clock source抽象的counter而言,其counter value都是针对clock计数的,具体一个clock有多少个纳秒是和输入频率相关的。

通过read获取当前的counter value,这个计数值是基于cycle的(数据类型是cycle_t,U64)。不过,对于用户和其他driver而言,cycle数据是没有意义的,最好统一使用纳秒这样的单位,因此在struct clocksource中就有了mult和shift这两个成员了。我们先看看如何将A个cycles数转换成纳秒,具体公式如下:

转换后的纳秒数目 = (A / F)    x   NSEC_PER_SEC

这样的转换公式需要除法,绝大部分的CPU都有乘法器,但是有些处理器是不支持除法,当然,对于不支持硬件除法器的CPU上,toolchain中会提供除法的代码库,虽然我们无法将除法操作的代码编译成一条除法的汇编指令,但是也可以用代码库中的其他运算来取代除法。这样做的坏处就是性能会受影响(想到这里的代码会被内核其他模块反复调用,你是不是很心疼那些CPU的MIPS?)。肿么办?要知道,linux kernel的目标是星辰大海,它是打算服务多种CPU体系结构,并且要有优秀性能表现的。把1/F变成浮点数,这样就可以去掉除法了,但是又引入了浮点运算,kernel是不建议使用浮点运算的。解决方案很简单,使用移位操作,具体可以参考clocksource_cyc2ns的操作:

static inline s64 clocksource_cyc2ns(u64 cycles, u32 mult, u32 shift)
{
        return ((u64) cycles * mult) >> shift;
}

也就是说,通过clock source的read函数获取了cycle数目,乘以mult这个因子然后右移shift个bit就可以得到纳秒数。这样的操作虽然性能比较好,但是损失了精度,还是那句话,设计是平衡的艺术,看你自己的取舍。 其他的成员我们会在后面的代码分析中给出解释。

(2)这段的代码访问没有那么频繁,主要是和一些系统操作相关。例如list成员。系统将所有已经注册clocksource挂在一个链表中,list就是挂入链表的节点。suspend和resume是和电源管理相关,enable和disable是启停该clock source的callback函数。rating是描述clock source的精度的,毫无疑问,一个输入频率是20MHz的counter精度一定是大于10ms一个cycle的counter。关于rating,内核代码的注释已经足够了,如下:

    1-99: Unfit for real use
        Only available for bootup and testing purposes.
    100-199: Base level usability.
        Functional for real use, but not desired.
    200-299: Good.
        A correct and usable clocksource.
    300-399: Desired.
        A reasonably fast and accurate clocksource.
    400-499: Perfect
        The ideal clocksource. A must-use where  available.

clock source flag描述一些该clock source的特性,我们会在后面的代码分析中给出更详细的信息。

(3)第三部分的成员和clocksource watch dog相关。大家比较熟悉是系统的watch dog,主要监视系统,如果不及时喂狗,系统就会reset。clocksource watch dog当然是用来监视clock source的运行情况,如果watch dog发现它监视的clocksource的精度有问题,会修改其rating,告知系统。后面我们会有专门一个章节来描述这部分的内容。

三、注册和注销clock source

这个接口主要是提供给底层clock source chip driver使用的,在底层的driver初始化的时候会调用该接口向系统注册clock source。注销clock source是注册的逆过程。

static inline int __clocksource_register(struct clocksource *cs)
{
        return __clocksource_register_scale(cs, 1, 0);
}

static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
{
        return __clocksource_register_scale(cs, 1, hz);
}

static inline int clocksource_register_khz(struct clocksource *cs, u32 khz)
{
        return __clocksource_register_scale(cs, 1000, khz);
}
 

 可以看到注册都进入了__clocksource_register_scale() 函数,根据不同的参数,进行注册;

①第一种注册函数__clocksource_register(struct clocksource *cs):调用者已经计算好mult和shift

内核代码中,很少地方用这个注册函数,举例:

static struct clocksource clocksource_jiffies = {
        .name                   = "jiffies",
        .rating                 = 1, /* lowest valid rating*/
        .uncertainty_margin     = 32 * NSEC_PER_MSEC,
        .read                   = jiffies_read,
        .mask                   = CLOCKSOURCE_MASK(32),
        .mult                   = TICK_NSEC << JIFFIES_SHIFT, /* details above */
        .shift                  = JIFFIES_SHIFT,
        .max_cycles             = 10,
};

static int __init init_jiffies_clocksource(void)
{
        return __clocksource_register(&clocksource_jiffies);
}

如果在驱动代码中已经根据输入频率设定好了mult和shift,那么内核的clocksource layer是不需要进行这部分的内容的计算;

②clocksource_register_hz 和 clocksource_register_khz:调用者给出输入clock频率,需要在注册过程中计算mult、shift和max_idle_ns,这个使用最多

clock source chip driver可以调用clocksource_register_hz或者clocksource_register_khz这两个接口函数来注册一个clocksource。具体的接口定义如下:

   

static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
{
        return __clocksource_register_scale(cs, 1, hz);
}

static inline int clocksource_register_khz(struct clocksource *cs, u32 khz)
{
        return __clocksource_register_scale(cs, 1000, khz);
}

hz和khz参数是传递counter的输入频率信息。这两个接口函数实际是调用了__clocksource_register_scale函数进行具体的操作,代码如下:

 

int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
{
        unsigned long flags;

        clocksource_arch_init(cs);

        if (cs->vdso_clock_mode < 0 ||
            cs->vdso_clock_mode >= VDSO_CLOCKMODE_MAX) {
                pr_warn("clocksource %s registered with invalid VDSO mode %d. Disabling VDSO support.\n",
                        cs->name, cs->vdso_clock_mode);
                cs->vdso_clock_mode = VDSO_CLOCKMODE_NONE;
        }

        /* Initialize mult/shift and max_idle_ns */

//计算mult/shift 和max_idle_ns
        __clocksource_update_freq_scale(cs, scale, freq);

        /* Add clocksource to the clocksource list */
        mutex_lock(&clocksource_mutex);

        clocksource_watchdog_lock(&flags);

//将该clock source按照rating的顺序插入到全局clock source list中
        clocksource_enqueue(cs);
        clocksource_enqueue_watchdog(cs);
        clocksource_watchdog_unlock(&flags);

//选择rating 最高的clocksource,可能注册了很多,但是只使用最优的clocksource

        clocksource_select();
        clocksource_select_watchdog(false);
        __clocksource_suspend_select(cs);
        mutex_unlock(&clocksource_mutex);
        return 0;
}
EXPORT_SYMBOL_GPL(__clocksource_register_scale);
 

最核心的代码就是__clocksource_update_freq_scale这个函数,代码如下:

void __clocksource_update_freq_scale(struct clocksource *cs, u32 scale, u32 freq)
{
        u64 sec;

        /*
         * Default clocksources are *special* and self-define their mult/shift.
         * But, you're not special, so you should specify a freq value.
         */
        if (freq) {
                /*
                 * Calc the maximum number of seconds which we can run before
                 * wrapping around. For clocksources which have a mask > 32-bit
                 * 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 >= 40-bit and f >= 4GHz. That maps to
                 * ~ 0.06ppm granularity for NTP.
                 */
                sec = cs->mask;
                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);
        }

        /*
         * If the uncertainty margin is not specified, calculate it.
         * If both scale and freq are non-zero, calculate the clock
         * period, but bound below at 2*WATCHDOG_MAX_SKEW.  However,
         * if either of scale or freq is zero, be very conservative and
         * take the tens-of-milliseconds WATCHDOG_THRESHOLD value for the
         * uncertainty margin.  Allow stupidly small uncertainty margins
         * to be specified by the caller for testing purposes, but warn
         * to discourage production use of this capability.
         */
        if (scale && freq && !cs->uncertainty_margin) {
                cs->uncertainty_margin = NSEC_PER_SEC / (scale * freq);
                if (cs->uncertainty_margin < 2 * WATCHDOG_MAX_SKEW)
                        cs->uncertainty_margin = 2 * WATCHDOG_MAX_SKEW;
        } else if (!cs->uncertainty_margin) {
                cs->uncertainty_margin = WATCHDOG_THRESHOLD;
        }
        WARN_ON_ONCE(cs->uncertainty_margin < 2 * WATCHDOG_MAX_SKEW);

        /*
         * Ensure clocksources that have large 'mult' values don't overflow
         * when adjusted.
         */
        cs->maxadj = clocksource_max_adjustment(cs);
        while (freq && ((cs->mult + cs->maxadj < cs->mult)
                || (cs->mult - cs->maxadj > cs->mult))) {
                cs->mult >>= 1;
                cs->shift--;
                cs->maxadj = clocksource_max_adjustment(cs);
        }

        /*
         * Only warn for *special* clocksources that self-define
         * their mult/shift values and don't specify a freq.
         */
        WARN_ONCE(cs->mult + cs->maxadj < cs->mult,
                "timekeeping: Clocksource %s might overflow on 11%% adjustment\n",
                cs->name);

        clocksource_update_max_deferment(cs);

        pr_info("%s: mask: 0x%llx max_cycles: 0x%llx, max_idle_ns: %lld ns\n",
                cs->name, cs->mask, cs->max_cycles, cs->max_idle_ns);
}
EXPORT_SYMBOL_GPL(__clocksource_update_freq_scale);

(1)硬件counter本身就是由有限个bit组成,因此它能表示的时间有一个最大的限制,超过之后counter就溢出了。根据counter的bit数目以及输入频率可以计算出该硬件counter能表示的最大的时间长度。cs->mask最是最大的cycle数目,除以频率就是能表示的最大的时间范围(以秒为单位)。因此,代码至此,我们知道,[0, sec秒]就是该counter能表示的以秒计算的时间范围。这里

 do_div(sec, freq); 就得到sec 的值,再do_div(sec, scale);scale 是1或者1000,是根据hz 和khz 参数来确定的。

(2)在解析这里的代码之前,我们先考虑这样一个问题:如何获取最佳的mult和shift组合?mult这个因子一定是越大越好了,越大精度越高,但是,我们的运算过程都是64 比特的,也就是说,中间的结果(cycle x mult)不能超过64bit。对于32bit的count,这个要求还好,因为cycle就是32bit的,只要mult不超过32bit就OK了(mult本来就是u32的)。因此,对于那些超过32 bit的count,我们就需要注意了,我们要人为的限制最大时间值,也就是说,虽然硬件counter可以表示出更大的时间值,但是通过mult和shift将这个大的cycle值转成ns值的时候,如果cycle太大,势必导致mult要小一些(否则溢出),而mult较小会导致精度不足,而精度不足(这时候如果要保证转换算法正确,mult需要取一个较小的数值,从而降低了精度)导致转换失去意义,因此,这里给出一个600秒的限制,这个限制也是和max idle ns相关的,具体参考上面的描述。

其实这里仍然是一个设计平衡的问题:一方面希望保持精度,因此mult要大,cycle到ns转换的时间范围就会小。另外一方面,cpuidle模块从电源管理的角度看,当然希望其在没有任务的时候,能够idle的时间越长越好,600秒是一个折衷的选择,让双方都基本满意。

(3)进入真正的mult和shift计算时刻了,代码如下:

void clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec)
{
    u64 tmp;
    u32 sft, sftacc= 32;

    tmp = ((u64)maxsec * from) >> 32;-------------------(a)
    while (tmp) {
        tmp >>=1;
        sftacc--;------------------------------(b)
    }

    for (sft = 32; sft > 0; sft--) {-----------------------(c)
        tmp = (u64) to << sft;
        tmp += from / 2;
        do_div(tmp, from);--------------------------(d)
        if ((tmp >> sftacc) == 0)
            break;
    }
    *mult = tmp;
    *shift = sft;
}

(a)在这个场景中,from是count的输入频率,maxsec是最大的表示的范围。maxsec * from就是将秒数转换成了cycle数目。对于大于32 bit的counter而言,最大的cycle数目有可能需要超过32个bit来表示,因此这里要进行右移32bit的操作。对于32 bit以下的counter,这时候的tmp右移32bit之后一定会等于0,而对于大于32 bit的counter,tmp是一个非0值。

(b)sftacc保存了左移多少位才会造成最大cycle数(对应最大的时间范围值)的溢出,对于32 bit以下的counter,统一设定为32个bit,而对于大于32 bit的counter,sftacc需要根据tmp值(这时候tmp保存了最大cycle数的高32 bit值)进
行计算。

(c)如何获取最佳的mult和shift组合?我们之前已经回答这个问题了,现在进入实现的层面。当一个公式中有两个可变量的时候,最好的办法就是固定其中一个,求出另外一个,然后带入约束条件进行检验。我们首先固定shift这个参数。mult这个因子一定是越大越好,mult越大也就是意味着shift越大。当然shift总有一个起始值,我们设定为32bit,因此sft从32开始搜索,看看是否满足最大时间范围的要求。如果满足,那么就找到最佳的mult和shift组合,否则要sft递减,进行下一轮搜索。

(d)我们先考虑如何计算mult值。根据公式cycles * mult) >> shift可以得到ns数,由此可以得到计算mult值的公式:

mult = (ns<<shift)/cycles

如果我们设定ns数是10^9纳秒(也就是1秒)的话,cycles数目就是频率值(所谓频率不就是1秒振荡的次数嘛)。因此上面的公式可以修改为:

mult = (NSEC_PER_SEC<<shift)/HW counter input frequency

看看上面的公式,再对照代码,一切就很清晰了,这里的tmp就是得到了一个mult值。这个mult值是否合适?不一定,因此这个mult值有可能导致溢出(转换的时候,我们要保证最大时间范围对应的cycle数目乘以这个mult值不能造成64 bit的溢出),如果溢出的话,只能说明mult值还是要小一点哦,从而进入下一次的loop。如何判断mult值会溢出?我们可以看下面的图片:

 

 

在步骤b中计算得到的sftacc就是multi的最大的bit数目。因此,(tmp >> sftacc)== 0就是判断找到最优mult的条件。

(4)这段代码主要是计算clock source的maxadj成员。由于mult有可能被NTP修改,NTP会根据情况会增加或者减少mult的值。我们设定NTP的修正在11%左右,因此clocksource_max_adjustment很是非常直观的,这里就不再描述了。

(5)检查maxadj设定是否OK,是否会溢出,如果是的话,说明mult值还是太大,那么需要还是要降低。

(6)计算max_idle_ns。

③ 注销clocksource

clocksource_unregister函数用来注销一个clocksource,其主要的逻辑都在clocksource_unbind中,代码如下:

/**
 * clocksource_unregister - remove a registered clocksource
 * @cs: clocksource to be unregistered
 */
int clocksource_unregister(struct clocksource *cs)
{
        int ret = 0;

        mutex_lock(&clocksource_mutex);
        if (!list_empty(&cs->list))
                ret = clocksource_unbind(cs);
        mutex_unlock(&clocksource_mutex);
        return ret;
}
EXPORT_SYMBOL(clocksource_unregister);
 


static int clocksource_unbind(struct clocksource *cs)
{
        unsigned long flags;

//不能注销watchdog机制中的基准clocksource

        if (clocksource_is_watchdog(cs)) {
                /* Select and try to install a replacement watchdog. */
                clocksource_select_watchdog(true);
                if (clocksource_is_watchdog(cs))
                        return -EBUSY;
        }

//注销当前clock source的处理

        if (cs == curr_clocksource) {
                /* Select and try to install a replacement clock source */

//选择一个新的当前clock source
                clocksource_select_fallback();
                if (curr_clocksource == cs)  //是系统中的唯一的clock source,不能注销
                        return -EBUSY;
        }

// Select the best clocksource for suspend timing

        if (clocksource_is_suspend(cs)) {
                /*
                 * Select and try to install a replacement suspend clocksource.
                 * If no replacement suspend clocksource, we will just let the
                 * clocksource go and have no suspend clocksource.
                 */
                clocksource_suspend_select(true);
        }

        clocksource_watchdog_lock(&flags);

//将该节点从watchdog的全局列表中取下来
        clocksource_dequeue_watchdog(cs);
        list_del_init(&cs->list);//将该节点从clocksource的全局列表中取下来
        clocksource_watchdog_unlock(&flags);

        return 0;
}

四、如何挑选clock source

1、系统在什么时候会启动选择clock source的过程?

主要context如下:

(1)注册一个新的clock source。有新货到来,总是要再挑挑拣拣吧,说不定会有新发现。

(2)注销clock source。如果注销掉的就是current clock source,总得在剩下的矮子中选一个将军吧

(3)在clock source watchdog中启动。

(4)底层的clock source chip driver调用clocksource_change_rating修改rating。底层的clock source chip driver有可能自废武功,也有可能满血复活,这时候当然要重新选举,否则有可能废材当盟主。

(5)来自用户空间的请求。用户空间的程序可以通过current_clocksource的属性文件强行指定current clocksource。这时候,用户空间程序会给出clock source的名字,内核将用户空间向设定的名字写入override_name buffer,然后调用clocksource_select函数。

2、选举最优clock source的过程

static void clocksource_select(void)
{
        __clocksource_select(false);
}

static void clocksource_select_fallback(void)
{
        __clocksource_select(true);
}

调用clocksource_select函数可以启动选举最优clock source的过程,而该函数实际上是调用__clocksource_select来完成具体的操作,__clocksource_select代码如下:

static void __clocksource_select(bool skipcur)
{
        bool oneshot = tick_oneshot_mode_active();  //获取clocksource 的mode
        struct clocksource *best, *cs;

        /* Find the best suitable clocksource */
        best = clocksource_find_best(oneshot, skipcur); //(1)
        if (!best)
                return;

        if (!strlen(override_name))
                goto found;

        /* Check for the override clocksource. */
        list_for_each_entry(cs, &clocksource_list, list) { 
                if (skipcur && cs == curr_clocksource)
                        continue;
                if (strcmp(cs->name, override_name) != 0)  //不是用户指定的那个,忽略之
                        continue;
                /*
                 * Check to make sure we don't switch to a non-highres
                 * capable clocksource if the tick code is in oneshot
                 * mode (highres or nohz)
                 */

//这下面去判断用户指定的clocksource是否是最好的,支不支持
                if (!(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES) && oneshot) {
                        /* Override clocksource cannot be used. */
                        if (cs->flags & CLOCK_SOURCE_UNSTABLE) {
                                pr_warn("Override clocksource %s is unstable and not HRT compatible - cannot switch while in HRT/NOHZ mode\n",
                                        cs->name);
                                override_name[0] = 0;
                        } else {
                                /*
                                 * The override cannot be currently verified.
                                 * Deferring to let the watchdog check.
                                 */
                                pr_info("Override clocksource %s is not currently HRT compatible - deferring\n",
                                        cs->name);
                        }
                } else
                        /* Override clocksource can be used. */
                        best = cs;
                break;
        }

found:
        if (curr_clocksource != best && !timekeeping_notify(best)) {
                pr_info("Switched to clocksource %s\n", best->name);
                curr_clocksource = best;
        }
}
 

(1)找到最好的那个clock source。oneshot这个参数表示本CPU的tick device的工作模式,这个工作模式有两种,一种是周期性tick,也就是大家熟悉的传统的tick。另外一种叫做one shot模式;由于工作在one shot模式下的tick device对clock source有特别的需求,因此ocksource_find_best函数需要知道本CPU的tick device的工作模式。具体代码如下:

    static struct clocksource *clocksource_find_best(bool oneshot, bool skipcur)
    {
        struct clocksource *cs;

        if (!finished_booting || list_empty(&clocksource_list))--------------(a)
            return NULL;

        list_for_each_entry(cs, &clocksource_list, list) {----------------(b)
            if (skipcur && cs == curr_clocksource)
                continue;
            if (oneshot && !(cs->flags & CLOCK_SOURCE_VALID_FOR_HRES))
                continue;
            return cs;
        }
        return NULL;
    }

(a)系统中的timer硬件有可能包括多个,这些HW timer的驱动都会向系统注册clock source,这导致在初始化的过程中,current clock source会变来变去。与其这样,不如等到尘埃落定(系统启动完毕,各个clock source chip完成初始化)的时候再启动clock source的甄选。finished_booting就是启这个作用的。当然,clock source全局列表是空的话也会返回NULL。

(b)实际的选择当然是rating最高的那个clock source(也就是clock sourcelist中最前面的节点)。但是,如果当前是one shot模式,那么clock source是需要设定CLOCK_SOURCE_VALID_FOR_HRES这个flag的。

(2)这段代码是处理用户空间指定current clock source的请求。用户空间程序会将其心仪的clock source的名字放入到override_name中,在clocksourceselect的时候需要scan clock source列表,找到用户指定的那个clock source,并将其设定为best。注意:这里会覆盖clocksource_find_best函数中找到的那个best clock source。当然,用户虽然是上帝,但是用户也许是愚蠢的,他有可能指定错误的clock source。例如其指定的clock source不支持高精度timer和NO HZ的配置,但是当前的系统恰恰是需要这种能力的。这时候,我们当然不能纵然用户。

(3)调用timekeeping_notify函数通知timekeeping模块。

五、clock source watchdog

呵呵~~~有空再写吧。

六、用户空间接口

1、sysfs接口初始化

在系统初始化的时候会调用init_clocksource_sysfs函数来初始化clock source的sys file system接口,如下:

static int __init init_clocksource_sysfs(void)
{
        int error = subsys_system_register(&clocksource_subsys, NULL);

        if (!error)
                error = device_register(&device_clocksource);

        return error;
}
 

    

(1)一路陪着linux kernel走来的工程师应该对sysdev_class、sys_device、sysdev_driver以及SYSDEV_ATTR这些定义有印象。linux kernel提供了一个pseudo-bus,这条bus主要是用于cpus,中断控制器,timer等系统设备。sysdev_class、sys_device和sysdev_driver组成系统设备三件套,对应设备模型只能够的bus type,device和driver。其实system device模型中需要处理的也是和linux设备模型中类似的逻辑:注册设备、注册driver、driver和设备的匹配等
等。当然也有不同的地方,例如:在电源管理过程中,系统进入suspend状态的时候,优先处理其他的设备,最后处理system设备,唤醒的时候,先唤醒systemdevice,然后是其他普通设备。系统设备是和其他普通设备息息相关的,因此需要首先唤醒,只有这样处理,普通设备在唤醒后才能正常使用系统设备提供的功能。Anyway,虽有不同,但是从high level的层面看还是大部分相同的,用两套逻辑来处理类似的东西还是看起来有些怪异的。因此,3.3的kernel代码废除了systemdevice机制,使用统一设备模型来处理系统设备。

能统一处理当然好,又回到大家熟悉的bus type,device和driver的路子上来。但是,kernel不是活在真空中,大量的AP软件使用了旧的system device机制提供的sysfs接口,为了兼容,subsys_system_register这样的函数被设计出来,调用该接口便创建和旧的system device机制一样的sysfs接口。

(2)OK,回归设备模型当然要定义三件套了,bus type定义如下:

    static struct bus_type clocksource_subsys = {
        .name = "clocksource",
        .dev_name = "clocksource",
    };

device定义如下:

 

static struct device device_clocksource = {
        .id     = 0,
        .bus    = &clocksource_subsys,
        .groups = clocksource_groups,
};

 调用device_register就可以把clock source这个device加入到系统中,统一设备模型会帮我们做一切事情(例如设定clock source这个device对象的名字。作为一个object,clock source device需要一个名字,就是其kobject成员的名字。内核允许将设备的名字留空,当调用device_register函数将设备注册到设备模型中后,设备模型会将"bus->dev_name+device ID”的名字赋给该设备对象,看,多么的贴心)

(3)原来使用SYSDEV_ATTR定义的系统设备属性先改成使用普通的DEVICE_ATTR,具体如下:

static DEVICE_ATTR_RW(current_clocksource);

static DEVICE_ATTR_WO(unbind_clocksource);
static DEVICE_ATTR_RO(available_clocksource);
 

static struct attribute *clocksource_attrs[] = {
        &dev_attr_current_clocksource.attr,
        &dev_attr_unbind_clocksource.attr,
        &dev_attr_available_clocksource.attr,
        NULL
};
ATTRIBUTE_GROUPS(clocksource);

系统中有多个clocksource,通过available_clocksource这个设备属性可以知道当前系统有多少个可用的clocksource。虽然系统有多个clocksource,但是内核会以一定的策略选择一个(例如rating最高的哪个)作为当前的clocksource,这个可以通过current_clocksource这个属性文件获得当前系统正在使用的clocksource。对该属性文件写可以设定current clock source。unbind_clocksource属性是write only的,该接口可以对某个clocksource进行unbind的操作。

.groups = clocksource_groups,为clocksource设备创建上面定义的三个属性文件,这三个属性文件还有对应的 show 和store 函数;这个三个属性文件会在

# ls /sys/bus/clocksource/devices/clocksource0
available_clocksource  current_clocksource  offset_ns  power  subsystem  uevent  unbind_clocksource

七、提供给其他driver计时用的接口函数

1、为何会有timecounter和cyclecounter

在内核的driver中,我们可能有这样的需求:获取drive中的A事件和B事件之间的时间值或者一个event stream过程中,各个event的时刻值。这里,driver不关心绝对的时间点,关心的是事件之间的时长。为了应对这个需求,clock source模块提供了timecounter和cyclecounter。

内核中使用struct cyclecounter 来抽象一个free running的counter,从0开始,不断累加。由于counter的bit数目有限,因此,某个时间后,counter会wraparound,从0继续开始。该数据结构定义如下:

    struct cyclecounter {
        cycle_t (*read)(const struct cyclecounter *cc);--获取当前的counter value,单位是cycle
        cycle_t mask;------------该count有多少个bit?
        u32 mult;--------------转换成ns需要的乘积因子
        u32 shift; --------------转换成ns需要的右移因子
    };

每个cycle counter的counter value都是针对clock计数的,因此,通过read获取的counter value是基于cycle的,而cycle又是和输入频率有关。不过,对于其他driver而言,cycle数据是没有意义的,最好统一使用纳秒这样的单位,因此在
struct cyclecounter 中就有了mult和shift这两个成员了。读到这里,我相信大部分的读者都会怒吼:你这些文字在描述clock source的时候就说过一遍,现在又拿出来骗。呵呵~~稍安勿躁,实际上内核的确把一个HW block抽象成了两个数据结构,具体参考下图:

cyclecounter

实际上,最开始的时候,内核的确是只有clock source模块,它位于timekeeping模块和硬件之间。但是,其他内核模块也有访问free running counter的需要,这时候,内核开发人员创建了cycle counter和timer counter这样的概念,虽然代码有一点重复,但是这样不会触及clock source代码的改动。

timecounter是构架在cycle counter之上,使用纳秒这样的时间单位而不是cycle数目,这样的设计会让用户接口变得更加友好,毕竟大家还是喜欢直观的纳秒值。timecounter的定义如下:


struct timecounter {
        const struct cyclecounter *cc;    //该timercounter base的cycle counter
        u64 cycle_last;   //上一次访问的 counter value
        u64 nsec;   //当前的纳秒值
        u64 mask;
        u64 frac;    //累积的小数纳秒
};

2、如何使用timecounter的接口

文件路径:kernel/time/timecounter.c

首先需要初始化,代码如下:

void timecounter_init(struct timecounter *tc,
                      const struct cyclecounter *cc,
                      u64 start_tstamp)
{
        tc->cc = cc;   //该time counter需要哪一个cycle counter
        tc->cycle_last = cc->read(cc); //获取初始化时刻HW counter的counter value
        tc->nsec = start_tstamp; //设定纳秒的基准值
        tc->mask = (1ULL << cc->shift) - 1;
        tc->frac = 0;
}
EXPORT_SYMBOL_GPL(timecounter_init);

如果start_tstamp等于0的话,在调用timecounter_init这个时刻被定义为0纳秒。之后,驱动代码可以调用timecounter_read函数来获取当前的时间值(基于start_tstamp的),代码如下:

  

u64 timecounter_read(struct timecounter *tc)
{
        u64 nsec;

        /* increment time by nanoseconds since last call */
        nsec = timecounter_read_delta(tc);//离上次调用timecounter_init或者timecounter_read过去了多久
        nsec += tc->nsec;
        tc->nsec = nsec; //设定当前时间

        return nsec;
}
EXPORT_SYMBOL_GPL(timecounter_read);
 

  • 30
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值