查看“Interrupt Service Routines”的源代码
←
Interrupt Service Routines
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[:Category:x86|x86]]体系结构是一个[[interrupt|中断]]驱动的系统。 外部事件触发中断 - 中断正常控制流,并调用'''中断服务例程'''(ISR-Interrupt Service Routine)。 这样的事件可以由硬件或软件触发。 硬件中断的一个例子是键盘: 每按一次键,键盘就会触发IRQ1(中断请求1),并调用相应的中断处理程序。 定时器和磁盘请求完成是硬件中断的其他可能来源。 软件驱动的中断由<tt>int</tt>操作码触发; 例如,MS-DOS提供的服务由触发 <tt>INT 21h</tt>的软件调用,并在CPU寄存器中传递适用的参数。 为了让系统知道当某个中断发生时调用哪个中断服务例程,当您处于 [[Protected mode|保护模式]] 时,ISR的偏移量存储在 [[IDT|中断描述符表-Interrupt Descriptor Table]] 中, 或者在[[Real Mode|实模式]]下的[[Interrupt Vector Table|中断向量表]]中。 ISR由CPU直接调用,并且用于调用ISR的协议不同于调用C函数那样的调用。 最重要的是,ISR必须以 <tt>iret</tt> 操作码 (或 <tt>iretq</tt> 在长模式下-是的,即使是使用intel语法) 结束,而通常的C函数以 <tt>ret</tt> 或 <tt>ret</tt> 结尾。 这个显而易见但却错误的解决方案导致了操作系统程序员中最“流行”的错误之一:三重故障(triple-fault)。 ==调用处理程序的时间== === x86 === 当CPU调用中断处理程序时,CPU将这些值按以下顺序推送到堆栈上: <pre> EFLAGS -> CS -> EIP </pre> CS值用两个字节填充,形成一个双字(doubleword)。 如果门类型不是陷阱门,CPU将清除中断标志。 如果中断是异常,则CPU会将错误代码作为双字推送到堆栈上。 CPU将把相关IDT描述符中的段选择器值加载到CS中。 === x86-64 === 当CPU调用中断处理程序时,它会将RSP寄存器中的值更改为IST中指定的值,如果没有,堆栈将保持不变。 到新堆栈上,CPU按以下顺序推送这些值: <pre> SS:RSP (original RSP) -> RFLAGS -> CS -> RIP </pre> CS被填充成一个四字(quadword)。 如果中断是从不同的Ring调用的,则SS设置为0,表示空选择器。 CPU将修改RFLAGS寄存器,将TF,NT和RF位设置为0。 如果门类型是陷阱门,CPU将清除中断标志。 如果中断是异常,CPU将把一个错误代码推入堆栈,并用字节填充以形成一个四字。 CPU会将段选择器值从关联的IDT描述符加载到CS中,并检查以确保CS是有效的代码段选择器。 ==问题== 许多人回避汇编,希望尽可能多地使用他们最喜欢的高级语言。 [[GCC]] (以及其他编译器) 允许您添加内联汇编,因此许多程序员都想编写这样的ISR: <source lang="C"> /*如何不编写中断处理程序*/ void interrupt_handler(void) { asm("pushad"); /*保存寄存器。*/ /* 做点什么 */ asm("popad"); /*恢复寄存器*/ asm("iret"); /*这将是三重故障!*/ } </source> 这行不通。 编译器不明白发生了什么。 它不理解在ASM语句之间需要保留寄存器和堆栈;优化器可能会损坏函数。 此外,编译器会在函数之前和之后添加堆栈处理代码,这与iret一起导致类似于以下内容的汇编代码: <source lang="asm"> push %ebp mov %esp,%ebp sub $<size of local variables>,%esp pushad # C code comes here popad iret # “leave” 如果使用局部变量,则“pop %ebp”。 leave ret </source> 这将如何打乱堆栈应该是显而易见的(ebp被push,但从未pop)。 不要这样做。 相反,以下是你的选择。 ==解决方案== === 纯汇编 === 充分了解汇编,在其中编写中断处理程序。;-) === 两阶段汇编包装 === 编写一个汇编包装器调用C函数来完成实际工作,然后执行iret。 <source lang="asm"> /* filename: isr_wrapper.s */ .globl isr_wrapper .align 4 isr_wrapper: pushad cld /* sysV ABI后面的C代码要求在函数输入时清除DF* / call interrupt_handler popad iret </source> <source lang="C"> /* 文件名:interrupt_handler.c*/ void interrupt_handler(void) { /* 做点什么 */ } </source> === 特定于编译器的中断指令 === 某些处理器的一些编译器具有允许您声明例程中断、提供#pragma中断或专用宏的指令。 Clang 3.9,Borland C,Watcom C/C,Microsoft C 6.0和免费Pascal编译器1.9.* 以上和GCC提供这一点。 Visual C++提供了一个替代的'''Naked Functions''' ==== Clang ==== 从版本3.9开始,它支持x86/x86-64目标的[http://clang.llvm.org/docs/AttributeReference.html#id1 中断属性]。 <source lang="C"> struct interrupt_frame { uword_t ip; uword_t cs; uword_t flags; uword_t sp; uword_t ss; }; __attribute__ ((interrupt)) void interrupt_handler(struct interrupt_frame *frame) { /* do something */ } </source> ==== Borland C ==== <source lang="C"> /* Borland C */ void interrupt interrupt_handler(void) { /* do something */ } </source> ==== Watcom C/C++ ==== <source lang="C"> /* Watcom C/C++ */ void _interrupt interrupt_handler(void) { /* do something */ } </source> ==== Naked Functions ==== 有些编译器可以用来制作中断例程,但需要您手动处理堆栈和返回操作。 这样做需要在没有尾声或序言的情况下生成函数。 这称为使函数''naked'' -这是在Visual C中通过将属性 “_ declspec(naked)” 添加到函数来完成的。 您需要验证是否包含返回操作(如“iretd”),因为这是尾声的一部分,编译器现在已被指示不包含该操作。 如果您打算使用局部变量,则必须按照编译器期望的方式设置堆栈帧;但是,由于ISR是不可重入(non-reentrant)的,您可以简单地使用静态变量。 ==== Visual C++ ==== Visual C还提供了 __LOCAL_SIZE汇编程序宏,它会告知您堆栈上的对象需要多少空间用于函数。 <source lang="C"> /* Microsoft Visual C++ */ void _declspec(naked) interrupt_handler() { _asm pushad; /* do something */ _asm{ popad iretd } } </source> ==== GCC / G++ ==== [https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#x86-Function-Attributes 在线文档]说,通过使用GCC的函数属性,他们增加了使用__attribute__((interrupt))在C接口中编写中断处理程序的能力。 所以替代如下做法: <source lang="C"> /* 黑魔法-强烈不推荐!*/ void interrupt_handler() { __asm__("pushad"); /* do something */ __asm__("popad; leave; iret"); /*黑魔法*/ } </source> 你可以有正确的(GCC)形式: <source lang="C"> struct interrupt_frame; __attribute__((interrupt)) void interrupt_handler(struct interrupt_frame* frame) { /* do something */ } </source> GCC的文档指出,如果使用中断属性(interrupt attribute),则在x86和x86-64体系结构上将使用iret指令代替ret。 它还说,“因为GCC不保留SSE、MMX或x87状态,所以GCC选项-mgeneral regs只能用于编译中断和异常处理程序。” =====黑魔法===== 查看[[Interrupt_Service_Routines#问题|上面]]错误的代码,跳过了正确的C函数退出代码,破坏了堆栈。 现在,考虑这个代码片段,其中手动添加退出代码: <source lang="C"> /*黑魔法-强烈反对!*/ void interrupt_handler() { __asm__("pushad"); /* 做点什么 */ __asm__("popad; leave; iret"); /*黑魔法*/ } </source> 相应的输出看起来有点像这样: <source lang="asm"> push %ebp mov %esp,%ebp sub $<size of local variables>,%esp pushad # C code comes here popad leave iret leave # dead code ret # dead code </source> 这假设 <tt>leave</tt> 是正确的函数结束处理-您正在 “手工” 执行函数返回代码,而将编译器生成的处理保留为 “死代码”。 不用说,对编译器内部的这种假设是危险的。 这段代码可以在不同的编译器上中断,甚至可以在同一编译器的不同版本上中断。 因此,强烈不鼓励它,这里仅出于完整性而列出。 =====汇编Goto===== :''全文:[[ISRS_PIC_AND_MULTI TASKING|ISRS、PIC和MULTI TASKING]]'' 自版本4.5起,GCC支持 “asm goto” 语句。 它可以用来使ISR成为返回ISR入口点正确地址的函数。 [[Category:Interrupts]] [[Category:x86]]
返回至“
Interrupt Service Routines
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息