启用定时器中断时触发页错误或GPF问题求助
定时器中断触发页错误/一般保护错误的排查与修复
启用定时器中断时,遇到了页错误(page fault)或一般保护错误(general protection fault)。具体现象:删除print("testing")语句后,定时器中断处理函数timerh能持续正常打印;但保留该语句时,系统会触发GPF或页错误,定时器可能运行几次后就报错。
相关代码
汇编中断服务例程(ISR)
extern timerh isr_timer: push rax push rdi push rdx push rcx push rsi push rbx push rsp push rbp push r8 push r9 push r10 push r11 push r12 push r13 push r14 push r15 call timerh pop r15 pop r14 pop r13 pop r12 pop r11 pop r10 pop r9 pop r8 pop rbp pop rsp pop rbx pop rsi pop rcx pop rdx pop rdi pop rax iretq
C语言中断处理函数
void timerh(uint64_t t) { print("timer\n"); *EOI = 0; return; }
启动定时器的代码
acpi(); print("testing\n");
问题分析与修复方案
核心问题:错误的栈指针操作
你的ISR中错误地执行了push rsp和pop rsp指令:push rsp会将当前栈指针值压入栈,但后续pop rsp会直接把这个旧值赋值给rsp,导致栈指针跳回压栈前的位置,此时栈上还有其他未弹出的寄存器值,后续的pop操作会在错误的栈地址执行,彻底破坏栈结构,最终触发页错误或GPF。- 修复:直接移除
push rsp和pop rsp指令,因为rsp不需要手动保存(CPU在进入中断时已自动处理栈指针切换),修正后的ISR代码如下:extern timerh isr_timer: push rax push rdi push rdx push rcx push rsi push rbx push rbp push r8 push r9 push r10 push r11 push r12 push r13 push r14 push r15 call timerh pop r15 pop r14 pop r13 pop r12 pop r11 pop r10 pop r9 pop r8 pop rbp pop rbx pop rsi pop rcx pop rdx pop rdi pop rax iretq
中断上下文的函数安全性
print函数在主程序和中断中同时调用时,必须是可重入的:- 如果
print操作VGA显存或全局缓冲区,未做同步保护的话,主程序和中断的并发访问会导致数据冲突,触发错误。 - 修复:在ISR开头添加
cli关闭中断(处理完成后发送EOI再执行sti开启),或者为print实现自旋锁,确保同一时间只有一个上下文访问输出资源。
- 如果
EOI信号的正确性
确认EOI指向的是正确的硬件寄存器:- 若使用PIC,主片EOI端口是
0x20,从片是0xA0;若使用APIC,需写入对应的EOI寄存器。错误的EOI会导致中断无法正常结束,引发重复触发或嵌套错误。
- 若使用PIC,主片EOI端口是
栈页表权限检查
确保中断使用的栈(无论是内核栈还是单独的中断栈)对应的页表项被标记为可读写,如果页表项被错误设置为只读或不存在,压栈操作会直接触发页错误。
现象解释
删除print("testing")后暂时正常,是因为主程序栈的布局变化较小,错误的栈恢复操作暂时没有立刻触发致命错误;但保留print时,主程序的栈操作改变了栈的深度和布局,ISR中错误的栈指针恢复会立刻破坏栈结构,触发页错误或GPF。
内容的提问来源于stack exchange,提问作者runningupthatroad
相关产品推荐
相关产品推荐

