1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 04:17:35 +00:00

Kernel: Improve time keeping and dramatically reduce interrupt load

This implements a number of changes related to time:
* If a HPET is present, it is now used only as a system timer, unless
  the Local APIC timer is used (in which case the HPET timer will not
  trigger any interrupts at all).
* If a HPET is present, the current time can now be as accurate as the
  chip can be, independently from the system timer. We now query the
  HPET main counter for the current time in CPU #0's system timer
  interrupt, and use that as a base line. If a high precision time is
  queried, that base line is used in combination with quering the HPET
  timer directly, which should give a much more accurate time stamp at
  the expense of more overhead. For faster time stamps, the more coarse
  value based on the last interrupt will be returned. This also means
  that any missed interrupts should not cause the time to drift.
* The default system interrupt rate is reduced to about 250 per second.
* Fix calculation of Thread CPU usage by using the amount of ticks they
  used rather than the number of times a context switch happened.
* Implement CLOCK_REALTIME_COARSE and CLOCK_MONOTONIC_COARSE and use it
  for most cases where precise timestamps are not needed.
This commit is contained in:
Tom 2020-12-03 22:12:50 -07:00 committed by Andreas Kling
parent a3fdf5148b
commit 5f51d85184
32 changed files with 318 additions and 190 deletions

View file

@ -44,6 +44,7 @@ public:
virtual bool is_periodic_capable() const override { return true; }
virtual void set_periodic() override;
virtual void set_non_periodic() override;
virtual void disable() override { }
virtual void reset_to_default_ticks_per_second() override;
virtual bool try_to_set_frequency(size_t frequency) override;

View file

