[kernel] Split loading from scheduler

In preparation for moving things to the init process, move process
loading out of the scheduler. memory_bootstrap now has a
load_simple_process function for mapping an args::program into memory,
and the stack setup has been simplified (though all the initv values are
still being added by the kernel - this needs rework) and normalized to
use the thread::add_thunk_user code path.
This commit is contained in:
Justin C. Miller
2021-01-28 01:30:17 -08:00
parent 35d8d2ab2d
commit 3aa909b917
12 changed files with 163 additions and 179 deletions

View File

@@ -1,20 +0,0 @@
%include "push_all.inc"
extern load_process_image
global preloaded_process_init
preloaded_process_init:
; create_process already pushed the arguments for load_process_image and
; the following iretq onto the stack for us
pop rdi ; a pointer to the program descriptor
call load_process_image
; user rsp is now in rax, put it in the right place for iret
mov [rsp + 0x18], rax
; the entrypoint should already be on the stack
swapgs
iretq

View File

@@ -35,12 +35,13 @@ extern "C" {
extern void __kernel_assert(const char *, unsigned, const char *);
using namespace kernel;
/// Bootstrap the memory managers.
void setup_pat();
void memory_initialize_pre_ctors(kernel::args::header &kargs);
void memory_initialize_post_ctors(kernel::args::header &kargs);
using namespace kernel;
void memory_initialize_pre_ctors(args::header &kargs);
void memory_initialize_post_ctors(args::header &kargs);
process * load_simple_process(args::program &program);
/// TODO: not this. this is awful.
args::framebuffer *fb = nullptr;
@@ -150,13 +151,8 @@ kernel_main(args::header *header)
scheduler *sched = new scheduler(devices.get_lapic());
// Skip program 0, which is the kernel itself
for (size_t i = 1; i < header->num_programs; ++i) {
args::program *prog = memory::to_virtual(&header->programs[i]);
thread *th = sched->load_process(*prog);
if (i == 2) {
//th->set_state(thread::state::constant);
}
}
for (unsigned i = 1; i < header->num_programs; ++i)
load_simple_process(header->programs[i]);
if (!has_video)
sched->create_kernel_task(logger_task, scheduler::max_priority/2, true);

View File

@@ -1,16 +1,20 @@
#include <utility>
#include "kernel_args.h"
#include "j6/init.h"
#include "kutil/assert.h"
#include "kutil/heap_allocator.h"
#include "kutil/no_construct.h"
#include "device_manager.h"
#include "frame_allocator.h"
#include "io.h"
#include "log.h"
#include "msr.h"
#include "objects/process.h"
#include "objects/thread.h"
#include "objects/system.h"
#include "objects/vm_area.h"
#include "vm_space.h"
@@ -19,6 +23,8 @@ using memory::kernel_max_heap;
using namespace kernel;
extern "C" void initialize_main_thread();
extern "C" uintptr_t initialize_main_user_stack();
// These objects are initialized _before_ global constructors are called,
// so we don't want them to have global constructors at all, lest they
@@ -178,3 +184,99 @@ setup_pat()
}
process *
load_simple_process(args::program &program)
{
using kernel::args::section_flags;
process *p = new process;
vm_space &space = p->space();
for (const auto &sect : program.sections) {
vm_flags flags =
(bitfield_has(sect.type, section_flags::execute) ? vm_flags::exec : vm_flags::none) |
(bitfield_has(sect.type, section_flags::write) ? vm_flags::write : vm_flags::none);
vm_area *vma = new vm_area_fixed(sect.size, flags);
space.add(sect.virt_addr, vma);
vma->commit(sect.phys_addr, 0, memory::page_count(sect.size));
}
uint64_t iopl = (3ull << 12);
uintptr_t trampoline = reinterpret_cast<uintptr_t>(initialize_main_thread);
thread *main = p->create_thread();
main->add_thunk_user(program.entrypoint, trampoline, iopl);
main->set_state(thread::state::ready);
return p;
}
template <typename T>
inline T * push(uintptr_t &rsp, size_t size = sizeof(T)) {
rsp -= size;
T *p = reinterpret_cast<T*>(rsp);
rsp &= ~(sizeof(uint64_t)-1); // Align the stack
return p;
}
uintptr_t
initialize_main_user_stack()
{
process &proc = process::current();
thread &th = thread::current();
TCB *tcb = th.tcb();
const char message[] = "Hello from the kernel!";
char *message_arg = push<char>(tcb->rsp3, sizeof(message));
kutil::memcpy(message_arg, message, sizeof(message));
extern args::framebuffer *fb;
j6_init_framebuffer *fb_desc = push<j6_init_framebuffer>(tcb->rsp3);
fb_desc->addr = fb ? reinterpret_cast<void*>(0x100000000) : nullptr;
fb_desc->size = fb ? fb->size : 0;
fb_desc->vertical = fb ? fb->vertical : 0;
fb_desc->horizontal = fb ? fb->horizontal : 0;
fb_desc->scanline = fb ? fb->scanline : 0;
fb_desc->flags = 0;
if (fb && fb->type == kernel::args::fb_type::bgr8)
fb_desc->flags |= 1;
unsigned n = 0;
j6_init_value *initv = push<j6_init_value>(tcb->rsp3);
initv->type = j6_init_handle_other;
initv->handle.type = j6_object_type_system;
initv->handle.handle = proc.add_handle(&system::get());
++n;
initv = push<j6_init_value>(tcb->rsp3);
initv->type = j6_init_handle_self;
initv->handle.type = j6_object_type_process;
initv->handle.handle = proc.self_handle();
++n;
initv = push<j6_init_value>(tcb->rsp3);
initv->type = j6_init_handle_self;
initv->handle.type = j6_object_type_thread;
initv->handle.handle = th.self_handle();
++n;
initv = push<j6_init_value>(tcb->rsp3);
initv->type = j6_init_desc_framebuffer;
initv->data = fb_desc;
++n;
uint64_t *initc = push<uint64_t>(tcb->rsp3);
*initc = n;
char **argv0 = push<char*>(tcb->rsp3);
*argv0 = message_arg;
uint64_t *argc = push<uint64_t>(tcb->rsp3);
*argc = 1;
th.clear_state(thread::state::loading);
return tcb->rsp3;
}

