Skip to content
Snippets Groups Projects
Commit 8dffa912 authored by Nadav Har'El's avatar Nadav Har'El Committed by Pekka Enberg
Browse files

clock: add monotonic uptime clock


This patch starts to solve both issue #142 ("Support MONOTONIC_CLOCK")
and issue #81 (use <chrono> for time).

First, it adds an uptime() function to the "clock" interface, and
implements it for kvm/xen/hpet by returning the system time from which
we subtract the system time at boot (but not adding any correction
for wallclock).

Second, it adds a new std::chrono-based interface to this clock, in
a new header file <osv/clock.hh>. Instead of the old-style
clock::get()->uptime(), one should prefer osv::clock::uptime::now().
This returns a std::chrono::time_point which is type-safe, in the
sense that: 1. It knows what its epoch is (i.e., that it belongs to
osv::clock::uptime), and 2. It knows what its units are (nanoseconds).
This allows the compiler to prevent a user from confusing measurements
from this clock with those from other clocks, or making mistakes in
its units.

Third, this patch implements clock_gettime(MONOTONIC_CLOCK), using
the new osv::clock::uptime::now().

Note that though the new osv::clock::uptime is almost identical to
std::chrono::steady_clock, they should not be confused. The former is
actually OSv's implementation of the latter: steady_clock is implemented
by the C++11 standard library using the Posix clock_gettime, and that
is implemented (in this patch) using osv::clock::uptime.

With this patch, we're *not* done with either issues #142 or #81.
For issue #142, i.e., for supporting MONOTONIC_CLOCK in timerfd, we
need OSv's timers to work on uptime(), not on clock::get()->time().
For issue #81, we should add a osv::clock::wall type too (similar to
what clock::get()->time() does today, but more correctly), and use either
osv::clock::wall or osv::clock::uptime everywhere that
clock::get()->time() is currently used in the code.
clock::get()->time() should be removed.

