forked from Bananymous/banan-os
Kernel: Initial work on new scheduler with queues
Sleeping is definately broken
This commit is contained in:
parent
a9acf1f6dc
commit
b8ee77eb78
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define ALWAYS_INLINE inline __attribute__((always_inline))
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <BAN/Function.h>
|
|
||||||
#include <BAN/LinkedList.h>
|
#include <BAN/LinkedList.h>
|
||||||
|
#include <BAN/Memory.h>
|
||||||
#include <kernel/Thread.h>
|
#include <kernel/Thread.h>
|
||||||
|
|
||||||
namespace Kernel
|
namespace Kernel
|
||||||
|
@ -9,33 +9,48 @@ namespace Kernel
|
||||||
|
|
||||||
class Scheduler
|
class Scheduler
|
||||||
{
|
{
|
||||||
BAN_NON_COPYABLE(Scheduler);
|
|
||||||
BAN_NON_MOVABLE(Scheduler);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void initialize();
|
static BAN::ErrorOr<void> initialize();
|
||||||
static Scheduler& get();
|
static Scheduler& get();
|
||||||
|
|
||||||
const Thread& current_thread() const;
|
|
||||||
|
|
||||||
BAN::ErrorOr<void> add_thread(const BAN::Function<void()>& function);
|
|
||||||
|
|
||||||
void reschedule();
|
|
||||||
void set_current_thread_sleeping();
|
|
||||||
void start();
|
void start();
|
||||||
|
void reschedule();
|
||||||
|
|
||||||
static constexpr size_t ms_between_switch = 4;
|
BAN::ErrorOr<void> add_thread(BAN::RefCounted<Thread>);
|
||||||
|
|
||||||
|
void set_current_thread_sleeping(uint64_t);
|
||||||
|
[[noreturn]] void set_current_thread_done();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Scheduler() {}
|
Scheduler() = default;
|
||||||
void switch_thread();
|
|
||||||
|
BAN::RefCounted<Thread> current_thread();
|
||||||
|
|
||||||
|
void wake_threads();
|
||||||
|
[[nodiscard]] bool save_current_thread();
|
||||||
|
void get_next_thread();
|
||||||
|
[[noreturn]] void execute_current_thread();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BAN::LinkedList<Thread> m_threads;
|
struct ActiveThread
|
||||||
BAN::LinkedList<Thread>::iterator m_current_iterator;
|
{
|
||||||
uint64_t m_last_reschedule = 0;
|
BAN::RefCounted<Thread> thread;
|
||||||
|
uint64_t padding;
|
||||||
|
};
|
||||||
|
|
||||||
friend class Thread;
|
struct SleepingThread
|
||||||
|
{
|
||||||
|
BAN::RefCounted<Thread> thread;
|
||||||
|
uint64_t wake_delta;
|
||||||
|
};
|
||||||
|
|
||||||
|
BAN::RefCounted<Thread> m_idle_thread;
|
||||||
|
BAN::LinkedList<ActiveThread> m_active_threads;
|
||||||
|
BAN::LinkedList<SleepingThread> m_sleeping_threads;
|
||||||
|
|
||||||
|
BAN::LinkedList<ActiveThread>::iterator m_current_thread;
|
||||||
|
|
||||||
|
uint64_t m_last_reschedule = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
|
@ -12,41 +12,35 @@ namespace Kernel
|
||||||
BAN_NON_MOVABLE(Thread);
|
BAN_NON_MOVABLE(Thread);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum class State
|
static BAN::ErrorOr<BAN::RefCounted<Thread>> create(const BAN::Function<void()>&);
|
||||||
{
|
|
||||||
NotStarted,
|
|
||||||
Running,
|
|
||||||
Paused,
|
|
||||||
Sleeping,
|
|
||||||
Done,
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
Thread(const BAN::Function<void()>&);
|
|
||||||
~Thread();
|
~Thread();
|
||||||
|
|
||||||
uint32_t tid() const { return m_tid; }
|
uint32_t tid() const { return m_tid; }
|
||||||
|
|
||||||
void set_rsp(uintptr_t rsp) { m_rsp = rsp; }
|
void set_rsp(uintptr_t rsp) { m_rsp = rsp; }
|
||||||
void set_rip(uintptr_t rip) { m_rip = rip; }
|
void set_rip(uintptr_t rip) { m_rip = rip; }
|
||||||
void set_state(State state) { m_state = state; }
|
|
||||||
uintptr_t rsp() const { return m_rsp; }
|
uintptr_t rsp() const { return m_rsp; }
|
||||||
uintptr_t rip() const { return m_rip; }
|
uintptr_t rip() const { return m_rip; }
|
||||||
State state() const { return m_state; }
|
|
||||||
|
void set_started() { m_started = true; }
|
||||||
|
bool started() const { return m_started; }
|
||||||
|
|
||||||
const BAN::Function<void()>* function() const { return &m_function; }
|
const BAN::Function<void()>* function() const { return &m_function; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Thread(const BAN::Function<void()>&);
|
||||||
void on_exit();
|
void on_exit();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void* m_stack_base = nullptr;
|
void* m_stack_base = nullptr;
|
||||||
State m_state = State::NotStarted;
|
|
||||||
uintptr_t m_rip = 0;
|
uintptr_t m_rip = 0;
|
||||||
uintptr_t m_rsp = 0;
|
uintptr_t m_rsp = 0;
|
||||||
const uint32_t m_tid = 0;
|
const uint32_t m_tid = 0;
|
||||||
|
bool m_started = false;
|
||||||
|
|
||||||
BAN::Function<void()> m_function;
|
BAN::Function<void()> m_function;
|
||||||
|
|
||||||
|
friend class BAN::RefCounted<Thread>;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
namespace PIT
|
namespace PIT
|
||||||
{
|
{
|
||||||
|
|
||||||
static uint64_t s_system_time = 0;
|
static uint64_t s_system_time = 0;
|
||||||
|
|
||||||
void irq_handler()
|
void irq_handler()
|
||||||
|
@ -52,9 +53,9 @@ namespace PIT
|
||||||
|
|
||||||
void sleep(uint64_t ms)
|
void sleep(uint64_t ms)
|
||||||
{
|
{
|
||||||
uint64_t end = s_system_time + ms;
|
if (ms == 0)
|
||||||
while (s_system_time < end)
|
return;
|
||||||
Kernel::Scheduler::get().set_current_thread_sleeping();
|
Kernel::Scheduler::get().set_current_thread_sleeping(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,42 @@
|
||||||
#include <kernel/Arch.h>
|
#include <kernel/Arch.h>
|
||||||
|
#include <kernel/Attributes.h>
|
||||||
#include <kernel/InterruptController.h>
|
#include <kernel/InterruptController.h>
|
||||||
#include <kernel/Scheduler.h>
|
#include <kernel/Scheduler.h>
|
||||||
|
|
||||||
|
#include <kernel/PCI.h>
|
||||||
|
|
||||||
|
#define DISABLE_INTERRUPTS() asm volatile("cli")
|
||||||
|
#define ENABLE_INTERRUPTS() asm volatile("sti")
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
#define VERIFY_CLI() ASSERT(interrupts_disabled())
|
||||||
|
#else
|
||||||
|
#define VERIFY_CLI()
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Kernel
|
namespace Kernel
|
||||||
{
|
{
|
||||||
|
|
||||||
static Scheduler* s_instance = nullptr;
|
|
||||||
|
|
||||||
extern "C" void start_thread(const BAN::Function<void()>* function, uintptr_t rsp, uintptr_t rip);
|
extern "C" void start_thread(const BAN::Function<void()>* function, uintptr_t rsp, uintptr_t rip);
|
||||||
extern "C" void continue_thread(uintptr_t rsp, uintptr_t rip);
|
extern "C" void continue_thread(uintptr_t rsp, uintptr_t rip);
|
||||||
extern "C" uintptr_t read_rip();
|
extern "C" uintptr_t read_rip();
|
||||||
|
|
||||||
void Scheduler::initialize()
|
static Scheduler* s_instance = nullptr;
|
||||||
|
|
||||||
|
static bool interrupts_disabled()
|
||||||
{
|
{
|
||||||
ASSERT(!s_instance);
|
uintptr_t flags;
|
||||||
|
asm volatile("pushf; pop %0" : "=r"(flags));
|
||||||
|
return !(flags & (1 << 9));
|
||||||
|
}
|
||||||
|
|
||||||
|
BAN::ErrorOr<void> Scheduler::initialize()
|
||||||
|
{
|
||||||
|
ASSERT(s_instance == nullptr);
|
||||||
s_instance = new Scheduler();
|
s_instance = new Scheduler();
|
||||||
ASSERT(s_instance);
|
ASSERT(s_instance);
|
||||||
MUST(s_instance->add_thread(BAN::Function<void()>([] { for(;;) asm volatile("hlt"); })));
|
s_instance->m_idle_thread = TRY(Thread::create([] { for (;;) asm volatile("hlt"); }));
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
Scheduler& Scheduler::get()
|
Scheduler& Scheduler::get()
|
||||||
|
@ -25,21 +45,18 @@ namespace Kernel
|
||||||
return *s_instance;
|
return *s_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Thread& Scheduler::current_thread() const
|
void Scheduler::start()
|
||||||
{
|
{
|
||||||
return *m_current_iterator;
|
VERIFY_CLI();
|
||||||
|
ASSERT(!m_active_threads.empty());
|
||||||
|
m_current_thread = m_active_threads.begin();
|
||||||
|
execute_current_thread();
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BAN::RefCounted<Thread> Scheduler::current_thread()
|
||||||
BAN::ErrorOr<void> Scheduler::add_thread(const BAN::Function<void()>& function)
|
|
||||||
{
|
{
|
||||||
uintptr_t flags;
|
return m_current_thread ? m_current_thread->thread : m_idle_thread;
|
||||||
asm volatile("pushf; pop %0" : "=r"(flags));
|
|
||||||
asm volatile("cli");
|
|
||||||
TRY(m_threads.emplace_back(function));
|
|
||||||
if (flags & (1 << 9))
|
|
||||||
asm volatile("sti");
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::reschedule()
|
void Scheduler::reschedule()
|
||||||
|
@ -47,94 +64,151 @@ namespace Kernel
|
||||||
ASSERT(InterruptController::get().is_in_service(PIT_IRQ));
|
ASSERT(InterruptController::get().is_in_service(PIT_IRQ));
|
||||||
InterruptController::get().eoi(PIT_IRQ);
|
InterruptController::get().eoi(PIT_IRQ);
|
||||||
|
|
||||||
uint64_t current_time = PIT::ms_since_boot();
|
if (PIT::ms_since_boot() <= m_last_reschedule)
|
||||||
if (m_last_reschedule + ms_between_switch > current_time)
|
|
||||||
return;
|
return;
|
||||||
m_last_reschedule = current_time;
|
m_last_reschedule = PIT::ms_since_boot();
|
||||||
|
|
||||||
|
if (!m_sleeping_threads.empty())
|
||||||
|
m_sleeping_threads.front().wake_delta--;
|
||||||
|
wake_threads();
|
||||||
|
|
||||||
for (Thread& thread : m_threads)
|
if (save_current_thread())
|
||||||
if (thread.state() == Thread::State::Sleeping)
|
return;
|
||||||
thread.set_state(Thread::State::Paused);
|
get_next_thread();
|
||||||
|
execute_current_thread();
|
||||||
switch_thread();
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::switch_thread()
|
void Scheduler::wake_threads()
|
||||||
{
|
{
|
||||||
|
VERIFY_CLI();
|
||||||
|
|
||||||
|
while (!m_sleeping_threads.empty() && m_sleeping_threads.front().wake_delta == 0)
|
||||||
|
{
|
||||||
|
auto thread = m_sleeping_threads.front().thread;
|
||||||
|
m_sleeping_threads.remove(m_sleeping_threads.begin());
|
||||||
|
|
||||||
|
// This should work as we released enough memory from sleeping thread
|
||||||
|
static_assert(sizeof(ActiveThread) == sizeof(SleepingThread));
|
||||||
|
MUST(m_active_threads.push_back({ thread, 0 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BAN::ErrorOr<void> Scheduler::add_thread(BAN::RefCounted<Thread> thread)
|
||||||
|
{
|
||||||
|
if (interrupts_disabled())
|
||||||
|
{
|
||||||
|
TRY(m_active_threads.push_back({ thread, 0 }));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DISABLE_INTERRUPTS();
|
||||||
|
TRY(m_active_threads.push_back({ thread, 0 }));
|
||||||
|
ENABLE_INTERRUPTS();
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::get_next_thread()
|
||||||
|
{
|
||||||
|
VERIFY_CLI();
|
||||||
|
|
||||||
|
if (m_active_threads.empty())
|
||||||
|
{
|
||||||
|
m_current_thread = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_current_thread || ++m_current_thread == m_active_threads.end())
|
||||||
|
m_current_thread = m_active_threads.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this is declared always inline, so we don't corrupt the stack
|
||||||
|
// after getting the rsp
|
||||||
|
ALWAYS_INLINE bool Scheduler::save_current_thread()
|
||||||
|
{
|
||||||
|
VERIFY_CLI();
|
||||||
|
|
||||||
uintptr_t rsp, rip;
|
uintptr_t rsp, rip;
|
||||||
push_callee_saved();
|
push_callee_saved();
|
||||||
if (!(rip = read_rip()))
|
if (!(rip = read_rip()))
|
||||||
{
|
{
|
||||||
pop_callee_saved();
|
pop_callee_saved();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
read_rsp(rsp);
|
read_rsp(rsp);
|
||||||
|
|
||||||
ASSERT(m_threads.size() > 1);
|
auto current = current_thread();
|
||||||
|
current->set_rip(rip);
|
||||||
Thread& current = *m_current_iterator;
|
current->set_rsp(rsp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (current.state() == Thread::State::Done)
|
void Scheduler::execute_current_thread()
|
||||||
|
{
|
||||||
|
VERIFY_CLI();
|
||||||
|
|
||||||
|
auto current = current_thread();
|
||||||
|
|
||||||
|
if (current->started())
|
||||||
{
|
{
|
||||||
m_threads.remove(m_current_iterator);
|
continue_thread(current->rsp(), current->rip());
|
||||||
m_current_iterator = m_threads.begin();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
current.set_rsp(rsp);
|
current->set_started();
|
||||||
current.set_rip(rip);
|
start_thread(current->function(), current->rsp(), current->rip());
|
||||||
if (current.state() != Thread::State::Sleeping)
|
|
||||||
current.set_state(Thread::State::Paused);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto next_iterator = m_current_iterator;
|
ASSERT_NOT_REACHED();
|
||||||
if (++next_iterator == m_threads.end())
|
}
|
||||||
next_iterator = ++m_threads.begin();
|
|
||||||
if (next_iterator->state() == Thread::State::Sleeping)
|
|
||||||
next_iterator = m_threads.begin();
|
|
||||||
Thread& next = *next_iterator;
|
|
||||||
|
|
||||||
m_current_iterator = next_iterator;
|
void Scheduler::set_current_thread_sleeping(uint64_t wake_delta)
|
||||||
|
{
|
||||||
|
DISABLE_INTERRUPTS();
|
||||||
|
|
||||||
switch (next.state())
|
ASSERT(m_current_thread);
|
||||||
|
|
||||||
|
auto current = m_current_thread->thread;
|
||||||
|
|
||||||
|
auto temp = m_current_thread;
|
||||||
|
if (save_current_thread())
|
||||||
|
return;
|
||||||
|
get_next_thread();
|
||||||
|
m_active_threads.remove(temp);
|
||||||
|
|
||||||
|
auto it = m_sleeping_threads.begin();
|
||||||
|
|
||||||
|
for (; it != m_sleeping_threads.end(); it++)
|
||||||
{
|
{
|
||||||
case Thread::State::NotStarted:
|
if (wake_delta <= it->wake_delta)
|
||||||
next.set_state(Thread::State::Running);
|
|
||||||
start_thread(next.function(), next.rsp(), next.rip());
|
|
||||||
break;
|
break;
|
||||||
case Thread::State::Paused:
|
wake_delta -= it->wake_delta;
|
||||||
next.set_state(Thread::State::Running);
|
|
||||||
continue_thread(next.rsp(), next.rip());
|
|
||||||
break;
|
|
||||||
case Thread::State::Sleeping: ASSERT(false);
|
|
||||||
case Thread::State::Running: ASSERT(false);
|
|
||||||
case Thread::State::Done: ASSERT(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT(false);
|
if (it != m_sleeping_threads.end())
|
||||||
|
it->wake_delta -= wake_delta;
|
||||||
|
|
||||||
|
// This should work as we released enough memory from active thread
|
||||||
|
static_assert(sizeof(ActiveThread) == sizeof(SleepingThread));
|
||||||
|
MUST(m_sleeping_threads.insert(it, { current, wake_delta }));
|
||||||
|
|
||||||
|
execute_current_thread();
|
||||||
|
ASSERT_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Scheduler::set_current_thread_done()
|
||||||
|
{
|
||||||
|
DISABLE_INTERRUPTS();
|
||||||
|
|
||||||
|
ASSERT(m_current_thread);
|
||||||
|
|
||||||
|
auto temp = m_current_thread;
|
||||||
|
get_next_thread();
|
||||||
|
m_active_threads.remove(temp);
|
||||||
|
|
||||||
void Scheduler::set_current_thread_sleeping()
|
execute_current_thread();
|
||||||
{
|
ASSERT_NOT_REACHED();
|
||||||
asm volatile("cli");
|
|
||||||
m_current_iterator->set_state(Thread::State::Sleeping);
|
|
||||||
switch_thread();
|
|
||||||
asm volatile("sti");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Scheduler::start()
|
|
||||||
{
|
|
||||||
ASSERT(m_threads.size() > 1);
|
|
||||||
|
|
||||||
m_current_iterator = m_threads.begin();
|
|
||||||
|
|
||||||
Thread& current = *m_current_iterator;
|
|
||||||
ASSERT(current.state() == Thread::State::NotStarted);
|
|
||||||
current.set_state(Thread::State::Running);
|
|
||||||
|
|
||||||
start_thread(current.function(), current.rsp(), current.rip());
|
|
||||||
|
|
||||||
ASSERT(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -59,7 +59,8 @@ namespace Kernel
|
||||||
TTY_PRINT("{}", m_prompt);
|
TTY_PRINT("{}", m_prompt);
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
Scheduler::get().set_current_thread_sleeping();
|
//PIT::sleep(1); // sleep until next reschedule
|
||||||
|
asm volatile("hlt");
|
||||||
Input::update();
|
Input::update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,7 +183,7 @@ argument_done:
|
||||||
|
|
||||||
s_thread_spinlock.lock();
|
s_thread_spinlock.lock();
|
||||||
|
|
||||||
MUST(Scheduler::get().add_thread(Function<void()>(
|
MUST(Scheduler::get().add_thread(MUST(Thread::create(
|
||||||
[this, &arguments]
|
[this, &arguments]
|
||||||
{
|
{
|
||||||
auto args = arguments;
|
auto args = arguments;
|
||||||
|
@ -191,7 +192,7 @@ argument_done:
|
||||||
PIT::sleep(5000);
|
PIT::sleep(5000);
|
||||||
process_command(args);
|
process_command(args);
|
||||||
}
|
}
|
||||||
)));
|
))));
|
||||||
|
|
||||||
while (s_thread_spinlock.is_locked());
|
while (s_thread_spinlock.is_locked());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,12 @@ namespace Kernel
|
||||||
memcpy((void*)rsp, (void*)&value, size);
|
memcpy((void*)rsp, (void*)&value, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BAN::ErrorOr<BAN::RefCounted<Thread>> Thread::create(const BAN::Function<void()>& function)
|
||||||
|
{
|
||||||
|
return BAN::RefCounted<Thread>::create(function);
|
||||||
|
}
|
||||||
|
|
||||||
Thread::Thread(const BAN::Function<void()>& function)
|
Thread::Thread(const BAN::Function<void()>& function)
|
||||||
: m_tid(s_next_tid++)
|
: m_tid(s_next_tid++)
|
||||||
, m_function(function)
|
, m_function(function)
|
||||||
|
@ -46,10 +52,8 @@ namespace Kernel
|
||||||
|
|
||||||
void Thread::on_exit()
|
void Thread::on_exit()
|
||||||
{
|
{
|
||||||
asm volatile("cli");
|
Scheduler::get().set_current_thread_done();
|
||||||
m_state = State::Done;
|
ASSERT_NOT_REACHED();
|
||||||
Scheduler::get().switch_thread();
|
|
||||||
ASSERT(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -104,9 +104,10 @@ extern "C" void kernel_main()
|
||||||
dprintln("Could not initialize input drivers");
|
dprintln("Could not initialize input drivers");
|
||||||
dprintln("Input initialized");
|
dprintln("Input initialized");
|
||||||
|
|
||||||
Scheduler::initialize();
|
MUST(Scheduler::initialize());
|
||||||
Scheduler& scheduler = Scheduler::get();
|
Scheduler& scheduler = Scheduler::get();
|
||||||
MUST(scheduler.add_thread(BAN::Function<void()>(
|
#if 1
|
||||||
|
MUST(scheduler.add_thread(MUST(Thread::create(
|
||||||
[terminal_driver]
|
[terminal_driver]
|
||||||
{
|
{
|
||||||
if (auto error = VirtualFileSystem::initialize(); error.is_error())
|
if (auto error = VirtualFileSystem::initialize(); error.is_error())
|
||||||
|
@ -121,15 +122,16 @@ extern "C" void kernel_main()
|
||||||
else
|
else
|
||||||
terminal_driver->set_font(font_or_error.release_value());
|
terminal_driver->set_font(font_or_error.release_value());
|
||||||
}
|
}
|
||||||
)));
|
))));
|
||||||
MUST(scheduler.add_thread(BAN::Function<void()>(
|
#endif
|
||||||
|
MUST(scheduler.add_thread(MUST(Thread::create(
|
||||||
[tty1]
|
[tty1]
|
||||||
{
|
{
|
||||||
Shell* shell = new Shell(tty1);
|
Shell* shell = new Shell(tty1);
|
||||||
ASSERT(shell);
|
ASSERT(shell);
|
||||||
shell->run();
|
shell->run();
|
||||||
}
|
}
|
||||||
)));
|
))));
|
||||||
scheduler.start();
|
scheduler.start();
|
||||||
ASSERT(false);
|
ASSERT(false);
|
||||||
}
|
}
|
Loading…
Reference in New Issue