#include "task.h" #include "sched.h" #include #include #include #include #include "timer.h" #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_delete(process *pr); //From task_.asm extern "C" uint32_t read_eip(); extern "C" void task_idle(void*); static uint32_t nextpid = 1; process *processes = 0, *kernel_process = 0, *current_process = 0; thread *current_thread = 0, *idle_thread = 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() { cli(); kernel_process = new process(); //This process must be hidden to users kernel_process->pid = kernel_process->uid = kernel_process->thread_count = 0; kernel_process->privilege = PL_KERNEL; kernel_process->parent = kernel_process; kernel_process->pagedir = kernel_pagedir; kernel_process->next = 0; kernel_process->threads = 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; process* it = processes; while (it != 0) { it->pagedir->tables[idx] = table; it->pagedir->tablesPhysical[idx] = tablephysical; it = it->next; } } /* 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 << "\n Exception stack trace :\n"; stack_trace(regs->ebp); PANIC("Kernel error'd."); } 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_delete(pr); } 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) { pid = (nextpid++); uid = _uid; thread_count = 0; threads = 0; privilege = _privilege; parent = _parent; pagedir = pagedir_new(); next = processes; 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; } processes = this; } /* 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_delete(process *pr) { thread *it = pr->threads; while (it != 0) { thread_delete(it); it = it->next; } if (processes == pr) { processes = pr->next; } else { process *it = processes; while (it) { if (it->next == pr) { it->next = pr->next; break; } it = it->next; } } pagedir_delete(pr->pagedir); kfree(pr); } 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; } // syscalls 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); }