我是靠谱客的博主 踏实刺猬,最近开发中收集的这篇文章主要介绍实现8086虚拟机(六)——中断系统,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

    • 基础知识
    • 实现中断机制
    • 实现单步中断
    • 结束语

这个系列的最后一篇文章讲述如何实现中断系统,如何实现内置的 1 号中断——单步中断。

基础知识

内容来自《汇编语言》第12 章

任何一个通用的 CPU,比如 8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从 CPU 外部发过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。中断的意思是指,CPU 不再接着向下执行,而是转去处理这个特殊信息。

下面是 8086 CPU 在收到中断信息后,所引发的中断过程。

  1. (从中断信息中)取得中断类型码
  2. 标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中)
  3. 设置标志寄存器的第 8 位 TF 和第 9 位 IF 的值为0
  4. CS 的内容入栈
  5. IP 的内容入栈
  6. 从内存地址为中断类型码 *4 和中断类型码 *4+2 的两个字单元中读取中断处理程序的入口地址设置 IP 和 CS。

中断处理程序的编写方法:

  1. 保存用到的寄存器
  2. 处理中断
  3. 恢复用到的寄存器
  4. 用 iret 指令返回

单步中断:

基本上,CPU 在执行完一条指令后,如果检测到标志寄存器的 TF 位为 1,则产生单步中断,引发中断过程。单步中断的中断类型码为 1。CPU 提供单步中断功能的原因就是,为单步跟踪程序的执行过程,提供了实现机制。

实现中断机制

我实现的中断机制很简单:中断不能同时发生,中断也没有优先级,哪个中断先产生就先处理。

先在 EU 结构体里最后加上几个中断相关的变量:

type EU struct {
	ax     uint16
	cx     uint16
	dx     uint16
	bx     uint16
	sp     uint16
	bp     uint16
	si     uint16
	di     uint16
	eflags uint16

	//与 BIU 的通信接口
	biuCtrl chan BIURequest
	biuData chan uint16

	// 当前正在执行的指令
	currentInstruction byte
	// 是否停止执行的标志位
	stop bool

	// 是否有内部中断产生
	innerInterruptOccur bool
	innerInterruptType  uint8

	// 是否有外部中断产生
	externalInterruptOccur bool
	externalInterruptType  uint8

	// 是否刚执行完 iret 指令
	retFrominterrupt bool
}

定义产生内部中断、外部中断的方法:

func (e *EU) generateInnerInterrupt(intNum uint8) {
	e.innerInterruptOccur = true
	e.innerInterruptType = intNum
}

func (e *EU) generateExternalInterrupt(intNum uint8) {
	e.externalInterruptOccur = true
	e.externalInterruptType = intNum
}

定义处理中断函数:

// 内置的几个中断类型
const (
	DivideError uint8 = iota
	SingleStep
	NonMaskable
	OneByteIntInstruction
	Overflow
)

func (e *EU) processInterrupt(intNum uint8) {
	// push flags
	e.pushFlags()

	// let temp = TF
	temp := e.readEFLAGS(tfFlag)

	// clear IT & TF
	e.writeEFLAGS(ifFlag, 0)
	e.writeEFLAGS(tfFlag, 0)

	// push CS & IP
	e.pushCS()
	e.pushIP()

	// 从中断向量表获取中断处理程序的入口地址
	addr := 4 * intNum
	newIP := e.readMemoryWord(uint32(addr))
	newCS := e.readMemoryWord(uint32(addr + 2))
	e.writeSeg(CS, newCS)
	e.writeIP(newIP)

	// 如果 TF 标志为为 1,并且发生的中断不是单步中断,那么先执行单步中断
	if temp == 1 && intNum != SingleStep {
		e.processInterrupt(SingleStep)
	}
}

processInterrupt 的函数最后判断 如果 TF 标志为为 1,那么先执行单步中断。因为单步中断是要在执行完一条指令后就必须要执行的,它的优先级要比其他的内部,外部中断优先级高。

