diff --git a/apps/tests.c b/apps/tests.c index 936e21d7..7f877f88 100644 --- a/apps/tests.c +++ b/apps/tests.c @@ -413,6 +413,69 @@ static int pi(void* arg) return 0; } +#define REPS 10000 + +volatile uint64_t t1, t2; +volatile int stop = !!0; +volatile int sid = 0; + +static int measure_ctx_switch(void* arg) +{ + int id = !!(int)arg; + int oid = !id; + uint64_t freq = get_cpu_frequency() *1000 *1000; + uint64_t diff, min = (uint64_t)-1, max = 0, avg = 0; + int i; + uint32_t a=0,b,c,d; + + // Size of a timeslice in ticks + uint64_t timeslice = freq / TIMER_FREQ; + + kprintf("ID: %d, ", id); +#ifdef SW_TASK_SWITCH + kprintf("Measuring SW task switch.\n"); +#else + kprintf("Measuring HW task switch.\n"); +#endif + + for (i=0; i < REPS && stop == 0; i++) { + while(id == sid && stop == 0) { + t2 = rdtsc(); + cpuid(0,&a,&b,&c,&d); + } + + cpuid(0,&a,&b,&c,&d); + diff = rdtsc() -t2; + + // The last measurement is garbage + if (stop) break; + // The first ones are garbage, too + if (i < 5) goto next_try; + if (diff >= timeslice) { + i--; + goto next_try; + } + + kprintf("%i: diff= %llu, i= %i\n", id, diff, i); + if (diff > max) max = diff; + if (diff < min) min = diff; + avg += diff; + +next_try: + sid = id; + } + avg /= i-5; + + stop = 1; + + kprintf("maximum gap: %llu ticks\n", max); + kprintf("minimum gap: %llu ticks\n", min); + kprintf("average gap: %llu ticks\n", avg); + kprintf("Timeslice size: %llu ticks\n", timeslice); + + return 0; + } + int test_init(void) { // char* argv[] = {"/bin/mshell", NULL}; @@ -424,8 +487,11 @@ int test_init(void) //sem_init(&consuming, 0); //mailbox_int32_init(&mbox); - create_kernel_task(NULL, foo, "Hello from foo1", NORMAL_PRIO); - create_kernel_task(NULL, join_test, NULL, NORMAL_PRIO); + create_kernel_task(NULL, measure_ctx_switch, (int)0, NORMAL_PRIO); + create_kernel_task(NULL, measure_ctx_switch, (int)1, NORMAL_PRIO); + //create_kernel_task(NULL, foo, "Hello from foo1", NORMAL_PRIO); + //create_kernel_task(NULL, foo, "Hello from foo2", NORMAL_PRIO); + //create_kernel_task(NULL, join_test, NULL, NORMAL_PRIO); //create_kernel_task(NULL, producer, , NORMAL_PRIO); //create_kernel_task(NULL, consumer, NULL, NORMAL_PRIO); //create_kernel_task(NULL, mail_ping, NULL, NORMAL_PRIO); @@ -436,7 +502,7 @@ int test_init(void) //create_kernel_task(NULL, laplace, NULL, NORMAL_PRIO); //create_kernel_task(NULL, jacobi, NULL, NORMAL_PRIO); //create_user_task(NULL, "/bin/hello", argv); - create_user_task(NULL, "/bin/tests", argv); + //create_user_task(NULL, "/bin/tests", argv); //create_user_task(NULL, "/bin/jacobi", argv); //create_user_task(NULL, "/bin/mshell", argv); //create_user_task(NULL, "/bin/jacobi", argv); diff --git a/arch/x86/kernel/entry.asm b/arch/x86/kernel/entry.asm index 8d34dff9..b1082278 100644 --- a/arch/x86/kernel/entry.asm +++ b/arch/x86/kernel/entry.asm @@ -507,6 +507,40 @@ hack: jmp 0x00 : 0xDEADBEAF ret +; This procedure is used by scheduler() to switch tasks. +; It is the software-equivalent to the hw-procedure switch_task from above. +; Call it in C with the following arguments: +; sw_switch_context(&old_tasks_stack_pointer, &new_tasks_stack_pointer) +global sw_switch_context +sw_switch_context: + ; The stack layout looks like this: + ; [new stack pointer] + ; [old stack pointer] + ;pushf ; [this procedure's return address] overwritten by: EFLAGS (*1) + push DWORD 0x8 ; CS + push DWORD [esp+4] ; EIP + push DWORD 0 ; Interrupt number + push DWORD 0xc0edbabe ; Error code + pusha ; Registers... + ; ---- This will be popped off by iret later. + + pushf + pop eax + mov [esp+48], eax ; Move EFLAGS to position (*1) by overwriting + ; the return address of sw_switch_context() + + mov ecx, [esp+52] + mov [ecx], esp ; Save stack position in old task structure + mov ecx, [esp+56] + mov esp, [ecx] ; Load new stack + +sw_rollback: + popa + + add esp, 8 + iret + + ; 32: IRQ0 irq0: ; irq0 - irq15 are registered as "Interrupt Gate" diff --git a/arch/x86/kernel/gdt.c b/arch/x86/kernel/gdt.c index 12631a65..1ec3c24f 100644 --- a/arch/x86/kernel/gdt.c +++ b/arch/x86/kernel/gdt.c @@ -27,7 +27,11 @@ #include gdt_ptr_t gp; +#ifdef SW_TASK_SWITCH +static tss_t task_state_segments[MAX_CORES] __attribute__ ((aligned (PAGE_SIZE))); +#else static tss_t task_state_segments[MAX_TASKS] __attribute__ ((aligned (PAGE_SIZE))); +#endif static unsigned char kstacks[MAX_TASKS][KERNEL_STACK_SIZE] __attribute__ ((aligned (PAGE_SIZE))) = {[0 ... MAX_TASKS-1][0 ... KERNEL_STACK_SIZE-1] = 0xCD}; uint32_t default_stack_pointer = (uint32_t) kstacks[0] + KERNEL_STACK_SIZE - sizeof(size_t); // currently, our kernel has full access to the ioports @@ -71,6 +75,7 @@ int register_task(task_t* task) { int arch_fork(task_t* task) { +#ifndef SW_TASK_SWITCH uint16_t cs = 0x08; uint16_t ds = 0x10; uint32_t id; @@ -120,6 +125,7 @@ int arch_fork(task_t* task) asm volatile ("pushf; pop %%eax" : "=a"(task_state_segments[id].eflags)); // This will be the entry point for the new task. asm volatile ("call read_eip" : "=a"(task_state_segments[id].eip)); +#endif return 0; } @@ -130,10 +136,60 @@ int create_default_frame(task_t* task, internal_entry_point_t ep, void* arg) uint16_t ds = 0x10; uint32_t id; +#ifdef SW_TASK_SWITCH + uint32_t *stack; + struct state *stptr; + uint32_t short_state_size = sizeof(struct state)/sizeof(uint32_t) -2; +#endif + if (BUILTIN_EXPECT(!task, 0)) return -EINVAL; id = task->id; +#ifdef SW_TASK_SWITCH + memset(kstacks[id], 0xCD, KERNEL_STACK_SIZE); + + /* The difference between setting up a task for SW-task-switching + * and not for HW-task-switching is setting up a stack and not a TSS. + * This is the stack which will be activated and popped off for iret later. + */ + stack = kstacks[id] +KERNEL_STACK_SIZE -sizeof(uint32_t); + + /* The next three things on the stack are a marker for debugging purposes, ... */ + *stack-- = 0xDEADBEEF; + /* the first-function-to-be-called's arguments, ... */ + *stack-- = arg; + /* and the "caller" we shall return to. + * This procedure cleans the task after exit. */ + *stack = leave_kernel_task; + + /* Next bunch on the stack is the initial register state. + * The stack must look like the stack of a task which was + * scheduled away previously. */ + + /* short_state_size was introduced because the convenient "struct state" + * is used for filling the stack with initial values. But the problem is that + * "iret" will not remove the last two entries from the stack, since we're + * "returning" from kernel space to kernel space. Therefore it is shortened + * by its last two entries. */ + stack -= short_state_size; + + stptr = stack; + memset(stptr, 0x00, short_state_size*sizeof(uint32_t)); + stptr->esp = stack +short_state_size; + stptr->int_no = 0xB16B00B5; + stptr->error = 0xC03DB4B3; + + /* The instruction pointer shall be set on the first function to be called + * after IRETing */ + stptr->eip = ep; + stptr->cs = cs; + stptr->eflags = 0x1002; + + /* Set the task's stack pointer entry to the stack we have crafted right now. + * This is the pointer which will be used by sw_switch_task(old_task, new_task) later.*/ + task->stack = stack; +#else /* reset buffers */ memset(task_state_segments+id, 0x00, sizeof(tss_t)); memset(kstacks[id], 0xCD, KERNEL_STACK_SIZE); @@ -161,9 +217,48 @@ int create_default_frame(task_t* task, internal_entry_point_t ep, void* arg) /* setup for the kernel stack frame */ task_state_segments[id].ss0 = 0x10; task_state_segments[id].esp0 = (uint32_t) kstacks[id] + KERNEL_STACK_SIZE - sizeof(size_t); +#endif + return 0; +} + +#ifdef SW_TASK_SWITCH +int create_default_tss(int id) +{ + uint16_t cs = 0x08; + uint16_t ds = 0x10; + + /* reset buffers */ + memset(task_state_segments+id, 0x00, sizeof(tss_t)); + + /* set default values of all registers */ + task_state_segments[id].cs = cs; + task_state_segments[id].ss = ds; + task_state_segments[id].ds = ds; + task_state_segments[id].fs = ds; + task_state_segments[id].gs = ds; + task_state_segments[id].es = ds; + task_state_segments[id].eflags = 0x1002; // 0x1202; + //task_state_segments[id].cr3 = (uint32_t) (virt_to_phys((size_t)task->pgd)); + //task_state_segments[id].eip = (uint32_t) ep; + task_state_segments[id].esp = (uint32_t) kstacks[id] + KERNEL_STACK_SIZE - sizeof(size_t); + + /* build default stack frame */ + *((size_t*)task_state_segments[id].esp) = 0xDEADBEAF; /* dead-end */ + /* + task_state_segments[id].ebp = task_state_segments[id].esp; + task_state_segments[id].esp -= sizeof(size_t); + *((size_t*)task_state_segments[id].esp) = (size_t) arg; + task_state_segments[id].esp -= sizeof(size_t); + *((size_t*)task_state_segments[id].esp) = (size_t) leave_kernel_task; + */ + + /* setup for the kernel stack frame */ + task_state_segments[id].ss0 = 0x10; + task_state_segments[id].esp0 = (uint32_t) kstacks[id] + KERNEL_STACK_SIZE - sizeof(size_t); return 0; } +#endif /* Setup a descriptor in the Global Descriptor Table */ static void gdt_set_gate(int num, unsigned long base, unsigned long limit, @@ -203,7 +298,11 @@ void gdt_install(void) { unsigned int i; +#ifdef SW_TASK_SWITCH + memset(task_state_segments, 0x00, MAX_CORES*sizeof(tss_t)); +#else memset(task_state_segments, 0x00, MAX_TASKS*sizeof(tss_t)); +#endif /* Setup the GDT pointer and limit */ gp.limit = (sizeof(gdt_entry_t) * GDT_ENTRIES) - 1; @@ -247,7 +346,12 @@ void gdt_install(void) /* * Create TSS for each task at ring0 (we use these segments for task switching) */ +#ifdef SW_TASK_SWITCH + for(i=0; i 1 @@ -1372,16 +1378,21 @@ get_task_out: orig_task->flags &= ~TASK_FPU_USED; } - //kprintf("schedule from %u to %u with prio %u on core %u\n", - // orig_task->id, curr_task->id, (uint32_t)curr_task->prio, CORE_ID); + //kprintf("schedule from %u to %u with prio %u on core %u\n", orig_task->id, curr_task->id, (uint32_t)curr_task->prio, CORE_ID); +#ifndef SW_TASK_SWITCH switch_task(curr_task->id); +#endif finish_task_switch(0); +#ifdef SW_TASK_SWITCH + write_cr3(virt_to_phys((size_t)curr_task->pgd)); + sw_switch_context(&orig_task->stack, &curr_task->stack); +#endif } } void reschedule(void) { uint32_t flags = irq_nested_disable(); - scheduler(); + scheduler(); irq_nested_enable(flags); }