blob: 4fdf229b9704cbbc216141363da9c54baf84408f [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
// This file defines:
// * Initialization code for kernel/object module
// * Singleton instances and global locks
// * Helper functions
//
// TODO(dbort): Split this file into self-consistent pieces.
#include <inttypes.h>
#include <trace.h>
#include <kernel/cmdline.h>
#include <lk/init.h>
#include <lib/oom.h>
#include <object/diagnostics.h>
#include <object/excp_port.h>
#include <object/job_dispatcher.h>
#include <object/policy_manager.h>
#include <object/port_dispatcher.h>
#include <object/process_dispatcher.h>
#include <fbl/function.h>
#include <zircon/types.h>
#define LOCAL_TRACE 0
// All jobs and processes are rooted at the |root_job|.
static fbl::RefPtr<JobDispatcher> root_job;
// The singleton policy manager, for jobs and processes. This is
// not a Dispatcher, just a plain class.
static PolicyManager* policy_manager;
fbl::RefPtr<JobDispatcher> GetRootJobDispatcher() {
return root_job;
}
PolicyManager* GetSystemPolicyManager() {
return policy_manager;
}
// Counts and optionally prints all job/process descendants of a job.
namespace {
class OomJobEnumerator final : public JobEnumerator {
public:
// If |prefix| is non-null, also prints each job/process.
OomJobEnumerator(const char* prefix)
: prefix_(prefix) { reset_counts(); }
void reset_counts() {
num_jobs_ = 0;
num_processes_ = 0;
num_running_processes_ = 0;
}
size_t num_jobs() const { return num_jobs_; }
size_t num_processes() const { return num_processes_; }
size_t num_running_processes() const { return num_running_processes_; }
private:
bool OnJob(JobDispatcher* job) final {
if (prefix_ != nullptr) {
char name[ZX_MAX_NAME_LEN];
job->get_name(name);
printf("%sjob %6" PRIu64 " '%s'\n", prefix_, job->get_koid(), name);
}
num_jobs_++;
return true;
}
bool OnProcess(ProcessDispatcher* process) final {
// Since we want to free memory by actually killing something, only
// count running processes that aren't attached to a debugger.
// It's a race, but will stop us from re-killing a job that only has
// blocked-by-debugger processes.
zx_info_process_t info = {};
process->GetInfo(&info);
if (info.started && !info.exited && !info.debugger_attached) {
num_running_processes_++;
}
if (prefix_ != nullptr) {
const char* tag = "new";
if (info.started) {
tag = "run";
}
if (info.exited) {
tag = "dead";
}
if (info.debugger_attached) {
tag = "dbg";
}
char name[ZX_MAX_NAME_LEN];
process->get_name(name);
printf("%sproc %5" PRIu64 " %4s '%s'\n",
prefix_, process->get_koid(), tag, name);
}
num_processes_++;
return true;
}
const char* prefix_;
size_t num_jobs_;
size_t num_processes_;
size_t num_running_processes_;
};
} // namespace
// Called from a dedicated kernel thread when the system is low on memory.
static void oom_lowmem(size_t shortfall_bytes) {
printf("OOM: oom_lowmem(shortfall_bytes=%zu) called\n", shortfall_bytes);
printf("OOM: Process mapped committed bytes:\n");
DumpProcessMemoryUsage("OOM: ", /*min_pages=*/8 * MB / PAGE_SIZE);
printf("OOM: Finding a job to kill...\n");
OomJobEnumerator job_counter(nullptr);
OomJobEnumerator job_printer("OOM: + ");
bool killed = false;
int next = 3; // Used to print a few "up next" jobs.
JobDispatcher::ForEachJob([&](JobDispatcher* job) {
// TODO(dbort): Consider adding an "immortal" bit on jobs and skip them
// here if they (and and all of their ancestors) have it set.
bool kill = false;
if (!killed) {
// We want to kill a job that will actually free memory by dying, so
// look for one with running process descendants (i.e., started,
// non-exited, not blocked in a debugger).
job_counter.reset_counts();
job->EnumerateChildren(&job_counter, /*recurse=*/true);
kill = job_counter.num_running_processes() > 0;
}
const char* tag;
if (kill) {
tag = "*KILL*";
} else if (!killed) {
tag = "(skip)";
} else {
tag = "(next)";
}
char name[ZX_MAX_NAME_LEN];
job->get_name(name);
printf("OOM: %s job %6" PRIu64 " '%s'\n", tag, job->get_koid(), name);
if (kill) {
// Print the descendants of the job we're about to kill.
job_printer.reset_counts();
job->EnumerateChildren(&job_printer, /*recurse=*/true);
printf("OOM: = %zu running procs (%zu total), %zu jobs\n",
job_printer.num_running_processes(),
job_printer.num_processes(), job_printer.num_jobs());
// TODO(dbort): Join on dying processes/jobs to make sure we've
// freed memory before returning control to the OOM thread?
// TODO(ZX-961): 'kill -9' these processes (which will require new
// ProcessDispatcher features) so we can reclaim the memory of
// processes that are stuck in a debugger or in the crashlogger.
job->Kill();
killed = true;
} else if (killed) {
if (--next == 0) {
return ZX_ERR_STOP;
}
}
return ZX_OK;
});
}
static void object_glue_init(uint level) TA_NO_THREAD_SAFETY_ANALYSIS {
Handle::Init();
root_job = JobDispatcher::CreateRootJob();
policy_manager = PolicyManager::Create();
PortDispatcher::Init();
// Be sure to update kernel_cmdline.md if any of these defaults change.
oom_init(cmdline_get_bool("kernel.oom.enable", true),
ZX_SEC(cmdline_get_uint64("kernel.oom.sleep-sec", 1)),
cmdline_get_uint64("kernel.oom.redline-mb", 50) * MB,
oom_lowmem);
}
LK_INIT_HOOK(libobject, object_glue_init, LK_INIT_LEVEL_THREADING);