修改 EU 的 run 方法,在执行完一条指令后,执行处理中断的逻辑:

func (e *EU) run() {
	var instructions []byte
	for {
	    // 从BIU获取一个字节指令
		e.biuCtrl <- FetchInstruction
		instruction := byte(<-e.biuData)
		// 拼接指令
		instructions = append(instructions, instruction)
		// 解码当前的指令字节序列
		decodedInstructions := Decode(instructions)
		// 当前是一条有效的指令
		if decodedInstructions != nil {
			// 执行指令
			e.execute(decodedInstructions)
			// 清空指令字节序列
			instructions = instructions[:0]
			// 如果要求程序终止,则退出循环
			if e.stop {
				e.stop = false
				break
			}
			
			if !e.retFrominterrupt {
				if e.innerInterruptOccur { // 有内部中断
					e.innerInterruptOccur = false
					e.processInterrupt(e.innerInterruptType)
				} else {
					if e.externalInterruptOccur && // 有外部中断
						e.readEFLAGS(ifFlag) == 1 { // IF == 1
						e.externalInterruptOccur = false
						e.processInterrupt(e.externalInterruptType)
					}

					if e.readEFLAGS(tfFlag) == 1 { // TF == 1
						e.processInterrupt(SingleStep)
					}
				}
			} else {
				e.retFrominterrupt = false
			}
		}
	}
}

retFrominterrupt 标志位是执行 iret 指令时设置的,这是为了防止循环执行中断!

实现单步中断

实现了中断机制后,就可以实现单步中断。我实现的单步中断程序仅仅是休眠一秒钟。它的中断处理程序非常简单,只有两行汇编语句:

int 22h
iret

原因是我把实际的逻辑都放到执行 int 指令时去做了【为了偷懒,不想写汇编】:

func (e *EU) singleStep() {
	time.Sleep(1 * time.Second)
}

func (e *EU) executeInt(instructions []byte) {
	if instructions[1] == 0x21 {
		e.stop = true
	} else if instructions[1] == 0x22 {
		e.singleStep()
	}
}

接下来就是要修改 main 函数将这个中断处理程序【二进制】加载到内存中,并修改中断向量表:

	//装载单步中断程序
	//cs = 0x2000
	phyAddr = uint32(0x2000) << 4
	//单步中断程序执行 int 22h; iret
	c.writeMemory(phyAddr, []byte{0b11001101, 0x22, 0b11001111})
	//修改中单步中断的中断向量
	phyAddr = 1 * 4
	//	ip
	c.writeMemory(phyAddr, []byte{0, 0})
	// cs
	c.writeMemory(phyAddr+2, []byte{0x00, 0x20})

    // CPU 开始执行程序,第3个参数为是否开启单步调试
    c.Run(uint16(cs), uint16(programHeader.codeEntryProgOffset), true)

CPU 的 Run 方法修改为添加了一个参数,值为 true 时,就开启单步调试功能:

func (c *CPU) Run(cs, ip uint16, debug bool) {
	c.biu.run()
	if debug {
		c.eu.writeEFLAGS(tfFlag, 1)
	}
	c.eu.writeSeg(CS, cs)
	c.eu.writeIP(ip)
	c.eu.run()
}

接下来执行程序时,CPU 每执行完一条指令后都会 sleep 1秒。大功告成!

结束语

本系列文章到此就结束了,了解了这些编译器和虚拟机的基本原理及代码实现后,更进一步可以实现编译《汇编语言》书中所有的程序的编译器和运行它们的虚拟机,我本人没有更多精力投入在这方面了,感兴趣的朋友可以去实现!

最后

以上就是踏实刺猬为你收集整理的实现8086虚拟机(六)——中断系统的全部内容,希望文章能够帮你解决实现8086虚拟机(六)——中断系统所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部