Reviewed-by: default avatarGlauber Costa <glommer@cloudius-systems.com>
Signed-off-by: default avatarNadav Har'El <nyh@cloudius-systems.com>
Signed-off-by: default avatarPekka Enberg <penberg@cloudius-systems.com>
parent 5c68e049
No related branches found
No related tags found
No related merge requests found
/* /*
* Copyright (C) 2013 Cloudius Systems, Ltd. * Copyright (C) 2013-2014 Cloudius Systems, Ltd.
* *
* This work is open source software, licensed under the terms of the * This work is open source software, licensed under the terms of the
* BSD license as described in the LICENSE file in the top-level directory. * BSD license as described in the LICENSE file in the top-level directory.
...@@ -10,12 +10,56 @@ ...@@ -10,12 +10,56 @@
#include <osv/types.h> #include <osv/types.h>
/**
* OSv low-level time-keeping interface
*
* This is an abstract class providing an interface to OSv's basic
* time-keeping functions.
*
* clock::get() returns the single concrete instance of this interface,
* which may be a kvmclock, xenclock, or hpetclock - depending on which
* of these the hypervisor provides. This clock instance may then be
* queried for the current time - for example, clock::get()->time().
*
* The methods of this class are not type-safe, in that they return times
* as unadorned integers (s64) whose type does not specify the time units
* or the epoch. Prefer instead to use the types from <osv/clock.hh>:
* osv::clock::monotonic et al. Their efficiency is identical to the
* methods of this class, but they allow much better compile-time checking.
*/
class clock { class clock {
public: public:
virtual ~clock(); virtual ~clock();
virtual s64 time() = 0; virtual s64 time() = 0;
static void register_clock(clock* c); static void register_clock(clock* c);
/**
* Get a pointer to the single concrete instance of the clock class.
*
* This instance can then be used to query the current time.
* For example, clock::get()->time().
*
* This function always returns the same clock, which OSv considers the
* best and most efficient way to query the time on the this hypervisor.
* On KVM and Xen, it can be kvmclock or xenclock respectively, which are
* very efficient para-virtual clocks. As a last resort, it defaults to
* hpetclock.
* \return A pointer to the concrete instance of the clock class.
*/
static clock* get() __attribute__((no_instrument_function)); static clock* get() __attribute__((no_instrument_function));
/**
* Get the current value of the nanosecond-resolution uptime clock.
*
* The uptime clock is a *monotonic* clock, which can only go forward.
* It measures, in nanoseconds, the time that has passed since OSv's boot,
* and is usually used to measure time intervals.
*
* For improved type safety, it is recommended to use
* osv::clock::uptime::now() instead of clock::get()->uptime()
*
* Note that uptime() may remain at 0 without change for some time during
* the very early stages of the boot process.
*/
virtual s64 uptime() = 0;
private: private:
static clock* _c; static clock* _c;
}; };
......
...@@ -25,6 +25,7 @@ class hpetclock : public clock { ...@@ -25,6 +25,7 @@ class hpetclock : public clock {
public: public:
hpetclock(uint64_t hpet_address); hpetclock(uint64_t hpet_address);
virtual s64 time() __attribute__((no_instrument_function)); virtual s64 time() __attribute__((no_instrument_function));
virtual s64 uptime() override __attribute__((no_instrument_function));
private: private:
mmioaddr_t _addr; mmioaddr_t _addr;
uint64_t _wall; uint64_t _wall;
...@@ -151,6 +152,11 @@ s64 hpetclock::time() ...@@ -151,6 +152,11 @@ s64 hpetclock::time()
return _wall + (mmio_getq(_addr + HPET_COUNTER) * _period); return _wall + (mmio_getq(_addr + HPET_COUNTER) * _period);
} }
s64 hpetclock::uptime()
{
return (mmio_getq(_addr + HPET_COUNTER) * _period);
}
void __attribute__((constructor(init_prio::hpet))) hpet_init() void __attribute__((constructor(init_prio::hpet))) hpet_init()
{ {
XENPV_ALTERNATIVE( XENPV_ALTERNATIVE(
......
...@@ -20,13 +20,15 @@ class kvmclock : public clock { ...@@ -20,13 +20,15 @@ class kvmclock : public clock {
public: public:
kvmclock(); kvmclock();
virtual s64 time() __attribute__((no_instrument_function)); virtual s64 time() __attribute__((no_instrument_function));
virtual s64 uptime() override __attribute__((no_instrument_function));
static bool probe(); static bool probe();
private: private:
u64 wall_clock_boot(); u64 wall_clock_boot();
u64 system_time(); static u64 system_time();
static void setup_cpu(); static void setup_cpu();
private: private:
static bool _smp_init; static bool _smp_init;
static s64 _boot_systemtime;
static bool _new_kvmclock_msrs; static bool _new_kvmclock_msrs;
pvclock_wall_clock* _wall; pvclock_wall_clock* _wall;
u64 _wall_ns; u64 _wall_ns;
...@@ -35,6 +37,7 @@ private: ...@@ -35,6 +37,7 @@ private:
}; };
bool kvmclock::_smp_init = false; bool kvmclock::_smp_init = false;
s64 kvmclock::_boot_systemtime = 0;
bool kvmclock::_new_kvmclock_msrs = true; bool kvmclock::_new_kvmclock_msrs = true;
PERCPU(pvclock_vcpu_time_info, kvmclock::_sys); PERCPU(pvclock_vcpu_time_info, kvmclock::_sys);
...@@ -56,6 +59,7 @@ void kvmclock::setup_cpu() ...@@ -56,6 +59,7 @@ void kvmclock::setup_cpu()
memset(&*_sys, 0, sizeof(*_sys)); memset(&*_sys, 0, sizeof(*_sys));
processor::wrmsr(system_time_msr, mmu::virt_to_phys(&*_sys) | 1); processor::wrmsr(system_time_msr, mmu::virt_to_phys(&*_sys) | 1);
_smp_init = true; _smp_init = true;
_boot_systemtime = system_time();
} }
bool kvmclock::probe() bool kvmclock::probe()
...@@ -83,6 +87,15 @@ s64 kvmclock::time() ...@@ -83,6 +87,15 @@ s64 kvmclock::time()
return r; return r;
} }
s64 kvmclock::uptime()
{
if (_smp_init) {
return system_time() - _boot_systemtime;
} else {
return 0;
}
}
u64 kvmclock::wall_clock_boot() u64 kvmclock::wall_clock_boot()
{ {
return pvclock::wall_clock_boot(_wall); return pvclock::wall_clock_boot(_wall);
......
...@@ -17,19 +17,24 @@ ...@@ -17,19 +17,24 @@
#include "xen.hh" #include "xen.hh"
#include "debug.hh" #include "debug.hh"
#include "prio.hh" #include "prio.hh"
#include <preempt-lock.hh>
class xenclock : public clock { class xenclock : public clock {
public: public:
xenclock(); xenclock();
virtual s64 time() __attribute__((no_instrument_function)); virtual s64 time() __attribute__((no_instrument_function));
virtual s64 uptime() override __attribute__((no_instrument_function));
private: private:
pvclock_wall_clock* _wall; pvclock_wall_clock* _wall;
static void setup_cpu(); static void setup_cpu();
static bool _smp_init; static bool _smp_init;
static s64 _boot_systemtime;
sched::cpu::notifier cpu_notifier; sched::cpu::notifier cpu_notifier;
static u64 system_time();
}; };
bool xenclock::_smp_init = false; bool xenclock::_smp_init = false;
s64 xenclock::_boot_systemtime = 0;
xenclock::xenclock() xenclock::xenclock()
: cpu_notifier(&xenclock::setup_cpu) : cpu_notifier(&xenclock::setup_cpu)
...@@ -40,6 +45,7 @@ xenclock::xenclock() ...@@ -40,6 +45,7 @@ xenclock::xenclock()
void xenclock::setup_cpu() void xenclock::setup_cpu()
{ {
_smp_init = true; _smp_init = true;
_boot_systemtime = system_time();
} }
s64 xenclock::time() s64 xenclock::time()
...@@ -61,6 +67,24 @@ s64 xenclock::time() ...@@ -61,6 +67,24 @@ s64 xenclock::time()
return r; return r;
} }
u64 xenclock::system_time()
{
WITH_LOCK(preempt_lock) {
auto cpu = sched::cpu::current()->id;
auto sys = &xen::xen_shared_info.vcpu_info[cpu].time;
return pvclock::system_time(sys);
}
}
s64 xenclock::uptime()
{
if (_smp_init) {
return system_time() - _boot_systemtime;
} else {
return 0;
}
}
static __attribute__((constructor(init_prio::clock))) void setup_xenclock() static __attribute__((constructor(init_prio::clock))) void setup_xenclock()
{ {
// FIXME: find out if the HV supports positioning the vcpu structure // FIXME: find out if the HV supports positioning the vcpu structure
......
/*
* Copyright (C) 2013-2014 Cloudius Systems, Ltd.
*
* This work is open source software, licensed under the terms of the
* BSD license as described in the LICENSE file in the top-level directory.
*/
#ifndef OSV_CLOCK_HH_
#define OSV_CLOCK_HH_
#include <drivers/clock.hh>
#include <chrono>
namespace osv {
/**
* OSv Clock namespace
*/
namespace clock {
/**
* Nanosecond-resolution monotonic uptime clock.
*
* The monotonic clock can only go forward, and measures, in nanoseconds,
* the time that has passed since OSv's boot.
*
* This class is very similar to std::chrono::steady_clock, except the
* latter is actually implemented on top of this one with clock_gettime()
* in the middle, so this class is faster.
*/
class uptime {
public:
typedef std::chrono::nanoseconds duration;
typedef std::chrono::time_point<osv::clock::uptime> time_point;
/**
* Get the current value of the nanosecond-resolution monotonic clock.
*
* This is done using the most efficient clock available from the host.
* It can be a very efficient paravirtual clock (kvmclock or xenclock),
* or at last resort, the hpet clock (emulated by the host).
*/
static time_point now() {
return time_point(duration(::clock::get()->uptime()));
}
};
}
}
#endif /* OSV_CLOCK_HH_ */
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
#include <unistd.h> #include <unistd.h>
#include <osv/stubbing.hh> #include <osv/stubbing.hh>
#include "libc.hh" #include "libc.hh"
#include "drivers/clock.hh" #include <osv/clock.hh>
#include "sched.hh" #include "sched.hh"
u64 convert(const timespec& ts) u64 convert(const timespec& ts)
...@@ -47,15 +47,28 @@ int usleep(useconds_t usec) ...@@ -47,15 +47,28 @@ int usleep(useconds_t usec)
int clock_gettime(clockid_t clk_id, struct timespec* ts) int clock_gettime(clockid_t clk_id, struct timespec* ts)
{ {
if (clk_id != CLOCK_REALTIME) { switch (clk_id) {
case CLOCK_REALTIME:
{
auto time = clock::get()->time();
auto sec = time / 1000000000;
auto nsec = time % 1000000000;
ts->tv_sec = sec;
ts->tv_nsec = nsec;
return 0;
}
case CLOCK_MONOTONIC:
{
auto t = osv::clock::uptime::now().time_since_epoch();
ts->tv_sec = std::chrono::duration_cast<std::chrono::seconds>(t).
count();
ts->tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(t).
count() % 1000000000;
return 0;
}
default:
return libc_error(EINVAL); return libc_error(EINVAL);
} }
u64 time = clock::get()->time();
auto sec = time / 1000000000;
auto nsec = time % 1000000000;
ts->tv_sec = sec;
ts->tv_nsec = nsec;
return 0;
} }
extern "C" extern "C"
...@@ -63,16 +76,17 @@ int __clock_gettime(clockid_t clk_id, struct timespec* ts) __attribute__((alias( ...@@ -63,16 +76,17 @@ int __clock_gettime(clockid_t clk_id, struct timespec* ts) __attribute__((alias(
int clock_getres(clockid_t clk_id, struct timespec* ts) int clock_getres(clockid_t clk_id, struct timespec* ts)
{ {
if (clk_id != CLOCK_REALTIME) { switch (clk_id) {
case CLOCK_REALTIME:
case CLOCK_MONOTONIC:
if (ts) {
ts->tv_sec = 0;
ts->tv_nsec = 1;
}
return 0;
default:
return libc_error(EINVAL); return libc_error(EINVAL);
} }
if (ts) {
ts->tv_sec = 0;
ts->tv_nsec = 1;
}
return 0;
} }
int clock_getcpuclockid(pid_t pid, clockid_t* clock_id) int clock_getcpuclockid(pid_t pid, clockid_t* clock_id)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment