i'm working on my own scheduler for switching a task, but I'm stuck on the context switching mechanism.
All tasks are currently running in kernel mode.
The code compiles correctly, but the kernel randomly triggers page faults when tasks are switched.
Sometimes it happens after 5 context switches, sometimes after 10. There doesn't seem to be any pattern to it.
From my observations, it looks like the stack sometimes becomes corrupted or shifted after returning from the timer interrupt where task switching is performed.
But I have no idea why.
This is the fragment of main64.c where I initialize the tasks.
Code: Select all
void test_3()
{
uint64_t start_time = read_rtc_to_unix();
printf("Test 3: Starting time: %x\n", start_time);
while(1) {
uint64_t current_time = read_rtc_to_unix();
if (start_time < current_time) {
printf("Test 3 time over 5 seconds.\n");
start_time = current_time;
}
}
}
void test_4()
{
uint64_t start_time = read_rtc_to_unix();
printf("Test 4: Starting time: %x\n", start_time);
while(1) {
uint64_t current_time = read_rtc_to_unix();
if (start_time < current_time) {
printf("Test 4 time over 3 seconds.\n");
start_time = current_time;
}
}
}
__attribute__((naked)) void start_kernel()
{
//Other code [..]
add_new_task("TEST3\0", (void*)test_3);
add_new_task("TEST4\0", (void*)test_4);
printf("Enable scheduler switch.\n");
turnon_switch();
while(1);
}
Code: Select all
irq0_handler_asm:
cli
; Save GPRs on the current task's stack.
push rax
push rbx
push rcx
push rdx
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
; current_task points to task_t, and its first field is the saved rsp.
mov rax, [current_task]
mov [rax], rsp
; The scheduler can replace current_task with a new task.
; sub rsp, 8
call switch_task
; add rsp, 8
; Load the stack of the task selected by the scheduler.
mov rax, [current_task]
mov rsp, [rax]
; Restore GPRs in the reverse order of the pushes.
pop r15 ; 0
pop r14 ; 1
pop r13 ; 2
pop r12 ; 3
pop r11 ; 4
pop r10 ; 5
pop r9 ; 6
pop r8 ; 7
pop rdi ; 8
pop rsi ; 9
pop rbp ; 10
pop rdx ; 11
pop rcx ; 12
pop rbx ; 13
pop rax ; 14
mov al, 0x20
out 0x20, al
cld ; Restore the default direction for string instructions because we do not know what the task did.
iretq
Code: Select all
#define TASK_LIMIT 255
typedef struct task {
uint64_t rsp; // pointer to the saved GPR context
uint64_t top_of_stack; // stack top address (for mapping)
uint8_t fpu_state[512] __attribute__((aligned(16))); // buffer for FXSAVE (512 bytes, 16-byte alignment!)
uint64_t entry; // task entry function
int id; // task ID (optional)
char task_name[100];
} task_t;
typedef struct {
char task_name[100];
uint64_t stack_addr;
int enabled;
task_t task;
int running;
int id;
} schedule_task;
scheduler.c
Code: Select all
#include <stdint.h>
#include <stddef.h>
#include "liballoc/liballoc.h"
#include "mem.h"
#include "string.h"
#include "io.h"
#include "gdt.h"
#include "schedule.h"
volatile uint32_t task_list_counter = 0;
extern uint64_t _kernel_end[];
volatile schedule_task task_list[TASK_LIMIT];
task_t *current_task;
uint64_t *get_new_stack_for_task();
static void task_return_trap(void)
{
while (1) {
asm volatile("hlt");
}
}
static void task_bootstrap(void)
{
// while (1);
void (*entry_fn)(void);
entry_fn = (void (*)(void))current_task->entry;
if (entry_fn == NULL) {
printf_rs232("Task bootstrap: missing entry point %x\n", current_task->entry);
task_return_trap();
}
printf_rs232("Task bootstrap: jumping to entry point %x\n", current_task->entry);
entry_fn();
printf_rs232("Task bootstrap: task returned\n");
task_return_trap();
}
void init_schedule()
{
current_task = (task_t*)malloc(sizeof(task_t)); // Allocate memory for Task 0 and adjust the address to the offset
if (!current_task) /* panic */;
memset(current_task, 0, sizeof(task_t));
current_task->id = -1;
printf("task addr: %x\n", current_task);
printf("fpu addr: %x\n", current_task->fpu_state);
uint64_t phys = virtual_to_physical((uint64_t)current_task);
printf("phys: %x\n", phys);
current_task->rsp = 2137;
current_task->fpu_state[0] = 0x42; // Test write to the FPU state
printf("Initial FPU state first byte: %x\n", current_task->fpu_state[0]);
printf("Initial RSP: %x\n", current_task->rsp);
// Key step: save the current stack state as the Task 0 context
// Do this AFTER setting current_task, but BEFORE enabling timers
asm volatile("mov %%rsp, %0" : "=r"(current_task->rsp));
// Initialize the FPU (if fpu_state is available)
asm volatile("fxsave %0" : "=m"(current_task->fpu_state) : : "memory");
printf("Saved initial RSP to Task 0: %x\n", current_task->rsp);
printf("Saved initial FPU state first byte: %x\n", current_task->fpu_state[0]);
}
uint64_t get_current_stack_addr_from_task()
{
return current_task->top_of_stack;
}
int switch_on = 0;
void turnon_switch()
{
switch_on = 1;
}
int task_number = 0;
int timer_ticks = 0;
void switch_task()
{
if (!switch_on) { // Wait for task init
return;
}
if (timer_ticks < 12) {
timer_ticks++;
return; // Simple "every 100 ticks" implementation - you can adjust it to your needs
} else {
timer_ticks = 0; // reset the counter
}
if (current_task->id != -1) {
task_list[current_task->id].task = *current_task; // Save the current task context to its structure
task_list[current_task->id].running = 0;
} else {
// Save FXSAVE state of task 0
asm volatile("fxsave %0" : "=m"(current_task->fpu_state) : : "memory");
// printf("Saved FPU state of Task 0 before first switch: %x\n", current_task->fpu_state[0]);
}
current_task = &task_list[task_number].task; // Switch to the new task
uint64_t *stack_dump = (uint64_t *)(current_task->rsp);
uint64_t *stack_dump2 = (uint64_t *)(task_list[task_number].task.rsp);
if (task_list[task_number].running == 0) {
task_list[task_number].running = 1;
*(uint64_t*)(current_task->rsp + 8*18) = current_task->rsp;
// Clear FXSAVE in the CPU
asm volatile("fninit");
} else {
// Restore the task FPU state
asm volatile("fxrstor %0" : : "m"(current_task->fpu_state) : "memory");
}
task_number++;
// printf("jmp");
}
void brake_point_call()
{
if (switch_on) {
while (1);
}
}
void add_new_task(char *task_name, uint64_t task_entry)
{
if (task_list_counter >= TASK_LIMIT) {
printf_rs232("Max task limit reached!\n");
return;
}
schedule_task *new_task = &task_list[task_list_counter];
strcpy(new_task->task_name, task_name);
new_task->task_name[sizeof(new_task->task_name) - 1] = '\0';
new_task->task.task_name[sizeof(new_task->task_name) - 1] = '\0';
new_task->id = task_list_counter;
new_task->task.id = task_list_counter;
// === Stack allocation (grows downward!) ===
uint64_t stack = (uint64_t)get_new_stack_for_task(); // Get the stack top address and add the size because the stack grows downward
new_task->task.rsp = stack + STACK_SIZE; // remember the top of the stack (for debugging)
new_task->task.top_of_stack = stack; // remember the top of the stack (for mapping)
uint64_t *new_stack = (uint64_t *)(stack + STACK_SIZE);
for (int i = 0; i < STACK_SIZE / sizeof(uint64_t); i++) {
((uint64_t *)stack)[i] = 0x00; // Clear the stack
}
// // === CREATE A FAKE CONTEXT FRAME (as if the task had been interrupted) ===
new_task->task.entry = task_entry;
// 1. Interrupt frame (popped by iretq)
*--new_stack = 0x206; // RFLAGS (IF=1 + bit 1 always set)
*--new_stack = 0x08; // CS (kernel code segment - adjust if you have another selector!)
*--new_stack = (uint64_t)task_bootstrap; // RIP = task startup wrapper
// // 2. All general-purpose registers (GPR) - zero them initially
*--new_stack = 0; // r15
*--new_stack = 0; // r14
*--new_stack = 0; // r13
*--new_stack = 0; // r12
*--new_stack = 0; // r11
*--new_stack = 0; // r10
*--new_stack = 0; // r9
*--new_stack = 0; // r8
*--new_stack = 0; // rdi
*--new_stack = 0; // rsi
*--new_stack = 0; // rbp
*--new_stack = 0; // rdx
*--new_stack = 0; // rcx
*--new_stack = 0; // rbx
*--new_stack = 0; // rax
// // === Save the stack pointer to the task structure ===
new_task->task.rsp = (uint64_t)stack + STACK_SIZE - sizeof(uint64_t)*18; // must point to rax (first pop)
new_task->enabled = 1;
new_task->running = 0;
task_list_counter++;
printf_rs232("[START] Stack for new task %s [id %d]:\n", new_task->task_name, new_task->id);
printf_rs232("------------------------------\n");
printf_rs232("Top of stack (for mapping): %x\n", new_task->task.top_of_stack);
printf_rs232("Current addr of stack %x\n", new_task->task.rsp);
printf("END\n");
}
void print_stack_from_all_tasks()
{
for (int t = 0; t < task_list_counter; t++) {
if (task_list[t].enabled) {
printf_rs232("[END] Stack for task %s [id %d]:\n", task_list[t].task_name, t);
uint64_t *stack = (uint64_t*)task_list[t].task.top_of_stack;
for (int i = 0; i < STACK_SIZE / sizeof(uint64_t); i++) { // Print first 20 entries for brevity
printf_rs232(" [%d] = %x\n", i, stack[i]);
}
}
}
}
Code: Select all
Servicing hardware INT=0x20
254: v=20 e=0000 i=0 cpl=0 IP=0008:ffffffff8000578c pc=ffffffff8000578c SP=0000:ffff800000410070 env->regs[R_EAX]=0000000000000000
RAX=0000000000000000 RBX=0000000000000000 RCX=00000000000007d0 RDX=00000000000007e5
RSI=0000000000000002 RDI=00000000000007e5 RBP=ffff800000410088 RSP=ffff800000410070
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=ffffffff8000578c RFL=00000202 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
Servicing hardware INT=0x20
255: v=20 e=0000 i=0 cpl=0 IP=0008:ffffffff80005da6 pc=ffffffff80005da6 SP=0000:ffff800000410108 env->regs[R_EAX]=ffffffff80902014
RAX=ffffffff80902014 RBX=0000000000000000 RCX=0000000000000006 RDX=0000000000000002
RSI=0000000000000002 RDI=0000000000000026 RBP=ffff800000410128 RSP=ffff800000410108
RIP=ffffffff80005da6 RFL=00000202 [-------] CPL=0 II=0
Servicing hardware INT=0x20
256: v=20 e=0000 i=0 cpl=0 IP=0008:ffffffff80005da6 pc=ffffffff80005da6 SP=0000:ffff800000410108 env->regs[R_EAX]=ffffffff80902014
RAX=ffffffff80902014 RBX=0000000000000000 RCX=0000000000000006 RDX=0000000000000002
RSI=0000000000000002 RDI=0000000000000026 RBP=ffff800000410128 RSP=ffff800000410108
RIP=ffffffff80005da6 RFL=00000202 [-------] CPL=0 II=0
Servicing hardware INT=0x20
257: v=20 e=0000 i=0 cpl=0 IP=0008:ffffffff80005da6 pc=ffffffff80005da6 SP=0000:ffff800000410108 env->regs[R_EAX]=ffffffff80902014
RAX=ffffffff80902014 RBX=0000000000000000 RCX=0000000000000006 RDX=0000000000000002
RSI=0000000000000002 RDI=0000000000000026 RBP=ffff800000410128 RSP=ffff800000410108
RIP=ffffffff80005da6 RFL=00000202 [-------] CPL=0 II=0
Servicing hardware INT=0x20
258: v=20 e=0000 i=0 cpl=0 IP=0008:ffffffff80005da6 pc=ffffffff80005da6 SP=0000:ffff800000410108 env->regs[R_EAX]=ffffffff80902014
RAX=ffffffff80902014 RBX=0000000000000000 RCX=0000000000000006 RDX=0000000000000002
RSI=0000000000000002 RDI=0000000000000026 RBP=ffff800000410128 RSP=ffff800000410108
RIP=ffffffff80005da6 RFL=00000202 [-------] CPL=0 II=0
Servicing hardware INT=0x20
259: v=20 e=0000 i=0 cpl=0 IP=0008:ffffffff80005da6 pc=ffffffff80005da6 SP=0000:ffff800000410108 env->regs[R_EAX]=ffffffff80902014
RAX=ffffffff80902014 RBX=0000000000000000 RCX=0000000000000006 RDX=0000000000000002
RSI=0000000000000002 RDI=0000000000000026 RBP=ffff800000410128 RSP=ffff800000410108
RIP=ffffffff80005da6 RFL=00000202 [-------] CPL=0 II=0
Servicing hardware INT=0x20
260: v=20 e=0000 i=0 cpl=0 IP=0008:ffffffff80005da6 pc=ffffffff80005da6 SP=0000:ffff800000410108 env->regs[R_EAX]=ffffffff80902014
RAX=ffffffff80902014 RBX=0000000000000000 RCX=0000000000000006 RDX=0000000000000002
RSI=0000000000000002 RDI=0000000000000026 RBP=ffff800000410128 RSP=ffff800000410108
RIP=ffffffff80005da6 RFL=00000202 [-------] CPL=0 II=0
check_exception old: 0xffffffff new 0xe
261: v=0e e=0000 i=0 cpl=0 IP=0008:ffff80000061008c pc=ffff80000061008c SP=0000:ffff80000060ffc0 CR2=0000000058a7ffff
RAX=0000000000000000 RBX=ffff800000610088 RCX=00000000000007db RDX=00000000000007db
RSI=0000000000000002 RDI=00000000000007db RBP=ffff800000610088 RSP=ffff80000060ffc0
R8 =0000000000000000 R9 =0000000000000000 R10=0000000000000000 R11=0000000000000000
R12=0000000000000000 R13=0000000000000000 R14=0000000000000000 R15=0000000000000000
RIP=ffff80000061008c
CR2=0000000058a7ffff
CR3=00000000018fb000
RFL=00000246 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
CS =0008 0000000000000000 00000000 00209a00 DPL=0 CS64 [-R-]
SS =0000 0000000000000000 ffffffff 00c09300 DPL=0 DS [-WA]
TR =0018 ffffffff000c86a0 00000067 00008900 DPL=0 TSS64-avl
GDT= ffffffff800c8660 00000027
IDT= ffffffff80902040 0000ffff
CR0=80000013
CR4=00000620
EFER=0000000000000500
