概述
文章目录
- 基础知识
- 实现中断机制
- 实现单步中断
- 结束语
这个系列的最后一篇文章讲述如何实现中断系统,如何实现内置的 1 号中断——单步中断。
基础知识
内容来自《汇编语言》第12 章
任何一个通用的 CPU,比如 8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从 CPU 外部发过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。中断的意思是指,CPU 不再接着向下执行,而是转去处理这个特殊信息。
下面是 8086 CPU 在收到中断信息后,所引发的中断过程。
- (从中断信息中)取得中断类型码
- 标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中)
- 设置标志寄存器的第 8 位 TF 和第 9 位 IF 的值为0
- CS 的内容入栈
- IP 的内容入栈
- 从内存地址为中断类型码 *4 和中断类型码 *4+2 的两个字单元中读取中断处理程序的入口地址设置 IP 和 CS。
中断处理程序的编写方法:
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用 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虚拟机(六)——中断系统所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复