CeDOS - kernel/sched/sched.c

kernel/sched/sched.c (view raw)

#include "sched/sched.h"
#include "sched/process.h"
#include "sched/sched_strats.h"

#include "mm/paging.h"
#include "memory.h"

#include "drivers/console.h"
#include "drivers/speaker.h"

#include "core.h"
#include "interrupts.h"
#include "pit.h"
#include "pic.h"
#include "elf.h"
#include "file.h"
#include "alarm.h"

#include "assembly.h"
#include "assert.h"

#define KERNEL_PRIVATE_STACK (void*)(0xC0600000)
#define USER_STACK (void*)(0xC0000000)

#define PROCESS_STD_EFLAGS (0x00000200)

#ifdef DEBUG
#define PRINT_DBG(...) printk("[" __FILE__ "] " __VA_ARGS__)
#else
#define PRINT_DBG(...) {}
#endif

PROCESS* get_slot(void) {
    PROCESS *new_process = (PROCESS*)malloc(sizeof(PROCESS));
    memset(new_process, 0, sizeof(PROCESS));
    return new_process;
}

PROCESS_ID current_pid;

PROCESS_ID get_current_process(void) {
    return current_pid;
}

int sched_dispatcher(void);


void entry_idle(char *args) {
    while (1) { 
        hlt();
    }
}

/*!
 * Spawn a new process and returns its process ID.
 */
PROCESS_ID sched_spawn(const char *name, char *args, int flags) {
    crit_enter();

    PRINT_DBG("process name: %s\n", name);
    PRINT_DBG("process args: %s\n", args);
    PRINT_DBG("process flags: %i\n", flags);

    if (name != NULL) {
        int fd = file_open(name, 0);
        if (fd == -1) { return -1; }
    }

    PHYS_ADDR page_dir = create_empty_page_dir();

    // set process context
    PROCESS *p = get_slot();
    p->page_dir = page_dir;
    p->ebp = USER_STACK;
    p->esp = USER_STACK - sizeof(SCHED_FRAME);
    p->eflags = PROCESS_STD_EFLAGS;
    p->entry = (PROCESS_MAIN*)(0xDEADBEEF);
    
    if (name == NULL) {
        p->eip = entry_idle;
    } else {
        p->eip = sched_dispatcher;
    }

    if (flags != 0) {
        p->stdin = (int)(flags & 0xFF);
        p->stdout = (int)(flags >> 8);
    } else {
        p->stdin = 0;
        p->stdout = 1;
    }
    
    // TODO: implement with malloc
    strcpy(p->name_buf, name);

    if (args == 0) {
        p->args_buf[0] = 0;
    } else {
        strcpy(p->args_buf, args);
    }

    p->name = (const char*)&(p->name_buf);
    p->args = (const char*)&(p->args_buf);

    PROCESS_ID pid = add_process(p, current_pid);
    p->id = pid;

    // setup stack
    static SCHED_FRAME frame;
    frame.eax = frame.ebx = frame.ecx = frame.edx = 0;
    frame.esi = frame.edi = 0;
    frame.ebp = (uint32_t)(p->ebp);
    frame.esp = (uint32_t)(p->esp);
    frame.eflags = p->eflags;
    frame.cs = 0x18;
    frame.eip = (uint32_t)(p->eip);

    // load stack
    copy_to_pdir(&frame, sizeof(frame), p->page_dir, p->esp);

    // save stack checksum
    stack_compute_checksum(&(p->checksum), &frame, &(&frame)[1]);

    /* TODO: check if code exists at entry point */

    // start the process
    p->state = PSTATE_READY;
    p->starvation = 0;

    crit_exit();

    return pid;
}


