建筑与设计
从保护的角度来看,x86 架构是基于分层环的,根据该环,处理器交付的所有执行空间被划分为四个分层保护域,每个保护域都分配了自己的权限级别。这种设计假设大部分时间代码将在最低特权域中执行,有时会请求来自更高特权安全域的服务,这些服务将抢占特权较低的活动到堆栈上,然后以这样的方式恢复它:对于特权较低的代码,整个抢占将是不可见的。
分级保护域的设计表明,控制不能在不同的安全域之间任意传递。
门是 x86 架构的一项功能,用于将控制权从特权较低的代码段转移到特权较高的代码段,但反之则不然。此外,在较低特权段中的控制将被传递的点可以是任意的,但在更高特权段中的控制将被传递的点是严格指定的。只有通过IRET
指令才允许向后控制传递到特权较少的段。在这方面,英特尔软件开发人员手册声称:
较低权限段中的代码模块只能通过称为门的严格控制和保护的接口访问在较高权限段上运行的模块。尝试在不通过保护门且没有足够访问权限的情况下访问更高特权段会导致生成一般保护异常 ( #GP
)。
换句话说,门是具有所需访问权限和目标地址的特权域入口点。这样,所有门都是相似的,并且用于几乎相同的目的,并且所有门描述符都包含 DPL 字段,处理器用于控制访问权限。但请注意,仅当调用源是软件、或指令时,处理器才会检查门的 DPL CALL
,JMP
并INT
在调用源是硬件时绕过此检查。
门的类型
尽管所有门都相似,但它们还是有一些区别,因为最初英特尔工程师认为不同的门将用于不同的目的。
任务门
任务门只能存储在 IDT 和 GDT 中并由INT
指令调用。这是一种非常特殊的门,与其他门有很大不同。
最初,英特尔工程师认为他们将通过提供基于 CPU 的任务切换功能来彻底改变多任务处理。他们引入了TSS(Task State Segment),它保存了任务的寄存器状态,可用于硬件任务切换。触发硬件任务切换有两种方式:使用TSS本身和使用Task Gate。要进行硬件任务切换,您可以使用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 年代末,Intel 和 AMD 为系统调用引入了额外的指令:SYSENTER
/ SYSEXIT
(Intel)和SYSCALL
/ SYSRET
(AMD)。与调用门相比,新指令提供了性能优势并已被采用。
概括
我不同意迈克尔·福卡拉基斯的观点。抱歉,除了影响IF
标志外,中断和陷阱之间没有任何区别。
理论上,每种类型的门都可以用作指向具有任何级别权限的段的接口。在实践中,在现代操作系统中仅使用中断和陷阱门,在 IDT 中用于系统调用、中断和异常处理,因此它们都用作内核入口点。
任何类型的门(包括中断、陷阱和任务)都可以通过使用INT
指令在软件中调用。唯一可以禁止用户模式代码访问特定门的功能是 DPL。例如,当操作系统构建 IDT 时,无论特定门的类型如何,将用于硬件事件处理的门的内核设置 DPL 为 0,并且根据此权限,仅允许从内核空间访问此门(最多运行特权域),但是当它为系统调用设置门时,它将 DPL 设置为 3 以允许从任何代码访问该门。结果,用户模式任务能够使用 DPL = 3 的门进行系统调用,但会在尝试调用键盘中断处理程序时捕获一般保护错误。
IDT 中的任何类型的门都可以由硬件调用。人们仅在他们想要实现某些同步的情况下才使用中断门来处理此硬件事件。例如确保内核堆栈溢出是不可能的。例如,我在基于 APIC 的系统上成功地使用陷阱门处理硬件中断。
类似地,IDT 中任何类型的门都可以在软件中调用。对系统调用和异常使用陷阱门的原因很简单。没有任何理由禁用中断。中断禁用是一件坏事,因为它会增加中断处理延迟并增加中断丢失的可能性。因此,没有任何严重的理由,没有人不会禁用它们。
中断处理程序通常以严格的可重入风格编写。这样,中断处理程序通常不共享数据,并且可以透明地相互抢占。即使我们需要在中断处理程序中相互排除对数据的并发访问,我们也可以使用 cli 和 sti 指令仅保护对共享数据的访问。没有任何理由将整个中断处理程序视为关键部分。没有任何理由使用中断门,除了希望防止在基于 PIC 的系统上可能发生的内核堆栈溢出。
陷阱门是内核接口的默认解决方案。如果有一些严重的原因,可以使用中断门代替陷阱门。