I took the Ghost OS approach of passing the esp of the current task to my task switcher and cast it as a pointer to a struct that holds CPU state.
I also now start new tasks by constructing a default ISR stack.
In new threads, I set the eip to the address of the starting function, I set cs to be the offset of my kernel's code segment, and I set the IF bit in eflags and leave all other eflags bits as 0.
Here's my updated flow:
Code: Select all
#
# IRQ0 entry point
#
irq_0:
pushal # push GPRs
pushl %esp # push esp
call irq_0_handler # handle PIT and task logic
movl %eax, %esp # update esp
popal # pop GPRs
iret # pop ISR stack
Code: Select all
/**
* IRQ0 handler
*/
uint32_t irq_0_handler(uint32_t esp)
{
// This is currently only used to determine if it's time to switch tasks.
// We'll need some synchronization primitives before threads can share the PIT.
main_ticks++;
// Send the EOI.
k_outb(0x20, 0x20);
// Select the next task.
uint32_t next_esp = k_switch_task(main_ticks, esp);
return next_esp;
}
Code: Select all
/**
* task switching
*/
uint32_t k_switch_task(uint32_t main_ticks, uint32_t esp)
{
// If there is no current task,
// then tasking has not yet been initialized.
if (current_task == NULL || initialized == 0)
{
fprintf(stddbg, "tasking has not yet been initialized\n");
return 0;
}
// Update the stored CPU state of the current task.
current_task->cpu_state = (k_regs*)esp;
// We currently switch tasks approximately every 2 seconds.
// This logic is just for developing the task switch functionality.
if (main_ticks % 2000 != 0)
return (uint32_t)(current_task->cpu_state);
// Alternate between tasks.
switch (current_task->id)
{
case 1:
current_task = &task_a;
break;
case 2:
current_task = &task_b;
break;
case 3:
current_task = &main_task;
break;
default:
break;
}
return (uint32_t)(current_task->cpu_state);
}