diff --git a/modules.yaml b/modules.yaml index ebdd98f..7e95a72 100644 --- a/modules.yaml +++ b/modules.yaml @@ -39,9 +39,9 @@ modules: - src/kernel/msr.cpp - src/kernel/objects/handle.cpp - src/kernel/objects/kobject.cpp + - src/kernel/objects/thread.cpp - src/kernel/page_manager.cpp - src/kernel/pci.cpp - - src/kernel/process.cpp - src/kernel/scheduler.cpp - src/kernel/screen.cpp - src/kernel/serial.cpp diff --git a/src/include/j6/signals.h b/src/include/j6/signals.h index 0f27c2f..938e66b 100644 --- a/src/include/j6/signals.h +++ b/src/include/j6/signals.h @@ -2,17 +2,25 @@ /// \file signals.h /// Collection of constants for the j6_signal_t type -// Signals 0-7 are common to all types -#define j6_signal_no_handles (1 << 0) +// Signals 0-15 are common to all types +#define j6_signal_no_handles (1ull << 0) -// Signals 8-15 are user-defined signals -#define j6_signal_user0 (1 << 8) -#define j6_signal_user1 (1 << 9) -#define j6_signal_user2 (1 << 10) -#define j6_signal_user3 (1 << 11) -#define j6_signal_user4 (1 << 12) -#define j6_signal_user5 (1 << 13) -#define j6_signal_user6 (1 << 14) -#define j6_signal_user7 (1 << 15) +// Signals 16-47 are defined per-object-type -// All other signals are type-specific +// Signals 48-63 are user-defined signals +#define j6_signal_user0 (1ull << 48) +#define j6_signal_user1 (1ull << 49) +#define j6_signal_user2 (1ull << 50) +#define j6_signal_user3 (1ull << 51) +#define j6_signal_user4 (1ull << 52) +#define j6_signal_user5 (1ull << 53) +#define j6_signal_user6 (1ull << 54) +#define j6_signal_user7 (1ull << 55) +#define j6_signal_user8 (1ull << 56) +#define j6_signal_user9 (1ull << 57) +#define j6_signal_user10 (1ull << 58) +#define j6_signal_user11 (1ull << 59) +#define j6_signal_user12 (1ull << 60) +#define j6_signal_user13 (1ull << 61) +#define j6_signal_user14 (1ull << 62) +#define j6_signal_user15 (1ull << 63) diff --git a/src/kernel/cpu.h b/src/kernel/cpu.h index 89bc528..66110a7 100644 --- a/src/kernel/cpu.h +++ b/src/kernel/cpu.h @@ -2,7 +2,7 @@ #include -struct process; +struct TCB; struct cpu_state { @@ -18,7 +18,7 @@ struct cpu_data { uintptr_t rsp0; uintptr_t rsp3; - process *tcb; + TCB *tcb; }; extern cpu_data bsp_cpu_data; diff --git a/src/kernel/interrupts.cpp b/src/kernel/interrupts.cpp index 97f7221..2efa27d 100644 --- a/src/kernel/interrupts.cpp +++ b/src/kernel/interrupts.cpp @@ -11,6 +11,7 @@ #include "interrupts.h" #include "io.h" #include "log.h" +#include "page_manager.h" #include "scheduler.h" #include "syscall.h" diff --git a/src/kernel/loader.s b/src/kernel/loader.s index 79f88a7..8720435 100644 --- a/src/kernel/loader.s +++ b/src/kernel/loader.s @@ -11,7 +11,7 @@ ramdisk_process_loader: pop rdi ; the address of the program image pop rsi ; the size of the program image - pop rdx ; the address of this process' process structure + pop rdx ; the address of this thread's TCB call load_process_image diff --git a/src/kernel/main.cpp b/src/kernel/main.cpp index e9d8f00..b2343d9 100644 --- a/src/kernel/main.cpp +++ b/src/kernel/main.cpp @@ -48,6 +48,7 @@ void memory_initialize_post_ctors(kernel::args::header *kargs); using namespace kernel; +/* class test_observer : public kobject::observer { @@ -67,6 +68,7 @@ public: const char *m_name; }; +*/ void init_console() @@ -174,7 +176,7 @@ kernel_main(args::header *header) syscall_enable(); scheduler *sched = new (&scheduler::get()) scheduler(devices.get_lapic()); - sched->create_kernel_task(-1, logger_task, scheduler::max_priority-1, process_flags::const_pri); + sched->create_kernel_task(logger_task, scheduler::max_priority-1, true); for (auto &ird : initrds) { for (auto &f : ird.files()) { @@ -185,6 +187,7 @@ kernel_main(args::header *header) log::info(logs::objs, "Testing object system:"); + /* test_observer obs1("event"); test_observer obs2("no handles"); { @@ -197,6 +200,7 @@ kernel_main(args::header *header) handle h(1, 0, &e); } + */ sched->start(); } diff --git a/src/kernel/objects/kobject.cpp b/src/kernel/objects/kobject.cpp index fff0e9d..017d940 100644 --- a/src/kernel/objects/kobject.cpp +++ b/src/kernel/objects/kobject.cpp @@ -2,6 +2,7 @@ #include "j6/signals.h" #include "j6/types.h" #include "objects/kobject.h" +#include "objects/thread.h" // TODO: per-cpu this? static j6_koid_t next_koid; @@ -14,7 +15,8 @@ kobject::kobject(type t, j6_signal_t signals) : kobject::~kobject() { - notify_signal_observers(0, j6_status_destroyed); + for (auto *t : m_blocked_threads) + t->wake_on_result(this, j6_status_destroyed); } j6_koid_t @@ -33,58 +35,30 @@ void kobject::assert_signal(j6_signal_t s) { m_signals |= s; - notify_signal_observers(s); + notify_signal_observers(); } void kobject::deassert_signal(j6_signal_t s) { m_signals &= ~s; - notify_signal_observers(s); } void -kobject::register_signal_observer(observer *object, j6_signal_t s) +kobject::notify_signal_observers() { - m_observers.emplace(object, s); -} + size_t i = 0; + bool readied = false; + while (i < m_blocked_threads.count()) { + thread *t = m_blocked_threads[i]; -void -kobject::deregister_signal_observer(observer *object) -{ - for (size_t i = 0; i < m_observers.count(); ++i) { - auto ® = m_observers[i]; - if (reg.object != object) continue; - - reg = m_observers[m_observers.count() - 1]; - m_observers.remove(); - break; - } -} - -void -kobject::notify_signal_observers(j6_signal_t mask, j6_status_t result) -{ - for (auto ® : m_observers) { - if (mask == 0 || (reg.signals & mask) != 0) { - if (!reg.object->on_signals_changed(this, m_signals, mask, result)) - reg.object = nullptr; + if (t->wake_on_signals(this, m_signals)) { + m_blocked_threads.remove_swap_at(i); + readied = true; + } else { + ++i; } } - - // Compact the observer list - long last = m_observers.count() - 1; - while (m_observers[last].object == nullptr && last >= 0) last--; - - for (long i = 0; i < long(m_observers.count()) && i < last; ++i) { - auto ® = m_observers[i]; - if (reg.object != nullptr) continue; - reg = m_observers[last--]; - - while (m_observers[last].object == nullptr && last >= i) last--; - } - - m_observers.set_size(last + 1); } void diff --git a/src/kernel/objects/kobject.h b/src/kernel/objects/kobject.h index d8a8b39..875a824 100644 --- a/src/kernel/objects/kobject.h +++ b/src/kernel/objects/kobject.h @@ -6,6 +6,8 @@ #include "j6/types.h" #include "kutil/vector.h" +class thread; + /// Base type for all user-interactable kernel objects class kobject { @@ -39,6 +41,9 @@ public: /// \returns The object type for the koid static type koid_type(j6_koid_t koid); + /// Get this object's koid + inline j6_koid_t koid() const { return m_koid; } + /// Set the given signals active on this object /// \arg s The set of signals to assert void assert_signal(j6_signal_t s); @@ -47,31 +52,6 @@ public: /// \arg s The set of signals to deassert void deassert_signal(j6_signal_t s); - class observer - { - public: - /// Callback for when signals change. - /// \arg obj The object triggering the callback - /// \arg s The current state of obj's signals - /// \arg ds Which signals caused the callback - /// \arg result Status code for this notification - /// \returns True if this object wants to keep watching signals - virtual bool on_signals_changed( - kobject *obj, - j6_signal_t s, - j6_signal_t ds, - j6_status_t result) = 0; - }; - - /// Register a signal observer - /// \arg object The observer - /// \arg s The signals the object wants notifiations for - void register_signal_observer(observer *object, j6_signal_t s); - - /// Deegister a signal observer - /// \arg object The observer - void deregister_signal_observer(observer *object); - /// Increment the handle refcount inline void handle_retain() { ++m_handle_count; } @@ -80,31 +60,30 @@ public: if (--m_handle_count == 0) on_no_handles(); } + /// Add the given thread to the list of threads waiting on this object. + inline void add_blocked_thread(thread *t) { m_blocked_threads.append(t); } + + /// Remove the given thread from the list of threads waiting on this object. + inline void remove_blocked_thread(thread *t) { m_blocked_threads.remove_swap(t); } + protected: + /// Interface for subclasses to handle when all handles are closed. Subclasses + /// should either call the base version, or assert j6_signal_no_handles. + virtual void on_no_handles(); + +private: kobject() = delete; kobject(const kobject &other) = delete; kobject(const kobject &&other) = delete; /// Notifiy observers of this object - /// \arg mask The signals that triggered this call. If 0, notify all observers. /// \arg result The result to pass to the observers - void notify_signal_observers(j6_signal_t mask, j6_status_t result = j6_status_ok); - - /// Interface for subclasses to handle when all handles are closed. Subclasses - /// should either call the base version, or assert j6_signal_no_handles. - virtual void on_no_handles(); + void notify_signal_observers(); j6_koid_t m_koid; j6_signal_t m_signals; uint16_t m_handle_count; - struct observer_registration - { - observer_registration(observer* o = nullptr, j6_signal_t s = 0) : - object(o), signals(s) {} - - observer *object; - j6_signal_t signals; - }; - kutil::vector m_observers; +protected: + kutil::vector m_blocked_threads; }; diff --git a/src/kernel/objects/thread.cpp b/src/kernel/objects/thread.cpp new file mode 100644 index 0000000..4161524 --- /dev/null +++ b/src/kernel/objects/thread.cpp @@ -0,0 +1,83 @@ +#include "j6/signals.h" +#include "objects/thread.h" +#include "scheduler.h" + +static constexpr j6_signal_t thread_default_signals = 0; + +thread::thread(page_table *pml4, priority_t pri) : + kobject(kobject::type::thread, thread_default_signals), + 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->priority = pri; + tcbp->thread_data = this; + set_state(state::ready); +} + +void +thread::wait_on_signals(kobject *obj, j6_signal_t signals) +{ + m_wait_type = wait_type::signal; + m_wait_data = signals; + clear_state(state::ready); +} + +void +thread::wait_on_time(uint64_t t) +{ + m_wait_type = wait_type::time; + m_wait_data = t; + clear_state(state::ready); +} + +bool +thread::wake_on_signals(kobject *obj, j6_signal_t signals) +{ + if (m_wait_type != wait_type::signal || + (signals & m_wait_data) == 0) + return false; + + m_wait_type = wait_type::none; + m_wait_data = j6_status_ok; + m_wait_obj = obj->koid(); + set_state(state::ready); + return true; +} + +bool +thread::wake_on_time(uint64_t now) +{ + if (m_wait_type != wait_type::time || + now < m_wait_data) + return false; + + m_wait_type = wait_type::none; + m_wait_data = j6_status_ok; + m_wait_obj = 0; + set_state(state::ready); + return true; +} + +void +thread::wake_on_result(kobject *obj, j6_status_t result) +{ + m_wait_type = wait_type::none; + m_wait_data = result; + m_wait_obj = obj->koid(); + set_state(state::ready); +} + +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 new file mode 100644 index 0000000..6d18ad5 --- /dev/null +++ b/src/kernel/objects/thread.h @@ -0,0 +1,133 @@ +#pragma once +/// \file thread.h +/// Definition of thread kobject types + +#include "kutil/linked_list.h" +#include "objects/kobject.h" + +using priority_t = uint8_t; + +struct page_table; + +struct TCB +{ + // Data used by assembly task control routines. If you change any of these, + // be sure to change the assembly definitions in 'tasking.inc' + uintptr_t rsp; + uintptr_t rsp0; + uintptr_t rsp3; + page_table *pml4; + + priority_t priority; + // note: 3 bytes padding + + // TODO: move state into TCB? + + uintptr_t kernel_stack; + size_t kernel_stack_size; + + uint32_t time_left; + uint64_t last_ran; + + thread *thread_data; +}; + +using tcb_list = kutil::linked_list; +using tcb_node = tcb_list::item_type; + +class thread : + public kobject +{ +public: + enum class wait_type : uint8_t { none, signal, time }; + enum class state : uint8_t { + ready = 0x01, + loading = 0x02, + exited = 0x04, + constant = 0x80, + 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); } + + /// Get the `loading` state of the thread. + /// \returns True if the thread has not finished loading. + inline bool loading() const { return has_state(state::loading); } + + /// Get the `constant` state of the thread. + /// \returns True if the thread has constant priority. + inline bool constant() const { return has_state(state::constant); } + + /// Get the thread priority. + inline priority_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; } + + /// Block the thread, waiting on the given object's signals. + /// \arg obj Object to wait on + /// \arg signals Mask of signals to wait for + void wait_on_signals(kobject *obj, j6_signal_t signals); + + /// Block the thread, waiting for a given clock value + /// \arg t Clock value to wait for + void wait_on_time(uint64_t t); + + /// Wake the thread if it is waiting on signals. + /// \arg obj Object that changed signals + /// \arg signals Signal state of the object + /// \returns True if this action unblocked the thread + bool wake_on_signals(kobject *obj, j6_signal_t signals); + + /// Wake the thread if it is waiting on the clock. + /// \arg now Current clock value + /// \returns True if this action unblocked the thread + bool wake_on_time(uint64_t now); + + /// Wake the thread with a given result code. + /// \arg obj Object that changed signals + /// \arg result Result code to return to the thread + void wake_on_result(kobject *obj, j6_status_t result); + + inline bool has_state(state s) const { + return static_cast(m_state) & static_cast(s); + } + + inline void set_state(state s) { + m_state = static_cast(static_cast(m_state) | static_cast(s)); + } + + inline void clear_state(state s) { + m_state = static_cast(static_cast(m_state) & ~static_cast(s)); + } + + inline tcb_node * tcb() { return &m_tcb; } + + /// Terminate this thread. + /// \arg code The return code to exit with. + void exit(unsigned code); + +private: + thread() = delete; + thread(const thread &other) = delete; + thread(const thread &&other) = delete; + + tcb_node m_tcb; + + state m_state; + wait_type m_wait_type; + // There should be 1 byte of padding here + + uint32_t m_return_code; + + uint64_t m_wait_data; + j6_koid_t m_wait_obj; +}; diff --git a/src/kernel/scheduler.cpp b/src/kernel/scheduler.cpp index 16ec416..e77e4e6 100644 --- a/src/kernel/scheduler.cpp +++ b/src/kernel/scheduler.cpp @@ -24,45 +24,97 @@ const uint64_t rflags_int = 0x202; extern "C" { void ramdisk_process_loader(); - uintptr_t load_process_image(const void *image_start, size_t bytes, process *proc); + uintptr_t load_process_image(const void *image_start, size_t bytes, TCB *tcb); }; extern uint64_t idle_stack_end; +/// Set up a new empty kernel stack for this thread. Sets rsp0 on the +/// TCB object, but also returns it. +/// \returns The new rsp0 as a pointer +static void * +setup_kernel_stack(TCB *tcb) +{ + constexpr size_t initial_stack_size = 0x1000; + constexpr unsigned null_frame_entries = 2; + constexpr size_t null_frame_size = null_frame_entries * sizeof(uint64_t); + + void *stack_bottom = kutil::kalloc(initial_stack_size); + kutil::memset(stack_bottom, 0, initial_stack_size); + + log::debug(logs::memory, "Created kernel stack at %016lx size 0x%lx", + stack_bottom, initial_stack_size); + + void *stack_top = + kutil::offset_pointer(stack_bottom, + initial_stack_size - null_frame_size); + + uint64_t *null_frame = reinterpret_cast(stack_top); + for (unsigned i = 0; i < null_frame_entries; ++i) + null_frame[i] = 0; + + tcb->kernel_stack_size = initial_stack_size; + tcb->kernel_stack = reinterpret_cast(stack_bottom); + tcb->rsp0 = reinterpret_cast(stack_top); + tcb->rsp = tcb->rsp0; + + return stack_top; +} + +/// Initialize this process' kenrel stack with a fake return segment for +/// returning out of task_switch. +/// \arg tcb TCB of the thread to modify +/// \arg rip The rip to return to +static void +add_fake_task_return(TCB *tcb, uintptr_t rip) +{ + tcb->rsp -= sizeof(uintptr_t) * 7; + uintptr_t *stack = reinterpret_cast(tcb->rsp); + + stack[6] = rip; // return rip + stack[5] = tcb->rsp0; // rbp + stack[4] = 0xbbbbbbbb; // rbx + stack[3] = 0x12121212; // r12 + stack[2] = 0x13131313; // r13 + stack[1] = 0x14141414; // r14 + stack[0] = 0x15151515; // r15 +} + scheduler::scheduler(lapic *apic) : m_apic(apic), m_next_pid(1), m_clock(0) { - auto *idle = new process_node; + page_table *pml4 = page_manager::get_pml4(); + thread *idle = new thread(pml4, max_priority); + + auto *tcb = idle->tcb(); + + log::debug(logs::task, "Idle thread koid %llx", idle->koid()); // The kernel idle task, also the thread we're in now - idle->pid = 0; - idle->ppid = 0; - idle->priority = max_priority; - idle->rsp = 0; // This will get set when we switch away - idle->rsp3 = 0; // Never used for the idle task - idle->rsp0 = reinterpret_cast(&idle_stack_end); - idle->pml4 = page_manager::get_pml4(); - idle->flags = - process_flags::running | - process_flags::ready | - process_flags::const_pri; + tcb->rsp = 0; // This will get set when we switch away + tcb->rsp3 = 0; // Never used for the idle task + tcb->rsp0 = reinterpret_cast(&idle_stack_end); - m_runlists[max_priority].push_back(idle); - m_current = idle; + idle->set_state(thread::state::constant); - bsp_cpu_data.rsp0 = idle->rsp0; - bsp_cpu_data.tcb = idle; + m_runlists[max_priority].push_back(tcb); + m_current = tcb; + + bsp_cpu_data.rsp0 = tcb->rsp0; + bsp_cpu_data.tcb = tcb; } uintptr_t -load_process_image(const void *image_start, size_t bytes, process *proc) +load_process_image(const void *image_start, size_t bytes, TCB *tcb) { // We're now in the process space for this process, allocate memory for the // process code and load it page_manager *pager = page_manager::get(); + thread *th = tcb->thread_data; + log::debug(logs::loader, "Loading task! ELF: %016lx [%d]", image_start, bytes); // TODO: Handle bad images gracefully @@ -108,33 +160,30 @@ load_process_image(const void *image_start, size_t bytes, process *proc) kutil::memcpy(dest, src, header->size); } - proc->flags &= ~process_flags::loading; + th->clear_state(thread::state::loading); uintptr_t entrypoint = image.entrypoint(); - log::debug(logs::loader, " Loaded! New process rip: %016lx", entrypoint); + log::debug(logs::loader, " Loaded! New thread rip: %016lx", entrypoint); return entrypoint; } -process_node * -scheduler::create_process(pid_t pid) +thread * +scheduler::create_process(page_table *pml4) { - kassert(pid <= 0, "Cannot specify a positive pid in create_process"); + thread *th = new thread(pml4, default_priority); + auto *tcb = th->tcb(); - auto *proc = new process_node; - proc->pid = pid ? pid : m_next_pid++; - proc->priority = default_priority; - proc->time_left = quantum(default_priority) + startup_bonus; + tcb->time_left = quantum(default_priority) + startup_bonus; - log::debug(logs::task, "Creating process %d, priority %d, time slice %d", - proc->pid, proc->priority, proc->time_left); + log::debug(logs::task, "Creating thread %llx, priority %d, time slice %d", + th->koid(), tcb->priority, tcb->time_left); - return proc; + return th; } void scheduler::load_process(const char *name, const void *data, size_t size) { - auto *proc = create_process(); uint16_t kcs = (1 << 3) | 0; // Kernel CS is GDT entry 1, ring 0 uint16_t cs = (5 << 3) | 3; // User CS is GDT entry 5, ring 3 @@ -143,19 +192,22 @@ scheduler::load_process(const char *name, const void *data, size_t size) uint16_t ss = (4 << 3) | 3; // User SS is GDT entry 4, ring 3 // Set up the page tables - this also allocates an initial user stack - proc->pml4 = page_manager::get()->create_process_map(); + page_table *pml4 = page_manager::get()->create_process_map(); + + thread* th = create_process(pml4); + auto *tcb = th->tcb(); // Create an initial kernel stack space - void *sp0 = proc->setup_kernel_stack(); + void *sp0 = setup_kernel_stack(tcb); uintptr_t *stack = reinterpret_cast(sp0) - 7; // Pass args to ramdisk_process_loader on the stack stack[0] = reinterpret_cast(data); stack[1] = reinterpret_cast(size); - stack[2] = reinterpret_cast(proc); + stack[2] = reinterpret_cast(tcb); - proc->rsp = reinterpret_cast(stack); - proc->add_fake_task_return( + tcb->rsp = reinterpret_cast(stack); + add_fake_task_return(tcb, reinterpret_cast(ramdisk_process_loader)); // Arguments for iret - rip will be pushed on before these @@ -164,46 +216,42 @@ scheduler::load_process(const char *name, const void *data, size_t size) stack[5] = initial_stack; stack[6] = ss; - proc->rsp3 = initial_stack; - proc->flags = - process_flags::running | - process_flags::ready | - process_flags::loading; + tcb->rsp3 = initial_stack; - m_runlists[default_priority].push_back(proc); + m_runlists[default_priority].push_back(tcb); - log::debug(logs::task, "Creating process %s: pid %d pri %d", name, proc->pid, proc->priority); - log::debug(logs::task, " RSP %016lx", proc->rsp); - log::debug(logs::task, " RSP0 %016lx", proc->rsp0); - log::debug(logs::task, " PML4 %016lx", proc->pml4); + log::debug(logs::task, "Loading thread %s: koid %llx pri %d", name, th->koid(), tcb->priority); + log::debug(logs::task, " RSP %016lx", tcb->rsp); + log::debug(logs::task, " RSP0 %016lx", tcb->rsp0); + log::debug(logs::task, " PML4 %016lx", tcb->pml4); } void -scheduler::create_kernel_task(pid_t pid, void (*task)(), uint8_t priority, process_flags flags) +scheduler::create_kernel_task(void (*task)(), uint8_t priority, bool constant) { - auto *proc = create_process(pid); + page_table *pml4 = page_manager::get()->get_kernel_pml4(); + thread *th = create_process(pml4); + auto *tcb = th->tcb(); uint16_t kcs = (1 << 3) | 0; // Kernel CS is GDT entry 1, ring 0 uint16_t kss = (2 << 3) | 0; // Kernel SS is GDT entry 2, ring 0 // Create an initial kernel stack space - proc->setup_kernel_stack(); - proc->add_fake_task_return( + setup_kernel_stack(tcb); + add_fake_task_return(tcb, reinterpret_cast(task)); - proc->priority = priority; - proc->pml4 = page_manager::get()->get_kernel_pml4(); - proc->flags = - process_flags::running | - process_flags::ready | - flags; + tcb->priority = priority; + tcb->pml4 = page_manager::get()->get_kernel_pml4(); + if (constant) + th->set_state(thread::state::constant); - m_runlists[default_priority].push_back(proc); + m_runlists[default_priority].push_back(tcb); - log::debug(logs::task, "Creating kernel task: pid %d pri %d", proc->pid, proc->priority); - log::debug(logs::task, " RSP0 %016lx", proc->rsp0); - log::debug(logs::task, " RSP %016lx", proc->rsp); - log::debug(logs::task, " PML4 %016lx", proc->pml4); + log::debug(logs::task, "Creating kernel task: thread %llx pri %d", th->koid(), tcb->priority); + log::debug(logs::task, " RSP0 %016lx", tcb->rsp0); + log::debug(logs::task, " RSP %016lx", tcb->rsp); + log::debug(logs::task, " PML4 %016lx", tcb->pml4); } uint32_t @@ -223,45 +271,48 @@ scheduler::start() void scheduler::prune(uint64_t now) { - // TODO: Promote processes that haven't been scheduled in too long - // Find processes that aren't ready or aren't running and // move them to the appropriate lists. for (auto &pri_list : m_runlists) { - auto *proc = pri_list.front(); - while (proc) { - uint64_t age = now - proc->last_ran; - process_flags flags = proc->flags; - uint8_t priority = proc->priority; + 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 running = flags && process_flags::running; - bool ready = flags && process_flags::ready; + bool ready = th->has_state(thread::state::ready); + bool constant = th->has_state(thread::state::constant); bool stale = age > quantum(priority) * 2 && - proc->priority > promote_limit && - !(flags && process_flags::const_pri); + tcb->priority > promote_limit && + !constant; - if (running && ready) { - auto *remove = proc; - proc = proc->next(); + 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 process %d, priority %d", - remove->pid, remove->priority); + log::debug(logs::task, "Scheduler promoting thread %llx, priority %d", + th->koid(), remove->priority); } continue; } - auto *remove = proc; - proc = proc->next(); + auto *remove = tcb; + tcb = tcb->next(); pri_list.remove(remove); - if (!(remove->flags && process_flags::running)) { + 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); @@ -270,7 +321,10 @@ void scheduler::prune(uint64_t now) } 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); } } @@ -278,13 +332,15 @@ void scheduler::prune(uint64_t now) // Find blocked processes that are ready (possibly after waking wating // ones) and move them to the appropriate runlist. - auto *proc = m_blocked.front(); - while (proc) { - bool ready = proc->flags && process_flags::ready; - ready |= proc->wake_on_time(now); + auto *tcb = m_blocked.front(); + while (tcb) { + thread *th = tcb->thread_data; - auto *remove = proc; - proc = proc->next(); + bool ready = th->has_state(thread::state::ready); + ready |= th->wake_on_time(now); + + auto *remove = tcb; + tcb = tcb->next(); if (!ready) continue; m_blocked.remove(remove); @@ -295,17 +351,19 @@ void scheduler::prune(uint64_t now) void scheduler::schedule() { - pid_t lastpid = m_current->pid; uint8_t priority = m_current->priority; uint32_t remaining = m_apic->stop_timer(); m_current->time_left = remaining; + thread *th = m_current->thread_data; + const bool constant = th->has_state(thread::state::constant); - if (remaining == 0 && priority < max_priority && - !(m_current->flags && process_flags::const_pri)) { - // Process used its whole timeslice, demote it - ++m_current->priority; - log::debug(logs::task, "Scheduler demoting process %d, priority %d", - m_current->pid, m_current->priority); + if (remaining == 0) { + if (priority < max_priority && !constant) { + // Process used its whole timeslice, demote it + ++m_current->priority; + log::debug(logs::task, "Scheduler demoting thread %llx, priority %d", + th->koid(), m_current->priority); + } m_current->time_left = quantum(m_current->priority); } else if (remaining > 0) { // Process gave up CPU, give it a small bonus to its @@ -315,7 +373,7 @@ scheduler::schedule() } m_runlists[priority].remove(m_current); - if (m_current->flags && process_flags::ready) { + if (th->has_state(thread::state::ready)) { m_runlists[m_current->priority].push_back(m_current); } else { m_blocked.push_back(m_current); @@ -332,18 +390,20 @@ scheduler::schedule() m_current->last_ran = m_clock; - m_current = m_runlists[priority].pop_front(); - m_current->last_ran = m_clock; - m_apic->reset_timer(m_current->time_left); + auto *next = m_runlists[priority].pop_front(); + next->last_ran = m_clock; + m_apic->reset_timer(next->time_left); - if (lastpid != m_current->pid) { + if (next != m_current) { + m_current = next; task_switch(m_current); - log::debug(logs::task, "Scheduler switched to process %d, priority %d time left %d @ %lld.", - m_current->pid, m_current->priority, m_current->time_left, m_clock); + log::debug(logs::task, "Scheduler switched to thread %llx, priority %d time left %d @ %lld.", + th->koid(), m_current->priority, m_current->time_left, m_clock); } } +/* process_node * scheduler::get_process_by_id(uint32_t pid) { @@ -364,3 +424,4 @@ scheduler::get_process_by_id(uint32_t pid) return nullptr; } +*/ diff --git a/src/kernel/scheduler.h b/src/kernel/scheduler.h index efcf857..f772f78 100644 --- a/src/kernel/scheduler.h +++ b/src/kernel/scheduler.h @@ -3,15 +3,14 @@ /// The task scheduler and related definitions #include -#include "process.h" +#include "objects/thread.h" class lapic; struct page_table; struct cpu_state; extern "C" void isr_handler(cpu_state*); -extern "C" void task_switch(process *next); -extern "C" void task_fork(process *child); +extern "C" void task_switch(TCB *next); /// The task scheduler @@ -27,7 +26,7 @@ public: /// Default priority on process creation static const uint8_t default_priority = 1; - /// Loest (most urgent) priority achieved via promotion + /// Lowest (most urgent) priority achieved via promotion static const uint8_t promote_limit = 1; /// How long the base timer quantum is, in us @@ -50,15 +49,13 @@ public: void load_process(const char *name, const void *data, size_t size); /// Create a new kernel task - /// \arg pid Pid to use for this task, must be negative /// \arg proc Function to run as a kernel task /// \arg priority Priority to start the process with - /// \arg flags Flags to add to the process + /// \arg constant True if this task cannot be promoted/demoted void create_kernel_task( - pid_t pid, void (*task)(), uint8_t priority, - process_flags flags = process_flags::none); + bool constant = false); /// Get the quantum for a given priority. uint32_t quantum(int priority); @@ -70,14 +67,16 @@ public: /// Run the scheduler, possibly switching to a new task void schedule(); - /// Get the current process. - /// \returns A pointer to the current process' process struct - inline process * current() { return m_current; } + /// Get the current TCB. + /// \returns A pointer to the current thread's TCB + inline TCB * current() { return m_current; } + /* /// Look up a process by its PID /// \arg pid The requested PID /// \returns The process matching that PID, or nullptr - process_node * get_process_by_id(uint32_t pid); + tcb_node * get_process_by_id(uint32_t pid); + */ /// Get a reference to the system scheduler /// \returns A reference to the global system scheduler @@ -89,9 +88,9 @@ private: /// Create a new process object. This process will have its pid /// set but nothing else. - /// \arg pid The pid to give the process (0 for automatic) - /// \returns The new process object - process_node * create_process(pid_t pid = 0); + /// \arg pml4 The root page table of the process + /// \returns The new process' main thread + thread * create_process(page_table *pml4); void prune(uint64_t now); @@ -100,10 +99,10 @@ private: uint32_t m_next_pid; uint32_t m_tick_count; - process_node *m_current; - process_list m_runlists[num_priorities]; - process_list m_blocked; - process_list m_exited; + 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; diff --git a/src/kernel/syscalls.inc b/src/kernel/syscalls.inc index b726252..f716e7e 100644 --- a/src/kernel/syscalls.inc +++ b/src/kernel/syscalls.inc @@ -2,8 +2,8 @@ SYSCALL(0x00, object_noop, void) SYSCALL(0x01, object_wait, j6_handle_t, j6_signal_t, j6_signal_t *) SYSCALL(0x11, process_exit, int64_t) -SYSCALL(0x12, process_fork, pid_t*) -SYSCALL(0x13, process_getpid, pid_t*) +SYSCALL(0x12, process_fork, uint32_t*) +SYSCALL(0x13, process_getpid, uint32_t*) SYSCALL(0x14, process_log, const char *) SYSCALL(0x15, process_pause, void) SYSCALL(0x16, process_sleep, uint64_t) diff --git a/src/kernel/syscalls/object.cpp b/src/kernel/syscalls/object.cpp index 2f50471..d1572ab 100644 --- a/src/kernel/syscalls/object.cpp +++ b/src/kernel/syscalls/object.cpp @@ -10,8 +10,9 @@ j6_status_t object_noop() { auto &s = scheduler::get(); - auto *p = s.current(); - log::debug(logs::syscall, "Process %d called noop syscall.", p->pid); + TCB *tcb = s.current(); + thread *th = tcb->thread_data; + log::debug(logs::syscall, "Thread %llx called noop syscall.", th->koid()); return j6_status_ok; } diff --git a/src/kernel/syscalls/process.cpp b/src/kernel/syscalls/process.cpp index ea217a4..694c8db 100644 --- a/src/kernel/syscalls/process.cpp +++ b/src/kernel/syscalls/process.cpp @@ -10,16 +10,18 @@ j6_status_t process_exit(int64_t status) { auto &s = scheduler::get(); - auto *p = s.current(); - log::debug(logs::syscall, "Process %d exiting with code %d", p->pid, status); + TCB *tcb = s.current(); + thread *th = tcb->thread_data; + log::debug(logs::syscall, "Thread %llx exiting with code %d", th->koid(), status); - p->exit(status); + th->exit(status); s.schedule(); log::error(logs::syscall, "returned to exit syscall"); return j6_err_unexpected; } +/* j6_status_t process_fork(pid_t *pid) { @@ -54,6 +56,7 @@ process_getpid(pid_t *pid) *pid = p->pid; return j6_status_ok; } +*/ j6_status_t process_log(const char *message) @@ -63,17 +66,22 @@ process_log(const char *message) } auto &s = scheduler::get(); - auto *p = s.current(); - log::info(logs::syscall, "Message[%d]: %s", p->pid, message); + TCB *tcb = s.current(); + thread *th = tcb->thread_data; + log::info(logs::syscall, "Message[%llx]: %s", th->koid(), message); return j6_status_ok; } +j6_status_t process_fork(uint32_t *pid) { *pid = 5; return process_log("CALLED FORK"); } +j6_status_t process_getpid(uint32_t *pid) { *pid = 0; return process_log("CALLED GETPID"); } + j6_status_t process_pause() { auto &s = scheduler::get(); - auto *p = s.current(); - p->wait_on_signal(-1ull); + TCB *tcb = s.current(); + thread *th = tcb->thread_data; + th->wait_on_signals(th, -1ull); s.schedule(); return j6_status_ok; } @@ -82,10 +90,11 @@ j6_status_t process_sleep(uint64_t til) { auto &s = scheduler::get(); - auto *p = s.current(); - log::debug(logs::syscall, "Process %d sleeping until %llu", p->pid, til); + TCB *tcb = s.current(); + thread *th = tcb->thread_data; + log::debug(logs::syscall, "Thread %llx sleeping until %llu", th->koid(), til); - p->wait_on_time(til); + th->wait_on_time(til); s.schedule(); return j6_status_ok; } diff --git a/src/libraries/kutil/include/kutil/vector.h b/src/libraries/kutil/include/kutil/vector.h index ce0eaf4..77a4a98 100644 --- a/src/libraries/kutil/include/kutil/vector.h +++ b/src/libraries/kutil/include/kutil/vector.h @@ -112,6 +112,28 @@ public: m_elements[m_size].~T(); } + /// Remove the first occurance of an item from the array, not + /// order-preserving. Does nothing if the item is not in the array. + void remove_swap(const T &item) + { + for (size_t i = 0; i < m_size; ++i) { + if (m_elements[i] == item) { + remove_swap_at(i); + break; + } + } + } + + /// Remove the item at the given index from the array, not + /// order-preserving. + void remove_swap_at(size_t i) + { + if (i >= count()) return; + if (i < m_size - 1) + m_elements[i] = m_elements[m_size - 1]; + remove(); + } + /// Remove an item from the end of the array and return it. T pop() {