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)