x86处理器中的特权级检查及Linux系统调用的实现

该部分内容需要仔细阅读Intel或者AMD的相关文档,Intel文档名为《Intel 64 and IA-32 Architectures Software Developer's Manual》, AMD的文档名是《AMD64 Architecture Programmer's Manual》,这些文档在intel和amd的官方网站上都可以下到,据我了解intel甚至提供纸质的文档供开发者使用(需要自己提交申请)。 这些文档基本上围绕x86的系统编程展开,包括x86的指令集,一般情况下大家很少会自己去写一个操作系统,那么大部分情况下阅读这些文档是为了理解Linux内核,所有关于x86系统编程方面的文档都不会超出上面提到的这两篇。个人的建议是IA32看Intel的文档,64位的看AMD64文档。

关于x86 64位的历史这里也简单提一下,当年AMD提出64位概念时,是要兼容以前的32位x86处理器的,而当时intel正忙于安腾处理器,这是个全新的架构,也就是现在说的IA64(Intel Architecture 64),和x86处理器完全不兼容。就技术本身而言,很难说安腾是个失败的产品,但是最终还是市场说了算,因为x86 32位多年历史的积累,上面运行着无数的应用程序,而这些程序将无法在安腾(Itanium)处理器上运行,导致了IA64无法在市场上获得成功, intel曾经有team专门在二进制级别上将x86 32的指令翻译成IA64指令,希望在市场全面转向IA64时作为一个兼容x86的临时解决方案。而AMD64处理器因为兼容32位而最终被市场所接受。历史上AMD的股价在05年底06年初时曾达到40多美元,那段时间对intel来说是比较郁闷的,直到后来core架构的出现。客观地说AMD在其发展历史上曾有过很多不错的技术与创新,可惜差不多都昙花一现,其中原因很多,我想外部因素主要应该有以下两方面,一方面是因为其对手Intel在x86领域深厚的技术沉淀与积累,另一方面是Intel具有强大的资金资源与市场影响力。很多时候AMD弄出来的东西Intel会明确表态不予支持,那么这个东西基本上就不会有多少市场(MSFT OS都不支持了,Application会去支持吗?!),搞得AMD不但必须得推倒自己的东西,而且还要反过来支持intel的,因为无论技术如何,最终还是由市场说了算。现在我们提x86 64位,比较中立的说法是x86 64,这估计也是intel乐于见到的,因为在x86 64位上,实际是AMD首先实现的,所以AMD将64位x86称之为AMD64. 有点扯远了...

下面回到本帖的主题,在x86处理器上如何进行代码特权级的检查。我将一些比较容易引起混淆的概念整理如下:

1. 对于数据段访问的权限检查:
在处理器把要访问的数据段选择子加载到相应的段寄存器(DS, ES, SS, FS, GS)之前,处理器会先做个权限检查。它会比较CPL,要加载的数据段选择子的RPL以及选择子所选择的数据段描述符中的DPL.只有当下列条件满足时,才可以访问相应的数 据段:
DPL >= CPL && DPL >= RPL. 否则就产生一个通用保护异常,这种情况下段寄存器并不会被加载。

2. 堆栈段作为一种特殊的数据段,其权限检查比较特别:只有当CPL=RPL=DPL时,堆栈段才允许被访问。

3. 对于代码间的转移,有几种不同的情况:
a)Far形式的JMP, CALL, RET指令后跟的目标代码段的选择子(该selector中的低2位代表RPL,当前CS寄存器的低2位代表CPL)
这里又分两种情况,非一致代码段和一致代码段:
Nonconforming Code Segment --这种情况要求CPL一定要等于DPL,其次RPL<=DPL.如果成功转移到了目标代码段,CPL保持不变,即跳转前后无特权级变化。
Conforming Code Segment --这种情况要求CPL >= DPL,RPL则无需检查。转移后特权级也不变化。因为转移前后CPL没有变化,所以也没有堆栈切换发生。
绝大多数代码段都是非一致代码段,这种情况下(Far JMP, CALL, RET CS_Selector)不会引起特权级变化。
一致代码段还是非一致代码段由目标代码段所属描述符的Type字段决定。