void sched_interrupt_c(SCHED_FRAME * volatile frame, uint32_t volatile ebp) {
    PROCESS* current = get_process(current_pid);

    alarm_tick();

    if (current_pid != 0) {
        current->esp = (VIRT_ADDR)(frame);
        current->ebp = (VIRT_ADDR)(ebp);
        current->eip = (VIRT_ADDR)frame->eip;
        current->eflags = frame->eflags;

        // save stack checksum
        stack_compute_checksum(&(current->checksum), current->esp, current->ebp);
    }

    PRINT_DBG("esp: %p\n", current->esp);
    PRINT_DBG("ebp: %p\n", current->ebp);
    PRINT_DBG("eip: %p\n", current->eip);
    PRINT_DBG("eflags: %p\n", current->eflags);

    // select next process
    PRINT_DBG("exiting %i, ", current_pid);
    current_pid = next_schedule(current_pid);
    PRINT_DBG("entering %i\n", current_pid);

    // prepare to return to process
    PROCESS* next = get_process(current_pid);
    switch_page_dir(next->page_dir);

    PRINT_DBG("esp: %p\n", next->esp);
    PRINT_DBG("ebp: %p\n", next->ebp);
    PRINT_DBG("eip: %p\n", next->eip);
    PRINT_DBG("eflags: %p\n", next->eflags);

    STACK_CHECKSUM checksum;
    stack_compute_checksum(&(checksum), next->esp, next->ebp);

    // check stack
    if (stack_compare_checksum(&(next->checksum), &(checksum))) {
        printk("STACK DAMAGED: PROCESS %i (%s), ESP %X, EBP %X\n", current_pid, get_process(current_pid)->name, next->esp, next->ebp);
        memdump((void*)(next->esp), (void*)((uint32_t)(next->ebp) - (uint32_t)(next->esp)));
        kpanic("CRITICAL STACK DAMAGE");
    }

    // prepare stack
    frame = (volatile SCHED_FRAME*)(next->esp);
    ebp = next->ebp;

    if (current_pid == 0) {
        frame->cs = 0x18;
        frame->eip = next->eip;
        frame->eflags = (uint32_t)(next->eflags);
        frame->esp = (uint32_t)(next->esp);
        frame->ebp = (uint32_t)(next->ebp);
    }

    PRINT_DBG("esp: %p, ebp: %p, eip: %p, eflags: %p\n", frame->esp, frame->ebp, frame->eip, frame->eflags);


    // reset the timer
    pit_setup_channel(PIT_CHANNEL_0, PIT_MODE_0, SCHED_INTERVAL);
    
    pic1_eoi();
}

extern void* sched_interrupt;

int sched_init(void) {
    // install scheduler interrupt
    install_interrupt(PIC1_IRQ(0x00), &sched_interrupt, 0x18, INT_GATE);

    current_pid = 0;

    // create idle process
    PROCESS_ID idle_pid = sched_spawn(NULL, NULL, 0);
    assert(idle_pid == 0);

    PROCESS *idle = get_process(idle_pid);
    assert(idle != NULL);

    idle->state = PSTATE_READY;

    return 1;
}

void sched_yield(void) {
    crit_enter();
    PROCESS *current = get_process(current_pid);

    uint32_t csc = crit_stash();
    INT(0x20);
    sti();
    crit_restore(csc);

    crit_exit();
}

void sched_sleep(int ticks) {
    // block the process. unblocking is done by the alarm.
    PROCESS *process = get_process(current_pid);
    process->state = PSTATE_BLOCKED;

    // create a wakeup-alarm to unblock the process.
    alarm_add(ticks, current_pid, ALARM_WAKEUP);

    sched_yield();

    // if this happens, it means the scheduler chose
    // a blocked process as next process, which should
    // never happen.
    if (process->state == PSTATE_BLOCKED) {
        kpanic("ERROR!");
    }
}

void sched_unblock(int pid) {
    PROCESS *process = get_process(pid);
    
    // only unblock previously blocked processes
    if (process->state == PSTATE_BLOCKED) {
        process->state = PSTATE_READY;
    }
}

int sched_kill(PROCESS_ID pid) {
    int success = 1;
    crit_enter();
    
    PROCESS *process = get_process(pid);
    if (process != NULL) {
        // kill all children
        for (PROCESS *child = process->child; child != NULL; child = child->next_sibling) {
            sched_kill(child->id);
        }

        process->state = PSTATE_TERMINATED;
    } else {
        success = 0;
    }

    if (pid == current_pid) {
        // current process has terminated
        sched_yield();
    }


    crit_exit();
    return success;
}

void sched_wait(PROCESS_ID pid) {
    assert(pid != current_pid);

    while (1) {
        PROCESS *p = get_process(pid);
        if (p == NULL || p->state == PSTATE_TERMINATED) { break; }

        sched_yield();    
    }
}

int sched_start(void) {
    current_pid = 0;

    // perform the first timer interrupt manually
    pic_unmask_interrupt(0);
    INT(0x20);

    return 0;
}

int sched_stop(void) {
    // send signals to all processes and allow them to wrap up

    // kill all processes

    // disable interrupts
    pic_mask_interrupt(0);

    return 0;
}

int sched_dispatcher(void) {
    PRINT_DBG("Dispatching process %i...\n", current_pid);

    PROCESS* this = get_process(current_pid);


    // enter the actual program
    elf_exec(this->name, this->args);

    PRINT_DBG("Process %i terminated.\n", current_pid);

    sched_kill(current_pid);

    // just for absolute safety
    kpanic("Executing a terminated process!!\n");

    return 0;
}