最近想研究下Linux下的时钟中断,因为时钟中断算是一个操作系统下最频繁的中断事件了吧(个人认为)。
以4.5 x86_64 Linux内核为例。
面对庞大的代码量,无从下手啊。不如从中断号看起吧Linux 源码中有这样的定义(arch/x86/include/asm/irq_vectors.h):
#define LOCAL_TIMER_VECTOR 0xef
如果没猜错的话,应该就是Linux下的时钟中断向量了(0xEF=239)。为了保险起见,验证一下吧,不过该怎么验证呢?参考CPU硬件的中断处理过程,可按如下方法找到239号中断的处理函数入口地址:
1)先通过idtr寄存器,找到IDT(中断描述符表)的地址(线性地址),然后读取该描述符表的第239个entry。
idtr和中断描述符表IDT中entry的格式分别如下:
IDTR
Offset | Size | Description |
0 | 2 | Limit - Maximum addressable byte in table |
2 | 8 | Offset - Linear (paged) base address of IDT |
Offset | Size | Description |
0 | 2 | Offset low bits (0..15) |
2 | 2 | Selector (Code segment selector) |
4 | 1 | Zero |
5 | 1 | Type and Attributes (same as before) |
6 | 2 | Offset middle bits (16..31) |
8 | 4 | Offset high bits (32..63) |
12 | 4 | Zero |
2)从 IDT Descriptor 中提取出 Segment selector和Offset。
3)根据gdtr寄存器找到GDT的地址,再结合第2步中的段选择符,找到相应的段描述符。
4)从段描述符中提取基地址,再结合第2步中的Offset,便得到中断处理函数入口的线性地址。
需要注意的是,在64位模式中,AMD64的技术手册上有这样的描述:
Segmentation is disabled in 64-bit mode, and code segments span all of virtual memory. In this mode, code-segment base addresses are ignored. For the purpose of
virtual-address calculations, the base address is treated as if it has a value of zero.
原来,在64位系统中,早就不使用代码段和数据段的概念了(不过有些段还是在用的,例如TSS段),逻辑地址直接等于线性地址。因此以上步骤中的3、 4都是不必要的。只要从IDT descriptor中提取出 Offset,这便是中断处理函数的入口地址了(线性地址)。
下面来看看实际是怎么操作的吧:
1)读取idtr寄存器。额。。。得需要内嵌汇编了,本人不是很熟,写了下面很ugly的几句代码:
#include <stdio.h>
struct idtr
{
unsigned char byte[10];
};
int main(int argc, char* argv[])
{
struct idtr idtr;
int i;
__asm__ __volatile__ ("SIDT %0" : "=m"(idtr) );
for (i = 0; i < 10; i++)
printf("byte %02d: 0x%hhx\n", i, idtr.byte[i]);
return 0;
}
结果如下: