Browse Source

sched wip

master
Mathieu Serandour 5 months ago
parent
commit
299b12d1db
  1. 32
      kernel/sched/process.c
  2. 15
      kernel/sched/process.h
  3. 350
      kernel/sched/sched.c
  4. 35
      kernel/sched/sched.h
  5. 38
      kernel/sched/thread.c
  6. 47
      kernel/sched/thread.h

32
kernel/sched/process.c

@ -40,6 +40,8 @@ void dup_fd(file_descriptor_t* fd, file_descriptor_t* new_fd) {
new_fd->dir = vfs_dir_dup(fd->dir);
new_fd->dir_boff = fd->dir_boff;
break;
case FD_NONE:
break;
default:
assert(0);
}
@ -86,6 +88,7 @@ int create_process(
// we have to have the userspace map ready
// before calling this
elf_program_t* program = elf_load(elffile, elffile_sz);
if(!program)
return 0;
@ -94,7 +97,7 @@ int create_process(
file_descriptor_t* fds = NULL;
fds = malloc(sizeof(file_descriptor_t*) * MAX_FDS);
fds = malloc(sizeof(file_descriptor_t) * MAX_FDS);
char* cwd;
@ -102,11 +105,11 @@ int create_process(
// inherit parent file handlers
ppid = pparent->pid;
for(unsigned i = 0; i < MAX_FDS; i++) {
dup_fd(pparent->fds + i, fds + i);
}
// inherit parent directory
cwd = strdup(pparent->cwd);
}
@ -116,7 +119,7 @@ int create_process(
fds[i].type = FD_NONE;
fds[i].file = NULL;
}
//memset(fds, 0, sizeof(file_descriptor_t*) * MAX_FDS);
//memset(fds, 0, sizeof(file_descriptor_t) * MAX_FDS);
// root directory
cwd = strdup("/");
@ -158,6 +161,7 @@ int create_process(
.n_threads = 1,
.threads = threads,
.page_dir_paddr = user_page_map,
.saved_page_dir_paddr = user_page_map,
.pid = pid,
.ppid = ppid,
.cwd = cwd,
@ -180,7 +184,9 @@ void free_process(process_t* process) {
assert(!process->n_threads);
for(unsigned i = 0; i < MAX_FDS; i++) {
close_fd(process->fds + i);
if(process->fds[i].type != FD_NONE) {
close_fd(&process->fds[i]);
}
}
free(process->fds);
@ -191,8 +197,6 @@ void free_process(process_t* process) {
free(process->threads);
elf_free(process->program);
free(process);
}
int replace_process(process_t* process, void* elffile, size_t elffile_sz) {
@ -203,7 +207,9 @@ int replace_process(process_t* process, void* elffile, size_t elffile_sz) {
free_user_page_map(process->page_dir_paddr);
// recreate page directory
process->saved_page_dir_paddr =
process->page_dir_paddr = alloc_user_page_map();
set_user_page_map(process->page_dir_paddr);
@ -282,20 +288,18 @@ int set_process_entry_arguments(process_t* process,
uint64_t user_argv = (rsp -= argv_sz);
uint64_t user_envp = (rsp -= envp_sz);
log_warn("user_argv=%lx, user_envp=%lx", user_argv, user_envp);
// frame begin
uint64_t* user_frame_begin = (uint64_t*)(rsp -= sizeof(uint64_t));
*user_frame_begin = 0;
process->threads[0].rsp = (uint64_t*)(rsp -= sizeof(gp_regs_t));
process->threads[0].rsp = (void*)(rsp -= sizeof(gp_regs_t));
// stack.base < rsp < user_envp < user_argv
if(process->threads[0].rsp <= process->threads[0].stack.base) {
if((void*)process->threads[0].rsp <= process->threads[0].stack.base) {
// not enough space
return -1;
}
@ -329,12 +333,16 @@ int set_process_entry_arguments(process_t* process,
process->threads[0].rsp->rsp = rsp;
process->threads[0].rsp->rbp = user_frame_begin;
process->threads[0].rsp->rip = process->program->entry;
process->threads[0].rsp->rbp = (uint64_t)user_frame_begin;
process->threads[0].rsp->rip = (uint64_t)process->program->entry;
process->threads[0].rsp->cs = USER_CS;
process->threads[0].rsp->ss = USER_DS;
process->threads[0].rsp->rflags = USER_RF;
// the process is ready to be run
process->threads[0].state = READY;
return 0;
}

15
kernel/sched/process.h

@ -48,9 +48,24 @@ typedef struct process {
uint64_t page_dir_paddr;
// might be different from
// page_dir_paddr if the
// kernel was doing stuf
// with another process
// when it got interrupted
uint64_t saved_page_dir_paddr;
size_t n_threads;
struct thread* threads;
// lock for the process and
// threads
// The lock does not have to be taken
// to access fields in the process
// in a kernel task associated with
// a subsequent thread that is running
spinlock_t lock;
elf_program_t* program;

350
kernel/sched/sched.c

@ -6,6 +6,16 @@
#include "../memory/vmap.h"
#include "../memory/heap.h"
#include "../memory/paging.h"
#include "../smp/smp.h"
// for get_rflags()
#include "../lib/registers.h"
// for the yield interrupt handler
#include "../int/irq.h"
#define XSTR(s) STR(s)
#define STR(x) #x
/**
* schedule algorithm:
@ -47,6 +57,14 @@ static size_t n_threads = 0;
static process_t* processes;
/**
* this lock is used to protect the process table
* and the ready queues
*
*/
fast_spinlock_t sched_lock = {0};
/**
* when invoking a syscall,
* syscall_stacks[lapic_id] should contain
@ -56,28 +74,65 @@ static process_t* processes;
void** syscall_stacks;
static void yield_irq_handler(struct driver* unused) {
// nothing to do,
// everything is done by the
// common interrupt handler
(void) unused;
}
void sched_init(void) {
// init syscall stacks
syscall_stacks = malloc(sizeof(void*) * get_smp_count());
// init the virtual yield IRQ
register_irq(YIELD_IRQ, yield_irq_handler, 0);
}
/**
* @brief alloc a process in the processes list
* and return a pointer on it
* and return a pointer on it.
* The caller should have the interrupts disabled
*
* the scheduler lock is held and not released
* the caller is responsible for releasing the lock
* after inserting the process in the list
*
* @return process_t*
*/
static process_t* add_process(void) {
// add it in the end
spinlock_acquire(&sched_lock);
unsigned id = n_processes++;
processes = realloc(processes, sizeof(process_t) * n_processes);
return processes + id;
}
static void remove_process(process_t* process) {
// first lock the sched lock
spinlock_acquire(&sched_lock);
// remove it from the list
unsigned id = (process - processes);
processes[id] = processes[--n_processes];
// unlock the sched lock
spinlock_release(&sched_lock);
log_warn("process %d removed", id);
}
#define N_QUEUES 4
// alloc pids in a circular way
@ -92,6 +147,11 @@ gp_regs_t* kernel_saved_rsp = NULL;
void __attribute__((noreturn)) _restore_context(struct gp_regs* rsp);
/**
* the process lock should be taken before
* calling this function
*
*/
static
thread_t* get_thread_by_tid(process_t* process, tid_t tid) {
@ -107,14 +167,34 @@ thread_t* get_thread_by_tid(process_t* process, tid_t tid) {
process_t* sched_get_process(pid_t pid) {
if(pid == KERNEL_PID)
return NULL;
// not found
process_t* process = NULL;
_cli();
// lock the process table
spinlock_acquire(&sched_lock);
// sequencial research
for(unsigned i = 0; i < n_processes; i++) {
if(processes[i].pid == pid)
return &processes[i];
process = &processes[i];
}
return NULL;
// lock the process to make sure it won't be freed
spinlock_acquire(&process->lock);
// unlock the process table
spinlock_release(&sched_lock);
return process;
}
@ -127,29 +207,124 @@ pid_t sched_create_process(pid_t ppid, const void* elffile, size_t elffile_sz) {
// NULL: kernel process
process_t* parent = NULL;
uint64_t rf = get_rflags();
_cli();
if(ppid != KERNEL_PID) {
parent = sched_get_process(ppid);
if(!parent) {
// no such ppid
// unlock process
set_rflags(rf);
return -1;
}
}
if(!create_process(&new, parent, elffile, elffile_sz))
int ret = create_process(&new, parent, elffile, elffile_sz);
// unlock process
if(parent)
spinlock_release(&parent->lock);
if(!ret) {
// error
set_rflags(rf);
return -1;
}
_cli();
// add it to the processes list
*add_process() = new;
spinlock_release(&sched_lock);
_sti();
set_rflags(rf);
return new.pid;
}
int sched_kill_process(pid_t pid, int status) {
// the entier operation is critical
_cli();
// process is aquired there
process_t* process = sched_get_process(pid);
if(!process) {
// no such process
_sti();
return -1;
}
int running = 0;
for(unsigned i = 0; i < process->n_threads; i++) {
thread_t* thread = &process->threads[i];
if(thread->state == RUNNING) {
// we can't kill a running process
// we must wait for it to finish
// we do a lazy kill
// @todo send an IPI to the thread cpu
// to force it to stop earlier
thread->should_exit = 1;
running = 1;
}
else if(thread->state == READY) {
// remove it from the ready queue
// and free the thread
thread_terminate(thread, status);
}
}
if(!running) {
// no running thread, we can kill the process
// remove the process from the list
// and free it
// avoid deadlocks:
// we must release the process lock
// before taking the sched lock
// as a process could be concurrently
// try to take the sched lock
// and then try to take the process lock
spinlock_release(&process->lock);
remove_process(process);
free_process(process);
// the process has been successfully removed
// from the list. Thus, noone can fetch it.
// though it is still be taken
spinlock_acquire(&sched_lock);
}
else {
// we can't kill the process
// we must wait for it to finish
spinlock_release(&process->lock);
}
_sti();
// success
return 0;
}
void sched_save(gp_regs_t* rsp) {
@ -162,58 +337,191 @@ void sched_save(gp_regs_t* rsp) {
else {
kernel_saved_rsp = NULL;
// p's lock is taken there
process_t* p = sched_get_process(current_pid);
assert(p);
thread_t* t = get_thread_by_tid(p, current_tid);
assert(t);
t->state = READY;
t->rsp = rsp;
static int i = 0;
// save page table
p->saved_page_dir_paddr = get_user_page_map();
// release process lock
spinlock_release(&p->lock);
/*
log_warn("SAVED cs=%x, ss=%x, rsp=%x, rip=%x", t->rsp->cs, t->rsp->ss, t->rsp->rsp, t->rsp->rip);
stacktrace_print();
*/
}
}
void sched_yield(void) {
// invoke a custom interrupt
// that does nothing but a
// rescheduling
_cli();
// takes p's lock
process_t* p = sched_get_process(current_pid);
assert(p); // no such process
thread_t* t = get_thread_by_tid(p, current_tid);
assert(t); // no such thread
t->state = READY;
// release process lock
spinlock_release(&p->lock);
asm volatile("int $" XSTR(YIELD_IRQ));
__builtin_unreachable();
}
static process_t* choose_next(void) {
// lock the process table
spinlock_acquire(&sched_lock);
// sequencial research
for(int i = n_processes-1; i >= 0; i--) {
process_t* process = &processes[i];
if(process->n_threads > 0) {
// lock the process to make sure it won't be freed
spinlock_acquire(&process->lock);
// unlock the process table
spinlock_release(&sched_lock);
// return it
return process;
}
}
// unlock the process table
spinlock_release(&sched_lock);
// no process found
return NULL;
}
void schedule(void) {
if(currenly_in_irq && current_pid == KERNEL_PID) {
// do not schedule if the interrupted task
// was the kernel
assert(kernel_saved_rsp);
currenly_in_irq = 0;
_restore_context(kernel_saved_rsp);
}
// do not schedule if the interrupted task
// was the kernel
// only execute the process with pid 1 for now.
// disable interrupts
_cli();
process_t* p = sched_get_process(1);
thread_t* t = &p->threads[0];
// next process
process_t* p;
assert(p->n_threads == 1);
assert(p->pid == 1);
assert(t->tid == 1);
// next thread
thread_t* t;
// select one process
while(1) {
// lazy kill: we check if the process
// should be scheduled.
// process is locked there
p = choose_next();
t = &p->threads[0];
assert(p->n_threads == 1);
//assert(p->pid == 1);
assert(t->tid == 1);
if(t->should_exit && t->state == READY) {
// if t->state != READY,
// the process might be executing already
// in kernel space, on the core we are executing
// right now. Thus, we can't kill it, as
// our kernel stack resides in its memory.
// lazy thread kill
// we must kill the thread
// and free it
thread_terminate(t, t->exit_status);
if(--p->n_threads == 0) {
// no more threads, we can kill the process
// remove the process from the list
// and free it
free_process(p);
}
else {
// we can't kill the process
// we must wait for it to finish
spinlock_release(&p->lock);
}
}
else break;
}
// the process is selected for execution
// map the user space
set_user_page_map(p->page_dir_paddr);
set_user_page_map(p->saved_page_dir_paddr);
_cli();
t->state = RUNNING;
current_pid = p->pid;
current_tid = t->tid;
// unlock the process
// there is no risk for the process to be freed
// because the current thread is marked RUNNING
spinlock_release(&p->lock);
syscall_stacks[get_lapic_id()] = (void*) t->kernel_stack.base + t->kernel_stack.size;
_restore_context(t->rsp);
}
void schedule_irq_handler(void) {
schedule();
panic("unreachable");
}
pid_t sched_current_pid(void) {
return current_pid;
}
tid_t sched_current_tid(void) {
return current_tid;
}
process_t* sched_current_process(void) {
if(!current_pid) {
// current process: kernel

35
kernel/sched/sched.h

@ -5,10 +5,11 @@
#define KERNEL_PID ((pid_t)0)
#define YIELD_IRQ 47
// the caller should disable interrupts
void sched_save(gp_regs_t* context);
void sched_init(void);
@ -33,6 +34,11 @@ pid_t alloc_pid(void);
pid_t sched_create_process(pid_t ppid, const void* elffile, size_t elffile_sz);
// kill and remove the process with pid pid
// and return its exit status
int sched_kill_process(pid_t pid, int status);
void sched_register_process(process_t* process);
@ -40,20 +46,41 @@ void sched_register_process(process_t* process);
* @brief return the current process
* or NULL if the current task executing
* is the kernel
*
* the returned process is locked. It should
* be unlocked by the caller, using
* spinlock_release(&process->lock)
*/
process_t* sched_current_process(void);
// return NULL if no such pid
// the process belongs to the scheduler,
// the caller cannot free it
/**
* @brief lock the process with
* given pid and return it
*
* this function disables interrupts
*
* return NULL if no such pid
* the process belongs to the scheduler,
* the caller cannot free it
*
*/
process_t* sched_get_process(pid_t pid);
pid_t sched_current_pid(void);
tid_t sched_current_tid(void);
/**
* yield the scheduler from the current thread
* this function invokes an interrupt
*
* the function panics if the current thread
* does not exist
*/
void __attribute__((noreturn)) sched_yield(void);
// return the current task's kernel stack
// this function is to call in the syscall
// routine to load the right stack

38
kernel/sched/thread.c

@ -3,6 +3,10 @@
#include "../memory/vmap.h"
#include "../memory/heap.h"
// for freeing a thread stack
#include "../memory/paging.h"
#include "../memory/physical_allocator.h"
int create_thread(
thread_t* thread,
pid_t pid,
@ -17,9 +21,10 @@ int create_thread(
.size = stack_size,
},
.tid = tid,
.state = BLOCKED,
.lock = 0,
};
// set the stack frame
*(uint64_t*)stack_base = 0;
*((uint64_t*)stack_base + 1) = 0;
@ -39,12 +44,35 @@ int create_thread(
.size = THREAD_KERNEL_STACK_SIZE,
};
thread->type = READY;
return 0;
}
void free_thread(thread_t* thread) {
void thread_add_exit_hook(thread_t* thread, exit_hook_fun_t hook) {
thread->exit_hooks = realloc(
thread->exit_hooks,
sizeof(exit_hook_fun_t) * (thread->n_exit_hooks + 1)
);
thread->exit_hooks[thread->n_exit_hooks] = hook;
thread->n_exit_hooks++;
}
void thread_terminate(thread_t* thread, int status) {
// call exit hooks
for (size_t i = 0; i < thread->n_exit_hooks; i++)
thread->exit_hooks[i](thread, status);
free(thread->kernel_stack.base);
}
if(thread->exit_hooks)
free(thread->exit_hooks);
// @todo free the thread stack
//unmap_pages(thread->stack.base, thread->stack.size << 12);
}

47
kernel/sched/thread.h

@ -4,6 +4,7 @@
#include <stddef.h>
#include "../lib/assert.h"
#include "../sync/spinlock.h"
#include "process.h"
@ -29,8 +30,8 @@ typedef struct gp_regs {
uint64_t rbx;
uint64_t rdx;
uint64_t rcx;
uint64_t rbp;
uint64_t rax;
uint64_t rbp;
uint64_t rip;
@ -58,6 +59,9 @@ enum thread_state {
} tstate_t;
typedef void (*exit_hook_fun_t)(struct thread* thread, int status);
typedef
struct thread {
pid_t pid;
@ -69,16 +73,53 @@ struct thread {
stack_t stack;
gp_regs_t* rsp;
tstate_t type;
tstate_t state;
// if this field is set, then the thread
// is to be terminated when it is next
// scheduled
int should_exit;
// if should_exit is set, this field
// contains the exit status
int exit_status;
// lapic id of the cpu this
// thread is running on
// this is used to determine
// which cpu to send an interrupt
// to when the thread is to terminate
// this value is only valid when
// the thread is running
unsigned running_cpu_id;
exit_hook_fun_t* exit_hooks;
size_t n_exit_hooks;
// lock for the thread
spinlock_t lock;
} thread_t;
#define THREAD_KERNEL_STACK_SIZE (1024 * 16)
// 0 if the thread cannot be created
// the created thread is blocked
int create_thread(thread_t* thread, pid_t pid, void* stack_base, size_t stacs_size, tid_t);
void free_thread(thread_t* thread);
// add a hook to be called when the thread exits
void thread_add_exit_hook(thread_t* thread, exit_hook_fun_t hook);
// the right process should be
// locked and mapped before calling
// this function
//
// terminate a thread:
// - call all exit hooks
// - free the thread's data
// including its stack
// and kernel stack
void thread_terminate(thread_t* thread, int status);

Loading…
Cancel
Save