blob: 47bdca7e399956b67e79aeada94e47db1eec2115 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
// Copyright (c) 2008-2014 Travis Geiselbrecht
// Copyright (c) 2012-2012 Shantanu Gupta
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
/**
* @file
* @brief Mutex functions
*
* @defgroup mutex Mutex
* @{
*/
#include "kernel/mutex.h"
#include <assert.h>
#include <debug.h>
#include <inttypes.h>
#include <lib/affine/ratio.h>
#include <lib/affine/utils.h>
#include <lib/arch/intrin.h>
#include <lib/ktrace.h>
#include <lib/zircon-internal/ktrace.h>
#include <lib/zircon-internal/macros.h>
#include <platform.h>
#include <trace.h>
#include <zircon/errors.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <kernel/auto_preempt_disabler.h>
#include <kernel/lock_trace.h>
#include <kernel/scheduler.h>
#include <kernel/spin_tracing.h>
#include <kernel/task_runtime_timers.h>
#include <kernel/thread.h>
#include <kernel/thread_lock.h>
#include <ktl/type_traits.h>
#include <ktl/enforce.h>
#define LOCAL_TRACE 0
namespace {
enum class KernelMutexTracingLevel {
None, // No tracing is ever done. All code drops out at compile time.
Contested, // Trace events are only generated when mutexes are contested.
All // Trace events are generated for all mutex interactions.
};
// By default, kernel mutex tracing is disabled.
template <KernelMutexTracingLevel = KernelMutexTracingLevel::None, typename = void>
class KTracer;
template <>
class KTracer<KernelMutexTracingLevel::None> {
public:
KTracer() = default;
void KernelMutexUncontestedAcquire(const Mutex* mutex) {}
void KernelMutexUncontestedRelease(const Mutex* mutex) {}
void KernelMutexBlock(const Mutex* mutex, Thread* blocker, uint32_t waiter_count) {}
void KernelMutexWake(const Mutex* mutex, Thread* new_owner, uint32_t waiter_count) {}
};
template <KernelMutexTracingLevel Level>
class KTracer<Level, ktl::enable_if_t<(Level == KernelMutexTracingLevel::Contested) ||
(Level == KernelMutexTracingLevel::All)>> {
public:
KTracer() : ts_(ktrace_timestamp()) {}
void KernelMutexUncontestedAcquire(const Mutex* mutex) {
if constexpr (Level == KernelMutexTracingLevel::All) {
KernelMutexTrace(TAG_KERNEL_MUTEX_ACQUIRE, mutex, nullptr, 0);
}
}
void KernelMutexUncontestedRelease(const Mutex* mutex) {
if constexpr (Level == KernelMutexTracingLevel::All) {
KernelMutexTrace(TAG_KERNEL_MUTEX_RELEASE, mutex, nullptr, 0);
}
}
void KernelMutexBlock(const Mutex* mutex, const Thread* blocker, uint32_t waiter_count) {
KernelMutexTrace(TAG_KERNEL_MUTEX_BLOCK, mutex, blocker, waiter_count);
}
void KernelMutexWake(const Mutex* mutex, const Thread* new_owner, uint32_t waiter_count) {
KernelMutexTrace(TAG_KERNEL_MUTEX_RELEASE, mutex, new_owner, waiter_count);
}
private:
void KernelMutexTrace(uint32_t tag, const Mutex* mutex, const Thread* t, uint32_t waiter_count) {
if (ktrace_thunks::category_enabled("kernel:sched"_category)) {
auto tid_type = fxt::StringRef{(t == nullptr ? "none"_intern
: t->user_thread() == nullptr ? "kernel_mode"_intern
: "user_mode"_intern)};
fxt::Argument mutex_id_arg{"mutex_id"_intern,
fxt::Pointer(reinterpret_cast<uintptr_t>(mutex))};
fxt::Argument tid_name_arg{"tid"_intern,
fxt::Koid(t == nullptr ? ZX_KOID_INVALID : t->tid())};
fxt::Argument tid_type_arg{"tid_type"_intern, tid_type};
fxt::Argument wait_count_arg{"waiter_count"_intern, waiter_count};
auto event_name = fxt::StringRef{(tag == TAG_KERNEL_MUTEX_ACQUIRE ? "mutex_acquire"_intern
: tag == TAG_KERNEL_MUTEX_RELEASE ? "mutex_release"_intern
: tag == TAG_KERNEL_MUTEX_BLOCK ? "mutex_block"_intern
: "unknown"_intern)};
fxt_duration_complete("kernel:sched"_category, ts_, t->fxt_ref(), event_name, ts_ + 50,
mutex_id_arg, tid_name_arg, tid_type_arg, wait_count_arg);
}
}
const uint64_t ts_;
};
} // namespace
Mutex::~Mutex() {
magic_.Assert();
DEBUG_ASSERT(!arch_blocking_disallowed());
if (LK_DEBUGLEVEL > 0) {
if (val() != STATE_FREE) {
Thread* h = holder();
panic("~Mutex(): thread %p (%s) tried to destroy locked mutex %p, locked by %p (%s)\n",
Thread::Current::Get(), Thread::Current::Get()->name(), this, h, h->name());
}
}
val_.store(STATE_FREE, ktl::memory_order_relaxed);
}
// By parameterizing on whether we're going to set a timeslice extension or not
// we can shave a few cycles.
template <bool TimesliceExtensionEnabled>
bool Mutex::AcquireCommon(zx_duration_t spin_max_duration,
TimesliceExtension<TimesliceExtensionEnabled> timeslice_extension) {
magic_.Assert();
DEBUG_ASSERT(!arch_blocking_disallowed());
DEBUG_ASSERT(arch_num_spinlocks_held() == 0);
Thread* const current_thread = Thread::Current::Get();
const uintptr_t new_mutex_state = reinterpret_cast<uintptr_t>(current_thread);
{
// Record whether we set a timeslice extension for later return/rollback. Is marked unused for
// when there is no timeslice extension.
bool set_extension = false;
if constexpr (TimesliceExtensionEnabled) {
// To ensure there is no gap between acquiring the mutex, and setting the timeslice extension,
// we optimistically set the timeslice extension first. Should acquiring the mutex fail we
// will roll it back.
set_extension =
current_thread->preemption_state().SetTimesliceExtension(timeslice_extension.value);
}
// Fast path: The mutex is unlocked and uncontested. Try to acquire it immediately.
//
// We use the weak form of compare exchange here, which is faster on some
// architectures (e.g. aarch64). In the rare case it spuriously fails, the slow
// path will handle it.
uintptr_t old_mutex_state = STATE_FREE;
if (likely(val_.compare_exchange_weak(old_mutex_state, new_mutex_state,
ktl::memory_order_acquire, ktl::memory_order_relaxed))) {
RecordInitialAssignedCpu();
// TODO(maniscalco): Is this the right place to put the KTracer? Seems like
// it should be the very last thing we do.
//
// Don't bother to update the ownership of the wait queue. If another thread
// attempts to acquire the mutex and discovers it to be already locked, it
// will take care of updating the wait queue ownership while it is inside of
// the thread_lock.
KTracer{}.KernelMutexUncontestedAcquire(this);
return set_extension;
}
if constexpr (TimesliceExtensionEnabled) {
if (set_extension) {
current_thread->preemption_state().ClearTimesliceExtension();
}
}
}
return AcquireContendedMutex(spin_max_duration, current_thread, timeslice_extension);
}
template <bool TimesliceExtensionEnabled>
__NO_INLINE bool Mutex::AcquireContendedMutex(
zx_duration_t spin_max_duration, Thread* current_thread,
TimesliceExtension<TimesliceExtensionEnabled> timeslice_extension) {
LOCK_TRACE_DURATION("Mutex::AcquireContended");
// It looks like the mutex is most likely contested (at least, it was when we
// just checked). Enter the adaptive mutex spin phase, where we spin on the
// mutex hoping that the thread which owns the mutex is running on a different
// CPU, and will release the mutex shortly.
//
// If we manage to acquire the mutex during the spin phase, we can simply
// exit, having achieved our goal. Otherwise, there are 3 reasons we may end
// up terminating the spin phase and dropping into a block operation.
//
// 1) We exceed the system's configured |spin_max_duration|.
// 2) The mutex is marked as CONTESTED, meaning that at least one other thread
// has dropped out of its spin phase and blocked on the mutex.
// 3) We think that there is a reasonable chance that the owner of this mutex
// was assigned to the same core that we are running on.
//
// Notes about #3:
//
// In order to implement this behavior, the Mutex class maintains a variable
// called |maybe_acquired_on_cpu_|. This is the system's best guess as to
// which CPU the owner of the mutex may currently be assigned to. The value of
// the variable is set when a thread successfully acquires the mutex, and
// cleared when the thread releases the mutex later on.
//
// This behavior is best effort; the guess is just a guess and could be wrong
// for several legitimate reasons. The owner of the mutex will assign the
// variable to the value of the CPU is it running on immediately after it
// successfully mutates the mutex state to indicate that it owns the mutex.
//
// A spinning thread my observe:
// 1) A value of INVALID_CPU, either because of weak memory ordering, or
// because the thread was preempted after updating the mutex state, but
// before recording the assigned CPU guess.
// 2) An incorrect value of the assigned CPU, again either because of weak
// memory ordering, or because the thread either moved to a different CPU
// or blocked after the guess was recorded.
//
// So, it is possible to keep spinning when we probably shouldn't, and also
// possible to drop out of a spin when we might want to stay in it.
//
// TODO(https://fxbug.dev/42109976): Optimize cache pressure of spinners and default spin max.
const uintptr_t new_mutex_state = reinterpret_cast<uintptr_t>(current_thread);
// Make sure that we don't leave this scope with preemption disabled. If
// we've got a timeslice extension, we're going to disable preemption while
// spinning to ensure that we can't get "preempted early" if we end up
// acquiring the mutex in the spin phase. However, if a preemption becomes
// pending while spinning, we'll briefly enable then disable preemption to
// allow a reschedule.
AutoPreemptDisabler preempt_disabler(AutoPreemptDisabler::Defer);
if constexpr (TimesliceExtensionEnabled) {
preempt_disabler.Disable();
}
// Remember the last call to current_ticks.
zx_ticks_t now_ticks = current_ticks();
spin_tracing::Tracer<kSchedulerLockSpinTracingEnabled> spin_tracer{now_ticks};
const affine::Ratio time_to_ticks = platform_get_ticks_to_time_ratio().Inverse();
const zx_ticks_t spin_until_ticks =
affine::utils::ClampAdd(now_ticks, time_to_ticks.Scale(spin_max_duration));
do {
uintptr_t old_mutex_state = STATE_FREE;
// Attempt to acquire the mutex by swapping out "STATE_FREE" for our current thread.
//
// We use the weak form of compare exchange here: it saves an extra
// conditional branch on ARM, and if it fails spuriously, we'll just
// loop around and try again.
//
if (likely(val_.compare_exchange_weak(old_mutex_state, new_mutex_state,
ktl::memory_order_acquire, ktl::memory_order_relaxed))) {
spin_tracer.Finish(spin_tracing::FinishType::kLockAcquired, this->encoded_lock_id());
RecordInitialAssignedCpu();
// Same as above in the fastest path: leave accounting to later contending
// threads.
KTracer{}.KernelMutexUncontestedAcquire(this);
if constexpr (TimesliceExtensionEnabled) {
return Thread::Current::preemption_state().SetTimesliceExtension(timeslice_extension.value);
}
return false;
}
// Stop spinning if the mutex is or becomes contested. All spinners convert
// to blocking when the first one reaches the max spin duration.
if (old_mutex_state & STATE_FLAG_CONTESTED) {
break;
}
{
// Stop spinning if it looks like we might be running on the same CPU which
// was assigned to the owner of the mutex.
//
// Note: The accuracy of |curr_cpu_num| depends on whether preemption is
// currently enabled or not and whether we re-enable it below.
const cpu_num_t curr_cpu_num = arch_curr_cpu_num();
if (curr_cpu_num == maybe_acquired_on_cpu_.load(ktl::memory_order_relaxed)) {
break;
}
if constexpr (TimesliceExtensionEnabled) {
// If this CPU has a preemption pending, briefly enable then disable
// preemption to give this CPU a chance to reschedule.
const cpu_mask_t curr_cpu_mask = cpu_num_to_mask(arch_curr_cpu_num());
if ((Thread::Current::preemption_state().preempts_pending() & curr_cpu_mask) != 0) {
// Reenable preemption to trigger a local reschedule and then disable it again.
preempt_disabler.Enable();
preempt_disabler.Disable();
}
}
}
// Give the arch a chance to relax the CPU.
arch::Yield();
now_ticks = current_ticks();
} while (now_ticks < spin_until_ticks);
// Capture the end-of-spin timestamp for our spin tracer, but do not finish
// the event just yet. We don't actually know if we are going to block or not
// yet; we have one last chance to grab the lock after we obtain a few more
// spinlocks. Once we have dropped into the final locks, we should be able to
// produce our spin-record using the timestamp explicitly recorded here.
spin_tracing::SpinTracingTimestamp spin_end_ts{};
if ((LK_DEBUGLEVEL > 0) && unlikely(this->IsHeld())) {
panic("Mutex::Acquire: thread %p (%s) tried to acquire mutex %p it already owns.\n",
current_thread, current_thread->name(), this);
}
ContentionTimer timer(current_thread, now_ticks);
// |OwnedWaitQueue::BlockAndAssignOwner| requires that preemption be disabled.
preempt_disabler.Disable();
{
// we contended with someone else, will probably need to block
Guard<MonitoredSpinLock, IrqSave> guard{ThreadLock::Get(), SOURCE_TAG};
// Check if the queued flag is currently set. The contested flag can only be changed
// whilst the thread lock is held so we know we aren't racing with anyone here. This
// is just an optimization and allows us to avoid redundantly doing the atomic OR.
uintptr_t old_mutex_state = val();
if (unlikely(!(old_mutex_state & STATE_FLAG_CONTESTED))) {
// Set the queued flag to indicate that we're blocking.
//
// We may find the old state was |STATE_FREE| if we raced with the
// holder as they dropped the mutex. We use the |acquire| memory ordering
// in the |fetch_or| just in case this happens, to ensure we see the memory
// released by the previous lock holder.
old_mutex_state = val_.fetch_or(STATE_FLAG_CONTESTED, ktl::memory_order_acquire);
if (unlikely(old_mutex_state == STATE_FREE)) {
// Since we set the contested flag we know that there are no
// waiters and no one is able to perform fast path acquisition.
// Therefore we can just take the mutex, and remove the queued
// flag.
val_.store(new_mutex_state, ktl::memory_order_relaxed);
RecordInitialAssignedCpu();
spin_tracer.Finish(spin_tracing::FinishType::kLockAcquired, this->encoded_lock_id(),
spin_end_ts);
if constexpr (TimesliceExtensionEnabled) {
return Thread::Current::preemption_state().SetTimesliceExtension(
timeslice_extension.value);
}
return false;
}
}
spin_tracer.Finish(spin_tracing::FinishType::kBlocked, this->encoded_lock_id(), spin_end_ts);
const uint64_t flow_id = current_thread->TakeNextLockFlowId();
LOCK_TRACE_FLOW_BEGIN("contend_mutex", flow_id);
// extract the current holder of the mutex from oldval, no need to
// re-read from the mutex as it cannot change if the queued flag is set
// without holding the thread lock (which we currently hold). We need
// to be sure that we inform our owned wait queue that this is the
// proper queue owner as we block.
Thread* cur_owner = holder_from_val(old_mutex_state);
KTracer{}.KernelMutexBlock(this, cur_owner, wait_.Count() + 1);
zx_status_t ret = wait_.BlockAndAssignOwner(Deadline::infinite(), cur_owner,
ResourceOwnership::Normal, Interruptible::No);
if (unlikely(ret < ZX_OK)) {
// mutexes are not interruptible and cannot time out, so it
// is illegal to return with any error state.
panic("Mutex::Acquire: wait queue block returns with error %d m %p, thr %p, sp %p\n", ret,
this, current_thread, __GET_FRAME());
}
// someone must have woken us up, we should own the mutex now
DEBUG_ASSERT(current_thread == holder());
LOCK_TRACE_FLOW_END("contend_mutex", flow_id);
}
if constexpr (TimesliceExtensionEnabled) {
return Thread::Current::preemption_state().SetTimesliceExtension(timeslice_extension.value);
}
return false;
}
inline uintptr_t Mutex::TryRelease(Thread* current_thread) {
// Try the fast path. Assume that we are locked, but uncontested.
uintptr_t old_mutex_state = reinterpret_cast<uintptr_t>(current_thread);
if (likely(val_.compare_exchange_strong(old_mutex_state, STATE_FREE, ktl::memory_order_release,
ktl::memory_order_relaxed))) {
// We're done. Since this mutex was uncontested, we know that we were
// not receiving any priority pressure from the wait queue, and there is
// nothing further to do.
KTracer{}.KernelMutexUncontestedRelease(this);
return STATE_FREE;
}
// The mutex is contended, return the current state of the mutex.
return old_mutex_state;
}
__NO_INLINE void Mutex::ReleaseContendedMutex(Thread* current_thread, uintptr_t old_mutex_state) {
LOCK_TRACE_DURATION("Mutex::ReleaseContended");
// Sanity checks. The mutex should have been either locked by us and
// uncontested, or locked by us and contested. Anything else is an internal
// consistency error worthy of a panic.
if (LK_DEBUGLEVEL > 0) {
uintptr_t expected_state = reinterpret_cast<uintptr_t>(current_thread) | STATE_FLAG_CONTESTED;
if (unlikely(old_mutex_state != expected_state)) {
auto other_holder = reinterpret_cast<Thread*>(old_mutex_state & ~STATE_FLAG_CONTESTED);
panic(
"Mutex::ReleaseContendedMutex: sanity check failure. Thread %p (%s) tried to release "
"mutex %p. Expected state (%lx) != observed state (%lx). Other holder (%s)\n",
current_thread, current_thread->name(), this, expected_state, old_mutex_state,
other_holder ? other_holder->name() : "<none>");
}
}
// Attempt to release a thread. If there are still waiters in the queue
// after we successfully have woken a thread, be sure to assign ownership of
// the queue to the thread which was woken so that it can properly receive
// the priority pressure of the remaining waiters.
using Action = OwnedWaitQueue::Hook::Action;
Thread* woken;
auto cbk = [](Thread* woken, void* ctx) -> Action {
*(reinterpret_cast<Thread**>(ctx)) = woken;
return Action::SelectAndAssignOwner;
};
KTracer tracer;
{
// Changes in ownership of node in a PI graph have the potential to affect
// the running/runnable status of multiple threads at the same time.
// Because of this, the OwnedWaitQueue class requires that we disable eager
// rescheduling in order to optimize a situation where we might otherwise
// send multiple (redundant) IPIs to the same CPUs during one PI graph
// mutation.
//
// Note that this is an optimization, not a requirement. What _is_ a
// requirement is that we keep preemption disabled during the PI
// propagation. Currently, all of the invariants of OwnedWaitQueues and PI
// graphs are protected by a single global "thread lock" which must be held
// when a thread calls into the scheduler. If a change to a PI graph would
// cause the current scheduler to choose a different thread to run on that
// CPU, however, the current thread will be preempted, and the ownership of
// the thread lock will be transferred to the newly selected thread. As the
// new thread unwinds, it is going to drop the thread lock and return to
// executing, leaving the first thread in the middle of what was supposed to
// be an atomic operation.
//
// Because if this, it is critically important that local preemption be
// disabled (at a minimum) when mutating a PI graph. In the case that a
// thread eventually blocks, the OWQ code will make sure that all invariants
// will be restored before the thread finally blocks (and eventually wakes
// and unwinds).
AutoEagerReschedDisabler eager_resched_disabler;
wait_.WakeThreads(1, {cbk, &woken});
}
tracer.KernelMutexWake(this, woken, wait_.Count());
// So, the mutex is now in one of three states. It can be...
//
// 1) Owned and contested (we woke a thread up, and there are still waiters)
// 2) Owned and uncontested (we woke a thread up, but it was the last one)
// 3) Unowned (no thread woke up when we tried to wake one)
//
// Note, the only way to be in situation #3 is for the lock to have become
// contested at some point in the past, but then to have a thread stop
// waiting for the lock before acquiring it (either it timed out or was
// killed).
//
uintptr_t new_mutex_state;
if (woken != nullptr) {
LOCK_TRACE_FLOW_STEP("contend_mutex", woken->lock_flow_id());
// We woke _someone_ up. We be in situation #1 or #2
new_mutex_state = reinterpret_cast<uintptr_t>(woken);
if (!wait_.IsEmpty()) {
// Situation #1.
DEBUG_ASSERT(wait_.owner() == woken);
new_mutex_state |= STATE_FLAG_CONTESTED;
} else {
// Situation #2.
DEBUG_ASSERT(wait_.owner() == nullptr);
}
} else {
DEBUG_ASSERT(wait_.IsEmpty());
DEBUG_ASSERT(wait_.owner() == nullptr);
new_mutex_state = STATE_FREE;
}
if (unlikely(!val_.compare_exchange_strong(old_mutex_state, new_mutex_state,
ktl::memory_order_release,
ktl::memory_order_relaxed))) {
panic("bad state (%lx != %lx) in mutex release %p, current thread %p\n",
reinterpret_cast<uintptr_t>(current_thread) | STATE_FLAG_CONTESTED, old_mutex_state, this,
current_thread);
}
}
void Mutex::Release() {
magic_.Assert();
DEBUG_ASSERT(!arch_blocking_disallowed());
Thread* current_thread = Thread::Current::Get();
ClearInitialAssignedCpu();
if (const uintptr_t old_mutex_state = TryRelease(current_thread); old_mutex_state != STATE_FREE) {
// Disable preemption to prevent switching to the woken thread inside of
// WakeThreads() if it is assigned to this CPU. If the woken thread is
// assigned to a different CPU, the thread lock prevents it from observing
// the inconsistent owner before the correct owner is recorded.
AnnotatedAutoPreemptDisabler preempt_disable;
Guard<MonitoredSpinLock, IrqSave> guard{ThreadLock::Get(), SOURCE_TAG};
ReleaseContendedMutex(current_thread, old_mutex_state);
}
}
void Mutex::ReleaseThreadLocked() {
magic_.Assert();
DEBUG_ASSERT(!arch_blocking_disallowed());
DEBUG_ASSERT(arch_ints_disabled());
preempt_disabled_token.AssertHeld();
thread_lock.AssertHeld();
Thread* current_thread = Thread::Current::Get();
ClearInitialAssignedCpu();
if (const uintptr_t old_mutex_state = TryRelease(current_thread); old_mutex_state != STATE_FREE) {
ReleaseContendedMutex(current_thread, old_mutex_state);
}
}
// Explicit instantiations since it's not defined in the header.
template bool Mutex::AcquireCommon(zx_duration_t spin_max_duration, TimesliceExtension<false>);
template bool Mutex::AcquireCommon(zx_duration_t spin_max_duration, TimesliceExtension<true>);