Linux中断详解


这里以linux-kernel 0.11版本为基础整理中断相关知识,目的在于对于中断有一个全面、清晰和简洁的认识

1、Linux的中断类型

Linux的各种中断都是由系统负责统一处理的。在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理函数或中断服务例程。CPU执行完一条指令后,下一条指令的逻辑地址会被放在相应的寄存器中(CS和EIP),在执行新指令之前,系统会检查是否有中断产生(有相应的寄存器来表示状态),如果有,就对中断进行处理。Linux处理中断,大致可分为如下的几步:

(1)、保存正在执行的进程的上下文,便于中断处理返回后能恢复进程的执行

(2)、对中断进行解析,确定产生中断的中断源,识别中断的类型。但系统接受中断时,能够从物理硬件中获得一个关于中断的数

,用这个数去做查表操作,能获得关于这个中断类型、中断处理程序等相关信息

(3)、内核调用由第二步获得的中断处理程序,对中断进行处理

(4)、中断处理程序执行完对中断的处理后返回。恢复之前被中断的进程的上下文,或者根据需要调度优先级更高的进程去执行

中断信号可分为两类:硬件中断和软件中断,软件中断一般被称为异常。Intel x86公有256个中断,每个中断都有一个0~255之间的数来表示,Intel将前32个中断号(0~31)已经固定设定好或者保留未用。中断号32~255分配给操作系统和应用程序使用。在Linux中,中断号32~47对应于一个硬件芯片的16个中断请求信号,这16个中断包括时钟、键盘、软盘、数学协处理器、硬盘等硬件的中断。系统调用设为中断号128,即0x80。

2、中断描述表

获取中断号(或称为中断向量)的过程涉及到太多的硬件过程,这里不做介绍。系统获得中断向量之后,要获得相应的处理程序,需要去做一个查表的操作,这里所查询的这个表,就是中断描述表。

中断描述表是存储系统中断向量对应中断处理程序的表。Intel x86中共有256个中断,因此中断描述表中也有256项,每一项对应一个中断。系统以中断向量作为索引从表中取出相应的中断服务例程。

[中断描述表在内存中断的位置]

中断描述表可以在内存中的任意位置,它的具体地址保存在IDTR寄存器中。

[中断描述表的初始化]

在Linux内核版本0.11中,中断描述表的初始化分为两个部分。第一部分是在内核引导启动部分,在head.s中实现。这里的效果是将中断描述符表(用IDT表示),设置成具有256项,并且都指向ignore_int的中断门。然后将IDT的地址加载到IDTR寄存器中。因此,这里的初始化只是把所有中断的服务例程都设置成了一个哑的中断服务程序,即不做任何中断的处理。第一部分的初始化代码(AT&T的汇编语言格式)很短,这里摘抄如下。setup_idt子程序在head.s的第24行被调用

setup_idt:

 

lea ignore_idt, %edx #获取ignore_ldt的有效地址,并且把结果保存在edx寄存器中

 

movl $0x0080000, %eax #eax寄存器中的高16位变成0x0008。用来做段选择符

 

movw %dx,%ax #ax寄存器中值现在变成ignore_idt的有效地址的低16位,ax是eax寄存器的低半部分 。那么现在eax寄存器的高

 

#16位的值是段选择符0x0008,低16位是ignore_idt的偏移地址。简单理解可以用来找到ignore_idt程序即可

 

movw $0x8E00, %dx #设置中断描述符表中断门的数据

 

lea _idt, %edi #_idt是中断描述符表的地址,将中断描述符表的地址存放在edi寄存器中

 

mov $256,%ecx #循环使用

 

rp_sidt:

 

movl %eax , (%edi) #edi指向的是中断描述符表,而eax中保存的是可以用来找到中断服务例程ignore_idt的地址,所以这里的效果是把

 

#用于找到ignore_idt的数据存入edi寄存器中地址指向的内存中

 

movl %edx, 4(%edi) #中断描述符表的每一项都是64位(中断描述符表中每一项的数据结构后续会将)

 

addl $8, %edi #指向下一项

 

dec %ecx #循环计数器减一

 

jne rp_sidt #jne的意思是如果不相等就跳到rp_sidt子程序去。jne使用的是ZF标志位。ZF=0就执行下一条指令,

 

#ZF=1就跳转。而dec %ecx会影响这个标志位。只有当ecx寄存器中的值减为0的时候,才会使得ZF=0

 

# 所以,这里实现的效果就是会使得这段程序执行256次。将中断描述表中的每一项都填入相同的内容

 

