#include <errno.h> #include <malloc.h> #include <wchar.h> #include <string.h> #include <stdlib.h> #include <kernel/kernel.h> #include <kernel/thread.h> #include <kernel/memory.h> #include <kernel/proc.h> #include <kernel/handle.h> #include <kernel/vmm.h> #include <kernel/sys.h> #include <os/os.h> thread_queue_t thr_running[32]; thread_queue_t thr_suspended, thr_sleeping; dword thr_canrun; thread_t *thr_first, *thr_last, *current; dword thr_lastid; bool thr_needschedule; //SEMAPHORE(sem_scheduler); extern tss_t tss; extern thread_t thr_idle;//, *old_current; extern descriptor_t _gdt[]; void semAcquire(semaphore_t* sem) { if (sem->owner == NULL || sem->owner == current) { sem->owner = current; sem->locks++; } else { while (sem->locks) enable(); sem->owner = current; sem->locks++; } } void semRelease(semaphore_t* sem) { if (sem->owner != current) { wprintf(L"%S(%d) ", sem->file, sem->line); if (sem->owner) wprintf(L"owned by %u\n", sem->owner->id); else wprintf(L"not owned\n"); assert(sem->owner == current); } sem->locks--; if (sem->locks == 0) sem->owner = NULL; } bool semTryAcquire(semaphore_t* sem) { if ((sem->owner == NULL || sem->owner == current) && sem->locks == 0) { semAcquire(sem); return true; } else return false; } //! Creates a new thread. /*! * The new thread is created suspended, in the following state: * - For kernel threads (level == 0), CS is set to 0x18 (the flat kernel code * selector) with RPL == 0 * - For user threads (level == 3), CS is set to 0x28 (the flat user code * selector) with RPL == 3 * - DS, ES, GS and SS are set to 0x30 (the flat data selector) with RPL == * level * - FS is set to 0x40 (the per-thread data selector) with RPL == level * - EIP is set to entry_point * - All general-purpose registers are zeroed * - A 4KB kernel stack is created, and the initial context is set up as if * the thread had just entered a timer IRQ * - Space for a user stack is allocated out of the process's stack space. * This is initially 4KB, but it can grow as needed. * - All flags are zero except IF, the interrupt enable flag * * Each thread is associated with a particular process. To create a thread as * part of the kernel, use proc = &proc_idle. * * The thread is created suspended (to unsuspend, use thread->suspend--). A * per-thread information structure is allocated in the process's address * space and its members are filled out. * * The new thread is not scheduled immediately. * * \param level The level of thread to create. This should be less than or * equal to the process's level (i.e. equally or more priviliged). * \param proc The process within which the thread will execute. * \param entry_point The function where thread execution will start. * * \return The new thread object, or NULL if thread creation failed. */ char *sbrk(size_t size); thread_t* thrCreate(int level, process_t* proc, const void* entry_point, unsigned priority) { thread_t* thr; context_t* ctx; thread_info_t* info; vm_area_t* area; if (priority >= 32) return false; if (proc == NULL) return false; if (level > 3) return false; /* Allocate a handle for the thread structure */ thr = hndAlloc(sizeof(thread_t), proc); if (!thr) return NULL; //semAcquire(&sem_scheduler); memset(thr, 0, sizeof(thread_t)); /* Allocate a PL0 stack */ thr->kernel_stack = (void*) sbrk(PAGE_SIZE); assert(thr->kernel_stack != NULL); thr->kernel_esp = (dword) thr->kernel_stack + PAGE_SIZE - sizeof(context_t); //wprintf(L"kernel_esp = %x\n", thr->kernel_esp); /* Create the thread's context and initialise its selectors and registers */ ctx = thrContext(thr); if (level == 0) ctx->cs = 0x18 | level; else ctx->cs = 0x28 | level; ctx->ds = ctx->es = ctx->gs = ctx->ss = 0x30 | level; ctx->fs = 0x40 | level; memset(&ctx->regs, 0, sizeof(ctx->regs)); /* Set up the task frame for a context switch -- simulate a timer IRQ return */ ctx->intr = 0; ctx->error = -1; /* Enable interrupts */ ctx->eflags = 2 | EFLAG_IF | 0x3000; // xxx - IOPL==3 ctx->eip = (dword) entry_point; ctx->esp = proc->stack_end; ctx->kernel_esp = thr->kernel_esp; /* Deduct the thread's stack from the process's memory */ /* xxx - need vmmAlloc here */ proc->stack_end -= PAGE_SIZE; thr->next = NULL; thr->prev = thr_last; thr->prev_queue = NULL; thr->next_queue = NULL; thr->id = ++thr_lastid; thr->suspend = 1; thr->state = THREAD_RUNNING; thr->process = proc; thr->priority = priority; /* Allocate process memory for the user-mode thread structure */ thr->info = vmmAlloc(proc, 1, NULL, MEM_USER | MEM_COMMIT | MEM_READ | MEM_WRITE); assert(thr->info != NULL); //wprintf(L"%s(%d): TCB at %x\n", proc->mod_first->name, thr->id, thr->info); area = vmmArea(proc, thr->info); if (area == NULL) { wprintf(L"thr->info = %p\n", thr->info); assert(area != NULL); } if (area) { dword phys; phys = memTranslate(proc->page_dir, area->start) & -PAGE_SIZE; /* Initialize the fields */ info = (thread_info_t*) phys; //info->except = (exception_registration_t*) 0xffffffff; // no handler info->info = (thread_info_t*) thr->info; info->tid = thr->id; info->pid = 0; info->last_error = 0; info->process = proc->info; } if (thr_last) thr_last->next = thr; if (!thr_first) thr_first = thr; thr_last = thr; thrEnqueue(thr, &thr_suspended); //semRelease(&sem_scheduler); return thr; } //! Creates a thread which will operate in Virtual 8086 mode. /*! * The IA32 processor architecture allows a task to run in an real-mode * context under a protected-mode environment. The kernel allows this by * creating a new thread which operates under this mode. * * The new thread is created in user mode; however, certain priviliged * instructions (such as port reads/writes) are emulated by the kernel. * Interrupt calls are handled transparently; the thread uses the real- * mode interrupt vector table at 0000:0000. The exception to this is the * kernel syscall interrupt, 0x30: this functions the same way as in * protected mode. Hence a V86 thread can terminate itself with the * following sequence: * * mov ax, 106h * int 30h * * \note To access system calls with IDs > 0xFFFF, a V86 thread must use * the operand-size override and load eax with the system call ID, not * ax. Initially all registers, including the high words of general- * purpose registers, are zeroed. * * The function maps the lower 1MB of memory into the process's address space. * This is necessary for two reasons: * - The V86 thread can only execute out of addresses below 1MB * - The V86 thread can access ordinary real-mode code and data, such as the * BIOS. Note that a real-mode operating system, such as MS-DOS, will * not have been loaded before The Möbius, and will not be present * in memory. * * The code passed to the thrCreate86() function is copied into low memory, * at address 4000:0000. * * The new thread will execute independently to the calling thread. If * synchronous operation is needed (whereby the calling thread treats the * V86 thread as a subroutine), the thrWaitHandle() function can be used. * * \param proc The process within which the thread will execute. * \param code An real-mode function to execute as the thread. * \param code_size The length, in bytes, of the function. * * \return The new thread object, or NULL if thread creation failed. */ thread_t* thrCreate86(process_t* proc, const byte* code, size_t code_size, V86HANDLER handler, unsigned priority) { thread_t* thr; context_t* ctx86; byte* page; vm_area_t* area; area = vmmArea(proc, NULL); if (area) { wprintf(L"Real-mode memory block already allocated: size = %x\n", area->pages * PAGE_SIZE); //if (area->phys != NULL) //return NULL; //vmmFree(proc, area); area->flags = MEM_READ | MEM_WRITE | MEM_USER | MEM_LITERAL; } thr = thrCreate(3, proc, NULL, priority); assert(thr != NULL); ctx86 = thrContext(thr); if (area == NULL) { page = vmmAlloc(proc, 1048576 / PAGE_SIZE, 0, MEM_READ | MEM_WRITE | MEM_USER | MEM_LITERAL); assert(page == NULL); // gotcha! page == start of block == zero area = vmmArea(proc, NULL); } vmmCommit(area, NULL, -1); //page = vmmAlloc(proc, 1, 0x40000, MEM_READ | MEM_WRITE | MEM_USER | MEM_COMMIT); page = (byte*) 0x40000; assert(page != NULL); memcpy(page, code, code_size); ctx86->eip = 0; ctx86->cs = ctx86->ss = ((dword) page) >> 4; ctx86->ds = ctx86->es = ctx86->fs = ctx86->gs = ctx86->ss = 0x30 | 3; ctx86->esp = 0; ctx86->eflags = 2 | EFLAG_IF | EFLAG_IOPL3 | EFLAG_VM | EFLAG_TF; thr->v86handler = handler; thrSuspend(thr, false); return thr; } //! Terminates and deletes a thread. /*! * The thread handle is signalled and all the data associated with it is * freed. If it is the current thread, a reschedule takes place. * * \param thr The thread to be deleted */ void thrDelete(thread_t* thr) { if (thr == &thr_idle) { wprintf(L"Not ending idle thread\n"); halt(0); return; } //semAcquire(&sem_scheduler); while (thr->suspend) thrSuspend(thr, false); thrDequeue(thr, &thr_running[thr->priority]); //wprintf(L"Deleting thread %u\n", thr->id); //thr->suspend++; if (thr->prev) thr->prev->next = thr->next; if (thr->next) thr->next->prev = thr->prev; if (thr == thr_last) thr_last = thr->prev; if (thr == thr_first) thr_first = thr->next; vmmFree(vmmArea(thr->process, thr->info)); //free(thr->kernel_stack); //memFree(thr->user_stack, 1); thr->state = THREAD_DELETED; hndSignal(thr, true); if (thr == current) { //wprintf(L"thrDelete: ending current thread (%d)\n", current->id); //old_current = NULL; thrSchedule(); } hndFree(thr); //semRelease(&sem_scheduler); } //! Determines whether a thread is ready to continue execution. /*! * This function is called by thrSchedule() in order to find the next thread * that is ready to execute. * * A thread may be unable to execute for any of the following reasons: * - It is the idle thread (thr->id == 0) * - It is suspended (thr->suspend != 0) * - It is sleeping for a specific duration of time (thr->state == * THREAD_WAIT_TIMEOUT) * - It is waiting for a handle to be signalled (thr->state == * THR_WAIT_HANDLE) * * \param thr The thread to be checked * \return Returns true if the thread can execute, or false otherwise. */ #if 0 bool thrIsReady(thread_t* thr) { if (thr->suspend || thr->id == 0) return false; else if (thr->state == THREAD_WAIT_TIMEOUT) { if (uptime >= thr->wait.time) { //wprintf(L"%d: sleep ended\n", thr->id); thr->state = THREAD_RUNNING; return true; } else { //wprintf(L"%d: sleeping\r", thr->id); return false; } } else if (thr->state == THREAD_WAIT_HANDLE) { bool wait_end = false; unsigned i, first = -1; if (thr->wait.handle.wait_all) { wait_end = true; /* * Keep searching until we find a non-signalled handle; * if we found a signalled handle, the wait can end. */ for (i = 0; i < thr->wait.handle.num_handles; i++) if (thr->wait.handle.handles[i] != NULL && !hndIsSignalled(thr->wait.handle.handles[i])) { /* This handle is not signalled: the wait cannot end */ wait_end = false; break; } else if (first == -1) { //wprintf(L"WaitAND: Handle %d ready\n", i); /* Save the index of the first signalled handle */ first = i; } } else { wait_end = false; for (i = 0; i < thr->wait.handle.num_handles; i++) if (thr->wait.handle.handles[i] != NULL && hndIsSignalled(thr->wait.handle.handles[i])) { //wprintf(L"WaitOR: Handle %d ready\n", i); wait_end = true; first = i; break; } } if (wait_end) { //context_t* ctx = thrContext(thr); //wprintf(L"[%d] wait ended on handle %u = %p\n", //thr->id, first, thr->wait.handle.handles[first]); /* Reset all handles */ for (i = 0; i < thr->wait.handle.num_handles; i++) hndSignal(thr->wait.handle.handles[i], false); /* Clear the thread's wait state */ hndFree(thr->wait.handle.handles); thr->wait.handle.handles = NULL; thr->wait.handle.num_handles = 0; thr->state = THREAD_RUNNING; //ctx->regs.eax = first; return true; } else { //wprintf(L"%d: waiting\r", thr->id); return false; } } else { //wprintf(L"%d: running\r", thr->id); return true; } } #endif //! Schedules a new thread for execution. /*! * This function is called on the timer IRQ by the isr() function. It finds * the next thread ready for execution; if none are found it schedules the * idle thread. * * It is possible to the kernel to be in a mixed context after this function * returns. current may point to any thread, but the full context switch * will not take place until the timer IRQ handler returns, causing the * new process's page directory to be loaded. However, the thread * information block (starting at FS:[0]) \e is loaded. * * If the current thread is suspended then a reschedule will not take place. * Therefore, it is possible for a thread which is currently in some * critical section of code (such as a kernel device driver) to prevent * a context switch from taking place while still keeping interrupts * enabled. */ void thrSchedule() { //bool end = false; dword mask; int i; thread_t *thr, *next; if (current->suspend /*|| !semTryAcquire(&sem_scheduler)*/) return; /* //wprintf(L"%x\r", tss.esp0); *((word*) 0xb8000) = 0x7120; do { // *((word*) 0xb809c) = 0x7100 | (current->id + '0'); //for (i = 0; i < 2500; i++) //; current = current->next; if (!current) { if (end) { current = &thr_idle; break; } end = true; current = thr_first; } } while (!thrIsReady(current)); */ for (thr = thr_sleeping.first; thr; thr = next) { next = thr->next_queue; if (uptime >= thr->wait.time) { thrDequeue(thr, &thr_sleeping); thrRun(thr); } } if (thr_canrun == 0) current = &thr_idle; else { mask = thr_canrun; i = 0; while ((mask & 1) == 0) { i++; mask >>= 1; } if (thr_running[i].current == NULL) thr_running[i].current = thr_running[i].first; assert(thr_running[i].current != NULL); current = thr_running[i].current; thr_running[i].current = thr_running[i].current->next_queue; if (thr_running[i].current == NULL) thr_running[i].current = thr_running[i].first; } assert(current->info != NULL); i386_set_descriptor(_gdt + 8, (addr_t) current->info, 1, ACS_DATA | ACS_DPL_3, ATTR_BIG | ATTR_GRANULARITY); *((word*) 0xb8000) = 0x7100 | (current->id + '0'); //semRelease(&sem_scheduler); } //! Returns the thread context structure for the specified thread. /*! * While in kernel mode, each thread has its own context structure which * describes the user-mode state of the processor at the time it entered * kernel mode. Thus this structure uniquely identifies the state of a * thread -- context switching is possible by switching the processor's * state between the different threads' context structures. * * The thread's context is held in the thread's kernel stack and was placed * there by the initial interrupt handle code, immediately before passing * control to the isr() function. * * \param thr The thread for which the context is requested * \return The thread context structure for the specified thread. */ context_t* thrContext(thread_t* thr) { return (context_t*) (thr->kernel_esp - 4); } //! Places the specified thread in a sleeping state until the specified interval has elapsed. /*! * If the thread is currently executing then a reschedule will take place. * * \param thr The thread to be slept * \param ms The duration of the sleep, in milliseconds */ void thrSleep(thread_t* thr, dword ms) { thr->wait.time = uptime + ms; thr->state = THREAD_WAIT_TIMEOUT; thrDequeue(thr, &thr_running[thr->priority]); thrEnqueue(thr, &thr_sleeping); if (thr == current) thr_needschedule = true; } bool thrWaitFinished(void** hnd, unsigned num_handles, bool wait_all) { bool any_waiting = false, any_signalled = false; unsigned i; for (i = 0; i < num_handles; i++) { if (hndIsSignalled(hnd[i])) { hndSignal(hnd[i], false); hnd[i] = NULL; any_signalled = true; } if (hnd[i] != NULL) any_waiting = true; } if ((wait_all && !any_waiting) || (!wait_all && any_signalled)) return true; else return false; } //! Causes the specified thread to be paused until the specified handle is signalled. /*! * If the thread is currently executing then a reschedule will take place. * * \param thr The thread which will wait * \param hnd The handle to wait for */ void thrWaitHandle(thread_t* thr, void** hnd, unsigned num_handles, bool wait_all) { context_t* ctx = thrContext(thr); int i; void** temp; //wprintf(L"thrWaitHandle(%p, %S(%d))\n", thr, handle->file, handle->line); temp = hndAlloc(sizeof(void*) * num_handles, NULL); memcpy(temp, hnd, sizeof(void*) * num_handles); if (thr != current || (ctx->intr == 0x30 && ctx->regs.eax == 0x0101)) { //_cputws(L"User wait\n"); if (thrWaitFinished(temp, num_handles, wait_all)) { handle_t *h; h = hndHandle(hnd[0]); //wprintf(L"%d: %S(%d) already signalled\n", //thr->id, h->file, h->line); hndFree(temp); return; } for (i = 0; i < num_handles; i++) { handle_t *h = hndHandle(hnd[i]); if (hnd[i]) { //wprintf(L"Moving thread %d from %u queue to handle %S(%d) queue\n", //thr->id, thr->priority, h->file, h->line); thrDequeue(thr, &thr_running[thr->priority]); thrEnqueue(thr, &h->queue); } } thr->wait.handle.handles = temp; thr->wait.handle.num_handles = num_handles; thr->wait.handle.wait_all = wait_all; thr->state = THREAD_WAIT_HANDLE; thrSchedule(); } else { //unsigned i; //bool wait_end = false; //dword flags; /* * xxx - this is a very kludgy way of determining whether we are being * called from kernel or user mode * thrWaitHandle is being effectively emulated by a busy-wait */ wprintf(L"thrWaitHandle: current = %d, intr = %x\n", current->id, ctx->intr); while (!thrWaitFinished(temp, num_handles, wait_all)) enable(); hndFree(temp); /* asm("int3"); wprintf(L"%d: start wait\n", thr->id); //while (!hndIsSignalled(hnd)) //enable(); asm("pushfl\n" "pop %0" : "=g" (flags)); thrSuspend(current, true); while (!wait_end) { if (wait_all) { wait_end = true; for (i = 0; i < num_handles; i++) if (!hndIsSignalled(hnd[i])) { wait_end = false; break; } } else { wait_end = false; for (i = 0; i < num_handles; i++) if (hndIsSignalled(hnd[i])) { wait_end = true; break; } } enable(); } thrSuspend(current, false); asm("push %0\n" "popfl" : : "g" (flags)); //wprintf(L"%d: signalled\n", thr->id);*/ } } //! Calls a function in the context of the specified thread. /*! * Pushes the parameters provided onto the thread's stack. The function * will be called as the thread returns to user mode. * * \param addr A pointer to the function to be executed. To avoid * stack corruption, this function must be declared __stdcall. * \param params The parameters to be passed to the function. On the IA32 * architecture, these should be multiples of 32 bits in width and * aligned on 32-bit boundaries. * \param sizeof_params The size, in bytes, of the parameters pointed to * by params. This should be a multiple of 32 bits on the IA32 * architecture. */ void thrCall(thread_t* thr, void* addr, void* params, size_t sizeof_params) { context_t* ctx = thrContext(thr); if (current->process->level > 0) { assert(ctx->esp > 0 && ctx->esp <= 0x80000000); ctx->esp -= sizeof_params; memcpy((void*) ctx->esp, params, sizeof_params); ctx->esp -= 4; *((dword*) ctx->esp) = ctx->eip; ctx->eip = (dword) addr; } //else //i386_docall(addr, params, sizeof_params); } thread_t* thrCurrent() { return current; } bool thrDequeue(thread_t* thr, thread_queue_t* queue) { thread_t *found; for (found = queue->first; found; found = found->next_queue) if (found == thr) break; if (found == NULL) return false; if (thr->prev_queue) thr->prev_queue->next_queue = thr->next_queue; if (thr->next_queue) thr->next_queue->prev_queue = thr->prev_queue; if (thr == queue->first) queue->first = thr->next_queue; if (thr == queue->last) queue->last = thr->prev_queue; thr->next_queue = thr->prev_queue = NULL; if (queue->current == thr) { queue->current = thr->next_queue; if (queue->current == NULL) queue->current = queue->first; } if (queue == &thr_running[thr->priority]) if (thr_running[thr->priority].first == NULL) thr_canrun &= ~(1 << thr->priority); return true; } void thrEnqueue(thread_t* thr, thread_queue_t* queue) { assert(thr->next_queue == NULL); assert(thr->prev_queue == NULL); if (queue->last) queue->last->next_queue = thr; thr->prev_queue = queue->last; thr->next_queue = NULL; queue->last = thr; if (queue->first == NULL) queue->first = thr; if (queue->current == NULL) queue->current = thr; } void thrRun(thread_t* thr) { assert(thr->priority < countof(thr_running)); //wprintf(L"Moving thread %d onto run queue\n", thr->id); thrEnqueue(thr, &thr_running[thr->priority]); thr_canrun |= 1 << thr->priority; thr->state = THREAD_RUNNING; thr_needschedule = true; } void thrSuspend(thread_t* thr, bool suspend) { if (suspend) thr->suspend++; else thr->suspend--; if (suspend && thr->suspend == 1) { wprintf(L"Suspending thread %d\n", thr->id); thrDequeue(thr, &thr_running[thr->priority]); thrEnqueue(thr, &thr_suspended); } else if (!suspend && thr->suspend == 0) { wprintf(L"Unsuspending thread %d\n", thr->id); thrDequeue(thr, &thr_suspended); thrRun(thr); } } bool thrSetPriority(thread_t* thr, unsigned priority) { if (priority >= 32) return false; if (thr->suspend || thr->wait.handle.num_handles) thr->priority = priority; else { thrDequeue(thr, &thr_running[thr->priority]); thr->priority = priority; thrRun(thr); } return true; }