# Timers and time management in the Linux kernel. Part 7.
Date: 2020-06-07


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

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

## Linux内核中与时间相关的系统调用

这是第七章也是最后一章[chapter](https://0xax.gitbook.io/linux-insides/summary/timers/), 它描述了Linux内核中计数器和时间管理相关的内容. 在前面的章节 [part](https://0xax.gitbook.io/linux-insides/summary/timers/linux-timers-6), 我们在 [x86\_64](https://en.wikipedia.org/wiki/X86-64): [High Precision Event Timer](https://en.wikipedia.org/wiki/High_Precision_Event_Timer) 和[Time Stamp Counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter). 内部时间管理是Linux内核中一个有趣的部分, 但是，当然，不仅内核需要`time`概念. 我们的程序还需要知道时间。这一部分中，我们将考虑实际一些与时间管理相关的 [system calls](https://en.wikipedia.org/wiki/System_call). 这些系统调用:

- `clock_gettime`;
- `gettimeofday`;
- `nanosleep`.

我们将从一个简单的用户空间[C](https://en.wikipedia.org/wiki/C_%28programming_language%29) 程序开始, 并从[standard library](https://en.wikipedia.org/wiki/Standard_library) 函数执行某些系统调用。由于每个 [architecture](https://github.com/torvalds/linux/tree/master/arch) 提供了自己的某些系统调用实现,因此我们将仅考虑[x86\_64](https://en.wikipedia.org/wiki/X86-64)系统调用的特定实现，因为本书与此体系结构相关。

Additionally, we will not consider the concept of system calls in this part, but only implementations of these three system calls in the Linux kernel. If you are interested in what is a `system call`, there is a special [chapter](https://0xax.gitbook.io/linux-insides/summary/syscall) about this.

此外, 在本部分中， 我们将不考虑系统调用的的概念, 而仅考虑这三个系统调用Linux内核中的实现, 如果你对`system call`感兴趣， 可以参考这个章节 [chapter](https://0xax.gitbook.io/linux-insides/summary/syscall) about this.

因此，让我们从`gettimeofday`系统调用开始。

## `gettimeofday` 系统调用的实现

从名称`gettimeofday`可以理解，该函数返回当前时间。 首先，让我们看下面的简单示例：

```C
#include <time.h>
#include <sys/time.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    char buffer[40];
    struct timeval time;

    gettimeofday(&time, NULL);

    strftime(buffer, 40, "Current date/time: %m-%d-%Y/%T", localtime(&time.tv_sec));
    printf("%s\n",buffer);

    return 0;
}
```

如您所见，这里我们调用`gettimeofday`函数，该函数带有两个参数。 第一个参数是指向`timeval`结构的指针，它表示经过的时间：

```C
struct timeval {
    time_t      tv_sec;     /* seconds */
    suseconds_t tv_usec;    /* microseconds */
};
```

函数`gettimeofday`的第二个参数是指向表示时区的`timezone`结构的指针。 在我们的示例中，我们将`timeval time`的地址传递给`gettimeofday`函数，Linux内核填充给定的`timeval`结构并将其返回给我们。 另外，我们使用`strftime`函数设置时间格式，以获得比已经经过的微秒可具有可读性。 让我们看一下结果：

```C
~$ gcc date.c -o date
~$ ./date
Current date/time: 03-26-2016/16:42:02
```

你可能已经知道, 用户空间应用程序不会直接从内核空间调用系统调用。 在调用实际的系统调用条目之前，我们从标准库中调用一个函数 , 这个例子他使用的是[glibc](https://en.wikipedia.org/wiki/GNU_C_Library), `gettimeofday` 函数位于 [sysdeps/unix/sysv/linux/x86/gettimeofday.c](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/x86/gettimeofday.c;h=36f7c26ffb0e818709d032c605fec8c4bd22a14e;hb=HEAD) 文件中。 . `gettimeofday` 不是通常的系统调用。 它位于成为`VDSO`的特殊区域中 (你可以在这里[part](https://0xax.gitbook.io/linux-insides/summary/syscall/linux-syscall-3), 了解更多的相关信息)。

`gettimeofday`的`glibc`实现试图解析给定的符号;在我们的例子中，通过内部函数 `_dl_vdso_vsym` 调用， 该符号为`__vdso_gettimeofday`。如果符号无法解析， 返回`NULL`, 我们将回退到常规的系统调用。

```C
return (_dl_vdso_vsym ("__vdso_gettimeofday", &linux26)
  ?: (void*) (&__gettimeofday_syscall));
```

`gettimeofday`入口位于 [arch/x86/entry/vdso/vclock\_gettime.c](https://github.com/torvalds/linux/blob/master/arch/x86/entry/vdso/vclock_gettime.c) 源文件中， 我们能看到函数`gettimeofday` 的弱应用定义 `__vdso_gettimeofday`:

```C
int gettimeofday(struct timeval *, struct timezone *)
    __attribute__((weak, alias("__vdso_gettimeofday")));
```

`__vdso_gettimeofday`中定义`/lib/vdso/gettimeofday.c`中，如果给定的`timeval`不为null，则调用`do_hres`函数：

```C
static __maybe_unused int
__cvdso_gettimeofday_data(const struct vdso_data *vd,
              struct __kernel_old_timeval *tv, struct timezone *tz)
{

    if (likely(tv != NULL)) {
        struct __kernel_timespec ts;

        if (do_hres(&vd[CS_HRES_COARSE], CLOCK_REALTIME, &ts))
            return gettimeofday_fallback(tv, tz);

        tv->tv_sec = ts.tv_sec;
        tv->tv_usec = (u32)ts.tv_nsec / NSEC_PER_USEC;
    }

    if (unlikely(tz != NULL)) {
        if (IS_ENABLED(CONFIG_TIME_NS) &&
            vd->clock_mode == VDSO_CLOCKMODE_TIMENS)
            vd = __arch_get_timens_vdso_data();

        tz->tz_minuteswest = vd[CS_HRES_COARSE].tz_minuteswest;
        tz->tz_dsttime = vd[CS_HRES_COARSE].tz_dsttime;
    }

    return 0;
}

```

如果 `do_hres` 调用失败, 通过调用`_gettimeofday_fallback`，实现系统调用

```C
static __always_inline
long gettimeofday_fallback(struct __kernel_old_timeval *_tv,
               struct timezone *_tz)
{
    long ret;

    asm("syscall" : "=a" (ret) :
        "0" (__NR_gettimeofday), "D" (_tv), "S" (_tz) : "memory");

    return ret;
}

```

`do_hres` 函数从`vdso_data`中获取时间数据, `vdso_data` 定义在[/include/vdso/datapage.h](https://github.com/torvalds/linux/blob/master/include/vdso/datapage.h) 头文件中, 包含了`vdso_timestamp` 结构的映射以及与系统当前时钟源相关的两个字段。 该函数用来自`vdso_data`中的`vdso_timestamp`的值填充给定的timeval结构，该值包含与时间相关的数据，该数据通过计时器中断进行更新。

> 另外在5.7版本中可以看到 `CONFIG_TIME_NS`: TIME namespace, 支持从中获取时间信息 这里使用的clockid 为`CLOCK_REALTIME`

```C
...
while (unlikely((seq = READ_ONCE(vd->seq)) & 1)) {
    if (IS_ENABLED(CONFIG_TIME_NS) &&
        vd->clock_mode == VDSO_CLOCKMODE_TIMENS)
        return do_hres_timens(vd, clk, ts);
    cpu_relax();
}
...

static int do_hres_timens(const struct vdso_data *vdns, clockid_t clk,
              struct __kernel_timespec *ts)
{
    const struct vdso_data *vd = __arch_get_timens_vdso_data();
    const struct timens_offset *offs = &vdns->offset[clk];
    const struct vdso_timestamp *vdso_ts;
    u64 cycles, last, ns;
    u32 seq;
    s64 sec;

    if (clk != CLOCK_MONOTONIC_RAW)
        vd = &vd[CS_HRES_COARSE];
    else
        vd = &vd[CS_RAW];
    vdso_ts = &vd->basetime[clk];

    do {
        seq = vdso_read_begin(vd);

        if (unlikely(!vdso_clocksource_ok(vd)))
            return -1;

        cycles = __arch_get_hw_counter(vd->clock_mode);
        ns = vdso_ts->nsec;
        last = vd->cycle_last;
        ns += vdso_calc_delta(cycles, last, vd->mask, vd->mult);
        ns = vdso_shift_ns(ns, vd->shift);
        sec = vdso_ts->sec;
    } while (unlikely(vdso_read_retry(vd, seq)));

    /* Add the namespace offset */
    sec += offs->sec;
    ns += offs->nsec;

    /*
     * Do this outside the loop: a race inside the loop could result
     * in __iter_div_u64_rem() being extremely slow.
     */
    ts->tv_sec = sec + __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
    ts->tv_nsec = ns;

    return 0;
}
```

首先，获取`vdso_data`中的`vdso_timestamp`信息, 通过一系列运算，计算出最终结果。

```C
...
const struct vdso_timestamp *vdso_ts = &vd->basetime[clk];
...
do {
    while (unlikely((seq = READ_ONCE(vd->seq)) & 1)) {
        if (IS_ENABLED(CONFIG_TIME_NS) &&
            vd->clock_mode == VDSO_CLOCKMODE_TIMENS)
            return do_hres_timens(vd, clk, ts);
        cpu_relax();
    }
    smp_rmb();

    if (unlikely(!vdso_clocksource_ok(vd)))
        return -1;

    cycles = __arch_get_hw_counter(vd->clock_mode);
    ns = vdso_ts->nsec;
    last = vd->cycle_last;
    ns += vdso_calc_delta(cycles, last, vd->mask, vd->mult);
    ns = vdso_shift_ns(ns, vd->shift);
    sec = vdso_ts->sec;
} while (unlikely(vdso_read_retry(vd, seq)));

ts->tv_sec = sec + __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
ts->tv_nsec = ns;

```

当我们访问时，我们用`vdso_timestamp.sec`赋值`ts->tv_sec`, 以秒为单位存储当前时间 , 在Linux内核中的计时子系统初始化期间从实时时钟[real time clock](https://en.wikipedia.org/wiki/Real-time_clock) 获得，值相同，但以纳秒为单位。在这段代码的最后，我们只使用结果值填充给定`__kernel_timespec` 的结构。

这是关于 `gettimeofday` 下面介绍`clock_gettime`.

## clock\_gettime 系统调用的实现

`clock_gettime`通过第二个参数获取指定的时间。 通常 `clock_gettime`有两个参数:

- `clk_id` - 时钟标识符;
- `timespec` - `timespec` 结构指针,也就是获取当前时间.

下面看一个简单的例子:

```C
#include <time.h>
#include <sys/time.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    struct timespec elapsed_from_boot;

    clock_gettime(CLOCK_BOOTTIME, &elapsed_from_boot);

    printf("%d - seconds elapsed from boot\n", elapsed_from_boot.tv_sec);

    return 0;
}
```

打印 `uptime` 信息, 从启动到现在经过的秒数:

```C
~$ gcc uptime.c -o uptime
~$ ./uptime
14180 - seconds elapsed from boot
```

我们也可以通过 [uptime](https://en.wikipedia.org/wiki/Uptime#Using_uptime) 获取相关结果:

```
~$ uptime
up  3:56
```

`elapsed_from_boot.tv_sec` 表示从启动到现在过去的时间, 因此:

```python
>>> 14180 / 60
236
>>> 14180 / 60 / 60
3
>>> 14180 / 60 % 60
56
```

`clock_id` 可以设置以下值:

- `CLOCK_REALTIME` - system wide clock which measures real or wall-clock time;
- `CLOCK_REALTIME_COARSE` - faster version of the `CLOCK_REALTIME`;
- `CLOCK_MONOTONIC` - represents monotonic time since some unspecified starting point;
- `CLOCK_MONOTONIC_COARSE` - faster version of the `CLOCK_MONOTONIC`;
- `CLOCK_MONOTONIC_RAW` - the same as the `CLOCK_MONOTONIC` but provides non [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol) adjusted time.
- `CLOCK_BOOTTIME` - the same as the `CLOCK_MONOTONIC` but plus time that the system was suspended;
- `CLOCK_PROCESS_CPUTIME_ID` - per-process time consumed by all threads in the process;
- `CLOCK_THREAD_CPUTIME_ID` - thread-specific clock.

`clock_gettime`也不是通常的系统调用，但是作为`gettimeofday`，此系统调用位于vDSO区域中。 该系统调用的条目位于与`gettimeofday`相同的源代码文件中- [/arch/x86/entry/vdso/vclock\_gettime.c](https://github.com/torvalds/linux/blob/master/arch/x86/entry/vdso/vclock_gettime.c))\`.

> The Implementation of the `clock_gettime` depends on the clock id. If we have passed the `CLOCK_REALTIME` clock id, the `do_realtime` function will be called:

`clock_gettime`的实现取决于时钟id。`__vdso_clock_gettime`调用`__cvdso_clock_gettime` , 该函数位于`/lib/vdso/gettimeofday.c`, 之后调用`__cvdso_clock_gettime_common`， 如果处理失败通过`clock_gettime_fallback`系统调用方式完成

```C
static __always_inline
long clock_gettime_fallback(clockid_t _clkid, struct __kernel_timespec *_ts)
{
    long ret;

    asm ("syscall" : "=a" (ret), "=m" (*_ts) :
         "0" (__NR_clock_gettime), "D" (_clkid), "S" (_ts) :
         "rcx", "r11");

    return ret;
}

```

> 位于`/arch/x86/include/asm/vdso/gettimeofday.h`

函数根据clockid的被范围三类 `VDSO_HRES`, `VDSO_COARSE`, `VDSO_RAW`, 针对`VDSO_COARSE`调用`do_corase` 函数, 其他调用`do_hres`

```C
#define VDSO_BASES (CLOCK_TAI + 1)
#define VDSO_HRES  (BIT(CLOCK_REALTIME)        | \
             BIT(CLOCK_MONOTONIC)       | \
             BIT(CLOCK_BOOTTIME)        | \
             BIT(CLOCK_TAI))
#define VDSO_COARSE    (BIT(CLOCK_REALTIME_COARSE) | \
             BIT(CLOCK_MONOTONIC_COARSE))
#define VDSO_RAW   (BIT(CLOCK_MONOTONIC_RAW))

#define CS_HRES_COARSE 0
#define CS_RAW     1
```

> 位于 `/include/vdso/datapage.h`

```C
static __maybe_unused int
__cvdso_clock_gettime_common(const struct vdso_data *vd, clockid_t clock,
                 struct __kernel_timespec *ts)
{
    u32 msk;

    /* Check for negative values or invalid clocks */
    if (unlikely((u32) clock >= MAX_CLOCKS))
        return -1;

    /*
     * Convert the clockid to a bitmask and use it to check which
     * clocks are handled in the VDSO directly.
     */
    msk = 1U << clock;
    if (likely(msk & VDSO_HRES))
        vd = &vd[CS_HRES_COARSE];
    else if (msk & VDSO_COARSE)
        return do_coarse(&vd[CS_HRES_COARSE], clock, ts);
    else if (msk & VDSO_RAW)
        vd = &vd[CS_RAW];
    else
        return -1;

    return do_hres(vd, clock, ts);
}
```

可以看到 `do_coarse`处理流程和`do_hres`，流程类似，都是先判度是否使用名字空间存储时间信息， 满足则调用`do_{}_timens`

```C
static __always_inline int do_coarse(const struct vdso_data *vd, clockid_t clk,
                     struct __kernel_timespec *ts)
{
    const struct vdso_timestamp *vdso_ts = &vd->basetime[clk];
    u32 seq;

    do {
        while ((seq = READ_ONCE(vd->seq)) & 1) {
            if (IS_ENABLED(CONFIG_TIME_NS) &&
                vd->clock_mode == VDSO_CLOCKMODE_TIMENS)
                return do_coarse_timens(vd, clk, ts);
            cpu_relax();
        }
        smp_rmb();

        ts->tv_sec = vdso_ts->sec;
        ts->tv_nsec = vdso_ts->nsec;
    } while (unlikely(vdso_read_retry(vd, seq)));

    return 0;
}

```

`do_coarse_timens`与`do_hres_timens` 非常类似

```C
static int do_coarse_timens(const struct vdso_data *vdns, clockid_t clk,
                struct __kernel_timespec *ts)
{
    const struct vdso_data *vd = __arch_get_timens_vdso_data();
    const struct vdso_timestamp *vdso_ts = &vd->basetime[clk];
    const struct timens_offset *offs = &vdns->offset[clk];
    u64 nsec;
    s64 sec;
    s32 seq;

    do {
        seq = vdso_read_begin(vd);
        sec = vdso_ts->sec;
        nsec = vdso_ts->nsec;
    } while (unlikely(vdso_read_retry(vd, seq)));

    sec += offs->sec;
    nsec += offs->nsec;

    ts->tv_sec = sec + __iter_div_u64_rem(nsec, NSEC_PER_SEC, &nsec);
    ts->tv_nsec = nsec;
    return 0;
}
```

在上一节描述了`gettimeofday`函数实现的一些信息。 这里只有一个区别，我们的`gettimeofday`默认使用clockid `CLOCK_REALTIME`, `clock_gettime`的`clockid`则是可以指定的.如果`vdso`调用成功， 则这两个函数结果是一致的。

> vdso的值是在 [tick](https://elixir.bootlin.com/linux/latest/source/kernel/time/tick-common.c#L558) , 通过`timekeeping_xxx`更新vdso中的相关时间信息

## `nanosleep` 系统调用的实现

我们列表中的最后一个系统调用是`nanosleep`。 从名称可以理解，此功能提供了`sleeping`功能。 让我们看下面的简单示例：

```C
#include <time.h>
#include <stdlib.h>
#include <stdio.h>

int main (void)
{    
   struct timespec ts = {5,0};

   printf("sleep five seconds\n");
   nanosleep(&ts, NULL);
   printf("end of sleep\n");

   return 0;
}
```

编译执行得到如下结果:

```
~$ gcc sleep_test.c -o sleep
~$ ./sleep
sleep five seconds
end of sleep
```

第二行`5`秒后显示。

`nanosleep`与`gettimeofday`和`clock_gettime`函数不同，没有位于`VDSO`区域，因此,下面分析下标准库是如何调用位于内核空间的系统调用的。。`nanosleep`系统调用的实现将通过 [syscall](http://www.felixcloutier.com/x86/SYSCALL.html) 指令实现。在执行 `syscall` 指令之前将参数按照 [System V Application Binary Interface](http://www.x86-64.org/documentation/abi.pdf)接口描述:, 传入处理器的[registers](https://en.wikipedia.org/wiki/Processor_register) (寄存器)中。

- `rdi` - first parameter;
- `rsi` - second parameter;
- `rdx` - third parameter;
- `r10` - fourth parameter;
- `r8` - fifth parameter;
- `r9` - sixth parameter.

`nanosleep` 有两个参数, 两个`timespec` 结构指针.系统调用暂停调用线程， 直到给定的超时时间过去未为止. 此外，如果信号中断其执行, 两个参数，第一个代表其睡眠时间，第二个参数代表其剩余未睡眠时间。

```C
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

void donothing()
{
  return;
}

int main (void)
{
  struct timespec ts = { 10, 0 };
  struct timespec ts2 = { 5, 0 };

  signal(SIGINT, donothing);
  printf ("sleep five seconds\n");
  nanosleep (&ts, &ts2);

  printf("ts { %d %d }\n", ts.tv_sec, ts.tv_nsec);
  printf("ts2{ %d %d }\n", ts2.tv_sec, ts2.tv_nsec);
  printf ("end of sleep\n");

  return 0;
}

```

编译一下, 按一下 CTRL+C， 可以看到数据结果；

```
~$ gcc sleep_test2.c -o sleep2
~$ ./sleep2
sleep five seconds
^Cts { 10 0 }
ts2{ 9 471858528 }
end of sleep
```

`nanosleep` 有两个参数:

```C
int nanosleep(const struct timespec *req, struct timespec *rem);
```

要调用系统调用，我们需要将`req`放入`rdi`寄存器，并将`rem`参数放入`rsi`寄存器。 glibc在[sysdeps/unix/sysv/linux/x86\_64/sysdep.h](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/x86_64/sysdep.h;h=d023d68174d3dfb4e698160b31ae31ad291802e1;hb=HEAD)头文件中的INTERNAL\_SYSCALL宏中完成这些工作。

```C
# define INTERNAL_SYSCALL(name, err, nr, args...) \
  INTERNAL_SYSCALL_NCS (__NR_##name, err, nr, ##args)
```

其中包含系统调用的名称`__NR_##name`，在执行系统调用期间可能出现的错误`err`，系统调用的编号`nr`(可以在[system calls table](https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl)中找到的所有`X86_64`的系统调用)和某些系统调用的参数`##args`。 `INTERNAL_SYSCALL`宏只是扩展到`INTERNAL_SYSCALL_NCS`宏的调用，该宏准备系统调用的参数（以正确的顺序将其放入处理器寄存器），执行`syscall`指令并返回结果：

```C
# define INTERNAL_SYSCALL_NCS(name, err, nr, args...)      \
  ({                                                                          \
    unsigned long int resultvar;                                              \
    LOAD_ARGS_##nr (args)                                                     \
    LOAD_REGS_##nr                                                            \
    asm volatile (                                                            \
    "syscall\n\t"                                                             \
    : "=a" (resultvar)                                                        \
    : "0" (name) ASM_ARGS_##nr : "memory", REGISTERS_CLOBBERED_BY_SYSCALL);   \
    (long int) resultvar; })
```

宏 `LOAD_ARGS_##nr` 调用 宏`LOAD_ARGS_N` , 其中 `N` 是系统调用的参数.在这个例子里是 `LOAD_ARGS_2`. 这些宏扩展未一下内容: `rdi`设置为第一个参数， `rsi`设置为第二个参数.

```C
# define LOAD_REGS_TYPES_1(t1, a1)                     \
  register t1 _a1 asm ("rdi") = __arg1;                    \
  LOAD_REGS_0

# define LOAD_REGS_TYPES_2(t1, a1, t2, a2)                 \
  register t2 _a2 asm ("rsi") = __arg2;                    \
  LOAD_REGS_TYPES_1(t1, a1)
...
...
...
```

在执行`syscal`l指令后，将进行上下文切换[context switch](https://en.wikipedia.org/wiki/Context_switch) ，并且内核会将执行转移到系统调用处理程序。 `nanosleep`系统调用的系统调用处理程序位于 [kernel/time/hrtimer.c](https://github.com/torvalds/linux/blob/master/kernel/time/hrtimer.c) 源代码文件中，并使用SYSCALL\_DEFINE2宏进行了函数定义：

```C
#ifdef CONFIG_64BIT

SYSCALL_DEFINE2(nanosleep, struct __kernel_timespec __user *, rqtp,
        struct __kernel_timespec __user *, rmtp)
{
    struct timespec64 tu;

    if (get_timespec64(&tu, rqtp))
        return -EFAULT;

    if (!timespec64_valid(&tu))
        return -EINVAL;

    current->restart_block.nanosleep.type = rmtp ? TT_NATIVE : TT_NONE;
    current->restart_block.nanosleep.rmtp = rmtp;
    return hrtimer_nanosleep(timespec64_to_ktime(tu), HRTIMER_MODE_REL,
                 CLOCK_MONOTONIC);
}

#endif
```

您可以在有关系统调用的章节 [chapter](https://0xax.gitbook.io/linux-insides/summary/syscall) 中阅读有关`SYSCALL_DEFINE2`宏的更多信息。 如果我们看一下`nanosleep`系统调用的实现，首先我们将看到它是从`get_timespec64`函数的调用开始的。 此函数将给定的数据从用户空间复制到内核空间, 并判断是否是`32bit`进程的系统该调用()。 在我们的例子中，我们将超时值复制到睡眠超时值到内核空间`timespec64`结构，并通过调用`timespec64_valid`函数来检查给定的值是否有效：

```C
#define NSEC_PER_SEC   1000000000L

static inline bool timespec64_valid(const struct timespec64 *ts)
{
    if (ts->tv_sec < 0)
        return false;
    if ((unsigned long)ts->tv_nsec >= NSEC_PER_SEC)
        return false;
    return true;
}
```

它只是检查 `timespec64` 不代表`1970`年之前的日期, 纳秒不会溢出`1` 秒。 `nanosleep` 函数最终会调用同文件中的`hrtimer_nanosleep`, `hrtimer_nanosleep` 函数创建一个计时器 [timer](https://0xax.gitbook.io/linux-insides/summary/timers/linux-timers-4) 并调用 `do_nanosleep` 函数. `do_nanosleep`函数做主要的工作， 这个函数提供循环处理:

```C
do {
    set_current_state(TASK_INTERRUPTIBLE);
    hrtimer_start_expires(&t->timer, mode);

    if (likely(t->task))
        freezable_schedule();

} while (t->task && !signal_pending(current));

__set_current_state(TASK_RUNNING);
return t->task == NULL;
```

```C
do {
    set_current_state(TASK_INTERRUPTIBLE);
    hrtimer_sleeper_start_expires(t, mode);

    if (likely(t->task))
        freezable_schedule();

    hrtimer_cancel(&t->timer);
    mode = HRTIMER_MODE_ABS;

} while (t->task && !signal_pending(current));
```

在睡眠期间冻结当前任务。 在为当前任务设置`TASK_INTERRUPTIBLE`标志之后，`hrtimer_sleeper_start_expires`函数将在当前处理器上启动高精度的时器。 如果给定的高精度计时器将过期，因此该任务将再次运行。

## 小结

这是本章[chapter](https://0xax.gitbook.io/linux-insides/summary/timers/) 第七部分的结尾，它描述了Linux内核中与计时器和计时器管理相关的内容。 在上一部分中，我们看到了[x86\_64](https://en.wikipedia.org/wiki/X86-64)特定的时钟源。 如我一开始所写，这部分是本章的最后部分。 在本章中，我们看到了与时间管理相关的重要概念，例如`clocksource`和`clockevents`框架，`jiffies`计数器等。 当然，这并不涵盖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)
- [C programming language](https://en.wikipedia.org/wiki/C_%28programming_language%29)
- [standard library](https://en.wikipedia.org/wiki/Standard_library)
- [glibc](https://en.wikipedia.org/wiki/GNU_C_Library)
- [real time clock](https://en.wikipedia.org/wiki/Real-time_clock)
- [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol)
- [nanoseconds](https://en.wikipedia.org/wiki/Nanosecond)
- [register](https://en.wikipedia.org/wiki/Processor_register)
- [System V Application Binary Interface](http://www.x86-64.org/documentation/abi.pdf)
- [context switch](https://en.wikipedia.org/wiki/Context_switch)
- [Introduction to timers in the Linux kernel](https://0xax.gitbook.io/linux-insides/summary/timers/linux-timers-4)
- [uptime](https://en.wikipedia.org/wiki/Uptime#Using_uptime)
- [system calls table for x86\_64](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/arch/x86/entry/syscalls/syscall_64.tbl)
- [High Precision Event Timer](https://en.wikipedia.org/wiki/High_Precision_Event_Timer)
- [Time Stamp Counter](https://en.wikipedia.org/wiki/Time_Stamp_Counter)
- [x86\_64](https://en.wikipedia.org/wiki/X86-64)
- [previous part](https://0xax.gitbook.io/linux-insides/summary/timers/linux-timers-6)

