diff --git a/drivers/clock.hh b/drivers/clock.hh index 70cc77475144f83cdd4885944f3c014a7b1a9d6c..c759c2296d8924b2b25ca31093dfec1cbb8885b2 100644 --- a/drivers/clock.hh +++ b/drivers/clock.hh @@ -1,5 +1,5 @@ /* - * 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 * BSD license as described in the LICENSE file in the top-level directory. @@ -10,12 +10,56 @@ #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 { public: virtual ~clock(); virtual s64 time() = 0; 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)); + /** + * 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: static clock* _c; }; diff --git a/drivers/hpet.cc b/drivers/hpet.cc index a840bc6fa91569749991a2e8e1bd028bfa1ff5ab..249caf472b9dda0c4e5689454691158d1ff2cec3 100644 --- a/drivers/hpet.cc +++ b/drivers/hpet.cc @@ -25,6 +25,7 @@ class hpetclock : public clock { public: hpetclock(uint64_t hpet_address); virtual s64 time() __attribute__((no_instrument_function)); + virtual s64 uptime() override __attribute__((no_instrument_function)); private: mmioaddr_t _addr; uint64_t _wall; @@ -151,6 +152,11 @@ s64 hpetclock::time() 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() { XENPV_ALTERNATIVE( diff --git a/drivers/kvmclock.cc b/drivers/kvmclock.cc index b6489ce1c6b3af74728c981788f91bdbc3254138..0eaf1993174ad2ff28bf01140a56f1546d8062a5 100644 --- a/drivers/kvmclock.cc +++ b/drivers/kvmclock.cc @@ -20,13 +20,15 @@ class kvmclock : public clock { public: kvmclock(); virtual s64 time() __attribute__((no_instrument_function)); + virtual s64 uptime() override __attribute__((no_instrument_function)); static bool probe(); private: u64 wall_clock_boot(); - u64 system_time(); + static u64 system_time(); static void setup_cpu(); private: static bool _smp_init; + static s64 _boot_systemtime; static bool _new_kvmclock_msrs; pvclock_wall_clock* _wall; u64 _wall_ns; @@ -35,6 +37,7 @@ private: }; bool kvmclock::_smp_init = false; +s64 kvmclock::_boot_systemtime = 0; bool kvmclock::_new_kvmclock_msrs = true; PERCPU(pvclock_vcpu_time_info, kvmclock::_sys); @@ -56,6 +59,7 @@ void kvmclock::setup_cpu() memset(&*_sys, 0, sizeof(*_sys)); processor::wrmsr(system_time_msr, mmu::virt_to_phys(&*_sys) | 1); _smp_init = true; + _boot_systemtime = system_time(); } bool kvmclock::probe() @@ -83,6 +87,15 @@ s64 kvmclock::time() return r; } +s64 kvmclock::uptime() +{ + if (_smp_init) { + return system_time() - _boot_systemtime; + } else { + return 0; + } +} + u64 kvmclock::wall_clock_boot() { return pvclock::wall_clock_boot(_wall); diff --git a/drivers/xenclock.cc b/drivers/xenclock.cc index 058ee9a514b33bea5d270bc0e8602bee15aa6237..60665eda1d3b9f783574ec144f175721450afd0a 100644 --- a/drivers/xenclock.cc +++ b/drivers/xenclock.cc @@ -17,19 +17,24 @@ #include "xen.hh" #include "debug.hh" #include "prio.hh" +#include <preempt-lock.hh> class xenclock : public clock { public: xenclock(); virtual s64 time() __attribute__((no_instrument_function)); + virtual s64 uptime() override __attribute__((no_instrument_function)); private: pvclock_wall_clock* _wall; static void setup_cpu(); static bool _smp_init; + static s64 _boot_systemtime; sched::cpu::notifier cpu_notifier; + static u64 system_time(); }; bool xenclock::_smp_init = false; +s64 xenclock::_boot_systemtime = 0; xenclock::xenclock() : cpu_notifier(&xenclock::setup_cpu) @@ -40,6 +45,7 @@ xenclock::xenclock() void xenclock::setup_cpu() { _smp_init = true; + _boot_systemtime = system_time(); } s64 xenclock::time() @@ -61,6 +67,24 @@ s64 xenclock::time() 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() { // FIXME: find out if the HV supports positioning the vcpu structure diff --git a/include/osv/clock.hh b/include/osv/clock.hh new file mode 100644 index 0000000000000000000000000000000000000000..6defff6cd5c49e3f9d8357b2c62b34ea52c84100 --- /dev/null +++ b/include/osv/clock.hh @@ -0,0 +1,49 @@ +/* + * 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_ */ diff --git a/libc/time.cc b/libc/time.cc index 1d3d54d17c83640eb67ca3db4a0e71e28e896c80..a458ba1e8330d400907626a3f17931edcdbe5219 100644 --- a/libc/time.cc +++ b/libc/time.cc @@ -11,7 +11,7 @@ #include <unistd.h> #include <osv/stubbing.hh> #include "libc.hh" -#include "drivers/clock.hh" +#include <osv/clock.hh> #include "sched.hh" u64 convert(const timespec& ts) @@ -47,15 +47,28 @@ int usleep(useconds_t usec) 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); } - 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" @@ -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) { - 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); } - - if (ts) { - ts->tv_sec = 0; - ts->tv_nsec = 1; - } - - return 0; } int clock_getcpuclockid(pid_t pid, clockid_t* clock_id)