# linux-sides-Timers and time management-Introduction
Date: 2019-08-07


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

> _内核版本比对5.3-rc 进行了相关调整, 增加相关备注_

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

## 介绍

这是另一篇文章，它在[linux-insides](https://0xax.gitbooks.io/linux-insides/content/)一书中开启了新的篇章。前面的[部分](https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-4.html)描述了[系统调用](https://en.wikipedia.org/wiki/System_call)概念，现在是时候开始新篇章了。正如人们可能从标题中理解的那样，本章将专门讨论Linux内核中的“定时器”和“时间管理”。当前章节的主题选择并非偶然。定时器（通常是时间管理）非常重要，并且在Linux内核中广泛使用。 Linux内核使用计时器执行各种任务，例如[TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol)实现中的不同超时，内核知道当前时间，调度异步函数，下一个事件中断调度和还有更多。 因此，我们将开始学习本部分中不同时间管理相关内容的实现。我们将看到不同类型的计时器以及不同的Linux内核子系统如何使用它们。与往常一样，我们将从Linux内核的最早部分开始，并完成Linux内核的初始化过程。我们已经在特殊的[章节-Kernel initialization process](https://0xax.gitbooks.io/linux-insides/content/Initialization/index.html)中完成了它，它描述了Linux内核的初始化过程，但是你可能还记得我们错过了那里有些东西。其中一个是定时器的初始化。 我们开始吧。

## 初始化非标准PC硬件时钟

Linux内核解压缩后（更多关于此内容，您可以在[内核解压缩](https://0xax.gitbooks.io/linux-insides/content/Booting/linux-bootstrap-5.html)部分中阅读）非特定代码开始在[init/main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/init/main.c)源代码文件中工作。初始化[lock validator](https://www.kernel.org/doc/Documentation/locking/lockdep-design.txt)后，初始化[cgroups](https://en.wikipedia.org/wiki/Cgroups)并设置[canary](https://en.wikipedia.org/wiki/Buffer_overflow_protection)值，我们可以看到`setup_arch`函数的调用。 你可能还记得，这个函数（定义在[arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/setup.c#L842))准备/初始化特定于体系结构的东西（例如它为[bss](https://en.wikipedia.org/wiki/.bss)部分保留一个位置，为[initrd](https://en.wikipedia.org/wiki/Initrd)保留一个位置，解析内核命令行以及许多其他事情）。除此之外，我们还可以找到一些时间管理相关的功能。 首先是：

```C
x86_init.timers.wallclock_init();
```

我们在描述Linux内核初始化的章节中已经看到了`x86_init`结构。此结构包含指向不同平台的默认设置功能的指针，如 [Intel MID](https://en.wikipedia.org/wiki/Mobile_Internet_device#Intel_MID_platforms)，[Intel CE4100](http://www.wpgholdings.com/epaper/US/newsRelease_20091215/255874.pdf)  
等`x86_init`结构定义在[arch/x86/kernel/x86\_init.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/x86_init.c#L36)，正如您所看到的，它默认确定标准PC硬件。 我们可以看到，`x86_init`结构具有`x86_init_ops`类型，它为平台特定的设置提供了一组函数，如保留标准资源，平台特定的内存设置，中断处理程序的初始化等。这种结构如下所示：

```C
struct x86_init_ops {
    struct x86_init_resources resources;
    struct x86_init_mpparse mpparse;
    struct x86_init_irqs irqs;
    struct x86_init_oem oem;
    struct x86_init_paging paging;
    struct x86_init_timers timers;
    struct x86_init_iommu iommu;
    struct x86_init_pci pci;
    struct x86_hyper_init hyper;
    struct x86_init_acpi acpi;
};
```

* * *

> _备注_：  
> _新增以下两个成员_
> 
> ```C
>    struct x86_hyper_init hyper; /*x86 hypervisor init functions*/
>    struct x86_init_acpi acpi;   /*x86 ACPI init functions*/
> ```

* * *

注意具有`x86_init_timers`类型的`timers`字段。我们可以通过其名称理解该字段与时间管理和计时器有关。 `x86_init_timers`包含四个字段，这些字段都是在[void](https://en.wikipedia.org/wiki/Void_type)上返回指针的函数：

- `setup_percpu_clockev` - 为boot cpu设置每个cpu时钟事件设备;
- `timer_init` - 初始化平台计时器;
- `wallclock_init` - 初始化wallclock设备。

* * *

> _备注_：  
> _[x86\_init\_timers删除以下一个成员](https://github.com/torvalds/linux/commit/2f7a3f8e871eb0713d23c533bd5e44a544e43eb8#diff-c4f68300046af1d0acfac43a7d0c28f2)_
> 
> ```C
>    void (*tsc_pre_init)(void);  
> ```

* * *

因此，正如我们已经知道的那样，在我们的例子中，`wallclock_init`执行wallclock设备的初始化。如果我们查看`x86_init`结构，我们看到`wallclock_init`指向`x86_init_noop`：

```C
struct x86_init_ops x86_init __initdata = {
    ...
    ...
    ...
    .timers = {
        .wallclock_init         = x86_init_noop,
    },
    ...
    ...
    ...
}
```

用于标准PC硬件. `x86_init_noop`只是一个什么都不做的函数：

```C
void __cpuinit x86_init_noop（void）{}
```

实际上，`wallclock_init`功能用于[Intel MID](https://en.wikipedia.org/wiki/Mobile_Internet_device#Intel_MID_platforms)平台。 `x86_init.timers.wallclock_init`的初始化位于[arch/x86/platform/intel-mid/intel-mid.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/platform/intel-mid/intel-mid.c)源代码文件的函数`x86_intel_mid_early_setup`中：

```C
void __init x86_intel_mid_early_setup(void)
{
    ...
    ...
    ...
    x86_init.timers.wallclock_init = intel_mid_rtc_init;
    ...
    ...
    ...
}
```

`intel_mid_rtc_init`函数的实现在[arch/x86/platform/intel-mid /intel\_mid\_vrtc.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/platform/intel-mid/intel_mid_vrtc.c)源代码文件，看起来很简单。首先，这个函数解析[简单固件接口](https://en.wikipedia.org/wiki/Simple_Firmware_Interface)M-Real-Time-Clock表，用于将这些设备送到`sfi_mrtc_array`数组并初始化`set_time`和`get_time`函数：

```C
void __init intel_mid_rtc_init(void)
{
    unsigned long vrtc_paddr;

    sfi_table_parse(SFI_SIG_MRTC, NULL, NULL, sfi_parse_mrtc);

    vrtc_paddr = sfi_mrtc_array[0].phys_addr;
    if (!sfi_mrtc_num || !vrtc_paddr)
        return;

    vrtc_virt_base = (void __iomem *)set_fixmap_offset_nocache(FIX_LNW_VRTC,
                                vrtc_paddr);

    x86_platform.get_wallclock = vrtc_get_time;
    x86_platform.set_wallclock = vrtc_set_mmss;
}
```

就是这样，在此之后，基于“Intel MID”的设备将能够从硬件时钟中获得时间。正如我已经写过的那样，标准PC [x86\_64](https://en.wikipedia.org/wiki/X86-64)架构不支持`x86_init_noop`，在调用此函数时什么都不做。我们刚看到[实时时钟](https://en.wikipedia.org/wiki/Real-time_clock)[Intel MID](https://en.wikipedia.org/wiki/Mobile_Internet_device#Intel_MID_platforms)体系结构，现在是时候回到一般的现在是时候回到`x86_64`体系结构，并将在那里查看时间管理相关的东西。

## 熟悉jiffies

如果我们返回`setup_arch`函数(如你所记得的那样，在[arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/setup.c#L842)源代码文件)，我们看到下次调用时间管理相关函数：

```C
register_refined_jiffies(CLOCK_TICK_RATE);
```

在我们查看此函数的实现之前，我们必须了解[jiffy](https://en.wikipedia.org/wiki/Jiffy_%28time%29)。我们可以在维基百科上阅读：

```
Jiffy是一个非正式术语,表示不具体的非常短暂的时间段
```

这个定义非常类似于Linux内核中的`jiffy`。有一个带有“jiffies”的全局变量，它保存自系统启动以来发生的滴答数。 Linux内核将此变量设置为零：

```C
extern unsigned long volatile __jiffy_data jiffies;
```

在初始化过程中。在定时器中断期间，每次都会增加此全局变量。除此之外，在`jiffies`变量附近我们可以看到类似变量的定义

```C
extern u64 jiffies_64;
```

实际上，Linux内核中只使用其中一个变量，它取决于处理器类型。[x86\_64](https://en.wikipedia.org/wiki/X86-64)平台是`u64`类型,[x86](https://en.wikipedia.org/wiki/X86)平台下是`unsigned long`类型。我们看到这个看[arch/x86/kernel/vmlinux.lds.S](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/vmlinux.lds.S)链接器脚本：

```C
#ifdef CONFIG_X86_32
...
jiffies = jiffies_64;
...
#else
...
jiffies_64 = jiffies;
...
#endif
```

在`x86_32`的情况下，`jiffies`将是`jiffies_64`变量的低32位。我们可以将其想象两个变量的关系，见图示

```
                    jiffies_64
+-----------------------------------------------------+
|                       |                             |
|                       |                             |
|                       |       jiffies on x86_32   |
|                       |                             |
|                       |                             |
+-----------------------------------------------------+
63                     31                             0
```

现在我们知道关于`jiffies`的一点理论，并且可以返回到我们的函数。我们的函数没有特定于体系结构的实现 - `register_refined_jiffies`。此函数位于通用内核代码 - [kernel/time/jiffies.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/kernel/time/jiffies.c)源代码文件中。 `register_refined_jiffies`的要点是jiffy`clockource`的注册。在我们查看`register_refined_jiffies`函数的实现之前，我们必须知道`clocksource`是什么。我们可以在评论中看到：

```C
clocksource是自由运行计数器的硬件抽象。 
```

我不确定，但那个描述并没有对`clocksource`概念有很好的理解。让我们试着理解它是什么，但我们不会更深入，因为这个主题将在更详细的单独部分中描述。 `clocksource`的要点是计时抽象或非常简单的说-它为内核提供时间值。我们已经知道了`jiffies`接口，它表示自系统启动以来发生的滴答数。它由Linux内核中的全局变量表示，并在每个定时器中断后触发其值递增。 Linux内核可以使用`jiffies`进行时间测量。那么为什么我们需要像`clocksource`这样的单独环境？实际上，不同的硬件设备提供其功能不同的不同时钟源。用于时间间隔测量的更精确技术的可用性取决于硬件。

例如，`x86`在芯片上有一个64位计数器，称为[时间戳计数器](https://en.wikipedia.org/wiki/Time_Stamp_Counter)，其频率可以等于处理器频率。或者例如[高精度事件计时器](https://en.wikipedia.org/wiki/High_Precision_Event_Timer)，其由至少`10 MHz`频率的`64位`计数器组成。两个不同的计时器，他们为`x86`提供计时服务。如果我们将添加来自其他架构的计时器，这只会使这个问题变得更加复杂。 Linux内核提供了`clocksource`概念来解决问题。

clocksource概念由Linux内核中的`clocksource`结构表示。此结构在[include/linux/clocksource.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/clocksource.h)头文件中定义，并包含几个描述的字段时间计数器。例如，它包含 -`name`字段，它是计数器的名称，`flags`字段描述了计数器的不同属性，指向`suspend`和`resume`函数的指针等等。

让我们看一下在[kernel/time/jiffies.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/kernel/time/jiffies.c)源中定义的jiffies的`clocksource`结构代码文件：

```C
static struct clocksource clocksource_jiffies = {
    .name       = "jiffies",
    .rating     = 1, 
    .read       = jiffies_read,
    .mask       = CLOCKSOURCE_MASK(32),
    .mult       = TICK_NSEC << JIFFIES_SHIFT, 
    .shift      = JIFFIES_SHIFT,
    .max_cycles = 10,
};
```

* * *

> _备注_：  
> _[.mult 由NSEC\_PER\_JIFFY 改为 TICK\_NSEC](https://github.com/torvalds/linux/commit/93825f2ec736f30e034ab7c9d56b42849c5b00da)_
> 
> _[.mask由 xffffffff 改为宏 CLOCKSOURCE\_MASK(32)](https://github.com/torvalds/linux/commit/232d26373d310a941ef2ab46e53ea62fe076ed13#diff-104a22247b4748b207b1cbf27d4ac27a)_

* * *

我们可以在这里看到默认名称的定义 - `jiffies`。接下来是`rating`字段，它允许通过指定硬件可用的时钟源管理代码选择最佳注册时钟源。 `rating`可能具有以下设置为以下值：

- `1-99` - 仅用于启动和测试目的;
- `100-199` - 实用的功能，但不是理想的。
- `200-299` - 正确可用的clockource。
- `300-399` - 一个相当快速和准确的时钟源。
- `400-499` - 理想的时钟源。必要时必须使用; 例如，[时间戳计数器](https://en.wikipedia.org/wiki/Time_Stamp_Counter)的`rating`为`300`，但[高精度事件计时器](https：//en.wikipedia.org/wiki/High_Precision_Event_Timer)的`rating`是`250`。下一个字段是`read`\- 它是指向允许它读取clocksource的循环值的函数的指针;换句话说，它只返回`u64`类型的`jiffies`变量：

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

* * *

> _备注_：  
> _[函数返回值调整](://github.com/torvalds/linux/commit/2f7a3f8e871eb0713d23c533bd5e44a544e43eb8#diff-c4f68300046af1d0acfac43a7d0c28f2) 由circle\_t ->u64_

* * *

下一个字段是`mask`值，它确保来自非`64位`计数器的计数器值之间的减法不需要特殊的溢出逻辑。在我们的例子中，掩码是 `CLOCKSOURCE_MASK(32)`值为`0xffffffff`并且它是`32`位。这意味着`jiffy`在`42`秒后回绕到零：

```python
>>> 0xffffffff
4294967295
# 42 nanoseconds
>>> 42 * pow(10, -9)
4.2000000000000006e-08
# 43 nanoseconds
>>> 43 * pow(10, -9)
4.3e-08
```

接下来的两个字段`mult`和`shift`用于将clocksource的周期转换为每个周期的纳秒。当内核调用`clocksource.read`函数时，这个函数返回一个`machine`时间单位的值，用我们刚才看到的`u64`数据类型表示。要将此返回值转换为[纳秒](https://en.wikipedia.org/wiki/Nanosecond)，我们需要这两个字段：`mult`和`shift`。 `clocksource`提供`clocksource_cyc2ns`函数，它将使用以下表达式为我们执行：

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

我们可以看到`mult`字段等于：

```C
TICK_NSEC << JIFFIES_SHIFT

#define TICK_NSEC       ((NSEC_PER_SEC+HZ/2)/HZ)
#define NSEC_PER_SEC    1000000000L
```

默认情况下，`shift`是

```C
#if HZ <34
  #define JIFFIES_SHIFT 6
#elif HZ <67
  #define JIFFIES_SHIFT 7
＃else
  #define JIFFIES_SHIFT 8
＃endif
```

`jiffies`时钟源使用`TICK_NSEC`乘数转化为以纳秒为单位的时钟频率。

* * *

> _备注_：  
> _`clocksource.read`读取的机器的时钟滴答为`circle`,纳秒为单位时钟频率为`F`, 每个周期的纳秒为`t`为:_
> 
> ```C
> t = cirle / F
> ```
> 
> _如果使用上述的公式_
> 
> ```C
> t = (cycle * mult) >> shift;
> ```
> 
> _运算只要满足以下公式_
> 
> ```C
> F = (1 << shift) / mult;
> ```
> 
> _而mult值_
> 
> ```C
> mult= TICK_NSEC << shift;
> ```
> 
> _分子等于1, 分母等于一个滴答需要的纳秒数，分别左移shift位，放大${2}^{shift}$倍, 结果为F为单位的频率值_

* * *

注意，`JIFFIES_SHIFT`和`TICK_NSEC`的值取决于`HZ`值。 `HZ`表示系统计时器的频率。这个宏在[include/asm-generic/param.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/asm-generic/param.h)中定义，取决于`CONFIG_HZ`内核配置选项。对于每个支持的体系结构，`HZ`的值不同，但对于`x86`，它的定义如下：

```C
#define HZ CONFIG_HZ
```

`CONFIG_HZ`可以是以下值之一：

![](https://garlicspace.com/wp-content/uploads/2020/04/HZ-1024x608.png)

这意味着在我们的情况下，定时器中断频率为`250HZ`或每秒发生`250`次滴答或每`4ms`发生一次定时器中断。

我们在`clocksource_jiffies`结构的定义中可以看到的最后一个字段是 -`max_cycles`，它保存了最大循环值，可以安全地乘以而不会导致溢出。

好的，我们刚刚看到了`clocksource_jiffies`结构的定义，我们对`jiffies`和`clocksource`了解得很多，现在是时候回到我们函数的实现了。在本部分的开头，我们已经停止了以下的调用：

```C
register_refined_jiffies(CLOCK_TICK_RATE);
```

函数来自[arch/x86/kernel/setup.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/kernel/setup.c#L842)源代码文件。

正如我已经写过的那样，`register_refined_jiffies`函数的主要目的是注册`refined_jiffies` clocksource。我们已经看到`clocksource_jiffies`结构代表了标准的`jiffies`时钟源。现在，如果你查看[kernel/time/jiffies.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/kernel/time/jiffies.c)源代码文件，你会发现另一个时钟源定义：

```C
struct clocksource refined_jiffies;
```

`refined_jiffies`和`clocksource_jiffies`之间有一个区别：基于标准`jiffies`的时钟源是应该在所有系统上运行的最低公分母时钟源。正如我们所知，每次定时器中断都会使得`jiffies`的值递增。这意味着基于标准`jiffies`的时钟源具有与定时器中断频率相同的分辨率。由此我们可以理解，基于标准`jiffies`的时钟源可能会受到不准确的影响。`refined_jiffies`使用`CLOCK_TICK_RATE`作为`jiffies` shift的基础。

我们来看看这个函数的实现。首先，我们可以看到基于`clocksource_jiffies`结构的`refined_jiffies`时钟源：

```C
int register_refined_jiffies(long cycles_per_second)
{
    u64 nsec_per_tick, shift_hz;
    long cycles_per_tick;

    refined_jiffies = clocksource_jiffies;
    refined_jiffies.name = "refined-jiffies";
    refined_jiffies.rating++;
    ...
    ...
    ...
```

在这里我们可以看到，我们将`refined_jiffies`的名称更新为`refined-jiffies`并增加该结构的`rating`。你还记得，`clocksource_jiffies`的`rating`为 - `1`，所以我们的`refined_jiffies` clockource 的`rating`则为 - `2`。这意味着`refined_jiffies`将是时钟源管理的最佳选择。 在下一步中，我们需要计算每个ticket的周期数：

```C
cycles_per_tick = (cycles_per_second + HZ/2)/ HZ;
```

请注意，我们使用`NSEC_PER_SEC`宏作为标准`jiffies`乘数的基础。这里我们使用`cycles_per_second`，这是`register_refined_jiffies`函数的第一个参数。我们已将`CLOCK_TICK_RATE`宏传递给`register_refined_jiffies`函数。该宏在[arch/x86/include/asm/timex.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/include/asm/timex.h)头文件中定义并扩展到：

```C
#define CLOCK_TICK_RATE PIT_TICK_RATE
```

其中`PIT_TICK_RATE`宏扩展到[Intel 8253](https://en.wikipedia.org/wiki/Programmable_interval_timer)的频率：

```C
#define PIT_TICK_RATE 1193182ul
```

在此之后，我们为`register_refined_jiffies`计算`shift_hz`，它的值为`hz << 8`或换句话说系统计时器的频率。我们将`cycles_per_second`或可编程间隔定时器的频率向左移动到`8`以获得额外的准确度：

```C
shift_hz =(u64)cycles_per_second << 8;
shift_hz + = cycles_per_tick/2;
do_div(shift_hz，cycles_per_tick);
```

在下一步中，我们通过将`NSEC_PER_SEC`左移`8`来计算每个刻度的秒数，就像我们使用`shift_hz`一样，并执行与之前相同的计算：

```C
nsec_per_tick =(u64)NSEC_PER_SEC << 8;
nsec_per_tick + =(u32)shift_hz/2;
do_div(nsec_per_tick，(u32)shift_hz);
```

```C
refined_jiffies.mult = ((u32)nsec_per_tick)<< JIFFIES_SHIFT;
```

在`register_refined_jiffies`函数的末尾，我们使用在[include/linux/clocksource.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/clocksource.h)中定义的`__clocksource_register`函数注册新的时钟源头文件并返回：

```C
__clocksource_register(＆refined_jiffies);
return 0;

```

时钟源管理代码提供用于时钟源注册和选择的API。我们可以看到，通过在内核初始化期间调用`__clocksource_register`函数或从内核模块调用时钟源。在注册期间，时钟源管理代码将使用`clocksource.rating`字段选择系统中可用的最佳时钟源，我们在为`jiffies`初始化`clocksource`结构时已经看到了该字段。

## 使用jiffies

我们刚刚在前一段中看到了两个基于`jiffies`的时钟源的初始化：

_standard `jiffies`的时钟源;  
_refined `jiffies`时钟源;

如果您不理解这里的计算，请不要担心。起初他们看起来很可怕。很快，我们会一步一步地学习这些东西。所以，我们刚刚看到基于`jiffies`的时钟源的初始化，我们也知道Linux内核有全局变量`jiffies`，它保存自内核开始工作以来发生的滴答数。现在，让我们看看如何使用它。要使用`jiffies`，我们可以使用`jiffies`全局变量的名称或`get_jiffies_64`函数的调用。此函数在[kernel/time/jiffies.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/kernel/time/jiffies.c)源代码文件中定义，只返回完整的`64位``jiffies`的价值：

```C
u64 get_jiffies_64(void)
{
    unsigned long seq;
    u64 ret;

    do {
        seq = read_seqbegin(&jiffies_lock);
        ret = jiffies_64;
    } while (read_seqretry(&jiffies_lock, seq));
    return ret;
}
EXPORT_SYMBOL(get_jiffies_64);
```

请注意，`get_jiffies_64`函数未实现为`jiffies_read`，例如：

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

我们可以看到`get_jiffies_64`的实现更复杂。使用[seqlocks](https://en.wikipedia.org/wiki/Seqlock)实现`jiffies_64`变量的读取。实际上这是针对无法原子读取完整64位值的机器完成的。 如果我们可以访问`jiffies`或`jiffies_64`变量，我们可以将它转换为`human`时间单位。为了得到一秒，我们可以使用以下表达式：

```C
jiffies / HZ
```

所以，如果我们知道这一点，我们可以获得任何时间单位。例如：

```C
/ *从现在开始三十秒* /
jiffies + 30 * HZ

/ *从现在开始两分钟* /
jiffies + 120 * HZ

/ *从现在起一毫秒* /
jiffies + HZ / 1000
```

就这样。

## 结论

第一部分总结了Linux内核中与时间和时间管理相关的概念。我们首先遇到了两个概念及其初始化：`jiffies`和`clocksource`。在下一部分中，我们将继续深入探讨这个有趣的主题，正如我在本部分已经写过的那样，我们将尝试深入理解Linux内核中这些和其他时间管理概念。

如果您有任何问题或建议，请随时在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)。**

## 链接

- [system call](https://en.wikipedia.org/wiki/System_call)
- [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol)
- [lock validator](https://www.kernel.org/doc/Documentation/locking/lockdep-design.txt)
- [cgroups](https://en.wikipedia.org/wiki/Cgroups)
- [bss](https://en.wikipedia.org/wiki/.bss)
- [initrd](https://en.wikipedia.org/wiki/Initrd)
- [Intel MID](https://en.wikipedia.org/wiki/Mobile_Internet_device#Intel_MID_platforms)
- [TSC](https://en.wikipedia.org/wiki/Time_Stamp_Counter)
- [void](https://en.wikipedia.org/wiki/Void_type)
- [Simple Firmware Interface](https://en.wikipedia.org/wiki/Simple_Firmware_Interface)
- [x86\_64](https://en.wikipedia.org/wiki/X86-64)
- [real time clock](https://en.wikipedia.org/wiki/Real-time_clock)
- [Jiffy](https://en.wikipedia.org/wiki/Jiffy_%28time%29)
- [high precision event timer](https://en.wikipedia.org/wiki/High_Precision_Event_Timer)
- [nanoseconds](https://en.wikipedia.org/wiki/Nanosecond)
- [Intel 8253](https://en.wikipedia.org/wiki/Intel_8253)
- [seqlocks](https://en.wikipedia.org/wiki/Seqlock)
- [cloksource documentation](https://www.kernel.org/doc/Documentation/timers/timekeeping.txt)
- [Previous chapter](https://0xax.gitbooks.io/linux-insides/content/SysCall/index.html)