b)如果想在不同特权级代码间穿越,则必须使用门这种机制。x86处理器提供四种门:
调用门 陷进门 中断门 任务门
任务门用来做任务切换,陷阱门和中断门是一种特殊的调用门,用来呼叫陷阱和中断处理函数。

调用门 -- 通过调用门除了可以在不同特权级代码间转换外,还可以在16-bit和32-bit的代码间实现穿越。调用们描述符只存在于GDT或者
LDT,不在IDT中。用Far CALL/JMP gate_selector, offset就可以通过调用门实现代码的穿越。指令中的offset虽然需要,但是
处理器不会使用,应为被调用的代码段入口点将由调用们描述符中的offset来替换。
在权限检查方面,CALL指令与JMP指令稍有区别。但是它们都会检查以下四个部分:CPL, RPL, 调用门的DPL和目标代码段描述符的
DPL.

CALL和JMP要想访问调用门,首先必须确保both CPL and RPL <= DPL of the call gate. 其次对于非一致的目标代码段的描述符
中的DPL(简写为DDPL, Destination DPL),对于CALL指令要求:DDPL <= CPL,对于JMP指令,要求DDPL=CPL.所以,对于非一致目标
代码段而言,CALL指令可以切换到更高的优先级,而JMP则不可以。一旦CALL指令成功切换到更高优先级代码段,CPL=DDPL并伴有
堆栈的切换。

===============================分割线=============================

接下来在上面总结的基础上讨论一下Linux内核中关于系统调用与外部硬件中断的特权级检查问题。
x86下的系统调用和中断处理最终都是通过中断门描述符表IDT,系统调用以int $0x80的形式出现。现在来看看系统调用是如何将处理器的特权级从ring 3转到ring 0。

系统调用在IDT中的初始化发生在trap_init函数中:

  1. <arch/x86/kernel/traps.c>
  2. void __init trap_init(void){
  3. ...
  4. #ifdef CONFIG_X86_32
  5. set_system_trap_gate(SYSCALL_VECTOR, &system_call);
  6. set_bit(SYSCALL_VECTOR, used_vectors);
  7. #endif
  8. ...
  9. }

  10. < arch/x86/include/asm/desc.h>
  11. static inline void set_system_trap_gate(unsigned int n, void *addr)
  12. {
  13. BUG_ON((unsigned)> 0xFF);
  14. _set_gate(n, GATE_TRAP, addr, 0x3, 0, __KERNEL_CS);
  15. }


set_system_trap_gate函数用来对IDT中第0x80项所对应的描述符做初始化:类型为TRAP(所以Linux下的系统调用在x86处理器中属于trap类型, trap属于x86异常中的一种,在trap发生时,处理器会将引起trap的下条指令的cs, eip保存到内核栈中),addr所对应的实参为systgem_call,加上最后的一个要转移的代码段选择子__KERNEL_CS,所以很清楚当int $0x80指令执行时,代码将试图向__KERNEL_CS: system_call处转移。_set_gate函数中的ist在x86 32下用不着,只出现在64位上。最后一个与特权级密切相关的是0x3,它是0x80所对应IDT中描述符的DPL, int n指令在保护模式下需要进行特权检查(可以参考intel volume 2A中的int指令微码逻辑),在用户空间发起系统调用时CPL=3, 在内核空间发起系统调用时CPL=0,都满足CPL <= DPL的要求,所以代码将跳转至__KERNEL_CS: system_call,因为__KERNEL_CS中的RPL=0,所以系统调用使得转移后的代码运行在ring 0上。

关于外部中断的IDT描述符,在Linux内核中由set_intr_gate负责设定,其中DPL指定为0,这意味着如果在用户空间使用int n指令,因为CPL>DPL,所以会有通用保护异常发生,这是为了防止在用户空间有恶意程序用int指令来模拟硬件中断。但是硬件中断发生时,被中断的程序或者运行在用户态(CPL=3),或者运行在内核态(CPL=0),对于这种外部中断的发生,处理器不会对其做特权级检查,所以外部硬件中断发生时,总是会转向__KERNEL_CS:general_intr_handler, 其中general_intr_handler是中断处理函数。

(原文首发http://www.embexperts.com/forum.php/forum.php?mod=viewthread&tid=538&extra=page%3D1)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值