至此,我们已经理解了X86架构如何在硬件层面如何处理中断和异常,那么接下来,我们看看Linux内核管理这些中断和异常。
同所有的设备一样,我们在使能硬件之前,必须先初始化其相关的数据结构。而Linux使用中断描述符表IDT记录管理所有的中断和异常。那么,首先,Linux内核应该把IDT的起始地址写入idtr寄存器,然后初始化所有的表项。这一步在初始化系统时完成。
因为汇编指令int
允许用户进程发送任意编号的中断(0-255)。为此,IDT的初始化必须考虑阻止由用户进程int指令引发的非法中断和异常。可以通过将中断描述符表中的DPL域设为0来实现。如果用户进程试图发送非法中断信号,CPU控制单元比较CPL和DPL的值,发出常规保护
的异常。
但是,大部分时候,用户态进程必须能够发送可编程异常。那么把相应的中断或陷阱门描述符的DPL域设为3即可。比如系统调用。
让我们看看Linux如何实现这种策略。
在之前的文章中,我们已经介绍过,Intel提供了三种类型的中断描述符:任务,中断和陷阱门描述符。Linux的分类有些不同,它们如下所示:
into
、bound
和int $0x80
三条汇编指令发出对应的中断信号。Double fault
异常的处理程序。对应上面的5种分类,分别有相应的函数可以初始化IDT(这些函数与硬件架构息息相关),如下所示:
其实,IDT被初始化两次。第一次是在BIOS程序中,此时CPU还工作在实模式下。一旦Linux启动,IDT会被搬运到RAM的受保护区域并被第二次初始化,因为Linux不会使用任何BIOS程序。
IDT结构被存储在idt_table表中,包含256项。idt_descr变量存储IDT的大小和它的地址,在系统的初始化阶段,内核用来设置idtr寄存器,专用汇编指令是lidt。
内核初始化的时候,汇编函数setup_idt()用相同的中断门填充idt_table表的所有项,都指向ignore_int()中断处理函数:
setup_idt:
lea ignore_int, %edx
movl $(__KERNEL_CS << 16), %eax
movw %dx, %ax /* = 0x0010 = cs */
movw $0x8e00, %dx /* 中断门,DPL=0 */
lea idt_table, %edi /* 加载idt表的地址到寄存器edi中 */
mov $256, %ecx
rp_sidt:
movl %eax, (%edi) /* 设置中断处理函数 */
movl %edx, 4(%edi) /* 设置段描述符 */
addl $8, %edi /* 跳转到IDT表的下一项 */
dec %ecx /* 自减 */
jne rp_sidt
ret
中断处理函数ignore_int()
,也是一个汇编语言编写的函数,相当于一个null函数,它执行:
Unknown interrupt
系统消息`。正常情况下,此时的中断处理函数ignore_int()是不应该被执行的。如果在console或者log日志中出现Unknown interrupt
的消息,说明发生硬件错误或者内核错误。
完成这次IDT表的初始化之后,内核还会进行第二次初始化,用真正的trap或中断处理函数代替刚才的null函数。一旦这两步初始化都完成,IDT表就包含具体的中断、陷阱和系统门,用以控制每个中断请求。
对于IDT表的第二次初始化过程,我们将分别以异常和中断的视角分开阐述。请参考后面的文章。
本文分享自 嵌入式ARM和Linux 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有