# linux-sides-Timers and time management-Introduction to the clocksource framework
Date: 2019-08-20


这篇文章 [Timers and time management in the Linux kernel. Part 2.](https://github.com/0xAX/linux-insides/blob/master/Timers/linux-timers-2.md) 是出自 [linux-insides](https://0xax.gitbooks.io/linux-insides/content/)一书中 [Timers and time management](https://github.com/0xAX/linux-insides/tree/master/Timers) 章节 [Introduction to the clocksource framework](https://github.com/0xAX/linux-insides/blob/master/Timers/linux-timers-2.md)

# Linux内核中的定时器和时间管理.Part 2.

## `clocksource`框架简介

上一节[Part 1](https://0xax.gitbooks.io/linux-insides/content/Timers/linux-timers-1.html)是[Timer](https://0xax.gitbooks.io/linux-insides/content/Timers/index.html)的第一部分,描述了Linux内核中与计时器和时间管理相关的东西。我们在前一部分中了解了两个概念：

- `jiffies`
- `clocksource`

第一个全局变量定义在[include/linux/jiffies.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/jiffies.h)头文件中，代表在每个定时器中断期间增加的计数器。因此，如果我们可以访问这个全局变量并且我们知道定时器中断率，我们可以将`jiffies`转换为人类时间单位。我们已经知道在Linux内核中由编译时常量（称为“HZ”）表示的定时器中断速率。`HZ`的值等于`CONFIG_HZ`内核配置选项的值，如果我们通过文件[arch/x86/configs/x86\_64\_defconfig](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/configs/x86_64_defconfig)查找到内核相关配置:

```C
CONFIG_HZ_1000 = Y
```

设置内核配置选项后. [x86\_64](https://en.wikipedia.org/wiki/X86-64) 体系架构下 `CONFIG_HZ`值默认是 `1000` , 如果我们将`jiffies`除以`HZ`:

```C
jiffies / HZ
```

我们将获得自Linux内核开始工作之后经过的秒数，换句话说，我们将获得系统[uptime](https://en.wikipedia.org/wiki/Uptime). 由于`HZ`代表一秒钟内的定时器中断量，我们可以在未来的某个时间设置一个值。例如：

```C
/* one minute from now */
unsigned long later = jiffies + 60*HZ;

/* five minutes from now */
unsigned long later = jiffies + 5*60*HZ;
```

这是Linux内核中非常常见的做法。例如，如果您将查看 [arch/x86/kernel/smpboot.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/smpboot.c) 源代码文件，你会发现`do_boot_cpu`函数。此功能启动除引导处理器之外的所有处理器。您可以找到等待十秒钟的应用程序处理器响应的代码段：

```C
if (!boot_error) {
    timeout = jiffies + 10*HZ;
    while (time_before(jiffies, timeout)) {
        ...
        ...
        ...
        schedule();
    }
    ...
    ...
    ...
}
```

* * *

> _备注_： _原书udelay(100)后修改为schedule()_ _详见[x86/smpboot: Remove udelay(100) when polling cpu\_initialized\_map](https://github.com/torvalds/linux/commit/6e38f1e79d16f4fa9e5cf06792500e11c96a6f84#diff-05263b32e1cfae293e09ebed1fbfbcfd)_

* * *

我们在这里将`jiffies + 10 * HZ`值赋给`timeout`变量。这意味着超时十秒。在此之后，我们进入一个循环，我们使用`time_before`宏来比较当前的`jiffies`值和判断是否的超时。

或者例如，如果我们查看[sound/isa/sscape.c](https://github.com/torvalds/linux/blob/master/sound/isa/sscape.c)源代码文件，它代表[Ensoniq Soundscape Elite](https://en.wikipedia.org/wiki/Ensoniq_Soundscape_Elite)声卡驱动，我们将看到`obp_startup_ack`函数，它等待给定的超时，以便On-Board Processor返回其启动确认序列：

```C
static int obp_startup_ack(struct soundscape *s, unsigned timeout)
{
    unsigned long end_time = jiffies + msecs_to_jiffies(timeout);

    do {
        ...
        ...
        ...
        x = host_read_unsafe(s->io_base);
        ...
        ...
        ...
        if (x == 0xfe || x == 0xff)
            return 1;
        msleep(10);
    } while (time_before(jiffies, end_time));

    return 0;
}
```

正如您所看到的，`jiffies`变量在[Linux内核代码](https://elixir.bootlin.com/linux/v5.3-rc5/ident/jiffies)中广泛引用， 正如我已经写过的那样，我们在前一部分中遇到了另一个与时间管理相关的新概念 - `clocksource`。我们只看到了这个概念的简短描述和用于时钟源注册的API。让我们仔细看看这一部分.

## `clocksource`简介

`clocksource`概念表示Linux内核中时钟源管理的通用API。为什么我们需要一个单独的框架呢？让我们回到开头。`time`概念是Linux内核和其他操作系统内核的基本概念。计时是使用这个概念的必要条件之一。例如，Linux内核必须知道并更新自系统启动以来经过的时间，它必须确定当前进程已为每个处理器运行多长时间等等。Linux内核可以获取有关时间的信息吗？

- 首先是实时时钟或[RTC](https://en.wikipedia.org/wiki/Real-time_clock) 由非易失性设备代表。您可以在Linux内核文件夹[drivers/rtc](https://github.com/torvalds/linux/tree/master/drivers/rtc)中找到一组与架构无关的实时时钟驱动程序目录。除此之外，每个架构都可以为依赖于架构的实时时钟提供驱动程序，例如, [x86](https://en.wikipedia.org/wiki/X86)架构下的, `CMOS/RTC`\-[arch/x86/kernel/rtc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/rtc.c)
- 第二个是系统计时器 - 以定期速率激活 [interrupts](https://en.wikipedia.org/wiki/Interrupt)。例如，对于 [IBM PC](https://en.wikipedia.org/wiki/IBM_Personal_Computer)兼容机的 - [programmable interval timer](https://en.wikipedia.org/wiki/Programmable_interval_timer).

我们已经知道，为了计时目的，我们可以在Linux内核中使用`jiffies`。`jiffies`可以被认为是只读全局变量，它以`HZ`频率更新。我们知道`HZ`是一个编译时内核参数，其合理范围是从`100`到`1000`[Hz](https://en.wikipedia.org/wiki/Hertz)。因此，保证有一个时间测量接口，分辨率为`1` - `10`毫秒。除了标准的`jiffies`之外，我们在前一部分中看到了`refined_jiffies`时钟源，它基于`i8253/i8254` [programmable interval timer](https://en.wikipedia.org/wiki/Programmable_interval_timer) 频率大约`1193182`赫兹。因此，我们可以使用`refined_jiffies`获得关于`1`微秒分辨率的信息。这时， [nanoseconds](https://en.wikipedia.org/wiki/Nanosecond)是给定时钟源的时间值单位的最佳选择。

时间间隔测量是否可以做到的更精确取决于硬件。我们对`x86`依赖的定时器硬件知之甚少。但每个架构都提供自己的定时器硬件。早期，每个架构都有自己的实现。该问题的解决方案是通过公共代码框架中的抽象层和相关API，管理各种时钟源并独立于定时器中断。这个通用的代码框架变成了`clocksource`框架。

通用时间和时钟源管理框架将大量计时代码移动到独立于体系结构的框架代码部分，依赖于体系结构的部分简化为定义和管理低级硬件的时钟源。使用不同的硬件测量不同架构的时间间隔需要大量资源，而且非常复杂。每个时钟相关服务的实现与单个硬件设备密切相关，它导致不同体系结构的类似实现。

在此框架内，每个时钟源都需要将时间表示维持为单调递增值。正如我们在Linux内核代码中看到的那样，纳秒是当时时钟源的时间值单位的最佳选择。时钟源框架的要点之一是允许用户在配置系统以及选择，访问和缩放不同时钟源时，在从一系列可用时钟硬件设备中选择时钟源。

## `clocksource`结构

`clocksource`框架的基本定义是在 [include/linux/clocksource.h](https://github.com/torvalds/linux/blob/master/include/linux/clocksource.h)头文件的`clocksource`结构体中. 我们已经看到了前面[part 1](https://0xax.gitbooks.io/linux-insides/content/Timers/linux-timers-1.html) 中的`clocksource`结构提供的一些字段. 让我们看看这个结构的完整定义，并尝试描述它的所有字段：

```C
struct clocksource {
    u64 (*read)(struct clocksource *cs);
    u64 mask;
    u32 mult;
    u32 shift;
    u64 max_idle_ns;
    u32 maxadj;
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
    struct arch_clocksource_data archdata;
#endif
    u64 max_cycles;
    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);
+   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;
};
```

我们已经看到了前一部分中`clocksource`结构的第一个字段 - 它是指向`read`函数的指针，它返回由clocksource框架选择的最佳计数器。例如，我们使用`jiffies_read`函数来读取`jiffies`值：

```C
static struct clocksource clocksource_jiffies = {
    ...
    .read       = jiffies_read,
    ...
}
```

`jiffies_read` 返回:

```C
static cycle_t jiffies_read(struct clocksource *cs)
{
    return (cycle_t) jiffies;
}
```

使用`read_tsc`函数读取`jiffies`:

```C
static struct clocksource clocksource_tsc = {
    ...
    .read               = read_tsc,
    ...
};
```

对于 [time stamp counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter) 请参考.

下一个字段是`mask`，它允许确保来自非`64位`计数器的计数器值之间的减法不需要处理特殊的溢出逻辑。

在`mask`字段之后，我们可以看到两个字段：`mult`和`shift`。这些是基于数学函数的字段，它们提供转换特定于每个时钟源的时间值的能力。换句话说，这两个字段帮助我们将计数器的抽象机器时间单位转换为纳秒。

在这两个字段之后，我们可以看到`64`位`max_idle_ns`字段表示clocksource允许的最大空闲时间（以纳秒为单位）。我们需要在此字段中为Linux内核启用`CONFIG_NO_HZ`内核配置选项。此内核配置选项使Linux内核能够在没有常规计时器tick的情况下运行（我们将在其他部分中看到对此内容的完整说明）。`dynamic tick`允许内核睡眠时间长于单个tick的问题，而且睡眠时间可以是无限的。`max_idle_ns`字段表示此睡眠限制。

`max_idle_ns`之后的下一个字段是`maxadj`字段，它是`mult`的最大调整值。我们将周期转换为纳秒的主要公式：

```C
((u64) cycles * mult) >> shift;
```

不是`100%`准确。相反，数字尽可能接近纳秒，`maxadj`有助于纠正这一点，并使得clocksource API避免调整后可能溢出的`mult`值。

下一个字段是`max_cycles`，正如我们从名称中可以理解的那样，该字段表示潜在溢出之前的最大循环值。

接下来的六个字段是函数的指针：

- `enable` - 启用clocksource的可选功能;
- `disable` - 禁用clocksource的可选功能;
- `suspend` - 暂停clockource的功能;
- `resume` - resumeource的恢复功能;
- `mark_unstable` - 通过watchdog标志clocksource不稳定功能;
- `tick_stable` - 通过watchdog设置稳定的同步点;

最后一个字段是`owner`表示对作为clocksource所有者的内核模块 [module](https://en.wikipedia.org/wiki/Loadable_kernel_module)的引用

我们刚刚浏览了`clocksource`结构的所有标准字段。但你可以注意到我们错过了`clocksource`结构的一些领域。我们可以将所有错过的字段划分为两种类型：

- 第一种类型, 已经为我们所知。例如，它们是`name`字段，表示`clocksource`的名称，`rating`字段有助于Linux内核选择最佳时钟源等。
- 第二种类型，依赖于不同Linux内核配置选项的字段。 让我们来看看这些字段。

第一个字段是`archdata`。该字段具有`arch_clocksource_data`类型，取决于`CONFIG_ARCH_CLOCKSOURCE_DATA`内核配置选项。此字段仅适用于[x86](https://en.wikipedia.org/wiki/X86)和[IA64](https://en.wikipedia.org/wiki/IA-64)体系结构。同样，正如我们从字段名称中可以理解的那样，它代表了时钟源的体系结构特定数据。例如，它代表`vDSO`时钟模式：

```C
struct arch_clocksource_data {
    int vclock_mode;
};
```

对于`x86`架构。`vDSO`时钟模式可以是以下之一：

```C
#define VCLOCK_NONE 0
#define VCLOCK_TSC  1
#define VCLOCK_HPET 2
#define VCLOCK_PVCLOCK 3
#define VCLOCK_MAX 3
```

最后三个字段是`wd_list`，`cs_last`，`wd_last`取决于`CONFIG_CLOCKSOURCE_WATCHDOG`内核配置选项。 首先让我们试着了解它是什么`watchdog`。简而言之，`watchdog`是一个计时器，用于检测计算机故障并从中恢复。所有这三个字段都包含`clocksource`框架使用的`watchdog`相关数据。只有在[arch/x86/KConfig](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/Kconfig#L54)内核配置文件包含`CONFIG_CLOCKSOURCE_WATCHDOG`内核配置选项,才可以看到。 那么，为什么在在`x86`和`x86_64`中需要 [watchdog](https://en.wikipedia.org/wiki/Watchdog_timer)? 您可能已经知道所有`x86`处理器都有特殊的64位寄存器 - [time stamp counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter). 该寄存器包含重启后的 [cycles](https://en.wikipedia.org/wiki/Clock_rate).有时需要针对另一个时钟源验证时间戳计数器。我们不会在这部分中看到`watchdog`计时器的初始化，在此之前我们必须了解更多关于计时器的信息。

描述特殊属性的标志`flag`

```C
#define CLOCK_SOURCE_IS_CONTINUOUS     0x01
#define CLOCK_SOURCE_MUST_VERIFY       0x02
#define CLOCK_SOURCE_WATCHDOG          0x10
#define CLOCK_SOURCE_VALID_FOR_HRES        0x20
#define CLOCK_SOURCE_UNSTABLE          0x40
#define CLOCK_SOURCE_SUSPEND_NONSTOP       0x80
#define CLOCK_SOURCE_RESELECT          0x100
```

就这样。从这一刻起，我们就知道了`clocksource`结构的所有领域。这些知识将帮助我们学习`clocksource`框架的内部。

## 新的时钟源注册

我们前面从 [part 1](https://0xax.gitbooks.io/linux-insides/content/Timers/linux-timers-1.html)只看到了 `clocksource` 框架的一个函数 -`**clocksource_register**`**. 函数定义在头文件 [include/linux/clocksource.h](https://github.com/torvalds/linux/tree/master/include/linux/clocksource.h)我们能够从函数名看出，他的主要功能是注册新的clocksource, 如果我们查看 `__clocksource_register` 的实现， 将会发现他只是调用了** `clocksource_register_scale` 并返回:

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

在我们看到`__clocksource_register_scale`函数的实现之前，我们可以看到`clocksource`为新的时钟源注册提供了额外的API：

```C
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_scale`函数在[kernel/time/clocksource.c](https://github.com/torvalds/linux/tree/master/kernel/time/clocksource.c)源代码文件中定义。为了理解这些函数之间的区别，让我们看一下`clocksource_register_khz`函数的参数。我们可以看到，这个函数有三个参数：

- `cs` - 要安装的clocksource;
- `scale` - 时钟源的比例因子。换句话说，如果我们将在频率上乘以该参数的值，我们将得到一个时钟源的`hz`;
- `freq` - 时钟源频率除以比例。

下面看下 `__clocksource_register_scale` 函数的实现:

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

+   clocksource_arch_init(cs);

    __clocksource_update_freq_scale(cs, scale, freq);

    mutex_lock(&clocksource_mutex);

    clocksource_watchdog_lock(&flags);
    clocksource_enqueue(cs);
    clocksource_enqueue_watchdog(cs);
    clocksource_watchdog_unlock(&flags);

+   clocksource_select();
+   clocksource_select_watchdog(false);
+   __clocksource_suspend_select(cs);
+   mutex_unlock(&clocksource_mutex);
    return 0;
}
```

* * *

> _+:后续版本增加部分_

* * *

* * *

clocksource\_arch\_init(cs) 涉及archDaTa内容的完整性检查

```C
void clocksource_arch_init(struct clocksource *cs)
{
    if (cs->archdata.vclock_mode == VCLOCK_NONE)
        return;

    if (cs->archdata.vclock_mode > VCLOCK_MAX) {
        pr_warn("clocksource %s registered with invalid vclock_mode %d. Disabling vclock.\n",
            cs->name, cs->archdata.vclock_mode);
        cs->archdata.vclock_mode = VCLOCK_NONE;
    }

    if (cs->mask != CLOCKSOURCE_MASK(64)) {
        pr_warn("clocksource %s registered with invalid mask %016llx. Disabling vclock.\n",
            cs->name, cs->mask);
        cs->archdata.vclock_mode = VCLOCK_NONE;
    }
}
```

> _后续版本增加函数_

* * *

首先，我们可以看到`**clocksource_register_scale**`**函数从调用同一源代码文件中定义的**`clocksource_update_freq_scale`函数开始，并以新频率更新指定的时钟源。

我们来看看这个函数的实现。在第一步中，我们需要检查给定频率，如果它没有作为`zero`传递，我们需要计算给定时钟源的`mult`和`shift`参数。为什么我们需要检查`frequency`的值 ? 实际上它可以为0。如果你仔细查看`__clocksource_register`函数的实现，你可能已经注意到我们将`frequency`传递为`0`。我们只对一些具有自定义`mult`和`shift`参数的时钟源进行此操作。查看上一节 [part 1](https://0xax.gitbooks.io/linux-insides/content/Timers/linux-timers-1.html) 我们看到了`jiffies`的`mult`和`shift`的计算。`__clocksource_update_freq_scale`函数将为我们的其他时钟源提供更新时钟频率此操作。

所以在`__clocksource_update_freq_scale`函数的开头我们检查`frequency`参数的值，如果不是零，我们需要为给定的时钟源计算`mult`和`shift`。让我们看看`mult`和`shift`计算：

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

        if (freq) {
             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);
       }
        ...
        ...
        ...
}
```

在这里，我们可以看到在时钟源计数器溢出之前我们可以运行的最大秒数的计算。首先，我们使用时钟源掩码`mask`的值填充`sec`变量。请记住，时钟源的掩码表示对给定时钟源有效的最大位数。在此之后，我们可以看到两个分工操作。

- 首先，我们将`sec`变量除以时钟源频率，然后除以比例因子。`freq`参数显示我们将在一秒钟内发生多少个定时器中断。因此，我们将`mask`值除以表示计时器频率的计数器的最大数量(例如`jiffy`),并获得特定时钟源的最大秒数。
- 其次，第二次除法操作将给出我们特定时钟源的最大秒数, 取决于其比例因子，其可以是`1Hz`或`1kHz`（10^3Hz）。

在我们获得最大秒数后，我们检查此值并将其设置为`1`或`600`取决于下一步的结果。这些值是clockource的最大休眠时间（以秒为单位）。在下一步中，我们可以看到`clocks_calc_mult_shift`的调用。该函数的要点是计算给定时钟源的`mult`和`shift`值。在`__clocksource_update_freq_scale`函数的最后，我们检查刚刚计算出的给定时钟源的`mult`值在调整后不会导致溢出，更新给定时钟源的最大空闲时间`max_idle_ns`和`max_cycles`,并将结果打印到内核缓冲区：

```C
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);
```

看下 [dmesg](https://en.wikipedia.org/wiki/Dmesg) 输出:

```C
$ dmesg | grep "clocksource:"
[    0.000000] clocksource: refined-jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1910969940391419 ns
[    0.000000] clocksource: hpet: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 133484882848 ns
[    0.094084] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 1911260446275000 ns
[    0.205302] clocksource: acpi_pm: mask: 0xffffff max_cycles: 0xffffff, max_idle_ns: 2085701024 ns
[    1.452979] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x7350b459580, max_idle_ns: 881591204237 ns
```

执行完 `__clocksource_update_freq_scale` , 将返回函数`__clocksource_register_scale` 他将注册新的clock source, 我们可以看到下面几个函数:

```C
    mutex_lock(&clocksource_mutex);
    clocksource_watchdog_lock(&flags);
    clocksource_enqueue(cs);
    clocksource_enqueue_watchdog(cs);
    clocksource_watchdog_unlock(&flags);

+   clocksource_select();
+   clocksource_select_watchdog(false);
+   __clocksource_suspend_select(cs);
+   mutex_unlock(&clocksource_mutex);
```

请注意，在调用`clocksource_enqueue`函数前, 锁定 `clocksource_mutex` [mutex](https://en.wikipedia.org/wiki/Mutual_exclusion). `clocksource_mutex`互斥体主要是保护变量`curr_clocksource` , 该变量代表当前选择的 `clocksource`和已注册的`clocksources` 列表`clocksource_list`, 现在，让我们来看看三个功能:

- `clocksource_enqueue` 函数和其他两个函数在同一源代码[file](https://github.com/torvalds/linux/tree/master/kernel/time/clocksource.c). 我们通过所有已经注册的`clocksources`或遍历`clocksource_list`的所有元素，并试图找到给定`clocksource`的最佳位置

```C
static void clocksource_enqueue(struct clocksource *cs)
{
    struct list_head *entry = &clocksource_list;
    struct clocksource *tmp;

    list_for_each_entry(tmp, &clocksource_list, list)
        if (tmp->rating >= cs->rating)
            entry = &tmp->list;
    list_add(&cs->list, entry);
}
```

最后我们将新的`clocksource` 放入 `clocksource_list`.

- `clocksource_enqueue_watchdog` 与之前的函数几乎完全相同，但是它将新的时钟源插入到`wd_list`中, 取决于`struct clocksource`的`flags`并启动新的[watchdog](https://en.wikipedia.org/wiki/Watchdog_timer)计时器。正如我已经写过的那样，我们不会在这部分中考虑`watchdog`相关的东西，但会在接下来的部分进行。
    
- `clocksource_select` 我们可以从函数的名称中理解这个函数的要点 - 从注册的时钟源中选择最好的`clocksource`:
    

```C
static void clocksource_select(void)
{
    return __clocksource_select(false);
}
```

注意`__clocksource_select`函数接受一个参数（在我们的例子中是`false`）。这个[bool](https://en.wikipedia.org/wiki/Boolean_data_type)参数显示了如何遍历`clocksource_list`。在我们的例子中，我们传递`false`，这意味着我们将遍历`clocksource_list`的所有条目。我们已经知道，在调用`clocksource_enqueue`函数之后，具有最佳评级的`clocksource`将是`clocksource_list`中的第一个，所以我们可以很容易地从这个列表中获取它。在我们找到具有最佳评级的时钟源后，我们切换到它：

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

可以从`dmesg`输出结果中看到:

```C
$ dmesg | grep Switched
[    0.199688] clocksource: Switched to clocksource hpet
[    2.452966] clocksource: Switched to clocksource tsc
```

请注意，我们可以在`dmesg`输出中看到两个时钟源（在我们的例子中是`hpet`和`tsc`）。是的，实际上特定硬件上可能有许多不同的时钟源。因此，Linux内核知道所有已注册的时钟源，并在每次注册新时钟源后切换到具有更好评级的时钟源。

* * *

- `__clocksource_suspend_select`设置挂起时钟源来源

```C
static void __clocksource_suspend_select(struct clocksource *cs)
{
    if (!(cs->flags & CLOCK_SOURCE_SUSPEND_NONSTOP))
        return;
    if (cs->suspend || cs->resume) {
        pr_warn("Nonstop clocksource %s should not supply suspend/resume interfaces\n",
            cs->name);
    }

    if (!suspend_clocksource || cs->rating > suspend_clocksource->rating)
        suspend_clocksource = cs;
}
```

> _后续版本增加_

* * *

如果我们将查看[kernel/time/clocksource.c](https://github.com/torvalds/linux/tree/master/kernel/time/clocksource.c)源代码文件的底部，我们将看到它有[sysfs](https://en.wikipedia.org/wiki/Sysfs)接口。主要初始化发生在`init_clocksource_sysfs`函数中，该函数将在设备`initcalls`期间调用。让我们看一下`init_clocksource_sysfs`函数的实现：

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

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

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

    return error;
}

device_initcall(init_clocksource_sysfs);
```

> _这部分与原书版本略有不同_

首先，我们可以看到它通过调用`subsys_system_register`函数来注册`clocksource`子系统。换句话说，在调用此函数后，我们将有以下目录：

```C
$ pwd
/sys/devices/system/clocksource
```

在这一步之后，我们可以看到`device_clocksource`设备的注册，它由以下结构表示：

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

并创建三个文件:

- `dev_attr_current_clocksource`;
- `dev_attr_unbind_clocksource`;
- `dev_attr_available_clocksource`.

这些文件将提供有关系统中当前时钟源信息， 系统中可用的时钟源以及允许取消绑定的时钟源接口

`init_clocksource_sysfs` 执行后, 我们将在一下内容中找到可用的时钟源的一些信息:

```C
$ cat /sys/devices/system/clocksource/clocksource0/available_clocksource 
tsc hpet acpi_pm 
```

有关系统中当前时钟源的信息

```C
$ cat /sys/devices/system/clocksource/clocksource0/current_clocksource 
tsc
```

在上一节中， 我们看到了注册`jiffies`时钟源的API, 但是没有深入了解`clocksource`框架，这一节中我们完成相关工作并且看到了注册新时钟源的实现和选择系统最佳评级的时钟源. 当然这并不是`clocksource`所有的API ,还有一些额外的函数，比如,`clocksource_unregister` 用于从 `clocksource_list`中删除给定的时钟源头等等. 但是我并不会在这部分描述他， 他们现在不是很重要， 如果你感兴趣可以查看[kernel/time/clocksource.c](https://github.com/torvalds/linux/tree/master/kernel/time/clocksource.c).

就这样。

## 结论

这是第二部分的结尾，本节描述了Linux内核中定时器和定时器管理机制的内容。在前一个章节中，我们 了解到以下两个概念：`jiffies` and `clocksource`. 这一部分我们看到了一些`jiffies`用法的例子，并且了解了更多 `clocksource`的实现细节

如果您有任何问题或建议，请随时在twitter [0xAX](https://twitter.com/0xAX)上ping我，给我发电子邮件[email](anotherworldofworld@gmail.com)或者只创建[问题](https://github.com/0xAX/linux-insides/issues/new)。

**请注意，英语不是我的第一语言，我很抱歉给您带来不便。如果您发现任何错误，请将PR发送至[linux-insides](https://github.com/0xAX/linux-insides)。**

## Links

- [x86](https://en.wikipedia.org/wiki/X86)
- [x86\_64](https://en.wikipedia.org/wiki/X86-64)
- [uptime](https://en.wikipedia.org/wiki/Uptime)
- [Ensoniq Soundscape Elite](https://en.wikipedia.org/wiki/Ensoniq_Soundscape_Elite)
- [RTC](https://en.wikipedia.org/wiki/Real-time_clock)
- [interrupts](https://en.wikipedia.org/wiki/Interrupt)
- [IBM PC](https://en.wikipedia.org/wiki/IBM_Personal_Computer)
- [programmable interval timer](https://en.wikipedia.org/wiki/Programmable_interval_timer)
- [Hz](https://en.wikipedia.org/wiki/Hertz)
- [nanoseconds](https://en.wikipedia.org/wiki/Nanosecond)
- [dmesg](https://en.wikipedia.org/wiki/Dmesg)
- [time stamp counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter)
- [loadable kernel module](https://en.wikipedia.org/wiki/Loadable_kernel_module)
- [IA64](https://en.wikipedia.org/wiki/IA-64)
- [watchdog](https://en.wikipedia.org/wiki/Watchdog_timer)
- [clock rate](https://en.wikipedia.org/wiki/Clock_rate)
- [mutex](https://en.wikipedia.org/wiki/Mutual_exclusion)
- [sysfs](https://en.wikipedia.org/wiki/Sysfs)
- [previous part](https://0xax.gitbooks.io/linux-insides/content/Timers/linux-timers-1.html)

