#include #include #include #include #include #include #include #include #include #include #include #define THREAD_QUANTUM 10000 // queue 0: interrupt threads // queue 1: kernel threads // queue 2: user threads #define QUEUE_COUNT 3 sched_queue queues[QUEUE_COUNT]; // XXX allocate these in a better way static int pidlock; static pid_t nextpid = 1; static proc_t* init; proc_t* sched_getinit(){ return init; } static int getnextpid(){ spinlock_acquire(&pidlock); int pid = nextpid++; spinlock_release(&pidlock); return pid; } static proc_t* allocproc(size_t threadcount){ proc_t* proc = alloc(sizeof(proc_t)); if(!proc) return NULL; proc->threads = alloc(sizeof(thread_t*) * threadcount); if(!proc->threads){ free(proc); return NULL; } if(fd_tableinit(&proc->fdtable)){ free(proc->threads); free(proc); return NULL; } proc->pid = getnextpid(); return proc; } static thread_t* allocthread(proc_t* proc, state_t state, pid_t tid, size_t kstacksize){ thread_t* thread = alloc(sizeof(thread_t)); if(!thread) return NULL; thread->regs = alloc(sizeof(arch_regs)); if(!thread->regs){ free(thread); return NULL; } thread->kernelstackbase = alloc(kstacksize); if(!thread->kernelstackbase){ free(thread->regs); free(thread); return NULL; } thread->ctx = vmm_newcontext(); if(!thread->ctx){ free(thread->kernelstackbase); free(thread->regs); free(thread); return NULL; } thread->proc = proc; thread->state = state; thread->tid = tid; thread->kernelstack = thread->kernelstackbase + kstacksize; thread->stacksize = kstacksize; arch_regs_firsttimesetup(thread->regs, &thread->extraregs); return thread; } static thread_t* freethread(thread_t* thread){ free(thread->regs); free(thread->kernelstack); free(thread); } static void queue_add(sched_queue* queue, thread_t* thread){ thread->next = NULL; thread->prev = queue->end; if(queue->end) queue->end->next = thread; queue->end = thread; if(!queue->start) queue->start = thread; } static void queue_remove(sched_queue* queue, thread_t* thread){ if(thread->next) thread->next->prev = thread->prev; else queue->end = thread->prev; if(thread->prev) thread->prev->next = thread->next; else queue->start = thread->next; } static thread_t* getnext(){ thread_t* thread = NULL; while(thread == NULL){ for(int i = 0; i < QUEUE_COUNT && thread == NULL; ++i){ spinlock_acquire(&queues[i].lock); if(queues[i].start){ thread = queues[i].start; queue_remove(&queues[i], thread); } spinlock_release(&queues[i].lock); } if(!thread){ arch_interrupt_enable(); arch_halt(); arch_interrupt_disable(); } } return thread; } void sched_queuethread(thread_t* thread){ sched_queue* queue = &queues[thread->priority]; arch_interrupt_disable(); spinlock_acquire(&queue->lock); queue_add(queue, thread); spinlock_release(&queue->lock); arch_interrupt_enable(); } void sched_timerhook(arch_regs* regs){ #ifdef __X86_64__ apic_eoi(); #endif thread_t* current = arch_getcls()->thread; sched_queue* currentqueue = &queues[current->priority]; memcpy(current->regs, regs, sizeof(arch_regs)); arch_regs_saveextra(¤t->extraregs); spinlock_acquire(¤tqueue->lock); queue_add(currentqueue, current); spinlock_release(¤tqueue->lock); thread_t* next = getnext(); arch_getcls()->thread = next; if(next){ memcpy(regs, next->regs, sizeof(arch_regs)); } if(next->ctx != current->ctx) vmm_switchcontext(next->ctx); arch_setkernelstack(next->kernelstack); arch_regs_setupextra(&next->extraregs); timer_add(&arch_getcls()->schedreq, THREAD_QUANTUM, false); } void switch_thread(thread_t* thread){ thread_t* current = arch_getcls()->thread; // if we don't have to change address spaces don't waste time arch_getcls()->thread = thread; if(thread->ctx != current->ctx) vmm_switchcontext(thread->ctx); arch_setkernelstack(thread->kernelstack); arch_regs_setupextra(&thread->extraregs); arch_switchcontext(thread->regs); __builtin_unreachable(); } thread_t* sched_newuthread(void* ip, size_t kstacksize, void* stack, proc_t* proc, bool run, int prio){ thread_t* thread = allocthread(NULL, THREAD_STATE_RUNNING, 0, kstacksize); if(!proc){ proc = allocproc(1); if(!proc){ freethread(thread); return NULL; } thread->tid = proc->pid; proc->threadcount = 1; proc->threads[0] = thread; } else{ spinlock_acquire(&proc->lock); thread_t** tmp = realloc(proc->threads, sizeof(thread_t*) * (proc->threadcount + 1)); if(!tmp){ freethread(thread); return NULL; } proc->threads = tmp; proc->threads[proc->threadcount] = thread; ++proc->threadcount; spinlock_release(&proc->lock); thread->tid = getnextpid(); } thread->priority = prio; thread->proc = proc; arch_regs_setupuser(thread->regs, ip, stack, true); if(run){ arch_interrupt_disable(); spinlock_acquire(&queues[prio].lock); queue_add(&queues[prio], thread); spinlock_release(&queues[prio].lock); arch_interrupt_enable(); } return thread; } thread_t* sched_newkthread(void* ip, size_t stacksize, bool run, int prio){ thread_t* thread = allocthread(NULL, THREAD_STATE_RUNNING, 0, stacksize); if(!thread) return NULL; thread->priority = prio; arch_regs_setupkernel(thread->regs, ip, thread->kernelstack, true); if(run){ arch_interrupt_disable(); spinlock_acquire(&queues[prio].lock); queue_add(&queues[prio], thread); spinlock_release(&queues[prio].lock); arch_interrupt_enable(); } return thread; } void sched_yieldtrampoline(thread_t* thread){ arch_regs_saveextra(&thread->extraregs); if(thread->state == THREAD_STATE_RUNNING){ spinlock_acquire(&queues[thread->priority].lock); queue_add(&queues[thread->priority], thread); spinlock_release(&queues[thread->priority].lock); } thread_t* nthread = getnext(); timer_resume(); switch_thread(nthread); } void arch_sched_yieldtrampoline(thread_t* thread, arch_regs* regs); // these 3 expect interrupts to be disabled in entry void sched_yield(){ timer_stop(); thread_t* thread = arch_getcls()->thread; arch_sched_yieldtrampoline(thread, thread->regs); } void sched_eventsignal(event_t* event, thread_t* thread){ if(thread->state != THREAD_STATE_BLOCKED && thread->state != THREAD_STATE_BLOCKED_INTR) return; if(thread->state == THREAD_STATE_BLOCKED_INTR && event == &thread->sigevent) return; thread->state = THREAD_STATE_RUNNING; thread->awokenby = event; spinlock_acquire(&queues[thread->priority].lock); queue_add(&queues[thread->priority], thread); spinlock_release(&queues[thread->priority].lock); } void sched_dequeue(long state){ timer_stop(); arch_interrupt_disable(); thread_t* current = arch_getcls()->thread; thread_t* thread = getnext(); timer_resume(); // for safety arch_getcls()->thread = thread; vmm_switchcontext(thread->ctx); current->state = state; if(state == THREAD_STATE_DEAD) spinlock_release(¤t->proc->threadexitlock); switch_thread(thread); } __attribute__((noreturn)) int sched_die(){ sched_dequeue(THREAD_STATE_DEAD); } void sched_threadexitcheck(){ if(arch_getcls()->thread->shouldexit) sched_die(); } void sched_block(bool interruptible){ thread_t* thread = arch_getcls()->thread; thread->state = interruptible ? THREAD_STATE_BLOCKED_INTR : THREAD_STATE_BLOCKED; sched_yield(); } void sched_init(){ arch_getcls()->thread = allocthread(NULL, THREAD_STATE_RUNNING, 0, 0); arch_getcls()->thread->priority = THREAD_PRIORITY_KERNEL; timer_req* req = &arch_getcls()->schedreq; req->func = sched_timerhook; timer_add(&arch_getcls()->schedreq, THREAD_QUANTUM, true); } void sched_runinit(){ printf("Loading /sbin/init\n"); thread_t* thread = sched_newuthread(NULL, THREAD_DEFAULT_KSTACK_SIZE, NULL, NULL, false, THREAD_PRIORITY_USER); proc_t* proc = thread->proc; init = proc; arch_getcls()->thread = thread; vmm_switchcontext(thread->ctx); vfs_open(&proc->root, vfs_root(), "/"); vfs_open(&proc->cwd, vfs_root(), "/"); int tmp; fd_t *stdinfd, *stdoutfd, *stderrfd; fd_alloc(&proc->fdtable, &stdinfd, &tmp, 0); fd_alloc(&proc->fdtable, &stdoutfd, &tmp, 0); fd_alloc(&proc->fdtable, &stderrfd, &tmp, 0); #define CONSOLE_PATH "dev/console" int ret = vfs_open(&stdinfd->node, vfs_root(), CONSOLE_PATH); if(ret){ printf("Open failed: %s\n", strerror(ret)); _panic("Could not open stdin for init", 0); } ret = vfs_open(&stdoutfd->node, vfs_root(), CONSOLE_PATH); if(ret){ printf("Open failed: %s\n", strerror(ret)); _panic("Could not open stdout for init", 0); } ret = vfs_open(&stderrfd->node, vfs_root(), CONSOLE_PATH); if(ret){ printf("Open failed: %s\n", strerror(ret)); _panic("Could not open stderr for init", 0); } stdinfd->offset = stdoutfd->offset = stderrfd->offset = 0; stdinfd->flags = O_RDONLY + 1; stdoutfd->flags = stderrfd->flags = O_WRONLY + 1; vnode_t* node; ret = vfs_open(&node, vfs_root(), "/sbin/init"); if(ret){ printf("Open failed: %s\n", strerror(ret)); _panic("Could not load init", 0); } char* argv[] = {"/sbin/init", NULL}; char* env[] = {NULL}; void *entry, *stack; ret = elf_load(thread, node, argv, env, &stack, &entry); if(ret){ printf("ELF load error: %s\n", strerror(ret)); _panic("Could not load init", 0); } vfs_close(node); arch_regs_setupuser(thread->regs, entry, stack, true); fd_release(stdinfd); fd_release(stdoutfd); fd_release(stderrfd); proc->umask = 022; switch_thread(thread); __builtin_unreachable(); }