blob: c0ae2aa774bff16cd68db219c78731eb820f5358 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors
//
// 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
#ifndef ZIRCON_KERNEL_INCLUDE_KERNEL_OWNED_WAIT_QUEUE_H_
#define ZIRCON_KERNEL_INCLUDE_KERNEL_OWNED_WAIT_QUEUE_H_
#include <fbl/canary.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/macros.h>
#include <kernel/thread.h>
#include <kernel/thread_lock.h>
#include <kernel/wait.h>
// Owned wait queues are an extension of wait queues which adds the concept of
// ownership for use when priority inheritance semantics are needed.
//
// An owned wait queue maintains an unmanaged pointer to a Thread in order to
// track who owns it at any point in time. In addition, it contains node state
// which can be used by the owning thread in order to track the wait queues that
// the thread is currently an owner of. This also makes use of unmanaged
// pointer.
//
// It should be an error for any thread to destruct while it owns an
// OwnedWaitQueue. Likewise, it should be an error for any wait queue to
// destruct while it has an owner. These invariants are enforced in the
// destructor for OwnedWaitQueue and Thread. This enforcement is considered
// to be the reasoning why holding unmanaged pointers is considered to be safe.
//
class OwnedWaitQueue : public WaitQueue, public fbl::DoublyLinkedListable<OwnedWaitQueue*> {
public:
// A small helper class which can be injected into Wake and Requeue
// operations to allow calling code to get a callback for each thread which
// is either woken, or requeued. This callback serves two purposes...
//
// 1) It allows the caller to perform some limited filtering operations, and
// to choose which thread (if any) becomes the new owner of the queue.
// See the comments in the |Action| enum member for details.
// 2) It gives code such as |FutexContext| a chance to perform their own
// per-thread bookkeeping as the wait queue code chooses which threads to
// either wake or re-queue.
//
// Note that during a wake or requeue operation, the threads being
// considered will each be presented to the user provided Hook (if any)
// by the OwnedWaitQueue code before deciding whether or not to actually
// wake or requeue the thread.
class Hook {
public:
// A set of 3 actions which may be taken when considering whether or not
// to wake or requeue a thread. If no user supplied Hook is provided
// for a given operation, the default behavior will be to return
// Action::SelectAndKeepGoing.
enum class Action {
// Do not wake or requeue this thread and stop considering threads.
Stop,
// Select this thread to be either woken or requeued, then continue
// to consider more threads (if any). Do not assign this thread to
// be the owner.
SelectAndKeepGoing,
// Select this thread to be either woken or requeued, assign it to
// to be the owner of the queue, then stop considering more threads.
// It is illegal to wake a thread and assign it as the owner for the
// queue if at least one thread has already been woken.
SelectAndAssignOwner,
};
using Callback = Action (*)(Thread* thrd, void* ctx);
Hook() : cbk_(nullptr) {}
Hook(Callback cbk, void* ctx) : cbk_(cbk), ctx_(ctx) {}
Action operator()(Thread* thrd) const TA_REQ(thread_lock) {
return cbk_ ? cbk_(thrd, ctx_) : Action::SelectAndKeepGoing;
}
private:
Callback cbk_;
void* ctx_;
};
static constexpr uint32_t kOwnedMagic = fbl::magic("ownq");
constexpr OwnedWaitQueue() : WaitQueue(kOwnedMagic) {}
~OwnedWaitQueue();
// No copy or move is permitted.
DISALLOW_COPY_ASSIGN_AND_MOVE(OwnedWaitQueue);
// Release ownership of all wait queues currently owned by |t| and update
// bookkeeping as appropriate. This is meant to be called from the thread
// itself and therefor it is assumed that the thread in question is not
// blocked on any other wait queues.
static void DisownAllQueues(Thread* t) TA_REQ(thread_lock);
// const accessor for the owner member.
Thread* owner() const TA_REQ(thread_lock) { return owner_; }
// Debug Assert wrapper which skips the thread analysis checks just to
// assert that a specific queue is unowned. Used by FutexContext
void AssertNotOwned() const TA_NO_THREAD_SAFETY_ANALYSIS { DEBUG_ASSERT(owner_ == nullptr); }
// Assign ownership of this wait queue to |new_owner|, or explicitly release
// ownership if |new_owner| is nullptr.
//
// Note, if the new owner exists, but is dead or dying, it will not be
// permitted to become the new owner of the wait_queue. Any existing owner
// will be replaced with no owner in this situation.
void AssignOwner(Thread* new_owner) TA_REQ(thread_lock, preempt_disabled_token) {
DEBUG_ASSERT(magic() == kOwnedMagic);
if (new_owner != owner()) {
UpdateBookkeeping(new_owner, BlockedPriority());
}
}
// Block the current thread on this wait queue, and re-assign ownership to
// the specified thread (or remove ownership if new_owner is null);
//
// Note, if the new owner exists, but is dead or dying, it will not be
// permitted to become the new owner of the wait_queue. Any existing owner
// will be replaced with no owner in this situation.
zx_status_t BlockAndAssignOwner(const Deadline& deadline, Thread* new_owner,
ResourceOwnership resource_ownership, Interruptible interruptible)
TA_REQ(thread_lock, preempt_disabled_token);
// Wake the up to specified number of threads from the wait queue and then
// handle the ownership bookkeeping based on what the Hook told us to do.
// See |Hook::Action| for details.
void WakeThreads(uint32_t wake_count, Hook on_thread_wake_hook = {})
TA_REQ(thread_lock, preempt_disabled_token);
// A specialization of WakeThreads which will...
//
// 1) Wake the number of threads indicated by |wake_count|
// 2) Move the number of threads indicated by |requeue_count| to the |requeue_target|.
// 3) Update ownership bookkeeping as indicated by |owner_action| and |requeue_owner|.
//
// This method is used by futexes in order to implement futex_requeue. It
// is wrapped up into a specialized form instead of broken into individual
// parts in order to minimize any thrash in re-computing effective
// priorities for PI purposes. We don't want to re-evaluate ownership or PI
// pressure until after all of the changes to wait queue have taken place.
//
// |requeue_target| *must* be non-null. If there is no |requeue_target|,
// use WakeThreads instead.
//
// Note, if the |requeue_owner| exists, but is dead or dying, it will not be
// permitted to become the new owner of the |requeue_target|. Any existing
// owner will be replaced with no owner in this situation.
void WakeAndRequeue(uint32_t wake_count, OwnedWaitQueue* requeue_target, uint32_t requeue_count,
Thread* requeue_owner, Hook on_thread_wake_hook = {},
Hook on_thread_requeue_hook = {}) TA_REQ(thread_lock, preempt_disabled_token);
private:
// Give permission to the WaitQueue thunk to call the
// WaitersPriorityChanged method (below).
friend void WaitQueue::UpdatePriority(int old_prio);
// Called whenever the pressure of a wait queue currently owned by |t| has
// just changed. Propagates priority inheritance side effects.
//
// It is an error to call this function if |old_prio| == |new_prio|. Be
// sure to check inline before calling.
static void QueuePressureChanged(Thread* t, int old_prio, int new_prio)
TA_REQ(thread_lock, preempt_disabled_token);
// A hook called by the WaitQueue level when the maximum priority across all
// current waiters has changed.
void WaitersPriorityChanged(int old_prio) TA_REQ(thread_lock, preempt_disabled_token);
// Updates ownership bookkeeping and deals with priority inheritance side
// effects. Called by internal code, typically after changes to the
// contents of the queue have been made which may have an effect of the
// maximum priority of the set of waiters.
//
// |new_owner|
// A pointer to the thread which should be the owner of this wait queue,
// or nullptr if this queue should have no owner.
//
// |old_prio|
// The priority of this wait queue as recorded by the caller before
// they started to make changes to the queue's contents.
void UpdateBookkeeping(Thread* new_owner, int old_prio)
TA_REQ(thread_lock, preempt_disabled_token);
// Wake the specified number of threads from the wait queue, and return the
// new owner (first thread woken) via the |out_new_owner| out param, or
// nullptr if there should be no new owner. This code is shared by Wake as
// well as WakeAndRequeue. Doing so allows us to preserve common code, and
// to defer the PI pressure recalculations until the point at which all of
// the queue manipulations have taken place.
void WakeThreadsInternal(uint32_t wake_count, Thread** out_new_owner, zx_time_t now,
Hook on_thread_wake_hook) TA_REQ(thread_lock, preempt_disabled_token);
Thread* owner_ TA_GUARDED(thread_lock) = nullptr;
};
#endif // ZIRCON_KERNEL_INCLUDE_KERNEL_OWNED_WAIT_QUEUE_H_