lidt idt_descr #使用lidt指令将中断描述表的地址加载到IDTR寄存器中

 

ret

 

 

 

经过第一部分的操作,中断描述表已经得到初始化。但其中的数据,是没有意义的,无法服务任何中断。为了使中断描述表中的数据真正有意义,还需要往中断描述表中对应项插入合适的数据。这第二部分的过程是在各个驱动初始化的时候完成的。这里以时钟中断为例,来解析数据插入过程。在这之前可以先简要介绍下其它中断的初始化,0~16号中断的初始化在kernel/traps.c的trap_init函数中实现(181行)。trap_init函数的调用则是在main.c的main函数中调用的。

 

对于时钟中断来说,它往IDT中真正写入中断处理例程的实现代码是sched.c文件的406行中。相关的部分代码如下所示

 

/* 下面的汇编语句是清楚NT标志,NT表示是用来表示是否有程序递归调用的标志Nested Task。但NT标志为1时,那么当前中断任务执行iret指令时就会引起任务切换。NT标志指出了在TSS中的back_link字段是否有效 */

 

__asm__( "pushfl ; andl $0xffffbfff,(%esp) ; popfl" );

 

ltr(0); //将任务0的TSS加载到任务寄存器tr中

 

lldt(0); //加载局部描述符表到局部描述符表寄存器

 

//下面是设置8253定时器

 

outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */

 

outb_p(LATCH & 0xff , 0x40); /* LSB */

 

outb(LATCH >> 8 , 0x40); /* MSB */

 

set_intr_gate(0x20,&timer_interrupt); //设置时钟中断的中断处理程序

 

outb(inb_p(0x21)&~0x01,0x21);

 

set_system_gate(0x80,&system_call);

 

这段代码是在sched.c的sched_init函数中,sched_init函数的调用是在main.c的main函数中调用的。到这里,就完整地实现了在中断描述表中设置时钟中断的工作 [中断描述表的数据格式] 从上面对时钟中断的设置中来看,使用的是set_intr_gate来进行设定的,这是一个宏。类似的,还有两个宏来实现对中断描述表的写操作,分别是set_trap_gate和set_system_gate。对set_intr_gate宏的定义如下 #define set_intr_gate(n,addr) \ _set_gate(&idt[n],14,0,addr) 使用这个宏,需要传递两个参数,一个是中断号,一个是中断处理函数。在set_intr_gate宏中又调用了_set_gate宏。另外的两个宏,也是调用_set_gate宏来完成工作的。因此从_set_gate宏中可以了解中断描述表中每一项的数据结构。_set_gate宏的定义如下 gate_addr描述符的地址,type描述中断类型,dpl描述中断处理的特权级别,addr中断处理程序的地址。 #define _set_gate(gate_addr,type,dpl,addr) \ __asm__ ("movw %%dx,%%ax\n\t" \ //将偏移地址低字与选择符组合成描述符低4直接(eax) "movw %0,%%dx\n\t" \ //将类型标志字与偏移高字组合成描述符高4字节 (edx) "movl %%eax,%1\n\t" \ //设置描述符的低4字节 "movl %%edx,%2" \ //设置描述符的高4字节 : \ : "i" ((short ) (0x8000+(dpl<<13)+(type<<8))), \ "o" (*((char *) (gate_addr))), \ "o" (*(4+(char *) (gate_addr))), \ //每一个描述符占64位,获取高32位地址,即高4字节地址 "d" ((char *) (addr)),"a" (0x00080000)) 这种嵌入了汇编语句,GUN gcc手册中描述的嵌入汇编的基本格式为 asm("汇编语句" :输出寄存器 :输入寄存器 :会被修改的寄存器); %0,%1……等是使用参数的编号,从输出寄存器开始,从左往右,从上往下。那么%0表示的则是有0x8000+(dpl<<13)+(type<<8)的值。我们分析这个值,dpl左移13位,这刚好是下图中的DPL的部分(左边32开始算起的左移),type左移8位,这对应的是9,10,和11位的三位类型码。0x8000表示成二进制就是1000 0000 0000 0000,刚好是把P标志位设置成1。 仅仅从这段汇编上不容易理解具体的数据结构,下面用图来进行说明  
内核中关于IDT相应的定义如下(head.h) typedefstruct desc_struct { unsignedlong a,b; } desc_table[256]; extern desc_table idt,gdt;
  • 1
  • 2
  • 下一页

相关内容