blob: ea030777750116fec34af55f4feb17fb5aff34d7 [file] [log] [blame]
// Copyright 2016 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_OBJECT_INCLUDE_OBJECT_THREAD_DISPATCHER_H_
#define ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_THREAD_DISPATCHER_H_
#include <platform.h>
#include <sys/types.h>
#include <zircon/compiler.h>
#include <zircon/syscalls/debug.h>
#include <zircon/syscalls/exception.h>
#include <zircon/types.h>
#include <arch/exception.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <kernel/event.h>
#include <kernel/owned_wait_queue.h>
#include <kernel/thread.h>
#include <ktl/atomic.h>
#include <ktl/string_view.h>
#include <object/channel_dispatcher.h>
#include <object/dispatcher.h>
#include <object/exceptionate.h>
#include <object/futex_context.h>
#include <object/handle.h>
#include <object/thread_state.h>
#include <vm/vm_address_region.h>
class ProcessDispatcher;
class ThreadDispatcher final : public SoloDispatcher<ThreadDispatcher, ZX_DEFAULT_THREAD_RIGHTS>,
public fbl::DoublyLinkedListable<ThreadDispatcher*> {
public:
// When in a blocking syscall, or blocked in an exception, the blocking reason.
// There is one of these for each syscall marked "blocking".
// See //zircon/vdso.
enum class Blocked {
// Not blocked.
NONE,
// The thread is blocked in an exception.
EXCEPTION,
// The thread is sleeping (zx_nanosleep).
SLEEPING,
// zx_futex_wait
FUTEX,
// zx_port_wait
PORT,
// zx_channel_call
CHANNEL,
// zx_object_wait_one
WAIT_ONE,
// zx_object_wait_many
WAIT_MANY,
// zx_interrupt_wait
INTERRUPT,
// pager
PAGER,
};
// Entry state for a thread
struct EntryState {
uintptr_t pc = 0;
uintptr_t sp = 0;
uintptr_t arg1 = 0;
uintptr_t arg2 = 0;
};
static zx_status_t Create(fbl::RefPtr<ProcessDispatcher> process, uint32_t flags,
ktl::string_view name, KernelHandle<ThreadDispatcher>* out_handle,
zx_rights_t* out_rights);
~ThreadDispatcher();
static ThreadDispatcher* GetCurrent() { return Thread::Current::Get()->user_thread(); }
// Terminates the current thread. Does not return.
static void ExitCurrent() __NO_RETURN { Thread::Current::Exit(0); }
// Marks the current thread for termination. The thread will actually termiante when
// the kernel stack unwinds.
static void KillCurrent() { Thread::Current::Kill(); }
// Dispatcher implementation.
zx_obj_type_t get_type() const final { return ZX_OBJ_TYPE_THREAD; }
zx_koid_t get_related_koid() const final;
// Sets whether or not this is the initial thread in its process.
// Should only be called by ProcessDispatcher upon adding the initialized thread.
void set_is_initial_thread(bool is_initial_thread) { is_initial_thread_ = is_initial_thread; }
// Performs initialization on a newly constructed ThreadDispatcher
// If this fails, then the object is invalid and should be deleted
zx_status_t Initialize() TA_EXCL(get_lock());
// Start this thread running inside the parent process with the provided entry state, only
// valid to be called on a thread in the INITIALIZED state that has not yet been started. If
// `ensure_initial_thread` is true, the thread will only start if it is the first thread in the
// process.
zx_status_t Start(const EntryState& entry, bool ensure_initial_thread);
// Transitions a thread from the INITIALIZED state to either the RUNNING or SUSPENDED state.
// Is the caller's responsibility to ensure this thread is registered with the parent process,
// as such this is only expected to be called from the ProcessDispatcher.
zx_status_t MakeRunnable(const EntryState& entry, bool suspended);
void Kill();
// Suspends the thread.
// Returns ZX_OK on success, or ZX_ERR_BAD_STATE iff the thread is dying or dead.
zx_status_t Suspend();
void Resume();
// Issues a restricted kick on the thread which will kick the thread out of restricted
// mode to normal mode if it's currently in restricted mode or remember the kick state for
// the next attempt to enter restricted state.
// Returns ZX_OK on success or ZX_ERR_BAD_STATE iff the thread is dying or dead.
zx_status_t RestrictedKick();
// accessors
ProcessDispatcher* process() const { return process_.get(); }
// Returns true if the thread is dying or dead. Threads never return to a previous state
// from dying/dead so once this is true it will never flip back to false.
bool IsDyingOrDead() const TA_EXCL(get_lock());
// Returns true if the thread was ever started (even if it is dead now).
// Threads never return to an INITIAL state after starting, so once this is
// true it will never flip back to false.
bool HasStarted() const TA_EXCL(get_lock());
[[nodiscard]] zx_status_t set_name(const char* name, size_t len) final __NONNULL((2))
TA_EXCL(get_lock());
[[nodiscard]] zx_status_t get_name(char (&out_name)[ZX_MAX_NAME_LEN]) const final
TA_EXCL(get_lock());
// Assuming the thread is stopped waiting for an exception response,
// fill in |*report| with the exception report.
// Returns ZX_ERR_BAD_STATE if not in an exception.
zx_status_t GetExceptionReport(zx_exception_report_t* report);
Exceptionate* exceptionate();
// Sends an exception over the exception channel and blocks for a response.
//
// |sent| will indicate whether the exception was successfully sent over
// the given |exceptionate| channel. This can be used in the ZX_ERR_NEXT
// case to determine whether the exception channel didn't exist or it did
// exist but the receiver opted not to handle the exception.
//
// Returns:
// ZX_OK if the exception was processed and the thread should resume.
// ZX_ERR_NEXT if there is no channel or the receiver opted to skip.
// ZX_ERR_NO_MEMORY on allocation failure.
// ZX_ERR_INTERNAL_INTR_KILLED if the thread was killed before
// receiving a response.
zx_status_t HandleException(Exceptionate* exceptionate,
fbl::RefPtr<ExceptionDispatcher> exception, bool* sent);
// Similar to HandleException(), but for single-shot exceptions which are
// sent to at most one handler, e.g. ZX_EXCP_THREAD_STARTING.
//
// The main difference is that this takes |exception_type| and |context|
// rather than a full exception object, and internally sets up the required
// state and creates the exception object.
//
// Returns true if the exception was sent.
bool HandleSingleShotException(Exceptionate* exceptionate, zx_excp_type_t exception_type,
const arch_exception_context_t& context) TA_EXCL(get_lock());
// Fetch the state of the thread for userspace tools.
zx_status_t GetInfoForUserspace(zx_info_thread_t* info);
// Fetch per thread stats for userspace.
zx_status_t GetStatsForUserspace(zx_info_thread_stats_t* info) TA_EXCL(get_lock());
// Fetch a consistent snapshot of the runtime stats, compensated for unaccumulated runtime in the
// ready or running state.
TaskRuntimeStats GetCompensatedTaskRuntimeStats() const;
// For debugger usage.
zx_status_t ReadState(zx_thread_state_topic_t state_kind, user_out_ptr<void> buffer,
size_t buffer_size) TA_EXCL(get_lock());
zx_status_t WriteState(zx_thread_state_topic_t state_kind, user_in_ptr<const void> buffer,
size_t buffer_size) TA_EXCL(get_lock());
// Profile support
zx_status_t SetBaseProfile(const SchedulerState::BaseProfile& profile) TA_EXCL(get_lock());
zx_status_t SetSoftAffinity(cpu_mask_t mask) TA_EXCL(get_lock());
// Thread Sampling Support
zx_status_t EnableStackSampling(uint64_t sampler_id) TA_EXCL(get_lock());
uint64_t SamplerId() const TA_EXCL(get_lock()) {
Guard<CriticalMutex> guard{get_lock()};
return sampler_id_;
}
void DisableStackSampling() TA_EXCL(get_lock()) {
Guard<CriticalMutex> guard{get_lock()};
sampler_id_ = ZX_KOID_INVALID;
}
// For ChannelDispatcher use.
ChannelDispatcher::MessageWaiter* GetMessageWaiter() { return &channel_waiter_; }
// Blocking syscalls, once they commit to a path that will likely block the
// thread, use this helper class to properly set/restore |blocked_reason_|.
class AutoBlocked final {
public:
explicit AutoBlocked(Blocked reason)
: thread_(ThreadDispatcher::GetCurrent()),
prev_reason(thread_->blocked_reason_.load(ktl::memory_order_acquire)) {
DEBUG_ASSERT(reason != Blocked::NONE);
thread_->blocked_reason_.store(reason, ktl::memory_order_release);
}
~AutoBlocked() { thread_->blocked_reason_.store(prev_reason, ktl::memory_order_release); }
private:
ThreadDispatcher* const thread_;
const Blocked prev_reason;
};
// This is called from Thread as it is exiting, just before it stops for good.
// It is an error to call this on anything other than the current thread.
void ExitingCurrent();
// callback from kernel when thread is suspending
void Suspending();
// callback from kernel when thread is resuming
void Resuming();
// Update the runtime stats for this thread/process. This is called by
// Scheduler to update the runtime stats of the thread as it changes thread
// state.
//
// Must be called with interrupts disabled.
void UpdateRuntimeStats(thread_state new_state);
// Update time spent handling page faults. This is called by the VM during page fault handling.
void AddPageFaultTicks(zx_ticks_t ticks);
// Update time spent contended on locks. This is called by lock implementations.
void AddLockContentionTicks(zx_ticks_t ticks);
private:
ThreadDispatcher(fbl::RefPtr<ProcessDispatcher> process, uint32_t flags);
ThreadDispatcher(const ThreadDispatcher&) = delete;
ThreadDispatcher& operator=(const ThreadDispatcher&) = delete;
// friend FutexContext so that it can manipulate the blocking_futex_id_ member of
// ThreadDispatcher, and so that it can access the "thread_" member of the class so that
// wait_queue operations can be performed on ThreadDispatchers
friend class FutexContext;
// OwnedWaitQueue is a friend only so that it can access the blocking_futex_id_ member for tracing
// purposes.
friend class OwnedWaitQueue;
// kernel level entry point
static int StartRoutine(void* arg);
// Return true if waiting for an exception response.
bool InExceptionLocked() TA_REQ(get_lock());
// Returns true if the thread is suspended or processing an exception.
bool SuspendedOrInExceptionLocked() TA_REQ(get_lock());
// change states of the object, do what is appropriate for the state transition
void SetStateLocked(ThreadState::Lifecycle lifecycle) TA_REQ(get_lock());
bool IsDyingOrDeadLocked() const TA_REQ(get_lock());
bool HasStartedLocked() const TA_REQ(get_lock());
template <typename T, typename F>
zx_status_t ReadStateGeneric(F get_state_func, user_out_ptr<void> buffer, size_t buffer_size)
TA_EXCL(get_lock());
template <typename T, typename F>
zx_status_t WriteStateGeneric(F set_state_func, user_in_ptr<const void> buffer,
size_t buffer_size) TA_EXCL(get_lock());
// a ref pointer back to the parent process.
const fbl::RefPtr<ProcessDispatcher> process_;
// The runtime stats for this thread. Placed near the front of ThreadDispatcher due to frequent
// updates by the scheduler.
ThreadRuntimeStats runtime_stats_;
// The thread as understood by the lower kernel. This is set to nullptr when
// `state_` transitions to DEAD.
Thread* core_thread_ TA_GUARDED(get_lock()) = nullptr;
// User thread starting register values.
EntryState user_entry_;
ThreadState state_ TA_GUARDED(get_lock());
// This is only valid while |state_.lifecycle()| is RUNNING.
//
// This field is an atomic because it may be accessed concurrently by multiple
// threads. It may be read by any thread, but may only be updated by the
// "this" thread.
//
// In general, loads of this field should be performed with acquire semantics
// and stores with release semantics because this field is used to synchronize
// threads (think: wait for a thread to become blocked, then inspect some
// state the thread has written).
//
// Because this is simply an atomic, readers must be OK with observing stale
// values. That is, by the time a reader can take action on the value, the
// value may no longer be accurate.
ktl::atomic<Blocked> blocked_reason_ = Blocked::NONE;
// Support for sending an exception to an exception handler and then waiting for a response.
// Exceptionates have internal locking so we don't need to guard it here.
Exceptionate exceptionate_;
// Non-null if the thread is currently processing a channel exception.
fbl::RefPtr<ExceptionDispatcher> exception_ TA_GUARDED(get_lock());
// Holds the type of the exceptionate currently processing the exception,
// which may be our |exceptionate_| or one of our parents'.
uint32_t exceptionate_type_ TA_GUARDED(get_lock()) = ZX_EXCEPTION_CHANNEL_TYPE_NONE;
// Tracks the number of times Suspend() has been called. Resume() will resume this thread
// only when this reference count reaches 0.
int suspend_count_ TA_GUARDED(get_lock()) = 0;
// Per-thread structure used while waiting in a ChannelDispatcher::Call.
// Needed to support the requirements of being able to interrupt a Call
// in order to suspend a thread.
ChannelDispatcher::MessageWaiter channel_waiter_;
// If true and ancestor job has a debugger attached, thread will block on
// start and will send a process start exception.
bool is_initial_thread_ = false;
// The ID of the futex we are currently waiting on, or 0 if we are not
// waiting on any futex at the moment.
//
// TODO(johngro): figure out some way to apply clang static thread analysis
// to this. Right now, there is no good (cost free) way for the compiler to
// figure out that this thread belongs to a specific process/futex-context,
// and therefor the thread's futex-context lock can be used to guard this
// futex ID.
FutexId blocking_futex_id_{FutexId::Null()};
// Marker to denote that thread sampling has been requested for this thread and that we should
// take a sample when we handle THREAD_SIGNAL_SAMPLE_STACK.
//
// A thread should have its thread_sampling_session checked to ensure it matches the current
// session before taking a sample.
//
// The session is associated with a specific thread sampler's koid. When a thread is marked to be
// sampled, its thread_sampling_session will be set to the current sampler's koid. This allows us
// to eliminate the need to track when threads we have marked to be sampled and avoid iterating
// through to clean up after a session as when a new session is created, the koid recorded in
// `sampler_id_` will no longer match.
uint64_t sampler_id_ TA_GUARDED(get_lock()){0};
};
#endif // ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_THREAD_DISPATCHER_H_