Hello,
I am just starting to add task switching to my kernel and have decided to go with software task switching as recommended in several threads.
One thing I have found is that regardless of what ring you are in, on a task switch you will ALWAYS be in ring0. Therefore you only need one set of code to do the switching.
As an example I have this code pointed to by the timer interrupt :-
Code: Select all
scheduler_irq_handler:
cld
pushad
push ds
push es
push fs
push gs
mov ax, SYSTEM_DATA_SELECTOR
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp ;Push pointer to all the stuff we just pushed
call reschedule ;Call C code
mov esp, eax ;Replace the stack with what the C code gave us
pop gs
pop fs
pop es
pop ds
popad ;Put the standard registers back - Switch to another task
iretd
Then the reschedule code :-
Code: Select all
uint_t reschedule(uint_t OldESP)
{
#ifdef debug_scheduler
kprintf("RESCHEDULER\n");
#endif
// update that timer thing
// technically this should be adjusted for the speed of the processor
timer_ticks++;
#ifdef debug_scheduler
kprintf("current task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
kprintf("OldESP = 0x%X.\n", OldESP);
#endif
// common for all
if (system_started)
{
current_task_pointer->kernel_stack_top = OldESP;
// bill the task
current_task_pointer->times_process_scheduled++;
}
else
{
system_started = 1;
// but don't save the stupid pointer to knowhere
}
#ifdef debug_scheduler
kprintf("kernel_stack_top = 0x%X.\n", current_task_pointer->kernel_stack_top);
#endif
if (current_task_pointer->process_next_ready != NIL_PROC)
{
#ifdef debug_scheduler
kprintf("There's a task on the ready task list - switching!\n");
kprintf("old task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
#endif
current_task_pointer = current_task_pointer->process_next_ready;
#ifdef debug_scheduler
kprintf("new task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
#endif
}
else
{
#ifdef debug_scheduler
kprintf("No more tasks on the task list - going to beginning again.\n");
kprintf("old task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
#endif
current_task_pointer = process_ready_list;
#ifdef debug_scheduler
kprintf("new task_pointer = 0x%X.\n", (uint_t)current_task_pointer);
#endif
}
#ifdef debug_scheduler
kprintf("kernel_stack_top = 0x%X.\n", current_task_pointer->kernel_stack_top);
kprintf("should be the same as what we entered with!.\n");
#endif
outb(0x20,0x20); // ack the timer interrupt
// skip context switching for Device Driver's
if (current_task_pointer->process_type == PT_DEVICE_DRIVER)
return(current_task_pointer->kernel_stack_top); // new ESP0 of process
// we have a new process_pointer that isn't a Device Driver, do we have to change the context???
// not necessary for threads though as they share the previous context as their parent
if (current_task_pointer->process_page_directory != current_task_page_directory)
{
current_task_page_directory = current_task_pointer->process_page_directory;
write_cr3((uint_t*)current_task_page_directory);
// a new task also needs it's kernel stack pointer in the TSS set correctly to it knows where
// to store data later
// point it to the end of the process table
process_tss_p->esp0 = (current_task_pointer->kernel_stack_top & 0xFFFFF000) + 0xFFC;
}
// Done
return(current_task_pointer->kernel_stack_top); // new ESP0 of process
}
The tasks are stored in a linked list and all the reschedular does is save the ESP of the current task to the current task slot and load a new one from another. It also checks to see if a new page directory needs to be loaded.
The code's definately not cut-and-pastable into your own OS (due to massive dependancies/defines etc) but this should give you a fair idea of what is needed to support Ring0 + Ring3 tasks and threads.
All my tasks / system services are/will be ring 3. Only Device Drivers will run at ring 0. Anyway....
On the subject of interrupt gates etc, each one has a certain level of access, so the timer interrupt gate I made in the IDT would have a ring 0 interrupt gate pointing to the shedular interrupt code.
A syscall / kernel trap used by an application would still point to kernel code but have a ring 3 interrupt gate allowing the ring 3 application to access it. The processor switches to ring 0 automatically, switches to the stack defined in a/the TSS (see below) automatically, and begin running ring 0 code.
Finally, for the task switching described only one TSS is needed and it only needs one field - ESP0 set, pointing to where the kernel stack is when you switch from ring0 to ring3. I cheat by aiming ESP0 to the end of the tasks process table so regardless of priviledge level, they all look the same to me.
Yes this is a long post but I figured if I got it all out my head while I'm thinking about this stuff, it helps me get it right in my mind. And it wouldn't be too bad if it helped you too eh?