When I don't use the test module, only the idle thread runs and everything works fine—interrupts return correctly. But when I enable multi‑threading tests, an Invalid Opcode exception occurs. At the time of the exception, I see that the `rip` register points to a linear‑mapped address (physical address + offset), which is an area I use for kernel data.
Here is a partial log when running on a single core:
Code: Select all
[TASK]task data init success
[SMP] smp init succeed
[TASK]task init success
[TASK_TEST]Thread A started
[TASK_TEST]Thread B started
[TASK_TEST]Thread C started
[TASK_TEST]Thread D started
[TASK_TEST]Thread E started
[TASK_TEST]Thread F started
Invalid Opcode
rdi=0x0xFFFF80807F003000
rsi=0x0xFFFF80807E064000
rdx=0x0x0000000000000000
rcx=0x0x0000000000000837
rax=0x0x0000000000000028
r8=0x0x0000000000000282
r9=0x0xFFFF80807F007FAF
r10=0x0xFFFF80807F007FAF
r11=0x0xCCCCCCCCCCCCCCCD
rbx=0x0x0000000000000028
rbp=0x0xCCCCCCCCCCCCCCCD
r12=0x0xFFFF80807E063F20
r13=0x0xFFFF80807E07E000
r14=0x0x0000000000000041
r15=0x0x0000000000000001
vector=6
error_code=0x0x0000000000000000
rip=0x0xFFFF80807E063FFC
cs=0x0x0000000000000008
rflags=0x0x0000000000000046
rsp=0x0xFFFF80807E063F18
ss=0x0x0000000000000010
Invalid Opcode
Code: Select all
static void test_thread(void *arg) {
char id = (char)(uintptr_t)arg;
uint64_t cnt = 0;
TASK_TEST_PRINT("Thread %c started\n", id);
while (1) {
cnt++;
if (cnt % 5 == 0) {
TASK_TEST_PRINT("[%c] count=%llu\n", id, cnt);
}
cpu_halt();
}
}
static void test(void) {
if (get_logical_id() == bootboot->bspid) {
task_create_kernel_thread(test_thread, (void*)'A');
task_create_kernel_thread(test_thread, (void*)'B');
task_create_kernel_thread(test_thread, (void*)'C');
task_create_kernel_thread(test_thread, (void*)'D');
task_create_kernel_thread(test_thread, (void*)'E');
task_create_kernel_thread(test_thread, (void*)'F');
}
}
Below are the core parts of my interrupt handling and `switch_to`:
Common interrupt handler:
Code: Select all
.section .text
.globl common_interrupt
.type common_interrupt, @function
.align 8
common_interrupt:
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbp
pushq %rbx
pushq %r11
pushq %r10
pushq %r9
pushq %r8
pushq %rax
pushq %rcx
pushq %rdx
pushq %rsi
pushq %rdi
testb $3, 144(%rsp)
jz 1f
swapgs
1:
movq %rsp, %r12
andq $-16, %rsp
movq %r12, %rdi
call irq_entry
movq %r12, %rsp
testb $3, 144(%rsp)
jz 2f
swapgs
2:
popq %rdi
popq %rsi
popq %rdx
popq %rcx
popq %rax
popq %r8
popq %r9
popq %r10
popq %r11
popq %rbx
popq %rbp
popq %r12
popq %r13
popq %r14
popq %r15
addq $16, %rsp
iretq
[rsp] error code (or 0)
[rsp+8] vector number
[rsp+16] hardware‑pushed RIP
[rsp+24] hardware‑pushed CS
[rsp+32] hardware‑pushed RFLAGS
[rsp+40] hardware‑pushed RSP (if from user mode)
[rsp+48] hardware‑pushed SS (if from user mode)
C interrupt entry:
Code: Select all
void irq_entry(struct pt_regs *regs) {
// Get current timestamp
uint64_t now = smp_get_timestamp();
/**
* Update time
* The updated period is from the last kernel exit to this kernel entry,
* i.e., the time spent in user mode.
*/
time_update(now);
// Account user mode time to the current task
uint64_t user_delta = time_delta();
task_add_current_tick(user_delta);
uint64_t vector = regs->vector;
bool is_vector = (vector < 32);
if (irq_table[vector]) {
void (*irq)(struct pt_regs *regs) = (void (*)(struct pt_regs *regs))irq_table[vector];
irq(regs);
} else {
if (is_vector) {
printp("CPU ERROR\n");
} else {
IRQ_WARN("NO HANDLER FOR VECTOR\n");
}
}
// Update timestamp again
now = smp_get_timestamp();
time_update(now);
if (is_vector) {
// Exception finished, accumulate kernel time for the current task
uint64_t kernel_delta = time_delta();
task_add_current_tick(kernel_delta);
} else {
// External interrupt finished, only update timestamp, no accumulation
apic_eoi();
}
if (smp_check_need_sched()) task_sched();
}
Code: Select all
__attribute__((naked, noinline))
void switch_to(struct thread_struct *prev, struct thread_struct *next) {
__asm__ volatile (
// Save prev registers to prev->thread
"movq %%rbx, %c[thr_rbx](%%rdi)\n\t"
"movq %%rbp, %c[thr_rbp](%%rdi)\n\t"
"movq %%r12, %c[thr_r12](%%rdi)\n\t"
"movq %%r13, %c[thr_r13](%%rdi)\n\t"
"movq %%r14, %c[thr_r14](%%rdi)\n\t"
"movq %%r15, %c[thr_r15](%%rdi)\n\t"
"movq %%rsp, %c[thr_rsp](%%rdi)\n\t"
// Save return address
"movq (%%rsp), %%rax\n\t"
"movq %%rax, %c[thr_rip](%%rdi)\n\t"
// Save fs_base
"rdfsbase %%rax\n\t"
"movq %%rax, %c[thr_fs](%%rdi)\n\t"
// Save cr3
"movq %%cr3, %%rax\n\t"
"movq %%rax, %c[thr_cr3](%%rdi)\n\t"
// Load next registers
"movq %c[thr_rsp](%%rsi), %%rsp\n\t"
"movq %c[thr_rbx](%%rsi), %%rbx\n\t"
"movq %c[thr_rbp](%%rsi), %%rbp\n\t"
"movq %c[thr_r12](%%rsi), %%r12\n\t"
"movq %c[thr_r13](%%rsi), %%r13\n\t"
"movq %c[thr_r14](%%rsi), %%r14\n\t"
"movq %c[thr_r15](%%rsi), %%r15\n\t"
// Restore fs_base
"movq %c[thr_fs](%%rsi), %%rax\n\t"
"wrfsbase %%rax\n\t"
// Load page table
"movq %c[thr_cr3](%%rsi), %%rax\n\t"
"movq %%rax, %%cr3\n\t"
// Jump to next->rip
"pushq %c[thr_rip](%%rsi)\n\t"
"ret\n"
:
: [thr_rip] "i" (THR_RIP),
[thr_cr3] "i" (THR_CR3),
[thr_rsp] "i" (THR_RSP),
[thr_fs] "i" (THR_FS),
[thr_rbx] "i" (THR_RBX),
[thr_rbp] "i" (THR_RBP),
[thr_r12] "i" (THR_R12),
[thr_r13] "i" (THR_R13),
[thr_r14] "i" (THR_R14),
[thr_r15] "i" (THR_R15)
: "rax", "memory"
);
}
The exception seems to occur after `task_sched` returns
Code: Select all
void task_sched(void) {
uint64_t flags = get_cpu_flags();
irq_off();
struct task_struct *prev = smp_get_task_current();
if (!prev) {
goto out;
}
if (prev->state == TASK_RUNNING && prev != smp_get_idle()) {
sched_class_ptr->enqueue(prev);
}
struct task_struct *next = sched_class_ptr->pick_next();
if (!next) {
goto out;
}
// If the same task, no need to switch; just set the timer and return
if (prev == next) {
prev->sched.exec_ns = 0;
task_set_next_timer();
goto out;
}
fpu_save(prev->thread);
smp_set_task_current(next);
fpu_restore(next->thread);
// Set the next timer interrupt
task_set_next_timer();
switch_to(prev->thread, next->thread);
out:
write_cpu_flags(flags);
}
Here is my GitHub repository: https://github.com/shizi297/ShiziOS
