结合源代码分析一个完整的中断过程

时间:2022-11-05 17:51:41

  转载请注明出处:http://blog.csdn.net/rosetta  

结合源代码分析一个完整的中断过程

  此文详细描述了中断产生到中断处理程序执行、中断处理程序返回以及中断描述符初始化整个过程,结合linux-0.00源代码片断学习将会更直观易懂。

   中断是指当前执行程序或任务在执行到某处时出现一个事件,该事件需要程序处理。这种事件会导致程序会从当前运行程序转移到被称为中断(或异常)处理程序中执行。中断可以由硬件发生,也可以由软件调用int n指令产生。异常发生在CPU执行一条指令时,检测到一个出错条件时发生,例如被0除出错。

       那么中断产生时,当CPU去执行中断处理程序之前如何保存当前程序的信息?需要保存的信息有哪些,以便在处理完中断处理程序后恢复执行之前的程序?再者CPU怎么去找中断处理程序的?

       下面以int n产生中断为例,以linux-0.00中的head.s源码为实例,并使用bochs单步调试,理论联系实际,分析从中断产生到执行并返回整个过程。   

       Boch调试环境及linux-0.00运行环境搭建可参考《Linux-0.00运行环境搭建》

       从head.s如下代码处开始分析。完整的代码可参考《一个简单多任务内核实例的分析 》

236 task0:

237 movl $0x17, %eax

238 movw %ax, %ds

239 movb $65, %al /*print 'A' 65*/

240 int $0x80

241 movl $0xfff, %ecx

242 1: loop 1b

243 jmp task0

       运行bochs 调试脚本,按以下方法找到task0的地址,因为head程序在物理地址0x00处,所以使用objdump显示的偏移地址+段地址0x00还是其本身,即最终的断点位置。

       在源代码目录执行:

[root@xxx linux-0.00-rh9]# objdump -d -j.text -t ./head.o|grep task0

000010e0 l .text 00000000 task0

       所以在bochs中直接下断到0x10e0处。

结合源代码分析一个完整的中断过程

  在执行int 0x80前先停下观察cpu相应寄存器及栈的数据。

结合源代码分析一个完整的中断过程

  栈中状态:

结合源代码分析一个完整的中断过程

  可以看到此时的cs=0x000f(62行iret返回后设置的cs值),eip=0x10e9(当前即将执行的int 0x80代码),如果这里不产生中断的话下一条代码将是执行0x10eb,但这里使用int 0x80 产生了一个中断,从而去调用IDT中断描述符表中索引为0x80(第128个)的描述符。

       使用跟进函数指令s跟进中断处理程序并显示当前cpu、栈的情况。

结合源代码分析一个完整的中断过程


结合源代码分析一个完整的中断过程

   寄存器中主要变化的是cs,eip,ss,esp,而栈的变化也很大,那么他们是如何变化的呢?

  1. eip=0x10e9 => 0x0166

       eip的变化很好理解,调用int0x80之前的指令0x10e9处跳到了现在的0x0166处,之前指令的后一条将要执行的指令地址0x10eb存在了栈中(用于中断返回)。

  2.  cs=0x000f => 0x0008

  那么cs为何变成了0x0008呢?注意此时是保护模式下,所以0x0008其实为段选择符。

  因为此时已经跳转到了IDT中第0x80个描述符,那么0x80处的描述符长何样?

36 # setup timer & system call interruptdescriptors.

37 movl $0x00080000, %eax

38 movw $timer_interrupt, %ax

39 movw $0x8E00, %dx

40 movl $0x08, %ecx # The PC default timer int.

41 lea idt(,%ecx,8), %esi

42 movl %eax,(%esi)

43 movl %edx,4(%esi)

44 movw$system_interrupt, %ax

45 movw $0xef00, %dx

46 movl $0x80, %ecx

47 lea idt(,%ecx,8), %esi

48 movl %eax,(%esi)

49 movl %edx,4(%esi)

       如上代码,因为IDT中的每一项中断描述符都为8字节,以上第37行,44~49行就是设置IDT表中第0x80个位置处的中断描述符,其内容:eax为低4字节,其中eax的高16位为段选择符0x0008(即刚才cs的变化,由0x000f => 0x0008),eax的低16位为中断处理程序system_interrupt偏移地址;dx为中断描述符类型及DPL等,此时的中断描述符类型为0xf=1111为陷阱门描述符。

  3.ss=0x0017=> 0x0010, esp=0x0bd8 => 0xe4c

  代码中的tss0内容为:

205tss0:   .long 0             /* back link */

206 .long krn_stk0, 0x10 /* esp0, ss0 */

207 .long 0, 0, 0, 0, 0 /* esp1, ss1, esp2, ss2, cr3 */

208 .long 0, 0, 0, 0, 0 /* eip, eflags, eax, ecx, edx */

209 .long 0, 0, 0, 0, 0 /* ebx esp, ebp, esi, edi */

210 .long 0, 0, 0, 0, 0, 0 /* es, cs, ss, ds, fs, gs */

211 .long LDT0_SEL, 0x8000000 /* ldt, trace bitmap */

212

213 .fill 128,4,0

214 krn_stk0:

215 # .long 0

  使用objdump查看head.o中的krn_stak0地址:
[root@plmlinux-0.00-rh9]# objdump -d -j .text -t ./head.o|grep krn_stk0

00000e60 l .text 00000000krn_stk0

00000e60<krn_stk0>:

       4.栈的变化

  ss的值加载了tss任务段描述符中的ss0字段,为0x10;esp也是加载其中的esp0字段,所以应该是0xe60,但是因为该栈已经被使用过了,所以导致esp减至0xe4c。

       栈中从0xe60开始至0xe4c处都是什么内容?此时看下system_interrupt是如何返回的?

152 .align 2

153system_interrupt:

154 push %ds

155 pushl %edx

156 pushl %ecx

157 pushl %ebx

158 pushl %eax

159 movl $0x10, %edx

160 mov %dx, %ds

161 call write_char

162 popl %eax

163 popl %ebx

164 popl %ecx

165 popl %edx

166 pop %ds

167 iret

       发现其最后是iret,而执行此指令会导致:

eip <= ss:esp  esp=esp+4 ; eip <= 0x10eb

cs <= ss:esp esp=esp+4; cs <= 0x000f

eflags <= ss:esp esp=esp+4; eflags <= 246

esp <= ss:esp esp=esp+4; esp <= 0x0bd8

ss <= ss:esp esp=esp+4; ss <= 0x0017

   所以,栈中保存的内容都是中断前的信息,以便返回时使用。

   System_interrupt中断处理程序调用iret返回后将继续执行int 0x80后面的指令cs:eip=0x00f:0x10eb。

  此文仅描述中断方面的知识,对于任务task的切换和加载、tss任务段描述符未做说明,此部分内容将在下一篇文章中记录。

参考文献:

Linux内核完全剖析》赵炯