Linux Kernel之 IDT

@vrqq  March 4, 2018
看了那么多介绍,只怪自己英文太渣,中文的又强行翻译,很是难过。
所以莫慌,我看了好多还是觉得乱了吧唧的,现在按照此文重新来过吧。

整个文章均以intel x86-64 (i386)的CPU举例,先来看一下Linux的中断系统。
在没有Kernel之前,这个cpu就是高级的单片机,而这个单片机的datasheet写的就是bios使用说明。
然后我们现在有了Linux,这个单片机变得高贵了起来。

CPU做单片机使用时,我们要操作它的寄存器。
假设此时bios已经初始化完成了它和外设的连接,即:cpu可以访问内存了,cpu可以在显示器上显示东西了,总之cpu可以调动主板上的一切东西。
(先看一个名词叫:Privilege Level我们暂且跳过他,对全文理解无影响。)

Interrupt Descriptor Table (IDT)

叫中断描述表,这个表存在内存上。
这个表里面最多能存256条信息,每一条称为一个entry,也称为一条interrupt vector(中断向量)
(对于64位系统)每条信息长度128bit (128/8 = 16 Bytes),因此整个表占了内存16*256 = 4kB这么大。

对于cpu而言,我要提前知道这个表存在了哪里,因此cpu有个专门的寄存器IDTR用来存IDT这个表的{开头位置,大小}。
IDTR寄存器挺长的,分成一大一小两部分,前半部分存“IDT开头地址”,后半部分存“IDT有多长”。
\t\\例如:第80条vector在内存里面的地址 = IDTR.base + 8016 详情可用下面代码测试一下*

Linux启动时会在内存里开一块空间重新建Interrupt Descriptor Table,然后使用asm(LIDT)指令修改cpu寄存器IDTR的值,这才从bios过渡到了Linux。
Interrupt Descriptor Table里面的每一条称为entry/vector,一共有4种不同类型的vector,分别叫:

  • Call gates
  • Trap gates (属于一种特殊的Call gates)
  • Interrupt gates (亦属于一种特殊的Call gates)
  • Task gates
    这个就是许多翻译中提到的“任务门”/“中断门”/“陷阱门”,忘记中文吧我们现在只知道他们叫Gate。

这几种Gate的格式不一样!
举个例子 "interrupt gate" (type = 14 or 6):

  • 无论是asm(int)产生的,还是cpu做了“100除以0”,抑或是我们敲击键盘,step1 它产生了。
  • step2 这些interrupt通过层层电路传达给cpu,但在cpu看来没什么区别,都叫interrupt。传到cpu时候带着vectorID,然后cpu查IDT表找到这条vector,看属于哪种Gate再决定怎么处理。
  • step3 cpu查到这条vector是interrupt gate类型的,那么cpu执行:关中断(IF-bit in EFLAGS),然后调vector里面存的handler function。
  • handler function 是我们自己写的代码,finally 跑完调用asm(IRET)结束此次中断处理。
其实这么来看,起名gate很机制啊,cpu作为硬件电路,提前预留了几个gate,然后中断来了,cpu指点它穿过gate,交给我们写的代码处理。

【扩展】《Intel® 64 and IA-32 Architectures Software Developer’s Manual》 - Volume 3A - 5.8.2 Gate Descriptors : https://www.intel.cn/content/www/cn/zh/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html

【必看】更多详情请移步阅读:注意64位cpu参考IA-32e https://wiki.osdev.org/Interrupt_Descriptor_Table
以及https://stackoverflow.com/questions/3425085/the-difference-between-call-gate-interrupt-gate-trap-gate

谨记,永远都是先由cpu处理中断,然后cpu再把中断分发给我们写的处理函数。而不同的Gate是给cpu看的,跟我们的处理函数没关。

