#include "task.h" #include "sched.h" #include #include #include #include #include "timer.h" #include #include #include #include #define KSTACKSIZE 0x8000 //Static routines for handling threads exiting and all cleanup static void thread_exit_stackJmp(uint32_t reason); static void thread_exit2(uint32_t reason); static void thread_delete(thread *th); static void process_finish(process *pr, int retval); //From task_.asm extern "C" uint32_t read_eip(); extern "C" void task_idle(void*); earray *proc_by_pid; earray *proc_retval; process *kernel_process = 0, *current_process = 0; thread *current_thread = 0, *idle_thread = 0; int proc_count = 0; uint32_t tasking_tmpStack[KSTACKSIZE]; /* Sets up tasking. Called by kmain on startup. Creates a kernel process and an IDLE thread in it. */ void tasking_init() { proc_by_pid = new earray(12, 32); proc_retval = new earray(12, 32); cli(); kernel_process = new process(); kernel_process->uid = kernel_process->thread_count = 0; kernel_process->pid = proc_by_pid->add(kernel_process); kernel_process->privilege = PL_KERNEL; kernel_process->parent = kernel_process; kernel_process->pagedir = kernel_pagedir; kernel_process->threads = 0; kernel_process->threads_waiting = 0; current_thread = 0; idle_thread = new thread(kernel_process, task_idle, 0, 0); sti(); } /* Called by the paging functions when a page table is allocated in the kernel space (>K_HIGHHALF_ADDR). Updates the page directories of all the processes. */ void tasking_updateKernelPagetable(uint32_t idx, page_table *table, uint32_t tablephysical) { if (idx < FIRST_KERNEL_PAGETABLE) return; for (int i = 0; i < proc_by_pid->count(); i++) { process *it = proc_by_pid->at(i); if (it == 0) continue; it->pagedir->tables[idx] = table; it->pagedir->tablesPhysical[idx] = tablephysical; } } /* Called when a timer IRQ fires. Does a context switch. */ void schedule() { //if (processes == 0) PANIC("No processes are running !"); asm volatile("cli"); uint32_t esp, ebp, eip; asm volatile("mov %%esp, %0" : "=r"(esp)); asm volatile("mov %%ebp, %0" : "=r"(ebp)); eip = read_eip(); if (eip == 0x12345) { return; } if (current_thread != 0) { current_thread->esp = esp; current_thread->ebp = ebp; current_thread->eip = eip; if (current_thread->state == TS_RUNNING) sched_enqueue(current_thread); } current_thread = sched_dequeue(); ASSERT(current_thread != 0); current_process = current_thread->process; pagedir_switch(current_process->pagedir); gdt_setKernelStack(((uint32_t)current_thread->kernelStack_addr) + current_thread->kernelStack_size); asm volatile(" \ mov %0, %%ebp; \ mov %1, %%esp; \ mov %2, %%ecx; \ mov $0x12345, %%eax; \ jmp *%%ecx;" : : "r"(current_thread->ebp), "r"(current_thread->esp), "r"(current_thread->eip)); } /* Called when an exception happens. Provides a stack trace if it was in kernel land. Ends the thread for most exceptions, ends the whole process for page faults. */ uint32_t tasking_handleException(registers *regs) { if (current_thread == 0) return 0; //No tasking yet NL; WHERE; ke_vt->writeStr("Exception: `"); char *exception_messages[] = {"Division By Zero","Debug","Non Maskable Interrupt","Breakpoint", "Into Detected Overflow","Out of Bounds","Invalid Opcode","No Coprocessor", "Double Fault", "Coprocessor Segment Overrun","Bad TSS","Segment Not Present","Stack Fault","General Protection Fault", "Page Fault","Unknown Interrupt","Coprocessor Fault","Alignment Check","Machine Check"}; *ke_vt << exception_messages[regs->int_no]; *ke_vt << "'\teip:" << regs->eip; if (regs->eip >= K_HIGHHALF_ADDR) { *ke_vt << "\nException in kernel. Stack trace:\n"; stack_trace(regs->ebp); } if (regs->int_no == 14) { *ke_vt << "\n>>> Process exiting.\n"; thread_exit_stackJmp(EX_PR_EXCEPTION); } else { *ke_vt << "\n>>> Thread exiting.\n"; thread_exit_stackJmp(EX_TH_EXCEPTION); } PANIC("This should never have happened. Please report this."); return 0; } /* Puts the current thread in an inactive state. */ void thread_goInactive() { current_thread->state = TS_WAKEWAIT; schedule(); } /* Wakes up the given thread. */ void thread::wakeUp() { if (state == TS_WAKEWAIT) { state = TS_RUNNING; sched_enqueue(this); } } /* Returns the privilege level of the current process. */ int proc_priv() { if (current_thread == 0 || current_process == 0) return PL_UNKNOWN; return current_process->privilege; } /* For internal use only. Called by thread_exit_stackJmp on a stack that will not be deleted. Exits current thread or process, depending on the reason. */ void thread_exit2(uint32_t reason) { //See EX_TH_* defines in task.h /* * if reason == EX_TH_NORMAL, it is just one thread exiting because it has to * if reason == EX_TH_EXCEPTION, it is just one thread exiting because of an exception * if reason is none of the two cases above, it is the whole process exiting (with error code = reason) */ thread *th = current_thread; process* pr; if (th == 0 || th->process == 0) goto retrn; pr = th->process; if ((reason == EX_TH_NORMAL || reason == EX_TH_EXCEPTION) && pr->thread_count > 1) { thread_delete(th); } else { process_finish(pr, reason); } retrn: sti(); schedule(); } /* For internal use only. Called by thread_exit and process_exit. Switches to a stack that will not be deleted when current thread is deleted. */ void thread_exit_stackJmp(uint32_t reason) { cli(); uint32_t *stack; stack = tasking_tmpStack + (KSTACKSIZE / 4); stack--; *stack = reason; stack--; *stack = 0; asm volatile(" \ mov %0, %%esp; \ mov %1, %%ebp; \ mov %2, %%ecx; \ mov %3, %%cr3; \ jmp *%%ecx;" : : "r"(stack), "r"(stack), "r"(thread_exit2), "r"(kernel_pagedir->physicalAddr)); } /* System call. Exit the current thread. */ void thread_exit() { thread_exit_stackJmp(EX_TH_NORMAL); } /* System call. Exit the current process. */ void process_exit(size_t retval) { if (retval == EX_TH_NORMAL || retval == EX_TH_EXCEPTION) retval = EX_PR_EXCEPTION; thread_exit_stackJmp(retval); } /* For internal use only. This is called when a newly created thread first runs (its address is the value given for EIP). It switches to user mode if necessary and calls the entry point. */ static void thread_run(void* u_esp, thread *thread, thread_entry entry_point, void *data) { pagedir_switch(thread->process->pagedir); if (thread->process->privilege >= PL_USER) { //User mode ! uint32_t *stack = (uint32_t*)u_esp; stack--; *stack = (uint32_t)data; stack--; *stack = 0; size_t esp = (size_t)stack, eip = (size_t)entry_point; //Setup a false structure for returning from an interrupt : //value for esp is in ebx, for eip is in ecx //- update data segments to 0x23 = user data segment with RPL=3 //- push value for ss : 0x23 (user data seg rpl3) //- push value for esp //- push flags //- update flags, set IF = 1 (interrupts flag) //- push value for cs : 0x1B = user code segment with RPL=3 //- push eip //- return from fake interrupt asm volatile(" \ mov $0x23, %%ax; \ mov %%ax, %%ds; \ mov %%ax, %%es; \ mov %%ax, %%fs; \ mov %%ax, %%gs; \ \ pushl $0x23; \ pushl %%ebx; \ pushf; \ pop %%eax; \ or $0x200, %%eax; \ push %%eax; \ pushl $0x1B; \ push %%ecx; \ iret; \ " : : "b"(esp), "c"(eip)); } else { asm volatile("sti"); entry_point(data); } thread_exit(); } /* Creates a new thread for given process. Allocates a kernel stack and a user stack if necessary. Sets up the kernel stack for values to be passed to thread_run. */ thread::thread(class process *proc, thread_entry entry_point, void *data, void *u_esp) { process = proc; next = 0; proc->thread_count++; if (u_esp == 0) u_esp = (void*)proc->stack; kernelStack_addr = kmalloc(KSTACKSIZE); kernelStack_size = KSTACKSIZE; uint32_t *stack = (uint32_t*)((size_t)kernelStack_addr + kernelStack_size); //Pass parameters stack--; *stack = (uint32_t)data; stack--; *stack = (uint32_t)entry_point; stack--; *stack = (uint32_t)this; stack--; *stack = (uint32_t)u_esp; stack--; *stack = 0; esp = (uint32_t)stack; ebp = esp + 8; eip = (uint32_t)thread_run; state = TS_RUNNING; sched_enqueue(this); if (proc->threads == 0) { proc->threads = this; } else { thread *i = proc->threads; while (i->next != 0) i = i->next; i->next = this; } } /* Creates a new process. Creates a struct process and fills it up. */ process::process(process* _parent, uint32_t _uid, uint32_t _privilege) : fd(12, 64) { uid = _uid; thread_count = 0; threads = 0; threads_waiting = 0; privilege = _privilege; parent = _parent; pagedir = pagedir_new(); data = 0; dataseg = 0; fd.add((node*)-1); // descriptor #0 must not be used stack = 0; if (privilege >= PL_USER) { //We are running in user mode size_t stacksBottom = K_HIGHHALF_ADDR - 0x01000000; (new simpleseg(stacksBottom, USER_STACK_SIZE, 1))->map(pagedir, 0); stack = stacksBottom + USER_STACK_SIZE - 4; } pid = proc_by_pid->add(this); proc_count++; } /* Deletes given thread, freeing the stack(s). */ static void thread_delete(thread *th) { if (th->process->threads == th) { th->process->threads = th->next; } else { thread *it = th->process->threads; while (it) { if (it->next == th) { it->next = th->next; break; } it = it->next; } } if (current_thread == th) current_thread = 0; th->process->thread_count--; kfree(th->kernelStack_addr); kfree(th); } /* Deletes a process. First, deletes all its threads. Also deletes the corresponding page directory. */ static void process_finish(process *pr, int retval) { proc_retval->set(pr->pid, (void*)retval); thread *it = pr->threads; while (it != 0) { thread_delete(it); it = it->next; } pagedir_delete(pr->pagedir); proc_by_pid->set(pr->pid, 0); while (pr->threads_waiting != 0) { pr->threads_waiting->wakeUp(); pr->threads_waiting = pr->threads_waiting->queue_next; } kfree(pr); proc_count--; if (proc_count == 0) PANIC("No more processes are running! This is bad! Or is it?"); } size_t process::sbrk(size_t size) { if (data == 0) return -1; ASSERT(data < K_HIGHHALF_ADDR); if (data + size >= K_HIGHHALF_ADDR) return -1; size_t ret; if (dataseg == 0) { size_t start = data; if (start & 0x0FFF) start = (start & 0xFFFFF000) + 0x1000; size_t end = start + size; if (end & 0x0FFF) end = (end & 0xFFFFF000) + 0x1000; segment *s = new simpleseg(start, end - start, 1); if (s == 0) return -5; dataseg = s->map(pagedir, 0); if (dataseg == 0) return -1; ret = start; data = start + size; } else { size_t start = dataseg->start; size_t end = data + size; if (end <= start) return -1; if (end & 0x0FFF) end = (end & 0xFFFFF000) + 0x1000; simpleseg *s = (simpleseg*)(dataseg->seg); s->resize(dataseg, end - start); ret = data; data += size; } /* (DBG) ke_vt->writeStr("(sbrk "); ke_vt->writeHex(size); ke_vt->writeStr(" "); ke_vt->writeHex(ret); ke_vt->writeStr(")"); */ return ret; } void* process::set_args(char** args) { if (args == 0) return 0; cli(); // we're writing to this process's user space page_directory *r = current_pagedir; pagedir_switch(pagedir); int len = sizeof(char*), argc = 0; for (char** a = args; *a != 0; a++) { len += strlen(*a) + 1 + sizeof(char*); argc++; } void* p = (void*)sbrk(len); char** si = (char**)p; char* s = (char*)p + ((argc + 1) * sizeof(char*)); for (int i = 0; i < argc; i++) { si[i] = s; strcpy(s, args[i]); s += strlen(args[i]) + 1; } si[argc] = 0; pagedir_switch(r); sti(); return p; } // ===================== SYSTEM CALLS size_t process_sbrk(size_t size) { return current_process->sbrk(size); } void process_brk(size_t ptr) { ASSERT(ptr < K_HIGHHALF_ADDR); current_process->sbrk(ptr - current_process->data); } int process_run(char* file, char** args) { node* bin = vfs_find(root, file); if (bin == 0) return E_NOT_FOUND; if ((bin->type & FT_FILE) == 0) return E_INVALID; int len = bin->get_size(); void *v = kmalloc(len); int r = bin->read(0, len, (char*)v); if (r != len) { kfree(v); return E_NOT_IMPLEMENTED; } if (elf_check((uint8_t*)v) != 0) { kfree(v); return E_NOT_IMPLEMENTED; } // copy arguments to kernel char **arg_data; int argc = 0; if (args != 0) { for (char** a = args; *a != 0; a++) argc++; arg_data = (char**)kmalloc(sizeof(char*) * (argc + 2)); for (int i = 0; i < argc; i++) arg_data[i+1] = strdup(args[i]); } else { arg_data = (char**)kmalloc(sizeof(char*)*2); } arg_data[0] = strdup(file); arg_data[argc + 1] = 0; // load process *p = elf_exec((uint8_t*)v, PL_USER, arg_data); kfree(v); for (int i = 0; arg_data[i] != 0; i++) kfree(arg_data[i]); kfree(arg_data); return (p == 0 ? E_INVALID : p->pid); } int process_waitpid(int pid) { if (pid <= 0) return E_INVALID_RANGE; process *p = proc_by_pid->at(pid); if (p != 0) { current_thread->queue_next = p->threads_waiting; p->threads_waiting = current_thread; thread_goInactive(); } return (int)proc_retval->at(pid); }