diff --git a/kernel-src/arch/x86-64/main.c b/kernel-src/arch/x86-64/main.c index a6b80410..bb88bc39 100644 --- a/kernel-src/arch/x86-64/main.c +++ b/kernel-src/arch/x86-64/main.c @@ -126,7 +126,7 @@ void kernel_entry() { initrd_unpack(); defaultauth_init(); - sched_runinit(); + proc_run_init(); sched_threadexit(); } diff --git a/kernel-src/arch/x86-64/smp.c b/kernel-src/arch/x86-64/smp.c index 69f67b10..b4fe06de 100644 --- a/kernel-src/arch/x86-64/smp.c +++ b/kernel-src/arch/x86-64/smp.c @@ -36,7 +36,7 @@ static void cpuwakeup(struct limine_smp_info *info) { __atomic_add_fetch(&arch_smp_cpusawake, 1, __ATOMIC_SEQ_CST); - sched_apentry(); + sched_ap_entry(); } void arch_smp_sendipi(cpu_t *targcpu, isr_t *isr, int target, bool nmi) { diff --git a/kernel-src/include/kernel/proc.h b/kernel-src/include/kernel/proc.h index bfcaeb80..4159ddd0 100644 --- a/kernel-src/include/kernel/proc.h +++ b/kernel-src/include/kernel/proc.h @@ -13,6 +13,9 @@ #include +#define PROC_STATE_NORMAL 0 +#define PROC_STATE_ZOMBIE 1 + typedef struct proc_t { mutex_t mutex; int refcount; @@ -107,5 +110,7 @@ int proc_signal_all(int signal, proc_t *sender); void proc_stop_other_threads(void); pid_t proc_allocate_pid(void); void proc_init(void); +void proc_exit(void); +void proc_run_init(); #endif diff --git a/kernel-src/include/kernel/scheduler.h b/kernel-src/include/kernel/scheduler.h index c33cfbee..03434d2c 100644 --- a/kernel-src/include/kernel/scheduler.h +++ b/kernel-src/include/kernel/scheduler.h @@ -1,26 +1,8 @@ #ifndef _SCHEDULER_H #define _SCHEDULER_H -#include -#include -#include -#include -#include -#include -#include -#include -#include #include - -#define SCHED_THREAD_FLAGS_QUEUED 1 -#define SCHED_THREAD_FLAGS_RUNNING 2 -#define SCHED_THREAD_FLAGS_SLEEP 4 -#define SCHED_THREAD_FLAGS_INTERRUPTIBLE 8 -#define SCHED_THREAD_FLAGS_PREEMPTED 16 -#define SCHED_THREAD_FLAGS_DEAD 32 - -#define SCHED_PROC_STATE_NORMAL 0 -#define SCHED_PROC_STATE_ZOMBIE 1 +#include #define SCHED_WAKEUP_REASON_NORMAL 0 #define SCHED_WAKEUP_REASON_INTERRUPTED -1 @@ -29,18 +11,16 @@ #define INTERP_BASE (void *)0x00000beef0000000 void sched_init(); -void sched_runinit(); -__attribute__((noreturn)) void sched_threadexit(); +void sched_ap_entry(); + void sched_queue(thread_t *thread); -void sched_stopcurrentthread(); -int sched_yield(); -void sched_preparesleep(bool interruptible); bool sched_wakeup(thread_t *thread, int reason); -thread_t *sched_newthread(void *ip, size_t kstacksize, int priority, proc_t *proc, void *ustack); -void sched_destroythread(thread_t *); -void sched_targetcpu(struct cpu_t *cpu); + +void sched_stop_current_thread(); +void sched_prepare_sleep(bool interruptible); +void sched_target_cpu(struct cpu_t *cpu); void sched_reschedule_on_cpu(struct cpu_t *cpu, bool target); -void sched_sleepus(size_t us); -void sched_apentry(); +void sched_sleep_us(size_t us); +int sched_yield(); #endif diff --git a/kernel-src/include/kernel/thread.h b/kernel-src/include/kernel/thread.h index ae801afc..8809fdf5 100644 --- a/kernel-src/include/kernel/thread.h +++ b/kernel-src/include/kernel/thread.h @@ -5,6 +5,14 @@ #include #include #include +#include + +#define THREAD_FLAGS_QUEUED 1 +#define THREAD_FLAGS_RUNNING 2 +#define THREAD_FLAGS_SLEEP 4 +#define THREAD_FLAGS_INTERRUPTIBLE 8 +#define THREAD_FLAGS_PREEMPTED 16 +#define THREAD_FLAGS_DEAD 32 struct proc_t; @@ -46,4 +54,8 @@ typedef struct thread_t { } signals; } thread_t; +__attribute__((noreturn)) void sched_threadexit(); +thread_t *sched_newthread(void *ip, size_t kstacksize, int priority, struct proc_t *proc, void *ustack); +void sched_destroythread(thread_t *); + #endif diff --git a/kernel-src/io/acpi/glue.c b/kernel-src/io/acpi/glue.c index a71d5d0b..34ec379c 100644 --- a/kernel-src/io/acpi/glue.c +++ b/kernel-src/io/acpi/glue.c @@ -270,7 +270,7 @@ void uacpi_kernel_stall(uacpi_u8 usec) { } void uacpi_kernel_sleep(uacpi_u64 msec) { - sched_sleepus(msec * 1000); + sched_sleep_us(msec * 1000); } struct acpi_irqctx { @@ -355,7 +355,7 @@ static void acpi_dowork(struct acpi_workctx *ctx) { } static void acpi_dogpework() { - sched_targetcpu(get_bsp()); + sched_target_cpu(get_bsp()); sched_yield(); acpi_dowork(&gpework); @@ -418,7 +418,7 @@ static void work_await(struct acpi_workctx *ctx) { if (empty) return; - sched_sleepus(100 * 1000); + sched_sleep_us(100 * 1000); } } diff --git a/kernel-src/io/block/nvme.c b/kernel-src/io/block/nvme.c index 29c35621..675496ca 100644 --- a/kernel-src/io/block/nvme.c +++ b/kernel-src/io/block/nvme.c @@ -323,7 +323,7 @@ static void enqueueandwait(queuepair_t *queuepair, entrypair_t *entries) { queuepair->submission.index %= queuepair->submission.entrycount; *queuepair->submission.doorbell = queuepair->submission.index; - sched_preparesleep(false); + sched_prepare_sleep(false); entries->thread = current_thread(); spinlock_release(&queuepair->lock); sched_yield(); diff --git a/kernel-src/io/block/vioblk.c b/kernel-src/io/block/vioblk.c index 2adb896c..63799169 100644 --- a/kernel-src/io/block/vioblk.c +++ b/kernel-src/io/block/vioblk.c @@ -87,7 +87,7 @@ static void vioblk_enqueue(vioblkdev_t *blkdev, requestheader_t *headerphys, voi size_t driveridx = VIO_QUEUE_DRV_IDX(&blkdev->queue); VIO_QUEUE_DRV_RING(&blkdev->queue)[driveridx % blkdev->queue.size] = idx; - sched_preparesleep(false); + sched_prepare_sleep(false); blkdev->queuewaiting[idx / 3] = current_thread(); ++VIO_QUEUE_DRV_IDX(&blkdev->queue); diff --git a/kernel-src/io/net/arp.c b/kernel-src/io/net/arp.c index 8b3fc88b..1cc895b6 100644 --- a/kernel-src/io/net/arp.c +++ b/kernel-src/io/net/arp.c @@ -77,7 +77,7 @@ static void set(netdev_t *netdev, uint32_t ip, mac_t mac, timespec_t time) { static void cleanupthreadfn() { for (;;) { - sched_sleepus(1000000); // 1 second + sched_sleep_us(1000000); // 1 second MUTEX_ACQUIRE(&cachelock, false); timespec_t currtime = timekeeper_time(); hashentry_t entrybuffer; diff --git a/kernel-src/io/net/vionet.c b/kernel-src/io/net/vionet.c index c2313448..6efb1e3e 100644 --- a/kernel-src/io/net/vionet.c +++ b/kernel-src/io/net/vionet.c @@ -138,7 +138,7 @@ static int vionet_sendpacket(netdev_t *internal, netdesc_t desc, mac_t targetmac size_t driveridx = VIO_QUEUE_DRV_IDX(&netdev->txqueue)++; VIO_QUEUE_DRV_RING(&netdev->txqueue)[driveridx % netdev->txqueue.size] = idx; - sched_preparesleep(false); + sched_prepare_sleep(false); netdev->txwait[idx] = current_thread(); *netdev->txqueue.notify = 0; diff --git a/kernel-src/io/poll.c b/kernel-src/io/poll.c index 8185d000..4d81c54a 100644 --- a/kernel-src/io/poll.c +++ b/kernel-src/io/poll.c @@ -96,10 +96,10 @@ int poll_dowait(polldesc_t *desc, size_t ustimeout) { return 0; } - sched_preparesleep(true); + sched_prepare_sleep(true); if (ustimeout != 0) { - sched_targetcpu(current_cpu()); + sched_target_cpu(current_cpu()); timer_insert(current_cpu()->timer, &sleepentry, timeout, desc, ustimeout, false); } @@ -117,7 +117,7 @@ int poll_dowait(polldesc_t *desc, size_t ustimeout) { if (ustimeout != 0 && ret != 1) { timer_remove(current_cpu()->timer, &sleepentry); - sched_targetcpu(NULL); + sched_target_cpu(NULL); } interrupt_set(intstate); diff --git a/kernel-src/lib/semaphore.c b/kernel-src/lib/semaphore.c index 58fa5fba..15baa646 100644 --- a/kernel-src/lib/semaphore.c +++ b/kernel-src/lib/semaphore.c @@ -71,7 +71,7 @@ int semaphore_wait(semaphore_t *sem, bool interruptible) { if (--sem->i < 0) { insert(sem, current_thread()); - sched_preparesleep(interruptible); + sched_prepare_sleep(interruptible); spinlock_release(&sem->lock); ret = sched_yield(); if (ret) { @@ -104,7 +104,7 @@ int semaphore_timedwait(semaphore_t *sem, time_t timeoutusec, bool interruptible timeoutusec -= sleepus; if (sleepus) - sched_sleepus(sleepus); + sched_sleep_us(sleepus); } while (timeoutusec); return false; diff --git a/kernel-src/sys/proc.c b/kernel-src/sys/proc.c index 825c16c2..dfa03648 100644 --- a/kernel-src/sys/proc.c +++ b/kernel-src/sys/proc.c @@ -5,6 +5,10 @@ #include #include #include +#include +#include +#include +#include static hashtable_t pid_table; static scache_t *processcache; @@ -76,7 +80,7 @@ proc_t *proc_create() { memset(proc, 0, sizeof(proc_t)); // TODO move this to slab - proc->state = SCHED_PROC_STATE_NORMAL; + proc->state = PROC_STATE_NORMAL; proc->runningthreadcount = 1; MUTEX_INIT(&proc->mutex); SPINLOCK_INIT(proc->nodeslock); @@ -228,3 +232,151 @@ void proc_init(void) { __assert(hashtable_init(&pid_table, 100) == 0); MUTEX_INIT(&proc_pid_table_mutex); } + +// called when all threads in a process have exited +void proc_exit(void) { + proc_t *proc = current_thread()->proc; + __assert(proc != init_proc); + + // the process is going to be referenced by the child list, hold it + // it only holds it now because it has to have running threads to be in the list + // which hold a reference. it won't now since its a zombie, which is why it will be held + PROC_HOLD(proc); + + jobctl_detach(proc); + + // close fds + for (int fd = 0; fd < proc->fdcount; ++fd) + fd_close(fd); + + // turn off interval timers + itimer_pause(&proc->timer.realtime, NULL, NULL); + itimer_pause(&proc->timer.virtualtime, NULL, NULL); + itimer_pause(&proc->timer.profiling, NULL, NULL); + + // zombify the proc + MUTEX_ACQUIRE(&proc->mutex, false); + + VOP_RELEASE(proc->root); + VOP_RELEASE(proc->cwd); + + proc->state = PROC_STATE_ZOMBIE; + proc_t *lastchild = proc->child; + + + MUTEX_ACQUIRE(&init_proc->mutex, false); + + int belowzombiecount = 0; + + while (lastchild && lastchild->sibling) { + if (lastchild->state == PROC_STATE_ZOMBIE) + ++belowzombiecount; + + lastchild->parent = init_proc; + lastchild = lastchild->sibling; + } + + if (lastchild) { + lastchild->sibling = init_proc->child; + init_proc->child = proc->child; + } + + MUTEX_RELEASE(&init_proc->mutex); + MUTEX_RELEASE(&proc->mutex); + + signal_signalproc(proc->parent, SIGCHLD); + semaphore_signal(&proc->parent->waitsem); + // TODO sigaction flag for this + for (int i = 0; i < belowzombiecount; ++i) { + semaphore_signal(&init_proc->waitsem); + } +} + +void proc_run_init() { + printf("sched: loading /init\n"); + + vmmcontext_t *vmmctx = vmm_newcontext(); + __assert(vmmctx); + + // leave kernel context to load elf + vmm_switchcontext(vmmctx); + + proc_t *proc = proc_create(); + __assert(proc); + + init_proc = proc; + + vnode_t *initnode; + __assert(vfs_open(vfsroot, "/init", 0, &initnode) == 0); + + auxv64list_t auxv64; + char *interp = NULL; + void *entry; + + __assert(elf_load(initnode, NULL, &entry, &interp, &auxv64) == 0); + if (interp) { + vnode_t *interpnode; + __assert(vfs_open(vfsroot, interp, 0, &interpnode) == 0); + auxv64list_t interpauxv; + char *interpinterp = NULL; + __assert(elf_load(interpnode, INTERP_BASE, &entry, &interpinterp, &interpauxv) == 0); + __assert(interpinterp == NULL); + } + + vnode_t *consolenode; + __assert(devfs_getbyname("console", &consolenode) == 0); + VOP_LOCK(consolenode); + __assert(VOP_OPEN(&consolenode, V_FFLAGS_READ | V_FFLAGS_NOCTTY, &proc->cred) == 0); + VOP_HOLD(consolenode); + __assert(VOP_OPEN(&consolenode, V_FFLAGS_WRITE | V_FFLAGS_NOCTTY, &proc->cred) == 0); + VOP_HOLD(consolenode); + __assert(VOP_OPEN(&consolenode, V_FFLAGS_WRITE | V_FFLAGS_NOCTTY, &proc->cred) == 0); + VOP_UNLOCK(consolenode); + + file_t *stdin = fd_allocate(); + file_t *stdout = fd_allocate(); + file_t *stderr = fd_allocate(); + + stdin->vnode = consolenode; + stdout->vnode = consolenode; + stderr->vnode = consolenode; + + stdin->flags = FILE_READ; + stdout->flags = stderr->flags = FILE_WRITE; + stdin->offset = stdout->offset = stderr->offset = 0; + stdin->mode = stdout->mode = stderr-> mode = 0644; + + proc->fd[0].file = stdin; + proc->fd[0].flags = 0; + proc->fd[1].file = stdout; + proc->fd[1].flags = 0; + proc->fd[2].file = stderr; + proc->fd[2].flags = 0; + + proc->cwd = vfsroot; + VOP_HOLD(vfsroot); + proc->root = vfsroot; + VOP_HOLD(vfsroot); + + char *argv[] = {"/init", cmdline_get("initarg"), NULL}; + char *envp[] = {NULL}; + + void *stack = elf_preparestack(STACK_TOP, &auxv64, argv, envp); + __assert(stack); + + // reenter kernel context + vmm_switchcontext(&vmm_kernelctx); + + thread_t *uthread = sched_newthread(entry, PAGE_SIZE * 16, 1, proc, stack); + __assert(uthread); + + proc->threadlist = uthread; + uthread->procnext = NULL; + + uthread->vmmctx = vmmctx; + sched_queue(uthread); + + VOP_RELEASE(initnode); + // proc starts with 1 refcount, release it here as to only have the thread reference + PROC_RELEASE(proc); +} diff --git a/kernel-src/sys/scheduler.c b/kernel-src/sys/scheduler/scheduler.c similarity index 56% rename from kernel-src/sys/scheduler.c rename to kernel-src/sys/scheduler/scheduler.c index 4722983b..e118288e 100644 --- a/kernel-src/sys/scheduler.c +++ b/kernel-src/sys/scheduler/scheduler.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -18,8 +17,6 @@ #define QUANTUM_US 100000 #define SCHEDULER_STACK_SIZE PAGE_SIZE * 16 -static scache_t *threadcache; - #define RUNQUEUE_COUNT 64 typedef struct { @@ -31,48 +28,6 @@ static rqueue_t runqueue[RUNQUEUE_COUNT]; static uint64_t runqueuebitmap; static spinlock_t runqueuelock; -thread_t *sched_newthread(void *ip, size_t kstacksize, int priority, proc_t *proc, void *ustack) { - thread_t *thread = slab_allocate(threadcache); - if (thread == NULL) - return NULL; - - memset(thread, 0, sizeof(thread_t)); - - thread->kernelstack = vmm_map(NULL, kstacksize, VMM_FLAGS_ALLOCATE, ARCH_MMU_FLAGS_WRITE | ARCH_MMU_FLAGS_READ | ARCH_MMU_FLAGS_NOEXEC, NULL); - if (thread->kernelstack == NULL) { - slab_free(threadcache, thread); - return NULL; - } - - thread->kernelstacktop = (void *)((uintptr_t)thread->kernelstack + kstacksize); - - // non kernel thread vmm contexts are handled by the caller - thread->vmmctx = proc ? NULL : &vmm_kernelctx; - thread->proc = proc; - thread->priority = priority; - thread->kernelstacksize = kstacksize; - if (proc) { - // each thread holds one reference to proc - PROC_HOLD(proc); - thread->tid = proc_allocate_pid(); - } - - CTX_INIT(&thread->context, proc != NULL, true); - CTX_XINIT(&thread->extracontext, proc != NULL); - CTX_SP(&thread->context) = proc ? (ctxreg_t)ustack : (ctxreg_t)thread->kernelstacktop; - CTX_IP(&thread->context) = (ctxreg_t)ip; - SPINLOCK_INIT(thread->sleeplock); - SPINLOCK_INIT(thread->signals.lock); - EVENT_INITHEADER(&thread->signals.waitpendingevent); - - return thread; -} - -void sched_destroythread(thread_t *thread) { - vmm_unmap(thread->kernelstack, thread->kernelstacksize, 0); - slab_free(threadcache, thread); -} - static thread_t *getinrunqueue(rqueue_t *rq) { thread_t *thread = rq->list; @@ -114,14 +69,14 @@ static thread_t *runqueuenext(int minprio) { } if (thread) - thread->flags &= ~SCHED_THREAD_FLAGS_QUEUED; + thread->flags &= ~THREAD_FLAGS_QUEUED; leave: interrupt_set(intstate); return thread; } -static __attribute__((noreturn)) void switchthread(thread_t *thread) { +static __attribute__((noreturn)) void switch_thread(thread_t *thread) { interrupt_set(false); thread_t* current = current_thread(); @@ -135,15 +90,15 @@ static __attribute__((noreturn)) void switchthread(thread_t *thread) { // XXX make the locking of the flags field better, candidate for a r/w lock spinlock_acquire(&runqueuelock); if (current) - current->flags &= ~SCHED_THREAD_FLAGS_RUNNING; + current->flags &= ~THREAD_FLAGS_RUNNING; - __assert((thread->flags & SCHED_THREAD_FLAGS_RUNNING) == 0); + __assert((thread->flags & THREAD_FLAGS_RUNNING) == 0); - if (current && current->flags & SCHED_THREAD_FLAGS_SLEEP) + if (current && current->flags & THREAD_FLAGS_SLEEP) spinlock_release(¤t->sleeplock); - thread->flags |= SCHED_THREAD_FLAGS_RUNNING; - __assert((thread->flags & SCHED_THREAD_FLAGS_QUEUED) == 0); + thread->flags |= THREAD_FLAGS_RUNNING; + __assert((thread->flags & THREAD_FLAGS_QUEUED) == 0); spinlock_release(&runqueuelock); void *schedulerstack = current_cpu()->schedulerstack; @@ -154,8 +109,8 @@ static __attribute__((noreturn)) void switchthread(thread_t *thread) { } static void runqueueinsert(thread_t *thread) { - __assert((thread->flags & SCHED_THREAD_FLAGS_RUNNING) == 0); - thread->flags |= SCHED_THREAD_FLAGS_QUEUED; + __assert((thread->flags & THREAD_FLAGS_RUNNING) == 0); + thread->flags |= THREAD_FLAGS_QUEUED; runqueuebitmap |= ((uint64_t)1 << thread->priority); @@ -174,7 +129,7 @@ void sched_queue(thread_t *thread) { spinlock_acquire(&runqueuelock); // maybe instead of an assert, a simple return would suffice as the thread would already be queued anyways - __assert((thread->flags & SCHED_THREAD_FLAGS_QUEUED) == 0 && (thread->flags & SCHED_THREAD_FLAGS_RUNNING) == 0); + __assert((thread->flags & THREAD_FLAGS_QUEUED) == 0 && (thread->flags & THREAD_FLAGS_RUNNING) == 0); runqueueinsert(thread); @@ -183,12 +138,12 @@ void sched_queue(thread_t *thread) { // TODO yield if higher priority than current thread (or send another CPU an IPI) } -__attribute__((noreturn)) void sched_stopcurrentthread() { +__attribute__((noreturn)) void sched_stop_current_thread() { interrupt_set(false); spinlock_acquire(&runqueuelock); if (current_thread()) - current_thread()->flags &= ~SCHED_THREAD_FLAGS_RUNNING; + current_thread()->flags &= ~THREAD_FLAGS_RUNNING; thread_t *next = runqueuenext(0x0fffffff); if (next == NULL) @@ -196,109 +151,7 @@ __attribute__((noreturn)) void sched_stopcurrentthread() { spinlock_release(&runqueuelock); - switchthread(next); -} - -// called when all threads in a process have exited -void sched_procexit() { - proc_t *proc = current_thread()->proc; - __assert(proc != init_proc); - - // the process is going to be referenced by the child list, hold it - // it only holds it now because it has to have running threads to be in the list - // which hold a reference. it won't now since its a zombie, which is why it will be held - PROC_HOLD(proc); - - jobctl_detach(proc); - - // close fds - for (int fd = 0; fd < proc->fdcount; ++fd) - fd_close(fd); - - // turn off interval timers - itimer_pause(&proc->timer.realtime, NULL, NULL); - itimer_pause(&proc->timer.virtualtime, NULL, NULL); - itimer_pause(&proc->timer.profiling, NULL, NULL); - - // zombify the proc - MUTEX_ACQUIRE(&proc->mutex, false); - - VOP_RELEASE(proc->root); - VOP_RELEASE(proc->cwd); - - proc->state = SCHED_PROC_STATE_ZOMBIE; - proc_t *lastchild = proc->child; - - - MUTEX_ACQUIRE(&init_proc->mutex, false); - - int belowzombiecount = 0; - - while (lastchild && lastchild->sibling) { - if (lastchild->state == SCHED_PROC_STATE_ZOMBIE) - ++belowzombiecount; - - lastchild->parent = init_proc; - lastchild = lastchild->sibling; - } - - if (lastchild) { - lastchild->sibling = init_proc->child; - init_proc->child = proc->child; - } - - MUTEX_RELEASE(&init_proc->mutex); - MUTEX_RELEASE(&proc->mutex); - - signal_signalproc(proc->parent, SIGCHLD); - semaphore_signal(&proc->parent->waitsem); - // TODO sigaction flag for this - for (int i = 0; i < belowzombiecount; ++i) { - semaphore_signal(&init_proc->waitsem); - } -} - -static void threadexit_internal(context_t *, void *) { - thread_t *thread = current_thread(); - // we don't need to access the current thread anymore, as any state will be ignored and we are running on the scheduler stack - // as well as not being preempted at all - interrupt_set(false); - current_cpu()->thread = NULL; - - spinlock_acquire(&runqueuelock); - thread->flags |= SCHED_THREAD_FLAGS_DEAD; - spinlock_release(&runqueuelock); - - // because a thread deallocating its own data is a nightmare, thread deallocation and such will be left to whoever frees the proc it's tied to - // (likely an exit(2) call) - // FIXME this doesn't apply to kernel threads and something should be figured out for them - - sched_stopcurrentthread(); -} - -__attribute__((noreturn)) void sched_threadexit() { - thread_t *thread = current_thread(); - proc_t *proc = thread->proc; - - vmmcontext_t *oldctx = thread->vmmctx; - vmm_switchcontext(&vmm_kernelctx); - - if (proc) { - __atomic_fetch_sub(&proc->runningthreadcount, 1, __ATOMIC_SEQ_CST); - __assert(oldctx != &vmm_kernelctx); - if (proc->runningthreadcount == 0) { - if (thread->shouldexit) - proc->status = -1; - - sched_procexit(); - vmm_destroycontext(oldctx); - PROC_RELEASE(proc); - } - } - - interrupt_set(false); - arch_context_saveandcall(threadexit_internal, current_cpu()->schedulerstack, NULL); - __builtin_unreachable(); + switch_thread(next); } typedef struct { @@ -361,7 +214,7 @@ static void yield(context_t *context, void *) { spinlock_acquire(&runqueuelock); - bool sleeping = thread->flags & SCHED_THREAD_FLAGS_SLEEP; + bool sleeping = thread->flags & THREAD_FLAGS_SLEEP; thread_t *next = runqueuenext(sleeping ? 0x0fffffff : thread->priority); bool gotsignal = false; @@ -380,14 +233,14 @@ static void yield(context_t *context, void *) { } } - if (sleeping && (thread->shouldexit || gotsignal) && (thread->flags & SCHED_THREAD_FLAGS_INTERRUPTIBLE)) { + if (sleeping && (thread->shouldexit || gotsignal) && (thread->flags & THREAD_FLAGS_INTERRUPTIBLE)) { sleeping = false; if (next) runqueueinsert(next); next = NULL; - thread->flags &= ~(SCHED_THREAD_FLAGS_SLEEP | SCHED_THREAD_FLAGS_INTERRUPTIBLE); + thread->flags &= ~(THREAD_FLAGS_SLEEP | THREAD_FLAGS_INTERRUPTIBLE); thread->wakeupreason = SCHED_WAKEUP_REASON_INTERRUPTED; spinlock_release(&thread->sleeplock); } @@ -395,7 +248,7 @@ static void yield(context_t *context, void *) { if (next || sleeping) { ARCH_CONTEXT_THREADSAVE(thread, context); - thread->flags &= ~SCHED_THREAD_FLAGS_RUNNING; + thread->flags &= ~THREAD_FLAGS_RUNNING; if (sleeping == false) runqueueinsert(thread); @@ -403,14 +256,14 @@ static void yield(context_t *context, void *) { next = current_cpu()->idlethread; spinlock_release(&runqueuelock); - switchthread(next); + switch_thread(next); } spinlock_release(&runqueuelock); } int sched_yield() { - bool sleeping = current_thread()->flags & SCHED_THREAD_FLAGS_SLEEP; + bool sleeping = current_thread()->flags & THREAD_FLAGS_SLEEP; bool old = sleeping ? current_thread()->sleepintstatus : interrupt_set(false); arch_context_saveandcall(yield, current_cpu()->schedulerstack, NULL); __assert(current_cpu()->ipl == IPL_NORMAL); @@ -418,24 +271,24 @@ int sched_yield() { return sleeping ? current_thread()->wakeupreason : 0; } -void sched_preparesleep(bool interruptible) { +void sched_prepare_sleep(bool interruptible) { current_thread()->sleepintstatus = interrupt_set(false); spinlock_acquire(¤t_thread()->sleeplock); // no locking needed as only we will be accessing it - current_thread()->flags |= SCHED_THREAD_FLAGS_SLEEP | (interruptible ? SCHED_THREAD_FLAGS_INTERRUPTIBLE : 0); + current_thread()->flags |= THREAD_FLAGS_SLEEP | (interruptible ? THREAD_FLAGS_INTERRUPTIBLE : 0); } bool sched_wakeup(thread_t *thread, int reason) { bool intstate = interrupt_set(false); spinlock_acquire(&thread->sleeplock); - if ((thread->flags & SCHED_THREAD_FLAGS_SLEEP) == 0 || ((reason == SCHED_WAKEUP_REASON_INTERRUPTED) && (thread->flags & SCHED_THREAD_FLAGS_INTERRUPTIBLE) == 0)) { + if ((thread->flags & THREAD_FLAGS_SLEEP) == 0 || ((reason == SCHED_WAKEUP_REASON_INTERRUPTED) && (thread->flags & THREAD_FLAGS_INTERRUPTIBLE) == 0)) { spinlock_release(&thread->sleeplock); interrupt_set(intstate); return false; } - thread->flags &= ~(SCHED_THREAD_FLAGS_SLEEP | SCHED_THREAD_FLAGS_INTERRUPTIBLE); + thread->flags &= ~(THREAD_FLAGS_SLEEP | THREAD_FLAGS_INTERRUPTIBLE); thread->wakeupreason = reason; // TODO send IPI to idle cores @@ -454,16 +307,16 @@ static void dopreempt() { thread_t *current = current_thread(); thread_t *next = runqueuenext(current->priority); - current->flags &= ~SCHED_THREAD_FLAGS_PREEMPTED; + current->flags &= ~THREAD_FLAGS_PREEMPTED; if (next) { - current->flags &= ~SCHED_THREAD_FLAGS_RUNNING; + current->flags &= ~THREAD_FLAGS_RUNNING; runqueueinsert(current); } else { next = current; } spinlock_release(&runqueuelock); - switchthread(next); + switch_thread(next); } static void preempt_dpc(context_t *context, dpcarg_t arg) { @@ -471,10 +324,10 @@ static void preempt_dpc(context_t *context, dpcarg_t arg) { interrupt_set(false); // no need to preempt it again - if (current->flags & SCHED_THREAD_FLAGS_PREEMPTED) + if (current->flags & THREAD_FLAGS_PREEMPTED) return; - current->flags |= SCHED_THREAD_FLAGS_PREEMPTED; + current->flags |= THREAD_FLAGS_PREEMPTED; ARCH_CONTEXT_THREADSAVE(current, context); CTX_INIT(context, false, false); @@ -493,7 +346,7 @@ static void reschedule_ipi(isr_t *, context_t *) { } static void cpuidlethread() { - sched_targetcpu(current_cpu()); + sched_target_cpu(current_cpu()); interrupt_set(true); while (1) { CPU_HALT(); @@ -501,7 +354,7 @@ static void cpuidlethread() { } } -void sched_targetcpu(cpu_t *cpu) { +void sched_target_cpu(cpu_t *cpu) { bool intstatus = interrupt_set(false); current_thread()->cputarget = cpu; interrupt_set(intstatus); @@ -518,7 +371,7 @@ static void reschedule_yield(context_t *context, void *_cpu) { ARCH_CONTEXT_THREADSAVE(thread, context); - thread->flags &= ~SCHED_THREAD_FLAGS_RUNNING; + thread->flags &= ~THREAD_FLAGS_RUNNING; runqueueinsert(thread); if (next == NULL) @@ -527,7 +380,7 @@ static void reschedule_yield(context_t *context, void *_cpu) { arch_smp_sendipi(cpu, cpu->reschedule_isr, ARCH_SMP_IPI_TARGET, false); spinlock_release(&runqueuelock); - switchthread(next); + switch_thread(next); } void sched_reschedule_on_cpu(cpu_t *cpu, bool target) { @@ -558,15 +411,15 @@ static void timeout(context_t *, dpcarg_t arg) { sched_wakeup(thread, 0); } -void sched_sleepus(size_t us) { +void sched_sleep_us(size_t us) { timerentry_t sleepentry = {0}; - sched_preparesleep(false); + sched_prepare_sleep(false); timer_insert(current_cpu()->timer, &sleepentry, timeout, current_thread(), us, false); sched_yield(); } -void sched_apentry() { +void sched_ap_entry() { current_cpu()->schedulerstack = vmm_map(NULL, SCHEDULER_STACK_SIZE, VMM_FLAGS_ALLOCATE, ARCH_MMU_FLAGS_READ | ARCH_MMU_FLAGS_WRITE | ARCH_MMU_FLAGS_NOEXEC, NULL); __assert(current_cpu()->schedulerstack); current_cpu()->schedulerstack = (void *)((uintptr_t)current_cpu()->schedulerstack + SCHEDULER_STACK_SIZE); @@ -579,13 +432,11 @@ void sched_apentry() { timer_insert(current_cpu()->timer, ¤t_cpu()->schedtimerentry, reschedule_timer_dpc, NULL, QUANTUM_US, true); timer_resume(current_cpu()->timer); - sched_stopcurrentthread(); + sched_stop_current_thread(); } void sched_init() { proc_init(); - threadcache = slab_newcache(sizeof(thread_t), 0, NULL, NULL); - __assert(threadcache); current_cpu()->schedulerstack = vmm_map(NULL, SCHEDULER_STACK_SIZE, VMM_FLAGS_ALLOCATE, ARCH_MMU_FLAGS_READ | ARCH_MMU_FLAGS_WRITE | ARCH_MMU_FLAGS_NOEXEC, NULL); __assert(current_cpu()->schedulerstack); @@ -605,95 +456,3 @@ void sched_init() { // XXX move this resume to a more appropriate place timer_resume(current_cpu()->timer); } - -#define STACK_TOP (void *)0x0000800000000000 -#define INTERP_BASE (void *)0x00000beef0000000 - -void sched_runinit() { - printf("sched: loading /init\n"); - - vmmcontext_t *vmmctx = vmm_newcontext(); - __assert(vmmctx); - - // leave kernel context to load elf - vmm_switchcontext(vmmctx); - - proc_t *proc = proc_create(); - __assert(proc); - - init_proc = proc; - - vnode_t *initnode; - __assert(vfs_open(vfsroot, "/init", 0, &initnode) == 0); - - auxv64list_t auxv64; - char *interp = NULL; - void *entry; - - __assert(elf_load(initnode, NULL, &entry, &interp, &auxv64) == 0); - if (interp) { - vnode_t *interpnode; - __assert(vfs_open(vfsroot, interp, 0, &interpnode) == 0); - auxv64list_t interpauxv; - char *interpinterp = NULL; - __assert(elf_load(interpnode, INTERP_BASE, &entry, &interpinterp, &interpauxv) == 0); - __assert(interpinterp == NULL); - } - - vnode_t *consolenode; - __assert(devfs_getbyname("console", &consolenode) == 0); - VOP_LOCK(consolenode); - __assert(VOP_OPEN(&consolenode, V_FFLAGS_READ | V_FFLAGS_NOCTTY, &proc->cred) == 0); - VOP_HOLD(consolenode); - __assert(VOP_OPEN(&consolenode, V_FFLAGS_WRITE | V_FFLAGS_NOCTTY, &proc->cred) == 0); - VOP_HOLD(consolenode); - __assert(VOP_OPEN(&consolenode, V_FFLAGS_WRITE | V_FFLAGS_NOCTTY, &proc->cred) == 0); - VOP_UNLOCK(consolenode); - - file_t *stdin = fd_allocate(); - file_t *stdout = fd_allocate(); - file_t *stderr = fd_allocate(); - - stdin->vnode = consolenode; - stdout->vnode = consolenode; - stderr->vnode = consolenode; - - stdin->flags = FILE_READ; - stdout->flags = stderr->flags = FILE_WRITE; - stdin->offset = stdout->offset = stderr->offset = 0; - stdin->mode = stdout->mode = stderr-> mode = 0644; - - proc->fd[0].file = stdin; - proc->fd[0].flags = 0; - proc->fd[1].file = stdout; - proc->fd[1].flags = 0; - proc->fd[2].file = stderr; - proc->fd[2].flags = 0; - - proc->cwd = vfsroot; - VOP_HOLD(vfsroot); - proc->root = vfsroot; - VOP_HOLD(vfsroot); - - char *argv[] = {"/init", cmdline_get("initarg"), NULL}; - char *envp[] = {NULL}; - - void *stack = elf_preparestack(STACK_TOP, &auxv64, argv, envp); - __assert(stack); - - // reenter kernel context - vmm_switchcontext(&vmm_kernelctx); - - thread_t *uthread = sched_newthread(entry, PAGE_SIZE * 16, 1, proc, stack); - __assert(uthread); - - proc->threadlist = uthread; - uthread->procnext = NULL; - - uthread->vmmctx = vmmctx; - sched_queue(uthread); - - VOP_RELEASE(initnode); - // proc starts with 1 refcount, release it here as to only have the thread reference - PROC_RELEASE(proc); -} diff --git a/kernel-src/sys/signal.c b/kernel-src/sys/signal.c index c3521790..c8958ede 100644 --- a/kernel-src/sys/signal.c +++ b/kernel-src/sys/signal.c @@ -153,7 +153,7 @@ void signal_suspend(sigset_t *sigset) { THREAD_LEAVE(thread); - sched_preparesleep(true); + sched_prepare_sleep(true); sched_yield(); } @@ -320,7 +320,7 @@ void signal_signalproc(struct proc_t *proc, int signal) { spinlock_acquire(&proc->threadlistlock); while (thread) { - if (thread->flags & SCHED_THREAD_FLAGS_DEAD) { + if (thread->flags & THREAD_FLAGS_DEAD) { thread = thread->procnext; continue; } @@ -502,7 +502,7 @@ bool signal_check(struct thread_t *thread, context_t *context, bool syscall, uin } // if not, just sleep. the sigcont won't be sent until THREAD_LEAVE and preparesleep is protecting it from a lost wakeup context_t savecontext = *context; // save context as it could be overwritten if, for example, its the thread->context one - sched_preparesleep(false); + sched_prepare_sleep(false); thread->signals.stopped = true; THREAD_LEAVE(thread); PROCESS_LEAVE(proc); diff --git a/kernel-src/sys/syscalls/pause.c b/kernel-src/sys/syscalls/pause.c index 7e812bc9..9e30b6d4 100644 --- a/kernel-src/sys/syscalls/pause.c +++ b/kernel-src/sys/syscalls/pause.c @@ -6,7 +6,7 @@ syscallret_t syscall_pause(context_t *) { .ret = -1 }; - sched_preparesleep(true); + sched_prepare_sleep(true); sched_yield(); return ret; diff --git a/kernel-src/sys/syscalls/sleep.c b/kernel-src/sys/syscalls/sleep.c index 2bcd85bf..5f05b91c 100644 --- a/kernel-src/sys/syscalls/sleep.c +++ b/kernel-src/sys/syscalls/sleep.c @@ -25,9 +25,9 @@ syscallret_t syscall_nanosleep(context_t *, timespec_t *utime, timespec_t *remai } timerentry_t sleepentry; - sched_preparesleep(true); + sched_prepare_sleep(true); - sched_targetcpu(current_cpu()); + sched_target_cpu(current_cpu()); timer_insert(current_cpu()->timer, &sleepentry, timeout, current_thread(), time.s * 1000000 + time.ns / 1000, false); ret.errno = sched_yield() == SCHED_WAKEUP_REASON_INTERRUPTED ? EINTR : 0; @@ -39,7 +39,7 @@ syscallret_t syscall_nanosleep(context_t *, timespec_t *utime, timespec_t *remai ret.errno = usercopy_touser(remaining, &time, sizeof(timespec_t)) ? EFAULT : EINTR; } - sched_targetcpu(NULL); + sched_target_cpu(NULL); ret.ret = 0; return ret; diff --git a/kernel-src/sys/syscalls/waitpid.c b/kernel-src/sys/syscalls/waitpid.c index 95098950..bc04c911 100644 --- a/kernel-src/sys/syscalls/waitpid.c +++ b/kernel-src/sys/syscalls/waitpid.c @@ -71,7 +71,7 @@ syscallret_t syscall_waitpid(context_t *context, pid_t pid, int *status, int opt stopped = true; } - zombie = iterator->state == SCHED_PROC_STATE_ZOMBIE; + zombie = iterator->state == PROC_STATE_ZOMBIE; } if (zombie || continued || stopped) @@ -114,7 +114,7 @@ syscallret_t syscall_waitpid(context_t *context, pid_t pid, int *status, int opt threadlist = threadlist->procnext; volatile int *flags = (volatile int *)(&freethread->flags); // wait until the thread can actually be unallocated - while ((*flags & SCHED_THREAD_FLAGS_DEAD) == 0) + while ((*flags & THREAD_FLAGS_DEAD) == 0) sched_yield(); sched_destroythread(freethread); diff --git a/kernel-src/sys/thread.c b/kernel-src/sys/thread.c new file mode 100644 index 00000000..5258d019 --- /dev/null +++ b/kernel-src/sys/thread.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include + +static scache_t *thread_cache; + +thread_t *sched_newthread(void *ip, size_t kstacksize, int priority, proc_t *proc, void *ustack) { + if (thread_cache == NULL) { + thread_cache = slab_newcache(sizeof(thread_t), 0, NULL, NULL); + __assert(thread_cache); + } + + thread_t *thread = slab_allocate(thread_cache); + if (thread == NULL) + return NULL; + + memset(thread, 0, sizeof(thread_t)); + + thread->kernelstack = vmm_map(NULL, kstacksize, VMM_FLAGS_ALLOCATE, ARCH_MMU_FLAGS_WRITE | ARCH_MMU_FLAGS_READ | ARCH_MMU_FLAGS_NOEXEC, NULL); + if (thread->kernelstack == NULL) { + slab_free(thread_cache, thread); + return NULL; + } + + thread->kernelstacktop = (void *)((uintptr_t)thread->kernelstack + kstacksize); + + // non kernel thread vmm contexts are handled by the caller + thread->vmmctx = proc ? NULL : &vmm_kernelctx; + thread->proc = proc; + thread->priority = priority; + thread->kernelstacksize = kstacksize; + if (proc) { + // each thread holds one reference to proc + PROC_HOLD(proc); + thread->tid = proc_allocate_pid(); + } + + CTX_INIT(&thread->context, proc != NULL, true); + CTX_XINIT(&thread->extracontext, proc != NULL); + CTX_SP(&thread->context) = proc ? (ctxreg_t)ustack : (ctxreg_t)thread->kernelstacktop; + CTX_IP(&thread->context) = (ctxreg_t)ip; + SPINLOCK_INIT(thread->sleeplock); + SPINLOCK_INIT(thread->signals.lock); + EVENT_INITHEADER(&thread->signals.waitpendingevent); + + return thread; +} + +void sched_destroythread(thread_t *thread) { + vmm_unmap(thread->kernelstack, thread->kernelstacksize, 0); + slab_free(thread_cache, thread); +} + +static void threadexit_internal(context_t *, void *) { + thread_t *thread = current_thread(); + // we don't need to access the current thread anymore, as any state will be ignored and we are running on the scheduler stack + // as well as not being preempted at all + interrupt_set(false); + current_cpu()->thread = NULL; + + thread->flags |= THREAD_FLAGS_DEAD; + + // because a thread deallocating its own data is a nightmare, thread deallocation and such will be left to whoever frees the proc it's tied to + // (likely an exit(2) call) + // FIXME this doesn't apply to kernel threads and something should be figured out for them + + sched_stop_current_thread(); +} + +__attribute__((noreturn)) void sched_threadexit() { + thread_t *thread = current_thread(); + proc_t *proc = thread->proc; + + vmmcontext_t *oldctx = thread->vmmctx; + vmm_switchcontext(&vmm_kernelctx); + + if (proc) { + __atomic_fetch_sub(&proc->runningthreadcount, 1, __ATOMIC_SEQ_CST); + __assert(oldctx != &vmm_kernelctx); + if (proc->runningthreadcount == 0) { + if (thread->shouldexit) + proc->status = -1; + + proc_exit(); + vmm_destroycontext(oldctx); + PROC_RELEASE(proc); + } + } + + interrupt_set(false); + arch_context_saveandcall(threadexit_internal, current_cpu()->schedulerstack, NULL); + __builtin_unreachable(); +}