<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>http://wiki.foofun.cn//index.php?action=history&amp;feed=atom&amp;title=Interrupt_Service_Routines</id>
	<title>Interrupt Service Routines - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="http://wiki.foofun.cn//index.php?action=history&amp;feed=atom&amp;title=Interrupt_Service_Routines"/>
	<link rel="alternate" type="text/html" href="http://wiki.foofun.cn//index.php?title=Interrupt_Service_Routines&amp;action=history"/>
	<updated>2026-05-14T04:15:15Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.37.1</generator>
	<entry>
		<id>http://wiki.foofun.cn//index.php?title=Interrupt_Service_Routines&amp;diff=956&amp;oldid=prev</id>
		<title>Zhang3：创建页面，内容为“x86体系结构是一个中断驱动的系统。 外部事件触发中断 - 中断正常控制流，并调用'''中断服务例程'''(ISR-Interrupt Service Routine)。  这样的事件可以由硬件或软件触发。 硬件中断的一个例子是键盘： 每按一次键，键盘就会触发IRQ1(中断请求1)，并调用相应的中断处理程序。 定时器和磁盘请求完成是硬件中断的其他可能来源。  软件…”</title>
		<link rel="alternate" type="text/html" href="http://wiki.foofun.cn//index.php?title=Interrupt_Service_Routines&amp;diff=956&amp;oldid=prev"/>
		<updated>2022-03-19T12:52:39Z</updated>

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