View File

@@ -106,7 +106,9 @@ process::create_thread(uint8_t priority, bool user)
vm_flags::zero|vm_flags::write);
m_space.add(stack_top - stack_size, vma);
th->tcb()->rsp3 = stack_top;
// Space for null frame - because the page gets zeroed on
// allocation, just pointing rsp here does the trick
th->tcb()->rsp3 = stack_top - 2 * sizeof(uint64_t);
}
m_threads.append(th);

View File

@@ -16,7 +16,7 @@ public:
constexpr static uintptr_t stacks_top = 0x0000800000000000;
/// Size of userspace thread stacks
constexpr static size_t stack_size = 0x4000;
constexpr static size_t stack_size = 0x4000000; // 64MiB
/// Value that represents default priority
constexpr static uint8_t default_priority = 0xff;

View File

@@ -149,6 +149,10 @@ thread::exit(int32_t code)
void
thread::add_thunk_kernel(uintptr_t rip)
{
// This adds just enough values to the top of the
// kernel stack to come out of task_switch correctly
// and start executing at rip (still in kernel mode)
m_tcb.rsp -= sizeof(uintptr_t) * 7;
uintptr_t *stack = reinterpret_cast<uintptr_t*>(m_tcb.rsp);
@@ -162,15 +166,24 @@ thread::add_thunk_kernel(uintptr_t rip)
}
void
thread::add_thunk_user(uintptr_t rip)
thread::add_thunk_user(uintptr_t rip3, uintptr_t rip0, uint64_t flags)
{
// This sets up the stack to:
// a) come out of task_switch and return to rip0 (default is the
// kernel/user trampoline) (via add_thunk_kernel) - if this is
// changed, it needs to end up at the trampoline with the stack
// as it was
// b) come out of the kernel/user trampoline and start executing
// in user mode at rip
m_tcb.rsp -= sizeof(uintptr_t) * 8;
uintptr_t *stack = reinterpret_cast<uintptr_t*>(m_tcb.rsp);
flags |= 0x200;
stack[7] = rip; // return rip in rcx
stack[7] = rip3; // return rip in rcx
stack[6] = m_tcb.rsp3; // rbp
stack[5] = 0xbbbbbbbb; // rbx
stack[4] = 0x00000200; // r11 sets RFLAGS
stack[4] = flags; // r11 sets RFLAGS
stack[3] = 0x12121212; // r12
stack[2] = 0x13131313; // r13
stack[1] = 0x14141414; // r14
@@ -178,7 +191,7 @@ thread::add_thunk_user(uintptr_t rip)
static const uintptr_t trampoline =
reinterpret_cast<uintptr_t>(kernel_to_user_trampoline);
add_thunk_kernel(trampoline);
add_thunk_kernel(rip0 ? rip0 : trampoline);
}
void

