女主宣言
通过阅读本文,您可以了解到:IDT是什么,它如何被初始化,什么是门,传统系统调用是如何实现的,以及硬件中断的实现。
PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!
1
如何设置IDT
IDT 中断描述符表定义
中断描述符表简单来说说是定义了发生中断/异常时,CPU按这张表中定义的行为来处理对应的中断/异常。
#define IDT_ENTRIES 256gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;
从上面我们可以知道,其包含了256项,它是一个gate_desc的数据,其下标0-256就表示中断向量,gate_desc我们在下面马上介绍。
中断描述符项定义
当中断发生,cpu获取到中断向量后,查找IDT中断描述符表得到相应的中断描述符,再根据中断描述符记录的信息来作权限判断,运行级别转换,最终调用相应的中断处理程序。这里涉及到Linux kernel的分段式内存管理,我们这里不详细展开,有兴趣的同学可以自行学习。如下简述之:
1. 我们知道CPU只认识逻辑地址,逻辑地址经分段处理转换成线性地址,线性地址经分页处理最终转换成物理地址,这样就可以从内存中读取了;
2. 逻辑地址你可以简单认为就是CPU执行代码时从CS(代码段寄存器) :IP (指令计数寄存器)中加载的代码,实际上通过CS可以得到逻辑地址的基地址,再加上IP这个相对于基地址的偏移量,就得到真正的逻辑地址;
3. CS寄存器16位,它不会包含真正的基地址,它一般被称为段选择子,包括一个index索引,指向GDT或 LDT的一项;一个指示位,指示index索引是属于GDT还是LDT; 还有CPL, 表明当前代码运行权限;
4. GDT: 全局描述符表,每一项记录着相应的段基址,段大小,段的访问权限DPL等,到这里终于可以获取到段基地址了,再加上之前IP寄存器里存放的偏移量,真正的逻辑地址就有了。
![0fd5141bfd56ae71ed289d23d767f6ac.png](https://img-blog.csdnimg.cn/img_convert/0fd5141bfd56ae71ed289d23d767f6ac.png)
我们先看中断描述符的定义:
struct gate_struct {
u16 offset_low; u16 segment; struct idt_bits bits; u16 offset_middle;#ifdef CONFIG_X86_64 u32 offset_high; u32 reserved;#endif} __attribute__((packed));
其中:
1. offset_high,offset_middle和offset_low合起来就是中断处理函数地址的偏移量;
2. segment就是相应的段选择子,根据它在GDT中查找可以最终获取到段基地址;
3. bits是该中断描述符的一些属性值:
struct idt_bits {
u16 ist : 3, zero : 5, type : 5, dpl : 2, p : 1;} __attribute__((packed));
ist表示此中断处理函数是使用pre-cpu的中断栈,还是使用IST的中断栈;
type表示所中断是何种类型,目前有以下四种:
enum {
GATE_INTERRUPT = 0xE, //中断门 GATE_TRAP = 0xF, // 陷入门 GATE_CALL = 0xC, // 调用门