架构和设计
从保护的角度来看,x86架构基于分层环,根据这个环,处理器提供的所有执行空间都被划分为四个层级保护域,每个域都有其自己分配的特权级别。这种设计假定大多数代码将在最低特权域中执行,有时需要从更高特权安全域请求服务,并将这些服务抢占到堆栈上,然后以一种方式恢复它,使整个抢占对较低特权代码不可见。
分层保护域设计规定控制不能在不同的安全域之间任意传递。
门是x86架构的一个特性,用于从低特权代码段向高特权代码段进行控制转移,反之则不行。此外,从哪个低特权片段传递控制可以是任意的,但是从哪个高特权片段传递控制必须严格指定。向低特权片段的回溯控制只允许使用IRET
指令。因此,在这方面,英特尔软件开发手册指出:
低特权段中的代码模块只能通过称为门的受控受保护接口访问在更高特权段操作的模块。未经过保护门且没有足够的访问权限进行访问更高级别段的尝试会引发一个通用保护异常(#GP
)。
换句话说,门是具有所需访问权限和目标地址的特权域入口点。因此所有的门都相似并用于几乎相同的目的,而所有门描述符都包含DPL字段,该字段由处理器用于控制访问权限。但请注意,当调用源是硬件时,处理器只检查门的DPL,如果调用源是软件CALL
、JMP
或INT
指令,则绕过此检查。
门的类型
尽管所有的门都相似,但它们之间存在一些差异,因为最初英特尔工程师认为不同的门将用于不同的目的。
任务门
任务门只能存储在IDT和GDT中,并可以由INT
指令调用。这是一种非常特殊的门类型,与其他门有很大的不同。
起初,英特尔的工程师们认为他们将通过提供基于CPU的任务切换功能来革新多任务处理。他们引入了TSS(任务状态段),用于保存任务的寄存器状态并可用于硬件任务切换。触发硬件任务切换有两种方法:使用TSS本身和使用任务门。要进行硬件任务切换,可以使用
CALL
或
JMP
指令。如果我理解正确,任务门被引入的主要原因是为了能够在中断到达时触发硬件任务切换,因为无法通过JMP到TSS选择器触发硬件任务切换。
实际上,没有人使用它或者硬件上下文切换。从性能的角度来看,这个功能实际上并不是最优的,而且使用起来也不方便。例如,考虑到TSS只能存储在GDT中,并且GDT的长度不能超过8192,从硬件的角度来看我们最多只能有8k个任务。
陷阱门只能存储在IDT中,并且由
INT
指令调用。它可以被视为一种基本类型的门。它只是将控制权传递到更高特权级别段中指定的陷阱门描述符地址,仅此而已。陷阱门被广泛用于不同的目的,其中包括:
- 系统调用实现(例如Linux使用
INT 0x80
,Windows使用
INT 0x2E
)。
- 异常处理实现(在异常的情况下我们没有理由禁用中断)。
- 在具有APIC的机器上实现中断处理(我们可以更好地控制内核栈)。
中断门也只能存储在IDT中,并且由
INT
指令调用。它和陷阱门相同,但额外的中断门调用通过自动清除EFLAGS寄存器中的IF标志禁止了将来的中断接受。
中断门被广泛用于中断处理的实现,特别是在基于PIC的机器上。原因在于要控制堆栈深度。PIC没有中断源优先级功能。因此,默认情况下,PIC仅禁用已在处理器中处理的中断。但是,其他中断仍然可能在中途到达并抢占中断处理。因此,在同一时刻内内核堆栈中可能会有15个中断处理程序。结果,内核开发人员被迫要么显著增加内核堆栈大小,这导致内存惩罚,要么准备面对零星的内核堆栈溢出。中断门可以保证在同一时间内只有一个处理程序可以在内核堆栈上。
调用门可以存储在GDL和LDT中,并且由
CALL
和
JMP
指令调用。与陷阱门类似,但还可以从用户模式任务堆栈传递参数到内核模式任务堆栈。传递的参数数量在调用门描述符中指定。调用门从未流行起来。原因如下:
- 它们可以被陷阱门所取代(奥卡姆剃刀)。
- 它们不太可移植。其他处理器没有这些特性,这意味着在移植操作系统时支持调用门作为系统调用是一种负担,因为那些调用必须被重写。
- 由于可以在堆栈之间传递的参数数量有限,它们不太灵活。
- 从性能角度来看,它们不太优化。
在1990年代末期,英特尔和AMD引入了用于系统调用的额外指令:SYSENTER
/SYSEXIT
(英特尔)和 SYSCALL
/SYSRET
(AMD)。与调用门相比,新指令提供了性能优势,并得到了广泛应用。
总结
我不同意Michael Foukarakis的观点。很抱歉,除了影响IF
标志位外,中断和陷阱之间没有任何区别。
从理论上讲,每种类型的门都可以作为接口,指向具有任何特权级别的段。实际上,在现代操作系统中,只有中断和陷阱门被用于IDT的系统调用、中断和异常处理,并且由于这个原因,它们都作为内核入口点。
任何类型的门(包括中断、陷阱和任务)都可以通过使用INT
指令在软件中调用。唯一能够禁止用户模式代码访问特定门的特性是DPL。例如,当操作系统构建IDT时,无论具体门的类型如何,内核都会将用于硬件事件处理的门的DPL设置为0,并根据此将仅允许来自内核空间(运行在最高特权域的空间)的访问该门,但当设置系统调用的门时,它将DPL设置为3以允许从任何代码访问该门。结果,用户模式任务可以使用DPL = 3的门进行系统调用,但是如果尝试调用键盘中断处理程序,则会捕获常规保护故障。
IDT中的任何类型的门都可以被硬件调用。人们只在想要实现某些同步的情况下使用中断门来处理这些硬件事件。例如,确保内核栈溢出是不可能的。例如,我有在基于APIC的系统上使用陷阱门处理硬件中断的成功经验。
同样地,可以通过软件调用IDT中任何类型的门。使用陷阱门进行系统调用和异常的原因很简单。没有理由禁用中断。禁用中断是一件坏事,因为它会增加中断处理延迟并增加中断丢失的概率。因此,没有任何人会在手头没有严重原因的情况下禁用它们。
-
中断处理程序通常采用严格可重入的方式编写。这样,中断处理程序通常不共享数据并可以透明地抢占彼此。即使我们需要互斥地访问中断处理程序中的数据,我们也只能使用cli和sti指令来保护对共享数据的访问。没有任何理由将整个中断处理程序视为关键部分。除了希望防止基于PIC系统的内核堆栈溢出之外,没有任何理由使用中断门。
陷阱门是内核接口的默认解决方案。如果有一些严重的原因,可以使用中断门代替陷阱门。