做网站打广告犯法吗/阿里云万网域名查询
文章目录
- CPU 架构
- EU(执行单元)
- BIU(总线接口单元)
- 小结一下
- 模拟内存
- 模拟 BIU
- 模拟 EU
- 模拟 CPU
- 总结
要模拟 8086 CPU 运行,必须知道 CPU 的一些知识。下文的知识点都来自《Intel_8086_Family_Users_Manual 》。
CPU 架构
微处理器通常通过重复循环执行以下步骤来执行程序(此描述有所简化):
- 从内存中取出下一条指令。
- 读取操作数(如果指令需要)。
- 执行指令。
- 写入结果(如果指令需要)。
在以前的 CPU 中,大多数这些步骤都是串行执行的,或者只有一个总线周期取指重叠。 8086 和 8088 CPU 的架构也执行相同步骤,但是是将它们分配给 CPU 内两个独立的处理单元。执行单元(EU)执行指令;总线接口单元 (BIU) 获取指令、读取操作数并写入结果。
这两个单元可以彼此独立运行,并且在大多数情况下能够广泛地重叠取指令和执行。结果是,在大多数情况下,通常获取指令所需的时间“消失”了,因为 EU 执行的指令已经被 BIU 提前预取了。
8086 CPU 内部组成如下:
更详细的结构如下:
EU(执行单元)
EU 中的一个 16 位算术/逻辑单元 (ALU) 维护 CPU 状态和控制标志,并操纵通用寄存器和指令操作数。 EU 中的所有寄存器和数据通路都是 16 位宽的,用于快速内部传输。EU与系统总线(即“外面的世界”)没有任何联系。它从 BIU 维护的队列中获取指令。当指令需要访问内存或外围设备时,EU 会请求 BIU 获取或存储数据。EU 使用的所有地址都是 16 位宽。然而,BIU 执行地址重定位【段地址左移4位加上偏移地址】,使 EU 可以访问完整的1 M字节的内存空间。
BIU(总线接口单元)
BIU 为 EU 执行所有总线操作。数据在 CPU 和内存或 I/O 设备之间传输。
此外,在 EU 忙于执行指令期间,BIU 会“向前看”并从内存中获取更多指令【指令预取】。指令存储在称为指令流队列的内部 RAM 阵列中。 8086 队列最多可存放六个指令字节。 这些队列大小允许 BIU 在大多数情况下为 EU 提供预取的指令,而无需独占系统总线。
8088 BIU在指令队列中还剩余 1 字节空间并且没有来自 EU 的总线请求访问时才获取下一个字节指令。8086 BIU 的操作与此类似,只是它直到指令队列中还剩余 2 字节空间时才开始读取操作。
在大多数情况下,指令队列中至少包含指令流的一个字节,并且 EU 无需等待就可获取。队列中的指令是存储在紧邻且高于当前正在执行的指令的内存位置中的指令。也就是说,只要执行顺序进行,它们就是下一条要被执行的指令。如果 EU 执行一个指令时将控制转移到另一个位置,BIU 将重置队列,从新地址获取指令,立即将其传递给 EU,然后开始从新位置重新填充队列。此外,只要 EU 请求内存或 I/O 读取或写入,BIU 就会暂停获取指令。
小结一下
BIU:
- 包含 ES,CS,SS,DS 这4个段寄存器和 IP 寄存器,可以读取和修改这些寄存器
- 包含一个指令队列,可从内存获取指令放入队列
- 可以读写内存【可以向内存芯片发起读或写请求】
- 可以接收 EU 的请求并处理
EU:
- 包含 AX,CX,DX,BX,SP,BP,SI,DI 这 8 个16 位通用寄存器和 16 位的 EFLAGS 寄存器,可以读取和修改这些寄存器
- 从 BIU 获取指令【可以向 BIU 发起获取指令的请求】
- 可以向 BIU 发起读写内存的请求
- 可以向 BIU 发起读写 4 个段寄存器和 IP 寄存器的请求
- 可以将二进制格式的指令解码成 EU 可以识别的指令格式并执行指令
模拟内存
由于 BIU 要和内存打交道,先用代码来模拟下内存芯片。
简单地说,内存就是一个大数组。但是为了模拟它是一个芯片,且有与 BIU 的交互行为,自然地就可以把它实现为可以接收 BIU 请求的对象,请求和数据都通过 channel 发送:
type memoryOpType uint8// 支持的操作
const (// 读一个字节memoryOpReadByte memoryOpType = iota// 读一个字memoryOpReadWord// 写一个字节memoryOpWriteByte// 写一个字memoryOpWriteWord
)// 8086 CPU 地址总线为20位,最大支持1M内存
const maxMemorySize = 1 << 20 type Memory struct {size uint32memory []byte// 与 BIU 的通信接口CtrlBus chan memoryOpTypeAddrBus chan uint32DataBus chan uint16
}func (m *Memory) Init(size uint32) {if m.size != 0 {log.Fatal("Memory reinit!")}if size > maxMemorySize {log.Fatal("memory size exceeds max!")}m.memory = make([]byte, size)m.size = sizem.CtrlBus = make(chan memoryOpType)m.AddrBus = make(chan uint32)m.DataBus = make(chan uint16)// 内存的工作函数go func() {for op := range m.CtrlBus {switch op {case memoryOpReadByte:addr := <-m.AddrBusm.DataBus <- uint16(m.memory[addr])case memoryOpReadWord:addr := <-m.AddrBusm.DataBus <- uint16(m.memory[addr]) | uint16(m.memory[addr+1])<<8case memoryOpWriteByte:addr := <-m.AddrBusdata := <-m.DataBusm.memory[addr] = byte(data & 0xff)case memoryOpWriteWord:addr := <-m.AddrBusdata := <-m.DataBusm.memory[addr] = byte(data)m.memory[addr+1] = byte(data >> 8)}}}()
}
CtrlBus 表示控制总线,AddrBus 表示地址总线,DataBus 表示数据总线。这些总线与下文的 BIU “相连”。
从控制总线获取操作命令,从地址总线获取地址,当为读操作时,数据通过数据总线返回,当为写操作时,要写入的数据从数据总线获取。
内存对象的使用也很简单:
m := Memory{}m.Init(1 << 20)
模拟 BIU
模拟 BIU 也是类似的,它主要包含一个指令队列,与内存芯片的通信接口,与 EU的通信接口:
// 表示无效的段前缀
const invalidSegPrefix uint8 = 0xff// 段寄存器ID
const (ES uint8 = iotaCSSSDS
)// 指令队列大小
const instructionQueueSize = 6
const instructionQueueEmptySize = 2type BIU struct {es, cs, ss, ds uint16ip uint16// 指令队列instructionQueue [instructionQueueSize]byte// 指令队列剩余空间大小nEmpty uint8// 取指令索引// 存指令索引getIndex, putIndex uint8// 与内存芯片的通信接口ctrlBus chan memoryOpTypeaddrBus chan uint32dataBus chan uint16// 与EU的通信接口InnerCtrlBus chan BIURequestInnerDataBus chan uint16// 虚拟IP指针virtIP uint16// 内存操作使用的段前缀segPrefix uint8
}
再把它支持的操作定义出来:
type BIURequest uint16// 支持的操作
const (// 取指令FetchInstruction BIURequest = iota// 读内存ReadMemory// 读写段寄存器ReadSegRegWriteSegReg// 读写IP寄存器ReadIPRegWriteIPReg// 读写栈内存ReadStackMemoryWriteStackMemoryReadVariableWriteVariable// 改变段前缀ChangeSegPrefix// 未实现// StringSource// StringDestination// BPAsBaseRegister
)
这些操作要满足 EU 的需求,比如需要有读写段寄存器和读写 IP 寄存器的操作,需要有读写内存的操作等等。
ReadStackMemoryWriteStackMemoryReadVariableWriteVariable// 未实现// StringSource// StringDestination// BPAsBaseRegister
这几种是手册中定义的不同的内存访问类型:
因为不同的内存访问操作使用不同的段寄存器和偏移地址【逻辑地址】。
比如读取指令使用的是 CS 段寄存器,偏移地址放在 IP 寄存器中。栈操作使用的 SS 寄存器,偏移地址放在 SP 寄存器中。普通的数据访问使用的 DS 段寄存器,偏移地址是 EU 传入的地址。
16 位段寄存器地址左移 4 位加上偏移地址就得到 20 位的物理地址。所以 BIU 在这里要根据访问类型计算物理地址。
我在实现时,偏移地址是由 EU 根据内存类型操作传入的。举例来说,EU 要执行栈操作时传入的偏移地址就是 SP 寄存器的值,BIU 并不关心偏移地址的来源,它只关心如果是栈操作,那么段寄存器就使用 SS。
读写内存的实现如下:
func (b *BIU) readMemoryByte(addr uint32) byte {b.ctrlBus <- memoryOpReadByteb.addrBus <- addrreturn byte(<-b.dataBus)
}func (b *BIU) readMemoryWord(addr uint32) uint16 {b.ctrlBus <- memoryOpReadWordb.addrBus <- addrreturn <-b.dataBus
}func (b *BIU) writeMemoryByte(addr uint32, data byte) {b.ctrlBus <- memoryOpWriteByteb.addrBus <- addrb.dataBus <- uint16(data)
}func (b *BIU) writeMemoryWord(addr uint32, data uint16) {b.ctrlBus <- memoryOpWriteWordb.addrBus <- addrb.dataBus <- data
}
就是通过通信接口向内存芯片发起请求。
与取指令相关的几个函数实现如下:
// 清空指令队列
func (b *BIU) emptyInstructionQueue() {b.nEmpty = instructionQueueSizeb.getIndex = 0b.putIndex = 0
}// 尝试预取指令
func (b *BIU) prefetchInstructions() {for b.nEmpty >= instructionQueueEmptySize {phyAddress := uint32(b.cs)<<4 + uint32(b.virtIP)instruction := b.readMemoryByte(phyAddress)b.instructionQueue[b.putIndex] = instructionb.nEmpty--b.putIndex++if b.putIndex == instructionQueueSize {b.putIndex = 0}// 虚拟IP指针加1b.virtIP++}
}// 从指令队列取出一个字节的指令给EU
func (b *BIU) fetchOneInstruction() byte {if b.nEmpty == instructionQueueSize {return 0x0f // 未使用的指令}instruction := b.instructionQueue[b.getIndex]b.nEmpty++b.getIndex++if b.getIndex == instructionQueueSize {b.getIndex = 0}// 取得一个字节指令后,IP指针加1b.ip++return instruction
}
我在实现时 EU 从 BIU 获取的是一条指令中的 1 字节数据而不是完整的一条指令。因为 BIU 每次都是从内存读取 1 字节指令,BIU 做不到每次从内存读取一条完整的指令。 而 BIU 做指令解码的工作又很奇怪,所以我将指令的解码工作放在 EU 里实现。
BIU 的工作流程就是不停地:
- 尝试预取指令
- 处理来自EU的各种请求
实现如下:
func (b *BIU) run() {if b.nEmpty == 0 {log.Fatal("not init!!!")}go func() {for {// 预取指令b.prefetchInstructions()// 处理EU的请求req := <-b.InnerCtrlBusswitch req {// 取指令case FetchInstruction:b.InnerDataBus <- uint16(b.fetchOneInstruction())// 读内存case ReadMemory:addrLow := <-b.InnerDataBusaddrHigh := <-b.InnerDataBussize := <-b.InnerDataBusphyAddr := uint32(addrHigh)<<16 | uint32(addrLow)if size == 8 {b.InnerDataBus <- uint16(b.readMemoryByte(phyAddr))} else {b.InnerDataBus <- b.readMemoryWord(phyAddr)}// 读段寄存器case ReadSegReg:reg := uint8(<-b.InnerDataBus)switch reg {case ES:b.InnerDataBus <- b.escase CS:b.InnerDataBus <- b.cscase SS:b.InnerDataBus <- b.sscase DS:b.InnerDataBus <- b.dsdefault:log.Fatal("error")}// 读IP寄存器case ReadIPReg:b.InnerDataBus <- b.ip// 写段寄存器case WriteSegReg:reg := uint8(<-b.InnerDataBus)val := <-b.InnerDataBusswitch reg {case ES:b.es = valcase CS:b.cs = val// 先修改IP,再修改CS,可能从旧的代码段取了指令,所以需要清空指令队列b.virtIP = b.ipb.emptyInstructionQueue()case SS:b.ss = valcase DS:b.ds = valdefault:log.Fatal("error")}// 写IP寄存器case WriteIPReg:val := <-b.InnerDataBusb.ip = valb.virtIP = valb.emptyInstructionQueue()fmt.Printf("change Ip to 0x%X\n", val)// 读栈内存,读普通数据内存case ReadStackMemory, ReadVariable:offset := <-b.InnerDataBussize := <-b.InnerDataBusvar phyAddress uint32if req == ReadStackMemory {phyAddress = uint32(b.ss)<<4 + uint32(offset)} else {//cs es ssif b.segPrefix == invalidSegPrefix {phyAddress = uint32(b.ds)<<4 + uint32(offset)} else {switch b.segPrefix {case ES:phyAddress = uint32(b.es)<<4 + uint32(offset)case CS:phyAddress = uint32(b.cs)<<4 + uint32(offset)case SS:phyAddress = uint32(b.ss)<<4 + uint32(offset)case DS:phyAddress = uint32(b.ds)<<4 + uint32(offset)default:log.Fatal("error")}}}if size == 8 {b.InnerDataBus <- uint16(b.readMemoryByte(phyAddress))} else {b.InnerDataBus <- b.readMemoryWord(phyAddress)}if req == ReadVariable && b.segPrefix != invalidSegPrefix {b.segPrefix = invalidSegPrefix}// 写栈内存,写普通数据内存case WriteStackMemory, WriteVariable:offset := <-b.InnerDataBussize := <-b.InnerDataBusval := <-b.InnerDataBusvar phyAddress uint32if req == WriteStackMemory {phyAddress = uint32(b.ss)<<4 + uint32(offset)} else {//cs es ssif b.segPrefix == invalidSegPrefix {phyAddress = uint32(b.ds)<<4 + uint32(offset)} else {switch b.segPrefix {case ES:phyAddress = uint32(b.es)<<4 + uint32(offset)case CS:phyAddress = uint32(b.cs)<<4 + uint32(offset)case SS:phyAddress = uint32(b.ss)<<4 + uint32(offset)case DS:phyAddress = uint32(b.ds)<<4 + uint32(offset)default:log.Fatal("error")}}}if size == 8 {b.writeMemoryByte(phyAddress, byte(val))} else {b.writeMemoryWord(phyAddress, val)}if req == WriteVariable && b.segPrefix != invalidSegPrefix {b.segPrefix = invalidSegPrefix}// case StringSource:// phyAddress = uint32(b.ds<<4) + uint32(req.Offset)// case StringDestination:// phyAddress = uint32(b.es<<4) + uint32(req.Offset)// case BPAsBaseRegister:// phyAddress = uint32(b.ss<<4) + uint32(req.Offset)// 改变段前缀case ChangeSegPrefix:if b.segPrefix != invalidSegPrefix {log.Fatal("error: invalid ")}b.segPrefix = uint8(<-b.InnerDataBus)default:log.Fatal("erer")}}}()
}
模拟 EU
模拟 EU 也是和模拟 BIU 类似,可用如下的结构体表示:
type EU struct {// 8 个 16位通用寄存器ax uint16cx uint16dx uint16bx uint16sp uint16bp uint16si uint16di uint16eflags uint16//与 BIU 的通信接口biuCtrl chan BIURequestbiuData chan uint16// 当前正在执行的指令currentInstruction byte// 是否停止执行的标志位stop bool// 略去了与中断相关的字段
}
它定义了如下读写寄存器的方法:
// 16位通用寄存器的ID
const (AL uint8 = iotaCLDLBLAHCHDHBH
)// 8 位通用寄存器的ID
const (AX uint8 = iotaCXDXBXSPBPSIDI
)// 标志寄存器的各种标志位
const (cfFlag uint8 = 0 //pfFlag uint8 = 2 //afFlag uint8 = 4zfFlag uint8 = 6 //sfFlag uint8 = 7 //tfFlag uint8 = 8ifFlag uint8 = 9dfFlag uint8 = 10ofFlag uint8 = 11 //
)// 写16位通用寄存器
func (e *EU) writeReg16(reg uint8, value uint16) {switch reg {case AX:e.ax = valuecase CX:e.cx = valuecase DX:e.dx = valuecase BX:e.bx = valuecase SP:e.sp = valuecase BP:e.bp = valuecase SI:e.si = valuecase DI:e.di = valuedefault:log.Fatal()}
}// 读16位通用寄存器
func (e *EU) readReg16(reg uint8) uint16 {var value uint16switch reg {case AX:value = e.axcase CX:value = e.cxcase DX:value = e.dxcase BX:value = e.bxcase SP:value = e.spcase BP:value = e.bpcase SI:value = e.sicase DI:value = e.didefault:log.Fatal()}return value
}// 写 8 位通用寄存器
func (e *EU) writeReg8(reg uint8, value uint8) {switch reg {case AL:e.ax &= 0xff00e.ax |= uint16(value)case CL:e.cx &= 0xff00e.cx |= uint16(value)case DL:e.dx &= 0xff00e.dx |= uint16(value)case BL:e.bx &= 0xff00e.bx |= uint16(value)case AH:e.ax &= 0x00ffe.ax |= uint16(value) << 8case CH:e.cx &= 0x00ffe.cx |= uint16(value) << 8case DH:e.dx &= 0x00ffe.dx |= uint16(value) << 8case BH:e.bx &= 0x00ffe.bx |= uint16(value) << 8default:log.Fatal()}
}// 读 8 位通用寄存器
func (e *EU) readReg8(reg uint8) uint8 {var value uint8switch reg {case AL:value = uint8(e.ax)case CL:value = uint8(e.cx)case DL:value = uint8(e.dx)case BL:value = uint8(e.bx)case AH:value = uint8(e.ax >> 8)case CH:value = uint8(e.cx >> 8)case DH:value = uint8(e.dx >> 8)case BH:value = uint8(e.bx >> 8)}return value
}// 设置标志寄存器的某一位
func (e *EU) writeEFLAGS(bitOffset uint8, value uint8) {if value == 0 {e.eflags &= ^uint16(1 << bitOffset)} else {e.eflags |= uint16(1 << bitOffset)}
}// 读取标志寄存器的某一位
func (e *EU) readEFLAGS(bitOffset uint8) uint8 {value := uint8(e.eflags>>bitOffset) & 0x1return value
}
定义了如下和 BIU 通信的方法【比如读写段寄存器,读写 IP 寄存器,读写内存等】:
func (e *EU) writeIP(val uint16) {e.biuCtrl <- WriteIPRege.biuData <- val
}func (e *EU) readIP() uint16 {e.biuCtrl <- ReadIPRegreturn <-e.biuData
}func (e *EU) readSeg(reg uint8) uint16 {e.biuCtrl <- ReadSegRege.biuData <- uint16(reg)return <-e.biuData
}func (e *EU) writeSeg(reg uint8, val uint16) {e.biuCtrl <- WriteSegRege.biuData <- uint16(reg)e.biuData <- val
}func (e *EU) readMemoryWord(phyAddr uint32) uint16 {e.biuCtrl <- ReadMemorye.biuData <- uint16(phyAddr)e.biuData <- uint16(phyAddr >> 16)e.biuData <- 16return <-e.biuData
}func (e *EU) readDataMemmoryByte(effectiveAddr uint16) uint8 {e.biuCtrl <- ReadVariablee.biuData <- effectiveAddre.biuData <- 8return uint8(<-e.biuData)
}func (e *EU) readDataMemmoryWord(effectiveAddr uint16) uint16 {e.biuCtrl <- ReadVariablee.biuData <- effectiveAddre.biuData <- 16return <-e.biuData
}func (e *EU) writeDataMemmoryByte(effectiveAddr uint16, val uint8) {e.biuCtrl <- WriteVariablee.biuData <- effectiveAddre.biuData <- 8e.biuData <- uint16(val)
}func (e *EU) writeDataMemmoryWord(effectiveAddr uint16, val uint16) {e.biuCtrl <- WriteVariablee.biuData <- effectiveAddre.biuData <- 16e.biuData <- val
}func (e *EU) readStackMemory() uint16 {e.biuCtrl <- ReadStackMemorye.biuData <- e.spe.biuData <- 16return <-e.biuData
}func (e *EU) writeStackMemory(val uint16) {e.biuCtrl <- WriteStackMemorye.biuData <- e.spe.biuData <- 16e.biuData <- val
}func (e *EU) changeSegPrefix(newPrefix uint8) {e.biuCtrl <- ChangeSegPrefixe.biuData <- uint16(newPrefix)
}
定义了最关键的执行指令的方法:
func (e *EU) execute(instructions []byte) {// 指令格式的第一字节表示指令类型instruction := instructions[0]e.currentInstruction = instruction// 根据指令类型执行不同的操作switch instruction {case InstructionMov:e.executeMov(instructions[1:])case InstructionAdd, InstructionOr, InstructionAdc, InstructionSbb,InstructionAnd, InstructionSub, InstructionXor, InstructionCmp:e.executeAddEtc(instructions[1:])case InstructionInc, InstructionDec, InstructionNot, InstructionNeg,InstructionMul, InstructionImul, InstructionDiv, InstructionIdiv:e.executeIncEtc(instructions[1:])case InstructionSegPrefix:e.executeSegPrefix(instructions[1:])case InstructionPush:e.executePush(instructions[1:])case InstructionPop:e.executePop(instructions[1:])case InstructionJmp:e.executeJmp(instructions[1:])case InstructionCall:e.executeCall(instructions[1:])case InstructionRet:e.executeRet(instructions[1:])case InstructionLoop:e.executeLoop(instructions[1:])case InstructionInt:e.executeInt(instructions[1:])case InstructionNop:e.executeNop(instructions[1:])default:log.Fatal("unsupported inssss---")}
}
EU 的工作流程就是不停地从 BIU 获取指令执行,直到程序终止。
实现如下:
func (e *EU) run() {var instructions []bytefor {// 从BIU获取一个字节指令e.biuCtrl <- FetchInstructioninstruction := byte(<-e.biuData)// 拼接指令instructions = append(instructions, instruction)// 解码当前的指令字节序列decodedInstructions := Decode(instructions)// 当前是一条有效的指令if decodedInstructions != nil {// 执行指令e.execute(decodedInstructions)// 清空指令字节序列instructions = instructions[:0]// 如果要求程序终止,则退出循环if e.stop {e.stop = falsebreak}}}
}
前面说过,一条指令通常包含多个字节,而从 BIU 获取的只是指令中的一个字节,所以需要拼接起来解码,看能否形成一条完整的指令!
模拟 CPU
模拟完 BIU 和 EU 之后,模拟 CPU 就很简单了:
type CPU struct {eu EUbiu BIU
}
为了将程序写入内存,为它实现了如下读写内存的方法:
func (c *CPU) writeMemory(addr uint32, data []byte) {for i, v := range data {c.biu.writeMemoryByte(addr+uint32(i), v)}
}func (c *CPU) readMemory(addr uint32, data []byte) {for i, _ := range data {data[i] = c.biu.readMemoryByte(addr + uint32(i))}
}
为了将它与内存芯片相连,实现了 ConnectMemory 方法:
func (c *CPU) ConnectMemory(m *Memory) {c.biu.connectMemory(m)
}
它就是调用 BIU 的connectMemory方法,它实现如下:
func (b *BIU) connectMemory(m *Memory) {b.ctrlBus = m.CtrlBusb.addrBus = m.AddrBusb.dataBus = m.DataBus
}
就是将 BIU 的控制总线、地址总线、数据总线与内存芯片的相连!
CPU 的初始化方法实现如下:
func (c *CPU) Init() {c.biu.Init()// 将 EU 的控制总线和 BIU 相连c.eu.biuCtrl = c.biu.InnerCtrlBusc.eu.biuData = c.biu.InnerDataBus
}
其中 BIU 的初始化方法实现如下:
func (b *BIU) Init() {// 情况指令序列b.emptyInstructionQueue()// 设置段前缀为无效b.segPrefix = invalidSegPrefix// 创建控制总线和数据总线b.InnerCtrlBus = make(chan BIURequest)b.InnerDataBus = make(chan uint16)
}
在 CPU 初始化,并与内存相连后,它们之间的通信数据流如下:
CPU 的工作函数实现如下:
func (c *CPU) Run(cs, ip uint16, debug bool) {// 让 BIU 开始工作c.biu.run()if debug {c.eu.writeEFLAGS(tfFlag, 1)}// 设置CS和IP寄存器的值c.eu.writeSeg(CS, cs)c.eu.writeIP(ip)// 让 EU 开始工作c.eu.run()
}
再回头看上篇文章 main 函数里面的加载并执行程序这段代码就很容易明白了:
// 4. 初始化一个CPU和内存芯片// 初始化一个内存芯片,大小为1Mm := Memory{}m.Init(1 << 20)// 初始化一个CPUc := CPU{}c.Init()// 将CPU与内存相连c.ConnectMemory(&m)// 5. 将程序写入内存// 计算出程序在内存中的起始地址var phyAddr uint32 = uint32(cs)<<4 - programHeader.codeSegProgOffset// 将程序加载到内存c.writeMemory(phyAddr, program)// 6. CPU 开始执行程序// 第一个参数是CPU开始执行时CS寄存器的值,第二个参数是IP寄存器的值c.Run(uint16(cs), uint16(programHeader.codeEntryProgOffset))
总结
本文介绍了怎样使用程序模拟 CPU 和内存,至此读者可以窥见 8086 虚拟机内部详细工作原理。 后续文章将介绍 EU 中最核心的部分——指令解码和执行的实现。
相关文章:

实现8086虚拟机(二)——模拟CPU和内存
文章目录CPU 架构EU(执行单元)BIU(总线接口单元)小结一下模拟内存模拟 BIU模拟 EU模拟 CPU总结要模拟 8086 CPU 运行,必须知道 CPU 的一些知识。下文的知识点都来自《Intel_8086_Family_Users_Manual 》。CPU 架构 微…...

Windows7下使用VMware11.1.1安装ubuntu-16.04.7
一、说明二、安装说明三、安装步骤详解1、先安装VMware软件2、创建虚拟机3、编辑虚拟机4、开启虚拟机,初始化Linux系统一、说明 虽然VMware和ubuntu最新版已经很高了,我这电脑由于是win7配值还低,所以采用低版本来安装 VMware版本࿱…...

基于SSM框架的CMS内容管理系统的设计与实现
基于SSM框架的CMS内容管理系统的设计与实现 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目…...

华为OD机试 - 运动会 | 机试题算法思路 【2023】
最近更新的博客 华为OD机试 - 自动曝光(Python) | 机试题算法思路 【2023】 华为OD机试 - 双十一(Python) | 机试题算法思路 【2023】 华为OD机试 - 删除最少字符(Python) | 机试题算法思路 【2023-02】 华为OD机试 - Excel 单元格数值统计(Python) | 机试题算法思路 …...

(C语言篇)扫雷的实现
文章目录 一、开始时的基本思维:二、进入游戏的逻辑(test.c文件中实现)三、游戏的编写 1. 初始化棋盘 I. test.cII. game.hIII. game.c 2.打印棋盘 I. test.cII. game.hIII. game.c 3.布置雷 I. test.cII. game.hIII. game.c 4.排查雷 I. test.cII. game.hIII. gam…...

华为手表开发:WATCH 3 Pro(8)获取位置服务
华为手表开发:WATCH 3 Pro(8)获取位置服务初环境与设备文件夹:文件新增第二页面geolocation.hmlgeolocation.js修改首页 -> 新建按钮 “ 跳转 ”index.hmlindex.js 引用包:system.router首页效果点击结果按钮跳转后…...

AnLogicFPGA设计的时序约束及时序收敛
本篇博文讲了三个内容:时序约束基本概念、时序约束命令、时序收敛技巧 时序约束基本概念 时序设计的实质就是满足每一个触发器的建立(setup)时间和保持(hold)时间。 建立时间(Tsu) 触发器的时钟信号沿到来以前&…...

ubuntu22.10安装sogou输入法后不能输入中文字符(可以输入中文标点符号)
问题描述 想在ubuntu22.10系统上安装sogou中文输入法,按照sogou输入法网站给出的步骤安装后,发现无法输入中文字符,但是可以输入中文标点符号。 sogou网站:https://shurufa.sogou.com/linux/guide 寻找答案1 通过各种百度和必…...

基于微信小程序的生活日用品交易平台 的设计与实现
基于微信小程序的生活日用品交易平台 的设计与实现 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一…...

15:高级篇 - CTK 事件与监听
作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 生命周期层事件 在 Plugin 生命周期的不同状态相互转换时,CTK Plugin Framework 会发出各种不同的事件,以供事先注册好的事件监听器处理,这些事件被称为“生命周期层事件”。CTK Plugin Framework 支持的…...

SpringBoot Notes
文章目录1 SpringBootWeb快速入门1.1Spring官网1.2 Web分析2. HTTP协议2.1 HTTP介绍34 SpringBootWeb请求响应5 响应6 分层解耦6.1 三层架构6.1.1 三层架构介绍6.1.2 基于三层架构的程序执行流程:6.1.3 代码拆分6.2 分层解耦6.2.1 内聚、耦合6.2.2 解耦思路6.3 IOC&…...

CoreDNS
目录 文章目录目录本节实战前言1、环境变量2、DNS1.DNS 解析过程2.根域名服务器3.顶级域名服务器4.权威性域名服务器5.dig 域名3、CoreDNS1.CoreDNS 扩展配置(1)开开启日志服务(2)特定域名使用自定义 DNS 服务器(3&…...

码农饭碗不保——ChatGPT正在取代Coder
码农饭碗不保——ChatGPT正在取代Coder 最近被OpenAI的ChatGPT刷屏了。我猜你已经读了很多关于ChatGPT的文章,不需要再介绍了。假如碰巧您还不太了解ChatGPT是什么,可以先看一下这篇文章,然后再回来继续。 与ChatGPT对话很有趣,…...

PAT (Advanced Level) Practice 1004 Counting Leaves
1004 Counting Leaves题目翻译代码分数 30 作者 CHEN, Yue 单位 浙江大学 A family hierarchy is usually presented by a pedigree tree. Your job is to count those family members who have no child. Input Specification: Each input file contains one test case. Eac…...

基于Redis实现的分布式锁
基于Redis实现的分布式锁什么是分布式锁分布式锁主流的实现方案Redis分布式锁Redis分布式锁的Java代码体现优化一:使用UUID防止误删除优化二:LUA保证删除原子性什么是分布式锁 单体单机部署中可以为一个操作加上锁,这样其他操作就会等待锁释…...

2023年,还找算法岗工作吗?
点击下方卡片,关注“CVer”公众号AI/CV重磅干货,第一时间送达2023年春招(补招)已经大规模启动了!距离2023年暑期实习不到2个月!距离2024届校招提前批不到4个月!距离2024届秋招正式批不到6个月&a…...

正点原子ARM裸机开发篇
裸机就是手动的操作硬件来实现驱动设备,后面会有驱动框架不需要这么麻烦 第八章 汇编 LED 灯实验 核心过程 通过汇编语言来控制硬件(驱动程序) 代码流程 1、使能 GPIO1 时钟 GPIO1 的时钟由 CCM_CCGR1 的 bit27 和 bit26 这两个位控制&…...

20222023华为OD机试 - 压缩报文还原(JS)
压缩报文还原 题目 为了提升数据传输的效率,会对传输的报文进行压缩处理。 输入一个压缩后的报文,请返回它解压后的原始报文。 压缩规则:n[str],表示方括号内部的 str 正好重复 n 次。 注意 n 为正整数(0 < n <= 100),str只包含小写英文字母,不考虑异常情况。 …...

SheetJS的部分操作
成文时间:2023年2月18日 使用版本:"xlsx": "^0.18.5" 碎碎念: 有错请指正。 这个库自说自话升级到0.19。旧版的文档我记得当时是直接写在github的README上。 我不太会使用github,现在我不知道去哪里可以找到…...

pytest总结
这里写目录标题一、pytest的命名规则二、界面化配置符合命名规则的方法前面会有运行标记三、pytest的用例结构三部分组成四、pytest的用例断言断言写法:五、pytest测试框架结构六、pytest参数化用例1、pytest参数化实现方式2、单参数:每一条测试数据都会…...

CNI 网络分析(九)Calico IPIP
文章目录环境流量分析Pod 间Node 到 PodPod 到 serviceNode 到 serviceNetworkPolicy理清和观测网络流量环境 可以看到,在宿主机上有到每个 pod IP 的路由指向 veth 设备 到对端节点网段的路由 指向 tunl0 下一跳 ens10 的 ip 有到本节点网段 第一个 ip 即 tunl0 的…...

分布式任务调度(XXL-JOB)
什么是分布式任务调度? 任务调度顾名思义,就是对任务的调度,它是指系统为了完成特定业务,基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。通常任务调度的程序是集成在应用中的,比如:…...

Django框架之模型视图--Session
Session 1 启用Session Django项目默认启用Session。 可以在settings.py文件中查看,如图所示 如需禁用session,将上图中的session中间件注释掉即可。 2 存储方式 在settings.py文件中,可以设置session数据的存储方式,可以保存…...

二极管的“几种”应用
不知大家平时有没有留意,二极管的应用范围是非常广的,下面我们来看看我想到几种应用,也可以加深对电路设计的认识: A,特性应用: 由于二极管的种类非常之多,这里这个大类简单罗列下:…...

github上传本地文件详细过程
repository 也就是俗称的仓库 声明:后续操作基于win10系统 前提:有一个github账号、电脑安装了git(官方安装地址) 目的: 把图中pdf文件上传到github上的个人仓库中 效果: 温馨提示: git中复制: ctrl insert…...

常用聚类算法分析
1. 什么是聚类 1.1. 聚类的定义 聚类(Clustering)是按照某个特定标准(如距离)把一个数据集分割成不同的类或簇,使得同一个簇内的数据对象的相似性尽可能大,同时不在同一个簇中的数据对象的差异性也尽可能地大。也即聚类后同一类的数据尽可能聚集到一起…...

OSG三维渲染引擎编程学习之五十八:“第五章:OSG场景渲染” 之 “5.16 简单光源”
目录 第五章 OSG场景渲染 5.16 简单光源 5.16.1 场景中使用光源 5.16.2 简单光源示例 第五章 OSG场景渲染 OSG存在场景树和渲染树,“场景数”的构建在第三章“OSG场景组...

80211无线网络架构
无线网络架构物理组件BSS(Basic Service Set)基本服务集BSSID(BSS Identification)ssid(Service Set Identification)ESS(Extended Service Set)扩展服务集物理组件 无线网络包含四…...

基于springboot+vue的便利店库存管理系统
基于springbootvue的便利店库存管理系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背景…...

3|物联网控制|计算机控制-刘川来胡乃平版|第1章:绪论|青岛科技大学课堂笔记|U1 ppt
目录绪论(2学时)常用仪表设备(3学时)计算机总线技术(4学时)过程通道与人机接口(6学时)数据处理与控制策略(6学时)网络与通讯技术(3学时࿰…...