kernel: Handle OOMs better and without deadlocking

Use a separate task to do it.

Also fix a bug where the init thread would get no kernel stack ever since 5f698b477.
This commit is contained in:
apio 2023-08-17 20:14:33 +02:00
parent d43590e68c
commit 4a654bf093
Signed by: apio
GPG Key ID: B8A7D06E42258954
8 changed files with 77 additions and 9 deletions

View File

@ -1,5 +1,6 @@
#include "fs/StorageCache.h" #include "fs/StorageCache.h"
#include "Log.h" #include "Log.h"
#include <luna/Heap.h>
#include <luna/ScopeGuard.h> #include <luna/ScopeGuard.h>
static LinkedList<StorageCache> g_storage_caches; static LinkedList<StorageCache> g_storage_caches;
@ -23,9 +24,13 @@ Result<StorageCache::CacheEntry*> StorageCache::fetch_entry(u64 block)
void StorageCache::clear() void StorageCache::clear()
{ {
ScopedKMutexLock<100> lock(m_mutex); m_mutex.lock();
kdbgln("cache: clearing %lu entries, out of %lu buckets", m_cache_entries.size(), m_cache_entries.capacity());
m_cache_entries.clear(); m_cache_entries.clear();
kdbgln("cache: done");
m_mutex.unlock();
} }
StorageCache::StorageCache() StorageCache::StorageCache()

View File

@ -31,6 +31,16 @@ void reap_thread()
} }
} }
void oom_thread()
{
while (true)
{
kernel_wait_for_event();
// OOM! Do everything we can to recover memory.
StorageCache::clear_caches();
}
}
[[noreturn]] void init() [[noreturn]] void init()
{ {
{ {
@ -58,6 +68,10 @@ void reap_thread()
"Failed to create the process reaper kernel thread"); "Failed to create the process reaper kernel thread");
Scheduler::set_reap_thread(reap); Scheduler::set_reap_thread(reap);
auto oom = mark_critical(Scheduler::new_kernel_thread(oom_thread, "[oom]"),
"Failed to create the out-of-memory kernel thread");
Scheduler::set_oom_thread(oom);
#ifdef ARCH_X86_64 #ifdef ARCH_X86_64
ATA::Controller::scan(); ATA::Controller::scan();
#endif #endif

View File

@ -146,9 +146,9 @@ namespace MemoryManager
bool ok = frame_bitmap->find_and_toggle(false, start_index).try_set_value(index); bool ok = frame_bitmap->find_and_toggle(false, start_index).try_set_value(index);
if (!ok) if (!ok)
{ {
kwarnln("OOM alert! Trying to free caches..."); kwarnln("OOM alert! Scheduling the OOM thread...");
StorageCache::clear_caches(); Scheduler::signal_oom_thread();
if (!frame_bitmap->find_and_toggle(false, start_index).try_set_value(index)) return err(ENOMEM); return err(ENOMEM);
} }
start_index = index + 1; start_index = index + 1;

View File

@ -13,6 +13,7 @@ static Thread g_idle;
static Thread* g_current = nullptr; static Thread* g_current = nullptr;
static Thread* g_init = nullptr; static Thread* g_init = nullptr;
static Thread* g_reap = nullptr; static Thread* g_reap = nullptr;
static Thread* g_oom = nullptr;
static const usize TICKS_PER_TIMESLICE = 20; static const usize TICKS_PER_TIMESLICE = 20;
@ -70,6 +71,17 @@ namespace Scheduler
if (g_reap) g_reap->wake_up(); if (g_reap) g_reap->wake_up();
} }
void set_oom_thread(Thread* thread)
{
g_oom = thread;
g_oom->unrestricted_task = true;
}
void signal_oom_thread()
{
if (g_oom) g_oom->wake_up();
}
Result<Thread*> new_kernel_thread_impl(Thread* thread, const char* name) Result<Thread*> new_kernel_thread_impl(Thread* thread, const char* name)
{ {
// If anything fails, make sure to clean up. // If anything fails, make sure to clean up.
@ -154,8 +166,13 @@ namespace Scheduler
u64 argv = TRY(image->push_string_vector_on_stack(args)); u64 argv = TRY(image->push_string_vector_on_stack(args));
u64 envp = TRY(image->push_string_vector_on_stack(env)); u64 envp = TRY(image->push_string_vector_on_stack(env));
const u64 kernel_stack_base = TRY(MemoryManager::alloc_for_kernel(4, MMU::ReadWrite | MMU::NoExecute));
Stack kernel_stack { kernel_stack_base, 4 * ARCH_PAGE_SIZE };
guard.deactivate(); guard.deactivate();
thread->kernel_stack = kernel_stack;
image->apply(thread); image->apply(thread);
thread->set_arguments(args.size(), argv, env.size(), envp); thread->set_arguments(args.size(), argv, env.size(), envp);
@ -254,8 +271,12 @@ namespace Scheduler
new_thread->ticks_left = 1; // The idle task only runs for 1 tick so we can check for new runnable tasks new_thread->ticks_left = 1; // The idle task only runs for 1 tick so we can check for new runnable tasks
// as fast as possible. // as fast as possible.
} }
else else if (new_thread->unrestricted_task)
new_thread->ticks_left = TICKS_PER_TIMESLICE; {
check(new_thread->is_kernel);
new_thread->ticks_left = -1;
}
else { new_thread->ticks_left = TICKS_PER_TIMESLICE; }
} }
void switch_task(Registers* regs) void switch_task(Registers* regs)

View File

@ -13,6 +13,9 @@ namespace Scheduler
void set_reap_thread(Thread*); void set_reap_thread(Thread*);
void signal_reap_thread(); void signal_reap_thread();
void set_oom_thread(Thread*);
void signal_oom_thread();
Result<Thread*> new_kernel_thread(u64 address, const char* name); Result<Thread*> new_kernel_thread(u64 address, const char* name);
Result<Thread*> new_kernel_thread(void (*func)(void), const char* name); Result<Thread*> new_kernel_thread(void (*func)(void), const char* name);
Result<Thread*> new_kernel_thread(void (*func)(void*), void* arg, const char* name); Result<Thread*> new_kernel_thread(void (*func)(void*), void* arg, const char* name);

View File

@ -109,6 +109,8 @@ struct Thread : public LinkedListNode<Thread>
sigset_t pending_signals { 0 }; sigset_t pending_signals { 0 };
bool interrupted { false }; bool interrupted { false };
bool unrestricted_task { false };
FPData fp_data; FPData fp_data;
ThreadState state = ThreadState::Runnable; ThreadState state = ThreadState::Runnable;

View File

@ -49,6 +49,16 @@ template <typename K, typename V> struct HashMap
return m_table.try_remove(HashPair<K, V> { key, {} }); return m_table.try_remove(HashPair<K, V> { key, {} });
} }
usize capacity() const
{
return m_table.capacity();
}
usize size() const
{
return m_table.size();
}
void clear() void clear()
{ {
m_table.clear(); m_table.clear();

View File

@ -101,10 +101,23 @@ template <typename T> class HashTable
void clear() void clear()
{ {
for (usize i = 0; i < m_capacity; i++) m_buckets[i].~Option(); if (m_capacity)
{
for (usize i = 0; i < m_capacity; i++) m_buckets[i].~Option();
free_impl(m_buckets); free_impl(m_buckets);
m_capacity = m_size = 0; m_capacity = m_size = 0;
}
}
usize capacity() const
{
return m_capacity;
}
usize size() const
{
return m_size;
} }
~HashTable() ~HashTable()