summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNico Weber <thakis@chromium.org>2020-11-05 16:00:51 -0500
committerAndreas Kling <kling@serenityos.org>2020-11-10 19:03:08 +0100
commit323e727a4c14ba94ff8e89447381e768ee090b6c (patch)
treec6521914438b46f224d98431b7c499f78644e032
parent28abfd6290ca8d740ebd8cc2c6893e92bf4c4f0f (diff)
downloadserenity-323e727a4c14ba94ff8e89447381e768ee090b6c.zip
Kernel+LibC: Add adjtime(2)
Most systems (Linux, OpenBSD) adjust 0.5 ms per second, or 0.5 us per 1 ms tick. That is, the clock is sped up or slowed down by at most 0.05%. This means adjusting the clock by 1 s takes 2000 s, and the clock an be adjusted by at most 1.8 s per hour. FreeBSD adjusts 5 ms per second if the remaining time adjustment is >= 1 s (0.5%) , else it adjusts by 0.5 ms as well. This allows adjusting by (almost) 18 s per hour. Since Serenity OS can lose more than 22 s per hour (#3429), this picks an adjustment rate up to 1% for now. This allows us to adjust up to 36s per hour, which should be sufficient to adjust the clock fast enough to keep up with how much time the clock currently loses. Once we have a fancier NTP implementation that can adjust tick rate in addition to offset, we can think about reducing this. adjtime is a bit old-school and most current POSIX-y OSs instead implement adjtimex/ntp_adjtime, but a) we have to start somewhere b) ntp_adjtime() is a fairly gnarly API. OpenBSD's adjfreq looks like it might provide similar functionality with a nicer API. But before worrying about all this, it's probably a good idea to get to a place where the kernel APIs are (barely) good enough so that we can write an ntp service, and once we have that we should write a way to automatically evaluate how well it keeps the time adjusted, and only then should we add improvements ot the adjustment mechanism.
-rw-r--r--Base/usr/share/man/man2/adjtime.md33
-rw-r--r--Kernel/API/Syscall.h3
-rw-r--r--Kernel/Process.h1
-rw-r--r--Kernel/Syscalls/clock.cpp30
-rw-r--r--Kernel/Time/TimeManagement.cpp18
-rw-r--r--Kernel/Time/TimeManagement.h4
-rw-r--r--Libraries/LibC/sys/time.h1
-rw-r--r--Libraries/LibC/time.cpp6
8 files changed, 94 insertions, 2 deletions
diff --git a/Base/usr/share/man/man2/adjtime.md b/Base/usr/share/man/man2/adjtime.md
new file mode 100644
index 0000000000..29b315426a
--- /dev/null
+++ b/Base/usr/share/man/man2/adjtime.md
@@ -0,0 +1,33 @@
+## Name
+
+adjtime - gradually adjust system clock
+
+## Synopsis
+
+```**c++
+#include <sys/time.h>
+
+int adjtime(const struct timeval* delta, struct timeval* old_delta);
+```
+
+## Description
+
+`adjtime()` gradually increments the system time by `delta`, if it is non-null.
+
+Serenity OS slows down or speeds up the system clock by at most 1%, so adjusting the time by N seconds takes 100 * n seconds to complete.
+
+Calling `settimeofday()` or `clock_settime()` cancels in-progress time adjustments done by `adjtime`.
+
+If `delta` is not null, `adjtime` can only called by the superuser.
+
+If `old_delta` is not null, it returns the currently remaining time adjustment. Querying the remaining time adjustment does not need special permissions.
+
+## Pledge
+
+In pledged programs, the `settime` promise is required when `delta` is not null.
+
+## Errors
+
+* `EFAULT`: `delta` and/or `old_delta` are not null and not in readable memory.
+* `EINVAL`: `delta` is not null and has a `tv_nsec` field that's less than 0 or larger or equal to `10^6`. Negative deltas should have a negative `tv_sec` field but a `tv_nsec` that's larger or equal zero. For example, a delta of -0.5 s is represented by `{-1, 500'000}`.
+* `EPERM`: `delta` is not null but geteuid() is not 0.
diff --git a/Kernel/API/Syscall.h b/Kernel/API/Syscall.h
index 54fb533f13..897d05d64d 100644
--- a/Kernel/API/Syscall.h
+++ b/Kernel/API/Syscall.h
@@ -193,7 +193,8 @@ namespace Kernel {
S(recvfd) \
S(sysconf) \
S(set_process_name) \
- S(disown)
+ S(disown) \
+ S(adjtime)
namespace Syscall {
diff --git a/Kernel/Process.h b/Kernel/Process.h
index 743d654e8b..f62671fd74 100644
--- a/Kernel/Process.h
+++ b/Kernel/Process.h
@@ -241,6 +241,7 @@ public:
int sys$fchdir(int fd);
int sys$sleep(unsigned seconds);
int sys$usleep(useconds_t usec);
+ int sys$adjtime(Userspace<const timeval*>, Userspace<timeval*>);
int sys$gettimeofday(Userspace<timeval*>);
int sys$clock_gettime(clockid_t, Userspace<timespec*>);
int sys$clock_settime(clockid_t, Userspace<const timespec*>);
diff --git a/Kernel/Syscalls/clock.cpp b/Kernel/Syscalls/clock.cpp
index 3ee0c85339..e7d40c5be3 100644
--- a/Kernel/Syscalls/clock.cpp
+++ b/Kernel/Syscalls/clock.cpp
@@ -24,6 +24,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include <AK/Time.h>
#include <Kernel/Process.h>
#include <Kernel/Time/TimeManagement.h>
@@ -119,6 +120,35 @@ int Process::sys$clock_nanosleep(Userspace<const Syscall::SC_clock_nanosleep_par
}
}
+int Process::sys$adjtime(Userspace<const timeval*> user_delta, Userspace<timeval*> user_old_delta)
+{
+ if (user_old_delta) {
+ timespec old_delta_ts = TimeManagement::the().remaining_epoch_time_adjustment();
+ timeval old_delta;
+ timespec_to_timeval(old_delta_ts, old_delta);
+ if (!copy_to_user(user_old_delta, &old_delta))
+ return -EFAULT;
+ }
+
+ if (user_delta) {
+ REQUIRE_PROMISE(settime);
+ if (!is_superuser())
+ return -EPERM;
+ timeval delta;
+ if (!copy_from_user(&delta, user_delta))
+ return -EFAULT;
+
+ if (delta.tv_usec < 0 || delta.tv_usec >= 1'000'000)
+ return -EINVAL;
+
+ timespec delta_ts;
+ timeval_to_timespec(delta, delta_ts);
+ TimeManagement::the().set_remaining_epoch_time_adjustment(delta_ts);
+ }
+
+ return 0;
+}
+
int Process::sys$gettimeofday(Userspace<timeval*> user_tv)
{
REQUIRE_PROMISE(stdio);
diff --git a/Kernel/Time/TimeManagement.cpp b/Kernel/Time/TimeManagement.cpp
index 4e1578cc1e..68dedaea66 100644
--- a/Kernel/Time/TimeManagement.cpp
+++ b/Kernel/Time/TimeManagement.cpp
@@ -25,6 +25,7 @@
*/
#include <AK/Singleton.h>
+#include <AK/StdLibExtras.h>
#include <AK/Time.h>
#include <Kernel/ACPI/Parser.h>
#include <Kernel/CommandLine.h>
@@ -59,6 +60,7 @@ void TimeManagement::set_epoch_time(timespec ts)
{
InterruptDisabler disabler;
m_epoch_time = ts;
+ m_remaining_epoch_time_adjustment = { 0, 0 };
}
timespec TimeManagement::epoch_time() const
@@ -256,7 +258,21 @@ void TimeManagement::increment_time_since_boot(const RegisterState&)
{
ASSERT(!m_time_keeper_timer.is_null());
- timespec epoch_tick = { .tv_sec = 0, .tv_nsec = 1'000'000 }; // FIXME: Don't assume that one tick is 1 ms.
+ // Compute time adjustment for adjtime. Let the clock run up to 1% fast or slow.
+ // That way, adjtime can adjust up to 36 seconds per hour, without time getting very jumpy.
+ // Once we have a smarter NTP service that also adjusts the frequency instead of just slewing time, maybe we can lower this.
+ constexpr long NanosPerTick = 1'000'000; // FIXME: Don't assume that one tick is 1 ms.
+ constexpr time_t MaxSlewNanos = NanosPerTick / 100;
+ static_assert(MaxSlewNanos < NanosPerTick);
+
+ // Clamp twice, to make sure intermediate fits into a long.
+ long slew_nanos = clamp(clamp(m_remaining_epoch_time_adjustment.tv_sec, (time_t)-1, (time_t)1) * 1'000'000'000 + m_remaining_epoch_time_adjustment.tv_nsec, -MaxSlewNanos, MaxSlewNanos);
+ timespec slew_nanos_ts;
+ timespec_sub({ 0, slew_nanos }, { 0, 0 }, slew_nanos_ts); // Normalize tv_nsec to be positive.
+ timespec_sub(m_remaining_epoch_time_adjustment, slew_nanos_ts, m_remaining_epoch_time_adjustment);
+
+ timespec epoch_tick = { .tv_sec = 0, .tv_nsec = NanosPerTick };
+ epoch_tick.tv_nsec += slew_nanos; // No need for timespec_add(), guaranteed to be in range.
timespec_add(m_epoch_time, epoch_tick, m_epoch_time);
if (++m_ticks_this_second >= m_time_keeper_timer->ticks_per_second()) {
diff --git a/Kernel/Time/TimeManagement.h b/Kernel/Time/TimeManagement.h
index f3a3bb0188..b6c268e3af 100644
--- a/Kernel/Time/TimeManagement.h
+++ b/Kernel/Time/TimeManagement.h
@@ -62,6 +62,9 @@ public:
static timeval now_as_timeval();
+ timespec remaining_epoch_time_adjustment() const { return m_remaining_epoch_time_adjustment; }
+ void set_remaining_epoch_time_adjustment(const timespec& adjustment) { m_remaining_epoch_time_adjustment = adjustment; }
+
private:
bool probe_and_set_legacy_hardware_timers();
bool probe_and_set_non_legacy_hardware_timers();
@@ -73,6 +76,7 @@ private:
u32 m_ticks_this_second { 0 };
u32 m_seconds_since_boot { 0 };
timespec m_epoch_time { 0, 0 };
+ timespec m_remaining_epoch_time_adjustment { 0, 0 };
RefPtr<HardwareTimerBase> m_system_timer;
RefPtr<HardwareTimerBase> m_time_keeper_timer;
};
diff --git a/Libraries/LibC/sys/time.h b/Libraries/LibC/sys/time.h
index ac3013747e..11faa3bdd4 100644
--- a/Libraries/LibC/sys/time.h
+++ b/Libraries/LibC/sys/time.h
@@ -42,6 +42,7 @@ struct timezone {
int tz_dsttime;
};
+int adjtime(const struct timeval* delta, struct timeval* old_delta);
int gettimeofday(struct timeval* __restrict__, void* __restrict__) __attribute__((nonnull(1)));
int settimeofday(struct timeval* __restrict__, void* __restrict__) __attribute__((nonnull(1)));
diff --git a/Libraries/LibC/time.cpp b/Libraries/LibC/time.cpp
index 8e19609191..73f1a4234d 100644
--- a/Libraries/LibC/time.cpp
+++ b/Libraries/LibC/time.cpp
@@ -49,6 +49,12 @@ time_t time(time_t* tloc)
return tv.tv_sec;
}
+int adjtime(const struct timeval* delta, struct timeval* old_delta)
+{
+ int rc = syscall(SC_adjtime, delta, old_delta);
+ __RETURN_WITH_ERRNO(rc, rc, -1);
+}
+
int gettimeofday(struct timeval* __restrict__ tv, void* __restrict__)
{
int rc = syscall(SC_gettimeofday, tv);