diff --git a/modules.yaml b/modules.yaml index 7e95a72..5ecb5e2 100644 --- a/modules.yaml +++ b/modules.yaml @@ -40,6 +40,7 @@ modules: - src/kernel/objects/handle.cpp - src/kernel/objects/kobject.cpp - src/kernel/objects/thread.cpp + - src/kernel/objects/process.cpp - src/kernel/page_manager.cpp - src/kernel/pci.cpp - src/kernel/scheduler.cpp diff --git a/src/include/j6/signals.h b/src/include/j6/signals.h index 938e66b..c1154d0 100644 --- a/src/include/j6/signals.h +++ b/src/include/j6/signals.h @@ -6,6 +6,7 @@ #define j6_signal_no_handles (1ull << 0) // Signals 16-47 are defined per-object-type +#define j6_signal_process_exit (1ull << 16) // Signals 48-63 are user-defined signals #define j6_signal_user0 (1ull << 48) diff --git a/src/kernel/objects/process.cpp b/src/kernel/objects/process.cpp new file mode 100644 index 0000000..86751a0 --- /dev/null +++ b/src/kernel/objects/process.cpp @@ -0,0 +1,78 @@ +#include "j6/signals.h" +#include "kutil/assert.h" +#include "objects/process.h" +#include "objects/thread.h" +#include "page_manager.h" + +kutil::vector process::s_processes; + +process::process(page_table *pml4) : + kobject(kobject::type::process), + m_pml4(pml4) +{ + s_processes.append(this); +} + +process::~process() +{ + s_processes.remove_swap(this); +} + +void +process::exit(unsigned code) +{ + for (auto *thread : m_threads) { + thread->exit(code); + } + m_return_code = code; + page_manager::get()->delete_process_map(m_pml4); + assert_signal(j6_signal_process_exit); +} + +void +process::update() +{ + kassert(m_threads.count() > 0, "process::update with zero threads!"); + + size_t i = 0; + uint32_t status = 0; + while (i < m_threads.count()) { + thread *th = m_threads[i]; + if (th->has_state(thread::state::exited)) { + status = th->m_return_code; + m_threads.remove_swap_at(i); + continue; + } + i++; + } + + if (m_threads.count() == 0) { + // TODO: What really is the return code in this case? + exit(status); + } +} + +thread * +process::create_thread(uint8_t priority) +{ + thread *th = new thread(*this, priority); + kassert(th, "Failed to create thread!"); + m_threads.append(th); + return th; +} + +bool +process::thread_exited(thread *th) +{ + kassert(&th->m_parent == this, "Process got thread_exited for non-child!"); + uint32_t status = th->m_return_code; + m_threads.remove_swap(th); + delete th; + + if (m_threads.count() == 0) { + exit(status); + return true; + } + + return false; +} diff --git a/src/kernel/objects/process.h b/src/kernel/objects/process.h new file mode 100644 index 0000000..38e8d22 --- /dev/null +++ b/src/kernel/objects/process.h @@ -0,0 +1,46 @@ +#pragma once +/// \file process.h +/// Definition of process kobject types + +#include "objects/kobject.h" +#include "page_table.h" + +class process : + public kobject +{ +public: + /// Constructor. + /// \args pml4 Root of the process' page tables + process(page_table *pml4); + + /// Destructor. + virtual ~process(); + + /// Terminate this process. + /// \arg code The return code to exit with. + void exit(unsigned code); + + /// Update internal bookkeeping about threads. + void update(); + + /// Get the process' page table root + page_table * pml4() { return m_pml4; } + + /// Create a new thread in this process + /// \args priority The new thread's scheduling priority + /// \returns The newly created thread object + thread * create_thread(uint8_t priorty); + + /// Inform the process of an exited thread + /// \args th The thread which has exited + /// \returns True if this thread ending has ended the process + bool thread_exited(thread *th); + +private: + uint32_t m_return_code; + + page_table *m_pml4; + kutil::vector m_threads; + + static kutil::vector s_processes; +}; diff --git a/src/kernel/objects/thread.cpp b/src/kernel/objects/thread.cpp index 4161524..b492b8c 100644 --- a/src/kernel/objects/thread.cpp +++ b/src/kernel/objects/thread.cpp @@ -1,18 +1,20 @@ #include "j6/signals.h" #include "objects/thread.h" +#include "objects/process.h" #include "scheduler.h" static constexpr j6_signal_t thread_default_signals = 0; -thread::thread(page_table *pml4, priority_t pri) : +thread::thread(process &parent, uint8_t pri) : kobject(kobject::type::thread, thread_default_signals), + m_parent(parent), m_state(state::loading), m_wait_type(wait_type::none), m_wait_data(0), m_wait_obj(0) { TCB *tcbp = tcb(); - tcbp->pml4 = pml4; + tcbp->pml4 = parent.pml4(); tcbp->priority = pri; tcbp->thread_data = this; set_state(state::ready); @@ -74,8 +76,6 @@ thread::wake_on_result(kobject *obj, j6_status_t result) void thread::exit(uint32_t code) { - // TODO: check if the process containing this thread - // needs to exit and clean up. m_return_code = code; set_state(state::exited); clear_state(state::ready); diff --git a/src/kernel/objects/thread.h b/src/kernel/objects/thread.h index 6d18ad5..325dca2 100644 --- a/src/kernel/objects/thread.h +++ b/src/kernel/objects/thread.h @@ -5,9 +5,8 @@ #include "kutil/linked_list.h" #include "objects/kobject.h" -using priority_t = uint8_t; - struct page_table; +class process; struct TCB { @@ -18,7 +17,7 @@ struct TCB uintptr_t rsp3; page_table *pml4; - priority_t priority; + uint8_t priority; // note: 3 bytes padding // TODO: move state into TCB? @@ -48,11 +47,6 @@ public: none = 0x00 }; - /// Constructor. - /// \arg pml4 Root page table for the thread's owning process - /// \arg pri Initial priority level of this thread - thread(page_table *pml4, priority_t pri); - /// Get the `ready` state of the thread. /// \returns True if the thread is ready to execute. inline bool ready() const { return has_state(state::ready); } @@ -66,11 +60,11 @@ public: inline bool constant() const { return has_state(state::constant); } /// Get the thread priority. - inline priority_t priority() const { return m_tcb.priority; } + inline uint8_t priority() const { return m_tcb.priority; } /// Set the thread priority. /// \arg p The new thread priority - inline void set_priority(priority_t p) { if (!constant()) m_tcb.priority = p; } + inline void set_priority(uint8_t p) { if (!constant()) m_tcb.priority = p; } /// Block the thread, waiting on the given object's signals. /// \arg obj Object to wait on @@ -110,6 +104,7 @@ public: } inline tcb_node * tcb() { return &m_tcb; } + inline process & parent() { return m_parent; } /// Terminate this thread. /// \arg code The return code to exit with. @@ -119,6 +114,14 @@ private: thread() = delete; thread(const thread &other) = delete; thread(const thread &&other) = delete; + friend class process; + + /// Constructor. + /// \arg p The process which owns this thread + /// \arg pri Initial priority level of this thread + thread(process &parent, uint8_t pri); + + process &m_parent; tcb_node m_tcb; diff --git a/src/kernel/scheduler.cpp b/src/kernel/scheduler.cpp index e77e4e6..feeb82c 100644 --- a/src/kernel/scheduler.cpp +++ b/src/kernel/scheduler.cpp @@ -9,6 +9,7 @@ #include "kernel_memory.h" #include "log.h" #include "msr.h" +#include "objects/process.h" #include "page_manager.h" #include "scheduler.h" @@ -83,10 +84,12 @@ add_fake_task_return(TCB *tcb, uintptr_t rip) scheduler::scheduler(lapic *apic) : m_apic(apic), m_next_pid(1), - m_clock(0) + m_clock(0), + m_last_promotion(0) { page_table *pml4 = page_manager::get_pml4(); - thread *idle = new thread(pml4, max_priority); + m_kernel_process = new process(pml4); + thread *idle = m_kernel_process->create_thread(max_priority); auto *tcb = idle->tcb(); @@ -170,7 +173,8 @@ load_process_image(const void *image_start, size_t bytes, TCB *tcb) thread * scheduler::create_process(page_table *pml4) { - thread *th = new thread(pml4, default_priority); + process *p = new process(pml4); + thread *th = p->create_thread(default_priority); auto *tcb = th->tcb(); tcb->time_left = quantum(default_priority) + startup_bonus; @@ -271,83 +275,70 @@ scheduler::start() void scheduler::prune(uint64_t now) { - // Find processes that aren't ready or aren't running and + // Find processes that are ready or have exited and // move them to the appropriate lists. - for (auto &pri_list : m_runlists) { - auto *tcb = pri_list.front(); - while (tcb) { - thread *th = tcb->thread_data; - uint64_t age = now - tcb->last_ran; - uint8_t priority = tcb->priority; - - bool ready = th->has_state(thread::state::ready); - bool constant = th->has_state(thread::state::constant); - - bool stale = age > quantum(priority) * 2 && - tcb->priority > promote_limit && - !constant; - - if (ready) { - auto *remove = tcb; - tcb = tcb->next(); - - if (stale) { - m_runlists[remove->priority].remove(remove); - remove->priority -= 1; - remove->time_left = quantum(remove->priority); - m_runlists[remove->priority].push_back(remove); - log::debug(logs::task, "Scheduler promoting thread %llx, priority %d", - th->koid(), remove->priority); - } - - continue; - } - - auto *remove = tcb; - tcb = tcb->next(); - pri_list.remove(remove); - - bool exited = th->has_state(thread::state::exited); - - if (exited) { - // TODO: Alert continaing process thread exitied, - // and exit process if it was the last thread. - /* - auto *parent = get_process_by_id(remove->ppid); - if (parent && parent->wake_on_child(remove)) { - m_blocked.remove(parent); - m_runlists[parent->priority].push_back(parent); - delete remove; - } else { - m_exited.push_back(remove); - } - */ - m_exited.push_back(remove); - } else { - log::debug(logs::task, "Prune: moving blocked thread %llx", th->koid()); - m_blocked.push_back(remove); - } - } - } - - // Find blocked processes that are ready (possibly after waking wating - // ones) and move them to the appropriate runlist. auto *tcb = m_blocked.front(); while (tcb) { thread *th = tcb->thread_data; + uint8_t priority = tcb->priority; bool ready = th->has_state(thread::state::ready); + bool exited = th->has_state(thread::state::exited); + bool constant = th->has_state(thread::state::constant); + ready |= th->wake_on_time(now); auto *remove = tcb; tcb = tcb->next(); - if (!ready) continue; + if (!exited && !ready) + continue; m_blocked.remove(remove); - m_runlists[remove->priority].push_front(remove); + + if (exited) { + process &p = th->parent(); + if(p.thread_exited(th)) + delete &p; + } else { + log::debug(logs::task, "Prune: readying unblocked thread %llx", th->koid()); + m_runlists[remove->priority].push_back(remove); + } } } +void +scheduler::check_promotions(uint64_t now) +{ + for (auto &pri_list : m_runlists) { + for (auto *tcb : pri_list) { + const thread *th = m_current->thread_data; + const bool constant = th->has_state(thread::state::constant); + if (constant) + continue; + + const uint64_t age = now - tcb->last_ran; + const uint8_t priority = tcb->priority; + + bool stale = + age > quantum(priority) * 2 && + tcb->priority > promote_limit && + !constant; + + if (stale) { + // If the thread is stale, promote it + m_runlists[priority].remove(tcb); + tcb->priority -= 1; + tcb->time_left = quantum(tcb->priority); + m_runlists[tcb->priority].push_back(tcb); + log::debug(logs::task, "Scheduler promoting thread %llx, priority %d", + th->koid(), tcb->priority); + } + } + } + + m_last_promotion = now; +} + void scheduler::schedule() { @@ -381,6 +372,8 @@ scheduler::schedule() clock::get().update(); prune(++m_clock); + if (m_clock - m_last_promotion > promote_frequency) + check_promotions(m_clock); priority = 0; while (m_runlists[priority].empty()) { diff --git a/src/kernel/scheduler.h b/src/kernel/scheduler.h index f772f78..6b1e960 100644 --- a/src/kernel/scheduler.h +++ b/src/kernel/scheduler.h @@ -6,6 +6,7 @@ #include "objects/thread.h" class lapic; +class process; struct page_table; struct cpu_state; @@ -86,6 +87,8 @@ private: friend uintptr_t syscall_dispatch(uintptr_t, cpu_state &); friend class process; + static constexpr uint64_t promote_frequency = 10; + /// Create a new process object. This process will have its pid /// set but nothing else. /// \arg pml4 The root page table of the process @@ -93,19 +96,21 @@ private: thread * create_process(page_table *pml4); void prune(uint64_t now); + void check_promotions(uint64_t now); lapic *m_apic; uint32_t m_next_pid; uint32_t m_tick_count; + process *m_kernel_process; tcb_node *m_current; tcb_list m_runlists[num_priorities]; tcb_list m_blocked; - tcb_list m_exited; // TODO: lol a real clock uint64_t m_clock = 0; + uint64_t m_last_promotion; static scheduler s_instance; };