blob: f073a87cae1c3178bb2a7016e952b5e8d9f0e3bb [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_JOB_DISPATCHER_H_
#define ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_JOB_DISPATCHER_H_
#include <stdint.h>
#include <zircon/types.h>
#include <fbl/array.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/name.h>
#include <fbl/ref_counted.h>
#include <kernel/lockdep.h>
#include <ktl/array.h>
#include <object/dispatcher.h>
#include <object/exceptionate.h>
#include <object/handle.h>
#include <object/job_policy.h>
#include <object/process_dispatcher.h>
class JobNode;
// Job signal that is active when a job has no children (i.e., no child jobs and no child
// processes).
//
// TODO(fxbug.dev/53986): This is a temporary signal that we don't want userspace using (yet?). Either
// expose this signal to userspace in "zircon/types.h", or remove this signal.
#define ZX_JOB_NO_CHILDREN __ZX_OBJECT_SIGNAL_6
// Assume the typical set-policy call has 8 items or less.
constexpr size_t kPolicyBasicInlineCount = 8;
// Interface for walking a job/process tree.
class JobEnumerator {
public:
// Visits a job. If OnJob returns false, the enumeration stops.
virtual bool OnJob(JobDispatcher* job) { return true; }
// Visits a process. If OnProcess returns false, the enumeration stops.
virtual bool OnProcess(ProcessDispatcher* proc) { return true; }
protected:
virtual ~JobEnumerator() = default;
};
namespace internal {
struct JobDispatcherRawListTag {}; // Tag for a JobDispatcher's parent's raw job list.
struct JobDispatcherListTag {}; // Tag for a JobDispatcher's parent's job list.
} // namespace internal
// This class implements the Job object kernel interface. Each Job has a parent
// Job and zero or more child Jobs and zero or more Child processes. This
// creates a DAG (tree) that connects every living task in the system.
// This is critically important because of the bottoms up refcount nature of
// the system in which the scheduler keeps alive the thread and the thread keeps
// alive the process, so without the Job it would not be possible to enumerate
// or control the tasks in the system for which there are no outstanding handles.
//
// The second important job of the Job is to apply policies that cannot otherwise
// be easily enforced by capabilities, for example kernel object creation.
//
// The third one is to support exception propagation from the leaf tasks to
// the root tasks.
//
// Obviously there is a special case for the 'root' Job which its parent is null
// and in the current implementation will call platform_halt() when its process
// and job count reaches zero. The root job is not exposed to user mode, instead
// the single child Job of the root job is given to the userboot process.
class JobDispatcher final
: public SoloDispatcher<JobDispatcher, ZX_DEFAULT_JOB_RIGHTS, 0u, lockdep::LockFlagsNestable>,
public fbl::ContainableBaseClasses<
fbl::TaggedDoublyLinkedListable<JobDispatcher*, internal::JobDispatcherRawListTag>,
fbl::TaggedSinglyLinkedListable<fbl::RefPtr<JobDispatcher>,
internal::JobDispatcherListTag>> {
public:
using RawListTag = internal::JobDispatcherRawListTag;
using ListTag = internal::JobDispatcherListTag;
static fbl::RefPtr<JobDispatcher> CreateRootJob();
static zx_status_t Create(uint32_t flags, const fbl::RefPtr<JobDispatcher>& parent,
KernelHandle<JobDispatcher>* handle, zx_rights_t* rights);
~JobDispatcher() final;
// Dispatcher implementation.
zx_obj_type_t get_type() const final { return ZX_OBJ_TYPE_JOB; }
zx_koid_t get_related_koid() const final;
fbl::RefPtr<JobDispatcher> parent() { return fbl::RefPtr<JobDispatcher>(parent_); }
// Job methods.
void get_name(char out_name[ZX_MAX_NAME_LEN]) const final;
zx_status_t set_name(const char* name, size_t len) final;
uint32_t max_height() const { return max_height_; }
bool AddChildProcess(const fbl::RefPtr<ProcessDispatcher>& process);
void RemoveChildProcess(ProcessDispatcher* process);
// Terminate the child processes and jobs. Returns |false| if the job is already
// in the process of killing, or the children are already terminated. Regardless
// of return value, the Job now will not accept new children and eventually
// transitions to |DEAD|. |return_code| can be obtained via ZX_INFO_JOB.
bool Kill(int64_t return_code);
// Set basic policy. |mode| is is either ZX_JOB_POL_RELATIVE or ZX_JOB_POL_ABSOLUTE and
// in_policy is an array of |count| elements.
//
// It is an error to set policy on a non-empty job, i.e. a job with one or more sub-jobs or
// processes.
// V1 flavor (on its way out)
zx_status_t SetBasicPolicy(uint32_t mode, const zx_policy_basic_v1* in_policy,
size_t policy_count);
// V2 flavor (on its way in)
zx_status_t SetBasicPolicy(uint32_t mode, const zx_policy_basic_v2* in_policy,
size_t policy_count);
// Set timer slack policy.
//
// |policy.min_slack| must be >= 0.
//
// |policy.default_mode| must be one of ZX_TIMER_SLACK_CENTER, ZX_TIMER_SLACK_EARLY,
// ZX_TIMER_SLACK_LATE.
//
// It is an error to set policy on a non-empty job, i.e. a job with one or more sub-jobs or
// processes.
zx_status_t SetTimerSlackPolicy(const zx_policy_timer_slack& policy);
JobPolicy GetPolicy() const;
// Kills its lowest child job that has get_kill_on_oom() set.
// Returns false if no alive child job had get_kill_on_oom() set.
bool KillJobWithKillOnOOM();
// Walks the job/process tree and invokes |je| methods on each node. If
// |recurse| is false, only visits direct children of this job. Returns
// false if any methods of |je| return false; returns true otherwise.
bool EnumerateChildren(JobEnumerator* je, bool recurse);
fbl::RefPtr<ProcessDispatcher> LookupProcessById(zx_koid_t koid);
fbl::RefPtr<JobDispatcher> LookupJobById(zx_koid_t koid);
Exceptionate* exceptionate(Exceptionate::Type type);
void set_kill_on_oom(bool kill);
bool get_kill_on_oom() const;
void GetInfo(zx_info_job_t* info) const;
// Aggregate the runtime for all processes that were previously running or are currently running
// as children of this job.
//
// This includes runtime for threads that previously ran under those processes, but it does not
// include runtime for child jobs.
zx_status_t AccumulateRuntimeTo(zx_info_task_runtime_t* info) const;
uint32_t LockOrder() const;
private:
enum class State { READY, KILLING, DEAD };
using LiveRefsArray = fbl::Array<fbl::RefPtr<Dispatcher>>;
JobDispatcher(uint32_t flags, fbl::RefPtr<JobDispatcher> parent, JobPolicy policy);
bool AddChildJob(const fbl::RefPtr<JobDispatcher>& job);
void RemoveChildJob(JobDispatcher* job);
State GetState() const;
// Remove this job from its parent's job list and the global job tree,
// either when the job was killed or its last reference was dropped.
// It's safe to call this multiple times.
//
// We cannot be holding our lock when we call this because it requires
// locking our parent, and we only nest locks down the tree.
void RemoveFromJobTreesUnlocked() TA_EXCL(get_lock());
// Helpers to transition into the DEAD state.
//
// The check for whether we should transition needs to be done under the
// lock, but actually moving into the dead state has to be done after
// releasing the lock.
//
// FinishDeadTransitionUnlocked() is thread-safe and idempotent so it's OK
// if multiple concurrent threads end up calling it.
bool IsReadyForDeadTransitionLocked() TA_REQ(get_lock());
void FinishDeadTransitionUnlocked() TA_EXCL(get_lock());
// Set or clear the JobDispatcher's signals to reflect its current state.
void UpdateSignalsLocked() TA_REQ(get_lock());
template <typename T, typename Fn>
__attribute__((warn_unused_result)) LiveRefsArray ForEachChildInLocked(T& children,
zx_status_t* status,
Fn func)
TA_REQ(get_lock());
template <typename T>
uint64_t ChildCountLocked() const TA_REQ(get_lock());
bool CanSetPolicy() TA_REQ(get_lock());
using OOMBitJobArray = ktl::array<fbl::RefPtr<JobDispatcher>, 8>;
// Collects all jobs with get_kill_on_oom() up to the maxiumum fixed size of a
// OOMBitJobArray array. RefPtrs stored in |into| must be released once the
// corresponding job lock has been released. |count| is an in/out parameter
// that must start at 0, and will indicate the number of elements in |into| on
// return. |count| will not exceed the fixed capacity of OOMBitJobArray.
void CollectJobsWithOOMBit(OOMBitJobArray* into, int* count);
const fbl::RefPtr<JobDispatcher> parent_;
const uint32_t max_height_;
// The user-friendly job name. For debug purposes only. That
// is, there is no mechanism to mint a handle to a job via this name.
fbl::Name<ZX_MAX_NAME_LEN> name_;
// The common |get_lock()| protects all members below.
State state_ TA_GUARDED(get_lock());
int64_t return_code_ TA_GUARDED(get_lock());
// TODO(cpu): The OOM kill system is incomplete, see fxbug.dev/32577 for details.
bool kill_on_oom_ TA_GUARDED(get_lock());
template <typename Ptr, typename Tag>
using SizedDoublyLinkedList = fbl::DoublyLinkedList<Ptr, Tag, fbl::SizeOrder::Constant,
fbl::DefaultDoublyLinkedListTraits<Ptr, Tag>>;
using RawJobList = SizedDoublyLinkedList<JobDispatcher*, RawListTag>;
using JobList = fbl::TaggedSinglyLinkedList<fbl::RefPtr<JobDispatcher>, ListTag>;
using RawProcessList =
SizedDoublyLinkedList<ProcessDispatcher*, ProcessDispatcher::RawJobListTag>;
using ProcessList =
fbl::TaggedSinglyLinkedList<fbl::RefPtr<ProcessDispatcher>, ProcessDispatcher::JobListTag>;
// Access to the pointers in these lists, especially any promotions to
// RefPtr, must be handled very carefully, because the children can die
// even when |lock_| is held. See ForEachChildInLocked() for more details
// and for a safe way to enumerate them.
RawJobList jobs_ TA_GUARDED(get_lock());
RawProcessList procs_ TA_GUARDED(get_lock());
JobPolicy policy_ TA_GUARDED(get_lock());
Exceptionate exceptionate_;
Exceptionate debug_exceptionate_;
// Aggregated runtime stats for processes that have exited.
TaskRuntimeStats aggregated_runtime_stats_ TA_GUARDED(get_lock());
};
// Returns the job that is the ancestor of all other tasks.
fbl::RefPtr<JobDispatcher> GetRootJobDispatcher();
Handle* GetRootJobHandle();
// Start the RootJobObserver. Must be called after the root job has at
// least one child process or child job.
void StartRootJobObserver();
#endif // ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_JOB_DISPATCHER_H_