blob: d46f60a1d72141310a57c28b880e82789d33254e [file] [log] [blame]
// Copyright 2022 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
#include <lib/counters.h>
#include <lib/crypto/global_prng.h>
#include <fbl/ref_counted_upgradeable.h>
#include <kernel/auto_preempt_disabler.h>
#include <vm/compression.h>
#include <vm/debug_compressor.h>
#include <vm/vm_cow_pages.h>
namespace {
KCOUNTER(pq_compress_debug_random_compression, "pq.compress.debug_random_compression")
}
VmDebugCompressor::~VmDebugCompressor() { Shutdown(); }
void VmDebugCompressor::Shutdown() {
Guard<SpinLock, IrqSave> guard{&lock_};
if (thread_) {
ASSERT(state_ != State::Shutdown);
state_ = State::Shutdown;
event_.Signal();
Thread* t = thread_;
thread_ = nullptr;
guard.Release();
int retcode;
zx_status_t status = t->Join(&retcode, ZX_TIME_INFINITE);
ASSERT(status == ZX_OK);
}
}
void VmDebugCompressor::Add(vm_page_t* page, VmCowPages* object, uint64_t offset) {
bool signal = false;
{
Guard<SpinLock, IrqSave> guard{&lock_};
if (state_ != State::Running || list_->full()) {
return;
}
// Allow 10% of pages to get added.
if (rand_r(&rng_state_) >= (RAND_MAX / 10)) {
return;
}
// If currently empty then the compression thread will need a kick.
signal = list_->empty();
struct Entry entry {
fbl::MakeRefPtrUpgradeFromRaw(object, guard), page, offset
};
// Callers of |Add| are required to ensure |object| lives till the end of the call, so the
// upgrade should never fail.
ASSERT(entry.cow);
list_->push(ktl::move(entry));
}
if (signal) {
event_.Signal();
}
}
void VmDebugCompressor::CompressThread() {
VmCompression* compression = pmm_page_compression();
// It is an error to attempt to be using the debug compressor if compression isn't available.
ASSERT(compression);
do {
// Check for Shutdown prior to waiting to ensure that if a shutdown was triggered while we were
// processing entries we do not miss it.
{
Guard<SpinLock, IrqSave> guard{&lock_};
if (state_ == State::Shutdown) {
return;
}
}
zx_status_t status = event_.Wait();
ASSERT(status == ZX_OK);
// Acquire the compression instance, cannot keep this acquired between runs to avoid starving
// any legitimate compression efforts.
VmCompression::CompressorGuard instance = compression->AcquireCompressor();
// Work through all items in the list and attempt to compress them.
for (Entry entry = Pop(); entry.cow; entry = Pop()) {
status = instance.get().Arm();
ASSERT(status == ZX_OK);
if (entry.cow->ReclaimPage(entry.page, entry.offset, VmCowPages::EvictionHintAction::Ignore,
&instance.get())) {
pq_compress_debug_random_compression.Add(1);
DEBUG_ASSERT(!list_in_list(&entry.page->queue_node));
pmm_free_page(entry.page);
}
}
} while (true);
}
void VmDebugCompressor::Pause() {
{
Guard<SpinLock, IrqSave> guard{&lock_};
if (state_ == State::Shutdown) {
return;
}
ASSERT(state_ != State::Paused);
state_ = State::Paused;
}
// Drain list so that we aren't holding VMOs alive during an arbitrarily long pause.
for (Entry entry = Pop(); entry.cow; entry = Pop()) {
}
}
void VmDebugCompressor::Resume() {
Guard<SpinLock, IrqSave> guard{&lock_};
if (state_ == State::Shutdown) {
return;
}
ASSERT(state_ != State::Running);
// The list should be empty as nothing should have been added while paused.
ASSERT(list_->empty());
state_ = State::Running;
}
VmDebugCompressor::Entry VmDebugCompressor::Pop() {
Guard<SpinLock, IrqSave> guard{&lock_};
if (list_->empty()) {
return Entry{};
}
Entry e = ktl::move(list_->front());
list_->pop();
return e;
}
zx_status_t VmDebugCompressor::Init() {
// Allocate objects outside the lock.
// The compression thread is made as HIGH_PRIORITY since it will be holding refptrs to VMOs, and
// could be inadvertently keeping those VMOs alive if the list is not processed fast enough.
Thread* t = Thread::Create(
"page-queue-debug-compress-thread",
[](void* arg) -> int {
static_cast<VmDebugCompressor*>(arg)->CompressThread();
return 0;
},
this, HIGH_PRIORITY);
if (!t) {
return ZX_ERR_NO_MEMORY;
}
fbl::AllocChecker ac;
ktl::unique_ptr<fbl::RingBuffer<Entry, kArraySize>> list(
new (&ac) fbl::RingBuffer<Entry, kArraySize>());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
AnnotatedAutoPreemptDisabler apd;
Guard<SpinLock, IrqSave> guard{&lock_};
ASSERT(!thread_);
list_ = ktl::move(list);
thread_ = t;
thread_->Resume();
state_ = State::Running;
crypto::global_prng::GetInstance()->Draw(&rng_state_, sizeof(rng_state_));
return ZX_OK;
}