I'm attempting to do software task switching preemptively initiated by the PIT through my IRQ0 handler.
I can call each thread's function, but then I get a page fault when I attempt to swap the register values.
How can I repopulate the registers such that execution resumes in a different thread where it last left off?
Am I leaving out some registers when swapping? Should I be swapping the segment registers?
Every example I can find seems to have a different idea of which registers to switch.
Here is an outline of my thread-switching plan:
I've given each of these tasks a single 4KiB page from the same virtual address space to use as a stack.
I've assigned their esp and ebp values to the "bottom" (highest address) of their respective stacks.
The flow of my task switching logic is as follows:
1. IRQ0 occurs, enter assembly procedure irq_0, push GPRs, push eflags, then call the C function irq_0_handler
2. In irq_0_handler, call the C function k_switch_task to perform task switching logic
3. In k_switch_task, save the current register values and choose the next task
4. If the next task is not yet started, set esp and ebp, enable interrupts, and call its start function. This should not return.
5. If the next task is already running, load its register values into the registers and jump back to the irq0 assembly procedure to do an iret.
Here's a sort of flow chart:
Code: Select all
irq0 --> irq_0_handler --> k_switch_task --> start_kthread
|
|--> swap_kthread_regs --> ir0 --> iret (page fault somewhere in here)
Here is my IRQ0 handling assembly procedure:
Code: Select all
.global resume_irq0 # allow register swapper proc to jump back to irq0
irq_0:
pushal
pushfl
call irq_0_handler
popfl
popal
resume_irq0:
iret
Code: Select all
void irq_0_handler(
uint32_t eflags,
uint32_t edi,
uint32_t esi,
uint32_t ebp,
uint32_t esp,
uint32_t ebx,
uint32_t edx,
uint32_t ecx,
uint32_t eax
)
{
if (ticks > 0)
ticks--;
main_ticks++;
// Send the EOI before doing task switch logic
// since we don't normally return from a task switch.
k_outb(0x20, 0x20);
// Here is where we initiate a task switch
if (main_ticks % 2000 == 0)
{
k_switch_task(eflags,
edi,
esi,
ebp,
esp,
ebx,
edx,
ecx,
eax);
}
}
Code: Select all
void k_switch_task(uint32_t eflags,
uint32_t edi,
uint32_t esi,
uint32_t ebp,
uint32_t esp,
uint32_t ebx,
uint32_t edx,
uint32_t ecx,
uint32_t eax)
{
// If there is no current task,
// then tasking has not yet been initialized.
if (current_task == NULL)
return;
// Update the stored CPU state of the current task.
current_task->eflags = eflags;
current_task->edi = edi;
current_task->esi = esi;
current_task->ebp = ebp;
current_task->esp = esp;
current_task->ebx = ebx;
current_task->edx = edx;
current_task->ecx = ecx;
current_task->eax = eax;
// Alternate between task a and task b.
switch (current_task->id)
{
case 2:
current_task = &task_b;
break;
case 1:
case 3:
current_task = &task_a;
break;
default:
break;
}
// If the current task is new, start it.
if (current_task->status == TASK_NEW)
{
current_task->status = TASK_RUNNING;
start_kthread(
current_task->esp,
current_task->ebp,
current_task->start
);
return;
}
// Swap the registers. (jumps to irq0)
swap_kthread_regs(
current_task->eflags,
current_task->edi,
current_task->esi,
current_task->ebp,
current_task->esp,
current_task->ebx,
current_task->edx,
current_task->ecx,
current_task->eax
);
}
Code: Select all
# Starts a kernel thread.
# Updates esp and ebp, then calls the start function.
#
# Params:
# 8(%ebp) - the new esp value
# 12(%ebp) - the new ebp value
# 16(%ebp) - the address of the starting function
#
start_kthread:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl 8(%ebp), %eax
movl %eax, -4(%ebp) # temporarily store esp
movl 12(%ebp), %eax
movl %eax, -8(%ebp) # temporarily store ebp
movl 16(%ebp), %ecx # put the address of the start function in ecx
movl -4(%ebp), %esp # update esp
movl -8(%ebp), %ebp # update ebp
sti # enable interrupts
call *%ecx # call the start function
leave
ret
Code: Select all
# Populates the registers with the values from the next task.
# This procedure is only called while handling IRQ0, so after
# loading the register values, it jumps to IRQ0.
#
# Params:
# 8(%ebp) - eflags
# 12(%ebp) - edi
# 16(%ebp) - esi
# 20(%ebp) - ebp
# 24(%ebp) - esp
# 28(%ebp) - ebx
# 32(%ebp) - edx
# 36(%ebp) - ecx
# 40(%ebp) - eax
#
swap_kthread_regs:
movl %esp, %ebp
subl $12, %esp
movl 8(%ebp), %eax
movl %eax, -4(%ebp) # temporarily store eflags
movl 20(%ebp), %eax
movl %eax, -8(%ebp) # temporarily store ebp
movl 24(%ebp), %eax
movl %eax, -12(%ebp) # temporarily store esp
movl 12(%ebp), %edi # update edi
movl 16(%ebp), %esi # update esi
movl 28(%ebp), %ebx # update ebx
movl 32(%ebp), %edx # update edx
movl 36(%ebp), %ecx # update ecx
movl 40(%ebp), %eax # update eax
pushl -4(%ebp)
popfl # update eflags
movl -12(%ebp), %esp # update esp
movl -8(%ebp), %ebp # update ebp
jmp resume_irq0 # resume IRQ0
task_a:
Code: Select all
void task_a_action()
{
for(;;)
{
printf("This is task a.\n");
}
}
Code: Select all
void task_b_action()
{
for(;;)
{
printf("This is task b.\n");
}
}