View File

@@ -137,9 +137,12 @@ public:
/// \arg rip The address to return to, must be kernel space
void add_thunk_kernel(uintptr_t rip);
/// Add a stack header that returns to the given address in user space.
/// \arg rip The address to return to, must be user space
void add_thunk_user(uintptr_t rip);
/// Add a stack header that returns to the given address in user space
/// via a function in kernel space.
/// \arg rip3 The user space address to return to
/// \arg rip0 The kernel function to pass through, optional
/// \arg flags Extra RFLAGS values to set, optional
void add_thunk_user(uintptr_t rip3, uintptr_t rip0 = 0, uint64_t flags = 0);
/// Get the handle representing this thread to its process
j6_handle_t self_handle() const { return m_self_handle; }

View File

@@ -7,6 +7,7 @@
#include "console.h"
#include "cpu.h"
#include "debug.h"
#include "device_manager.h"
#include "gdt.h"
#include "interrupts.h"
#include "io.h"
@@ -32,11 +33,6 @@ extern kernel::args::framebuffer *fb;
const uint64_t rflags_noint = 0x002;
const uint64_t rflags_int = 0x202;
extern "C" {
void preloaded_process_init();
uintptr_t load_process_image(const kernel::args::program*);
};
extern uint64_t idle_stack_end;
scheduler::scheduler(lapic *apic) :
@@ -75,98 +71,13 @@ inline T * push(uintptr_t &rsp, size_t size = sizeof(T)) {
return p;
}
uintptr_t
load_process_image(const kernel::args::program *program)
{
using memory::page_align_down;
using memory::page_align_up;
using kernel::args::section_flags;
// We're now in the process space for this process, allocate memory for the
// process code and load it
process &proc = process::current();
thread &th = thread::current();
TCB *tcb = th.tcb();
vm_space &space = proc.space();
for (const auto &sect : program->sections) {
vm_flags flags =
(bitfield_has(sect.type, section_flags::execute) ? vm_flags::exec : vm_flags::none) |
(bitfield_has(sect.type, section_flags::write) ? vm_flags::write : vm_flags::none);
vm_area *vma = new vm_area_open(sect.size, space, flags);
space.add(sect.virt_addr, vma);
vma->commit(sect.phys_addr, 0, memory::page_count(sect.size));
}
// double zero stack sentinel
*push<uint64_t>(tcb->rsp3) = 0;
*push<uint64_t>(tcb->rsp3) = 0;
const char message[] = "Hello from the kernel!";
char *message_arg = push<char>(tcb->rsp3, sizeof(message));
kutil::memcpy(message_arg, message, sizeof(message));
j6_init_framebuffer *fb_desc = push<j6_init_framebuffer>(tcb->rsp3);
fb_desc->addr = fb ? reinterpret_cast<void*>(0x100000000) : nullptr;
fb_desc->size = fb ? fb->size : 0;
fb_desc->vertical = fb ? fb->vertical : 0;
fb_desc->horizontal = fb ? fb->horizontal : 0;
fb_desc->scanline = fb ? fb->scanline : 0;
fb_desc->flags = 0;
if (fb && fb->type == kernel::args::fb_type::bgr8)
fb_desc->flags |= 1;
j6_init_value *initv = push<j6_init_value>(tcb->rsp3);
initv->type = j6_init_handle_other;
initv->handle.type = j6_object_type_system;
initv->handle.handle = proc.add_handle(&system::get());
initv = push<j6_init_value>(tcb->rsp3);
initv->type = j6_init_handle_self;
initv->handle.type = j6_object_type_process;
initv->handle.handle = proc.self_handle();
initv = push<j6_init_value>(tcb->rsp3);
initv->type = j6_init_handle_self;
initv->handle.type = j6_object_type_thread;
initv->handle.handle = th.self_handle();
initv = push<j6_init_value>(tcb->rsp3);
initv->type = j6_init_desc_framebuffer;
initv->data = fb_desc;
uint64_t *initc = push<uint64_t>(tcb->rsp3);
*initc = 4;
char **argv0 = push<char*>(tcb->rsp3);
*argv0 = message_arg;
uint64_t *argc = push<uint64_t>(tcb->rsp3);
*argc = 1;
// Crazypants framebuffer part
if (fb) {
vm_area *vma = new vm_area_open(fb->size, space,
vm_flags::write|vm_flags::mmio|vm_flags::write_combine);
space.add(0x100000000, vma);
vma->commit(fb->phys_addr, 0, memory::page_count(fb->size));
}
th.clear_state(thread::state::loading);
return tcb->rsp3;
}
thread *
scheduler::create_process(bool user)
{
process *p = new process;
thread *th = p->create_thread(default_priority, user);
auto *tcb = th->tcb();
tcb->time_left = quantum(default_priority);
TCB *tcb = th->tcb();
log::debug(logs::task, "Creating thread %llx, priority %d, time slice %d",
th->koid(), tcb->priority, tcb->time_left);
@@ -174,45 +85,6 @@ scheduler::create_process(bool user)
return th;
}
thread *
scheduler::load_process(kernel::args::program &program)
{
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
uint16_t kss = (2 << 3) | 0; // Kernel SS is GDT entry 2, ring 0
uint16_t ss = (4 << 3) | 3; // User SS is GDT entry 4, ring 3
thread* th = create_process(true);
auto *tcb = th->tcb();
// Create an initial kernel stack space
uintptr_t *stack = reinterpret_cast<uintptr_t *>(tcb->rsp0) - 6;
// Pass args to preloaded_process_init on the stack
stack[0] = reinterpret_cast<uintptr_t>(&program);
tcb->rsp = reinterpret_cast<uintptr_t>(stack);
th->add_thunk_kernel(reinterpret_cast<uintptr_t>(preloaded_process_init));
// Arguments for iret - rip will be pushed on before these
stack[1] = reinterpret_cast<uintptr_t>(program.entrypoint);
stack[2] = cs;
stack[3] = rflags_int | (3 << 12);
stack[4] = process::stacks_top;
stack[5] = ss;
tcb->rsp3 = process::stacks_top;
log::debug(logs::task, "Loading thread %llx pri %d", 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);
return th;
}
void
scheduler::create_kernel_task(void (*task)(), uint8_t priority, bool constant)
{
@@ -248,6 +120,14 @@ scheduler::start()
m_apic->reset_timer(10);
}
void
scheduler::add_thread(TCB *t)
{
m_blocked.push_back(static_cast<tcb_node*>(t));
t->time_left = quantum(t->priority);
}
void scheduler::prune(uint64_t now)
{
// Find processes that are ready or have exited and

View File

@@ -60,7 +60,7 @@ public:
bool constant = false);
/// Get the quantum for a given priority.
uint32_t quantum(int priority);
static uint32_t quantum(int priority);
/// Start the scheduler working. This may involve starting
/// timer interrupts or other preemption methods.
@@ -73,7 +73,9 @@ public:
/// \returns A pointer to the current thread's TCB
inline TCB * current() { return m_current; }
inline void add_thread(TCB *t) { m_blocked.push_back(static_cast<tcb_node*>(t)); }
/// Start scheduling a new thread.
/// \arg t The new thread's TCB
void add_thread(TCB *t);
/// Get a reference to the system scheduler
/// \returns A reference to the global system scheduler

View File

@@ -12,7 +12,6 @@ extern syscall_registry
extern syscall_invalid
global syscall_handler_prelude
global syscall_handler_prelude.return
syscall_handler_prelude:
swapgs
mov [gs:CPU_DATA.rsp3], rsp
@@ -55,7 +54,8 @@ syscall_handler_prelude:
inc qword [rel __counter_syscall_sysret]
.return:
global kernel_to_user_trampoline
kernel_to_user_trampoline:
pop r15
pop r14
pop r13

View File

@@ -55,8 +55,14 @@ task_switch:
ret
extern syscall_handler_prelude.return
global kernel_to_user_trampoline
kernel_to_user_trampoline:
jmp syscall_handler_prelude.return
extern initialize_main_user_stack
extern kernel_to_user_trampoline
global initialize_main_thread
initialize_main_thread:
call initialize_main_user_stack
; user rsp is now in rax, put it in the right place for sysret
mov [rsp + 0x30], rax
; the entrypoint should already be on the stack
jmp kernel_to_user_trampoline