@ -197,10 +197,14 @@ void HPET::update_periodic_comparator_value()
auto& regs = registers();
u64 previous_main_value = (u64)regs.main_counter_value.low | ((u64)regs.main_counter_value.high << 32);
m_main_counter_drift += previous_main_value - m_main_counter_last_read;
m_main_counter_last_read = 0;
regs.main_counter_value.low = 0;
regs.main_counter_value.high = 0;
for (auto& comparator : m_comparators) {
auto& timer = regs.timers[comparator.comparator_number()];
if (!comparator.is_enabled())
continue;
if (comparator.is_periodic()) {
// Note that this means we're restarting all periodic timers. There is no
// way to resume periodic timers properly because we reset the main counter
@ -242,6 +246,33 @@ void HPET::update_non_periodic_comparator_value(const HPETComparator& comparator
timer.comparator_value.low = (u32)new_counter_value;
}
u64 HPET::update_time(u64& seconds_since_boot, u32& ticks_this_second, bool query_only)
{
// Should only be called by the time keeper interrupt handler!
u64 current_value = read_register_safe64(registers().main_counter_value);
u64 delta_ticks = m_main_counter_drift;
if (current_value >= m_main_counter_last_read)
delta_ticks += current_value - m_main_counter_last_read;
else
delta_ticks += m_main_counter_last_read - current_value; // the counter wrapped around
u64 ticks_since_last_second = (u64)ticks_this_second + delta_ticks;
auto ticks_per_second = frequency();
if (ticks_since_last_second > ticks_per_second) {
seconds_since_boot += ticks_since_last_second / ticks_per_second;
ticks_this_second = ticks_since_last_second % ticks_per_second;
} else {
ticks_this_second = ticks_since_last_second;
}
if (!query_only) {
m_main_counter_drift = 0;
m_main_counter_last_read = current_value;
}
// Return the time passed (in ns) since last time update_time was called
return (delta_ticks * 1000000000ull) / ticks_per_second;
}
void HPET::enable_periodic_interrupt(const HPETComparator& comparator)
{
#ifdef HPET_DEBUG
@ -253,7 +284,8 @@ void HPET::enable_periodic_interrupt(const HPETComparator& comparator)
auto capabilities = timer.capabilities;
ASSERT(capabilities & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable);
timer.capabilities = capabilities | (u32)HPETFlags::TimerConfiguration::GeneratePeriodicInterrupt;
enable(comparator);
if (comparator.is_enabled())
enable(comparator);
}
void HPET::disable_periodic_interrupt(const HPETComparator& comparator)
{
@ -266,7 +298,8 @@ void HPET::disable_periodic_interrupt(const HPETComparator& comparator)
auto capabilities = timer.capabilities;
ASSERT(capabilities & (u32)HPETFlags::TimerConfiguration::PeriodicInterruptCapable);
timer.capabilities = capabilities & ~(u32)HPETFlags::TimerConfiguration::GeneratePeriodicInterrupt;
enable(comparator);
if (comparator.is_enabled())
enable(comparator);
}
void HPET::disable(const HPETComparator& comparator)
@ -288,11 +321,6 @@ void HPET::enable(const HPETComparator& comparator)
timer.capabilities = timer.capabilities | (u32)HPETFlags::TimerConfiguration::InterruptEnable;
}
u64 HPET::frequency() const
{
return m_frequency;
}
Vector<unsigned> HPET::capable_interrupt_numbers(const HPETComparator& comparator)
{
ASSERT(comparator.comparator_number() <= m_comparators.size());
@ -394,7 +422,7 @@ HPET::HPET(PhysicalAddress acpi_hpet)
klog() << "HPET: frequency " << m_frequency << " Hz (" << MEGAHERTZ_TO_HERTZ(m_frequency) << " MHz) resolution: " << calculate_ticks_in_nanoseconds() << "ns";
ASSERT(regs.capabilities.main_counter_tick_period <= ABSOLUTE_MAXIMUM_COUNTER_TICK_PERIOD);
// Reset the counter, just in case...
// Reset the counter, just in case... (needs to match m_main_counter_last_read)
regs.main_counter_value.high = 0;
regs.main_counter_value.low = 0;
if (regs.capabilities.attributes & (u32)HPETFlags::Attributes::LegacyReplacementRouteCapable)

View file

@ -45,7 +45,7 @@ public:
static bool check_for_exisiting_periodic_timers();
static HPET& the();
u64 frequency() const;
u64 frequency() const { return m_frequency; }
const NonnullRefPtrVector<HPETComparator>& comparators() const { return m_comparators; }
void disable(const HPETComparator&);
@ -59,6 +59,8 @@ public:
void enable_periodic_interrupt(const HPETComparator& comparator);
void disable_periodic_interrupt(const HPETComparator& comparator);
u64 update_time(u64& seconds_since_boot, u32& ticks_this_second, bool query_only);
Vector<unsigned> capable_interrupt_numbers(u8 comparator_number);
Vector<unsigned> capable_interrupt_numbers(const HPETComparator&);
@ -80,7 +82,9 @@ private:
PhysicalAddress m_physical_acpi_hpet_registers;
OwnPtr<Region> m_hpet_mmio_region;
u64 m_main_counter_clock_period { 0 };
u64 m_main_counter_last_read { 0 };
u64 m_main_counter_drift { 0 };
u16 m_vendor_id;
u16 m_minimum_tick;
u64 m_frequency;

View file

@ -41,20 +41,31 @@ HPETComparator::HPETComparator(u8 number, u8 irq, bool periodic_capable)
: HardwareTimer(irq)
, m_periodic(false)
, m_periodic_capable(periodic_capable)
, m_enabled(false)
, m_comparator_number(number)
{
}
void HPETComparator::disable()
{
if (!m_enabled)
return;
m_enabled = false;
HPET::the().disable(*this);
}
void HPETComparator::set_periodic()
{
ASSERT(m_periodic_capable);
m_periodic = true;
m_enabled = true;
HPET::the().enable_periodic_interrupt(*this);
}
void HPETComparator::set_non_periodic()
{
ASSERT(m_periodic_capable);
m_periodic = false;
m_enabled = true;
HPET::the().disable_periodic_interrupt(*this);
}
@ -79,7 +90,7 @@ size_t HPETComparator::ticks_per_second() const
void HPETComparator::reset_to_default_ticks_per_second()
{
ASSERT(is_capable_of_frequency(OPTIMAL_TICKS_PER_SECOND_RATE));
dbg() << "reset_to_default_ticks_per_second";
m_frequency = OPTIMAL_TICKS_PER_SECOND_RATE;
if (!is_periodic())
set_new_countdown();
@ -89,13 +100,16 @@ void HPETComparator::reset_to_default_ticks_per_second()
bool HPETComparator::try_to_set_frequency(size_t frequency)
{
InterruptDisabler disabler;
if (!is_capable_of_frequency(frequency))
if (!is_capable_of_frequency(frequency)) {
dbg() << "HPETComparator: not cable of frequency: " << frequency;
return false;
if (m_frequency == frequency)
return true;
}
auto hpet_frequency = HPET::the().frequency();
ASSERT(frequency <= hpet_frequency);
m_frequency = frequency;
m_enabled = true;
#ifdef HPET_COMPARATOR_DEBUG
dbg() << "HPET Comparator: Max frequency " << hpet_frequency << " Hz, want to set " << frequency << " Hz, periodic: " << is_periodic();
#endif
@ -112,16 +126,17 @@ bool HPETComparator::is_capable_of_frequency(size_t frequency) const
{
if (frequency > HPET::the().frequency())
return false;
if ((HPET::the().frequency() % frequency) != 0)
return false;
// HPET::update_periodic_comparator_value and HPET::update_non_periodic_comparator_value
// calculate the best counter based on the desired frequency.
return true;
}
size_t HPETComparator::calculate_nearest_possible_frequency(size_t frequency) const
{
if (frequency >= HPET::the().frequency())
if (frequency > HPET::the().frequency())
return HPET::the().frequency();
// FIXME: Use better math here
return (frequency + (HPET::the().frequency() % frequency));
// HPET::update_periodic_comparator_value and HPET::update_non_periodic_comparator_value
// calculate the best counter based on the desired frequency.
return frequency;
}
}

View file

@ -42,6 +42,7 @@ public:
virtual const char* model() const override { return "HPET"; }
u8 comparator_number() const { return m_comparator_number; }
bool is_enabled() const { return m_enabled; }
virtual size_t ticks_per_second() const override;
@ -49,6 +50,7 @@ public:
virtual bool is_periodic_capable() const override { return m_periodic_capable; }
virtual void set_periodic() override;
virtual void set_non_periodic() override;
virtual void disable() override;
virtual void reset_to_default_ticks_per_second() override;
virtual bool try_to_set_frequency(size_t frequency) override;
@ -61,7 +63,7 @@ private:
HPETComparator(u8 number, u8 irq, bool periodic_capable);
bool m_periodic : 1;
bool m_periodic_capable : 1;
bool m_edge_triggered : 1;
bool m_enabled : 1;
u8 m_comparator_number { 0 };
};
}

View file

@ -57,6 +57,7 @@ public:
virtual bool is_periodic_capable() const = 0;
virtual void set_periodic() = 0;
virtual void set_non_periodic() = 0;
virtual void disable() = 0;
virtual size_t ticks_per_second() const = 0;

View file

@ -63,6 +63,7 @@ public:
virtual bool is_periodic_capable() const override { return true; }
virtual void set_periodic() override;
virtual void set_non_periodic() override;
virtual void disable() override { }
virtual void reset_to_default_ticks_per_second() override;
virtual bool try_to_set_frequency(size_t frequency) override;

View file

@ -42,6 +42,7 @@ public:
virtual bool is_periodic_capable() const override { return true; }
virtual void set_periodic() override { }
virtual void set_non_periodic() override { }
virtual void disable() override { }
virtual void reset_to_default_ticks_per_second() override;
virtual bool try_to_set_frequency(size_t frequency) override;

View file

@ -52,13 +52,33 @@ TimeManagement& TimeManagement::the()
return *s_the;
}
bool TimeManagement::is_valid_clock_id(clockid_t clock_id)
{
switch (clock_id) {
case CLOCK_MONOTONIC:
case CLOCK_MONOTONIC_COARSE:
case CLOCK_MONOTONIC_RAW:
case CLOCK_REALTIME:
case CLOCK_REALTIME_COARSE:
return true;
default:
return false;
};
}
KResultOr<timespec> TimeManagement::current_time(clockid_t clock_id) const
{
switch (clock_id) {
case CLOCK_MONOTONIC:
return monotonic_time();
return monotonic_time(TimePrecision::Precise);
case CLOCK_MONOTONIC_COARSE:
return monotonic_time(TimePrecision::Coarse);
case CLOCK_MONOTONIC_RAW:
return monotonic_time_raw();
case CLOCK_REALTIME:
return epoch_time();
return epoch_time(TimePrecision::Precise);
case CLOCK_REALTIME_COARSE:
return epoch_time(TimePrecision::Coarse);
default:
return KResult(EINVAL);
}
@ -69,22 +89,6 @@ bool TimeManagement::is_system_timer(const HardwareTimerBase& timer) const
return &timer == m_system_timer.ptr();
}
timespec TimeManagement::ticks_to_time(u64 ticks, time_t ticks_per_second)
{
timespec tspec;
tspec.tv_sec = ticks / ticks_per_second;
tspec.tv_nsec = (ticks % ticks_per_second) * (1'000'000'000 / ticks_per_second);
ASSERT(tspec.tv_nsec <= 1'000'000'000);
return tspec;
}
u64 TimeManagement::time_to_ticks(const timespec& tspec, time_t ticks_per_second)
{
u64 ticks = (u64)tspec.tv_sec * ticks_per_second;
ticks += ((u64)tspec.tv_nsec * ticks_per_second) / 1'000'000'000;
return ticks;
}
void TimeManagement::set_epoch_time(timespec ts)
{
InterruptDisabler disabler;
@ -92,27 +96,40 @@ void TimeManagement::set_epoch_time(timespec ts)
m_remaining_epoch_time_adjustment = { 0, 0 };
}
u64 TimeManagement::monotonic_ticks() const
timespec TimeManagement::monotonic_time(TimePrecision precision) const
{
long seconds;
u64 ticks;
// This is the time when last updated by an interrupt.
u64 seconds;
u32 ticks;
bool do_query = precision == TimePrecision::Precise && m_can_query_precise_time;
u32 update_iteration;
do {
update_iteration = m_update1.load(AK::MemoryOrder::memory_order_acquire);
seconds = m_seconds_since_boot;
ticks = m_ticks_this_second;
if (do_query) {
// We may have to do this over again if the timer interrupt fires
// while we're trying to query the information. In that case, our
// seconds and ticks became invalid, producing an incorrect time.
// Be sure to not modify m_seconds_since_boot and m_ticks_this_second
// because this may only be modified by the interrupt handler
HPET::the().update_time(seconds, ticks, true);
}
} while (update_iteration != m_update2.load(AK::MemoryOrder::memory_order_acquire));
return ticks + (u64)seconds * (u64)ticks_per_second();
ASSERT(m_time_ticks_per_second > 0);
ASSERT(ticks < m_time_ticks_per_second);
u64 ns = ((u64)ticks * 1000000000ull) / m_time_ticks_per_second;
ASSERT(ns < 1000000000ull);
return { (long)seconds, (long)ns };
}
timespec TimeManagement::monotonic_time() const
{
return ticks_to_time(monotonic_ticks(), ticks_per_second());
}
timespec TimeManagement::epoch_time() const
timespec TimeManagement::epoch_time(TimePrecision) const
{
// TODO: Take into account precision
timespec ts;
u32 update_iteration;
do {
@ -155,7 +172,9 @@ void TimeManagement::initialize(u32 cpu)
void TimeManagement::set_system_timer(HardwareTimerBase& timer)
{
ASSERT(Processor::current().id() == 0); // This should only be called on the BSP!
auto original_callback = m_system_timer->set_callback(nullptr);
m_system_timer->disable();
timer.set_callback(move(original_callback));
m_system_timer = timer;
}
@ -259,28 +278,36 @@ bool TimeManagement::probe_and_set_non_legacy_hardware_timers()
if (is_hpet_periodic_mode_allowed())
ASSERT(!periodic_timers.is_empty());
ASSERT(periodic_timers.size() + non_periodic_timers.size() >= 2);
ASSERT(periodic_timers.size() + non_periodic_timers.size() > 0);
if (periodic_timers.size() >= 2) {
m_time_keeper_timer = periodic_timers[1];
if (periodic_timers.size() > 0)
m_system_timer = periodic_timers[0];
} else {
if (periodic_timers.size() == 1) {
m_time_keeper_timer = periodic_timers[0];
m_system_timer = non_periodic_timers[0];
} else {
m_time_keeper_timer = non_periodic_timers[1];
m_system_timer = non_periodic_timers[0];
else
m_system_timer = non_periodic_timers[0];
m_system_timer->set_callback([this](const RegisterState& regs) {
// Update the time. We don't really care too much about the
// frequency of the interrupt because we'll query the main
// counter to get an accurate time.
if (Processor::current().id() == 0) {
// TODO: Have the other CPUs call system_timer_tick directly
increment_time_since_boot_hpet();
}
}
m_system_timer->set_callback(TimeManagement::timer_tick);
m_time_keeper_timer->set_callback(TimeManagement::update_time);
system_timer_tick(regs);
});
dbg() << "Reset timers";
m_system_timer->try_to_set_frequency(m_system_timer->calculate_nearest_possible_frequency(1024));
m_time_keeper_timer->try_to_set_frequency(OPTIMAL_TICKS_PER_SECOND_RATE);
// Use the HPET main counter frequency for time purposes. This is likely
// a much higher frequency than the interrupt itself and allows us to
// keep a more accurate time
m_can_query_precise_time = true;
m_time_ticks_per_second = HPET::the().frequency();
m_system_timer->try_to_set_frequency(m_system_timer->calculate_nearest_possible_frequency(OPTIMAL_TICKS_PER_SECOND_RATE));
// We don't need an interrupt for time keeping purposes because we
// can query the timer.
m_time_keeper_timer = m_system_timer;
return true;
}
@ -296,18 +323,43 @@ bool TimeManagement::probe_and_set_legacy_hardware_timers()
}
m_hardware_timers.append(PIT::initialize(TimeManagement::update_time));
m_hardware_timers.append(RealTimeClock::create(TimeManagement::timer_tick));
m_hardware_timers.append(RealTimeClock::create(TimeManagement::system_timer_tick));
m_time_keeper_timer = m_hardware_timers[0];
m_system_timer = m_hardware_timers[1];
// The timer is only as accurate as the interrupts...
m_time_ticks_per_second = m_time_keeper_timer->ticks_per_second();
return true;
}
void TimeManagement::update_time(const RegisterState& regs)
void TimeManagement::update_time(const RegisterState&)
{
TimeManagement::the().increment_time_since_boot(regs);
TimeManagement::the().increment_time_since_boot();
}
void TimeManagement::increment_time_since_boot(const RegisterState&)
void TimeManagement::increment_time_since_boot_hpet()
{
ASSERT(!m_time_keeper_timer.is_null());
ASSERT(m_time_keeper_timer->timer_type() == HardwareTimerType::HighPrecisionEventTimer);
// NOTE: m_seconds_since_boot and m_ticks_this_second are only ever
// updated here! So we can safely read that information, query the clock,
// and when we're all done we can update the information. This reduces
// contention when other processors attempt to read the clock.
auto seconds_since_boot = m_seconds_since_boot;
auto ticks_this_second = m_ticks_this_second;
auto delta_ns = HPET::the().update_time(seconds_since_boot, ticks_this_second, false);
// Now that we have a precise time, go update it as quickly as we can
u32 update_iteration = m_update1.fetch_add(1, AK::MemoryOrder::memory_order_acquire);
m_seconds_since_boot = seconds_since_boot;
m_ticks_this_second = ticks_this_second;
// TODO: Apply m_remaining_epoch_time_adjustment
timespec_add(m_epoch_time, { (time_t)(delta_ns / 1000000000), (long)(delta_ns % 1000000000) }, m_epoch_time);
m_update2.store(update_iteration + 1, AK::MemoryOrder::memory_order_release);
}
void TimeManagement::increment_time_since_boot()
{
ASSERT(!m_time_keeper_timer.is_null());
@ -318,7 +370,7 @@ void TimeManagement::increment_time_since_boot(const RegisterState&)
constexpr time_t MaxSlewNanos = NanosPerTick / 100;
static_assert(MaxSlewNanos < NanosPerTick);
u32 update_iteration = m_update1.fetch_add(1, AK::MemoryOrder::memory_order_relaxed);
u32 update_iteration = m_update1.fetch_add(1, AK::MemoryOrder::memory_order_acquire);
// 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);
@ -338,7 +390,7 @@ void TimeManagement::increment_time_since_boot(const RegisterState&)
m_update2.store(update_iteration + 1, AK::MemoryOrder::memory_order_release);
}
void TimeManagement::timer_tick(const RegisterState& regs)
void TimeManagement::system_timer_tick(const RegisterState& regs)
{
if (Processor::current().in_irq() <= 1) {
// Don't expire timers while handling IRQs

View file

@ -34,10 +34,15 @@
namespace Kernel {
#define OPTIMAL_TICKS_PER_SECOND_RATE 1000
#define OPTIMAL_TICKS_PER_SECOND_RATE 250
class HardwareTimerBase;
enum class TimePrecision {
Coarse = 0,
Precise
};
class TimeManagement {
AK_MAKE_ETERNAL;
@ -47,12 +52,15 @@ public:
static void initialize(u32 cpu);
static TimeManagement& the();
static timespec ticks_to_time(u64 ticks, time_t ticks_per_second);
static u64 time_to_ticks(const timespec& tspec, time_t ticks_per_second);
KResultOr<timespec> current_time(clockid_t clock_id) const;
timespec monotonic_time() const;
timespec epoch_time() const;
static bool is_valid_clock_id(clockid_t);
KResultOr<timespec> current_time(clockid_t) const;
timespec monotonic_time(TimePrecision = TimePrecision::Coarse) const;
timespec monotonic_time_raw() const
{
// TODO: implement
return monotonic_time(TimePrecision::Precise);
}
timespec epoch_time(TimePrecision = TimePrecision::Precise) const;
void set_epoch_time(timespec);
time_t ticks_per_second() const;
time_t boot_time() const;
@ -60,12 +68,13 @@ public:
bool is_system_timer(const HardwareTimerBase&) const;
static void update_time(const RegisterState&);
void increment_time_since_boot(const RegisterState&);
static void update_time_hpet(const RegisterState&);
void increment_time_since_boot_hpet();
void increment_time_since_boot();
static bool is_hpet_periodic_mode_allowed();
u64 uptime_ms() const;
u64 monotonic_ticks() const;
static timeval now_as_timeval();
timespec remaining_epoch_time_adjustment() const { return m_remaining_epoch_time_adjustment; }
@ -78,16 +87,19 @@ private:
Vector<HardwareTimerBase*> scan_for_non_periodic_timers();
NonnullRefPtrVector<HardwareTimerBase> m_hardware_timers;
void set_system_timer(HardwareTimerBase&);
static void timer_tick(const RegisterState&);
static void system_timer_tick(const RegisterState&);
// Variables between m_update1 and m_update2 are synchronized
Atomic<u32> m_update1 { 0 };
u32 m_ticks_this_second { 0 };
u32 m_seconds_since_boot { 0 };
u64 m_seconds_since_boot { 0 };
timespec m_epoch_time { 0, 0 };
timespec m_remaining_epoch_time_adjustment { 0, 0 };
Atomic<u32> m_update2 { 0 };
u32 m_time_ticks_per_second { 0 }; // may be different from interrupts/second (e.g. hpet)
bool m_can_query_precise_time { false };
RefPtr<HardwareTimerBase> m_system_timer;
RefPtr<HardwareTimerBase> m_time_keeper_timer;
};