【扩展】我们在没有linux时候,bios下中断能干啥:http://blog.csdn.net/pdcxs007/article/details/43378229
【扩展】在多个cpu时,每个CPU都有IDTR寄存器,但是这些CPU的IDTR都指向同一个地址。即:无论多少cpu,linux下都只有一张Interrupt Descriptor Table(中断表)。 http://blog.chinaunix.net/uid-27717694-id-4055450.html

然后我们写个module把Interrupt Descriptor Table打出来

这些是关键代码,建议尝试一下,建个module丢进去就好。Kernel保护系统很到位,所以我们只能在Module里面动手一试了!
API参照:https://elixir.bootlin.com/linux/latest/source/arch/x86/include/asm/desc_defs.h#L69
以及参照上述intel手册中Figure 6-7. 64-Bit IDT Gate Descriptors

#include <asm/desc.h>
#include <asm/desc_defs.h>

unsigned long i;
int vectorID=0;
gate_desc *eachVector;
struct desc_ptr pldt;
asm volatile("sidt %0" : "=m" (pldt) );
printk("IDT starting at 0x%lx, IDT length=0x%x.\neach vector size = %ld Bytes\n", pldt.address, pldt.size, sizeof(ldt_desc));
for ( i=pldt.address; i<pldt.address+pldt.size; i+=sizeof(ldt_desc) ) {
    eachVector = (gate_desc*)i;
    printk("vector %2d, segment=0x%x, offset=0x%08x 0x%04x 0x%04x  {IST=%d, GateType=%d, Privilege=%d} Valid(P)=%d.\n", 
            vectorID++, eachVector->segment, 
            eachVector->offset_high, eachVector->offset_middle, eachVector->offset_low,
            eachVector->bits.ist, eachVector->bits.type, eachVector->bits.dpl, eachVector->bits.p);
}

我的cpu intel core i7-2820,运行在64位模式下,可以看到:
idt.jpeg
发现,在Linux 4.x里面,IDT里面一切vector都使用interrupt gate类型?别再是打错表了吧囧。。。。。
转念一想,linux 4.x里不允许“中断处理”嵌套了(包括软件模拟的嵌套也不让),所谓“嵌套”(re-entrant)指的当前这个interrupt正处理着呢,又来一个中断,kernel把当前这个中断放下转手去处理新的,然后呢新的处理完返回后,接着运行上个干着一半的。
所以初始化时候应该都用interrupt gate吧,下文引自《Intel® 64 and IA-32手册》卷3

Because IA-32 architecture tasks are not re-entrant, an interrupt-handler task must disable interrupts between the time it completes handling the interrupt and the time it executes the IRET instruction.
A TSS allows only one context to be saved for a task; therefore, once a task is called (dispatched), a recursive (or re-entrant) call to the task would cause the current state of the task to be lost.
这样就明白了,cpu在硬件实现上不支持嵌套,如果我们非要软件模拟嵌套(re-entrant),也许可以这样做:
还是在IDT表里写interrupt gate,然后在处理函数里写:保存寄存器->开中断->真正的处理函数->关中断->还原寄存器。在操作开关中断时候要上锁吧,那么问题来了,要看是怎么实现锁的,锁莫非就是关调度器,关中断?值得深思。。。

也不知进入protect mode之前,有用trap gate的没??只见在kernel启动时候non-early interrupt init部分大量使用set_intr_gate。。

上面输出的IST(Interrupt Stack Table):是x86-64模式下的新玩意,在x86-64这个模式下,有最多7个process候着等着处理interrupt。那么处理过程中难免要开变量啊,传参啊,保存状态啊什么的,那么这个IST[i]就是给第i个process留的风水宝地啦。

总之吧,现在cpu越来越厉害,硬件能干的越来越多,kernel以后要做也许的会越来越少吧。。。。。。
本章完。

Interrupt Gate详解

上文说到了Linux在IDT里面用的全是interrupt gate,这个省心,cpu负责enable/disable interrupt,防止处理到一半有新来的中断搅合。


添加新评论