github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/vdso/vdso_time.cc (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include "vdso/vdso_time.h"
    16  
    17  #include <stdint.h>
    18  #include <sys/time.h>
    19  #include <time.h>
    20  
    21  #include "vdso/cycle_clock.h"
    22  #include "vdso/seqlock.h"
    23  #include "vdso/syscalls.h"
    24  
    25  // struct params defines the layout of the parameter page maintained by the
    26  // kernel (i.e., sentry).
    27  //
    28  // This is similar to the VVAR page maintained by the normal Linux kernel for
    29  // its VDSO, but it has a different layout.
    30  //
    31  // It must be kept in sync with VDSOParamPage in pkg/sentry/kernel/vdso.go.
    32  struct params {
    33    uint64_t seq_count;
    34  
    35    uint64_t monotonic_ready;
    36    int64_t monotonic_base_cycles;
    37    int64_t monotonic_base_ref;
    38    uint64_t monotonic_frequency;
    39  
    40    uint64_t realtime_ready;
    41    int64_t realtime_base_cycles;
    42    int64_t realtime_base_ref;
    43    uint64_t realtime_frequency;
    44  };
    45  
    46  // Returns a pointer to the global parameter page.
    47  //
    48  // This page lives in the page just before the VDSO binary itself. The linker
    49  // defines _params as the page before the VDSO.
    50  //
    51  // Ideally, we'd simply declare _params as an extern struct params.
    52  // Unfortunately various combinations of old/new versions of gcc/clang and
    53  // gold/bfd struggle to generate references to such a global without generating
    54  // relocations.
    55  //
    56  // So instead, we use inline assembly with a construct that seems to have wide
    57  // compatibility across many toolchains.
    58  #if __x86_64__
    59  
    60  inline struct params* get_params() {
    61    struct params* p = nullptr;
    62    asm("leaq _params(%%rip), %0" : "=r"(p) : :);
    63    return p;
    64  }
    65  
    66  #elif __aarch64__
    67  
    68  inline struct params* get_params() {
    69    struct params* p = nullptr;
    70    asm("adr %0, _params" : "=r"(p) : :);
    71    return p;
    72  }
    73  
    74  #else
    75  #error "unsupported architecture"
    76  #endif
    77  
    78  namespace vdso {
    79  
    80  const uint64_t kNsecsPerSec = 1000000000UL;
    81  
    82  inline struct timespec ns_to_timespec(uint64_t ns) {
    83    struct timespec ts;
    84    ts.tv_sec = ns / kNsecsPerSec;
    85    ts.tv_nsec = ns % kNsecsPerSec;
    86    return ts;
    87  }
    88  
    89  inline uint64_t cycles_to_ns(uint64_t frequency, uint64_t cycles) {
    90    uint64_t mult = (kNsecsPerSec << 32) / frequency;
    91    return ((unsigned __int128)cycles * mult) >> 32;
    92  }
    93  
    94  // ClockRealtime() is the VDSO implementation of clock_gettime(CLOCK_REALTIME).
    95  int ClockRealtime(struct timespec* ts) {
    96    struct params* params = get_params();
    97    uint64_t seq;
    98    uint64_t ready;
    99    int64_t base_ref;
   100    int64_t base_cycles;
   101    uint64_t frequency;
   102    int64_t now_cycles;
   103  
   104    do {
   105      seq = read_seqcount_begin(&params->seq_count);
   106      ready = params->realtime_ready;
   107      base_ref = params->realtime_base_ref;
   108      base_cycles = params->realtime_base_cycles;
   109      frequency = params->realtime_frequency;
   110      now_cycles = cycle_clock();
   111    } while (read_seqcount_retry(&params->seq_count, seq));
   112  
   113    if (!ready) {
   114      // The sandbox kernel ensures that we won't compute a time later than this
   115      // once the params are ready.
   116      return sys_clock_gettime(CLOCK_REALTIME, ts);
   117    }
   118  
   119    int64_t delta_cycles =
   120        (now_cycles < base_cycles) ? 0 : now_cycles - base_cycles;
   121    int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles);
   122    *ts = ns_to_timespec(now_ns);
   123    return 0;
   124  }
   125  
   126  // ClockMonotonic() is the VDSO implementation of
   127  // clock_gettime(CLOCK_MONOTONIC).
   128  int ClockMonotonic(struct timespec* ts) {
   129    struct params* params = get_params();
   130    uint64_t seq;
   131    uint64_t ready;
   132    int64_t base_ref;
   133    int64_t base_cycles;
   134    uint64_t frequency;
   135    int64_t now_cycles;
   136  
   137    do {
   138      seq = read_seqcount_begin(&params->seq_count);
   139      ready = params->monotonic_ready;
   140      base_ref = params->monotonic_base_ref;
   141      base_cycles = params->monotonic_base_cycles;
   142      frequency = params->monotonic_frequency;
   143      now_cycles = cycle_clock();
   144    } while (read_seqcount_retry(&params->seq_count, seq));
   145  
   146    if (!ready) {
   147      // The sandbox kernel ensures that we won't compute a time later than this
   148      // once the params are ready.
   149      return sys_clock_gettime(CLOCK_MONOTONIC, ts);
   150    }
   151  
   152    int64_t delta_cycles =
   153        (now_cycles < base_cycles) ? 0 : now_cycles - base_cycles;
   154    int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles);
   155    *ts = ns_to_timespec(now_ns);
   156    return 0;
   157  }
   158  
   159  }  // namespace vdso