Linux内核19-中断描述符表IDT的初始化

简介: Linux内核19-中断描述符表IDT的初始化

至此,我们已经理解了X86架构如何在硬件层面如何处理中断和异常,那么接下来,我们看看Linux内核管理这些中断和异常。

同所有的设备一样,我们在使能硬件之前,必须先初始化其相关的数据结构。而Linux使用中断描述符表IDT记录管理所有的中断和异常。那么,首先,Linux内核应该把IDT的起始地址写入idtr寄存器,然后初始化所有的表项。这一步在初始化系统时完成。

因为汇编指令int允许用户进程发送任意编号的中断(0-255)。为此,IDT的初始化必须考虑阻止由用户进程int指令引发的非法中断和异常。可以通过将中断描述符表中的DPL域设为0来实现。如果用户进程试图发送非法中断信号,CPU控制单元比较CPL和DPL的值,发出常规保护的异常。

但是,大部分时候,用户态进程必须能够发送可编程异常。那么把相应的中断或陷阱门描述符的DPL域设为3即可。比如系统调用。

让我们看看Linux如何实现这种策略。


中断、陷阱和系统门


在之前的文章中,我们已经介绍过,Intel提供了三种类型的中断描述符:任务,中断和陷阱门描述符。Linux的分类有些不同,它们如下所示:

  1. 中断门
    和Intel的中断门相同。所有的用户进程不能访问(该门的DPL设为0)。所有Linux的中断处理程序都是通过中断门激活的,也就是说只能在内核态访问。
  2. 系统门
    属于Intel的陷阱门,可以被用户态进程访问(该门的DPL设为3)。三个Linux异常处理程序对应的中断号分别是4、5和128,分别使用intoboundint $0x80三条汇编指令发出对应的中断信号。
  3. 系统中断门
    属于Intel的中断门,用户态进程可以访问(该门的DPL域设为3)。中断号为3的异常处理程序通过系统中断门激活,可以使用在用户态使用int3指令实现。
  4. 陷阱门
    属于Intel陷阱门,不能被用户态程序访问(该门的DPL设为0)。用来访问大部分的异常处理程序。
  5. 任务门
    属于Intel任务门,用户态进程不能访问(该门的DPL设为0)。专门访问处理Double fault异常的处理程序。

对应上面的5种分类,分别有相应的函数可以初始化IDT(这些函数与硬件架构息息相关),如下所示:

  • set_intr_gate(n,addr)
    插入中断门。该门内的端选择器设为内核态代码所在的段。Offset被设为addr,就是中断处理程序的地址。DPL域设为0。
  • set_system_gate(n,addr)
    插入系统门。其余描述与上面的函数相同。
  • set_system_intr_gate(n,addr)
    插入系统中断门。该门内的端选择器设为内核态代码所在的段。Offset被设为addr,就是异常处理程序的地址。DPL域设为3。
  • set_trap_gate(n,addr)
    插入陷阱门,DPL被设为3。其余与上面函数相同。
  • set_task_gate(n,gdt)
    插入任务门。段选择器设为要执行的函数所在的段。Offset设为0,而DPL设为3。


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函数,它执行:

  1. 保存一些寄存器到堆栈中。
  2. 调用printk()函数打印Unknown interrupt系统消息`。
  3. 从堆栈中恢复寄存器的内容。
  4. 执行iret指令回到调用处。

正常情况下,此时的中断处理函数ignore_int()是不应该被执行的。如果在console或者log日志中出现Unknown interrupt的消息,说明发生硬件错误或者内核错误。

完成这次IDT表的初始化之后,内核还会进行第二次初始化,用真正的trap或中断处理函数代替刚才的null函数。一旦这两步初始化都完成,IDT表就包含具体的中断、陷阱和系统门,用以控制每个中断请求。

对于IDT表的第二次初始化过程,我们将分别以异常和中断的视角分开阐述。请参考后面的文章。

相关文章
|
20天前
|
Linux C语言
Linux内核队列queue.h
Linux内核队列queue.h
|
21天前
|
存储 Linux
linux查看系统版本、内核信息、操作系统类型版本
linux查看系统版本、内核信息、操作系统类型版本
56 9
|
1月前
|
Ubuntu Linux
linux查看系统版本及内核信息
在Linux中检查系统版本和内核信息,可使用`uname -r`查看内核版本,`uname -a`获取详细信息,或者查看`/proc/version`。要了解发行版版本,尝试`lsb_release -a`(如果安装了)或查阅`/etc/os-release`。Red Hat家族用`/etc/redhat-release`,Debian和Ubuntu系用`/etc/issue`及相关文件。不同发行版可能需不同命令。
32 3
|
1天前
|
弹性计算 网络协议 Shell
自动优化Linux 内核参数
【4月更文挑战第29天】
5 1
|
2天前
|
弹性计算 网络协议 Linux
自动优化 Linux 内核参数
【4月更文挑战第28天】
8 0
|
13天前
|
算法 Linux 调度
深入理解Linux内核的进程调度机制
【4月更文挑战第17天】在多任务操作系统中,进程调度是核心功能之一,它决定了处理机资源的分配。本文旨在剖析Linux操作系统内核的进程调度机制,详细讨论其调度策略、调度算法及实现原理,并探讨了其对系统性能的影响。通过分析CFS(完全公平调度器)和实时调度策略,揭示了Linux如何在保证响应速度与公平性之间取得平衡。文章还将评估最新的调度技术趋势,如容器化和云计算环境下的调度优化。
|
19天前
|
算法 Linux 调度
深度解析:Linux内核的进程调度机制
【4月更文挑战第12天】 在多任务操作系统如Linux中,进程调度机制是系统的核心组成部分之一,它决定了处理器资源如何分配给多个竞争的进程。本文深入探讨了Linux内核中的进程调度策略和相关算法,包括其设计哲学、实现原理及对系统性能的影响。通过分析进程调度器的工作原理,我们能够理解操作系统如何平衡效率、公平性和响应性,进而优化系统表现和用户体验。
|
26天前
|
负载均衡 算法 Linux
深度解析:Linux内核调度器的演变与优化策略
【4月更文挑战第5天】 在本文中,我们将深入探讨Linux操作系统的核心组成部分——内核调度器。文章将首先回顾Linux内核调度器的发展历程,从早期的简单轮转调度(Round Robin)到现代的完全公平调度器(Completely Fair Scheduler, CFS)。接着,分析当前CFS面临的挑战以及社区提出的各种优化方案,最后提出未来可能的发展趋势和研究方向。通过本文,读者将对Linux调度器的原理、实现及其优化有一个全面的认识。
|
26天前
|
Ubuntu Linux
Linux查看内核版本
在Linux系统中查看内核版本有多种方法:1) 使用`uname -r`命令直接显示版本号;2) 通过`cat /proc/version`查看内核详细信息;3) 利用`dmesg | grep Linux`显示内核版本行;4) 如果支持,使用`lsb_release -a`查看发行版及内核版本。
36 6
|
28天前
|
Linux 内存技术
Linux内核读取spi-nor flash sn
Linux内核读取spi-nor flash sn
20 1