Bochs源代码分析

rainer800

贡献于2017-08-20

字数:0 关键词: Bochs 代码分析/审查/优化

Bochs 项目源码分析与注释 Understanding the source code of bochs 作者:喻强 MAY 2006 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 1 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 写在前面 今天,虚拟机及虚拟化技术在计算机领域扮演越来越重要的角色,从 CPU 模拟器到服 务器虚拟化管理软件,从商业化的成熟软件到开发源码的自由软件项目。形形色色的虚拟机 程序光名字就让人目不暇接。它们实现的功能也不尽相同: JAVA 虚拟机实现了代码的跨平 台运行,Virtual PC 提供了一台虚拟的 PC 机供人们使用,VMware ESX server 则将一台功能 强大的服务器划分称为若干网络虚拟主机。还有一些实际上可以被称为模拟器的虚拟机则仿 真了某种 CPU 及系统的功能。 本文讲述的 Bochs 即为一个 x86 CPU 的模拟器,它是一个用 C++语言写成的高移植性 的开源IA-32架构的PC模拟器,可以在很多的流行操作系统上运行。它模拟了Intel x86 CPU, 常见 IO 设备以及一个 BIOS。 当前, Bochs 可以编译为模拟 386,486,Pentium,Pentium Pro 或 AMD64 CPU,包括可选的 MMX,SSE,SSE2 和 3DNow!指令集。Bochs 中可以运行 的操作系统包括 Linux,DOS,Windows。Bochs 由 Kevin Lawton 在 1994 年开始编写。它最 初是作为商业产品开发的,但到了 2000 年 3 月,Mandrakesoft 公司买下了 Bochs,使之成 为遵循 GNU LGPL 的开源软件。2001 年 3 月,Kevin 帮助一些开发者把 Bochs 移到了 sourceforge.net 网站。 Bochs 可以使用不同的模式编译,有些还在开发之中。典型的应用就是提供了完整的 x86 PC 模拟器,包括 x86 CPU、硬件设备以及存储器。因为是一个开源的项目,在互联网 上得到了世界各地的程序爱好者的支持,不断的对其进行功能完善并修改存在的错误,在 2006 年 1 月 29 日,Bochs 的最新版本 2.2.6 版已经发行。 经过为期几个月的源码阅读,我把自己理解的一些东西整理出来,并结合相关知识点 给出了简要的分析,这些代码基本上覆盖了 Bochs 工程的主体线索。当然,整个 Bochs 工程 由 300 多个源文件组成,代码约 20 万行,不可能全部列出,因此,只对重要的数据和结构 作了注释。通过对源码的研究和学习,能够更为深入的理解 x86 CPU 及 PC 系统的工作原理。 你可以看到硬件对每条指令的执行是怎样用软件精确的再现出来,存储器地址究竟是如何映 射的,你也会学到怎么去编程驱动一个 ATA 设备或显卡……所有的隐藏在硬件中的东西被 Bochs 暴露无遗。 了解嵌入式系统的读者一定知道 SkyEye,这是清华大学开发的 ARM 平台模拟器,目 前很多功能已经实现,该项目得到了嵌入式 Linux 开发爱好者的支持和极力推崇。当我知道 了这个项目的时候,我觉得这是一条很好的路,作为致力于底层程序设计的软件人员,应该 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 踏实的掌握每一个细节,理解每一个具体概念,而不应当只停留在调用某个现成的函数的水 平上。 希望这篇源码分析报告能够给从事 x86 体系上系统程序或设备驱动程序设计的人们一 点参考和帮助。时常听说学习 Linux 的最好方法就是去阅读源代码,所以希望我的工作能够 起到抛砖引玉的作用,让更多的人来掌握和运用这个学习方法。 由于水平有限,文中出现的错误敬请指正! 2006.5于南京工业大学 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs Table of Content Chapter 1 x86 体系结构与 PC 系统概要 .....................................1 1.1 x86 CPU 结构...............................................................................................................1 1.1.1 冯诺依曼架构和 CISC 指令集......................................................................1 1.1.2 CPU 结构........................................................................................................1 1.1.3 CPU 工作模式................................................................................................2 1.2 x86 体系结构概览........................................................................................................3 1.3 PC 系统.........................................................................................................................5 1.3.1 PC 系统概述...................................................................................................5 2.3.2 总线拓扑.........................................................................................................6 2.3.3 存储器与 I/O 编址 .........................................................................................6 2.3.4 关于系统的启动与引导.................................................................................8 Chapter 2 Bochs 工程概述....................................................... 11 2.1 开源项目 Bochs 介绍.................................................................................................11 2.2 版本 2.2.1 源码组织..................................................................................................11 2.3 工程类结构.................................................................................................................12 2.4 主体框架结构分析.....................................................................................................13 2.4.1 Bochs 工程中的重要类................................................................................13 (1) VM 控制台界面类 .............................................................................................13 (2) CPU 模拟............................................................................................................14 (3) Memory 模拟......................................................................................................14 (4).I/O device 模拟...................................................................................................15 2.4.2 入口函数 main()及 Win32 Gui 初始化........................................................15 2.5 Bochs 的工作方式......................................................................................................18 Chapter 3 CPU 类的源码分析 ...................................................20 3.1 CPU 类概述................................................................................................................20 3.1.1 CPU 逻辑结构框图......................................................................................20 3.1.2 类 BX_CPU_C 成员归纳.............................................................................20 3.2 类 BX_CPU_C 源码分析..........................................................................................21 3.2.1 CPU 特性声明..............................................................................................21 3.2.2 类 bxInstruction_c 成员分析........................................................................22 3.2.3 类 BX_CPU_C 源码注释.............................................................................22 3.3 通用寄存器.................................................................................................................29 3.3.1 数据结构与注释...........................................................................................29 3.3.2 通用寄存器归纳...........................................................................................30 3.4 段寄存器、全局寄存器 GDI 和 IDT ........................................................................31 3.4.1 数据结构与注释...........................................................................................31 3.4.2 段寄存器结构分析.......................................................................................33 3.5 CPU 状态字 EFLAGS................................................................................................35 3.5.1 数据结构与注释...........................................................................................35 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 3.5.2 源码分析.......................................................................................................39 3.6 函数 CPU_LOOP()结构分析.....................................................................................40 3.6.1 CPU_LOOP()函数总体结构........................................................................40 3.6.2 函数 CPU_LOOP()源码注释.......................................................................41 3.7 函数 handleAsyncEvent()分析...................................................................................44 3.7.1 函数 handleAsyncEvent()知识准备.............................................................44 3.7.2 函数 handleAsyncEvent()结构分析.............................................................46 3.7.3 函数 handleAsyncEvent()源码注释.............................................................47 3.8 取指与执行.................................................................................................................50 3.8.1 Intel IA-32 指令结构....................................................................................50 3.8.2 类 bxInstruction_c 的数据成员....................................................................53 3.8.3 取指译码函数 FetchDecode()分析 ..............................................................55 3.8.4 模拟指令的执行...........................................................................................59 Chapter 4 CPU 中断处理任务管理............................................60 4.1 IA-32 体系结构中断知识准备 ..................................................................................60 4.1.1 中断和异常概述...........................................................................................60 4.1.2 异常和中断向量...........................................................................................60 4.1.3 中断和异常来源...........................................................................................61 4.1.4 与中断处理相关的数据结构.......................................................................62 4.1.5 异常和中断的处理方法...............................................................................64 4.2 Bochs 对中断的模拟..................................................................................................68 4.2.1 概述...............................................................................................................68 4.2.2 主要函数的源码注释...................................................................................69 4.3 虚拟机的任务管理.....................................................................................................82 4.3.1 IA-32 任务管理知识准备 ...........................................................................82 4.3.2 函数 task_switch()源码注释 ........................................................................89 Chapter 5 存储器源码分析........................................................104 5.1 IA-32 体系的存储器结构 ........................................................................................104 5.2 Bochs 对存储器的模拟............................................................................................105 5.3 存储器类 BX_MEM_C 部分源码分析...................................................................107 5.3.1 相关数据结构与类定义.............................................................................107 5.3.2 相关函数分析.............................................................................................108 5.3 模拟分页机制...........................................................................................................115 5.3.1 分页 (Paging)概述.....................................................................................115 5.3.2 页目录与页表.............................................................................................116 5.3.3 线形地址转换.............................................................................................117 5.3.4 实现分页机制的源码分析.........................................................................120 Chapter 6 系统板与外设模拟 ..................................................136 6.1 Bochs 系统板描述类 bx_pc_system_c ...................................................................136 6.1.1 类 bx_pc_system_c 功能概述 ....................................................................136 6.1.2 PC system 定时器管理...............................................................................136 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 6.1.3 类 bx_pc_system_c 源码分析与注释 ........................................................138 6.2 设备集合类 bx_devices_c........................................................................................146 6.2.1 类 bx_devices_c 成员变量分析 .................................................................146 6.2.2 类 bx_devices_c 成员函数分析与注释 .....................................................149 6.2.3 设备的初始化.............................................................................................150 6.2.4 设备复位.....................................................................................................154 6.2.5 设备访问(读写)的处理.........................................................................155 6.2.6 已注册的 I/O handlers 和 IRQ 清单 ..........................................................159 6.3 定时器(PIT)类分析.............................................................................................166 6.3.1 类 bx_pit_c 概述.........................................................................................166 6.3.2 类 bx_pit_c 源码注释.................................................................................166 6.3.3 关于类 bx_virt_timer_c ..............................................................................173 6.4 IDE 设备...................................................................................................................173 6.4.1 磁盘控制器 controller_t .............................................................................173 6.4.2 IDE 驱动器描述类 bx_hard_drive_c .........................................................174 6.4.3 读写磁盘映象.............................................................................................175 6.4.4 ATA/IDE 控制器(通道)和设备初始化.................................................176 6.4.5 ATA 控制器的寄存器读写 ........................................................................184 6.4.6 CD ROM 设备............................................................................................193 6.5 PCI 子系统 ...............................................................................................................203 6.5.1 PCI 概述 .....................................................................................................203 6.5.2 PCI 主桥描述类 bx_pci_c 分析.................................................................209 6.6 VGA 设备模拟.........................................................................................................215 6.6.1 模拟 VGA 的类 ..........................................................................................215 6.6.2 VGA 在 svga_timer 中的更新 ...................................................................216 Appendix ...................................................................................217 Appendix A.Bochs 配置文本说明 ....................................................................................217 Appendix B.ATA 和 ATAPI 编程介绍 .............................................................................225 Appendix C.Sound Blaster 16 编程...................................................................................230 Appendix D.VESA 编程介绍............................................................................................240 Appendix E.Bochs 上运行的操作系统示范.....................................................................261 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 1 Chapter 1 x86 体系结构与 PC 系统概要 Bochs 不仅仅模拟了 x86 CPU,还包括一整套 PC 系统的各种部件。因此在对 Bochs 进行研究之前,需要分析 x86 体系结构和 PC 系统。 1.1 x86 CPU 结构 1.1.1 冯诺依曼架构和 CISC 指令集 从系统结构上,计算机主要可以分为冯·诺伊曼结构和哈佛结构。冯·诺伊曼结构,也 称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。程序指 令存储地址和数据存储地址指向同一个存储器的不同物理位置,因此程序指令和数据的宽度 相同,Intel 公司 x86 就是使用了这个结构。当然还有其他的,如 MIPS 公司的 MIPS 处理器。 哈佛结构是一种将程序指令存储和数据存储分开的存储器结构。中央处理器首先到程序 指令存储器中读取程序指令内容,解码后得到数据地址,再到相应的数据存储器中读取数据, 并进行下一步的操作(通常是执行)。程序指令存储和数据存储分开,可以使指令和数据有 不同的数据宽度。哈佛结构的微处理器通常具有较高的执行效率。其程序指令和数据指令分 开组织和存储的,执行时可以预先读取下一条指令。使用哈佛结构的中央处理器和微控制器 有 Motorola 公司的 MC68 系列和 ARM 公司的 ARM9、ARM11 等。 从指令集划分,计算机可以分为复杂指令集计算机(CISC)和精简指令集计算机(RISC)。 从命名上就可以看出,CISC 包含了一套复杂的指令集。往往一条指令就可以完成一套复杂 的操作,而在 RISC 中,这些操作需要几条指令才能完成。CISC 和 RISC 各有利弊,一直称 为计算机结构讨论的热门话题,CISC 功能强大但译码器复杂,RISC 简洁但需要更多的指令 完成相同的操作。最终结论是 CISC 和 RISC 应该互相借鉴优点,而不应该在划分界线上争 论,事实上处理器的发展也正是沿这个趋势进行的。 1.1.2 CPU 结构 从物理结构上看,x86 CPU 结构比较复杂的,概括的说包括运算器,控制器以及辅助设 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 2 备如 MMU,FPU 以及高速缓存等,这也是一个处理器的基本结构,如图 2.1 所示。其核心 单元是取指-译码-执行单元,所有的 IA-32 指令都在这个单元内执行;控制单元起调度作用, 控制着执行单元的节拍;外部中断信号可以使执行单元发生转移,处理中断;FPU 辅助执 行单元处理浮点指令,MMU 实现虚拟地址,另外,为了提高执行效率,CPU 增加了指令高 速缓存和数据高速缓存来减少总线访问的次数,毕竟总线访问会占用大量的时间。 取指-译码-执行 单元 总线接口 高速缓存 (数据/指令) 控制单元 中断处理 MMU FPU TLB 外部总线 时钟 外部中断控制器 图 1.1 CPU 结构框图 实际的 CPU 还运用了很多加速技术,如流水线、乱序执行等。如果从逻辑层面上去描述, 所有的 CPU 操作都是围绕寄存器进行的。X86-32 的主要寄存器包括一组通用寄存器、段寄 存器、控制寄存器,CPU 状态 FFLAGS 以及程序指针 PC。虚拟机的实现的主要工作就是维 护这些寄存器的值,即通过 IA-32 的指令去修改和更新它们。 1.1.3 CPU 工作模式 x86 CPU 共有三种不同的工作模式加上一个准模式,即实模式、保护模式、系统管理模 式和虚拟 86 模式。 实模式是 x86 CPU 复位后首先进入的模式,实模式下的 32bit 处理器按照 16bit 8086 处 理器的方式执行指令,通过段寄存器左移 4 位加上偏移量进行寻址。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 3 在保护模式下段地址寄存器中内容的不再象实模式那样是段的基地址,而只是描述符表 中的一个索引,段的真正信息(基地址、限长、访问权限等)放在描述符表中,当访问一数 据时 CPU 会从描述符表取出段的描述信息来检查访问是否合法,不合法就产生异常,合法 则允许访问。每次访问都要读出描述符信息再检查是一个比较费时的过程,为了提高内存访 问的速度,Intel 公司在 CPU 中为每个段寄存器配备了一个高速缓冲器来存放段的描述符信 息,这样访问内存时就不用频繁地访问描述表,只要从高速缓冲进行校验就行,只有在改变 段寄存器的值时才访问描述符表将新的段描述符装入高速缓存中。 关于系统管理模式和虚拟 8086 模式,可以参考 Intel 体系结构设计手册。 1.2 x86 体系结构概览 x86 体系结构经过二十几年的发展,已经具备了非常成熟的分段分页机制和存储保护机制, 下面一副摘自 Intel IA-32 体系结构设计手册(卷 1)的图可以概括 32 位 x86 系统的主要架 构: Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 4 段描述符 控制寄存器 CR0- CR4 GDTR 6个通用寄存器 段寄存器 任务寄存器 TS EFLAGS TSS描述符 段描述符 TSS描述符 LDT描述符 段描述符 LDTR 调用门 中断门 陷阱门 任务门 IDTR 调用门段选择符 中断向量 中断向量表 IDT 全局描述符表 GDT 局部描述符表 LDT 代码/数据/堆栈段 任务状态段 TSS 代码 数据 堆栈 代码 堆栈 任务状态段 TSS 代码 数据 堆栈 当前TSS 任务 任务 中断handler 代码 堆栈当前TSS 异常handler 代码 堆栈当前TSS 保护模式的程序段 段选择符 TSS选择符 线性地址 段选择符 寄存器 线性地址 线性地址空间 DIR Table Offset 页目录条目 页目录 页表条目 页表 物理地址 物理页 CR3 该示例描述了 32-bit物理地址 ,页面大小为 4K页的寻址过程 物理地址 图 1.2 x86 体系结构概览 图 1.2 的上半部分描绘了 x86 体系结构的主要寄存器和系统数据结构的关系。保护模 式下,内存存在 3 种类型的表,它们是全局描述符表(GDT)、中断描述符表(IDT)和局 部描述符表(LDT)。其中 GDT 和 IDT 各只有一个,而 LDT 则有多个,分别对应于不同的 进程。寄存器 GDTR 和 IDTR 指向 GDT 和 IDT,LDTR 则指向当前进程的 LDT。选择符和 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 5 描述符是两个用的最多的数据结构,保护模式下的段寄存器存放着各个段选择符。中断门、 陷阱门、调用门等各种门也是选择符,它们存在于 IDT 和 LDT 中。 图 1.2 的下半部分则是以 4K 页面大小作为例子,给出了 32 位线性地址到 32 位物理地 址的映射过程。X86 为二级页表结构,需要经过页目录和页表两次查找再加上偏移量得到物 理地址。CR3 寄存器存放着页目录的基地址。当发生进程切换时,改变 CR3 的值就相当于 切换了整个 32 位线性空间。 1.3 PC 系统 1.3.1 PC 系统概述 随着 CPU 性能的提升和外设的复杂性增大,PC 系统也变得越来越庞大且越来越复杂, 但就框架来说都是基于 Intel 南-北桥结构的。图 1.3 描绘了基于 Intel 815E 芯片组的系统架 构: 82815GMCH(北桥) 82801BA(南桥) VGA接口 x86 CPU IDE 键盘/鼠标 系统BIOS 100/133MHz SDRAM 系统总线 Super I/O LAN 音频 USB PCI设备 PCI总线 串口/并口 LPC总线 HUB总线 图 1.3 由 815E 芯片组构成的系统框图 815E 芯片组由北桥芯片 82815(GMCH)和南桥芯片 82801BA(ICH2)组成,GMCH 为图形和内存控制中心的简称,它作为 CPU、存储器和外设交换的主要通路,它拥有和 CPU Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 6 的接口(64 位系统总线)、SDRAM 接口和 ICH2(输入/输出控制中心)的 Hub 接口。82815 GMCH 除了提供 AGP 扩展功能外,内部还集成了显卡功能。除了显示功能,其他绝大多数 的外设功能由 82801BA 提供,PC/104 需要用到的功能包括 PCI 总线,IDE 接口,LAN,音 频和 USB,传统外设诸如键盘、鼠标、串/并口由挂接在 LPC 总线(一种 ISA 总线的换代版 本)的 Super I/O 芯片连接。 1.3.2 总线拓扑 PC 作为一个完整的计算机系统,发展到至今,出现了各种各样的总线,如处理器本地 总线、AGP 总线、PCI 总线、ISA 总线以及 USB 总线等。在进行 PC 虚拟的时候,必须理 清除它们之间的关系,包括物理关系和逻辑关系(主要是逻辑关系)。 从图 2.3 我们已经大致了解了这些总线的物理连接关系,处理器本地总线连接 CPU 和 北桥芯片,南桥芯片实际是和北桥协同工作的,可以作为一个整体(芯片组)来看待。芯片 组里包含了丰富的功能,如 PCI 主桥,显示控制器,IDE 控制器等,实际上这些都是连接在 处理器总线上。因此,这些设备的寄存器分布在 CPU 的 IO 和存储器空间范围内。在下面的 一节里将会作详细分析。 1.3.3 存储器与 I/O 编址 1.2.3.1 存储器编址 从 80386DX 开始,Intel 处理器就可以寻址 4GB 的存储器地址空间,这 4GB 的空间被分为 四个基本区域(有一些区域被进一步细分): 1)常规内存:系统内存的第一个 640 KB 就是著名的常规内存。它是标准 DOS 程序、DOS 驱动程序、常驻内存程序等可用的区域,它们统统都被放置在 00000h~9FFFFh 之间。 2)上位内存区(Upper Memory Area,UMA):系统内存的第一个 1M 内存顶端的 384 KB(1024 KB - 640 KB)就是 UMA,它紧随在常规内存之后。也就是说,第一个 1MB 内存被分成 640KB 常规内存和 384KB 的 UMA。这个区域是系统保留区域,用户程序不能使用它。它一部分被 系统设备(CGA、VGA 等)使用,另外一部分被用做 ROM 投影和 Drivers。UMA 使用内 存区域 A0000H~FFFFFH。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 7 3)高端内存区(High Memory Area):系统内存第 2 个 1M 内存的第一个 64 KB 区域,被称做 HMA。从技术上讲,它属于扩展内存的第一个 64 KB,但它和其他扩展内存区域所不同的 是,它可以在实模式下被直接访问,其它的则不然。所以在 DOS 时代,后期的 DOS 版本允 许用户通过配置将 DOS 本身放置在 HMA,从而让用户可以有更多的常规内存可以使用。 HMA 占据地址 100000H~10FFEFH。 4)扩展内存(Extended Memory):从 HMA 结束的位置到系统物理内存的最大值之间的区域都 属于扩展内存。当一个 OS 运行在保护模式下时,它可以被访问,而在实模式下,则无法被 访问( 除非通过某些特殊方法) 。它的地址范围是 10FFF0h~Last address of system memory(maximum of 4G-1M)。从技术上说,HMA 也属于扩展内存,这依赖于如何看待这个 问题。 扩展内存 A0000H 常规内存 上位内存区 高端内存 0 10FFEFH 100000H FFFFFFFFH(4G) 扩展内存 图 1.4 PC 系统的内存分布 16 位的 PC 时代,常规内存是焦点,而当前,扩展空间则被 PC 系统大量的使用,绝大 多数物理内存都映射在这个空间,此外,一些设备如 BIOS ROM 也被映射到 4GB 的最顶端, 显示缓冲区和 PCI 内存区也在这一地址段内。 从 CPU 发出的地址信号送到北桥芯片(GMCH),北桥芯片根据预先的配置(一部分由 软件配置由 BIOS 完成)进行译码,译码后的地址送到对应的物理设备中。这些设备可以是 BIOS ROM,SDRAM,或者 PCI 的存储空间。 1.3.3.2 I/O 编址 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 8 常规设备的 IO 地址在制定 PC 标志的时候就已经确定下来,以后一直沿用下去。下表列出 了部分设备的 IO 地址作为示例。X86 处理器用 IN/OUT 指令进行 I/O 访问。 地址 xx0h - xx3h xx4h - xx7h xx8h - xxBh xxCh - xxFh 000-00Fh DMA 控制器,通道 0 - 3 010-01Fh 系统保留 020-02Fh 中断控制器 #1 (020-021h) 系统保留 030-03Fh 系统保留 040-04Fh 系统定时器 系统保留 050-05Fh 系统保留 060-06Fh 键盘& PS/2 鼠标 (060h), 扬声器 (061h) 键盘 & PS/2 鼠标 (064h) … 软盘控制器 COM1 (串行口 1) 3F0-3FFh 磁道加速卡 (3F0h) 第一个 IDE 控制器 (从设备) (3F6-3F7h) 1.3.4 关于系统的启动与引导 1.3.4.1 系统复位 系统上电或 RESET 管脚,处理器会执行硬件初始化(常被称为硬件初始化)和一个可 选的内建自检(BIST)。硬件复位使 CPU 的内部寄存器到可知状态且处理器进入实模式, 同时,禁用内部高速缓存、TLB 以及分支目标缓冲(BTB)。若系统是多处理器系统,BSP (主处理器)唤醒其它的 AP,让其执行自我配置代码。当所有的处理器都初始化并配置和 同步好, BSP 开始初始化操作系统。本论文中不对多处理器系统做深入讨论。 下表中列出了 IA-32 的 P6 家族处理器复位后部分寄存器的值: Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 9 寄存器名称 类型 寄存器复位值 EFLAG 标志位 00000002H EIP 程序计数器 0000FFF0H CR0 控制寄存器 60000010H CR2,CR3,CR4 控制寄存器 00000000H CS 段寄存器 选择符 = F000H 基地址= FFFF0000H 限量 = FFFFH AR = Present,R/W,Accessed SS,DS,ES,FS,GS 段寄存器 选择符 = 0000H 基地址 = 00000000H 限量 = FFFFH AR = Present,R/W,Accessed EDX 通用寄存器 00000FxxH EAX 通用寄存器 0 EBX,ECX,ESI,EDI,EBP,ESP 通用寄存器 00000000H 表:P6 家族处理器寄存器复位值 1.3.4.2 关于第一条指令 处理器硬件复位后,取的第一条指令的地址是 FFFFFFF0H, 即物理地址的最高地址 往下 16 个字节,这个位置正好是 BIOS EPROM 所在的位置。FFFFFFF0H 是一个 1M 字节 以外的地址,是通过以下方式得到的: CS 寄存器由两部分组成,可见的段选择符和不可见 的基地址。 在实模式下,基地址是应该由 16 位段选择符左移 4 位得到 20 位的基地址。但 是,在硬件复位时,段选择符是 F000H 而不可见的部分是 FFFF0000H。取指令的地址应该 是 CS 的基地址部分加上 EIP 寄存器的值,即 FFFF0000H + FFFF0H = FFFFFFF0H。 为了能保证 BIOS EPROM 中的指令代码执行完,代码中是不能含有远转跳或远调用, Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 10 因为这些都会导致 CS 的基地址被重新加载,加载规则是 CS 段选择符×16(落在低端 1M), 这个范围不在当前 BIOS 代码范围。当然,一旦 BIOS 程序执行完并从磁盘读入引导代码到 内存,就会正常的通过该调用转跳到 1MB 低端内存中去执行。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 11 Chapter 2 Bochs 工程概述 2.1 开源项目 Bochs 介绍 Bochs是一个用C++写成的高移植性的开源IA-32架构的PC模拟器,可以在很多的流 行操作系统上运行。它模拟了Intel x86 CPU,常见IO设备以及一个BIOS。 当前, Bochs可 以编译为模拟386,486,Pentium,Pentium Pro或AMD64 CPU,包括可选的MMX,SSESSE 和3DNow!指令集。Bochs可以运行的操作系统包括Linux,DOS,Windows。Bochs由Kevin Lawton在1994年开始编写。它最初是作为商业产品被开发的,但到了2000年3月, Mandrakesoft软件公司买下了Bochs,使之成为遵循GNU LGPL的开源软件。2001年3月,Kevin 帮助一些开发者把Bochs移到了sourceforge.net网站。 Bochs 可以使用不同的模式编译,有些还在开发之中。典型的应用就是提供了完整的 x86 PC 模拟器,包括 x86 CPU、硬件设备以及存储器。因为是一个开源的项目,在互联网 上得到了世界各地的程序爱好者的支持,不断的对其进行功能完善并修改存在的错误,在 2006 年 1 月 29 日,Bochs 的最新版本 2.2.6 版已经发行。 访问 Bochs 的网站 http://bochs.sourceforge.net 得到最新版本。 2.2 版本 2.2.1 源码组织 Bochs有众多源码版本,每个版本又有对应不同编译器的工程。以下以 2.2.1 for VC+ +版本(Windows 平台编译)做介绍。 Bochs v2.2.1 总共有 300 多个源文件(.h 和.cpp)约 200,000 行代码,根目录下是 VC 工程文件(.dsp 和.dsw) 和一些全局头文件如 Bochs.h 和主函数 main 所在的 main.cpp。 根目录下有以下文件夹: Bios 系统 BIOS 代码 Bx_debug Bochs 调试器的代码 Cpu 模拟 x86 CPU Dasm 反汇编,用于 bochs 调试版本中 Docs-html Html 格式的部分文档 Doc 部分文档 Font VGA 字体点阵 Fpu 浮点处理器 Gui 图形界面,和 OS 相关 Instrument 辅助插件 Iodev IO 设备 Memory 模拟存储器源代码 Misc 一些其它的部件源代码 用 VC6.0 打开 bochs.dsw 工作区文件,可以看到工作区的结构: Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 12 整个 Bochs 工作区包括 12 个工程,其中有些工程编译产生 Bochs 辅助工具,如 bximage 编 译产生 bximage.exe,该工具用来创建一个虚拟的硬盘。Bochs.exe 是由 bochs 工程编译生成 的,它依赖于其它几个工程,依赖关系如下: 因此,在对 bochs 工程进行编译之前,先要编译 iodev, cpu, memory 等工程,生成相应的 lib 供使用。 2.3 工程类结构 Bochs 的绝大多数代码都是 C++写出,因此具有很好的封装性,可读性也较强。所有的 类继承于 logfunctions 的类,该类完成了简单的日志功能,方便调试时输出相关信息。所有 的类中,三个主要类构成了 Bochs 的基石,它们是 „ BX_CPU_C - 描述 CPU „ BX_MEM_C - 描述储存器 „ IO device 的一系列派生类 而描述 PC 系统板的类 bx_pc_system_c 将它们连接起来形成一台完整的 PC。 在 Bochs 中引入了绝大多数的标准 PC 外设,为了统一外设的接口,Bochs 为每一类设 备定义了一个桩(stub)类,stub 中的虚函数提供统一接口,如存储器读写、I/O 读写等。这些 虚函数的最终实现是由具体设备中的函数完成的。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 13 bx_devmodel_c bx_pc_system_c BX_CPU_C BX_MEM_C bx_busm_stub_c bx_cmos_stub_c bx_dma_stub_c bx_floppy_stub_c bx_hard_drive_stub_c bx_keyb_stub_c bx_ne2k_stub_c bx_pci2isa_stub_c logfuctions bx_devices_c bx_pci_stub_c bx_pic_stub_c bx_serial_stub_c bx_speaker_stub_c bx_vga_stub_c 图 2.1 Bochs 工程的类结构 2.4 主体框架结构分析 2.4.1 Bochs 工程中的重要类 (1) VM 控制台界面类 class BOCHSAPI bx_gui_c : public logfunctions 从 bx_gui_c 派生出: … bx_win32_gui_c Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 14 … bx_svga_gui_c … bx_term_gui_c … bx_sdl_gui_c … bx_wx_gui_c … bx_rfb_gui_c … bx_nogui_gui_c … bx_macintosh_gui_c … bx_beos_gui_c … bx_amigaos_gui_c … bx_carbon_gui_c … bx_x_gui_c config.h 中进行配置,选择一个控制台界面 #define BX_WITH_X11 0 #define BX_WITH_BEOS 0 #define BX_WITH_WIN32 1 #define BX_WITH_MACOS 0 #define BX_WITH_CARBON 0 #define BX_WITH_NOGUI 0 #define BX_WITH_TERM 0 #define BX_WITH_RFB 0 #define BX_WITH_AMIGAOS 0 #define BX_WITH_SDL 0 #define BX_WITH_SVGA 0 #define BX_WITH_WX 0 (2) CPU 模拟 类 BX_CPU_C 用来描述 CPU class BOCHSAPI BX_CPU_C : public logfunctions 类 bxInstruction_c 定义一条指令,无基类 (3) Memory 模拟 类 BX_MEM_C class BOCHSAPI BX_MEM_C : public logfunctions // memory/memory.h 变量 #if BX_PROVIDE_CPU_MEMORY==1 #if BX_SMP_PROCESSORS==1 BOCHSAPI extern BX_MEM_C bx_mem; //外部全局变量 #else Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 15 BOCHSAPI extern BX_MEM_C *bx_mem_array[BX_ADDRESS_SPACES]; //对多处理器,使用 内存组 #endif /* BX_SMP_PROCESSORS */ #endif /* BX_PROVIDE_CPU_MEMORY==1 */ 全局变量 BOCHSAPI BX_CPU_C bx_cpu; BOCHSAPI BX_MEM_C bx_mem; (4).I/O device 模拟 1. Class bx_devices_c : public logfunctions 2. Class bx_devmodel_c : public logfunctions // iodev/iodev.h 派生类列表: bx_busm_stub_c //bus mouse bx_cmos_stub_c bx_devmodel_c bx_dma_stub_c bx_floppy_stub_c bx_hard_drive_stub_c bx_keyb_stub_c bx_ne2k_stub_c bx_pci2isa_stub_c bx_pci_ide_stub_c bx_pci_stub_c bx_pic_stub_c bx_serial_stub_c bx_speaker_stub_c bx_usb_stub_c bx_vga_stub_c 2.4.2 入口函数 main()及 Win32 Gui 初始化 Main.cpp 提供了宏定义用来选择编译器入口函数,默认的为 main: 入口函数选择 #if defined(__WXMSW__) WinMain() //使用 wxWidgets/win32 #if !defined(__WXMSW__) main() Main 函数: int main (int argc, char *argv[]) { bx_startup_flags.argc = argc; //全局变量,类型 BOCHSAPI,用于记录命令行参数 bx_startup_flags.argv = argv; #if BX_WITH_SDL && defined(WIN32) // if SDL/win32, try to create a console window. RedirectIOToConsole (); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 16 #endif return bxmain (); } 函数 bxmain () … 调用 bx_init_siminterface (); // create the SIM object { … SIM = new bx_real_sim_c(); …} 类 bx_real_sim_c 定义在 gui/simuinterface.cpp 中,从 bx_simulator_interface_c 派生 (gui/simuinterface.h) 调用 bx_init_main() #define BX_USE_TEXTCONFIG 1 模式定义为文本配置模式 class bx_real_sim_c : public bx_simulator_interface_c { … config_interface_callback_t ci_callback; //成员 //typedef int (*config_interface_callback_t)(void *userdata, ci_command_t command); … } 函数 ci_callback()被 bx_real_sim_c::configuration_interface()调用,而 configuration_interface()是在两个 地方被调用的 (1)bx_gui_c::config_handler 中, config_handler 被 bx_gui_c::init 调用。 (2)bxmain() 函数 ci_callback()中会调用 bx_config_interface() // gui/textconfig.cpp 中 case BX_CI_START_SIMULATION: { SIM->begin_simulation (bx_startup_flags.argc, bx_startup_flags.argv); // we don't expect it to return, but if it does, quit SIM->quit_sim(1); break; begin_simulation()调用 bx_begin_simulation() 函数 bx_begin_simulation() 1. 调用 load_and_init_display_lib() 初始化 GUI 对象 2. 如果定义 BX_DEBUGGER, 调用 bx_dbg_main(argc, argv); 3. 调用 bx_init_hardware() //in main.cpp 注意:是这个函数的执行初始化了 GUI! 4. bx_load32bitOSimagehack ??? 5. SIM->set_init_done (1); //这里应该是表示初始化完成 6. …. 7. 调用 BX_CPU(0)->cpu_loop(1); //启动 CPU,进入循环 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 17 // for one processor, the only reason for cpu_loop to return is // that kill_bochs_request was set by the GUI interface. bx_init_hardware() {… io->set_log_action (level, action); bx_pc_system.init_ips(bx_options.Oips->get ()); // bx_pc_system_c 类型变量,设置时钟频 率 memSize = bx_options.memory.Osize->get ()*1024*1024; //从配置文件获取内存容量 //单处理器的情况下 #if BX_SMP_PROCESSORS==1 #define BX_CPU(x) (&bx_cpu) #define BX_MEM(x) (&bx_mem) BX_MEM(0)->init_memory(memSize); //初始化内存 // 加载 BIOS and VGABIOS BX_MEM(0)->load_ROM(bx_options.rom.Opath->getptr (), bx_options.rom.Oaddress->get (), 0); BX_MEM(0)->load_ROM(bx_options.vgarom.Opath->getptr (), 0xc0000, 1); // Then load the optional ROM images …. BX_CPU(0)->init (BX_MEM(0)); //初始化处理器 BX_CPU(0)->set_cpu_id(0); BX_CPU(0)->sanity_checks(); //测试 CPU BX_INSTR_INIT(0); //可能不做什么 BX_CPU(0)->reset(BX_RESET_HARDWARE); … DEV_init_devices(); //初始化外设 DEV_reset_devices(BX_RESET_HARDWARE); bx_gui->init_signal_handlers (); bx_pc_system.start_timers(); }//end of init_hardware() 关于 win32 GUI 的启动和初始化 在 plugin.h 中定义了宏 #define PLUG_load_plugin(name,type) {lib##name##_LTX_plugin_init(NULL,type,0,NULL);} 查找 libwin32_LTX_plugin_init,没有找到。 从另外一条路出发: 在 gui\gui.h 中发现这样一个宏 #define IMPLEMENT_GUI_PLUGIN_CODE(gui_name) \ int lib##gui_name##_LTX_plugin_init(plugin_t *plugin, \ plugintype_t type, int argc, char *argv[]) { \ Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 18 genlog->info("installing %s module as the Bochs GUI", #gui_name); \ theGui = new bx_##gui_name##_gui_c (); //创建 GUI 对象 \ bx_gui = theGui; \ return(0); /* Success */ \ } \ 在 gui\win32.cpp 中找到了这个宏的一个扩展 IMPLEMENT_GUI_PLUGIN_CODE(win32) 即扩展成一个名字为 libwin32_LTX_plugin_init 的函数, 正是要找的。 函数调用了 bx_win32_gui_c(), 在 project 中查找 bx_win32_gui_c()函数,该函数是类 bx_win32_gui_c 的构造函数,由此可见 win32 的 GUI 是在类 bx_win32_gui_c 中实现的。 2.5 Bochs 的工作方式 和实际 CPU 的原理一样,Bochs 虚拟机 CPU 的运行方式为: 取指Æ执行Æ输出结果。 CPU 复位后进入实模式, EIP = 0x0000FFF0; CS 的 cache 内的 Base= 0xFFFF0000, 两种相加 得到 CS:EIP=0xFFFFFFF0, 该位置是 BIOS ROM 所在位置. 用户程序从该处开始运行。进入 CPU 循环后,除了取指和执行工作外,每次还要检测标志位,如果没有复位或退出命令, CPU 循环将会一直进行下去。 整个过程如下图所示: 上电复位 开始进入CPU循环 检测异步事件标志位(中断, 退出...) 译码和执行 取指令 可能出现的分支 输出结果 输入(指令/数据) 图 2.2 虚拟机工作流程 以上简要的阐述 Bochs 是如何运行的,实际上为了能尽可能准确的仿真出一台 PC, Bochs 完成了大量繁琐且复杂的工作。在下面的章节中将会具体分析 Bochs 对 CPU 的模拟 以及整个 PC 系统的仿真。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 19 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 20 Chapter 3 CPU 类的源码分析 3.1 CPU 类概述 3.1.1 CPU 逻辑结构框图 Bochs 用 BX_CPU_C 来描述 CPU,经过认真阅读源码,归纳出虚拟 CPU 的逻辑结构如下 所示: CPU初始化 CPU复位 reset() TLB_init() TLB_flush() TLB 存储器指针mem* 存储器访问函数 I/O访问函数 异步事件处理函数handleAsyncEvent() 执行指令 取指、译码 寄存器组 I387协处理器 各种标志位 外部存储器空间 外部I/O空间 CPU循环 INTR信号线 中断和异常处理函数 更新TLB 读取指令码 读写I/O 读写存储器 TLB失效 数据交换 执行浮点指令 标志位更新 更新指令计数器, 作为系统时钟源 外部中断控制器 3.1.2 类 BX_CPU_C 成员归纳 Bochs 描述 CPU 的类名是 BX_CPU_C,这个类也是 bochs 工程中最复杂的一个类,其成 员变量包括: 1.名称和 CPUID 2. 所有的 CPU 寄存器(通用寄存器,段寄存器,控制寄存器,状态字 EFLAG 以及系统数 据结构 GDTR 等), 3. i387 协处理器 4. 指向存储器 mem 的指针 5. 中断标识 EXT 及中断信号线 INTR 6. 异常处理用到的变量(用于恢复执行,如备份 EIP 使指令能够重新执行) 7. CPU 工作模式(实模式/保护模式) 8. 异步事件标志位 9. TLB,1024 条目 10. 存放转换后地址的数据单元 address_xlation 11. 指令缓存 iCache Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 21 12. 辅助译码/执行的自定义数据 成员函数包括: 1. CPU 初始化函数 init(),各种模式初始化函数 real_mode_int(),protected_mode_int() 2. 指令执行函数,包括 9 300 多个执行普通指令的函数 9 将近 100 个执行浮点指令的函数 9 50 多个 MMX 指令函数 9 定义了一些 3Dnow 指令函数原型,但没有实现 9 一些 SSE 指令集函数 3. 取指及译码函数 9 取指函数 fetchDecode() 9 一系列指令译码函数,如 Resolve16Mod0Rm0()等 9 边界取指 boundaryFetch() 9 取指准备 prefetch()已经其它 4. CPU 循环 cpu_loop() 5. 异步事件处理函数 handleAsyncEvent() 6. 存储器访问函数 9 读写权限检查 write_virtual_checks,read_virtual_checks()等 9 读写数据 read_RMW_virtual_byte,write_RMW_virtual_byte()等 7. TLB 相关,如初始化 TLB 的函数 TLB_init(),TLB 刷新 TLB_flush() 8. 中断相关,置 CPU 中断 set_INTR()和中断处理函数 interrupt() 9.执行异常处理 exception() 10. CPU 复位 reset() 11. IO 访问函数 inp32(),outp32()等 12. 任务切换 task_switch() 13. 读写各寄存器的函数 14. 管理 FPU,MMX 的函数 15. 其它一些辅助函数 16.构造与析构 3.2 类 BX_CPU_C 源码分析 代码位于 CPU\CPU.cpp 中 3.2.1 CPU 特性声明 #define BX_SUPPORT_BUSMOUSE 0 #define BX_SUPPORT_FPU 1 #define BX_SUPPORT_MMX 1 #define BX_SUPPORT_3DNOW 0 #define BX_SUPPORT_SSE 1 #define BX_SUPPORT_DAZ 0 #define BX_SUPPORT_PNI 0 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 22 #define BX_SUPPORT_SEP 0 #define BX_SUPPORT_4MEG_PAGES 1 #define BX_SupportGuest2HostTLB 1 #define BX_SupportRepeatSpeedups 1 #define BX_SupportGlobalPages 1 #define BX_SupportPAE 1 #define BX_SupportHostAsms 1 #define BX_SupportHostAsmsFpu 0 #define BX_SUPPORT_ICACHE 1 3.2.2 类 bxInstruction_c 成员分析 函数指针 void ( BX_CPP_AttrRegparmN(1) *ResolveModrm )(bxInstruction_c *); //译码函数指针 void (*execute)(bxInstruction_c *); //执行函数指针 unsigned metaInfo; // 26..23 ilen (0..15). Leave this one on top so no mask is needed. // 22..22 mod==c0 (modrm) // 21..13 b1 (9bits of opcode; 1byte-op=0..255, 2byte-op=256..511. // 12..12 BxRepeatableZF (pass-thru from fetchdecode attributes) // 11..11 BxRepeatable (pass-thru from fetchdecode attributes) // 10...9 repUsed (0=none, 2=0xF2, 3=0xF3). // 8...8 extend8bit // 7...7 as64 // 6...6 os64 // 5...5 as32 // 4...4 os32 // 3...3 (unused) // 2...0 seg 几种格式的指令定义成一个 Union … modRMForm … IxIxForm … IxForm … IqForm //64bit x86 CPU 一些 Utility 函数,获取指令中的信息, 如 BX_CPP_INLINE unsigned opcodeReg() { return IxForm.opcodeReg; } BX_CPP_INLINE unsigned modrm() { return (modRMForm.modRMData>>20) & 0xff; } BX_CPP_INLINE unsigned mod() { return modRMForm.modRMData & 0xc0; } 处理 metaInfo 变量的一些函数 BX_CPP_INLINE void initMetaInfo() BX_CPP_INLINE unsigned seg(void) { return metaInfo & 7;} BX_CPP_INLINE void setSeg(unsigned val) { metaInfo = (metaInfo & ~7) | val; } BX_CPP_INLINE unsigned os32L(void) { return metaInfo & (1<<4);} 3.2.3 类 BX_CPU_C 源码注释 定义 class BOCHSAPI BX_CPU_C : public logfunctions Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 23 成员变量 char name[64]; unsigned bx_cpuid; //用于 SMP 的标识 // General register set // eax: accumulator // ebx: base // ecx: count // edx: data // ebp: base pointer // esi: source index // edi: destination index // esp: stack pointer bx_gen_reg_t gen_reg[BX_GENERAL_REGISTERS]; //8 个通用寄存器 Bit32u eip; // instruction pointer // status and control flags register set Bit32u lf_flags_status; //??? bx_flags_reg_t eflags; // bx_flags_reg_t 是一个结构体, 包含两个成员变量 { Bit32u val32; // Raw 32-bit value in x86 bit position.Used to store some eflags which are not cached in separate fields. Bit32u VM_cached; //定义了 DECLARE_EFLAG_ACCESSOR 和 IMPLEMENT_EFLAG_ACCESSOR 来访问 EFLAG 寄存器 中的标//志位 } bx_lf_flags_entry oszapc; bx_lf_flags_entry oszap; // bx_if_flags_entry 是一个结构体(lazy_flags.h), 包含成员变量 { Bit8u op1_8; Bit8u op2_8; Bit8u result_8; Bit16u op1_16; Bit16u op2_16; Bit16u result_16; Bit32u op1_32; Bit32u op2_32; Bit32u result_32; unsigned instr; } //出错时,可以用下面两个变量恢复上一状态 bx_address prev_eip; bx_address prev_esp; unsigned inhibit_mask; //异步事件屏蔽位,主要事件有 BX_INHIBIT_INTERRUPTS(0x01) 和 BX_INHIBIT_DEBUG(0x02)两种 bx_segment_reg_t sregs[6]; //段寄存器 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 24 bx_global_segment_reg_t gdtr; /* global descriptor table register */ bx_global_segment_reg_t idtr; /* interrupt descriptor table register */ bx_segment_reg_t ldtr; /* local descriptor table register */ bx_segment_reg_t tr; /* task register */ /* DR0 - DR7 (Debug Register 0-7), unimplemented */ /* TR3 - TR7 (Test Register 3-7), unimplemented */ //控制寄存器 bx_cr0_t cr0; Bit32u cr1; bx_address cr2; bx_address cr3; bx_address cr3_masked; bx_cr4_t cr4; //486+ bx_regs_msr_t msr; //586+ i387_t the_i387; //浮点支持或者使用 MMX 指令 bx_xmm_reg_t xmm[BX_XMM_REGISTERS]; //SSE bx_mxcsr_t mxcsr; //SSE BX_MEM_C *mem; //指向系统 RAM 的指针 bx_bool EXT; /* 1 if processing external interrupt or exception * or if not related to current instruction, * 0 if current CS:IP caused exception */ unsigned errorno; /* signal exception during instruction emulation */ Bit32u debug_trap; // holds DR6 value to be set as well volatile bx_bool async_event; volatile bx_bool INTR; volatile bx_bool kill_bochs_request; /* wether this CPU is the BSP always set for UP */ bx_bool bsp; // for accessing registers by index number Bit16u *_16bit_base_reg[8]; Bit16u *_16bit_index_reg[8]; Bit32u empty_register; //用于译码指令; accessing seg reg's by index unsigned sreg_mod00_rm16[8]; unsigned sreg_mod01or10_rm16[8]; unsigned sreg_mod01or10_rm32[8]; unsigned sreg_mod0_base32[8]; unsigned sreg_mod1or2_base32[8]; // 用于异常处理 jmp_buf jmp_buf_env; Bit8u curr_exception[2]; bx_segment_reg_t save_cs; bx_segment_reg_t save_ss; Bit32u save_eip; Bit32u save_esp; // This help for OS/2 bx_bool except_chk; Bit16u except_cs; Bit16u except_ss; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 25 // Boundaries of current page, based on EIP bx_address eipPageBias; bx_address eipPageWindowSize; Bit8u *eipFetchPtr; Bit32u pAddrA20Page; // Guest physical address of current instruction // page with A20() already applied. unsigned cpu_mode; // for paging 用于分页管理 #if BX_USE_TLB struct { bx_TLB_entry entry[BX_TLB_SIZE] BX_CPP_AlignN(16); # define BX_TLB_LPF_VALUE(lpf) (lpf) } TLB; #endif // #if BX_USE_TLB //指令缓存 bxICache_c iCache BX_CPP_AlignN(32); //如果使用 Instruction cache //用于地址翻译的结构体 struct { bx_address rm_addr; // The address offset after resolution. Bit32u paddress1; // physical address after translation of 1st len1 bytes of data Bit32u paddress2; // physical address after translation of 2nd len2 bytes of data Bit32u len1; // Number of bytes in page 1 Bit32u len2; // Number of bytes in page 2 bx_ptr_equiv_t pages; // Number of pages access spans (1 or 2). Also used // for the case when a native host pointer is // available for the R-M-W instructions. The host // pointer is stuffed here. Since this field has // to be checked anyways (and thus cached), if it // is greated than 2 (the maximum possible for // normal cases) it is a native pointer and is used // for a direct write access. } address_xlation; //定义 ArithmeticalFlag 宏操作 Eflag 中的各标志位 #define ArithmeticalFlag(flag, lfMaskShift, eflagsBitShift) \ 成员函数 //构造与析构 BX_CPU_C(); virtual ~BX_CPU_C(); //初始化,参数为系统 RAM 的指针 void init (BX_MEM_C *addrspace); //CPU 指令模拟执行函数组 BX_SMF void ADD_EbGb(bxInstruction_c *); BX_SMF void ADD_EdGd(bxInstruction_c *); ……. 这些函数分布在 arith8.cpp arith16.cpp arith32.cpp arith32.cpp Logical8.cpp Logical16.cpp Logical32.cpp Logical64.cpp Shift8.cpp Shift16.cpp Shift32.cpp Shift64.cpp Multi8.cpp Multi16.cpp Multi32.cpp Multi64.cpp Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 26 Bcd.cpp Stack16.cpp stack32.cpp stack64.cpp Io.cpp Soft_int.cpp Data_xfer8.cpp Data_xfer16.cpp Data_xfer32.cpp Data_xfer64.cpp Ctrl_xfer8.cpp Ctrl_xfer16.cpp Ctrl_xfer32.cpp Ctrl_xfer64.cpp Flag_ctrl.cpp String.cpp Proc_ctrl.cpp Protect_ctrl.cpp Segment_ctrl.cpp Bit.cpp Cpuid.cpp Fpu_emu.cpp Fpu_load_store.cpp Fpu_const.cpp Fpu_arith.cpp Fpu_trans.cpp mmx.cpp 3dnow.cpp sse_XXX.cpp //取指 BX_SMF unsigned fetchDecode(Bit8u *, bxInstruction_c *, unsigned); // 48 个寻址函数, 在 Resovle16.cpp Resovle32.cpp Resovle64.cpp 中实现 BX_SMF void Resolve16Mod0Rm0(bxInstruction_c *) BX_CPP_AttrRegparmN(1); …… //一些辅助函数 now for some ancillary functions... BX_SMF void cpu_loop(Bit32s max_instr_count); //Cpu.cpp BX_SMF unsigned handleAsyncEvent(void); //Cpu.cpp BX_SMF void boundaryFetch(Bit8u *fetchPtr, unsigned remainingInPage, bxInstruction_c *i); BX_SMF void prefetch(void); //Cpu.cpp // revalidate_prefetch_q is now a no-op, due to the newer EIP window // technique. BX_SMF BX_CPP_INLINE void revalidate_prefetch_q(void) { } //空函数 ctrl_ BX_SMF BX_CPP_INLINE void invalidate_prefetch_q(void) { BX_CPU_THIS_PTR eipPageWindowSize = 0; } //一些段检查函数,包括段的读写, 在 access.cpp 中实现 BX_SMF void write_virtual_checks(bx_segment_reg_t *seg, bx_address offset, unsigned length) BX_CPP_AttrRegparmN(3); …… //转跳 BX_SMF void branch_near32(Bit32u new_eip) BX_CPP_AttrRegparmN(1); // ctrl_xfer??.cpp //关于 BX_SupportRepeatSpeedups 的一组函数,加速?? String.h 中实现 BX_SMF Bit32u FastRepMOVSB(bxInstruction_c *i, unsigned srcSeg, bx_address srcOff, unsigned dstSeg, bx_address dstOff, Bit32u count); //几个关于分页 Paging.cpp BX_SMF void access_linear(bx_address address, unsigned length, unsigned pl, unsigned rw, void *data) BX_CPP_AttrRegparmN(3); BX_SMF Bit32u translate_linear(bx_address laddr, unsigned pl, unsigned rw, unsigned access_type) BX_CPP_AttrRegparmN(3); BX_SMF Bit32u itranslate_linear(bx_address laddr, unsigned pl) BX_CPP_AttrRegparmN(2); BX_SMF Bit32u dtranslate_linear(bx_address laddr, unsigned pl, unsigned rw) BX_CPP_AttrRegparmN(3); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 27 //操作 TLB 的函数 paging.cpp BX_SMF void TLB_flush(bx_bool invalidateGlobal); BX_SMF void TLB_init(void); //中断 BX_SMF void set_INTR(bx_bool value); // init.cpp BX_SMF char *strseg(bx_segment_reg_t *seg) BX_CPP_AttrRegparmN(1); //access.cpp //Exception.cpp BX_SMF void interrupt(Bit8u vector, bx_bool is_INT, bx_bool is_error_code, Bit16u error_code); BX_SMF void real_mode_int(Bit8u vector, bx_bool is_INT, bx_bool is_error_code, Bit16u error_code); BX_SMF void protected_mode_int(Bit8u vector, bx_bool is_INT, bx_bool is_error_code, Bit16u error_code); //不清楚??? access.cpp BX_SMF int int_number(bx_segment_reg_t *seg); //操作 CR 寄存器,没怎么搞清楚??? Paging.cpp BX_SMF void CR3_change(bx_address value) BX_CPP_AttrRegparmN(1); BX_SMF void pagingCR0Changed(Bit32u oldCR0, Bit32u newCR0) BX_CPP_AttrRegparmN(2); BX_SMF void pagingCR4Changed(Bit32u oldCR4, Bit32u newCR4) BX_CPP_AttrRegparmN(2); BX_SMF void pagingA20Changed(void); //RESET CPU init.cpp BX_SMF void reset(unsigned source); //从保护模式下 转跳,调用,返回,中断返回时做的操作 ctrl_xfer_pro.cpp BX_SMF void jump_protected(bxInstruction_c *, Bit16u cs, bx_address disp) BX_CPP_AttrRegparmN(3); BX_SMF void call_protected(bxInstruction_c *, Bit16u cs, bx_address disp) BX_CPP_AttrRegparmN(3); BX_SMF void return_protected(bxInstruction_c *, Bit16u pop_bytes) BX_CPP_AttrRegparmN(2); BX_SMF void iret_protected(bxInstruction_c *) BX_CPP_AttrRegparmN(1); BX_SMF void validate_seg_regs(void); //虚拟 8086 方式 Vm8086.cpp BX_SMF void stack_return_to_v86(Bit32u new_eip, Bit32u raw_cs_selector, Bit32u flags32); BX_SMF void stack_return_from_v86(bxInstruction_c *); BX_SMF void init_v8086_mode(void); BX_SMF void v8086_message(void); //任务切换和 TSS 相关 tasking.cpp BX_SMF void task_switch(bx_selector_t *selector, bx_descriptor_t *descriptor, unsigned source, Bit32u dword1, Bit32u dword2); BX_SMF void get_SS_ESP_from_TSS(unsigned pl, Bit16u *ss, Bit32u *esp); // 读写 EFLAGS flag_ctrl_pro.cpp BX_SMF void write_flags(Bit16u flags, bx_bool change_IOPL, bx_bool change_IF) BX_CPP_AttrRegparmN(3); BX_SMF void write_eflags(Bit32u eflags, bx_bool change_IOPL, bx_bool change_IF, bx_bool change_VM, bx_bool change_RF); BX_SMF void writeEFlags(Bit32u eflags, Bit32u changeMask) BX_CPP_AttrRegparmN(2); // Newer variant. BX_SMF void write_eflags_fpu_compare(int float_relation); BX_SMF Bit32u force_flags(void); BX_SMF Bit16u read_flags(void); BX_SMF Bit32u read_eflags(void); //IO 端口读写, 在 pro_io.CPP 中实现 BX_SMF Bit8u inp8(Bit16u addr) BX_CPP_AttrRegparmN(1); BX_SMF void outp8(Bit16u addr, Bit8u value) BX_CPP_AttrRegparmN(2); BX_SMF Bit16u inp16(Bit16u addr) BX_CPP_AttrRegparmN(1); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 28 BX_SMF void outp16(Bit16u addr, Bit16u value) BX_CPP_AttrRegparmN(2); BX_SMF Bit32u inp32(Bit16u addr) BX_CPP_AttrRegparmN(1); BX_SMF void outp32(Bit16u addr, Bit32u value) BX_CPP_AttrRegparmN(2); BX_SMF bx_bool allow_io(Bit16u addr, unsigned len); //模式切换, 在 protect_ctrl_pro.cpp 中实现 BX_SMF void enter_protected_mode(void); BX_SMF void enter_real_mode(void); //段管理, 在 segment_ctrl_pro.cpp 中实现 BX_SMF void parse_selector(Bit16u raw_selector, bx_selector_t *selector) BX_CPP_AttrRegparmN(2); BX_SMF void parse_descriptor(Bit32u dword1, Bit32u dword2, bx_descriptor_t *temp) BX_CPP_AttrRegparmN(3); BX_SMF void load_ldtr(bx_selector_t *selector, bx_descriptor_t *descriptor); BX_SMF void load_cs(bx_selector_t *selector, bx_descriptor_t *descriptor, Bit8u cpl) BX_CPP_AttrRegparmN(3); BX_SMF void load_ss(bx_selector_t *selector, bx_descriptor_t *descriptor, Bit8u cpl) BX_CPP_AttrRegparmN(3); BX_SMF void fetch_raw_descriptor(bx_selector_t *selector, Bit32u *dword1, Bit32u *dword2, Bit8u exception) BX_CPP_AttrRegparmN(3); BX_SMF void load_seg_reg(bx_segment_reg_t *seg, Bit16u new_value) BX_CPP_AttrRegparmN(2); BX_SMF bx_bool fetch_raw_descriptor2(bx_selector_t *selector, Bit32u *dword1, Bit32u *dword2) BX_CPP_AttrRegparmN(3); //堆栈操作, 在 stack_pro.cpp 中实现 BX_SMF void push_16(Bit16u value16) BX_CPP_AttrRegparmN(1); BX_SMF void push_32(Bit32u value32); BX_SMF void pop_16(Bit16u *value16_ptr); BX_SMF void pop_32(Bit32u *value32_ptr); BX_SMF bx_bool can_push(bx_descriptor_t *descriptor, Bit32u esp, Bit32u bytes) BX_CPP_AttrRegparmN(3); BX_SMF bx_bool can_pop(Bit32u bytes); BX_SMF void decrementESPForPush(unsigned nBytes, Bit32u *eSP); //CPU 自检 在 init.cpp 中实现 BX_SMF void sanity_checks(void); //调试用,在 debugstuff.cpp 中实现 BX_SMF void debug(bx_address offset); //CPU 型号, CPUID.cpp 中实现 BX_SMF Bit32u get_cpu_version_information(void); BX_SMF Bit32u get_extended_cpuid_features(void); BX_SMF Bit32u get_std_cpuid_features(void); BX_CPP_INLINE unsigned which_cpu(void) { return bx_cpuid; } BX_CPP_INLINE const bx_gen_reg_t *get_gen_reg() { return gen_reg; } //一些宏用于访问 CPU 的标志位,通用寄存器等, 都在 cpu.h 中实现 BX_SMF BX_CPP_INLINE void set_AF(bx_bool val); …… BX_CPP_INLINE Bit8u get_CPL(void); BX_CPP_INLINE Bit32u get_EIP(void); …… BX_SMF BX_CPP_INLINE bx_address get_segment_base(unsigned seg); BX_SMF BX_CPP_INLINE bx_bool real_mode(void); //查询当前工作方式 BX_SMF BX_CPP_INLINE bx_bool protected_mode(void); BX_SMF BX_CPP_INLINE bx_bool v8086_mode(void); //如果支持 FPU, 在 fpu.cpp 中实现 BX_SMF void print_state_FPU(void); BX_SMF void prepareFPU(bxInstruction_c *i, bx_bool = 1, bx_bool = 1); BX_SMF void FPU_check_pending_exceptions(void); BX_SMF void FPU_stack_underflow(int stnr, int pop_stack = 0); BX_SMF void FPU_stack_overflow(void); BX_SMF int FPU_exception(int exception); BX_SMF int fpu_save_environment(bxInstruction_c *i); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 29 BX_SMF int fpu_load_environment(bxInstruction_c *i); //MMX 和 SSE 扩展 mmx.cpp sse_XXX.cpp BX_SMF void prepareMMX(void); BX_SMF void prepareFPU2MMX(void); /* cause transition from FPU to MMX technology state */ BX_SMF void print_state_MMX(void); BX_SMF void prepareSSE(void); BX_SMF void check_exceptionsSSE(int); //等待中断 BX_SMF void wait_for_interrupt(); //没有实现的空函数? //CR 寄存器 proc_ctrl.cpp BX_SMF void SetCR0(Bit32u val_32); BX_SMF void SetCR4(Bit32u val_32); //end of the class BX_CPU_C 3.3 通用寄存器 3.3.1 数据结构与注释 数据类型定义 8 个通用寄存器结构体 bx_gen_reg_t, 分 big endian 和 small endian 定义 // General register set // eax: accumulator // ebx: base // ecx: count // edx: data // ebp: base pointer // esi: source index // edi: destination index // esp: stack pointer #ifdef BX_BIG_ENDIAN 大段对齐 typedef struct { union { struct { Bit32u erx; } dword; struct { Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 30 Bit16u word_filler; union { Bit16u rx; struct { Bit8u rh; Bit8u rl; } byte; }; } word; }; } bx_gen_reg_t; #else 小尾对齐 typedef struct { union { struct { Bit32u erx; } dword; struct { union { Bit16u rx; struct { Bit8u rl; Bit8u rh; } byte; }; Bit16u word_filler; } word; }; } bx_gen_reg_t; #endif 3.3.2 通用寄存器归纳 Bochs 定义了结构体 bx_gen_reg_t 描述 8 个 32bit 通用寄存器 EAX,ECX 等。Bochs 是 一个纯 C++编写的代码,考虑了在不同平台上的移植性,因此必须考虑不同处理器的字节 对齐顺序问题。一般来说,有两种对齐顺序:大尾(big endian)和小尾(small endian),比 如 Intel X86 的处理器是小尾方式。另外,为了方便对寄存器的不同长度访问(如 32bit EAX 又可以进行 16bit AX,8bit AL 等访问),Bochs 将结构体 bx_gen_reg_t 定义成 union 类型, 如下图所示: Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 31 3.4 段寄存器、全局寄存器 GDI 和 IDT 3.4.1 数据结构与注释 段寄存器类型定义 typedef struct { bx_address base; /* base address: 24bits=286,32bits=386,64bits=x86-64 */ Bit16u limit; /* limit, 16bits */ } bx_global_segment_reg_t; //段寄存器 typedef struct { bx_selector_t selector; //段选择符 bx_descriptor_t cache; //段描述符高速缓存 } bx_segment_reg_t; 段选择符 Segment Selector 定义 typedef struct { /* bx_selector_t */ Bit16u value; /* the 16bit value of the selector */ /* the following fields are extracted from the value field in protected mode only. They're used for sake of efficiency 以下 3 个域只是用来提高程序性能*/ Bit16u index; /* 13bit index extracted from value in protected mode */ Bit8u ti; /* table indicator bit extracted from value */ Bit8u rpl; /* RPL extracted from value */ } bx_selector_t; 段描述符 Segment Descriptor 定义 typedef struct { bx_bool valid; // Holds above values, Or'd together. Used to // hold only 0 or 1. Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 32 #define SegValidCache (0x1) #define SegAccessROK (0x2) #define SegAccessWOK (0x4) bx_bool p; /* present */ Bit8u dpl; /* descriptor privilege level 0..3 */ bx_bool segment; /* 0 = system/gate, 1 = data/code segment */ Bit8u type; /* For system & gate descriptors, only * 0 = invalid descriptor (reserved) * 1 = 286 available Task State Segment (TSS) * 2 = LDT descriptor * 3 = 286 busy Task State Segment (TSS) * 4 = 286 call gate * 5 = task gate * 6 = 286 interrupt gate * 7 = 286 trap gate * 8 = (reserved) * 9 = 386 available TSS * 10 = (reserved) * 11 = 386 busy TSS * 12 = 386 call gate * 13 = (reserved) * 14 = 386 interrupt gate * 15 = 386 trap gate */ #define BX_GATE_TYPE_NONE (0x0) #define BX_SYS_SEGMENT_AVAIL_286_TSS (0x1) #define BX_SYS_SEGMENT_LDT (0x2) #define BX_SYS_SEGMENT_BUSY_286_TSS (0x3) #define BX_286_CALL_GATE (0x4) #define BX_TASK_GATE (0x5) #define BX_286_INTERRUPT_GATE (0x6) #define BX_286_TRAP_GATE (0x7) /* 0x8 reserved */ #define BX_SYS_SEGMENT_AVAIL_386_TSS (0x9) /* 0xa reserved */ #define BX_SYS_SEGMENT_BUSY_386_TSS (0xb) #define BX_386_CALL_GATE (0xc) /* 0xd reserved */ #define BX_386_INTERRUPT_GATE (0xe) #define BX_386_TRAP_GATE (0xf) union { struct { bx_bool executable; /* 1=code, 0=data or stack segment */ bx_bool c_ed; /* for code: 1=conforming, for data/stack: 1=expand down */ bx_bool r_w; /* for code: readable?, for data/stack: writeable? */ bx_bool a; /* accessed? */ bx_address base; /* base address: 286=24bits, 386=32bits, long=64 */ Bit32u limit; /* limit: 286=16bits, 386=20bits */ Bit32u limit_scaled; /* for efficiency, this contrived field is set to * limit for byte granular, and * (limit << 12) | 0xfff for page granular seg's */ #if BX_CPU_LEVEL >= 3 bx_bool g; /* granularity: 0=byte, 1=4K (page) */ bx_bool d_b; /* default size: 0=16bit, 1=32bit */ bx_bool avl; /* available for use by system */ #endif } segment; struct { Bit8u word_count; /* 5bits (0..31) #words to copy from caller's stack * to called procedure's stack. (call gates only)*/ Bit16u dest_selector; Bit16u dest_offset; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 33 } gate286; struct { /* type 5: Task Gate Descriptor */ Bit16u tss_selector; /* TSS segment selector */ } taskgate; #if BX_CPU_LEVEL >= 3 struct { Bit8u dword_count; /* 5bits (0..31) #dwords to copy from caller's stack * to called procedure's stack. (call gates only) */ Bit16u dest_selector; Bit32u dest_offset; } gate386; #endif struct { Bit32u base; /* 24 bit 286 TSS base */ Bit16u limit; /* 16 bit 286 TSS limit */ } tss286; #if BX_CPU_LEVEL >= 3 struct { bx_address base; /* 32/64 bit 386 TSS base */ Bit32u limit; /* 20 bit 386 TSS limit */ Bit32u limit_scaled; // Same notes as for 'segment' field bx_bool g; /* granularity: 0=byte, 1=4K (page) */ bx_bool avl; /* available for use by system */ } tss386; #endif struct { bx_address base; /* 286=24 386+ =32/64 bit LDT base */ Bit16u limit; /* 286+ =16 bit LDT limit */ } ldt; } u; } bx_descriptor_t; 变量声明部分 /* 用户段寄存器组 */ bx_segment_reg_t sregs[6]; /* 系统段寄存器 */ bx_global_segment_reg_t gdtr; /* global descriptor table register */ bx_global_segment_reg_t idtr; /* interrupt descriptor table register */ /* LDTR 和 TR 实际上也是段寄存器 */ bx_segment_reg_t ldtr; /* local descriptor table register */ bx_segment_reg_t tr; /* task register */ 3.4.2 段寄存器结构分析 IA32 有 6 个通用段寄存器 CS、SS、DS、ES、FS 和 GS,每一个寄存器由两部分组 成:可见的段选择符和不可见的基地址、段限、访问信息等组成。实际上不可见部分可以用 一个段描述符来描述。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 34 Bochs 定义了 bx_segment_reg_t 结构描述一个段寄存器: typedef struct { bx_selector_t selector; //段选择符 bx_descriptor_t cache; //段描述符高速缓存 } bx_segment_reg_t; 段选择符相对比较简单,16 位的。。。 而 64bit 的段描述符比较复杂,描述了包括段的类型(代码/数据段,系统段)基地址、段限 等,下图显示了段描述符的各个域的定义。 从广义上说,各种门(调用门、中断门、陷阱门和任务门)和 LDTR/TR 也都属于段描述符, 它们有着类似的结构但内容不同。下图显示了调用门(CallGate)的结构。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 35 Bochs 使用联合体数据类型定义了 bx_descriptor_t,用来兼容各种类型的段描述符。 关于段描述符的具体内容,参考 IA-32 体系结构设计手册(卷一,卷三)。 3.5 CPU 状态字 EFLAGS 3.5.1 数据结构与注释 EFLGA 数据类型定义 typedef struct { Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 36 /* 31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16 * ==|==|=====|==|==|==|==|==|==|==|==|==|==|==|== * 0| 0| 0| 0| 0| 0| 0| 0| 0| 0|ID|VP|VF|AC|VM|RF * * 15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0 * ==|==|=====|==|==|==|==|==|==|==|==|==|==|==|== * 0|NT| IOPL|OF|DF|IF|TF|SF|ZF| 0|AF| 0|PF| 1|CF */ Bit32u val32; // Raw 32-bit value in x86 bit position. Used to store // some eflags which are not cached in separate fields. Bit32u VM_cached; #define DECLARE_EFLAGS_ACCESSORS() \ BX_CPP_INLINE void setEFlags(Bit32u val); #define IMPLEMENT_EFLAGS_ACCESSORS() \ BX_CPP_INLINE void BX_CPU_C::setEFlags(Bit32u val) { \ BX_CPU_THIS_PTR eflags.val32 = val; \ BX_CPU_THIS_PTR eflags.VM_cached = val & (1<<17); \ if (BX_CPU_THIS_PTR cr0.pe && BX_CPU_THIS_PTR eflags.VM_cached) { \ BX_CPU_THIS_PTR cpu_mode = BX_MODE_IA32_V8086; \ } \ } // accessors for all eflags in bx_flags_reg_t // The macro is used once for each flag bit. #define DECLARE_EFLAG_ACCESSOR(name,bitnum) \ BX_CPP_INLINE void assert_##name (); \ BX_CPP_INLINE void clear_##name (); \ BX_CPP_INLINE Bit32u get_##name (); \ BX_CPP_INLINE bx_bool getB_##name (); \ BX_CPP_INLINE void set_##name (Bit8u val); #define IMPLEMENT_EFLAG_ACCESSOR(name,bitnum) \ BX_CPP_INLINE void BX_CPU_C::assert_##name () { \ BX_CPU_THIS_PTR eflags.val32 |= (1<> bitnum); \ } \ BX_CPP_INLINE Bit32u BX_CPU_C::get_##name () { \ Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 37 return BX_CPU_THIS_PTR eflags.val32 & (1 << bitnum); \ } \ BX_CPP_INLINE void BX_CPU_C::set_##name (Bit8u val) { \ BX_CPU_THIS_PTR eflags.val32 = \ (BX_CPU_THIS_PTR eflags.val32&~(1<0); \ } \ BX_CPP_INLINE void BX_CPU_C::set_VM(Bit32u val) { \ BX_CPU_THIS_PTR eflags.val32 = \ (BX_CPU_THIS_PTR eflags.val32&~(1<> 12); \ } #define EFlagsCFMask 0x00000001 #define EFlagsPFMask 0x00000004 #define EFlagsAFMask 0x00000010 #define EFlagsZFMask 0x00000040 #define EFlagsSFMask 0x00000080 #define EFlagsOFMask 0x00000800 #define EFlagsOSZAPCMask \ (EFlagsCFMask | EFlagsPFMask | EFlagsAFMask | EFlagsZFMask | EFlagsSFMask | EFlagsOFMask) #define EFlagsOSZAPMask \ (EFlagsPFMask | EFlagsAFMask | EFlagsZFMask | EFlagsSFMask | EFlagsOFMask) } bx_flags_reg_t; //EFLAG 的定义 bx_flags_reg_t eflags; //访问 EFLAG 函数的声明,利用宏扩展 DECLARE_EFLAGS_ACCESSORS() DECLARE_EFLAG_ACCESSOR (DF, 10) DECLARE_EFLAG_ACCESSOR (ID, 21) DECLARE_EFLAG_ACCESSOR (VIP, 20) DECLARE_EFLAG_ACCESSOR (VIF, 19) DECLARE_EFLAG_ACCESSOR (AC, 18) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 39 DECLARE_EFLAG_ACCESSOR_VM( 17) DECLARE_EFLAG_ACCESSOR (RF, 16) DECLARE_EFLAG_ACCESSOR (NT, 14) DECLARE_EFLAG_ACCESSOR_IOPL( 12) DECLARE_EFLAG_ACCESSOR (IF, 9) DECLARE_EFLAG_ACCESSOR (TF, 8) 3.5.2 源码分析 除了 VM 位和 IOPL,其它的标志位都是 1bit 长度,因此使用了同一个宏扩展来定义访问函 数: DECLARE_EFLAG_ACCESSOR(name,bitnum) IMPLEMENT_EFLAG_ACCESSOR(name,bitnum) 宏 DECLARE_EFLAG_ACCESSOR 是函数声明,内容如下 #define DECLARE_EFLAG_ACCESSOR(name,bitnum) \ BX_CPP_INLINE void assert_##name (); \ BX_CPP_INLINE void clear_##name (); \ BX_CPP_INLINE Bit32u get_##name (); \ BX_CPP_INLINE bx_bool getB_##name (); \ BX_CPP_INLINE void set_##name (Bit8u val); 总共定义了 5 个访问函数的原型,分别完成使能(assert)、清除(clear)、读取(get 和 getB)、 设置(set)功能。 在 IMPLEMENT_EFLAG_ACCESSOR 中,定义了对应的函数实现部分: #define IMPLEMENT_EFLAG_ACCESSOR(name,bitnum) \ BX_CPP_INLINE void BX_CPU_C::assert_##name () { \ BX_CPU_THIS_PTR eflags.val32 |= (1<= BX_CPU_THIS_PTR eipPageWindowSize) { prefetch(); eipBiased = RIP + BX_CPU_THIS_PTR eipPageBias; // eipBiased 是 CS:IP 在 4K 页内的 OFFSET } //no cache exist bx_address remainingInPage; remainingInPage = (BX_CPU_THIS_PTR eipPageWindowSize - eipBiased); //4K 页内的指令字节余量 unsigned maxFetch = 15; if (remainingInPage < 15) maxFetch = remainingInPage; //余量不足 15Byte, 取余量 Bit8u *fetchPtr = BX_CPU_THIS_PTR eipFetchPtr + eipBiased; // BX_CPU_THIS_PTR eipFetchPtr 是 prefetch()中经过 mem->getHostMemAddr()得到的指向当前指令 //存储器的指针, 可能是指向某个 ROM, 也可能是指向 SRAM, 该指针总是按 4KB 大小对齐. //将该指针加上 4K 页内的偏移量, 得到取下一条指令的首地址指针 ret = fetchDecode(fetchPtr, i, maxFetch); //maxFetch 限定了本次取指最多涉及的字节,一般来说,IA-32 的指令长度不会超过 15byte if (ret==0) { //如果取指操作不成功, 原因很可能是到达某个 4K 页的边界,调用边界取指函数 boundaryFetch(fetchPtr, remainingInPage, i); // boundaryFetch 中将会再次执行一次 fetchDecode(), 若还是不成功, 抛出 GP_EXCEPTION(=13) 异常 //由 exception()函数处理 } else { //这边没有做什么 } //start the execution in the following 开始执行指令 BxExecutePtr_tR resolveModRM = i->ResolveModrm; // Get as soon as possible for speculation BxExecutePtr_t execute = i->execute; // fetch as soon as possible for speculation Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 41 if (resolveModRM) BX_CPU_CALL_METHODR(resolveModRM, (i)); …… if ( !(i->repUsedL() && i->repeatableL()) ) { // non repeating instruction RIP += i->ilen(); //递增 EIP, 增量为本条指令长度 BX_CPU_CALL_METHOD(execute, (i)); //调用执行函数 BX_CPU_THIS_PTR prev_eip = RIP; // 记录当前 IP BX_CPU_THIS_PTR prev_esp = RSP; //记录当前 SP BX_INSTR_AFTER_EXECUTION(BX_CPU_ID, i); //执行后工作 BX_TICK1_IF_SINGLE_PROCESSOR(); //驱动系统时钟步进 1 个指令单位 } else { // ……有重复指令,重复执行 } }//end of while(1) } //end of loop() 3.6.2 函数 CPU_LOOP()源码注释 原型 void BX_CPU_C::cpu_loop(Bit32s max_instr_count) 执行: bxInstruction_c iStorage BX_CPP_AlignN(32); //定义一个 bxInstruction_c 指令类型变量 if (setjmp( BX_CPU_THIS_PTR jmp_buf_env )) //????是不是检测异常 { BX_INSTR_NEW_INSTRUCTION(BX_CPU_ID); //关于 Instrument 的,可能没有什么内容 } BX_CPU_THIS_PTR prev_eip = RIP; // commit new EIP, RIP=EIP BX_CPU_THIS_PTR prev_esp = RSP; // commit new ESP .RSP=ESP BX_CPU_THIS_PTR EXT = 0; BX_CPU_THIS_PTR errorno = 0; While(1) { // First check on events which occurred for previous instructions // (traps) and ones which are asynchronous to the CPU // (hardware interrupts). 检查前一个指令的事件: 异步 Traps 或硬件中断 if (BX_CPU_THIS_PTR async_event) { if (handleAsyncEvent()) { //处理异步事件 // If request to return to caller ASAP. return; //暂停 CPU,函数返回 } } //下面这段应该和预取指令有关 bx_address eipBiased = RIP + BX_CPU_THIS_PTR eipPageBias; //ip 在当前页中的偏址 if (eipBiased >= BX_CPU_THIS_PTR eipPageWindowSize) { //超过了当前页 prefetch(); eipBiased = RIP + BX_CPU_THIS_PTR eipPageBias; //重新计算 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 42 } //如果支持 Icache Bit32u pAddr = BX_CPU_THIS_PTR pAddrA20Page + eipBiased; unsigned iCacheHash = BX_CPU_THIS_PTR iCache.hash(pAddr); bxICacheEntry_c *cache_entry = &(BX_CPU_THIS_PTR iCache.entry[iCacheHash]); i = &(cache_entry->i); Bit32u pageWriteStamp = pageWriteStampTable.getPageWriteStamp(pAddr); if ((cache_entry->pAddr == pAddr) && //cache 命中 (cache_entry->writeStamp == pageWriteStamp)) { // iCache hit. Instruction is already decoded and stored in the // instruction cache. BX_INSTR_OPCODE() //这个需要 instrumentation 支持 } else //cache 没有命中 { // iCache miss. No validated instruction with matching fetch parameters // is in the iCache. Or we're not compiling iCache support in, in which // case we always have an iCache miss. :^) bx_address remainingInPage; remainingInPage = (BX_CPU_THIS_PTR eipPageWindowSize - eipBiased); //页内剩余量 unsigned maxFetch = 15; if (remainingInPage < 15) maxFetch = remainingInPage; Bit8u *fetchPtr = BX_CPU_THIS_PTR eipFetchPtr + eipBiased; #if BX_SUPPORT_ICACHE // The entry will be marked valid if fetchdecode will succeed cache_entry->writeStamp = ICacheWriteStampInvalid; InstrICache_Increment(iCacheMisses); #endif ret = fetchDecode(fetchPtr, i, maxFetch); if (ret==0) { #if BX_SUPPORT_ICACHE i = &iStorage; // Leave entry invalid #endif boundaryFetch(fetchPtr, remainingInPage, i); } else { #if BX_SUPPORT_ICACHE // In the case where the page is marked ICacheWriteStampInvalid, all // counter bits will be high, being eqivalent to ICacheWriteStampMax. // In the case where the page is marked as possibly having associated // iCache entries, we need to leave the counter as-is, unless we're // willing to dump all iCache entries which can hash to this page. // Therefore, in either case, we can keep the counter as-is and // replace the fetch mode bits. Bit32u fetchModeMask = BX_CPU_THIS_PTR iCache.fetchModeMask; pageWriteStamp &= ICacheWriteStampMask; // Clear out old fetch mode bits. pageWriteStamp |= fetchModeMask; // Add in new ones. pageWriteStampTable.setPageWriteStamp(pAddr, pageWriteStamp); cache_entry->pAddr = pAddr; cache_entry->writeStamp = pageWriteStamp; #endif #if BX_INSTRUMENTATION // An instruction was either fetched, or found in the iCache. BX_INSTR_OPCODE(BX_CPU_ID, fetchPtr, i->ilen(), BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].cache.u.segment.d_b); #endif } } //cache miss Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 43 BxExecutePtr_tR resolveModRM = i->ResolveModrm; // Get as soon as possible for speculation BxExecutePtr_t execute = i->execute; // fetch as soon as possible for speculation if (resolveModRM) BX_CPU_CALL_METHODR(resolveModRM, (i)); // An instruction will have been fetched using either the normal case, // or the boundary fetch (across pages), by this point. BX_INSTR_FETCH_DECODE_COMPLETED(BX_CPU_ID, i); // decoding instruction compeleted -> continue with execution BX_INSTR_BEFORE_EXECUTION(BX_CPU_ID, i); if ( !(i->repUsedL() && i->repeatableL()) ) { //没有重复指令 // non repeating instruction RIP += i->ilen(); BX_CPU_CALL_METHOD(execute, (i)); //执行指令 BX_CPU_THIS_PTR prev_eip = RIP; // commit new EIP BX_CPU_THIS_PTR prev_esp = RSP; // commit new ESP BX_INSTR_AFTER_EXECUTION(BX_CPU_ID, i); BX_TICK1_IF_SINGLE_PROCESSOR(); } else { repeat_loop: if (i->repeatableZFL()) { if (i->as32L()) { if (ECX != 0) { BX_CPU_CALL_METHOD(execute, (i)); ECX --; } if ((i->repUsedValue()==3) && (get_ZF()==0)) goto repeat_done; if ((i->repUsedValue()==2) && (get_ZF()!=0)) goto repeat_done; if (ECX == 0) goto repeat_done; goto repeat_not_done; } else { if (CX != 0) { BX_CPU_CALL_METHOD(execute, (i)); CX --; } if ((i->repUsedValue()==3) && (get_ZF()==0)) goto repeat_done; if ((i->repUsedValue()==2) && (get_ZF()!=0)) goto repeat_done; if (CX == 0) goto repeat_done; goto repeat_not_done; } } else { // normal repeat, no concern for ZF if (i->as32L()) { if (ECX != 0) { BX_CPU_CALL_METHOD(execute, (i)); ECX --; } if (ECX == 0) goto repeat_done; goto repeat_not_done; } else { // 16bit addrsize if (CX != 0) { BX_CPU_CALL_METHOD(execute, (i)); CX --; } if (CX == 0) goto repeat_done; goto repeat_not_done; } } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 44 // shouldn't get here from above repeat_not_done: BX_INSTR_REPEAT_ITERATION(BX_CPU_ID, i); BX_TICK1_IF_SINGLE_PROCESSOR(); if (BX_CPU_THIS_PTR async_event) { invalidate_prefetch_q(); goto debugger_check; } goto repeat_loop; repeat_done: RIP += i->ilen(); BX_CPU_THIS_PTR prev_eip = RIP; // commit new EIP BX_CPU_THIS_PTR prev_esp = RSP; // commit new ESP BX_INSTR_REPEAT_ITERATION(BX_CPU_ID, i); BX_INSTR_AFTER_EXECUTION(BX_CPU_ID, i); BX_TICK1_IF_SINGLE_PROCESSOR(); } debugger_check: // inform instrumentation about new instruction BX_INSTR_NEW_INSTRUCTION(BX_CPU_ID); #if (BX_SMP_PROCESSORS>1 && BX_DEBUGGER==0) // The CHECK_MAX_INSTRUCTIONS macro allows cpu_loop to execute a few // instructions and then return so that the other processors have a chance // to run. This is used only when simulating multiple processors. If only // one processor, don't waste any cycles on it! Also, it is not needed // with the debugger because its guard mechanism provides the same // functionality. CHECK_MAX_INSTRUCTIONS(max_instr_count); #endif } // end while (1) } //end cpu loop 3.7 函数 handleAsyncEvent()分析 3.7.1 函数 handleAsyncEvent()知识准备 IA32 体系结构规定的中断及异常优先级 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 45 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 46 TF Trap (bit 8). 置位:允许debugging时单步模式 复位:禁止单步模式。 在单步模式下,每执行一个指令都会产生一个调试异常,此时可以监视程序执行状态。如果 应用程序通过POPF,POPFD,IRET设置TF,这些指令执行的随后就会产生一个调试异常。 IF Interrupt enable (bit 9). 控制可屏蔽硬件中断的请求(见5.3.2节., “Maskable Hardware Interrupts”).置位则响应可屏蔽 硬件中断。IF不能禁止异常发生和NMI。 CPL、IOPL和CR4里VME标志位状态决定IF是否 可以用CLI, STI, POPF, POPFD, 和IRET指令修改。 RF Resume (bit 16). 控制处理器对指令断点条件作出反应。置位,则禁止由断点产生的调试异常,但不影响其它 异常的发生。清除RF,指令断点将会产生调试异常#DE。 这个功能的主要作用是允许由 指令断点条件产生的异常后,重新执行指令。 外部中断 外部中断从处理器管脚接收或者本地APIC接受。Pentium处理器主要的中断管脚是 LINT[1:0],它们连接到APIC。当本地APIC被使能,可以用APIC的LVT表对LINT[1:0]进行 编程。当本地APIC被禁用,它们被配置为INTR和NMI管脚,外部中断控制器(如8259)触 发INTR信号,处理器会通过总线从外部中断控制器读取向量值(IRQ值)。驱动NMI信号表 示一个不可屏蔽中断NMI的产生(向量2)。 ============================================================================ 3.7.2 函数 handleAsyncEvent()结构分析 Bochs在CPU LOOP中调用handleAsyncEvent()处理所有的异步事件,包括调试设置的断 点、外部中断、处理器的各种异常及错误等。 handleAsyncEvent()的基本思想是通过检测相关的标志位,从标志位获取事件,然后对事件 进行分类讨论。事件包括 9 调试陷阱(debug trap) 9 中止bochs的请求(当用户关闭bochs程序) 9 机器异常及各种陷阱 9 外部中断或DMA(检查信号INTR和bx_pc_system.HRQ信号) 9 指令错误(包括取指、译码和执行) Bochs按照IA32的对中断/异常划分的8个不同优先级顺序进行处理,当然,很多异常/中断 bochs还没有代码定义,如机器校验错误。最后,函数对相关标志位进行置位或复位处理后 返回。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 47 关于变量BX_CPU_C::inhibit_mask,bochs定义了两个mask值,分别是 #define BX_INHIBIT_INTERRUPTS 0x01 #define BX_INHIBIT_DEBUG 0x02 用来屏蔽中断和调试事件。STI、POP_SS 等指令会修改 inhibit_mask 的值。 由于 Bochs 的 BX_CPU_C 只定义了 INTR 变量(等效于硬件 INTR 管脚)而没有定义 NMI 管脚,因此函数只对 INTR 标志位做了处理。 3.7.3 函数 handleAsyncEvent()源码注释 ============================================================================ unsigned BX_CPU_C::handleAsyncEvent(void) { // // This area is where we process special conditions and events. // if (BX_CPU_THIS_PTR debug_trap & 0x80000000) { //是否调试陷阱 // I made up the bitmask above to mean HALT state. BX_CPU_THIS_PTR debug_trap = 0; // clear traps for after resume BX_CPU_THIS_PTR inhibit_mask = 0; // clear inhibits for after resume // for one processor, pass the time as quickly as possible until // an interrupt wakes up the CPU. while (1) { if (BX_CPU_INTR && BX_CPU_THIS_PTR get_IF ()) { break; } if (BX_CPU_THIS_PTR async_event == 2) { BX_INFO(("decode: reset detected in halt state")); break; } BX_TICK1(); } } else if (BX_CPU_THIS_PTR kill_bochs_request) { //若收到中止 bochs 的请求,返回 1 // setting kill_bochs_request causes the cpu loop to return ASAP. return 1; // Return to caller of cpu_loop. } // Priority 1: Hardware Reset and Machine Checks //硬件 RESET 或机器校验错误,bochs 未完成该代码 // RESET // Machine Check // (bochs doesn't support these) // Priority 2: Trap on Task Switch // T flag in TSS is set if (BX_CPU_THIS_PTR debug_trap & 0x00008000) { BX_CPU_THIS_PTR dr6 |= BX_CPU_THIS_PTR debug_trap; exception(BX_DB_EXCEPTION, 0, 0); // no error, not interrupt } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 48 // Priority 3: External Hardware Interventions 外部硬件干涉,bochs 未完成该代码 // FLUSH // STOPCLK // SMI // INIT // (bochs doesn't support these) // Priority 4: Traps on Previous Instruction // Breakpoints // Debug Trap Exceptions (TF flag set or data/IO breakpoint) if ( BX_CPU_THIS_PTR debug_trap && !(BX_CPU_THIS_PTR inhibit_mask & BX_INHIBIT_DEBUG) ) { // A trap may be inhibited on this boundary due to an instruction // which loaded SS. If so we clear the inhibit_mask below // and don't execute this code until the next boundary. // Commit debug events to DR6 BX_CPU_THIS_PTR dr6 |= BX_CPU_THIS_PTR debug_trap; exception(BX_DB_EXCEPTION, 0, 0); // no error, not interrupt } // Priority 5: External Interrupts 外部中断 // NMI Interrupts // Maskable Hardware Interrupts if (BX_CPU_THIS_PTR inhibit_mask & BX_INHIBIT_INTERRUPTS) { //是否屏蔽了外部中断 // Processing external interrupts is inhibited on this // boundary because of certain instructions like STI. // inhibit_mask is cleared below, in which case we will have // an opportunity to check interrupts on the next instruction // boundary. } else if (BX_CPU_INTR && BX_CPU_THIS_PTR get_IF () && BX_DBG_ASYNC_INTR) // #define BX_CPU_INTR (BX_CPU_THIS_PTR INTR || BX_CPU_THIS_PTR local_apic.INTR) Bit8u vector; // NOTE: similar code in ::take_irq() #if BX_SUPPORT_APIC if (BX_CPU_THIS_PTR local_apic.INTR) vector = BX_CPU_THIS_PTR local_apic.acknowledge_int (); else vector = DEV_pic_iac(); // may set INTR with next interrupt 从中断控制器获取向量 #else // if no local APIC, always acknowledge the PIC. vector = DEV_pic_iac(); // may set INTR with next interrupt 从中断控制器获取向量 #endif //BX_DEBUG(("decode: interrupt %u", // (unsigned) vector)); BX_CPU_THIS_PTR errorno = 0; BX_CPU_THIS_PTR EXT = 1; /* external event */ interrupt(vector, 0, 0, 0); // Set up environment, as would be when this main cpu loop gets // invoked. At the end of normal instructions, we always commmit // the new EIP/ESP values. But here, we call interrupt() much like // it was a sofware interrupt instruction, and need to effect the // commit here. This code mirrors similar code above. BX_CPU_THIS_PTR prev_eip = RIP; // commit new RIP BX_CPU_THIS_PTR prev_esp = RSP; // commit new RSP BX_CPU_THIS_PTR EXT = 0; BX_CPU_THIS_PTR errorno = 0; } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 49 else if (BX_HRQ && BX_DBG_ASYNC_DMA) { //DMA 请求 // NOTE: similar code in ::take_dma() // assert Hold Acknowledge (HLDA) and go into a bus hold state // #define BX_HRQ (bx_pc_system.HRQ) DEV_dma_raise_hlda(); //#define DEV_dma_raise_hlda() (bx_devices.pluginDmaDevice->raise_HLDA()) } // Priority 6: Faults from fetching next instruction 取指时错误 // Code breakpoint fault // Code segment limit violation (priority 7 on 486/Pentium) // Code page fault (priority 7 on 486/Pentium) // (handled in main decode loop) // Priority 7: Faults from decoding next instruction 指令译码错误 // Instruction length > 15 bytes // Illegal opcode // Coprocessor not available // (handled in main decode loop etc) // Priority 8: Faults on executing an instruction 指令执行错误 // Floating point execution // Overflow // Bound error // Invalid TSS // Segment not present // Stack fault // General protection // Data page fault // Alignment check // (handled by rest of the code) if (BX_CPU_THIS_PTR get_TF ()) { // TF is set before execution of next instruction. Schedule // a debug trap (#DB) after execution. After completion of // next instruction, the code above will invoke the trap. BX_CPU_THIS_PTR debug_trap |= 0x00004000; // BS flag in DR6 } // Now we can handle things which are synchronous to instruction // execution. if (BX_CPU_THIS_PTR get_RF ()) { BX_CPU_THIS_PTR clear_RF (); } // We have ignored processing of external interrupts and // debug events on this boundary. Reset the mask so they // will be processed on the next boundary. BX_CPU_THIS_PTR inhibit_mask = 0; //复位 inhibit_mask if ( !(BX_CPU_INTR || BX_CPU_THIS_PTR debug_trap || BX_HRQ || BX_CPU_THIS_PTR get_TF () )) BX_CPU_THIS_PTR async_event = 0; //清除异步事件标志 return 0; // Continue executing cpu_loop. } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 50 3.8 取指与执行 3.8.1 Intel IA-32 指令结构 (1).指令结构 从上图可以看到,最长的 x86 指令总共有六部分组成,但很多指令只含有其中的部分内容。 指令前缀:总共可以分成 4 组 组1 锁定和重复: • F0H—锁定,用于多处理器操作中. • F2H—REPNE/REPNZ (只在串操作中使用). • F3H—REP or REPE/REPZ (只在串操作中使用). 组2 段重载: 2EH—CS 段重载前缀 (use with any branch instruction is reserved). 36H—SS 段重载前缀(use with any branch instruction is reserved). 3EH—DS 段重载前缀(use with any branch instruction is reserved). 26H—ES 段重载前缀(use with any branch instruction is reserved). 64H—FS 段重载前缀(use with any branch instruction is reserved). 65H—GS 段重载前缀(use with any branch instruction is reserved). 2EH—Branch not taken (used only with Jcc instructions). 3EH—Branch taken (used only with Jcc instructions). 组3 — 66H—Operand-size 操作数重载前缀. 组4 — 67H—Address-size 地址重载前缀. 操作码:opcode 操作码的长度不定,有 1、2、3 个字节。很多简单的寄存器访问指令用 1 个字节的操作码 定义。另外,opcode 亦有可能将额外的 3bit 信息编码到下一个字节 modRM ModR/M 和 SIB 很多操作码后都会跟随一个 modR/M 字节,通常包含了 3 个部分的信息: mod 域和r/m域联合使用,共有32个可能的值: 8个通用寄存器和24种寻址模式。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 51 reg/opcode域指示一个通用寄存器号或者是opcode的额外3个比特值。取决与主opcode的内 容。 r/m 域指定一个寄存器为操作数或者和mod域联合使用确定寻址方式。 特定的ModR/M(r/m=4时)有时还需要一个附加字节SIB来完全确定寻址方式。 base-plus-index 和 scale-plus-index 寻址方式需要SIB一起生成32-bit地址。SIB包含以下域: • The scale 域指定缩放因子。. • The index 域指定了索引寄存器号( register number of the index register). • The base 域基址寄存器的寄存器号(register number of the base register). 关于scale寻址可以参考文献【…】 DISPLACEMENT 和立即数字节 有些地址的形成是包括ModR/M 后紧跟的displacement。如果需要一个displacement ,可以 是1、2或4字节。如果指令还指定了一个立即数, displacement后面还会有一个1、2或4字节 的立即数。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 52 (2) 关于 32 位模式和 16 位模式 实模式下,理论上能访问的内存空间为 1M,但是否可以在实模式下访问到 4Gb 的空间,一 个很有名的程序解决了这个问题。 它的基本思想是: 把一个段寄存器(比如 FS 段)的界限设为 4G,基址为 0。在实模式下不可以进行这样的操 作,所以必须先进入保护模式,装入这些值后再退回到实模式,即完成设置。然后用一个 32 位的段内偏移地址配合 FS 的基地址对内存进行读写操作。 该程序中使用了很多类似于下面的指令写法 db 0x66; mov CX,DS; 实际上相当于 mov ECX, DS, 其原理是通过指令前缀 0x66 将 16 位指令扩展成 32 位,这 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 53 说明 CPU 在实模式下仍然是可以运行 32 位指令集的。反之,也可以在保护模式下执行 16 位指令集。 另外,CPU 在 16 位实模式下寻址方式还是使用的段高速缓存的基地址,这点可以在 CPU 启动执行 BIOS 内第一条指令中证明。 由此看来,实模式下的很多工作方式是和保护模式相同的,只是实模式下没有启动存储保护 机制。 3.8.2 类 bxInstruction_c 的数据成员 (1).32bit metaInfo 变量 // 26..23 ilen (0..15). Leave this one on top so no mask is needed. // 22..22 mod==c0 (modrm) // 21..13 b1 (9bits of opcode; 1byte-op=0..255, 2byte-op=256..511. // 12..12 BxRepeatableZF (pass-thru from fetchdecode attributes) // 11..11 BxRepeatable (pass-thru from fetchdecode attributes) // 10...9 repUsed (0=none, 2=0xF2, 3=0xF3). // 8...8 extend8bit // 7...7 as64 // 6...6 os64 // 5...5 as32 // 4...4 os32 // 3...3 (unused) // 2...0 seg unsigned metaInfo; // Info in the metaInfo field. // Note: the 'L' at the end of certain flags, means the value returned // is for Logical comparisons, eg if (i->os32L() && i->as32L()). If you // want a bx_bool value, use os32B() etc. This makes for smaller // code, when a strict 0 or 1 is not necessary. BX_CPP_INLINE void initMetaInfo(unsigned seg, unsigned os32, unsigned as32, unsigned os64, unsigned as64) { metaInfo = seg | (os32<<4) | (as32<<5) | (os64<<6) | (as64<<7); } 这个函数是在 BX_CPU_C::fetchDecode()中调用的,用来初始化 metaInfo 变量 instruction->initMetaInfo(BX_SEG_REG_NULL, /*os32*/ is_32, /*as32*/ is_32, /*os64*/ 0, /*as64*/ 0); (2).描述 ModR/M 的数据结构 Union { // Form (longest case): [opcode+modrm+sib/displacement32/immediate32] struct { // Note: if you add more bits, mask the previously upper field, // in the accessor. // 27..20 modRM (modrm) // 19..16 index (sib) // 15..12 base (sib) // 11...8 nnn (modrm) 即 reg/opcode Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 54 // 7...6 mod (modrm) // 5...4 scale (sib) // 3...0 rm (modrm) Bit32u modRMData; union { Bit32u Id; Bit16u Iw; Bit8u Ib; }; union { Bit16u displ16u; // for 16-bit modrm forms Bit32u displ32u; // for 32-bit modrm forms }; } modRMForm; struct { Bit32u dummy; union { Bit32u Id; Bit16u Iw; Bit8u Ib; }; union { Bit32u Id2; // Not used (for alignment) Bit16u Iw2; Bit8u Ib2; }; } IxIxForm; struct { // For opcodes which don't use modRM, but which encode the // register in the low 3 bits of the opcode, extended by the // REX.B bit on x86-64, the register value is cached in opcodeReg. Bit32u opcodeReg; union { Bit32u Id; Bit16u Iw; Bit8u Ib; }; Bit32u dummy; } IxForm; } 这些变量存储了经过译码后的指令信息,这些变量在 BX_CPU_C::fetchDecode()中被赋值 (3)操作码表 在 fetchDecode.cpp 里定义了大小 1024 的操作码表,分为两部分――512 条 16bit 操作码 和 512 条 32bit 操作码 static BxOpcodeInfo_t BxOpcodeInfo[512*2] BxOpcodeInfo_t 结构记录了一条操作码的特征(attr)和执行函数的指针,另外还是有一个附 加的指向 BxOpcodeInfo_t 结构体的指针,这是因为考虑到同一个操作码可能还会有多个执 行函数。 typedef struct BxOpcodeInfo_t { Bit16u Attr; BxExecutePtr_t ExecutePtr; struct BxOpcodeInfo_t *AnotherArray; } BxOpcodeInfo_t; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 55 以下是操作码数组的片断: // // 512 entries for 16bit mode /* 00 */ { BxAnother | BxLockable, &BX_CPU_C::ADD_EbGb }, /* 01 */ { BxAnother | BxLockable, &BX_CPU_C::ADD_EwGw }, /* 02 */ { BxAnother, &BX_CPU_C::ADD_GbEb }, /* 03 */ { BxAnother | BxSplitMod11b, NULL, opcodesADD_GwEw }, /* 04 */ { BxImmediate_Ib, &BX_CPU_C::ADD_ALIb }, /* 05 */ { BxImmediate_Iv, &BX_CPU_C::ADD_AXIw }, … // 512 entries for 32bit mode /* 00 */ { BxAnother | BxLockable, &BX_CPU_C::ADD_EbGb }, /* 01 */ { BxAnother | BxLockable, &BX_CPU_C::ADD_EdGd }, /* 02 */ { BxAnother, &BX_CPU_C::ADD_GbEb }, /* 03 */ { BxAnother | BxSplitMod11b, NULL, opcodesADD_GdEd }, /* 04 */ { BxImmediate_Ib, &BX_CPU_C::ADD_ALIb }, /* 05 */ { BxImmediate_Iv, &BX_CPU_C::ADD_EAXId }, … 其中,opcodesADD_GwEw 和 opcodesADD_GdEd 为 static BxOpcodeInfo_t opcodesADD_GwEw[2] = { { 0, &BX_CPU_C::ADD_GwEEw }, { 0, &BX_CPU_C::ADD_GwEGw } }; static BxOpcodeInfo_t opcodesADD_GdEd[2] = { { 0, &BX_CPU_C::ADD_GdEEd }, { 0, &BX_CPU_C::ADD_GdEGd } }; 对照 IA-32 指令表, 我们可以把执行函数和指令关联起来: 操作 码 指令 操作说明 特征 attr 执行函数 00 /r ADD r/m8,r8 Add r8 to r/m8 BxAnother | BxLockableADD_EbGb() 01 /r ADD r/m16,r16 Add r16 to r/m16 BxAnother | BxLockable ADD_EwGw() 01 /r ADD r/m32,r32 Add r32 to r/m32 BxAnother | BxLockable ADD_EdGd() 02 /r ADD r8,r/m8 Add r/m8 to r8 BxAnother ADD_GbEb() 03 /r ADD r16,r/m16 Add r/m16 to r16 BxAnother | BxSplitMod11b ADD_GwEw[2] 03 /r ADD r32,r/m32 Add r/m32 to r32 BxAnother | BxSplitMod11b ADD_GdEd[2] 04 ib ADD AL,imm8 Add imm8 to AL BxImmediate_Ib ADD_ALIb() 05 iw ADD AX,imm16 Add imm16 to AX BxImmediate_Iv ADD_AXIw() 05 id ADD EAX,imm32 Add imm32 to EAX BxImmediate_Iv ADD_EAXId() … 3.8.3 取指译码函数 FetchDecode()分析 (1)函数定义 unsigned Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 56 BX_CPU_C::fetchDecode(Bit8u *iptr, bxInstruction_c *instruction, unsigned remain) Iptr 是指向当前指令空间的指针,从该处读取机器码字节(因为 x86 体系是冯诺依曼结构, 数据和指令存放在一起的,即该指针指向存储器空间) Instruction 为指令指针,存放取指/译码后的指令 remain 为本次取指最大限定长度,单位为字节, 因为 IA-32 属于 CISC 指令集,每条指令长度不固定,因此带来的问题是必须一边取字节一 边进行分析,根据取到指令字节的特征码一步步得到完整的指令。一般来说,IA-32 的长度 不会超过 14 字节,在取指的过程中,将已取的长度和 remain 比较,若超出长度,则退出, 本次取指失败 (2)取指过程分析 先对 instruction 相关的变量进行初始化 os_32 = is_32 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].cache.u.segment.d_b; //因为 0x66 指令前缀会影响当前指令 opsize,因此定义了两个与指令位数相关的变量 //os_32 指示本条指令是 16 位还是 32 位操作(是经过 0x66 前缀变换得到的) //is_32 指示当前代码段是 16 位还是 32 位 //读取 cs 寄存器,确定当前是 16 位段还是 32 位段,/ u.segment.d_b: 0=16bit, 1=32bit */ instruction->ResolveModrm = NULL; instruction->initMetaInfo(BX_SEG_REG_NULL, /*os32*/ is_32, /*as32*/ is_32, /*os64*/ 0, /*as64*/ 0); sse_prefix = SSE_PREFIX_NONE; 从指令空间读取取第一个 Byte, 赋值给 b1 fetch_b1: b1 = *iptr++; another_byte: offset = os_32 << 9; // * 512 计算偏移量 attr = BxOpcodeInfo[b1+offset].Attr; //对 b1 进行查表, 得到 b1 的属性(prefix,opcode…??) instruction->setRepAttr(attr & (BxRepeatable/*bit11*/ | BxRepeatableZF/*bit12*/)); //修改 metaInfo 的值,置 bit11 和 bit12 if (attr & BxAnother) 检查 attr, 如果 attr 中的 BxAnother 属性为真,即表示 b1 不是一条完整的指令, 后面还有 其他字节. 如果 BxAnother=1, 以下分情况讨论 1. 如果是 BxPrefix, 表示一个前缀 (1) b1=0x66, OpSize (2) b1=0x67, AddrSize (3) b1=0xF2, REPNE/REPNZ (4) b1=0xF3, REP/REPE/REPZ (5) b1=0x2E CS 置 metaInfo 为该段 (6) b1=0x26 ES (7) b1=0x36 SS (8) b1=0x3E DS Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 57 (9) b1=0x64 FS (10) b1=0x65 GS (11) b1=0xF0 LOCK 处理完后, 返回到前面 another_byte, 再次取 b1, 2. 不是前缀, 需要取下一个 byte, 赋值给 b2 如果 b1 == 0x0f, 意味着是一个 2byte 的 opcode b1 = 0x100 | b2; //给 b2 加 256, 用于后面的查表 BxOpcodeInfo[ ]中的 2 字节 opcode 部分 goto another_byte; //跳到开始取下一个 byte 如果 b1 不是 prefix, 也不是 0x0F,则 b2 应该是 mod-nnn-rm,以下代码开始分析 b2 //提取 mod, nnn 和 rm mod = b2 & 0xc0; // leave unshifted nnn = (b2 >> 3) & 0x07; rm = b2 & 0x07; instruction->modRMForm.modRMData = (b2<<20); instruction->modRMForm.modRMData |= mod; instruction->modRMForm.modRMData |= (nnn<<8); instruction->modRMForm.modRMData |= rm; …….. 如果 BxAnother=0, 即没有 MODRM 以及其他参数, b1 就应该是一条完整的指令 { instruction->execute = BxOpcodeInfo[b1+offset].ExecutePtr; instruction->IxForm.opcodeReg = b1 & 7; } 检查 LOCK 标志 看是否存在立即数 用 BxImmediate 进行对 attr 进行选择 3..0 位, BxImmediate=0x000f imm_mode = attr & BxImmediate; 如果是立即数,共有以下 10 种 #define BxImmediate_Ib 0x0001 // 8 bits regardless #define BxImmediate_Ib_SE 0x0002 // sign extend to OS size #define BxImmediate_Iv 0x0003 // 16 or 32 depending on OS size #define BxImmediate_Iw 0x0004 // 16 bits regardless #define BxImmediate_IvIw 0x0005 // call_Ap #define BxImmediate_IwIb 0x0006 // enter_IwIb #define BxImmediate_O 0x0007 // mov_ALOb, mov_ObAL, mov_eAXOv, mov_OveAX #define BxImmediate_BrOff8 0x0008 // Relative branch offset byte #define BxImmediate_BrOff16 0x0009 // Relative branch offset word #define BxImmediate_BrOff32 BxImmediate_Iv //就是 0x0003 最后收尾工作: instruction->setB1(b1); instruction->setILen(ilen); 返回取指译码成功。 (3)流程图 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 58 取第一个字节 b1=*iptr++ 查BxOpcodeInfo表,得到attr start 在b2中解析出mod-nnn-rm 32位寻址 若mod=11,则操作数只在 寄存器范围内 则b1是操作码,需要继续取 字节b2,b2=*iptr++ 16位寻址 取SIB字节,解析 需要SIB 分mod=00,mod=01, mod=10分别讨论,取disp 字节 执行函数指针解析:归组讨论,共4组 BxGrooupN,BxPrefixSSE, BxSplitMod11b,bxFPGroup 退出mod解析 分mod=00 mod=01 mod=10讨论, 取disp 由attr得知是否 有更多的字节码 看b1是否是前缀 有 看b1是否位0xf(即 双字节操作码) 检查mod是否为11 检查rm=4? 不需要SIB 16位代码没有SIB, 分mod=00 mod=01 mod=10讨论, 取disp 是 否 单字节指令,取得执行函数 指针和IxForm值 处理前缀后跳到取下一个字 符处 合并b2b1赋给b1,转跳到 查表处 检测Lock标记 取立即数(10种方式)检查imm_mo de, 看是否有立即数 存储指令长度,取指成功,退出 是 否 是 否 否 是 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 59 3.8.4 模拟指令的执行 举例说明指令的执行: 单字节 8 位指令 ADD,机器码为 00H,调用 ADD_EbGb() void BX_CPU_C::ADD_EbGb(bxInstruction_c *i) { Bit8u op2, op1, sum; op2 = BX_READ_8BIT_REGx(i->nnn(),i->extend8bitL()); //源操作数 if (i->modC0()) { //应该是 Mod =11, 见下图 op1 = BX_READ_8BIT_REGx(i->rm(),i->extend8bitL()); //目的操作数 sum = op1 + op2; BX_WRITE_8BIT_REGx(i->rm(), i->extend8bitL(), sum); } else { read_RMW_virtual_byte(i->seg(), RMAddr(i), &op1); sum = op1 + op2; Write_RMW_virtual_byte(sum); } SET_FLAGS_OSZAPC_8(op1, op2, sum, BX_INSTR_ADD8); //修改 } #define BX_READ_8BIT_REG(index) (((index) < 4) ? \ //见下图 (BX_CPU_THIS_PTR gen_reg[index].word.byte.rl) : \ (BX_CPU_THIS_PTR gen_reg[(index)-4].word.byte.rh)) #define BX_READ_8BIT_REGx(index,ext) BX_READ_8BIT_REG(index) //此处进行宏扩展 调用了指令类的成员函数 i->nnn() BX_CPP_INLINE unsigned nnn() { return (modRMForm.modRMData >> 8) & 0xf; } BX_CPP_INLINE unsigned modC0() { // This is a cheaper way to test for modRM instructions where // the mod field is 0xc0. FetchDecode flags this condition since // it is quite common to be tested for. return metaInfo & (1<<22); } BX_CPP_INLINE unsigned rm() { return modRMForm.modRMData & 0xf; } #define RMAddr(i) (BX_CPU_THIS_PTR address_xlation.rm_addr) #define SET_FLAGS_OSZAPC_SIZE(size, lf_op1, lf_op2, lf_result, ins) { \ BX_CPU_THIS_PTR oszapc.op1##size = lf_op1; \ BX_CPU_THIS_PTR oszapc.op2##size = lf_op2; \ BX_CPU_THIS_PTR oszapc.result##size = lf_result; \ BX_CPU_THIS_PTR oszapc.instr = ins; \ BX_CPU_THIS_PTR lf_flags_status = BX_LF_MASK_OSZAPC; \ } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 60 Chapter 4 CPU 中断处理任务管理 4.1 IA-32 体系结构中断知识准备 4.1.1 中断和异常概述 中断和异常的发生是因为系统或正在执行的程序满足了某个条件,需要引起CPU的注意或对 CPU发出请求。它们引发的结果常常是使正在执行的程序或任务强行被一个特殊的例程取 代,即中断(异常)处理函数。处理器响应中断的这个过程被称为中断服务。 中断的发生时常是随机的,特别是硬件发出的中断信号。系统硬件使用中断来处理CPU 的外部事件,譬如某个外设的请求。软件可以利用INT n指令产生中断。 当处理器在执行某条指令时侦测到错误时会引发异常,如除0。处理器可以检测到多种 异常包括保护错误、页错误以及机器内部错误。Pentium 4, Intel Xeon, P6 家族以及 Pentium 处理器的machine-check 架构可以在硬件内部发生错误时(如总线错误)产生一个异常。 IA32架构的中断和异常处理机制允许中断和异常的处理对应用程序和操作系统透明。 当收到某个中断或检测到一个异常时,当前正在运行的进程会自动被挂起,转向执行中断处 理程序。当中断处理完毕,处理器又恢复到原先的进程继续执行。中断的产生不会对进程执 行的连续性产生如何影响,除非是不能恢复的异常或中断例程使当前的进程中止。 4.1.2 异常和中断向量 为了辅助处理中断和异常,每个IA32体系结构都定义了一些中断向量。处理器利用分配的 中断向量作为IDT的索引定位中断处理程序的入口点。中断向量的范围从0-255,从0-31 的中断被IA32体系结构保留,虽然有些目前没有使用,那些是为了以后做扩展的。用户不 要使用这个范围内的中断向量。 从32-255的中断向量是给用户自定义的。它们常常分配给外部IO设备,允许外设向处 理器发送中断(见Section 5.3., “Sources of Interrupts”.) 表5-1显示了体系结构定义的中断和NMI中断。对每个异常,表中给出了异常类型以及 是否有错误代码存入堆栈。另外,异常源和NMI中断源也给了出来。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 61 4.1.3 中断和异常来源 中断来源 处理器可以从两个地方收到中断: • 外部中断(硬件产生). Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 62 • 软件产生的中断. (1). 外部中断 外部中断从处理器管脚接收或者本地APIC接受。Pentium处理器主要的中断管脚是 LINT[1:0],它们连接到APIC。当本地APIC被使能,可以用APIC的LVT表对LINT[1:0]进行 编程。当本地APIC被禁用,它们被配置为INTR和NMI管脚,外部中断控制器(如8259)触 发INTR信号,处理器会通过总线从外部中断控制器读取向量值(IRQ值)。驱动NMI信号表 示一个不可屏蔽中断NMI的产生(向量2)。 (2). 可屏蔽硬件中断(Maskable Hardware Interrupts) 如何通过INTR或本地APIC触发的外部中断都被称为可屏蔽硬件中断。 可以通过设置EFLAGS中的IF标志位屏蔽中断。 (见Section 5.8.1., “Masking Maskable Hardware Interrupts”). (3). 由软件产生的中断 软件可以通过INT n指令产生一个中断。如INT 35会生产一个向量号为35的中断调用。从0 -255的如何中断向量都可以作为指令的参数。如果使用了处理器预定义的NMI向量,尽管 如此,处理器对此做的处理和正常方式(硬件)产生的中断并不完全一样。如果向量号2(NMI 向量)作为指令次数,NMI处理例程会被调用,但处理器的NMI硬件并没有活动。 注:软件产生的中断不能被EFLAGS的IF位屏蔽。 异常来源 主要有以下几种异常来源: • 处理器检测到程序执行错误 • 软件产生 • 机器校验 (1).程序执行错误 当执行应用程序或运行操作系统时,处理器检测到错误时会生产一个异常。可以分为faults, traps和aborts (see Section 5.5., “Exception Classifications”). (2). 软件产生的异常 调用INTO, INT 3, 和BOUND指令产生的异常。 (3). 机器校验异常 P6家族处理器提供了外部和内部机器校验机制,用来检查芯片以及总线的数据校验错误。 当机器校验出错时,处理器会引发一个machine-check(向量18)异常并返回一个错误代码。 4.1.4 与中断处理相关的数据结构 中断描述符表 (IDT) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 63 中断描述符表(IDT)将一个中断(或异常)同一个中断例程的门描述符关联起来。类似GDT 和LDT, IDT也是一个8字节为单位构成的描述符数组。和GDT不同的时,第一个IDT包含 一个描述符。为了生成IDT的索引,处理器将中断向量号×8得到字节索引。因为中断向量 总数不会超过256,因此IDT的大小等于或小于256(不是所以所有的中断向量都会用到)。 空的IDT条目必须把存在标志设为0。 IDT描述符 IDT包括3种类型的门描述符: • 任务门(Task-gate)描述符 • 中断门(Interrupt-gate)描述符 • 陷阱门(Trap-gate)描述符 它们的结构有所不同,如下图所示: IDT中使用的任务门格式和GDT或LDT中的是一样的。任务门包含了中断例程(任务)的TSS 的段选择符。 中断门和陷阱门和调用门非常相似,它们包含一个远指针(段选择符和偏移量),处理器可 以利用它转跳到处理中断和异常的代码段中。这些门在处EFLAGS中IF标志时做法不一样。 (见Section 5.12.1.2., “Flag Usage By Exception- or Interrupt-Handler Procedure”). Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 64 4.1.5 异常和中断的处理方法 处理器在进行中断/异常处理调用时和CALL指令的效果类似。当处理器响应中断时,使用中 断向量值作为IDT中的描述符索引。如果该索引指向一个中断门或陷阱门,处理器调用中断 例程的过程类似于用CALL语句执行一个调用门。如果该索引指向的是任务门,处理器将会 执行任务切换,切换到中断例程任务中,这和CALL语句调用任务门类似。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 65 (1)异常或中断处理例程 一个中断门或陷阱门关联一个中断或异常处理例程。调用门的段选择符指向一个段描述符, 段描述符指向的代码段在GDT中或当前的LDT中。门描述符的偏移量域指向例程的开始位 置。 当处理器指向一个中断/异常处理例程的调用时,它将会保存当前的状态如EFLAGS寄存器、 CS、EIP寄存器到堆栈中(保存CS和EIP寄存器是为了返回到被中断的程序中)。如果某个 异常需要保存错误代码,处理器将会在继续将其压栈到EIP后。如果例程和被中断的程序在 同一个特权级,例程将会使用当前的堆栈。如果中断例程执行在一个比当前程序低的优先级 上,需要进行堆栈切换。 当进行堆栈切换时,需要返回的堆栈指针也会被压入堆栈(SS和ESP寄存器组合提供一个堆 栈返回指针)。中断例程用到的段选择符和堆栈指针可以从当前正在执行的任务的TSS中获 取。处理器从被中断的程序的堆栈中拷贝EFLAGS,SS,ESP,CS,EIP和错误代码信息到 中断例程的堆栈中。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 66 为了从中断/异常例程中返回,例程中必须使用IRET(或IRETD)指令。IRET指令和RET指 令类似,但它会从保存的标志位里恢复EFLAG寄存器。EFLAGS的IOPL域只有当CPL=0时 才会恢复。 IF标志位只有当CPL小于或等于IOPL时才会改变。见 “IRET/IRETD—Interrupt Return” in Chapter 3 of the IA-32 Intel Architecture Software Developer’s Manual, Volume 2 有 IRET指令的详细描述。 如果调用中断例程时发生任务切换, IRET指令在返回时切换回被中断的程序堆栈。 (2)中断/异常处理例程调用时的保护 进行中断/异常例程调用时,特权级保护和普通的过程调用(通过调用门)类似。处理 器不允许调用特权级的数值大于当前CPL(即低特权级)的中断处理例程。 试图超越这条规则将会引发#GP异常。中断/异常处理例程的保护机制有以下一些不同点: • 因为中断和异常向量没有RPL,在隐式的调用中将不会检查RPL。 • 只有当中断由INT n,INT 3或INTO指令产生时,处理器才会检查中断门或陷阱门的DPL。 此时CPL必须小于或等于门的DPL。该限制是为了阻止运行在特权级3的应用程序使用软件 中断访问关键异常处理例程,如页错误例程(这些例程都处于高特权级代码段中)。对由硬 件产生的中断和处理器检测的异常, 将会忽略中断门/陷阱门的DPL。 因为中断和异常会在不能预测的时间里发生,这些特权级规则有效的加强了中断例程运行的 特权级限制。以下的任何一条规则都可以用来避免发生特权级出错。 • 将中断/异常处理例程放在一个conforming的代码段中。这个技术可用作只需要访问堆栈中 的数据的例程(如除0异常)。如果例程还需要用到数据段中的数据,数据段从特权级3访问, 导致数据不能被保护 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 67 • 可以把例程放在一个nonconforming的特权级0的代码段中。该例程总是可以运行而不需要 考虑CPL。 \ (3)中断/异常处理例程中使用的标志位 当通过中断门或陷阱门访问某个中断例程时,处理器在保存EFLAGS寄存器到堆栈后,清除 EFLAGS中的TF标志位(在调用中断/异常例程时,处理器也会清除VM,RF和NT标志位)。 清除TF阻止了指令跟踪(instruction tracing)。随后的IRET指令从保存EFLAGS的堆栈中恢 复TF(以及VM,RF和NT)。 中断门和陷阱门唯一的不同之处在于处理器的处理IF标志位的方式上。当通过中断门调用中 断处理例程时,处理器清除IF标志位,阻止其它中断的发生对例程的干扰。IRET指令从压 入堆栈的EFLAGS中恢复IF的值,而通过陷阱门调用中断例程则不会影响IF标志。 (4)中断任务 当通过IDT中的任务门调用中断/异常处理例程时,任务切换发生了。用一个单独的任务来提 供中断服务有以下几点好处: • 整个任务或程序的上下文被自动保存。 • 一个新的TSS允许使用中断例程使用特权级0的堆栈。如果当前特权级0堆栈被破坏时发生 中断或异常,通过任务门访问中断例程可以防止系统崩溃,因为可以提供给例程一个新的特 权级0堆栈。 • 为处理例程分配不同的地址空间可以做到很好的隔离效果。可以通过给它一个独立的LDT 做到。 使用任务的缺点是任务切换时大量的压栈操作增加了中断响应的延时。 IDT中的任务门关联GDT中的一个TSS的描述符。切换到中断例程的过程和普通的任务切换 类似。中断任务的链接指针存储在上一个任务的连接指针域中。如果某个异常产生一个错误 代码,错误代码会被拷贝到新任务的堆栈中。 当中断/异常例程任务被操作系统使用,有两种机制可以用来调度(dispatch)任务:软件调 度器(操作系统的一部分)和硬件调度器(处理器中断机制的一部分)。软件调度器需要将 中断例程任务加载进去,以便在中断到来时进行调度。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 68 4.2 Bochs 对中断的模拟 4.2.1 概述 Bochs 的中断和异常处理在 cpu/exception.cpp 中实现, 主要函数有 4 个 „ exception() 异常处理,调用 interrupt() „ Interrupt() 针对当前处理器模式,调用 real_mode_int()或 protected_mode_int(), „ real_mode_int() 处理实模式下的中断,被 interrupt 调用 „ protected_mode_int() 处理保护模式下的中断,被 interrupt 调用 interrupt()函数被调用的地方: 1. Exception()函数,处理异常 2. BX_CPU_C::handleAsyncEvent(), CPU 收到中断信号后调用 3. INT 指令,如 INT 3 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 69 4.2.2 主要函数的源码注释 // vector: 0..255: vector in IDT // error_code: if exception generates and error, push this error code void BX_CPU_C::exception(unsigned vector, Bit16u error_code, bx_bool is_INT) { bx_bool push_error; Bit8u exception_type; unsigned prev_errno; invalidate_prefetch_q(); //使预取队列无效 UNUSED(is_INT); BX_INSTR_EXCEPTION(BX_CPU_ID, vector); BX_DEBUG(("exception(%02x h)", (unsigned) vector)); // if not initial error, restore previous register values from // previous attempt to handle exception if (BX_CPU_THIS_PTR errorno) { BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS] = BX_CPU_THIS_PTR save_cs; BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS] = BX_CPU_THIS_PTR save_ss; EIP = BX_CPU_THIS_PTR save_eip; ESP = BX_CPU_THIS_PTR save_esp; } BX_CPU_THIS_PTR errorno++; if (BX_CPU_THIS_PTR errorno >= 3) { #if BX_RESET_ON_TRIPLE_FAULT BX_ERROR(("exception(): 3rd (%d) exception with no resolution, shutdown status is %02xh, resetting", vector, DEV_cmos_get_reg(0x0f))); bx_pc_system.Reset( BX_RESET_SOFTWARE ); #else BX_PANIC(("exception(): 3rd (%d) exception with no resolution", vector)); BX_ERROR(("WARNING: Any simulation after this point is completely bogus.")); #endif longjmp(BX_CPU_THIS_PTR jmp_buf_env, 1); // go back to main decode loop } /* careful not to get here with curr_exception[1]==DOUBLE_FAULT */ /* ...index on DOUBLE_FAULT below, will be out of bounds */ /* if 1st was a double fault (software INT?), then shutdown */ if ( (BX_CPU_THIS_PTR errorno==2) && (BX_CPU_THIS_PTR curr_exception[0]==BX_ET_DOUBLE_FAULT) ) { #if BX_RESET_ON_TRIPLE_FAULT BX_INFO(("exception(): triple fault encountered, shutdown status is %02xh, resetting", DEV_cmos_get_reg(0x0f))); bx_pc_system.Reset( BX_RESET_SOFTWARE ); //3 次错误,则热启动 #else BX_PANIC(("exception(): triple fault encountered")); BX_ERROR(("WARNING: Any simulation after this point is completely bogus.")); #endif longjmp(BX_CPU_THIS_PTR jmp_buf_env, 1); // go back to main decode loop } /* ??? this is not totally correct, should be done depending on * vector */ /* backup IP to value before error occurred */ EIP = BX_CPU_THIS_PTR prev_eip; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 70 ESP = BX_CPU_THIS_PTR prev_esp; // note: fault-class exceptions _except_ #DB set RF in // eflags image. switch (vector) { //对中断向量号进行讨论 case 0: // DIV by 0 push_error = 0; exception_type = BX_ET_CONTRIBUTORY; BX_CPU_THIS_PTR assert_RF (); break; case 1: // debug exceptions push_error = 0; exception_type = BX_ET_BENIGN; break; case 2: // NMI push_error = 0; exception_type = BX_ET_BENIGN; break; case 3: // breakpoint push_error = 0; exception_type = BX_ET_BENIGN; break; case 4: // overflow push_error = 0; exception_type = BX_ET_BENIGN; break; case 5: // bounds check push_error = 0; exception_type = BX_ET_BENIGN; BX_CPU_THIS_PTR assert_RF (); break; case 6: // invalid opcode push_error = 0; exception_type = BX_ET_BENIGN; BX_CPU_THIS_PTR assert_RF (); break; case 7: // device not available push_error = 0; exception_type = BX_ET_BENIGN; BX_CPU_THIS_PTR assert_RF (); break; case 8: // double fault push_error = 1; exception_type = BX_ET_DOUBLE_FAULT; break; case 9: // coprocessor segment overrun (286,386 only) push_error = 0; exception_type = BX_ET_CONTRIBUTORY; BX_CPU_THIS_PTR assert_RF (); BX_PANIC(("exception(9): unfinished")); break; case 10: // invalid TSS push_error = 1; exception_type = BX_ET_CONTRIBUTORY; error_code = (error_code & 0xfffe) | BX_CPU_THIS_PTR EXT; BX_CPU_THIS_PTR assert_RF (); break; case 11: // segment not present push_error = 1; exception_type = BX_ET_CONTRIBUTORY; error_code = (error_code & 0xfffe) | BX_CPU_THIS_PTR EXT; BX_CPU_THIS_PTR assert_RF (); break; case 12: // stack fault push_error = 1; exception_type = BX_ET_CONTRIBUTORY; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 71 error_code = (error_code & 0xfffe) | BX_CPU_THIS_PTR EXT; BX_CPU_THIS_PTR assert_RF (); break; case 13: // general protection push_error = 1; exception_type = BX_ET_CONTRIBUTORY; error_code = (error_code & 0xfffe) | BX_CPU_THIS_PTR EXT; BX_CPU_THIS_PTR assert_RF (); break; case 14: // page fault if (BX_CPU_THIS_PTR except_chk) // Help with OS/2 { BX_CPU_THIS_PTR except_chk = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value = BX_CPU_THIS_PTR except_cs; BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value = BX_CPU_THIS_PTR except_ss; } push_error = 1; exception_type = BX_ET_PAGE_FAULT; // ??? special format error returned BX_CPU_THIS_PTR assert_RF (); break; case 15: // reserved BX_PANIC(("exception(15): reserved")); push_error = 0; // keep compiler happy for now exception_type = 0; // keep compiler happy for now break; case 16: // floating-point error push_error = 0; exception_type = BX_ET_BENIGN; BX_CPU_THIS_PTR assert_RF (); break; #if BX_CPU_LEVEL >= 4 case 17: // alignment check BX_PANIC(("exception(): alignment-check, vector 17 unimplemented")); push_error = 0; // keep compiler happy for now exception_type = 0; // keep compiler happy for now BX_CPU_THIS_PTR assert_RF (); break; #endif #if BX_CPU_LEVEL >= 5 case 18: // machine check BX_PANIC(("exception(): machine-check, vector 18 unimplemented")); push_error = 0; // keep compiler happy for now exception_type = 0; // keep compiler happy for now break; #endif default: BX_PANIC(("exception(%u): bad vector", (unsigned) vector)); push_error = 0; // keep compiler happy for now exception_type = 0; // keep compiler happy for now break; } //向量号讨论结束 if (exception_type != BX_ET_PAGE_FAULT) { // Page faults have different format error_code = (error_code & 0xfffe) | BX_CPU_THIS_PTR EXT; } BX_CPU_THIS_PTR EXT = 1; //置 CPU 类的 EXT 标志位 /* if we've already had 1st exception, see if 2nd causes a * Double Fault instead. Otherwise, just record 1st exception */ if (BX_CPU_THIS_PTR errorno >= 2) { if (is_exception_OK[BX_CPU_THIS_PTR curr_exception[0]][exception_type]) BX_CPU_THIS_PTR curr_exception[1] = exception_type; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 72 else { BX_CPU_THIS_PTR curr_exception[1] = BX_ET_DOUBLE_FAULT; vector = 8; } } else { BX_CPU_THIS_PTR curr_exception[0] = exception_type; } if (!real_mode()) { //保护模式 prev_errno = BX_CPU_THIS_PTR errorno; BX_CPU_THIS_PTR interrupt(vector, 0, push_error, error_code); //调用 CPU 的中断处理函数 BX_CPU_THIS_PTR errorno = 0; // error resolved longjmp(BX_CPU_THIS_PTR jmp_buf_env, 1); // go back to main decode loop } else // 实模式 { // not INT, no error code pushed BX_CPU_THIS_PTR interrupt(vector, 0, 0, 0); //调用 CPU 的中断处理函数 BX_CPU_THIS_PTR errorno = 0; // error resolved longjmp(BX_CPU_THIS_PTR jmp_buf_env, 1); // go back to main decode loop } } void BX_CPU_C::interrupt(Bit8u vector, bx_bool is_INT, bx_bool is_error_code, Bit16u error_code) { BX_DEBUG(("interrupt(): vector = %u, INT = %u, EXT = %u", (unsigned) vector, (unsigned) is_INT, (unsigned) BX_CPU_THIS_PTR EXT)); BX_INSTR_INTERRUPT(BX_CPU_ID, vector); invalidate_prefetch_q(); // Discard any traps and inhibits for new context; traps will // resume upon return. BX_CPU_THIS_PTR debug_trap = 0; BX_CPU_THIS_PTR inhibit_mask = 0; BX_CPU_THIS_PTR save_cs = BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS]; BX_CPU_THIS_PTR save_ss = BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS]; BX_CPU_THIS_PTR save_eip = EIP; BX_CPU_THIS_PTR save_esp = ESP; if(real_mode()) { real_mode_int(vector, is_INT, is_error_code, error_code); } else { protected_mode_int(vector, is_INT, is_error_code, error_code); } } 实模式的处理比较简单,基本过程是将要保存的寄存器压栈,然后从 IDT 查到 IP 和 CS, 加载它们,函数返回后处理器会转跳到 CS:EIP 执行。函数最后做标志位的清除工作。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 73 void BX_CPU_C::real_mode_int(Bit8u vector, bx_bool is_INT, bx_bool is_error_code, Bit16u error_code) { // real mode interrupt Bit16u cs_selector, ip; if ( (vector*4+3) > BX_CPU_THIS_PTR idtr.limit ) //对 IDT 做段限检查 { BX_ERROR(("interrupt(real mode) vector > idtr.limit")); exception(BX_GP_EXCEPTION, 0, 0); } push_16(read_flags()); //当前 16 位 FLAGS 压栈 cs_selector = BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value; push_16(cs_selector); //当前 CS 压栈 ip = EIP; push_16(ip); //当前 EIP 压栈 access_linear(BX_CPU_THIS_PTR idtr.base + 4 * vector, 2, 0, BX_READ, &ip); //从 IDT 得到中断例程的 IP EIP = (Bit32u) ip; //赋给 EIP access_linear(BX_CPU_THIS_PTR idtr.base + 4 * vector + 2, 2, 0, BX_READ, &cs_selector); load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS], cs_selector); //加载代码段寄存器 /* INT affects the following flags: I,T */ BX_CPU_THIS_PTR clear_IF (); BX_CPU_THIS_PTR clear_TF (); #if BX_CPU_LEVEL >= 4 BX_CPU_THIS_PTR clear_AC (); #endif BX_CPU_THIS_PTR clear_RF (); } ------------------------------------------------------------------------------------------------------------------------------------------ 保护模式下因为涉及到各种门描述符以及特权级(PL)的变换问题,因此中断函数比较复 杂 void BX_CPU_C::protected_mode_int(Bit8u vector, bx_bool is_INT, bx_bool is_error_code, Bit16u error_code) { // protected mode interrupt Bit32u dword1, dword2; bx_descriptor_t gate_descriptor, cs_descriptor; bx_selector_t cs_selector; Bit16u raw_tss_selector; bx_selector_t tss_selector; bx_descriptor_t tss_descriptor; Bit16u gate_dest_selector; Bit32u gate_dest_offset; // interrupt vector must be within IDT table limits, // else #GP(vector number*8 + 2 + EXT) if ( (vector*8 + 7) > BX_CPU_THIS_PTR idtr.limit) { //对 IDT 检查 BX_DEBUG(("IDT.limit = %04x", (unsigned) BX_CPU_THIS_PTR idtr.limit)); BX_DEBUG(("IDT.base = %06x", (unsigned) BX_CPU_THIS_PTR idtr.base)); BX_DEBUG(("interrupt vector must be within IDT table limits")); BX_DEBUG(("interrupt(): vector > idtr.limit")); exception(BX_GP_EXCEPTION, vector*8 + 2, 0); //保护错误 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 74 } // descriptor AR byte must indicate interrupt gate, trap gate, // or task gate, else #GP(vector*8 + 2 + EXT) //读取 IDT 条目(两个双字) access_linear(BX_CPU_THIS_PTR idtr.base + vector*8, 4, 0, BX_READ, &dword1); access_linear(BX_CPU_THIS_PTR idtr.base + vector*8 + 4, 4, 0, BX_READ, &dword2); //用读取的内容构造一个门描述符 parse_descriptor(dword1, dword2, &gate_descriptor); if ((gate_descriptor.valid==0) || gate_descriptor.segment) { //检查门描述符有效性 BX_DEBUG(("interrupt(): gate descriptor is not valid sys seg")); exception(BX_GP_EXCEPTION, vector*8 + 2, 0); } //分类型进行讨论 switch (gate_descriptor.type) { case BX_TASK_GATE: case BX_286_INTERRUPT_GATE: case BX_286_TRAP_GATE: case BX_386_INTERRUPT_GATE: case BX_386_TRAP_GATE: break; default: BX_DEBUG(("interrupt(): gate.type(%u) != {5,6,7,14,15}", (unsigned) gate_descriptor.type)); exception(BX_GP_EXCEPTION, vector*8 + 2, 0); return; } // if software interrupt, then gate descripor DPL must be >= CPL, 特权级检查 // else #GP(vector * 8 + 2 + EXT) if (is_INT && (gate_descriptor.dpl < CPL)) { BX_DEBUG(("interrupt(): is_INT && (dpl < CPL)")); exception(BX_GP_EXCEPTION, vector*8 + 2, 0); } // Gate must be present, else #NP(vector * 8 + 2 + EXT) if (gate_descriptor.p == 0) { BX_DEBUG(("interrupt(): gate not present")); exception(BX_NP_EXCEPTION, vector*8 + 2, 0); } //分类型进行讨论 switch (gate_descriptor.type) { case BX_TASK_GATE: //1. 任务门,做任务切换 // examine selector to TSS, given in task gate descriptor raw_tss_selector = gate_descriptor.u.taskgate.tss_selector; parse_selector(raw_tss_selector, &tss_selector); //得到 TSS 选择符 // must specify global in the local/global bit, // else #TS(TSS selector) // +++ // 486/Pent books say #TSS(selector) // PPro+ says #GP(selector) if (tss_selector.ti) { //执行 LDT BX_PANIC(("interrupt: tss_selector.ti=1")); exception(BX_TS_EXCEPTION, raw_tss_selector & 0xfffc, 0); return; } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 75 // index must be within GDT limits, else #TS(TSS selector) fetch_raw_descriptor(&tss_selector, &dword1, &dword2, BX_TS_EXCEPTION); // AR byte must specify available TSS, // else #TS(TSS selector) parse_descriptor(dword1, dword2, &tss_descriptor); //从 TSS 选择符得到 TSS 描述符 if (tss_descriptor.valid==0 || tss_descriptor.segment) { //检查 TSS 描述符有效性 BX_PANIC(("exception: TSS selector points to bad TSS")); exception(BX_TS_EXCEPTION, raw_tss_selector & 0xfffc, 0); return; } if (tss_descriptor.type!=9 && tss_descriptor.type!=1) { BX_INFO(("exception: TSS selector points to bad TSS")); exception(BX_TS_EXCEPTION, raw_tss_selector & 0xfffc, 0); return; } // TSS must be present, else #NP(TSS selector) // done in task_switch() // switch tasks with nesting to TSS 调用 task_switch()函数,进行任务切换 task_switch(&tss_selector, &tss_descriptor, BX_TASK_FROM_CALL_OR_INT, dword1, dword2); // if interrupt was caused by fault with error code // stack limits must allow push of 2 more bytes, else #SS(0) // push error code onto stack //??? push_16 vs push_32 将错误代码压栈 if ( is_error_code ) { //if (tss_descriptor.type==9) if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].cache.u.segment.d_b) push_32(error_code); else push_16(error_code); } // instruction pointer must be in CS limit, else #GP(0) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 76 if (EIP > BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].cache.u.segment.limit_scaled) { BX_ERROR(("exception(): EIP > CS.limit")); exception(BX_GP_EXCEPTION, 0, 0); } return; case BX_286_INTERRUPT_GATE: //2. 中断门或陷阱门 case BX_286_TRAP_GATE: case BX_386_INTERRUPT_GATE: case BX_386_TRAP_GATE: if ( gate_descriptor.type >= 14 ) { // 386 gate 得到门选择符和偏移量 gate_dest_selector = gate_descriptor.u.gate386.dest_selector; gate_dest_offset = gate_descriptor.u.gate386.dest_offset; } else { // 286 gate gate_dest_selector = gate_descriptor.u.gate286.dest_selector; gate_dest_offset = gate_descriptor.u.gate286.dest_offset; } // examine CS selector and descriptor given in gate descriptor // selector must be non-null else #GP(EXT) if ( (gate_dest_selector & 0xfffc) == 0 ) { BX_ERROR(("int_trap_gate(): selector null")); exception(BX_GP_EXCEPTION, 0, 0); } parse_selector(gate_dest_selector, &cs_selector); //由门选择符得到段选择符 // selector must be within its descriptor table limits // else #GP(selector+EXT) 由段选择符得到段描述符 fetch_raw_descriptor(&cs_selector, &dword1, &dword2, BX_GP_EXCEPTION); parse_descriptor(dword1, dword2, &cs_descriptor); // descriptor AR byte must indicate code seg // and code segment descriptor DPL<=CPL, else #GP(selector+EXT) if ( cs_descriptor.valid==0 || cs_descriptor.segment==0 || cs_descriptor.u.segment.executable==0 || cs_descriptor.dpl>CPL ) { BX_DEBUG(("interrupt(): not code segment")); exception(BX_GP_EXCEPTION, cs_selector.value & 0xfffc, 0); } // segment must be present, else #NP(selector + EXT) if ( cs_descriptor.p==0 ) { BX_DEBUG(("interrupt(): segment not present")); exception(BX_NP_EXCEPTION, cs_selector.value & 0xfffc, 0); } // if code segment is non-conforming and DPL < CPL then // INTERRUPT TO INNER PRIVILEGE: // 2.1 特权级转换,切换到内环运行, 此时需要进行堆栈切换,保存当前任务 //的 SS:ESP,并从当前 TSS 中得到中断例程的堆栈段选择符和堆栈指针 if ( cs_descriptor.u.segment.c_ed==0 && cs_descriptor.dpl=14) { // 386 int/trap gate // new stack must have room for 20|24 bytes, else #SS(0) if ( is_error_code ) bytes = 24; else bytes = 20; if (is_v8086_mode) bytes += 16; } else { // new stack must have room for 10|12 bytes, else #SS(0) if ( is_error_code ) bytes = 12; else bytes = 10; if (is_v8086_mode) { bytes += 8; BX_PANIC(("interrupt: int/trap gate VM")); } } // new stack must have enough room, else #SS(seg selector) if ( !can_push(&ss_descriptor, ESP_for_cpl_x, bytes) ) { BX_DEBUG(("interrupt(): new stack doesn't have room for %u bytes", (unsigned) bytes)); exception(BX_SS_EXCEPTION, SS_for_cpl_x & 0xfffc, 0); } // IP must be within CS segment boundaries, else #GP(0) if (gate_dest_offset > cs_descriptor.u.segment.limit_scaled) { BX_DEBUG(("interrupt(): gate EIP > CS.limit")); exception(BX_GP_EXCEPTION, 0, 0); } //保存当前 CPU 状态,包括被中断任务的 SS:ESP old_ESP = ESP; old_SS = BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value; old_EIP = EIP; old_CS = BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value; //加载新的 SS:SP load_ss(&ss_selector, &ss_descriptor, cs_descriptor.dpl); if (ss_descriptor.u.segment.d_b) ESP = ESP_for_cpl_x; else Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 79 SP = ESP_for_cpl_x; // leave upper 16bits // load new CS:IP values from gate // set CPL to new code segment DPL // set RPL of CS to CPL //加载新的 CS:EIP load_cs(&cs_selector, &cs_descriptor, cs_descriptor.dpl); EIP = gate_dest_offset; Bit32u eflags = read_eflags(); //清除相关的中断标志位 // if INTERRUPT GATE set IF to 0 if ( !(gate_descriptor.type & 1) ) // even is int-gate BX_CPU_THIS_PTR clear_IF (); BX_CPU_THIS_PTR clear_TF (); BX_CPU_THIS_PTR clear_VM (); BX_CPU_THIS_PTR clear_RF (); BX_CPU_THIS_PTR clear_NT (); if (is_v8086_mode) //如果是 V8086 模式 { if (gate_descriptor.type>=14) { // 386 int/trap gate push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector.value); push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector.value); push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value); push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value); } else { push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector.value); push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector.value); push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value); push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value); } BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector.value = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector.value = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value = 0; } if (gate_descriptor.type>=14) { // 386 int/trap gate // push long pointer to old stack onto new stack push_32(old_SS); push_32(old_ESP); // push EFLAGS push_32(eflags); // push long pointer to return address onto new stack push_32(old_CS); push_32(old_EIP); if ( is_error_code ) push_32(error_code); } else { // 286 int/trap gate // push long pointer to old stack onto new stack push_16(old_SS); push_16(old_ESP); // ignores upper 16bits Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 80 // push FLAGS push_16(eflags); // ignores upper 16bits // push return address onto new stack push_16(old_CS); push_16(old_EIP); // ignores upper 16bits if ( is_error_code ) push_16(error_code); } return; } if (v8086_mode()) { // if code segment DPL != 0 then #GP(new code segment selector) exception(BX_GP_EXCEPTION, cs_selector.value & 0xfffc, 0); } // if code segment is conforming OR code segment DPL = CPL then // INTERRUPT TO SAME PRIVILEGE LEVEL: //2.2 中断例程在同一个特权级 if ( cs_descriptor.u.segment.c_ed==1 || cs_descriptor.dpl==CPL ) { int bytes; Bit32u temp_ESP; if (BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.u.segment.d_b) temp_ESP = ESP; else temp_ESP = SP; BX_DEBUG(("int_trap_gate286(): INTERRUPT TO SAME PRIVILEGE")); // Current stack limits must allow pushing 6|8 bytes, else #SS(0) if (gate_descriptor.type >= 14) { // 386 gate if ( is_error_code ) bytes = 16; else bytes = 12; } else { // 286 gate if ( is_error_code ) bytes = 8; else bytes = 6; } if (! can_push(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache, temp_ESP, bytes)) { BX_DEBUG(("interrupt(): stack doesn't have room")); exception(BX_SS_EXCEPTION, 0, 0); } // EIP must be in CS limit else #GP(0) if (gate_dest_offset > cs_descriptor.u.segment.limit_scaled) { BX_ERROR(("interrupt(): IP > cs descriptor limit")); exception(BX_GP_EXCEPTION, 0, 0); } // push flags onto stack // push current CS selector onto stack // push return offset onto stack if (gate_descriptor.type >= 14) { // 386 gate push_32(read_eflags()); push_32(BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 81 push_32(EIP); if ( is_error_code ) push_32(error_code); } else { // 286 gate push_16(read_flags()); push_16(BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value); push_16(IP); if ( is_error_code ) push_16(error_code); } // load CS:IP from gate // load CS descriptor // set the RPL field of CS to CPL load_cs(&cs_selector, &cs_descriptor, CPL); EIP = gate_dest_offset; // if interrupt gate then set IF to 0 清除标志位 if ( !(gate_descriptor.type & 1) ) // even is int-gate BX_CPU_THIS_PTR clear_IF (); BX_CPU_THIS_PTR clear_TF (); BX_CPU_THIS_PTR clear_NT (); BX_CPU_THIS_PTR clear_VM (); BX_CPU_THIS_PTR clear_RF (); return; } // else #GP(CS selector + ext) BX_DEBUG(("interrupt: bad descriptor")); BX_DEBUG(("c_ed=%u, descriptor.dpl=%u, CPL=%u", (unsigned) cs_descriptor.u.segment.c_ed, (unsigned) cs_descriptor.dpl, (unsigned) CPL)); BX_DEBUG(("cs.segment = %u", (unsigned) cs_descriptor.segment)); exception(BX_GP_EXCEPTION, cs_selector.value & 0xfffc, 0); break; default: BX_PANIC(("bad descriptor type in interrupt()!")); break; } } =============================================================================== 函数 protected_mode_int()流程 =============================================================================== Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 82 4.3 虚拟机的任务管理 4.3.1 IA-32 任务管理知识准备 IA-32 的任务管理特性只有在保护模式下才能体现出来。以下内容主要来自 Intel IA-32 体系 结构开发手册(英文),由本文作者翻译。 (1) 任务管理概述 任务是一个小的工作单元,处理器可以对其进行调度、执行和挂起。任务可以是一小段代码 或一个进程或操作系统的某个服务。IA32 提供了保存任务状态的机制,目的是为了在任务 间进行切换。当处理器运行于保护模式下,必须至少有一个任务在执行,当然,一个复杂的 系统可以利用处理器的任务管理特性进行实现多任务操作系统。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 83 (2) 任务结构 任务由两部分构成:任务执行的空间(代码段、数据段和堆栈段)和任务状态段 TSS。如果 操作系统使用处理器的多个特权级,则需要为一个任务的每个特权级分配单独的堆栈空间。 TSS 是用来存储任务信息的一个段,在多任务环境下,TSS 也可以用来提供任务链接(若干 TSS 构成一个链表)。 注: 本章节只描述了32bit任务和32bit TSS,16bit任务则有所不同。 一个任务由TSS的段选择符标识。当某个任务被处理器加载执行,段选择符、基地址、段限 以及段属性将会被加载到任务寄存器(TR)中。如果分页功能打开,任务相关的页目录的 基地址被加载到CR3中。 (3). 任务状态 以下条目定义了当前任务的状态: • 任务当前执行空间,由段选择符表示 (CS, DS, SS, ES, FS, and GS). • 通用寄存器状态. • EFLAGS 状态. • EIP 状态 • CR3 状态 • 任务寄存器状态 • LDTR 寄存器状态 • I/O 位图基地址和 I/O 位图 (包含在 TSS 中). • 特权级 0、1、2 的堆栈指针 • 指向前一个任务的指针 (包含在 TSS 中). (4) 执行一个任务 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 84 软件或处理器可以通过以下方式执行一个任务: • 显式的使用 CALL 指令进行调用。 • 显式的使用 JMP 进行转跳。 • 隐式的(由处理器)调用一个中断处理任务。 • 隐式的调用异常处理任务。 • 当 EFLAGS 中的 NT 置位,调用返回指令(如 IRET)。 所有的这些调用都是通过加载指向某个任务门或 TSS 的段选择符进行的。当使用 CALL 或 JMP 调用任务时,指令的选择符可能是直接指向一个 TSS 或指向 TSS 的一个任务门。在进 行中断处理时切换任务,IDT 中必须包含一个指向中断处理例程的 TSS 的任务门。 当一个任务被调度执行时,任务的切换是自动进行的。在进行任务切换时,当前执行环境(也 叫上下文)被保存在 TSS 中,此后该任务被挂起。新任务的上下文被加载到处理器中,新 任务从 EIP 的位置开始执行。如果这个任务还没有运行过,则 EIP 应该是指向任务代码的开 始位置,否则,将会指向上次执行的最后一条语句的下一条。 任务管理相关的数据结构 处理器定义了 5 个数据结构: • 任务状态段(TSS) • 任务门描述符 • TSS 描述符 •任务寄存器 TR • EFLAGS 寄存器中的 NT 标志位 在保护模式下,必须至少创建一个任务的 TSS 和 TSS 描述符,将 TSS 的段选择符加载到任 务寄存器中(使用 LTR 指令)。 (6) 任务状态段(TSS) 用来保存任务状态信息,下图显示了 32bit CPU 的 TSS 结构(16bit 是另外一个结构):TSS 中的域主要有两类:动态域和静态域。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 85 发生任务切换时,处理器更新TSS的动态域,包括: 通用寄存器 EAX, ECX, EDX, EBX, ESP, EBP, ESI, 和 EDI 寄存器的状态 段选择符 存储在ES, CS, SS, DS, FS,和GS寄存器中 EFLAGS 寄存器 任务切换前的EFAGS EIP (指令指针)域 EIP 寄存器状态 上一个任务的链接Previous task link field 上一个任务的TSS的段选择符,该域又被称为back link,当执行IRET指令时,返回到上一个 被挂起的任务。处理器读取静态域,但一般不修改它们。这些域在任务创建的时候就设好了, Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 86 包括下面的域: LDT 段选择符 任务LDT的段选择符 CR3 当前任务使用的页目录基地址。 特权级-0, -1, 和 -2 的堆栈指针 包括堆栈段SS0, SS1, and SS2和偏移量ESP0, ESP1, and ESP2。 T (debug trap) 标志位 (byte 100, bit 0) 置位时, 当进行任务切换时,T标志位将会引发一个调试异常。 I/O 位图基地址 16bit 的偏移量(从 TSS),IO 允许位图和中断重定位的开始位置。如果该域存在,将会存 放在 TSS 的高端地址位置。 注:如果开启了分页功能,则不要将 TSS 的前 104 个字节放在页边界的位置。因为处理器 在读取它们的时候是连续进行的,因此这 104 个字节存放的必须物理上连续。否则,处理 器则会读到错误的数据且不会产生任何异常。 (7). TSS 描述符 和段描述符类似。 (8). 任务寄存器TR 保存当前正在执行任务的TSS的16bit段选择符、段描述符(32bit段基地址、26bit段限和描述 符属性) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 87 (9). 任务门描述符 任务门提供一个间接的方式来访问某个任务。任务门可以存在于GDT、LDT或IDT中。 访问一个任务可以通过门描述符或TSS描述符,已满足下面的要求: • 因为TSS只有一个,为了方便对其中的域(如busy标志位)引用,可以有多个任务门指向 一个TSS。 (10) 任务切换 任务切换在以下四种情况下发生: • 当前运行的程序或任务执行JMP或CALL语句到GDT中的某个TSS。 • 当前运行的程序或任务执行JMP或CALL语句到GDT或当前LDT中的一个任务门。 • 中断或异常向量指向IDT中的某个任务门。 • 当前任务指向IRET(EFLAGS中的NT标志位置位) JMP, CALL, 和 IRET 指令以及异常及中断都是一些重定位程序的方法。处理器执行任务切 换,需要做以下14步工作: Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 88 1. 从JMP或CALL指令的任务门中获得TSS的段选择符;使用IRET指令时,段选择符从前一 个任务链接域(Link)中得到。 2. 检查当前任务是否允许切换到新任务。应用数据访问特权规则对JMP和CALL指令检查, 当前任务的CPL和新任务段选择符的RPL必须小于或等于TSS描述符(或任务门)的DPL。 异常/中断(除了INT n产生的)和IRET指令允许切换任务而不考虑目标任务门或TSS描述符 的DPL。由软件调用INT n产生的中断,DPL还是会被检查。 3. 检查新任务的TSS描述符是否被标记”存在”,且有合法的长度(大于或等于67H)。 4. 检查新任务是否available(call, jump, exception, 或 interrupt) 或busy (IRET返回). 5. 检查当前的TSS、新任务TSS和所有用到的段描述符是否都已加载到内存中。 6. 如果任务切换由JMP或IRET指令引发,处理器清除当前(旧)任务的TSS描述符的busy (B)标志位,如果由CALL指令、异常或中断引发,busy标志位保留着被设置状态。 7. 如果任务切换由IRET指令引发,处理器清除临时保存的EFLAGS镜像中的NT标志位;如 果由CALL或JMP语句或中断/异常引发,NT不作改动。 8. 保存当前任务的状态到TSS,处理器从任务寄存器TR中找到当前TSS的基地址,拷贝以下 内容到TSS:所有的通用寄存器,段选择符,临时保存的EFLAGS镜像和程序指针EIP。 9. 如果任务切换由CALL指令、异常或中断引发,处理器置位新任务TSS中的EFLAGS镜像 中的NT标志位。如果由IRET指令引发,处理器从堆栈中的EFLAGS镜像恢复NT标志位。如 果由JMP引发,NT不作任何处理。 10. 如果任务切换由CALL指令、JMP指令或异常/中断引发,处理器置位新任务TSS描述符 中的busy标志位;如果由IRET引发,busy标志位保持不变。 11. 置位新任务TSS中保存的CR0镜像中的TS标志位。 12. 加载新任务TSS中的任务段选择符和描述符到任务寄存器TR。 13. 加载新任务状态(TSS中)到处理器中。如何和加载/验证段描述符相关的错误都有可能 在这一步方式(即需要进行加载时检查)。要加载的任务状态信息包括LDTR寄存器,PDBR (控制寄存器CR3)、EFLAGS、EIP、通过寄存器和段寄存器的段描述符部分。 14. 开始执行新任务 表 6.1 列出了任务切换过程中可能会发生的错误和异常 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 89 关于任务链接(TASK LINKING)和其它内容在编写操作系统软件时需要参考。 4.3.2 函数 task_switch()源码注释 函数 task_switch()完成硬件的任务切换工作,总的来说,其过程是按照以上介绍的 13 个步 骤进行的。因为要模拟一些保护机制,所以代码较长。 // Notes: // ====== // Step 2: TSS descriptor is not busy TS (for IRET); GP (for JMP, CALL, INT) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 90 // returns error code (Task's backlink TSS)??? // * TSS selector must map to GDT // * TSS is stored in linear address space // * what to do with I/O Map Base // * what to do with T flag // * where to set CR3 and flush paging cache // * what happens when fault occurs, with some seg regs having valid bit cleared? // * should check validity of current TR(TSS) before writing into it // // ====================== // 286 Task State Segment // ====================== // dynamic item | hex dec offset // 0 task LDT selector | 2a 42 // 1 DS selector | 28 40 // 1 SS selector | 26 38 // 1 CS selector | 24 36 // 1 ES selector | 22 34 // 1 DI | 20 32 // 1 SI | 1e 30 // 1 BP | 1c 28 // 1 SP | 1a 26 // 1 BX | 18 24 // 1 DX | 16 22 // 1 CX | 14 20 // 1 AX | 12 18 // 1 flag word | 10 16 // 1 IP (entry point) | 0e 14 // 0 SS for CPL 2 | 0c 12 // 0 SP for CPL 2 | 0a 10 // 0 SS for CPL 1 | 08 08 // 0 SP for CPL 1 | 06 06 // 0 SS for CPL 0 | 04 04 // 0 SP for CPL 0 | 02 02 // back link selector to TSS | 00 00 // ====================== // 386 Task State Segment // ====================== // |31 16|15 0| // |I/O Map Base |000000000000000000000|T| 64 static // |0000000000000000| LDT | 60 static // |0000000000000000| GS selector | 5c dynamic // |0000000000000000| FS selector | 58 dynamic // |0000000000000000| DS selector | 54 dynamic // |0000000000000000| SS selector | 50 dynamic // |0000000000000000| CS selector | 4c dynamic // |0000000000000000| ES selector | 48 dynamic // | EDI | 44 dynamic // | ESI | 40 dynamic // | EBP | 3c dynamic // | ESP | 38 dynamic // | EBX | 34 dynamic // | EDX | 30 dynamic // | ECX | 2c dynamic // | EAX | 28 dynamic // | EFLAGS | 24 dynamic Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 91 // | EIP (entry point) | 20 dynamic // | CR3 (PDPR) | 1c static // |000000000000000 | SS for CPL 2 | 18 static // | ESP for CPL 2 | 14 static // |000000000000000 | SS for CPL 1 | 10 static // | ESP for CPL 1 | 0c static // |000000000000000 | SS for CPL 0 | 08 static // | ESP for CPL 0 | 04 static // |000000000000000 | back link to prev TSS | 00 dynamic (updated only when return expected) // ================================================== // Effect of task switch on Busy, NT, and Link Fields // ================================================== // Field jump call/interrupt iret // ------------------------------------------------------ // new busy bit Set Set No change // old busy bit Cleared No change Cleared // new NT flag No change Set No change // old NT flag No change No change Cleared // new link No change old TSS selector No change // old link No change No change No change // CR0.TS Set Set Set // Note: I checked 386, 486, and Pentium, and they all exhibited // exactly the same behaviour as above. There seems to // be some misprints in the Intel docs. void BX_CPU_C::task_switch(bx_selector_t *tss_selector, bx_descriptor_t *tss_descriptor, unsigned source, Bit32u dword1, Bit32u dword2) { Bit32u obase32; // base address of old TSS Bit32u nbase32; // base address of new TSS Bit32u temp32, newCR3; Bit16u raw_cs_selector, raw_ss_selector, raw_ds_selector, raw_es_selector, raw_fs_selector, raw_gs_selector, raw_ldt_selector; Bit16u temp16, trap_word; bx_selector_t cs_selector, ss_selector, ds_selector, es_selector, fs_selector, gs_selector, ldt_selector; bx_descriptor_t cs_descriptor, ss_descriptor, ds_descriptor, es_descriptor, fs_descriptor, gs_descriptor, ldt_descriptor; Bit32u old_TSS_max, new_TSS_max, old_TSS_limit, new_TSS_limit; Bit32u newEAX, newECX, newEDX, newEBX; Bit32u newESP, newEBP, newESI, newEDI; Bit32u newEFLAGS, oldEFLAGS, newEIP; unsigned exception_no; Bit16u error_code; BX_DEBUG(( "TASKING: ENTER" )); invalidate_prefetch_q(); // Discard any traps and inhibits for new context; traps will // resume upon return. BX_CPU_THIS_PTR debug_trap = 0; BX_CPU_THIS_PTR inhibit_mask = 0; // The following checks are made before calling task_switch(), for // JMP & CALL only. These checks are NOT made for exceptions, // interrupts, & IRET. // // 1) TSS DPL must be >= CPL Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 92 // 2) TSS DPL must be >= TSS selector RPL // 3) TSS descriptor is not busy. TS(for IRET); GP(for JMP, CALL, INT) // Privilege and busy checks done in CALL, JUMP, INT, IRET exception_no = 256; // no exception error_code = 0; oldEFLAGS = read_eflags(); // Gather info about old TSS 搜集旧的 TSS 信息 if (BX_CPU_THIS_PTR tr.cache.type <= 3) { //286TSS // sanity check type: cannot have busy bit BX_ASSERT ((BX_CPU_THIS_PTR tr.cache.type & 2) == 0); obase32 = BX_CPU_THIS_PTR tr.cache.u.tss286.base; //从 TR 读取 old_TSS_max = 43; old_TSS_limit = BX_CPU_THIS_PTR tr.cache.u.tss286.limit; } else { //386TSS obase32 = BX_CPU_THIS_PTR tr.cache.u.tss386.base; old_TSS_max = 103; //最小合法长度 old_TSS_limit = BX_CPU_THIS_PTR tr.cache.u.tss386.limit_scaled; } // Gather info about new TSS if (tss_descriptor->type <= 3) { // {1,3} nbase32 = tss_descriptor->u.tss286.base; // new TSS.base //286TSS,从参数读取 new_TSS_max = 43; new_TSS_limit = tss_descriptor->u.tss286.limit; } else { // tss_descriptor->type = {9,11} nbase32 = tss_descriptor->u.tss386.base; // new TSS.base//386TSS new_TSS_max = 103; new_TSS_limit = tss_descriptor->u.tss386.limit_scaled; } // Task State Seg must be present, else #NP(TSS selector) if (tss_descriptor->p==0) { BX_ERROR(("task_switch: TSS.p == 0")); exception(BX_NP_EXCEPTION, tss_selector->value & 0xfffc, 0); } // TSS must have valid limit, else #TS(TSS selector) if (tss_selector->ti || tss_descriptor->valid==0 || new_TSS_limit < new_TSS_max) { BX_ERROR(("task_switch(): TR not valid")); exception(BX_TS_EXCEPTION, tss_selector->value & 0xfffc, 0); } #if BX_SUPPORT_PAGING // Check that old TSS, new TSS, and all segment descriptors // used in the task switch are paged in. if (BX_CPU_THIS_PTR cr0.pg) //启用了分页 { // Old TSS (void) dtranslate_linear(obase32, 0, BX_WRITE); (void) dtranslate_linear(obase32+old_TSS_max, 0, BX_WRITE); // New TSS (void) dtranslate_linear(nbase32, 0, BX_READ); (void) dtranslate_linear(nbase32+new_TSS_max, 0, BX_READ); // ??? Humm, we check the new TSS region with READ above, // but sometimes we need to write the link field in that // region. We also sometimes update other fields, perhaps Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 93 // we need to WRITE check them here also, so that we keep // the written state consistent (ie, we don't encounter a // page fault in the middle). // // ??? fix RW above // ??? touch old/new TSS descriptors here when necessary. } #endif // BX_SUPPORT_PAGING // Need to fetch all new registers and temporarily store them. if (tss_descriptor->type <= 3) { //------------------------读取 16bit TSS---------------------------------- access_linear(nbase32 + 14, 2, 0, BX_READ, &temp16); newEIP = temp16; // zero out upper word access_linear(nbase32 + 16, 2, 0, BX_READ, &temp16); newEFLAGS = temp16; // incoming TSS is 16bit: // - upper word of general registers is set to 0xFFFF // - upper word of eflags is zero'd // - FS, GS are zero'd // - upper word of eIP is zero'd access_linear(nbase32 + 18, 2, 0, BX_READ, &temp16); newEAX = 0xffff0000 | temp16; access_linear(nbase32 + 20, 2, 0, BX_READ, &temp16); newECX = 0xffff0000 | temp16; access_linear(nbase32 + 22, 2, 0, BX_READ, &temp16); newEDX = 0xffff0000 | temp16; access_linear(nbase32 + 24, 2, 0, BX_READ, &temp16); newEBX = 0xffff0000 | temp16; access_linear(nbase32 + 26, 2, 0, BX_READ, &temp16); newESP = 0xffff0000 | temp16; access_linear(nbase32 + 28, 2, 0, BX_READ, &temp16); newEBP = 0xffff0000 | temp16; access_linear(nbase32 + 30, 2, 0, BX_READ, &temp16); newESI = 0xffff0000 | temp16; access_linear(nbase32 + 32, 2, 0, BX_READ, &temp16); newEDI = 0xffff0000 | temp16; access_linear(nbase32 + 34, 2, 0, BX_READ, &raw_es_selector); access_linear(nbase32 + 36, 2, 0, BX_READ, &raw_cs_selector); access_linear(nbase32 + 38, 2, 0, BX_READ, &raw_ss_selector); access_linear(nbase32 + 40, 2, 0, BX_READ, &raw_ds_selector); access_linear(nbase32 + 42, 2, 0, BX_READ, &raw_ldt_selector); raw_fs_selector = 0; // use a NULL selector raw_gs_selector = 0; // use a NULL selector // No CR3 change for 286 task switch newCR3 = 0; // keep compiler happy (not used) trap_word = 0; // keep compiler happy (not used) } else { //--------------------------读取 32bit TSS--------------------------------------- if (BX_CPU_THIS_PTR cr0.pg) access_linear(nbase32 + 0x1c, 4, 0, BX_READ, &newCR3); else newCR3 = 0; // keep compiler happy (not used) access_linear(nbase32 + 0x20, 4, 0, BX_READ, &newEIP); access_linear(nbase32 + 0x24, 4, 0, BX_READ, &newEFLAGS); access_linear(nbase32 + 0x28, 4, 0, BX_READ, &newEAX); access_linear(nbase32 + 0x2c, 4, 0, BX_READ, &newECX); access_linear(nbase32 + 0x30, 4, 0, BX_READ, &newEDX); access_linear(nbase32 + 0x34, 4, 0, BX_READ, &newEBX); access_linear(nbase32 + 0x38, 4, 0, BX_READ, &newESP); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 94 access_linear(nbase32 + 0x3c, 4, 0, BX_READ, &newEBP); access_linear(nbase32 + 0x40, 4, 0, BX_READ, &newESI); access_linear(nbase32 + 0x44, 4, 0, BX_READ, &newEDI); access_linear(nbase32 + 0x48, 2, 0, BX_READ, &raw_es_selector); access_linear(nbase32 + 0x4c, 2, 0, BX_READ, &raw_cs_selector); access_linear(nbase32 + 0x50, 2, 0, BX_READ, &raw_ss_selector); access_linear(nbase32 + 0x54, 2, 0, BX_READ, &raw_ds_selector); access_linear(nbase32 + 0x58, 2, 0, BX_READ, &raw_fs_selector); access_linear(nbase32 + 0x5c, 2, 0, BX_READ, &raw_gs_selector); access_linear(nbase32 + 0x60, 2, 0, BX_READ, &raw_ldt_selector); access_linear(nbase32 + 0x64, 2, 0, BX_READ, &trap_word); // I/O Map Base Address ??? } // // Step 6: If JMP or IRET, clear busy bit in old task TSS descriptor, // otherwise leave set. //6. 如果任务切换由 JMP 或 IRET 指令引发,处理器清除当前(旧)任务的 TSS 描述符 的 busy(B)标志位,如果由 CALL 指令、异常或中断引发,busy 标志位保留着被设置 状态。I // effect on Busy bit of old task if ( (source==BX_TASK_FROM_JUMP) || (source==BX_TASK_FROM_IRET) ) { // Bit is cleared Bit32u laddr; laddr = BX_CPU_THIS_PTR gdtr.base + (BX_CPU_THIS_PTR tr.selector.index<<3) + 4; access_linear(laddr, 4, 0, BX_READ, &temp32); temp32 &= ~0x00000200; access_linear(laddr, 4, 0, BX_WRITE, &temp32); } // // Step 7: If IRET, clear NT flag in temp image of EFLAGS, otherwise // leave alone. //7. 如果任务切换由 IRET 指令引发,处理器清除临时保存的 EFLAGS 镜像中的 NT 标 志位;如果由 CALL 或 JMP 语句或中断/异常引发,NT 不作改动。 if (source == BX_TASK_FROM_IRET) { // NT flags in old task is cleared with an IRET oldEFLAGS &= ~0x00004000; } // // Step 8: Save dynamic state of old task. //8. 保存旧的 TSS 动态部分(分 16bit 和 32bit) // if (BX_CPU_THIS_PTR tr.cache.type <= 3) { // sanity check: tr.cache.type cannot have busy bit BX_ASSERT ((BX_CPU_THIS_PTR tr.cache.type & 2) == 0); temp16 = IP; access_linear(obase32 + 14, 2, 0, BX_WRITE, &temp16); temp16 = oldEFLAGS; access_linear(obase32 + 16, 2, 0, BX_WRITE, &temp16); temp16 = AX; access_linear(obase32 + 18, 2, 0, BX_WRITE, &temp16); temp16 = CX; access_linear(obase32 + 20, 2, 0, BX_WRITE, &temp16); temp16 = DX; access_linear(obase32 + 22, 2, 0, BX_WRITE, &temp16); temp16 = BX; access_linear(obase32 + 24, 2, 0, BX_WRITE, &temp16); temp16 = SP; access_linear(obase32 + 26, 2, 0, BX_WRITE, &temp16); temp16 = BP; access_linear(obase32 + 28, 2, 0, BX_WRITE, &temp16); temp16 = SI; access_linear(obase32 + 30, 2, 0, BX_WRITE, &temp16); temp16 = DI; access_linear(obase32 + 32, 2, 0, BX_WRITE, &temp16); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value; access_linear(obase32 + 34, 2, 0, BX_WRITE, &temp16); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 95 access_linear(obase32 + 36, 2, 0, BX_WRITE, &temp16); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value; access_linear(obase32 + 38, 2, 0, BX_WRITE, &temp16); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value; access_linear(obase32 + 40, 2, 0, BX_WRITE, &temp16); } else { temp32 = EIP; access_linear(obase32 + 0x20, 4, 0, BX_WRITE, &temp32); temp32 = oldEFLAGS; access_linear(obase32 + 0x24, 4, 0, BX_WRITE, &temp32); temp32 = EAX; access_linear(obase32 + 0x28, 4, 0, BX_WRITE, &temp32); temp32 = ECX; access_linear(obase32 + 0x2c, 4, 0, BX_WRITE, &temp32); temp32 = EDX; access_linear(obase32 + 0x30, 4, 0, BX_WRITE, &temp32); temp32 = EBX; access_linear(obase32 + 0x34, 4, 0, BX_WRITE, &temp32); temp32 = ESP; access_linear(obase32 + 0x38, 4, 0, BX_WRITE, &temp32); temp32 = EBP; access_linear(obase32 + 0x3c, 4, 0, BX_WRITE, &temp32); temp32 = ESI; access_linear(obase32 + 0x40, 4, 0, BX_WRITE, &temp32); temp32 = EDI; access_linear(obase32 + 0x44, 4, 0, BX_WRITE, &temp32); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector.value; access_linear(obase32 + 0x48, 2, 0, BX_WRITE, &temp16); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector.value; access_linear(obase32 + 0x4c, 2, 0, BX_WRITE, &temp16); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector.value; access_linear(obase32 + 0x50, 2, 0, BX_WRITE, &temp16); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector.value; access_linear(obase32 + 0x54, 2, 0, BX_WRITE, &temp16); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector.value; access_linear(obase32 + 0x58, 2, 0, BX_WRITE, &temp16); temp16 = BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector.value; access_linear(obase32 + 0x5c, 2, 0, BX_WRITE, &temp16); } // // Commit point. At this point, we commit to the new // context. If an unrecoverable error occurs in further // processing, we complete the task switch without performing // additional access and segment availablility checks and // generate the appropriate exception prior to beginning // execution of the new task. // // Task switch clears LE/L3/L2/L1/L0 in DR7 BX_CPU_THIS_PTR dr7 &= ~0x00000155; // effect on link field of new task if ( source==BX_TASK_FROM_CALL_OR_INT ) { // set to selector of old task's TSS temp16 = BX_CPU_THIS_PTR tr.selector.value; access_linear(nbase32 + 0, 2, 0, BX_WRITE, &temp16); } // // Step 9: If call or interrupt, set the NT flag in the eflags // image stored in new task's TSS. If IRET or JMP, // NT is restored from new TSS eflags image. (no change) //9. 如果任务切换由 CALL 指令、异常或中断引发,处理器置位新任务 TSS 中的 EFLAGS 镜像中的 NT 标志位。如果由 IRET 指令引发,处理器从堆栈中的 EFLAGS 镜像恢复 NT 标志位。如果由 JMP 引发,NT 不作任何处理。 // effect on NT flag of new task if ( source==BX_TASK_FROM_CALL_OR_INT ) { newEFLAGS |= 0x4000; // flag is set } // Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 96 // Step 10: If CALL, interrupt, or JMP, set busy flag in new task's // TSS descriptor. If IRET, leave set. //10. 如果任务切换由 CALL 指令、JMP 指令或异常/中断引发,处理器置位新任务 TSS 描述符中的 busy(B)标志位;如果由 IRET 引发,busy 标志位保持不变。 if ( (source==BX_TASK_FROM_JUMP) || (source==BX_TASK_FROM_CALL_OR_INT) ) { // set the new task's busy bit Bit32u laddr; laddr = BX_CPU_THIS_PTR gdtr.base + (tss_selector->index<<3) + 4; access_linear(laddr, 4, 0, BX_READ, &dword2); dword2 |= 0x00000200; access_linear(laddr, 4, 0, BX_WRITE, &dword2); } // // Step 11: Set TS flag in the CR0 image stored in the new task TSS. //11. 置位新任务 TSS 中保存的 CR0 镜像中的 TS 标志位。 // set TS bit in CR0 register BX_CPU_THIS_PTR cr0.ts = 1; BX_CPU_THIS_PTR cr0.val32 |= 0x00000008; // // Step 12: Load the task register with the segment selector and // descriptor for the new task TSS. //12. 加载新任务 TSS 中的任务段选择符和描述符到任务寄存器 TR。 BX_CPU_THIS_PTR tr.selector = *tss_selector; BX_CPU_THIS_PTR tr.cache = *tss_descriptor; // Reset the busy-flag, because all functions expect non-busy types in // tr.cache. From Peter Lammich . BX_CPU_THIS_PTR tr.cache.type &= ~2; // // Step 13: Load the new task (dynamic) state from new TSS. // Any errors associated with loading and qualification of // segment descriptors in this step occur in the new task's // context. State loaded here includes LDTR, CR3, // EFLAGS, EIP, general purpose registers, and segment // descriptor parts of the segment registers. //13. 加载新任务状态(TSS 中)到处理器中。如何和加载/验证段描述符相关的错误都 有可能在这一步方式(即需要进行加载时检查)。要加载的任务状态信息包括 LDTR 寄存器, PDBR(控制寄存器 CR3)、EFLAGS、EIP、通过寄存器和段寄存器的段描述符部分。 if ( (tss_descriptor->type >= 9) && BX_CPU_THIS_PTR cr0.pg) { CR3_change(newCR3); // Tell paging unit about new cr3 value BX_DEBUG (("task_switch changing CR3 to 0x%08x", newCR3)); BX_INSTR_TLB_CNTRL(BX_CPU_ID, BX_INSTR_TASKSWITCH, newCR3); } BX_CPU_THIS_PTR prev_eip = EIP = newEIP; write_eflags(newEFLAGS, 1,1,1,1); //通用寄存器 EAX = newEAX; ECX = newECX; EDX = newEDX; EBX = newEBX; ESP = newESP; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 97 EBP = newEBP; ESI = newESI; EDI = newEDI; // Fill in selectors for all segment registers. If errors // occur later, the selectors will at least be loaded. parse_selector(raw_es_selector, &es_selector); BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].selector = es_selector; parse_selector(raw_cs_selector, &cs_selector); BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].selector = cs_selector; parse_selector(raw_ss_selector, &ss_selector); BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].selector = ss_selector; parse_selector(raw_ds_selector, &ds_selector); BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].selector = ds_selector; parse_selector(raw_fs_selector, &fs_selector); BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].selector = fs_selector; parse_selector(raw_gs_selector, &gs_selector); BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].selector = gs_selector; parse_selector(raw_ldt_selector, &ldt_selector); BX_CPU_THIS_PTR ldtr.selector = ldt_selector; // Start out with invalid descriptor caches, fill in // with values only as they are validated. BX_CPU_THIS_PTR ldtr.cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES].cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS].cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS].cache.valid = 0; BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS].cache.valid = 0; // need to test valid bit in fetch_raw_descriptor?() // or set limit to 0 instead when LDT is loaded with // null. ??? +++ BX_CPU_THIS_PTR ldtr.cache.u.ldt.limit = 0; // LDTR if (ldt_selector.ti) { // LDT selector must be in GDT BX_INFO(("task_switch: bad LDT selector TI=1")); exception_no = BX_TS_EXCEPTION; error_code = raw_ldt_selector & 0xfffc; goto post_exception; } // ??? is LDT loaded in v8086 mode if ( (raw_ldt_selector & 0xfffc) != 0 ) { bx_bool good = fetch_raw_descriptor2(&ldt_selector, &dword1, &dword2); if (!good) { BX_INFO(("task_switch: bad LDT fetch")); exception_no = BX_TS_EXCEPTION; error_code = raw_ldt_selector & 0xfffc; goto post_exception; } parse_descriptor(dword1, dword2, &ldt_descriptor); // LDT selector of new task is valid, else #TS(new task's LDT) if (ldt_descriptor.valid==0 || ldt_descriptor.type!=BX_SYS_SEGMENT_LDT || ldt_descriptor.segment || ldt_descriptor.u.ldt.limit<7) { Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 98 BX_INFO(("task_switch: bad LDT segment")); exception_no = BX_TS_EXCEPTION; error_code = raw_ldt_selector & 0xfffc; goto post_exception; } // LDT of new task is present in memory, else #TS(new tasks's LDT) else if (ldt_descriptor.p==0) { exception_no = BX_TS_EXCEPTION; error_code = raw_ldt_selector & 0xfffc; goto post_exception; } // All checks pass, fill in LDTR shadow cache BX_CPU_THIS_PTR ldtr.cache = ldt_descriptor; } else { // NULL LDT selector is OK, leave cache invalid } if (v8086_mode()) { // load seg regs as 8086 registers load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS], raw_cs_selector); load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS], raw_ss_selector); load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_DS], raw_ds_selector); load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_ES], raw_es_selector); load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_FS], raw_fs_selector); load_seg_reg(&BX_CPU_THIS_PTR sregs[BX_SEG_REG_GS], raw_gs_selector); } else { //以下进行 CS,DS,SS,ES,FS,GS 描述符的加载和检查 // CS if ( (raw_cs_selector & 0xfffc) != 0 ) { bx_bool good = fetch_raw_descriptor2(&cs_selector, &dword1, &dword2); if (!good) { BX_INFO(("task_switch: bad CS fetch")); exception_no = BX_TS_EXCEPTION; error_code = raw_cs_selector & 0xfffc; goto post_exception; } parse_descriptor(dword1, dword2, &cs_descriptor); // CS descriptor AR byte must indicate code segment else #TS(CS) if (cs_descriptor.valid==0 || cs_descriptor.segment==0 || cs_descriptor.u.segment.executable==0) { BX_PANIC(("task_switch: CS not valid executable seg")); exception_no = BX_TS_EXCEPTION; error_code = raw_cs_selector & 0xfffc; goto post_exception; } // if non-conforming then DPL must equal selector RPL else #TS(CS) else if (cs_descriptor.u.segment.c_ed==0 && cs_descriptor.dpl!=cs_selector.rpl) { BX_INFO(("task_switch: non-conforming: CS.dpl!=CS.RPL")); exception_no = BX_TS_EXCEPTION; error_code = raw_cs_selector & 0xfffc; goto post_exception; } // if conforming then DPL must be <= selector RPL else #TS(CS) else if (cs_descriptor.u.segment.c_ed && cs_descriptor.dpl>cs_selector.rpl) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 99 { BX_INFO(("task_switch: conforming: CS.dpl>RPL")); exception_no = BX_TS_EXCEPTION; error_code = raw_cs_selector & 0xfffc; goto post_exception; } // Code segment is present in memory, else #NP(new code segment) else if (cs_descriptor.p==0) { BX_PANIC(("task_switch: CS.p==0")); exception_no = BX_NP_EXCEPTION; error_code = raw_cs_selector & 0xfffc; goto post_exception; } // All checks pass, fill in shadow cache BX_CPU_THIS_PTR sregs[BX_SEG_REG_CS].cache = cs_descriptor; } else { // If new cs selector is null #TS(CS) BX_PANIC(("task_switch: CS NULL")); exception_no = BX_TS_EXCEPTION; error_code = raw_cs_selector & 0xfffc; goto post_exception; } // SS if ( (raw_ss_selector & 0xfffc) != 0 ) { bx_bool good = fetch_raw_descriptor2(&ss_selector, &dword1, &dword2); if (!good) { BX_INFO(("task_switch: bad SS fetch")); exception_no = BX_TS_EXCEPTION; error_code = raw_ss_selector & 0xfffc; goto post_exception; } parse_descriptor(dword1, dword2, &ss_descriptor); // SS selector must be within its descriptor table limits else #TS(SS) // SS descriptor AR byte must must indicate writable data segment, // else #TS(SS) if (ss_descriptor.valid==0 || ss_descriptor.segment==0 || ss_descriptor.u.segment.executable || ss_descriptor.u.segment.r_w==0) { BX_INFO(("task_switch: SS not valid")); exception_no = BX_TS_EXCEPTION; error_code = raw_ss_selector & 0xfffc; goto post_exception; } // // Stack segment is present in memory, else #SF(new stack segment) // else if (ss_descriptor.p==0) { BX_PANIC(("task_switch: SS not present")); exception_no = BX_SS_EXCEPTION; error_code = raw_ss_selector & 0xfffc; goto post_exception; } // Stack segment DPL matches CS.RPL, else #TS(new stack segment) else if (ss_descriptor.dpl != cs_selector.rpl) { BX_PANIC(("task_switch: SS.rpl != CS.RPL")); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 100 exception_no = BX_TS_EXCEPTION; error_code = raw_ss_selector & 0xfffc; goto post_exception; } // Stack segment DPL matches selector RPL, else #TS(new stack segment) else if (ss_descriptor.dpl != ss_selector.rpl) { BX_PANIC(("task_switch: SS.dpl != SS.rpl")); exception_no = BX_TS_EXCEPTION; error_code = raw_ss_selector & 0xfffc; goto post_exception; } // All checks pass, fill in shadow cache BX_CPU_THIS_PTR sregs[BX_SEG_REG_SS].cache = ss_descriptor; } else { // SS selector is valid, else #TS(new stack segment) BX_PANIC(("task_switch: SS NULL")); exception_no = BX_TS_EXCEPTION; error_code = raw_ss_selector & 0xfffc; goto post_exception; } // if new selector is not null then perform following checks: // index must be within its descriptor table limits else #TS(selector) // AR byte must indicate data or readable code else #TS(selector) // if data or non-conforming code then: // DPL must be >= CPL else #TS(selector) // DPL must be >= RPL else #TS(selector) // AR byte must indicate PRESENT else #NP(selector) // load cache with new segment descriptor and set valid bit // DS if ( (raw_ds_selector & 0xfffc) != 0 ) { bx_bool good = fetch_raw_descriptor2(&ds_selector, &dword1, &dword2); if (!good) { BX_INFO(("task_switch: bad DS fetch")); exception_no = BX_TS_EXCEPTION; error_code = raw_ds_selector & 0xfffc; goto post_exception; } parse_descriptor(dword1, dword2, &ds_descriptor); if (ds_descriptor.valid==0 || ds_descriptor.segment==0 || (ds_descriptor.u.segment.executable && ds_descriptor.u.segment.r_w==0)) { BX_PANIC(("task_switch: DS not valid")); exception_no = BX_TS_EXCEPTION; error_code = raw_ds_selector & 0xfffc; goto post_exception; } // if data or non-conforming code else if (ds_descriptor.type<12 && (ds_descriptor.dpltype>=9) && (trap_word & 0x0001)) { BX_CPU_THIS_PTR debug_trap |= 0x00008000; // BT flag in DR6 BX_CPU_THIS_PTR async_event = 1; // so processor knows to check BX_INFO(("task_switch: T bit set in new TSS.")); } // // Step 14: Begin execution of new task. // BX_DEBUG(( "TASKING: LEAVE" )); return; post_exception: BX_CPU_THIS_PTR debug_trap = 0; BX_CPU_THIS_PTR inhibit_mask = 0; BX_INFO(("task switch: posting exception %u after commit point", exception_no)); exception(exception_no, error_code, 0); } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 104 Chapter 5 存储器源码分析 5.1 IA-32 体系的存储器结构 介绍 Intel x86 体系结构的资料很多, 从 80386DX 开始,Intel 处理器就可以寻址 4GB 的空间, 这 4GB 的空间被分为四个基本区域(有一些区域被进一步细分): • 常规内存(Conventional Memory):系统内存的第一个 640 KB 就是著名的常规内存。 它是标准 DOS 程序、DOS 驱动程序、常驻内存程序等可用的区域,它们统统都被 放置在 00000h~9FFFFh 之间。 • 上位内存区(Upper Memory Area):系统内存的第一个 1M 内存顶端的 384 KB(1024 KB - 640 KB)就是 UMA,它紧随在常规内存之后。也就是说,第一个 1M 内存被 分成 640KB 常规内存和 384KB 的 UMA。这个区域是系统保留区域,用户程序不能 使用它。它一部分被系统设备(CGA、VGA 等)使用,另外一部分被用做 ROM shadowing 和 Drivers。UMA 使用内存区域 A0000h~FFFFFh。 • 高端内存区(High Memory Area):系统内存第 2 个 1M 内存的第一个 64 KB 区域, 被称做 HMA。从技术上讲,它属于扩展内存的第一个 64 KB,但它和其他扩展内存 区域所不同的是,它可以在 real mode 下被直接访问,其它的则不然。所以在 DOS 时代,后期的 DOS 版本允许用户通过配置将 DOS 本身放置在 HMA,从而让用户可 以有更多的常规内存可以使用。HMA 占据地址 100000h~10FFEFh。 • 扩展内存(Extended Memory):从 HMA 结束的位置到系统物理内存的最大值之间的 区域都属于扩展内存。当一个 OS 运行在 Protected Mode 时,它可以被访问,而在 Real Mode 下,则无法被访问(除非通过某些 Hacker 方法)。它的地址范围是 10FFF0h~Last address of system momory(maximum of 4G-1M)。从技术上说,HMA 也 属于扩展内存,这依赖于你如何看待这个问题。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 105 随着 CPU 的发展, 寻址空间也变得愈来愈大, 下图显示了可以最大寻址 64GB 空间的分布 图,并详细的描述了 0-1M 内的分布情况,1 MB 以上的所有物理内存都被称做 Extended Memory (扩展内存)。尽管如此,IBM PC 机所用的内存空间都在 4GB 范围内讨论。 从 CPU 发出的地址信号送到北桥芯片(GMCH),北桥芯片根据内部的设置进行译码,会将 地址送到对应的物理设备中。这些设备可以是 BIOS ROM,SDRAM,或 者 PCI 的存储空间。 5.2 Bochs 对存储器的模拟 Bochs 中模拟存储器的类是 BX_MEM_C, 在 memory/memory.h 中定义,实现部分分布在 两个文件中: Memory/memory.cpp 和 Memory/misc_mem.cpp Bochs 中没有定义一个 4G 大小的 byte 数组来模拟 MEM 地址空间,而是将这些划分成不同 的设备,主要是三个部分:主存 SDRAM,256KB ROM,和另外一个附加的空间。 Bit8u *vector; // aligned correctly 指向系统内存(SDRAM)的指针 Bit8u *rom; // 256k rom space Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 106 Bit8u *bogus; // 4k for unexisting memory Vector 的大小是根据 config 里配置的内存大小分配的, 如 32MB。 Rom 空间可以装载系统 BIOS,Display BIOS 等。 同样 Bochs 中并没有北桥芯片这个类(实际上 Bochs 有一个 440FX 的 PCI 设备,即北桥芯 片功能),但 BX_MEM_C 类中定义了一个 GetHostMemAddr()函数, 该函数是将 Host 地址翻译成对应的设备上的地址,即映射到 vector,rom 还是其他。这个函数主要是用在 CPU 取指令操作上,通过它获得一个取指指针(可以参考取指部分)。用户程序对存储器的访问 是通过另外两个函数进行的,即 writePhysicalPage (BX_CPU_C *cpu, Bit32u addr, unsigned len, void *data) readPhysicalPage (BX_CPU_C *cpu, Bit32u addr, unsigned len, void *data) 在解释这两个函数之前,先说明一下在 bochs 中常用的思想――注册 handler。 为了解决前面提到的 4GB 空间有可能被 PCI 设备(bochs 目前没有 AGP)或占用的情况。 Bochs 定义了一个叫做 mem handler 的链表,其节点的数据结构如下 先引入一个结构体, 这个结构体存储注册的 mem_handler struct memory_handler_struct { struct memory_handler_struct *next; //下一个的 pointer, 用来构成链表 unsigned long begin; //受管理的内存端开始地址 unsigned long end; //受管理的内存端结束地址 memory_handler_t read_handler; //读函数 memory_handler_t write_handler; //写函数 void *read_param; void *write_param; }; 起点相同(ADDR>>20,即 1M 对齐)若干个 hanlder 串联成一张链表。当 BX_MEM_C 类检测 到有存储器的读写访问操作时,就调用 readPhysicalPage 或 writePhysicalPage, 在这两个函 数中,先由 memory_handlers[]找到链表的入口, 然后对该链表进行遍历,发现地址落在某 个 memory_handler_struct 中,即 begin>20 handlerXXX start end Read() Write() handlerXXX start end Read() Write() handlerXXX …… …… …… memory_handlers[] 5.3 存储器类 BX_MEM_C 部分源码分析 5.3.1 相关数据结构与类定义 (1).结构体 memory_handler_struct 先引入一个结构体, 这个结构体存储注册的 mem_handler struct memory_handler_struct { struct memory_handler_struct *next; //下一个结点的 pointer, 用来构成链表 unsigned long begin; //注册的内存段开始地址 unsigned long end; //注册的内存段结束地址 memory_handler_t read_handler; //读函数 memory_handler_t write_handler; //写函数 void *read_param; void *write_param; }; (2) Memory 类的定义 class BOCHSAPI BX_MEM_C : public logfunctions 成员变量 struct memory_handler_struct **memory_handlers; //执行 mem handler 链表的头指针 bx_bool rom_present[65]; //最多可以装载 65 个 ROM, 是否存在标志 Bit8u *actual_vector; //初始化时分配总内存(SDRAM+ROM+其他)的指针 Bit8u *vector; // aligned correctly 实际指向系统内存(SDRAM)的指针 size_t len; size_t megabytes; // (len in Megabytes) RAM 大小, M 为单位 Bit8u *rom; // 256k rom space Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 108 Bit8u *bogus; // 4k for unexisting memory 成员函数 BX_MEM_SMF void alloc_vector_aligned (size_t bytes, size_t alignment); //用 new 分配内存 BX_MEM_SMF void init_memory(int memsize); BX_MEM_SMF void readPhysicalPage(BX_CPU_C *cpu, Bit32u addr, unsigned len, void *data) BX_CPP_AttrRegparmN(3); BX_MEM_SMF void writePhysicalPage(BX_CPU_C *cpu, Bit32u addr, unsigned len, void *data) BX_CPP_AttrRegparmN(3); //以上两个函数用来访问物理内存, 工作是查询 mem handler,调用已注册 handler 函数处理 read/write 请求 //主要在 paging.cpp 和 proc_control.cpp 以及 DMA 中被调用 BX_MEM_SMF void load_ROM(const char *path, Bit32u romaddress, Bit8u type); //加载如 BIOS, VGA BIOS 等 //从文件读取 BX_MEM_SMF Bit32u get_memory_in_k(void); BX_MEM_SMF Bit8u* getHostMemAddr(BX_CPU_C *cpu, Bit32u a20Addr, unsigned op); //做地址映射,映射到 RAM 或 //ROM 或其他 BX_MEM_SMF bx_bool registerMemoryHandlers(memory_handler_t read_handler, void *read_param, memory_handler_t write_handler, void *write_param, unsigned long begin_addr, unsigned long end_addr); BX_MEM_SMF bx_bool unregisterMemoryHandlers(memory_handler_t read_handler, memory_handler_t write_handler, unsigned long begin_addr, unsigned long end_addr); 5.3.2 相关函数分析 (1).内存初始化函数 BX_MEM_C::init_memory void BX_MEM_C::init_memory(int memsize) { int idx; BX_DEBUG(("Init $Id: misc_mem.cc,v 1.59.2.2 2005/07/07 16:59:29 vruppert Exp $")); // you can pass 0 if memory has been allocated already through // the constructor, or the desired size of memory if it hasn't // BX_INFO(("%.2fMB", (float)(BX_MEM_THIS megabytes) )); if (BX_MEM_THIS vector == NULL) { // memory not already allocated, do now... alloc_vector_aligned (memsize + (1 << 18) + 4096, BX_MEM_VECTOR_ALIGN); //总共分配的空间大小为 memsize+256K rom+4K bogus BX_MEM_THIS len = memsize; BX_MEM_THIS megabytes = memsize / (1024*1024); //M 为单位 BX_MEM_THIS memory_handlers = new struct memory_handler_struct *[1024 * 1024]; //1M 个 handler 指针 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 109 BX_MEM_THIS rom = &BX_MEM_THIS vector[memsize]; BX_MEM_THIS bogus = &BX_MEM_THIS vector[memsize + (1 << 18)]; memset(BX_MEM_THIS rom, 0xff, (1 << 18)); memset(BX_MEM_THIS bogus, 0xff, 4096); for (idx = 0; idx < 1024 * 1024; idx++) BX_MEM_THIS memory_handlers[idx] = NULL; //handler 指针数组初始化为 NULL for (idx = 0; idx < 65; idx++) BX_MEM_THIS rom_present[idx] = 0; //ROM 存在标志清零 BX_INFO(("%.2fMB", (float)(BX_MEM_THIS megabytes) )); } actual_vector vector rom bogus 32M 256K 4K #if BX_DEBUGGER if (megabytes > BX_MAX_DIRTY_PAGE_TABLE_MEGS) { BX_INFO(("Error: memory larger than dirty page table can handle")); BX_PANIC(("Error: increase BX_MAX_DIRTY_PAGE_TABLE_MEGS")); } #endif } (2).函数 BX_MEM_C:: RegisterMemoryHandlers 需要占用存储空间的设备用该函数注册一段内存范围 /* * One needs to provide both a read_handler and a write_handler. * XXX: maybe we should check for overlapping memory handlers */ bx_bool BX_MEM_C::registerMemoryHandlers( memory_handler_t read_handler, void *read_param, //读 handler 和参数 memory_handler_t write_handler, void *write_param, //写 handler 和参数 unsigned long begin_addr, unsigned long end_addr) //范围 { //检查参数有效性 if (end_addr < begin_addr) return false; if (!read_handler) return false; if (!write_handler) return false; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 110 for (unsigned page_idx = begin_addr >> 20; page_idx <= end_addr >> 20; page_idx++) { struct memory_handler_struct *memory_handler = new struct memory_handler_struct; memory_handler->next = memory_handlers[page_idx]; //作简单的单向链接 memory_handlers[page_idx] = memory_handler; //添加索引项 //索引表 memory_handlers 以 1MB 为单位,可以立即得到链表入口, 再进入链表进行查找 memory_handler->read_handler = read_handler; memory_handler->write_handler = write_handler; memory_handler->read_param = read_param; memory_handler->write_param = write_param; memory_handler->begin = begin_addr; memory_handler->end = end_addr; } return true; } (3).函数 BX_MEM_C:: getHostMemAddr 该函数完成 Host 地址(虚拟机运行的宿主平台)到对于设备物理地址的映射,主要用在以 下几处: (1) CPU 取指时调用(每 4KB 指令调用一次) (2) 某些 IA32 指令,如 OUTSW_DXXv 或字符串操作(FastRepMOVSB)等 (3) 页表查询 如果映射的地址不是合理上述空间(如 PCI 的 mem 空间等),函数返回 NULL,表示不能 在该空间进行操作 // Return a host address corresponding to the guest physical memory // address (with A20 already applied), given that the calling // code will perform an 'op' operation. This address will be // used for direct access to guest memory as an acceleration by // a few instructions, like REP {MOV, INS, OUTS, etc}. // Values of 'op' are { BX_READ, BX_WRITE, BX_RW }. // // The other assumption is that the calling code _only_ accesses memory // directly within the page that encompasses the address requested. // // 参数 a20Addr 已经经过 A20 地址转换,OP 为访问方式标记 Bit8u * BX_CPP_AttrRegparmN(3) BX_MEM_C::getHostMemAddr(BX_CPU_C *cpu, Bit32u a20Addr, unsigned op) { #if BX_SUPPORT_APIC bx_generic_apic_c *local_apic = &cpu->local_apic; if (local_apic->get_base () == (a20Addr & ~0xfff)) return(NULL); // Vetoed! APIC address space bx_generic_apic_c *ioapic = bx_devices.ioapic; if (ioapic->get_base () == (a20Addr & ~0xfff)) return(NULL); // Vetoed! IOAPIC address space #endif //进行 handler 链表检索,若该地址被注册过,返回 NULL struct memory_handler_struct *memory_handler = memory_handlers[a20Addr >> 20]; while (memory_handler) { if (memory_handler->begin <= a20Addr && Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 111 memory_handler->end >= a20Addr) { //地址是否命中 return(NULL); // Vetoed! memory handler for vram, mmio and PCI PnP } memory_handler = memory_handler->next; } if (op == BX_READ) { //若为 READ 操作 if ( (a20Addr & 0xfffe0000) == 0x000a0000 ) return(NULL); // Vetoed! Mem mapped IO (VGA) 无效的空间 #if BX_SUPPORT_PCI else if ( bx_options.Oi440FXSupport->get () && ((a20Addr & 0xfffc0000) == 0x000c0000) ) // C0000-FFFFF { switch (DEV_pci_rd_memtype (a20Addr)) { //读取440FX的存储访问PAM(PROGRAMMABLE ATTRIBUTE MAP REGISTERS)寄 存 器 //参阅440FX芯片的datasheet Page 30 case 0x0: // Read from ROM return( (Bit8u *) & rom[a20Addr - 0xc0000]); //映射到 ROM case 0x1: // Read from ShadowRAM return( (Bit8u *) & vector[a20Addr]); //映射到 shadow RAM default: BX_PANIC(("getHostMemAddr(): default case")); return(0); } } #endif else if (a20Addr >= 0xfffe0000) //BIOS ROM 区域,系统上电后就是从这里取第一条指令 { return( (Bit8u *) & rom[a20Addr & 0x3ffff]); //bochs BIOS ROM 大小为 64K, 从 0xFFFF0000 开始 //在 rom[]中的位置是和顶端对齐 } else if (a20Addr < BX_MEM_THIS len) { if ( (a20Addr & 0xfffc0000) == 0x000c0000 ) { return( (Bit8u *) & rom[a20Addr - 0xc0000]); //c0000-cFFFF, 在不支持 PCI 的情况下,映射到 ROM } else { return( (Bit8u *) & vector[a20Addr]); //缺省的内存范围, 从 SDRAM 读取 } } else { // Error, requested addr is out of bounds. return( (Bit8u *) & bogus[a20Addr & 0x0fff]); } } else //讨论写操作的情况,与读类似 { // op == {BX_WRITE, BX_RW} Bit8u *retAddr; if ( a20Addr >= BX_MEM_THIS len ) return(NULL); // Error, requested addr is out of bounds. 越界 if ( (a20Addr & 0xfffe0000) == 0x000a0000 ) return(NULL); // Vetoed! Mem mapped IO (VGA) #if BX_SUPPORT_PCI else if ( bx_options.Oi440FXSupport->get () && Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 112 ((a20Addr & 0xfffc0000) == 0x000c0000) ) { switch (DEV_pci_wr_memtype (a20Addr)) { case 0x0: // Vetoed! ROMs return(NULL); case 0x1: // Write to ShadowRAM retAddr = (Bit8u *) & vector[a20Addr]; break; default: BX_PANIC(("getHostMemAddr(): default case")); return(0); } } #endif else { if ( (a20Addr & 0xfffc0000) != 0x000c0000 ) { retAddr = (Bit8u *) & vector[a20Addr]; } else { return(NULL); // Vetoed! ROMs } } #if BX_SUPPORT_ICACHE pageWriteStampTable.decWriteStamp(a20Addr); #endif return(retAddr); } } (4).函数 BX_MEM_C:: readPhysicalPage 访问物理内存时被调用, 该函数会将内存地址映射到对应的空间里 len:读取长度 data:数据缓冲区 void BX_CPP_AttrRegparmN(3) BX_MEM_C::readPhysicalPage(BX_CPU_C *cpu, Bit32u addr, unsigned len, void *data) { Bit8u *data_ptr; Bit32u a20addr; a20addr = A20ADDR(addr); //作 A20 MASK //以下对 memory handler 进行检索 struct memory_handler_struct *memory_handler = memory_handlers[a20addr >> 20]; while (memory_handler) { if (memory_handler->begin <= a20addr && memory_handler->end >= a20addr && memory_handler->read_handler(a20addr, len, data, memory_handler->read_param)) //如果命中, 调用已注册的 read/write 函数 return; memory_handler = memory_handler->next; } BX_INSTR_PHY_READ(cpu->which_cpu(), a20addr, len); #if BX_SUPPORT_APIC Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 113 bx_generic_apic_c *local_apic = &cpu->local_apic; bx_generic_apic_c *ioapic = bx_devices.ioapic; if (local_apic->is_selected (addr, len)) { local_apic->read (addr, data, len); return; } if (ioapic->is_selected (addr, len)) { ioapic->read (addr, data, len); return; } #endif //以下开始从 addr 的范围判断 if ( (a20addr + len) <= BX_MEM_THIS len ) { //地址没有越界 if ( (a20addr & 0xfff80000) != 0x00080000 ) { //地址不在 80000-FFFFF 范围内, 访问 vector if (len == 4) { //DWORD ReadHostDWordFromLittleEndian(&vector[a20addr], * (Bit32u*) data); return; } if (len == 2) { ReadHostWordFromLittleEndian(&vector[a20addr], * (Bit16u*) data); return; } if (len == 1) { * (Bit8u *) data = * ((Bit8u *) (&vector[a20addr])); return; } // len == 3 case can just fall thru to special cases handling } #ifdef BX_LITTLE_ENDIAN data_ptr = (Bit8u *) data; #else // BX_BIG_ENDIAN data_ptr = (Bit8u *) data + (len - 1); #endif //以下的读取方式是: 读取每个字节之前先判断地址范围, 直到计数器 len 为 1 read_one: if ( (a20addr & 0xfff80000) != 0x00080000 ) { // addr *not* in range 00080000 .. 000FFFFF *data_ptr = vector[a20addr]; inc_one: if (len == 1) return; len--; a20addr++; #ifdef BX_LITTLE_ENDIAN data_ptr++; #else // BX_BIG_ENDIAN data_ptr--; #endif goto read_one; } // addr in range 00080000 .. 000FFFFF //地址在 80000-FFFFF 范围内, 分情况讨论 地址落在 80000 - FFFFF,进行分段讨论: 1.80000-9ffff 常规内存 2.A0000-BFFFF 显示缓冲区 3.C0000-EFFFF 该地址范围可以通过对 440FX 芯片编程,使之具有只读/只写/读写 等属性,设计的目的是为了保护内存中影射的 BIOS 代码 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 114 4.F0000-FFFFF BIOS ROM 区 #if BX_SUPPORT_PCI if ( bx_options.Oi440FXSupport->get () && ((a20addr & 0xfffc0000) == 0x000c0000) ) //C0000-FFFFF 内 { switch (DEV_pci_rd_memtype (a20addr)) { //从 440FX 读取 PAM 寄存器 case 0x0: // Read from ROM *data_ptr = rom[a20addr - 0xc0000]; goto inc_one; case 0x1: // Read from ShadowRAM *data_ptr = vector[a20addr]; goto inc_one; default: BX_PANIC(("readPhysicalPage: default case")); } goto inc_one; } else #endif // #if BX_SUPPORT_PCI { if ( (a20addr & 0xfffc0000) == 0x000c0000 ) { *data_ptr = rom[a20addr - 0xc0000]; } else { *data_ptr = vector[a20addr]; } goto inc_one; } } else { // some or all of data is outside limits of physical memory 如果(起始地址+len)大于当前内存长度 #ifdef BX_LITTLE_ENDIAN //YES, IA-32 处理器都是小端对齐的 data_ptr = (Bit8u *) data; #else // BX_BIG_ENDIAN data_ptr = (Bit8u *) data + (len - 1); #endif for (unsigned i = 0; i < len; i++) { if (a20addr < BX_MEM_THIS len) //在 SDRAM 范围内 *data_ptr = vector[a20addr]; else if (a20addr >= 0xfffe0000) *data_ptr = rom[a20addr & 0x3ffff]; // BIOS ROM, 取 a20addr 的 256K 余数 else *data_ptr = 0xff; //返回无效值 FF addr++; a20addr = (addr); #ifdef BX_LITTLE_ENDIAN data_ptr++; #else // BX_BIG_ENDIAN data_ptr--; #endif } return; } } ====================================================================== 补充内容: 关于 A20 地址线 ====================================================================== Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 115 高端内存区 High Memory Area(缩写为 HMA),是扩展内存(Extended Memory)起始位置的 65,520 bytes(64 KB - 16bytes)。从技术上说,它是地址为 100000h~10FFEFh 的内存。它很特 别,因为它是扩展内存中唯一可以在 RealMode 下可以被直接访问的一部分。在正常的情况 下,CPU 根本无法在 RealMode 下直接访问扩展内存,如果要访问,则必须进入保护模式, 或者使用特殊的驱动程序。 请不要把它和上位内存区(Upper MemoryArea)混肴,它们是完全不同的区域。 由于早年奇怪的设计决策,或者因为当时的不寻常的环境背景,造成了今天 PC 机上存在许 多怪异的硬件和软件设计。HMA 或许是所有这些怪异的设计之中最奇怪的设计之一。尽管 前面我们一直在说 HMA 可以在 RealMode 下可以被直接访问,但事实上,它能够被访问与 否取决于一个被称做 A20 Gate 的信号是否被打开。如果 A20Gate 是被禁止的,HMA 则无 法被访问;只有 A20 Gate 是被打开的,HMA 才可以被访问。 HMA 的一个用途是,由于 DOS 是运行在 Real Mode 下的,所以当后来 640KB 常规内存开 始使用紧张的时候,DOS 就通过一个特殊的驱动程序 HIMEM.SYS 将自己放在 HMA 中, 以腾出更多的常规内存供应用程序使用。HIMEM.SYS 所做的就是将 A20Gate 打开。 由于 A20 Gate 的打开与否,决定的 HMA 是否可以在 RealMode 下被访问,所以 HMA 的另 外一个用途就是用来测试 A20 Gate 是否已经被打开。 其实,从我们前面的介绍中就已经明白,其实 HMA 仅仅在 Real Mode 下才有其特殊性,这 也是其被单独拿出来被叫做 HMA 的意义,在 Protected Mode 下,HMA 和其它的扩展内存 没有什么性质上的不同,所以在 Protected Mode 下谈论 HMA 是没有意义的。 5.3 模拟分页机制 5.3.1 分页 (Paging)概述 在保护模式下,IA32 体系结构允许直接映射到一片大的物理内存(如 4GB RAM)或 者使用间接映射(分页)到一段小的物理内存或磁盘空间。这种后来才发明的映射线形地址 技术也叫虚拟内存或页请求虚拟内存(demand-paged virtual memory). 当开启 paging 后,处理器将线形地址空间分为若干页(4K,2M 或 4M 长度),它们 可以分别映射到物理内存或磁盘中。当一个程序(或任务)需要使用某个逻辑地址描述的内 存单元时,处理器将逻辑地址转换成线形地址,再通过 paging 机制将线形地址转换成物理 地址。 如果包含该线形地址的页当前不在物理内存中,处理器会产生一个页错误(#PF)异 常。由异常处理程序通知操作系统或直接将页面从磁盘中加载到物理内存(或许会将现有内 存中的某个页存储到磁盘中)。页面加载到内存后,异常处理函数返回将会使引起异常的指 令重新执行。处理器线形地址映射到物理地址的信息保存在页目录和页表中,它们是存放在 内存中。Paging和segmentation不一样,因为它用的是固定大小的页,而段长度是不等的。 如果只使用segmentation,数据结构必须全部存放在物理内存中。而当分页开启后,数 据结构可以部分存放在物理内存部分放在磁盘中。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 116 为了减小地址转换时的总线访问周期,最常用的页目录和页表条目被缓存在处理的一个叫做 TLB的缓存中。TLB可以满足大多数地址翻译而不需要访问内存。只有当页表条目不在TLB 中时才会需要访问外部总线,这种情况一般是某个页面已经很长时间没有被访问了。 关于TLB详细,参考卷三 3.11., “Translation Lookaside Buffers (TLBs)” 5.3.2 页目录与页表 处理器将线形地址转换成物理地址的信息存在于以下4个数据结构中: • 页目录—每个条目为32位(4字节)大小,总共有1024个条目,占用4KB大小的一个页面。 • 页表Page table—每个条目为32位(4字节)大小,最多可以有1024个条目占用4KB大小的 页面。 (当使用2- MByte 或 4-MByte 页面大小时,不需要页表,从页目录就可以直接映 射到页面) • 页—4-KByte, 2-MByte, 或4-MByte平坦的地址空间. • 页目录指针表(Page-Directory-Pointer Table)—4个64bit条目的数组,每一个指向一个 页目录。该数据结构只有当进行物理地址扩展时才会使用。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 117 5.3.3 线形地址转换 1. 4KB 页面 先由线形地址的高 10 位索引页目录,从页目录查到对于页表,用线形地址的 12 位到 21 位 (共 10bit)查页表。最后通过页表中的页基地址(也叫物理页框)加上线形地址的低 12 位偏移量得到物理地址。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 118 2. 4MB 页面 直接使用线形地址的高 10 位,由页目录索引到对应的页。得到页基地址后加上线形地址的 22 位偏移量生成物理地址。 NOTE1. 使用PAE进行36-BIT 物理寻址 在Intel Pentium Pro处理器中开始引入的PAE分页机制实现了36bit物理地址访问。IA32是否 支持PAE可以从CPUID特性中获得。PAE是物理地址扩展的缩写,打开CR4中的PAE开关可 以使物理地址从32位扩展到36位。硬件上,处理器提供了额外的4根地址线来扩展寻址空间。 使用PAE功能,以下标志位需要设定: • CR0中的PG 标志(bit 31)—使能分页 • CR4中的PAE 标志(bit 5) -使能PAE Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 119 以下是PAE 32位寻址的原理图 NOTE2. 关于Global Page CR4寄存器Global (G) 标志位(bit 8) 从Pentium Pro处理器开始引入,置位表示这是一个全局页。 当一个页面被设为‘全局’属性且 CR4中的全局页使能(PGE)打开,当CR3被装载或任务切换时,页表或页目录条目不会在TLB 中失效。这个标志位的作用是为了防止频繁使用的页(如操作系统内核)被刷出TLB。只有软件 可以置位或清除该标志位。对于指向页表的页目录的条目,该标志位被忽略,‘全局’属性在具 体的页表条目中设置。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 120 5.3.4 实现分页机制的源码分析 Bochs 的分页功能在 paging.cpp 中实现 (1)相关数据结构 TLB 条目 typedef struct { bx_address lpf; // linear page frame 线形页框 Bit32u ppf; // physical page frame 物理页框 Bit32u accessBits; // Page Table Address for updating A & D bits bx_hostpageaddr_t hostPageAddr; } bx_TLB_entry; (2)主要函数注释 =================================================================== 3 个地址转换函数 translate_linear() 将线形地址转换成物理地址 dtranslate_linear() 数据空间地址转换,实际是调用了 translate_linear() itranslate_linear() 代码空间地址转换,实际是调用了 translate_linear() 2 个使用线形地址读写数据的函数 针对是否对编译器打开 paging 功能,定义了两个不同的用线性地址读写数据的函数 Void BX_CPU_C::access_linear(Bit32u laddr, unsigned length, unsigned pl, unsigned rw, void *data); 若不支持 paging ,即线形地址将会直接映射到物理地址空间,函数直接调用了 mem->readPhysicalPage()和 mem->writePhysicalPage()读写物理内存。 若支持 paging ,先调用 dtranslate_linear() 进行地址转换,然后调用 mem->readPhysicalPage()和 mem->writePhysicalPage()完成数据读写。 =============================================================================== 地址转换函数 translate_linear() =============================================================================== 参数说明 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 121 laddr 线形地址 pl 访问特权级 rw 读/写,取值为 BX_READ,BX_WRITE 或 BX_RW access_type 访问模式,取值 DATA_ACCESS 或 CODE_ACCESS 代码注释 Bit32u BX_CPP_AttrRegparmN(3) BX_CPU_C::translate_linear(bx_address laddr, unsigned pl, unsigned rw, unsigned access_type) { bx_address lpf; //逻辑页框 Bit32u accessBits, combined_access = 0, error_code = 0; unsigned priv_index; #if BX_USE_TLB Bit32u TLB_index; #endif InstrTLB_Increment(tlbLookups); //变量 tlbLookups++ InstrTLB_Stats(); //报告 TLB 统计数据 // note - we assume physical memory < 4gig so for brevity & speed, we'll use // 32 bit entries although cr3 is expanded to 64 bits. Bit32u ppf, poffset, paddress; //物理页框,偏移量和地址 bx_bool isWrite = (rw >= BX_WRITE); // write or r-m-w 是否‘写操作’ #if BX_SupportPAE //支持 PAE 功能 if (BX_CPU_THIS_PTR cr4.get_PAE()) // check PAE(36bit 寻址)使能 { bx_address pde, pdp; Bit32u pde_addr; Bit32u pdp_addr; lpf = laddr & BX_CONST64(0xfffffffffffff000); // linear page frame 线性页框 poffset = laddr & 0x00000fff; // physical offset 物理偏移量(LSB12bit) #if BX_USE_TLB TLB_index = BX_TLB_INDEX_OF(lpf); // TLB_index 取值 0-1023,这里使用哈希算法从 线形地址页框// 换算到 TLB 表索引值 // #define BX_TLB_INDEX_OF(lpf) (((lpf) & 0x003ff000) >> 12) bx_TLB_entry *tlbEntry = &BX_CPU_THIS_PTR TLB.entry[TLB_index]; // 获的 TLB entry 指针 if (tlbEntry->lpf == BX_TLB_LPF_VALUE(lpf)) //看是否 TLB 命中 { paddress = tlbEntry->ppf | poffset; //计算物理地址 accessBits = tlbEntry->accessBits; //获得访问属性 if (accessBits & (0x10 << ((isWrite<<1) | pl))) return(paddress); //TLB 访问成功,返回物理地址 // The current access does not have permission according to the info // in our TLB cache entry. Re-walk the page tables, in case there is // updated information in the memory image, and let the long path code // generate an exception if one is warranted. } #endif Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 122 //如果 TLB 没有命中 InstrTLB_Increment(tlbMisses); pdp_addr = BX_CPU_THIS_PTR cr3_masked | ((laddr & 0xc0000000) >> 27); //计算页目录指针 地址 BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, pdp_addr, sizeof(bx_address), &pdp); //访问物理内存,取目录指针 entry if ( !(pdp & 0x01) ) { goto page_fault_not_present; // PDP Entry NOT present } if ( !(pdp & 0x20) ) { pdp |= 0x20; BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, pdp_addr, sizeof(bx_address), &pdp); } // Get page dir entry //获得页目录 entry 的地址 pde_addr = (pdp & 0xfffff000) | ((laddr & 0x3fe00000) >> 18); BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, pde_addr, sizeof(bx_address), &pde); //访问内存,取得页目录 entry 的内容 if ( !(pde & 0x01) ) { //检测 bit0:页存在 goto page_fault_not_present; // Page Directory Entry NOT present 页或页表不存在 } #if BX_SUPPORT_4MEG_PAGES // (KPL) Weird. I would think the processor would consult CR.PSE? // if ((pde & 0x80) && (BX_CPU_THIS_PTR cr4.get_PSE())) {} if (pde & 0x80) { //(a). 4M 页情况 // 4M pages are enabled, and this is a 4Meg page. // Combined access is just access from the pde (no pte involved). combined_access = pde & 0x06; // U/S and R/W // Make up the physical page frame address. Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 123 ppf = (pde & 0xffe00000) | (laddr & 0x001ff000); #if BX_SupportGlobalPages if (BX_CPU_THIS_PTR cr4.get_PGE()) { // PGE==1 combined_access |= (pde & TLB_GlobalPage); // G } #endif priv_index = (BX_CPU_THIS_PTR cr0.wp<<4) | // bit 4 (pl<<3) | // bit 3 (combined_access & 0x06) | // bit 2,1 isWrite; // bit 0 if (!priv_check[priv_index]) goto page_fault_access; // Update PDE if A/D bits if needed. if ( ((pde & 0x20)==0) || (isWrite && ((pde&0x40)==0)) ) { pde |= (0x20 | (isWrite<<6)); // Update A and possibly D bits BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, pde_addr, sizeof(bx_address), &pde); } } else #endif { //(b).4K 页情况 bx_address pte; // Get page table entry Bit32u pte_addr = (pde & 0xfffff000) | ((laddr & 0x001ff000) >> 9); BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, pte_addr, sizeof(bx_address), &pte); //访问物理内存,获取页表 Entry if ( !(pte & 0x01) ) { goto page_fault_not_present; //页面不在内存 } combined_access = (pde & pte) & 0x06; // U/S and R/W // Make up the physical page frame address. ppf = pte & 0xfffff000; #if BX_SupportGlobalPages if (BX_CPU_THIS_PTR cr4.get_PGE()) { // PGE==1 combined_access |= (pte & TLB_GlobalPage); // G } #endif priv_index = (BX_CPU_THIS_PTR cr0.wp<<4) | // bit 4 (pl<<3) | // bit 3 (combined_access & 0x06) | // bit 2,1 isWrite; // bit 0 if (!priv_check[priv_index]) goto page_fault_access; // Update PDE A bit if needed. if ( (pde & 0x20)==0 ) { pde |= 0x20; // Update A bit. BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, pde_addr, sizeof(bx_address), &pde); } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 124 // Update PTE A/D bits if needed. if (((pte & 0x20)==0) || (isWrite && ((pte&0x40)==0))) { pte |= (0x20 | (isWrite<<6)); // Update A and possibly D bits BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, pte_addr, sizeof(bx_address), &pte); } } } else #endif // #if BX_SupportPAE //=若不支持 PAE= ,以下流程和支持 PAE 类似,只是少了取页目录指针环节 { // CR4.PAE==0 (and MSR.LMA==0) lpf = laddr & 0xfffff000; // linear page frame 线性页框 poffset = laddr & 0x00000fff; // physical offset 物理偏移量 #if BX_USE_TLB TLB_index = BX_TLB_INDEX_OF(lpf); bx_TLB_entry *tlbEntry = &BX_CPU_THIS_PTR TLB.entry[TLB_index]; if (tlbEntry->lpf == BX_TLB_LPF_VALUE(lpf)) { paddress = tlbEntry->ppf | poffset; accessBits = tlbEntry->accessBits; if (accessBits & (0x10 << ((isWrite<<1) | pl))) return(paddress); // The current access does not have permission according to the info // in our TLB cache entry. Re-walk the page tables, in case there is // updated information in the memory image, and let the long path code // generate an exception if one is warranted. } #endif InstrTLB_Increment(tlbMisses); Bit32u pde, pde_addr; // Get page dir entry pde_addr = BX_CPU_THIS_PTR cr3_masked | ((laddr & 0xffc00000) >> 20); BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, pde_addr, 4, &pde); if ( !(pde & 0x01) ) { goto page_fault_not_present; // Page Directory Entry NOT present } #if BX_SUPPORT_4MEG_PAGES if ((pde & 0x80) && (BX_CPU_THIS_PTR cr4.get_PSE())) // 4M 页 { // 4M pages are enabled, and this is a 4Meg page. // Note: when the PSE and PAE flags in CR4 are set, // the processor generates a PF if the reserved bits are not // set to 0. (We don't handle PAE yet, just a note for // the future). // Combined access is just access from the pde (no pte involved). combined_access = pde & 0x006; // {US,RW} // make up the physical frame number ppf = (pde & 0xFFC00000) | (laddr & 0x003FF000); #if BX_SupportGlobalPages Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 125 if (BX_CPU_THIS_PTR cr4.get_PGE()) { // PGE==1 combined_access |= pde & TLB_GlobalPage; // {G} } #endif priv_index = (BX_CPU_THIS_PTR cr0.wp<<4) | // bit 4 (pl<<3) | // bit 3 (combined_access & 0x06) | // bit 2,1 isWrite; // bit 0 if (!priv_check[priv_index]) goto page_fault_access; // Update PDE if A/D bits if needed. if (((pde & 0x20)==0) || (isWrite && ((pde&0x40)==0))) { pde |= (0x20 | (isWrite<<6)); // Update A and possibly D bits BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, pde_addr, 4, &pde); } } else // Else normal 4Kbyte page... 4K 页 #endif { Bit32u pte, pte_addr; #if (BX_CPU_LEVEL < 6) // update PDE if A bit was not set before if ( !(pde & 0x20) ) { pde |= 0x20; BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, pde_addr, 4, &pde); } #endif // Get page table entry pte_addr = (pde & 0xfffff000) | ((laddr & 0x003ff000) >> 10); BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, pte_addr, 4, &pte); if ( !(pte & 0x01) ) { goto page_fault_not_present; // Page Table Entry NOT present } // 386 and 486+ have different bahaviour for combining // privilege from PDE and PTE. #if BX_CPU_LEVEL == 3 combined_access = (pde | pte) & 0x04; // U/S combined_access |= (pde & pte) & 0x02; // R/W #else // 486+ combined_access = (pde & pte) & 0x06; // U/S and R/W #if BX_SupportGlobalPages if (BX_CPU_THIS_PTR cr4.get_PGE()) { combined_access |= (pte & TLB_GlobalPage); // G } #endif #endif // Make up the physical page frame address. 计算物理页框 ppf = pte & 0xfffff000; priv_index = #if BX_CPU_LEVEL >= 4 (BX_CPU_THIS_PTR cr0.wp<<4) | // bit 4 #endif (pl<<3) | // bit 3 (combined_access & 0x06) | // bit 2,1 isWrite; // bit 0 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 126 if (!priv_check[priv_index]) goto page_fault_access; #if (BX_CPU_LEVEL >= 6) // update PDE if A bit was not set before if ( !(pde & 0x20) ) { pde |= 0x20; BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, pde_addr, 4, &pde); } #endif // Update PTE if A/D bits if needed. if (((pte & 0x20)==0) || (isWrite && ((pte&0x40)==0))) { pte |= (0x20 | (isWrite<<6)); // Update A and possibly D bits BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, pte_addr, 4, &pte); } } } //PAE 分支在此处汇合,计算物理地址 // Calculate physical memory address and fill in TLB cache entry paddress = ppf | poffset; //物理页框+偏移量 #if BX_USE_TLB //刷新 TLB BX_CPU_THIS_PTR TLB.entry[TLB_index].lpf = BX_TLB_LPF_VALUE(lpf); BX_CPU_THIS_PTR TLB.entry[TLB_index].ppf = ppf; #endif // b3: Write User OK // b2: Write Sys OK // b1: Read User OK // b0: Read Sys OK if ( combined_access & 4 ) { // User // User priv; read from {user,sys} OK. accessBits = (TLB_ReadUserOK | TLB_ReadSysOK); if ( isWrite ) // Current operation is a write (Dirty bit updated) { if (combined_access & 2) { // R/W access from {user,sys} OK. accessBits |= (TLB_WriteUserOK | TLB_WriteSysOK); } else { accessBits |= TLB_WriteSysOK; // read only page, only {sys} write allowed } } } else { // System accessBits = TLB_ReadSysOK; // System priv; read from {sys} OK. if ( isWrite ) { // Current operation is a write (Dirty bit updated) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 127 accessBits |= TLB_WriteSysOK; // write from {sys} OK. } } #if BX_SupportGlobalPages accessBits |= combined_access & TLB_GlobalPage; // Global bit #endif #if BX_USE_TLB #if BX_SupportGuest2HostTLB // Attempt to get a host pointer to this physical page. Put that // pointer in the TLB cache. Note if the request is vetoed, NULL // will be returned, and it's OK to OR zero in anyways. BX_CPU_THIS_PTR TLB.entry[TLB_index].hostPageAddr = (bx_hostpageaddr_t) BX_CPU_THIS_PTR mem->getHostMemAddr(BX_CPU_THIS, A20ADDR(ppf), rw); if (BX_CPU_THIS_PTR TLB.entry[TLB_index].hostPageAddr) { // All access allowed also via direct pointer accessBits |= (accessBits & 0xf0) >> 4; } #endif BX_CPU_THIS_PTR TLB.entry[TLB_index].accessBits = accessBits; #endif return(paddress); // error checking order - page not present, reserved bits, protection #define ERROR_NOT_PRESENT 0x00 #define ERROR_PROTECTION 0x01 #define ERROR_RESERVED 0x08 #define ERROR_CODE_ACCESS 0x10 #if BX_SUPPORT_X86_64 // keep compiler happy page_fault_reserved: error_code |= ERROR_RESERVED; // RSVD = 1 #endif page_fault_access: error_code |= ERROR_PROTECTION; // P = 1 page_fault_not_present: error_code |= (pl << 2) | (isWrite << 1); //给出错误代码 BX_CPU_THIS_PTR cr2 = laddr; //将线形地址存入 CR2 // Invalidate TLB entry. #if BX_USE_TLB Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 128 BX_CPU_THIS_PTR TLB.entry[TLB_index].lpf = BX_INVALID_TLB_ENTRY; //TLB 失效 #endif exception(BX_PF_EXCEPTION, error_code, 0); //抛出处理器异常 return(0); // keep compiler happy } translate_linear()流程图 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 129 函数 access_linear(),编译时 Paging 功能关闭 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 130 void BX_CPU_C::access_linear(Bit32u laddr, unsigned length, unsigned pl, unsigned rw, void *data) { /* perhaps put this check before all code which calls this function, * so we don't have to here */ if (BX_CPU_THIS_PTR cr0.pg == 0) { //检查 CR0 的 PG 位 if (rw == BX_READ) BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, laddr, length, data); else BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, laddr, length, data); return; } BX_PANIC(("access_linear: paging not supported")); } =============================================================================== 函数 access_linear(),编译时 Paging 功能开启 =============================================================================== void BX_CPP_AttrRegparmN(3) BX_CPU_C::access_linear(bx_address laddr, unsigned length, unsigned pl, unsigned rw, void *data) { Bit32u pageOffset = laddr & 0x00000fff; unsigned xlate_rw = rw; if (rw==BX_RW) rw = BX_READ; if (BX_CPU_THIS_PTR cr0.pg) { //检查 paging 是否使能 /* check for reference across multiple pages */ if ( (pageOffset + length) <= 4096 ) { //所有的数据在一页内 // Access within single page. BX_CPU_THIS_PTR address_xlation.paddress1 =dtranslate_linear(laddr, pl, xlate_rw);//地址 转换 BX_CPU_THIS_PTR address_xlation.pages = 1; if (rw == BX_READ) { //读数据 BX_INSTR_LIN_READ(BX_CPU_ID, laddr, BX_CPU_THIS_PTR address_xlation.paddress1, length); BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress1, length, data); } else { //写数据 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 131 BX_INSTR_LIN_WRITE(BX_CPU_ID, laddr, BX_CPU_THIS_PTR address_xlation.paddress1, length); BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress1, length, data); } return; } else { // access across 2 pages 如果是跨页访问 BX_CPU_THIS_PTR address_xlation.paddress1 = dtranslate_linear(laddr, pl, xlate_rw); BX_CPU_THIS_PTR address_xlation.len1 = 4096 - pageOffset; BX_CPU_THIS_PTR address_xlation.len2 = length - BX_CPU_THIS_PTR address_xlation.len1; BX_CPU_THIS_PTR address_xlation.pages = 2; BX_CPU_THIS_PTR address_xlation.paddress2 = dtranslate_linear(laddr + BX_CPU_THIS_PTR address_xlation.len1, pl, xlate_rw); if (rw == BX_READ) { //读数据 BX_INSTR_LIN_READ(BX_CPU_ID, laddr, BX_CPU_THIS_PTR address_xlation.paddress1, BX_CPU_THIS_PTR address_xlation.len1); BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress1, BX_CPU_THIS_PTR address_xlation.len1, data); BX_INSTR_LIN_READ(BX_CPU_ID, laddr + BX_CPU_THIS_PTR address_xlation.len1, BX_CPU_THIS_PTR address_xlation.paddress2, BX_CPU_THIS_PTR address_xlation.len2); BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress2, BX_CPU_THIS_PTR address_xlation.len2, ((Bit8u*)data) + BX_CPU_THIS_PTR address_xlation.len1); } else { //写数据 BX_INSTR_LIN_WRITE(BX_CPU_ID, laddr, BX_CPU_THIS_PTR address_xlation.paddress1, BX_CPU_THIS_PTR address_xlation.len1); BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress1, BX_CPU_THIS_PTR address_xlation.len1, data); BX_INSTR_LIN_WRITE(BX_CPU_ID, laddr + BX_CPU_THIS_PTR address_xlation.len1, BX_CPU_THIS_PTR address_xlation.paddress2, BX_CPU_THIS_PTR address_xlation.len2); BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress2, BX_CPU_THIS_PTR address_xlation.len2, ((Bit8u*)data) + BX_CPU_THIS_PTR address_xlation.len1); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 132 } return; } } else { // Paging off. 分页关闭 if ( (pageOffset + length) <= 4096 ) { // Access within single page. BX_CPU_THIS_PTR address_xlation.paddress1 = laddr; BX_CPU_THIS_PTR address_xlation.pages = 1; if (rw == BX_READ) { BX_INSTR_LIN_READ(BX_CPU_ID, laddr, laddr, length); #if BX_SupportGuest2HostTLB Bit32u tlbIndex = BX_TLB_INDEX_OF(laddr); bx_TLB_entry *tlbEntry = &BX_CPU_THIS_PTR TLB.entry[tlbIndex]; Bit32u lpf = laddr & 0xfffff000; if (tlbEntry->lpf == BX_TLB_LPF_VALUE(lpf)) { BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, laddr, length, data); return; } // We haven't seen this page, or it's been bumped before. tlbEntry->lpf = BX_TLB_LPF_VALUE(lpf); tlbEntry->ppf = lpf; // Request a direct write pointer so we can do either R or W. tlbEntry->hostPageAddr = (bx_hostpageaddr_t) BX_CPU_THIS_PTR mem->getHostMemAddr(BX_CPU_THIS, A20ADDR(lpf), BX_WRITE); if (! tlbEntry->hostPageAddr) { // Direct write vetoed. Try requesting only direct reads. tlbEntry->hostPageAddr = (bx_hostpageaddr_t) BX_CPU_THIS_PTR mem->getHostMemAddr(BX_CPU_THIS, A20ADDR(lpf), BX_READ); if (tlbEntry->hostPageAddr) { // Got direct read pointer OK. tlbEntry->accessBits = (TLB_ReadSysOK | TLB_ReadUserOK | TLB_ReadSysPtrOK | TLB_ReadUserPtrOK); } else tlbEntry->accessBits = 0; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 133 } else { // Got direct write pointer OK. Mark for any operation to succeed. tlbEntry->accessBits =(TLB_ReadSysOK | TLB_ReadUserOK | TLB_WriteSysOK | TLB_WriteUserOK | TLB_ReadSysPtrOK | TLB_ReadUserPtrOK | TLB_WriteSysPtrOK | TLB_WriteUserPtrOK); } #endif // BX_SupportGuest2HostTLB // Let access fall through to the following for this iteration. BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, laddr, length, data); } else { // Write BX_INSTR_LIN_WRITE(BX_CPU_ID, laddr, laddr, length); #if BX_SupportGuest2HostTLB Bit32u tlbIndex = BX_TLB_INDEX_OF(laddr); bx_TLB_entry *tlbEntry = &BX_CPU_THIS_PTR TLB.entry[tlbIndex]; Bit32u lpf = laddr & 0xfffff000; if (tlbEntry->lpf == BX_TLB_LPF_VALUE(lpf)) { BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, laddr, length, data); return; } // We haven't seen this page, or it's been bumped before. tlbEntry->lpf = BX_TLB_LPF_VALUE(lpf); tlbEntry->ppf = lpf; // TLB.entry[tlbIndex].ppf field not used for PG==0. // Request a direct write pointer so we can do either R or W. tlbEntry->hostPageAddr = (bx_hostpageaddr_t) BX_CPU_THIS_PTR mem->getHostMemAddr(BX_CPU_THIS, A20ADDR(lpf), BX_WRITE); if (tlbEntry->hostPageAddr) { // Got direct write pointer OK. Mark for any operation to succeed. tlbEntry->accessBits = (TLB_ReadSysOK | TLB_ReadUserOK | TLB_WriteSysOK | TLB_WriteUserOK | TLB_ReadSysPtrOK | TLB_ReadUserPtrOK | TLB_WriteSysPtrOK | TLB_WriteUserPtrOK); } else tlbEntry->accessBits = 0; #endif // BX_SupportGuest2HostTLB Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 134 BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, laddr, length, data); } } else { // Access spans two pages. 分两页进行访问 BX_CPU_THIS_PTR address_xlation.paddress1 = laddr; BX_CPU_THIS_PTR address_xlation.len1 = 4096 - pageOffset; BX_CPU_THIS_PTR address_xlation.len2 = length - BX_CPU_THIS_PTR address_xlation.len1; BX_CPU_THIS_PTR address_xlation.pages = 2; BX_CPU_THIS_PTR address_xlation.paddress2 = laddr + BX_CPU_THIS_PTR address_xlation.len1; if (rw == BX_READ) { BX_INSTR_LIN_READ(BX_CPU_ID, laddr, BX_CPU_THIS_PTR address_xlation.paddress1, BX_CPU_THIS_PTR address_xlation.len1); BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress1, BX_CPU_THIS_PTR address_xlation.len1, data); BX_INSTR_LIN_READ(BX_CPU_ID, laddr + BX_CPU_THIS_PTR address_xlation.len1, BX_CPU_THIS_PTR address_xlation.paddress2, BX_CPU_THIS_PTR address_xlation.len2); BX_CPU_THIS_PTR mem->readPhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress2, BX_CPU_THIS_PTR address_xlation.len2, ((Bit8u*)data) + BX_CPU_THIS_PTR address_xlation.len1); } else { BX_INSTR_LIN_WRITE(BX_CPU_ID, laddr, BX_CPU_THIS_PTR address_xlation.paddress1, BX_CPU_THIS_PTR address_xlation.len1); BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress1, BX_CPU_THIS_PTR address_xlation.len1, data); BX_INSTR_LIN_WRITE(BX_CPU_ID, laddr + BX_CPU_THIS_PTR address_xlation.len1, BX_CPU_THIS_PTR address_xlation.paddress2, BX_CPU_THIS_PTR address_xlation.len2); BX_CPU_THIS_PTR mem->writePhysicalPage(BX_CPU_THIS, BX_CPU_THIS_PTR address_xlation.paddress2, BX_CPU_THIS_PTR address_xlation.len2, ((Bit8u*)data) + BX_CPU_THIS_PTR address_xlation.len1); } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 135 } } } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 136 Chapter 6 系统板与外设模拟 6.1 Bochs 系统板描述类 bx_pc_system_c 6.1.1 类 bx_pc_system_c 功能概述 从 PC 硬件结构去看, CPU,内存以及外设是通过系统板(或称主板)进行信息交互的, 这些 信息包括两类 (1) IO 或存储器访问地址和数据 (2) 中断, DMA 请求等事件信号 bochs 中的 PC system 完成了第二条功能, 但实际上如果从虚拟机软件角度出发,PC system 则是完成了重要的时钟脉冲功能。 我们知道,PC 中所有设备的运行是靠主板上的时钟电路提供的脉冲进行工作的,包括 CPU 时钟, 存储器时钟,PCI 时钟等等。Bochs 定义了 64 个定时器(相当于 64 个时钟源), 正是依靠这些定时器的动作,使整个系统运转起来。 PC system 类完成的功能概括如下 (1) 定时器管理: bochs 预定义了 64 个定时器, 用在不同的地方,如 cmos、 keyboard、vga 等设备 上。具体见附录。 这些定时器会在两个全局函数 tick1 和 tickn 中计数,它们被 CpuLOOP 调用 (2)事件信号的置位与复位 这里指事件包括 DMA 请求 HRQ(由 DMA 控制器调用),中 断 请 求 INTR(由外设调 用) (3)其他 如 Reset, A20 的开启与关闭等 6.1.2 PC system 定时器管理 定时器管理是类 bx_pc_system_c 最主要的工作,通过定时器来触发整个 PC 系统中的事件。 需要由时间来触发的事件(函数)必须先通过调用 register_timer_ticks()或 register_timer ()进行注册。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 137 bx_pc_system_c 最多允许注册 BX_MAX_TIMERS=64 个定时器,定时器结构体如下 struct { bx_bool inUse; // Timer slot is in-use (currently registered). Bit64u period; // Timer periodocity in cpu ticks. Bit64u timeToFire; // Time to fire next (in absolute ticks). bx_bool active; // 0=inactive, 1=active. bx_bool continuous; // 0=one-shot timer, 1=continuous periodicity. bx_timer_handler_t funct; // A callback function for when the // timer fires. void *this_ptr; // The this-> pointer for C++ callbacks // has to be stored as well. #define BxMaxTimerIDLen 32 char id[BxMaxTimerIDLen]; // String ID of timer. } timer[BX_MAX_TIMERS]; 成员的含义: bx_bool inUse; // 是否已被注册 Bit64u period; // 触发周期, 以 CPU 的指令周期(tick)为单位 Bit64u timeToFire; // 下一次触发时刻,参考为虚拟机运行以来的的总 ticks bx_bool active; // 0=当前处于非活动, 1=当前活动 bx_bool continuous; // 0=只作用一次(后进入休眠), 1=连续作用(自动装载初值) bx_timer_handler_t funct; // 定时器触发的函数指针 char id[BxMaxTimerIDLen]; // 一个以来标识的字符串, 长度不超过 32 bx_pc_system_c 通过 register_timer(间接调用 register_timer_ticks)在 timer[]中注册。 register_timer_ticks 会在 timer[]中查找空余的 timer[i],将属性和 handler 函数装入 timer[i] PC_system.h 中定义了两个全局静态函数, tick1 和 tickn,它们在 CPU 循环中别调用。从 这里可以看出,bochs 中的时钟节奏是以 CPU 的指令周期(tick)为基础单位的。 看 tick1 的函数代码 static BX_CPP_INLINE void tick1(void) { if (--bx_pc_system.currCountdown == 0) { bx_pc_system.countdownEvent(); } } bx_pc_system.currCountdown 是整个 PC 系统的一个递减计数器,当它递减到 0 时,调用 函数 bx_pc_system.countdownEvent()处理事件,这个函数中将会检查 timer[]中所有注册的 定时器,看是否有符合要求可以触发的。若存在,则执行 timer[i]->funct(),当然还要完成一 些其它工作,如属性 continuous 是 1 的定时器(可重复触发)要重装初值。 CurrCountdown 需要重装初值,初值的大小是这样得到的: Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 138 检查所有已经注册的 timer,找出下一次最先触发的 timer,看它还需要等待多长时间,把这 个时间赋给 currCountdown。 这样的好处是不需要每个 tick’都让 bx_pc_system.countdownEvent 执行一遍,尽管这样, 也不会让任何 timer 触发的时机错过。具体算法可以参考源代码。 6.1.3 类 bx_pc_system_c 源码分析与注释 类 bx_pc_system_c 从 logfunctions 派生出 成员变量 timer[64]; 64 个定时器 timer 的结构体定义 struct { bx_bool inUse; // Timer slot is in-use (currently registered). Bit64u period; // Timer periodocity in cpu ticks. 定时器周期(指令周期为单位) Bit64u timeToFire; // Time to fire next (in absolute ticks). bx_bool active; // 0=inactive, 1=active. bx_bool continuous; // 0=one-shot timer, 1=continuous periodicity. bx_timer_handler_t funct; // A callback function for when the // timer fires. void *this_ptr; // The this-> pointer for C++ callbacks 回调函数 // has to be stored as well. #define BxMaxTimerIDLen 32 char id[BxMaxTimerIDLen]; // String ID of timer.定时器名称 } imer[BX_MAX_TIMERS]; unsigned numTimers; //已分配的定时器数目 unsigned triggeredTimer; // ID of the actually triggered timer. //触发 Bit32u currCountdown; // Current countdown ticks value (decrements to 0). //在 currCountdownPeriod 基础上计数, 计到 0 时触发下一个事件 Bit32u currCountdownPeriod; // Length of current countdown period. //当前事件到下一个事件发生应该经过的周期数 Bit64u ticksTotal; //启动 bochs 模拟器以来的总 ticks Bit64u lastTimeUsec; // Last sequentially read time in usec. Bit64u usecSinceLast; // Number of useconds claimed since then. static const Bit64u NullTimerInterval; double m_ips; // Millions of Instructions Per Second //模拟器指令速度 bx_bool HRQ; // Hold 请求,用于 DMA 传输 //A20 地址线相关的两个变量 bx_bool enable_a20; Bit32u a20_mask; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 139 成员函数 bx_pc_system_c::bx_pc_system_c(void) //构造函数 void bx_pc_system_c::init_ips(Bit32u ips) void bx_pc_system_c::set_HRQ(bx_bool val) //设置 DMA 请求标志 HRQ void bx_pc_system_c::set_INTR(bx_bool value) // void bx_pc_system_c::inp(Bit16u addr, unsigned io_len) //端口操作,调用 device 类的对应函数 void bx_pc_system_c::outp(Bit16u addr, Bit32u value, unsigned io_len) //端口操作,调用 device 类的对应 函数 void bx_pc_system_c::set_enable_a20(Bit8u value) //修改 A20 mask bx_bool bx_pc_system_c::get_enable_a20(void) int bx_pc_system_c::Reset( unsigned type ) Bit8u bx_pc_system_c::IAC(void) void bx_pc_system_c::exit(void) int bx_pc_system_c::register_timer( void *this_ptr, void (*funct)(void *), int bx_pc_system_c::register_timer_ticks(void* this_ptr, bx_timer_handler_t funct, void bx_pc_system_c::countdownEvent(void) void bx_pc_system_c::nullTimer(void* this_ptr) Bit64u bx_pc_system_c::time_usec_sequential() Bit64u bx_pc_system_c::time_usec() { void bx_pc_system_c::start_timers(void) { } void bx_pc_system_c::activate_timer_ticks(unsigned i, Bit64u ticks, bx_bool continuous) void bx_pc_system_c::activate_timer(unsigned i, Bit32u useconds, bx_bool continuous) void bx_pc_system_c::deactivate_timer( unsigned i ) unsigned bx_pc_system_c::unregisterTimer(int timerIndex) //两个静态函数 static BX_CPP_INLINE void tick1(void) ; //在 BX_CPU_C::handleAsyncEvent(void)中执行 static BX_CPP_INLINE void tickn(Bit64u n); //在多处执行 成员函数分析 „ 该函数置位 HRQ 信号,发出 DMA 请求 void bx_pc_system_c::set_HRQ(bx_bool val) { HRQ = val; // 置 HRQ 标志位,CPU LOOP 中的函数 handleAsyncEvent()会检查该 标志位以决定是否启用 DMA if (val) BX_CPU(0)->async_event = 1; //设置 CPU 异步事件标志位 } void bx_pc_system_c::init_ips(Bit32u ips) { HRQ = 0; enable_a20 = 1; //set_INTR (0); #if BX_CPU_LEVEL < 2 a20_mask = 0xfffff; #elif BX_CPU_LEVEL == 2 a20_mask = 0xffffff; #else /* 386+ */ a20_mask = 0xffffffff; #endif Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 140 // parameter 'ips' is the processor speed in Instructions-Per-Second m_ips = double(ips) / 1000000.0L; //似乎没有什么用 BX_DEBUG(("ips = %u", (unsigned) ips)); } „ 该函数完成虚拟机复位,分硬件和软件,硬件复位相当于 PC 面板的 reset 键。 int bx_pc_system_c::Reset( unsigned type ) //分为 software 和 hardware 两种 { // type is BX_RESET_HARDWARE or BX_RESET_SOFTWARE BX_INFO(( "bx_pc_system_c::Reset(%s) called",type==BX_RESET_HARDWARE?"HARDWARE":"SOFTWARE" )); // Always reset cpu 总是 reset CPU for (int i=0; ireset(type); } // Reset devices only on Hardware resets 只有冷启动的时候才 reset 硬件 if (type==BX_RESET_HARDWARE) { DEV_reset_devices(type); } return(0); } „ 该函数在 bochs 中未被调用 Bit8u bx_pc_system_c::IAC(void) { return( DEV_pic_iac() ); } „ 被 int bx_atexit(void)调用,做退出前工作 void bx_pc_system_c::exit(void) { if (DEV_hd_present()) DEV_hd_close_harddrive(); //关闭硬盘驱动器 BX_INFO(("Last time is %u", (unsigned) DEV_cmos_get_timeval())); if (bx_gui) bx_gui->exit(); //退出 GUI 系统 } „ 注册定时器,间接调用 register_timer_ticks() int bx_pc_system_c::register_timer( void *this_ptr, void (*funct)(void *), Bit32u useconds, bx_bool continuous, bx_bool active, const char *id) //间隔:微秒为单位 { Bit64u ticks; // Convert useconds to number of ticks. ticks = (Bit64u) (double(useconds) * m_ips); //将微秒为单位的参数换算成指令单位 return register_timer_ticks(this_ptr, funct, ticks, continuous, active, id); // 间接调用 register_timer_ticks } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 141 int bx_pc_system_c::register_timer_ticks(void* this_ptr, bx_timer_handler_t funct, Bit64u ticks, bx_bool continuous, bx_bool active, const char *id) { unsigned i; // If the timer frequency is rediculously low, make it more sane. 检查定时器间隔是否合理 // This happens when 'ips' is too low. if (ticks < MinAllowableTimerPeriod) { //BX_INFO(("register_timer_ticks: adjusting ticks of %llu to min of %u", // ticks, MinAllowableTimerPeriod)); ticks = MinAllowableTimerPeriod; } for (i=0; i < numTimers; i++) { //定时器数组里面寻找一个空的单元 if (timer[i].inUse == 0) break; } //以下进行初始化 timer[i].inUse = 1; timer[i].period = ticks; timer[i].timeToFire = (ticksTotal + Bit64u(currCountdownPeriod-currCountdown)) + ticks; //下次触发时刻 timer[i].active = active; timer[i].continuous = continuous; timer[i].funct = funct; // 被触发的函数 timer[i].this_ptr = this_ptr; strncpy(timer[i].id, id, BxMaxTimerIDLen); timer[i].id[BxMaxTimerIDLen-1] = 0; // Null terminate if not already. if (active) { if (ticks < Bit64u(currCountdown)) { // This new timer needs to fire before the current countdown. // Skew the current countdown and countdown period to be smaller // by the delta. currCountdownPeriod -= (currCountdown - Bit32u(ticks)); currCountdown = Bit32u(ticks); } } // If we didn't find a free slot, increment the bound, numTimers. if (i==numTimers) numTimers++; // One new timer installed. // Return timer id. return(i); } „ 函数 countdownEvent()处理计数时发生的事件(即调用注册了 timer 的事件), 在 tick1()和 tickn()中被调用 void bx_pc_system_c::countdownEvent(void) { unsigned i; Bit64u minTimeToFire; bx_bool triggered[BX_MAX_TIMERS]; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 142 // The countdown decremented to 0. We need to service all the active // timers, and invoke callbacks from those timers which have fired. #if BX_TIMER_DEBUG if (currCountdown != 0) BX_PANIC(("countdownEvent: ticks!=0")); #endif // Increment global ticks counter by number of ticks which have // elapsed since the last update. ticksTotal += Bit64u(currCountdownPeriod); //计算总 ticks minTimeToFire = (Bit64u) -1; for (i=0; i < numTimers; i++) { //做这个循环的目的是求出所有的 Timer 中下一次触发的最小周期 triggered[i] = 0; // Reset triggered flag. if (timer[i].active) { #if BX_TIMER_DEBUG if (ticksTotal > timer[i].timeToFire) BX_PANIC(("countdownEvent: ticksTotal > timeToFire[%u], D " FMT_LL "u", i, timer[i].timeToFire-ticksTotal)); #endif if (ticksTotal == timer[i].timeToFire) { //时间到, 触发 timer[I]中的事件 // This timer is ready to fire. triggered[i] = 1; //置触发标志 if (timer[i].continuous==0) { // If triggered timer is one-shot, deactive. 如果是单次触发类型, deactive 定时器 timer[i].active = 0; } else { 如果是多次触发类型, 重置 timeToFire 变量 // Continuous timer, increment time-to-fire by period. timer[i].timeToFire += timer[i].period; if (timer[i].timeToFire < minTimeToFire) minTimeToFire = timer[i].timeToFire; } } else { // This timer is not ready to fire yet. 时间还没有到 if (timer[i].timeToFire < minTimeToFire) minTimeToFire = timer[i].timeToFire; } } } // Calculate next countdown period. We need to do this before calling // any of the callbacks, as they may call timer features, which need // to be advanced to the next countdown cycle. currCountdown = currCountdownPeriod = Bit32u(minTimeToFire - ticksTotal); //得到下一次待触发的这段时间片, currCountdown 用来计数, //CurrCountdownPeriod 保持不动 for (i=0; i < numTimers; i++) { // Call requested timer function. It may request a different // timer period or deactivate etc. if (triggered[i]) { triggeredTimer = i; timer[i].funct(timer[i].this_ptr); //调用对应的触发事件处理函数 triggeredTimer = 0; } } } 函数 countdownEvent(void) 在 tick1()和 tickn()中被调用 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 143 static BX_CPP_INLINE void tick1(void) { if (--bx_pc_system.currCountdown == 0) { bx_pc_system.countdownEvent(); } } static BX_CPP_INLINE void tickn(Bit64u n) { while (n >= Bit64u(bx_pc_system.currCountdown)) { n -= Bit64u(bx_pc_system.currCountdown); bx_pc_system.currCountdown = 0; bx_pc_system.countdownEvent(); // bx_pc_system.currCountdown is adjusted to new value by countdownevent(). } // 'n' is not (or no longer) >= the countdown size. We can just decrement // the remaining requested ticks and continue. bx_pc_system.currCountdown -= Bit32u(n); } „ 该函数作为 Timer[0]中的默认 callback 函数 static void nullTimer(void* this_ptr); { …. #if BX_SUPPORT_ICACHE purgeICache(); //只是简单做清空 cache 的工作 #endif } Bit64u bx_pc_system_c::time_usec_sequential() // { Bit64u this_time_usec = time_usec(); //虚拟时间, 从总 tick 换算到微秒为单位 if(this_time_usec != lastTimeUsec) { Bit64u diff_usec = this_time_usec-lastTimeUsec; lastTimeUsec = this_time_usec; if(diff_usec >= usecSinceLast) { usecSinceLast = 0; } else { usecSinceLast -= diff_usec; } } usecSinceLast++; return (this_time_usec+usecSinceLast); } „ 有总指令周期(tick)换算到微秒时间单位 Bit64u bx_pc_system_c::time_usec() { return (Bit64u) (((double)(Bit64s)time_ticks()) / m_ips ); } „ 空函数 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 144 void bx_pc_system_c::start_timers(void) { } //空函数 „ 激活某个定时器,参数 i 为定时器的索引, 重设 ticks 和 continuous 标志 void bx_pc_system_c::activate_timer_ticks(unsigned i, Bit64u ticks, bx_bool continuous) { // If the timer frequency is rediculously low, make it more sane. // This happens when 'ips' is too low. if (ticks < MinAllowableTimerPeriod) { //BX_INFO(("activate_timer_ticks: adjusting ticks of %llu to min of %u", // ticks, MinAllowableTimerPeriod)); ticks = MinAllowableTimerPeriod; } timer[i].period = ticks; timer[i].timeToFire = (ticksTotal + Bit64u(currCountdownPeriod-currCountdown)) + ticks; timer[i].active = 1; timer[i].continuous = continuous; if (ticks < Bit64u(currCountdown)) { // This new timer needs to fire before the current countdown. // Skew the current countdown and countdown period to be smaller // by the delta. //若发现 currCountdown 还很长(大于 ticks), 将 ticks 赋值给 currCountdown, //同时修改 currCountdownPeriod currCountdownPeriod -= (currCountdown - Bit32u(ticks)); currCountdown = Bit32u(ticks); } } „ 同上面的函数, 只是把参数换成了 second 为单位,实际调用了 activate_timer_ticks () void bx_pc_system_c::activate_timer(unsigned i, Bit32u useconds, bx_bool continuous) { Bit64u ticks; // if useconds = 0, use default stored in period field // else set new period from useconds if (useconds==0) { ticks = timer[i].period; } else { // convert useconds to number of ticks ticks = (Bit64u) (double(useconds) * m_ips); // If the timer frequency is rediculously low, make it more sane. // This happens when 'ips' is too low. if (ticks < MinAllowableTimerPeriod) { //BX_INFO(("activate_timer: adjusting ticks of %llu to min of %u", // ticks, MinAllowableTimerPeriod)); ticks = MinAllowableTimerPeriod; } timer[i].period = ticks; } activate_timer_ticks(i, ticks, continuous); //间接调用 activate_timer_ticks } „ 将 timer[I]置为非活动状态,参数 i 为定时器索引值 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 145 void bx_pc_system_c::deactivate_timer( unsigned i ) { timer[i].active = 0; } „ 取消定时器注册 unsigned bx_pc_system_c::unregisterTimer(int timerIndex) { unsigned i = (unsigned) timerIndex; if (timer[i].active) { BX_PANIC(("unregisterTimer: timer '%s' is still active!", timer[i].id)); return(0); // Fail. } // Reset timer fields for good measure. timer[i].inUse = 0; // No longer registered. timer[i].period = BX_MAX_BIT64S; // Max value (invalid) timer[i].timeToFire = BX_MAX_BIT64S; // Max value (invalid) timer[i].continuous = 0; timer[i].funct = NULL; timer[i].this_ptr = NULL; memset(timer[i].id, 0, BxMaxTimerIDLen); return(1); // OK } pc_system 类的其它函数 static BX_CPP_INLINE void tick1(void) { if (--bx_pc_system.currCountdown == 0) { bx_pc_system.countdownEvent(); } } static BX_CPP_INLINE void tickn(Bit64u n) { while (n >= Bit64u(bx_pc_system.currCountdown)) { n -= Bit64u(bx_pc_system.currCountdown); bx_pc_system.currCountdown = 0; bx_pc_system.countdownEvent(); // bx_pc_system.currCountdown is adjusted to new value by countdownevent(). } // 'n' is not (or no longer) >= the countdown size. We can just decrement // the remaining requested ticks and continue. bx_pc_system.currCountdown -= Bit32u(n); } //tickn()函数在以下几个地方被调用: CPU 主循环中执行 BX_TICKN(quantum); // main.cpp 中, quantum=5; CPU\String.cpp 中执行 BX_TICKN(byteCount-1); 这里应该是做 repeat 串操作指令 CPU\IO 中有两个函数调用它: 分别执行两条 IA-32 指令 void BX_CPU_C::OUTSW_DXXv(bxInstruction_c *i) void BX_CPU_C::INSW_YvDX(bxInstruction_c *i) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 146 6.2 设备集合类 bx_devices_c 6.2.1 类 bx_devices_c 成员变量分析 代码位于 IODEV\iodev.h class BOCHSAPI bx_devices_c : public logfunctions 成员变量和函数 //设备对象 bx_devmodel_c *pluginBiosDevice; bx_devmodel_c *pluginPciVgaAdapter; bx_devmodel_c *pluginPciDevAdapter; bx_devmodel_c *pluginPciPNicAdapter; bx_devmodel_c *pluginParallelDevice; bx_devmodel_c *pluginUnmapped; bx_devmodel_c *pluginExtFpuIrq; bx_devmodel_c *pluginSB16Device; bx_devmodel_c *pluginGameport; //-------------pointers------------------------------------- bx_ioapic_c *ioapic; bx_pci_stub_c *pluginPciBridge; bx_pci2isa_stub_c *pluginPci2IsaBridge; bx_pci_ide_stub_c *pluginPciIdeController; bx_pit_c *pit; bx_keyb_stub_c *pluginKeyboard; bx_dma_stub_c *pluginDmaDevice; bx_floppy_stub_c *pluginFloppyDevice; bx_cmos_stub_c *pluginCmosDevice; bx_serial_stub_c *pluginSerialDevice; bx_vga_stub_c *pluginVgaDevice; bx_pic_stub_c *pluginPicDevice; bx_hard_drive_stub_c *pluginHardDrive; bx_ne2k_stub_c *pluginNE2kDevice; bx_g2h_c *g2h; bx_speaker_stub_c *pluginSpeaker; //-------------设备实例 instances------------------------------------- bx_cmos_stub_c stubCmos; bx_keyb_stub_c stubKeyboard; bx_hard_drive_stub_c stubHardDrive; bx_dma_stub_c stubDma; bx_pic_stub_c stubPic; bx_floppy_stub_c stubFloppy; bx_vga_stub_c stubVga; bx_pci_stub_c stubPci; bx_pci2isa_stub_c stubPci2Isa; bx_pci_ide_stub_c stubPciIde; bx_ne2k_stub_c stubNE2k; bx_speaker_stub_c stubSpeaker; bx_serial_stub_c stubSerial; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 147 // Some info to pass to devices which can handled bulk IO. This allows // the interface to remain the same for IO devices which can't handle // bulk IO. We should probably implement special INPBulk() and OUTBulk() // functions which stick these values in the bx_devices_c class, and // then call the normal functions rather than having gross globals // variables. //用于批量传输的变量,如读写硬盘数据,提高传输效率 Bit8u* bulkIOHostAddr; unsigned bulkIOQuantumsRequested; unsigned bulkIOQuantumsTransferred; 关于 IO handler 的变量和函数 //定义了 io_handler_struct 结构,该结构可以构成一个双向链表, 方便查找某个 handle struct io_handler_struct { struct io_handler_struct *next; struct io_handler_struct *prev; void *funct; // C++ type checking is great, but annoying void *this_ptr; const char *handler_name; // name of device int usage_count; //用到的 IO 地址个数 Bit8u mask; // io_len mask }; //定义了两个 handler, 作为链表的起始节点 struct io_handler_struct io_read_handlers; struct io_handler_struct io_write_handlers; //指向 io handler 节点的指针数组, 在 init 中被初始化成 0x10000 大小 struct io_handler_struct **read_port_to_handler; struct io_handler_struct **write_port_to_handler; //IRQ0-15 的 handler const char *irq_handler_name[BX_MAX_IRQS]; // BX_MAX_IRQS=16 static Bit32u read_handler(void *this_ptr, Bit32u address, unsigned io_len);//调用 port92_read() static void write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len); //调用 port92_write() BX_DEV_SMF Bit32u port92_read(Bit32u address, unsigned io_len); BX_DEV_SMF void port92_write(Bit32u address, Bit32u value, unsigned io_len); static Bit32u default_read_handler(void *this_ptr, Bit32u address, unsigned io_len); static void default_write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len); //handler 注册和反注册 //a.注册一个端口 bx_bool register_io_read_handler(void *this_ptr, bx_read_handler_t f, Bit32u addr, const char *name, Bit8u mask ); bx_bool unregister_io_read_handler( void *this_ptr, bx_read_handler_t f, Bit32u addr, Bit8u mask ); bx_bool register_io_write_handler(void *this_ptr, bx_write_handler_t f, Bit32u addr, const char *name, Bit8u mask ); bx_bool unregister_io_write_handler( void *this_ptr, bx_write_handler_t f, Bit32u addr, Bit8u mask ); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 148 //b.注册端口范围 bx_bool register_io_read_handler_range( void *this_ptr, bx_read_handler_t f, Bit32u begin_addr, Bit32u end_addr, const char *name, Bit8u mask ); bx_bool register_io_write_handler_range( void *this_ptr, bx_write_handler_t f, Bit32u begin_addr, Bit32u end_addr, const char *name, Bit8u mask ); bx_bool unregister_io_read_handler_range( void *this_ptr, bx_read_handler_t f, Bit32u begin, Bit32u end, Bit8u mask ); bx_bool unregister_io_write_handler_range( void *this_ptr, bx_write_handler_t f, Bit32u begin, Bit32u end, Bit8u mask ); //c.缺省注册 bx_bool register_default_io_read_handler(void *this_ptr, bx_read_handler_t f, const char *name, Bit8u mask ); bx_bool register_default_io_write_handler(void *this_ptr, bx_write_handler_t f, const char *name, Bit8u mask ); 定时器 static void timer_handler(void *this_ptr); //处理定时器事件, 调用this_ptr指向的timer() 函数, //在类 bx_devices_c 的 init()函数中注册 timer_handler, // BX_IODEV_HANDLER_PERIOD=100 微秒 if (timer_handle != BX_NULL_TIMER_HANDLE) { timer_handle = bx_pc_system.register_timer( this, timer_handler, (unsigned) BX_IODEV_HANDLER_PERIOD, 1, 1, "devices.cc"); } void timer(void); //类 bx_device_c 的 handler int timer_handle; //猜想应该是记录一个 ID 号 其它 //构造和析构 bx_devices_c(void); ~bx_devices_c(void); //初始化 void iodev_init(void); void init(BX_MEM_C *); //复位 void reset(unsigned type); //内存空间指针 BX_MEM_C *mem; // address space associated with these devices //端口操作, 实际调用了 io_read_handler 和 io_write_handler Bit32u inp(Bit16u addr, unsigned io_len) BX_CPP_AttrRegparmN(2); void outp(Bit16u addr, Bit32u value, unsigned io_len) BX_CPP_AttrRegparmN(3); //查询设备使能状态 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 149 bx_bool is_serial_enabled (); bx_bool is_usb_enabled (); bx_bool is_parallel_enabled (); 6.2.2 类 bx_devices_c 成员函数分析与注释 //构造函数 bx_devices_c::bx_devices_c(void) { put("DEV"); settype(DEVLOG); read_port_to_handler = NULL; write_port_to_handler = NULL; #if BX_SUPPORT_PCI pluginPciBridge = &stubPci; pluginPci2IsaBridge = &stubPci2Isa; pluginPciIdeController = &stubPciIde; #if BX_SUPPORT_PCIVGA pluginPciVgaAdapter = NULL; #endif #if BX_SUPPORT_PCIDEV pluginPciDevAdapter = NULL; #endif #if BX_SUPPORT_PCIUSB pluginPciUSBAdapter = &stubUsbAdapter; #endif #if BX_SUPPORT_PCIPNIC pluginPciPNicAdapter = NULL; #endif #endif pit = NULL; pluginKeyboard = &stubKeyboard; #if BX_SUPPORT_BUSMOUSE pluginBusMouse = &stubBusMouse; #endif pluginDmaDevice = &stubDma; pluginFloppyDevice = &stubFloppy; pluginCmosDevice = &stubCmos; pluginSerialDevice = &stubSerial; pluginVgaDevice = &stubVga; pluginPicDevice = &stubPic; pluginHardDrive = &stubHardDrive; pluginNE2kDevice =&stubNE2k; pluginSpeaker = &stubSpeaker; g2h = NULL; pluginBiosDevice = NULL; pluginParallelDevice = NULL; pluginUnmapped = NULL; pluginExtFpuIrq = NULL; pluginGameport = NULL; pluginSB16Device = NULL; #if BX_SUPPORT_IODEBUG iodebug = NULL; #endif } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 150 //构造函数中的主要工作就是把指针指向 instance //析构函数 bx_devices_c::~bx_devices_c(void) { // nothing needed for now BX_DEBUG(("Exit.")); timer_handle = BX_NULL_TIMER_HANDLE; } //基本上没做什么工作 6.2.3 设备的初始化 在 init 函数中完成大部分设备的初始化工作。首先,init 函数初始化 I/O 读写 handler 链表 和 IRQ 链表,为设备注册做好准备工作。然后调用 PLUG_load_plugin 宏进行单个设备初 始化。 宏 PLUG_load_plugin 在 plugint.h 中定义,针对预定义变量 BX_PLUGINS 的取指 不同有两种不同的形式,该变量决定 bochs 是否采用插件的方式。 当 BX_PLUGINS=1 时, PLUG_load_plugin 定义为 #define PLUG_load_plugin(name,type) {bx_load_plugin(#name,type);} 实际是调用 bx_load_plugin 函数加载相关的插件,为 bochs 的插件扩展功能做准备。 Bochs 缺省的 BX_PLUGINS=1, 此时 PLUG_load_plugin 定义为 #define PLUG_load_plugin(name,type) \ {lib##name##_LTX_plugin_init(NULL,type,0,NULL);} 该宏被扩展成为特定设备的 init 函数,比如 name 为 pic ,则扩展成为 libpic_LTX_plugin_init(),这个函数在 iodev/pic.cpp 中定义: Int libpic_LTX_plugin_init(plugin_t *plugin, plugintype_t type, int argc, char *argv[]) { thePic = new bx_pic_c (); bx_devices.pluginPicDevice = thePic; BX_REGISTER_DEVICE_DEVMODEL(plugin, type, thePic, BX_PLUGIN_PIC); return(0); // Success } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 151 从源码可以看出,通过构造函数创建设备对象的实例,全局指针变量 thePic 指向这个实例。 同时将指针赋值给 pluginPicDevice ,供 bx_devices 类应用,另外,还调用了 BX_REGISTER_DEVICE_DEVMODEL 进行注册后返回。 所有设备加载完后,bx_devices 类已经得到了所有设备的全局变量指针,随后,调用各指 针的 init 函数逐一初始化设备: pluginXXXDevice->init(); XXX 为设备名称 init 函数完成的最后三项工作是: (1).注册了系统端口 92H 的读写 handler(端口 92H 用于 A20 使能) (2).初始化部分 CMOS 参数 (3).在 pc system 中注册一个定时器 ===================================start of code=============================== bx_devices_c::init(BX_MEM_C *newmem) { unsigned i; BX_DEBUG(("Init $Id: devices.cc,v 1.82 2005/02/16 17:53:40 vruppert Exp $")); mem = newmem; /* set no-default handlers, will be overwritten by the real default handler */ //初始化 io handler 双向链表的头节点 io_read_handlers.next = &io_read_handlers; io_read_handlers.prev = &io_read_handlers; io_read_handlers.handler_name = "Default"; io_read_handlers.funct = (void *)&default_read_handler; io_read_handlers.this_ptr = NULL; io_read_handlers.usage_count = 0; // not used with the default handler io_read_handlers.mask = 7; //动态分配 read_port_to_handler[]数组, 该数组作用是作 PORT ADDRÆIO Handler 的 映射 //数组大小 0x10000, 即最多可以映射 0x10000 个 IO 端口 if (read_port_to_handler) delete [] read_port_to_handler; if (write_port_to_handler) delete [] write_port_to_handler; read_port_to_handler = new struct io_handler_struct *[PORTS]; write_port_to_handler = new struct io_handler_struct *[PORTS]; /* set handlers to the default one */ 初始化成默认 handler for (i=0; i < PORTS; i++) { read_port_to_handler[i] = &io_read_handlers; write_port_to_handler[i] = &io_write_handlers; } //初始化 IRQ 数组 for (i=0; i < BX_MAX_IRQS; i++) { irq_handler_name[i] = NULL; } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 152 // BBD: At present, the only difference between "core" and "optional" // plugins is that initialization and reset of optional plugins is handled // by the plugin device list (). Init and reset of core plugins is done // "by hand" in this file. Basically, we're using core plugins when we // want to control the init order. // // CB: UNMAPPED and BIOSDEV should maybe be optional PLUG_load_plugin(unmapped, PLUGTYPE_CORE); PLUG_load_plugin(biosdev, PLUGTYPE_CORE); PLUG_load_plugin(cmos, PLUGTYPE_CORE); PLUG_load_plugin(dma, PLUGTYPE_CORE); PLUG_load_plugin(pic, PLUGTYPE_CORE); PLUG_load_plugin(vga, PLUGTYPE_CORE); PLUG_load_plugin(floppy, PLUGTYPE_CORE); PLUG_load_plugin(harddrv, PLUGTYPE_OPTIONAL); PLUG_load_plugin(keyboard, PLUGTYPE_OPTIONAL); #if BX_SUPPORT_BUSMOUSE if (bx_options.Omouse_type->get () == BX_MOUSE_TYPE_BUS) { PLUG_load_plugin(busmouse, PLUGTYPE_OPTIONAL); } #endif if (is_serial_enabled ()) PLUG_load_plugin(serial, PLUGTYPE_OPTIONAL); if (is_parallel_enabled ()) PLUG_load_plugin(parallel, PLUGTYPE_OPTIONAL); PLUG_load_plugin(extfpuirq, PLUGTYPE_OPTIONAL); #if BX_SUPPORT_GAME PLUG_load_plugin(gameport, PLUGTYPE_OPTIONAL); #endif PLUG_load_plugin(speaker, PLUGTYPE_OPTIONAL); // Start with registering the default (unmapped) handler pluginUnmapped->init (); // PCI logic (i440FX) Intel i440FX 芯片组 if (bx_options.Oi440FXSupport->get ()) { #if BX_SUPPORT_PCI PLUG_load_plugin(pci, PLUGTYPE_CORE); PLUG_load_plugin(pci2isa, PLUGTYPE_CORE); PLUG_load_plugin(pci_ide, PLUGTYPE_OPTIONAL); #if BX_SUPPORT_PCIVGA if (!strcmp(bx_options.Ovga_extension->getptr (), "vbe")) { PLUG_load_plugin(pcivga, PLUGTYPE_OPTIONAL); } #endif #if BX_SUPPORT_PCIUSB PLUG_load_plugin(pciusb, PLUGTYPE_OPTIONAL); #endif #if BX_SUPPORT_PCIDEV PLUG_load_plugin(pcidev, PLUGTYPE_OPTIONAL); #endif #if BX_SUPPORT_PCIPNIC if (bx_options.pnic.Oenabled->get ()) { PLUG_load_plugin(pcipnic, PLUGTYPE_OPTIONAL); } #endif #else BX_ERROR(("Bochs is not compiled with PCI support")); #endif } // NE2000 NIC if (bx_options.ne2k.Oenabled->get ()) { #if BX_SUPPORT_NE2K PLUG_load_plugin(ne2k, PLUGTYPE_OPTIONAL); #else BX_ERROR(("Bochs is not compiled with NE2K support")); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 153 #endif } #if BX_SUPPORT_APIC // I/O APIC 82093AA 初始化高级可编程中断控制器 ioapic = & bx_ioapic; ioapic->init (); #endif // BIOS log 初始化 BIOS 设备 pluginBiosDevice->init (); // CMOS RAM & RTC pluginCmosDevice->init (); /*--- 8237 DMA 控制器 ---*/ pluginDmaDevice->init(); //--- FLOPPY ---软驱 pluginFloppyDevice->init(); //--- SOUND blaster 声卡 if (bx_options.sb16.Oenabled->get ()) { #if BX_SUPPORT_SB16 PLUG_load_plugin(sb16, PLUGTYPE_OPTIONAL); #else BX_ERROR(("Bochs is not compiled with SB16 support")); #endif } #if BX_SUPPORT_PCI pluginPciBridge->init (); //初始化 PCI 主桥 pluginPci2IsaBridge->init (); //初始化 PCI-ISA 桥 #endif /*--- VGA 适配器 ---*/ pluginVgaDevice->init (); /*--- 8259A PIC ---*/ pluginPicDevice->init(); /*--- 8254 PIT 定时器 ---*/ pit = & bx_pit; pit->init(); bx_virt_timer.init(); bx_slowdown_timer.init(); // system hardware 系统硬件, 注册 92H 的读写 handler register_io_read_handler( this, &read_handler, //端口 92H 的 read handler 0x0092, "Port 92h System Control", 1 ); register_io_write_handler(this, &write_handler, //端口 92H 的 write handler 0x0092, "Port 92h System Control", 1 ); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 154 // misc. CMOS 初始化 COMS 中部分变量 Bit32u extended_memory_in_k = mem->get_memory_in_k() > 1024 ? (mem->get_memory_in_k() - 1024) : 0; if (extended_memory_in_k > 0xfc00) extended_memory_in_k = 0xfc00; //得到extended内存大小,如果大于0xfc00(64M), 则设为64MB DEV_cmos_set_reg(0x15, (Bit8u) BASE_MEMORY_IN_K); DEV_cmos_set_reg(0x16, (Bit8u) (BASE_MEMORY_IN_K >> 8)); //基本内存 640K DEV_cmos_set_reg(0x17, (Bit8u) (extended_memory_in_k & 0xff) ); DEV_cmos_set_reg(0x18, (Bit8u) ((extended_memory_in_k >> 8) & 0xff) ); //Extended Memory size DEV_cmos_set_reg(0x30, (Bit8u) (extended_memory_in_k & 0xff) ); DEV_cmos_set_reg(0x31, (Bit8u) ((extended_memory_in_k >> 8) & 0xff) ); //Actual extended mem Bit32u extended_memory_in_64k = mem->get_memory_in_k() > 16384 ? (mem->get_memory_in_k() - 16384) / 64 : 0; if (extended_memory_in_64k > 0xffff) extended_memory_in_64k = 0xffff; DEV_cmos_set_reg(0x34, (Bit8u) (extended_memory_in_64k & 0xff) ); DEV_cmos_set_reg(0x35, (Bit8u) ((extended_memory_in_64k >> 8) & 0xff) ); //在 PC system 中注册设备定时器 if (timer_handle != BX_NULL_TIMER_HANDLE) { timer_handle = bx_pc_system.register_timer( this, timer_handler, (unsigned) BX_IODEV_HANDLER_PERIOD, 1, 1, "devices.cc"); } // Clear fields for bulk IO acceleration transfers. 清空 bulk 传输相关变量 bulkIOHostAddr = 0; bulkIOQuantumsRequested = 0; bulkIOQuantumsTransferred = 0; bx_init_plugins(); /* now perform checksum of CMOS memory , 进行 CMOS 校验*/ DEV_cmos_checksum(); } 6.2.4 设备复位 //复位函数, 复位相关的设备 //参数 type 为复位方式,分为硬件复位和软件两种 void bx_devices_c::reset(unsigned type) { pluginUnmapped->reset(type); #if BX_SUPPORT_PCI if (bx_options.Oi440FXSupport->get ()) { pluginPciBridge->reset(type); pluginPci2IsaBridge->reset(type); } #endif #if BX_SUPPORT_IOAPIC ioapic->reset (type); #endif pluginBiosDevice->reset(type); pluginCmosDevice->reset(type); pluginDmaDevice->reset(type); pluginFloppyDevice->reset(type); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 155 pluginVgaDevice->reset(type); pluginPicDevice->reset(type); pit->reset(type); //8254 PIT bx_slowdown_timer.reset(type); #if BX_SUPPORT_IODEBUG iodebug->reset(type); #endif // now reset optional plugins bx_reset_plugins(type); } 6.2.5 设备访问(读写)的处理 Bit32u bx_devices_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len) //调用 port92_read() { #if !BX_USE_DEV_SMF bx_devices_c *class_ptr = (bx_devices_c *) this_ptr; return( class_ptr->port92_read(address, io_len) ); } Bit32u bx_devices_c::port92_read(Bit32u address, unsigned io_len) { #else UNUSED(this_ptr); #endif // !BX_USE_DEV_SMF BX_DEBUG(("port92h read partially supported!!!")); BX_DEBUG((" returning %02x", (unsigned) (BX_GET_ENABLE_A20() << 1))); return(BX_GET_ENABLE_A20() << 1); } // 通过访问芯片组控制器端口92H, 可以Enable或Disable A20控制( 读写92H的Bit[1] ) // 类 bx_devices_c 本身只有一个有用的 Io handler, 即控制 A20 使能,. 缺省的 handler 总是返回全 F Bit32u bx_devices_c::default_read_handler(void *this_ptr, Bit32u address, unsigned io_len) { UNUSED(this_ptr); BX_PANIC(("No default io-read handler found for 0x%04x/%d. Unmapped io-device not loaded ?", address, io_len)); return 0xffffffff; } void bx_devices_c::default_write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len) { UNUSED(this_ptr); BX_PANIC(("No default io-write handler found for 0x%04x/%d. Unmapped io-device not loaded ?", address, io_len)); } //对缺省端口的读操作返回的总是 0xFFFFFFFF, 写操作不起如何作用 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 156 定时器响应函数 timer_handler() bx_devices_c::timer_handler(void *this_ptr) { bx_devices_c *class_ptr = (bx_devices_c *) this_ptr; class_ptr->timer(); //调用 timer() } void bx_devices_c::timer() { #if (BX_USE_NEW_PIT==0) // BX_USE_NEW_PIT=1 ,不成立 if ( pit->periodic( BX_IODEV_HANDLER_PERIOD ) ) { // This is a hack to make the IRQ0 work DEV_pic_lower_irq(0); DEV_pic_raise_irq(0); } #endif // separate calls to bx_gui->handle_events from the keyboard code. 这段没怎么理解 { static int multiple=0; // multiple 为静态变量 if ( ++multiple==10) { multiple=0; SIM->periodic (); if (!BX_CPU(0)->kill_bochs_request) bx_gui->handle_events(); } } // KPL Removed lapic periodic timer registration here. } 函数 register_irq()用来注册 IRQ bx_bool bx_devices_c::register_irq(unsigned irq, const char *name) { if (irq >= BX_MAX_IRQS) { //判断范围有效性 BX_PANIC(("IO device %s registered with IRQ=%d above %u", name, irq, (unsigned) BX_MAX_IRQS-1)); return false; } if (irq_handler_name[irq]) { //该 irq 已经被注册过 BX_PANIC(("IRQ %u conflict, %s with %s", irq, irq_handler_name[irq], name)); return false; } irq_handler_name[irq] = name; //只是简单的赋个字符串 return true; } 注册 IO 读取 bx_bool Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 157 bx_devices_c::register_io_read_handler( void *this_ptr, bx_read_handler_t f, Bit32u addr, const char *name, Bit8u mask ) { addr &= 0x0000ffff; //取低 32 位 if (!f) return false; /* first check if the port already has a handlers != the default handler 检查该端口是否已被注册*/ if (read_port_to_handler[addr] && read_port_to_handler[addr] != &io_read_handlers) { // the default BX_ERROR(("IO device address conflict(read) at IO address %Xh", (unsigned) addr)); BX_ERROR((" conflicting devices: %s & %s", read_port_to_handler[addr]->handler_name, name)); return false; } /* first find existing handle for function or create new one 检查 handler f 是否已经存在于链表中 */ struct io_handler_struct *curr = &io_read_handlers; struct io_handler_struct *io_read_handler = NULL; do { if (curr->funct == f && curr->mask == mask && curr->this_ptr == this_ptr && curr->handler_name == name) { // really want the same name too io_read_handler = curr; break; } curr = curr->next; } while (curr->next != &io_read_handlers); //如果没有, 则新建一个 f 的节点 if (!io_read_handler) { io_read_handler = new struct io_handler_struct; io_read_handler->funct = (void *)f; io_read_handler->this_ptr = this_ptr; io_read_handler->handler_name = name; io_read_handler->mask = mask; io_read_handler->usage_count = 0; // add the handler to the double linked list of handlers 将节点插入双向链表 io_read_handlers.prev->next = io_read_handler; io_read_handler->next = &io_read_handlers; io_read_handler->prev = io_read_handlers.prev; io_read_handlers.prev = io_read_handler; } io_read_handler->usage_count++; //将该 handler 管理的端口数目加 1 read_port_to_handler[addr] = io_read_handler; return true; // address mapped successfully } //注册 IO_write 端口与 io_read 的函数类似 //注册 IO_read_range 也与 io_read 的函数类似 IO 端口读写, 供 IO 指令调用 Bit32u BX_CPP_AttrRegparmN(2) bx_devices_c::inp(Bit16u addr, unsigned io_len) { struct io_handler_struct *io_read_handler; Bit32u ret; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 158 BX_INSTR_INP(addr, io_len); io_read_handler = read_port_to_handler[addr]; if (io_read_handler->mask & io_len) //mask 相同, 调用 handler->funct()处理 { ret = ((bx_read_handler_t)io_read_handler->funct)(io_read_handler->this_ptr, (Bit32u)addr, io_len); } else //读写无效 { switch (io_len) { case 1: ret = 0xff; break; case 2: ret = 0xffff; break; default: ret = 0xffffffff; break; } BX_ERROR(("read from port 0x%04x with len %d returns 0x%x", addr, io_len, ret)); } BX_INSTR_INP2(addr, io_len, ret); BX_DBG_IO_REPORT(addr, io_len, BX_READ, ret); return(ret); } 判断端口使能情况,包括串口、USB 并口等 bx_bool bx_devices_c::is_serial_enabled () { for (int i=0; iget_param_bool (BXP_COMx_ENABLED(i+1))->get()) return true; //只要有一个 serial port 使能, 就返回 true } return false; } bx_bool bx_devices_c::is_usb_enabled () { for (int i=0; iget_param_bool (BXP_USBx_ENABLED(i+1))->get()) return true; //只要有一个 usb port 使能, 就返回 true } return false; } bx_bool bx_devices_c::is_parallel_enabled () { for (int i=0; iget_param_bool (BXP_PARPORTx_ENABLED(i+1))->get()) return true; //只要有一个 parallel port 使能, 就返回 true } return false; } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 159 6.2.6 已注册的 I/O handlers 和 IRQ 清单 CMOS 设备:Cmos.cpp 端口 70H 和 71H DEV_register_ioread_handler(this, read_handler, 0x0070, "CMOS RAM", 1); DEV_register_ioread_handler(this, read_handler, 0x0071, "CMOS RAM", 1); DEV_register_iowrite_handler(this, write_handler, 0x0070, "CMOS RAM", 1); DEV_register_iowrite_handler(this, write_handler, 0x0071, "CMOS RAM", 1); DEV_register_irq(8, "CMOS RTC"); DMA 控制器:DMA.cpp // 端口范围 0000..000F, 16 个 IO for (i=0x0000; i<=0x000F; i++) { DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1); //iolen=8bit DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3); //iolen=8bit/16bit } // 端口范围 00080..008F, 16 个 IO for (i=0x0080; i<=0x008F; i++) { DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1); DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3); } // 端口范围 000C0..00DE, 30 个 IO for (i=0x00C0; i<=0x00DE; i+=2) { DEV_register_ioread_handler(this, read_handler, i, "DMA controller", 1); DEV_register_iowrite_handler(this, write_handler, i, "DMA controller", 3); } 软驱:floppy.cpp 端口范围 0x3F2-0x3F7(6 个), 中断 6 DEV_register_irq(6, "Floppy Drive"); for (unsigned addr=0x03F2; addr<=0x03F7; addr++) { DEV_register_ioread_handler(this, read_handler, addr, "Floppy Drive", 1); //iolen=8bit DEV_register_iowrite_handler(this, write_handler, addr, "Floppy Drive", 1); } 游戏端口:GamePort.cpp // Allocate the gameport IO address range 0x200..0x207 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 160 //端口范围 0x200-0x207(8 个) for (unsigned addr=0x200; addr<0x208; addr++) { DEV_register_ioread_handler(this, read_handler, addr, "Gameport", 1); //iolen=8bit DEV_register_iowrite_handler(this, write_handler, addr, "Gameport", 1); } 硬盘驱动器:harddrv.cpp for (channel=0; channel 0) { for (i=0; i 0) { DEV_register_ioread_handler(this_ptr, f1, baseaddr + i, name, iomask[i]); DEV_register_iowrite_handler(this_ptr, f2, baseaddr + i, name, iomask[i]); } } } PCI-ISA 桥:pci2isa.cpp //总共 5 个地址, 8bit 访问 DEV_register_iowrite_handler(this, write_handler, 0x00B2, "PIIX3 PCI-to-ISA bridge", 1); DEV_register_iowrite_handler(this, write_handler, 0x00B3, "PIIX3 PCI-to-ISA bridge", 1); DEV_register_iowrite_handler(this, write_handler, 0x04D0, "PIIX3 PCI-to-ISA bridge", 1); DEV_register_iowrite_handler(this, write_handler, 0x04D1, "PIIX3 PCI-to-ISA bridge", 1); DEV_register_iowrite_handler(this, write_handler, 0x0CF9, "PIIX3 PCI-to-ISA bridge", 1); DEV_register_ioread_handler(this, read_handler, 0x00B2, "PIIX3 PCI-to-ISA bridge", 1); DEV_register_ioread_handler(this, read_handler, 0x00B3, "PIIX3 PCI-to-ISA bridge", 1); DEV_register_ioread_handler(this, read_handler, 0x04D0, "PIIX3 PCI-to-ISA bridge", 1); DEV_register_ioread_handler(this, read_handler, 0x04D1, "PIIX3 PCI-to-ISA bridge", 1); DEV_register_ioread_handler(this, read_handler, 0x0CF9, "PIIX3 PCI-to-ISA bridge", 1); PCI 设备:Pcidev.cpp if (!DEV_register_ioread_handler_range(&(BX_PCIDEV_THIS regions[idx]), … DEV_register_ioread_handler_range( &(BX_PCIDEV_THIS regions[io_reg_idx]), read_handler, BX_PCIDEV_THIS regions[io_reg_idx].start, BX_PCIDEV_THIS regions[io_reg_idx].end, "pcidev", 7); 中断控制器:pic.cpp //总共 4 个 8bit 端口, 读写 8259 可编程中断控制器 DEV_register_ioread_handler(this, read_handler, 0x0020, "8259 PIC", 1); DEV_register_ioread_handler(this, read_handler, 0x0021, "8259 PIC", 1); DEV_register_ioread_handler(this, read_handler, 0x00A0, "8259 PIC", 1); DEV_register_ioread_handler(this, read_handler, 0x00A1, "8259 PIC", 1); DEV_register_iowrite_handler(this, write_handler, 0x0020, "8259 PIC", 1); DEV_register_iowrite_handler(this, write_handler, 0x0021, "8259 PIC", 1); DEV_register_iowrite_handler(this, write_handler, 0x00A0, "8259 PIC", 1); DEV_register_iowrite_handler(this, write_handler, 0x00A1, "8259 PIC", 1); 定时器:Pit.cpp //总共 5 个 8bit 端口, 读写 8254 定时器 DEV_register_irq(0, "8254 PIT"); DEV_register_ioread_handler(this, read_handler, 0x0040, "8254 PIT", 1); DEV_register_ioread_handler(this, read_handler, 0x0041, "8254 PIT", 1); DEV_register_ioread_handler(this, read_handler, 0x0042, "8254 PIT", 1); DEV_register_ioread_handler(this, read_handler, 0x0043, "8254 PIT", 1); DEV_register_ioread_handler(this, read_handler, 0x0061, "8254 PIT", 1); DEV_register_iowrite_handler(this, write_handler, 0x0040, "8254 PIT", 1); DEV_register_iowrite_handler(this, write_handler, 0x0041, "8254 PIT", 1); DEV_register_iowrite_handler(this, write_handler, 0x0042, "8254 PIT", 1); DEV_register_iowrite_handler(this, write_handler, 0x0043, "8254 PIT", 1); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 163 DEV_register_iowrite_handler(this, write_handler, 0x0061, "8254 PIT", 1); 声卡:Sb16.cpp //分配 IO 地址 2x0..2xf, 3x0..3x4 and 388..38b for (addr=BX_SB16_IO; addrgetptr (), "vbe")) { for (addr=VBE_DISPI_IOPORT_INDEX; addr<=VBE_DISPI_IOPORT_DATA; addr++) { DEV_register_ioread_handler(this, vbe_read_handler, addr, "vga video", 7); DEV_register_iowrite_handler(this, vbe_write_handler, addr, "vga video", 7); } if (!BX_SUPPORT_PCIUSB || !bx_options.usb[0].Oenabled->get()) { for (addr=VBE_DISPI_IOPORT_INDEX_OLD; addr<=VBE_DISPI_IOPORT_DATA_OLD; addr++) { DEV_register_ioread_handler(this, vbe_read_handler, addr, "vga video", 7); DEV_register_iowrite_handler(this, vbe_write_handler, addr, "vga video", 7); } } bx_vga_c::init_iohandlers(bx_read_handler_t f_read, bx_write_handler_t f_write) { unsigned addr, i; Bit8u io_mask[16] = {3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1}; for (addr=0x03B4; addr<=0x03B5; addr++) { DEV_register_ioread_handler(this, f_read, addr, "vga video", 1); DEV_register_iowrite_handler(this, f_write, addr, "vga video", 3); } for (addr=0x03BA; addr<=0x03BA; addr++) { DEV_register_ioread_handler(this, f_read, addr, "vga video", 1); DEV_register_iowrite_handler(this, f_write, addr, "vga video", 3); } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 164 i = 0; for (addr=0x03C0; addr<=0x03CF; addr++) { DEV_register_ioread_handler(this, f_read, addr, "vga video", io_mask[i++]); DEV_register_iowrite_handler(this, f_write, addr, "vga video", 3); } for (addr=0x03D4; addr<=0x03D5; addr++) { DEV_register_ioread_handler(this, f_read, addr, "vga video", 3); DEV_register_iowrite_handler(this, f_write, addr, "vga video", 3); } for (addr=0x03DA; addr<=0x03DA; addr++) { DEV_register_ioread_handler(this, f_read, addr, "vga video", 1); DEV_register_iowrite_handler(this, f_write, addr, "vga video", 3); } } 已注册的 IRQ LIST PART 1 常规设备 Busmouse.cpp // BUS_MOUSE_IRQ=5, 默认配置中没有使用 busmouse DEV_register_irq(BUS_MOUSE_IRQ, "Bus Mouse"); Cmos.cpp // COMS 实时钟 RTC DEV_register_irq(8, "CMOS RTC"); Extfpuirq.cpp //外部 FPU DEV_register_irq(13, "External FPU IRQ"); Floppy.cpp //软盘驱动器 DEV_register_irq(6, "Floppy Drive"); Harddrv.cpp //硬盘驱动器 DEV_register_irq(BX_HD_THIS channels[channel].irq, strdup(string)); #ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 #ata1: enabled=0, ioaddr1=0x170, ioaddr2=0x370, irq=15 #ata2: enabled=0, ioaddr1=0x1e8, ioaddr2=0x3e0, irq=11 //一般不用 #ata3: enabled=0, ioaddr1=0x168, ioaddr2=0x360, irq=9 //一般不用 keyboard.cpp Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 165 //键盘 DEV_register_irq(1, "8042 Keyboard controller"); DEV_register_irq(12, "8042 Keyboard controller (PS/2 mouse)"); Ne2k.cpp //NE2000 兼容网卡 DEV_register_irq(BX_NE2K_THIS s.base_irq, "NE2000 ethernet NIC"); # ne2k: ioaddr=0x240, irq=9, mac=fe:fd:00:00:00:01, ethmod=fbsd, ethdev=en0 #macosx Parallel.cpp //并口 DEV_register_irq(BX_PAR_THIS s[port].IRQ, name); # 最多两个并口 Parallel port1: IRQ=7 Parallel port2: IRQ=5 Pit.cpp //8254 定时器 DEV_register_irq(0, "8254 PIT"); Serial.cpp //串口 DEV_register_irq(BX_SER_THIS s[i].IRQ, name); #最多 4 个串口 #COM1, IRQ=4 #COM2, IRQ=3 #COM3, IRQ=4 #COM4, IRQ=3 PART2 PCI 设备中断 Pci2isa.cpp DEV_register_irq(irq, "PIIX3 IRQ routing"); //该函数在 pci_register_irq()中被调用, 而 pci_register_irq()又在 pci_write()中被调用 PciDev.cpp ===Init 中的调用=== #BX_PCIDEV_THIS irq = PCIDEV_IRQ, PCIDEV_IRQ=11, 默认注册 IRQ11 DEV_register_irq(BX_PCIDEV_THIS irq, pcidev_name); ===Pci_write 中的调用=== DEV_register_irq(BX_PCIDEV_THIS irq, pcidev_name); Soundblaster system Sb16.cpp DEV_register_irq(BX_SB16_IRQ, "SB16"); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 166 6.3 定时器(PIT)类分析 6.3.1 类 bx_pit_c 概述 Bochs 工程中有两个版本的 PIT,一个由 pit.h 和 pit.cpp 定义,另外一个由 pit_wrap.h 和 pit_wrap.cpp 定义。后者为 Greg Alexander's 在 summer 2001 写的新版本。 编译条件 BX_USE_NEW_PIT 用来对两个版本进行选择。 我们对新版本的定时器进行分析。 pit_wrap.h 中有#include "pit82c54.h"语句,说明新定时器调用了 82c54 6.3.2 类 bx_pit_c 源码注释 class bx_pit_c : public logfunctions { public: bx_pit_c( void ); ~bx_pit_c( void ); BX_PIT_SMF int init( void ); BX_PIT_SMF void reset( unsigned type); BX_PIT_SMF bx_bool periodic( Bit32u usec_delta ); Bit16u get_timer(int Timer) { return s.timer.get_inlatch(Timer); } private: static Bit32u read_handler(void *this_ptr, Bit32u address, unsigned io_len); static void write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len); #if !BX_USE_PIT_SMF //YES Bit32u read( Bit32u addr, unsigned int len ); void write( Bit32u addr, Bit32u Value, unsigned int len ); #endif struct s_type { pit_82C54 timer; Bit8u speaker_data_on; bx_bool refresh_clock_div2; int timer_handle[3]; Bit64u last_usec; Bit32u last_next_event_time; Bit64u total_ticks; Bit64u usec_per_second; Bit64u ticks_per_second; Bit64u total_sec; Bit64u last_time; Bit64u last_sec_usec; Bit64u max_ticks; Bit64u stored_delta; Bit64u total_usec; Bit64u em_last_realtime; Bit64u last_realtime_delta; Bit64u last_realtime_ticks; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 167 } s; static void timer_handler(void *this_ptr); BX_PIT_SMF void handle_timer(); BX_PIT_SMF void write_count_reg( Bit8u value, unsigned timerid ); BX_PIT_SMF Bit8u read_counter( unsigned timerid ); BX_PIT_SMF void latch( unsigned timerid ); BX_PIT_SMF void set_GATE(unsigned pit_id, unsigned value); BX_PIT_SMF void start(unsigned timerid); BX_PIT_SMF void second_update_data(void); }; ------------------------------------------------------------------------------------------------------------------------------------------ 构造与析构函数 ------------------------------------------------------------------------------------------------------------------------------------------ bx_pit_c::bx_pit_c( void ) { put("PIT"); settype(PITLOG); s.speaker_data_on=0; /* 8254 PIT (Programmable Interval Timer) */ BX_PIT_THIS s.timer_handle[1] = BX_NULL_TIMER_HANDLE; BX_PIT_THIS s.timer_handle[2] = BX_NULL_TIMER_HANDLE; BX_PIT_THIS s.timer_handle[0] = BX_NULL_TIMER_HANDLE; } bx_pit_c::~bx_pit_c( void ) { } ------------------------------------------------------------------------------------------------------------------------------------------ 初始化 ------------------------------------------------------------------------------------------------------------------------------------------ Int bx_pit_c::init( void ) { //注册 8254 的 IRQ(IRQ0)和 I/O 端口(0x0040-0x0043,0x0061) DEV_register_irq(0, "8254 PIT"); DEV_register_ioread_handler(this, read_handler, 0x0040, "8254 PIT", 1); DEV_register_ioread_handler(this, read_handler, 0x0041, "8254 PIT", 1); DEV_register_ioread_handler(this, read_handler, 0x0042, "8254 PIT", 1); DEV_register_ioread_handler(this, read_handler, 0x0043, "8254 PIT", 1); DEV_register_ioread_handler(this, read_handler, 0x0061, "8254 PIT", 1); DEV_register_iowrite_handler(this, write_handler, 0x0040, "8254 PIT", 1); DEV_register_iowrite_handler(this, write_handler, 0x0041, "8254 PIT", 1); DEV_register_iowrite_handler(this, write_handler, 0x0042, "8254 PIT", 1); DEV_register_iowrite_handler(this, write_handler, 0x0043, "8254 PIT", 1); DEV_register_iowrite_handler(this, write_handler, 0x0061, "8254 PIT", 1); BX_DEBUG(("pit: starting init")); BX_PIT_THIS s.speaker_data_on = 0; BX_PIT_THIS s.refresh_clock_div2 = 0; BX_PIT_THIS s.timer.init(); //8254 初始化 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 168 Bit64u my_time_usec = bx_virt_timer.time_usec(); if (BX_PIT_THIS s.timer_handle[0] == BX_NULL_TIMER_HANDLE) { BX_PIT_THIS s.timer_handle[0] = bx_virt_timer.register_timer(this, timer_handler, (unsigned) 100 , 1, 1, "pit_wrap"); } BX_DEBUG(("pit: RESETting timer.")); bx_virt_timer.deactivate_timer(BX_PIT_THIS s.timer_handle[0]); BX_DEBUG(("deactivated timer.")); if(BX_PIT_THIS s.timer.get_next_event_time()) { bx_virt_timer.activate_timer(BX_PIT_THIS s.timer_handle[0], (Bit32u)BX_MAX(1,TICKS_TO_USEC(BX_PIT_THIS s.timer.get_next_event_time())), 0); BX_DEBUG(("activated timer.")); } BX_PIT_THIS s.last_next_event_time = BX_PIT_THIS s.timer.get_next_event_time(); BX_PIT_THIS s.last_usec=my_time_usec; BX_PIT_THIS s.total_ticks=0; BX_PIT_THIS s.total_usec=0; BX_DEBUG(("pit: finished init")); BX_DEBUG(("s.last_usec="FMT_LL"d",BX_PIT_THIS s.last_usec)); BX_DEBUG(("s.timer_id=%d",BX_PIT_THIS s.timer_handle[0])); BX_DEBUG(("s.timer.get_next_event_time=%d",BX_PIT_THIS s.timer.get_next_event_time())); BX_DEBUG(("s.last_next_event_time=%d",BX_PIT_THIS s.last_next_event_time)); return(1); } ------------------------------------------------------------------------------------------------------------------------------------------ RESET(空函数) ------------------------------------------------------------------------------------------------------------------------------------------ void bx_pit_c::reset(unsigned type) { } ------------------------------------------------------------------------------------------------------------------------------------------ 定时器 handler ------------------------------------------------------------------------------------------------------------------------------------------ void bx_pit_c::timer_handler(void *this_ptr) { bx_pit_c * class_ptr = (bx_pit_c *) this_ptr; class_ptr->handle_timer(); } void bx_pit_c::handle_timer() { Bit64u my_time_usec = bx_virt_timer.time_usec(); Bit64u time_passed = my_time_usec-BX_PIT_THIS s.last_usec; Bit32u time_passed32 = (Bit32u)time_passed; BX_DEBUG(("pit: entering timer handler")); if(time_passed32) { periodic(time_passed32); //更新定时器状态 } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 169 BX_PIT_THIS s.last_usec=BX_PIT_THIS s.last_usec + time_passed; if(time_passed || (BX_PIT_THIS s.last_next_event_time != BX_PIT_THIS s.timer.get_next_event_time()) ) { BX_DEBUG(("pit: RESETting timer.")); bx_virt_timer.deactivate_timer(BX_PIT_THIS s.timer_handle[0]); BX_DEBUG(("deactivated timer.")); if(BX_PIT_THIS s.timer.get_next_event_time()) { bx_virt_timer.activate_timer(BX_PIT_THIS s.timer_handle[0], (Bit32u)BX_MAX(1,TICKS_TO_USEC(BX_PIT_THIS s.timer.get_next_event_time())), 0); BX_DEBUG(("activated timer.")); } BX_PIT_THIS s.last_next_event_time = BX_PIT_THIS s.timer.get_next_event_time(); } BX_DEBUG(("s.last_usec="FMT_LL"d",BX_PIT_THIS s.last_usec)); BX_DEBUG(("s.timer_id=%d",BX_PIT_THIS s.timer_handle[0])); BX_DEBUG(("s.timer.get_next_event_time=%x",BX_PIT_THIS s.timer.get_next_event_time())); BX_DEBUG(("s.last_next_event_time=%d",BX_PIT_THIS s.last_next_event_time)); } ------------------------------------------------------------------------------------------------------------------------------------------ READ 操作,读之前对定时器进行更新 ------------------------------------------------------------------------------------------------------------------------------------------ // static IO port read callback handler // redirects to non-static class handler to avoid virtual functions Bit32u bx_pit_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len) { bx_pit_c *class_ptr = (bx_pit_c *) this_ptr; return( class_ptr->read(address, io_len) ); } Bit32u bx_pit_c::read( Bit32u address, unsigned int io_len ) { BX_DEBUG(("pit: entering read handler")); handle_timer(); //更新定时器状态 Bit64u my_time_usec = bx_virt_timer.time_usec(); if (bx_dbg.pit) BX_INFO(("pit: io read from port %04x", (unsigned) address)); //读寄存器(8bit),调用 8254 的读函数 switch (address) { case 0x40: /* timer 0 - system ticks */ return(BX_PIT_THIS s.timer.read(0)); break; case 0x41: /* timer 1 read */ return(BX_PIT_THIS s.timer.read(1)); break; case 0x42: /* timer 2 read */ return(BX_PIT_THIS s.timer.read(2)); break; case 0x43: /* timer 1 read */ Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 170 return(BX_PIT_THIS s.timer.read(3)); break; case 0x61: /* AT, port 61h */ BX_PIT_THIS s.refresh_clock_div2 = (bx_bool)((my_time_usec / 15) & 1); return( (BX_PIT_THIS s.timer.read_OUT(2)<<5) | (BX_PIT_THIS s.refresh_clock_div2<<4) | (BX_PIT_THIS s.speaker_data_on<<1) | (BX_PIT_THIS s.timer.read_GATE(2)?1:0) ); break; default: BX_PANIC(("pit: unsupported io read from port %04x", address)); } return(0); /* keep compiler happy */ } ------------------------------------------------------------------------------------------------------------------------------------------ WRITE 操作,写之前对定时器进行更新 ------------------------------------------------------------------------------------------------------------------------------------------ // static IO port write callback handler // redirects to non-static class handler to avoid virtual functions 重定位到非静态类,阻止虚函数? void bx_pit_c::write_handler(void *this_ptr, Bit32u address, Bit32u dvalue, unsigned io_len) { bx_pit_c *class_ptr = (bx_pit_c *) this_ptr; class_ptr->write(address, dvalue, io_len); } void bx_pit_c::write( Bit32u address, Bit32u dvalue, unsigned int io_len ) { Bit8u value; Bit64u my_time_usec = bx_virt_timer.time_usec(); Bit64u time_passed = my_time_usec - BX_PIT_THIS s.last_usec; Bit32u time_passed32 = (Bit32u)time_passed; BX_DEBUG(("pit: entering write handler")); if(time_passed32) { periodic(time_passed32); //更新定时器状态 } BX_PIT_THIS s.last_usec=BX_PIT_THIS s.last_usec + time_passed; value = (Bit8u ) dvalue; if (bx_dbg.pit) BX_INFO(("pit: write to port %04x = %02x", (unsigned) address, (unsigned) value)); //写寄存器(8bit),调用 8254 的写函数 switch (address) { case 0x40: /* timer 0: write count register */ BX_PIT_THIS s.timer.write(0,value); break; case 0x41: /* timer 1: write count register */ BX_PIT_THIS s.timer.write( 1,value ); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 171 break; case 0x42: /* timer 2: write count register */ BX_PIT_THIS s.timer.write( 2,value ); break; case 0x43: /* timer 0-2 mode control */ BX_PIT_THIS s.timer.write( 3,value ); break; case 0x61: BX_PIT_THIS s.speaker_data_on = (value >> 1) & 0x01; if ( BX_PIT_THIS s.speaker_data_on ) { DEV_speaker_beep_on((float)(1193180.0 / this->get_timer(2))); } else { DEV_speaker_beep_off(); } /*??? only on AT+ */ BX_PIT_THIS s.timer.set_GATE(2, value & 0x01); break; default: BX_PANIC(("pit: unsupported io write to port %04x = %02x", (unsigned) address, (unsigned) value)); } if ((BX_PIT_THIS s.timer.read_OUT(0))==1) { DEV_pic_raise_irq(0); } else { DEV_pic_lower_irq(0); } if(time_passed || (BX_PIT_THIS s.last_next_event_time != BX_PIT_THIS s.timer.get_next_event_time()) ) { BX_DEBUG(("pit: RESETting timer.")); bx_virt_timer.deactivate_timer(BX_PIT_THIS s.timer_handle[0]); BX_DEBUG(("deactivated timer.")); if(BX_PIT_THIS s.timer.get_next_event_time()) { bx_virt_timer.activate_timer(BX_PIT_THIS s.timer_handle[0], (Bit32u)BX_MAX(1,TICKS_TO_USEC(BX_PIT_THIS s.timer.get_next_event_time())), 0); BX_DEBUG(("activated timer.")); } BX_PIT_THIS s.last_next_event_time = BX_PIT_THIS s.timer.get_next_event_time(); } BX_DEBUG(("s.last_usec="FMT_LL"d",BX_PIT_THIS s.last_usec)); BX_DEBUG(("s.timer_id=%d",BX_PIT_THIS s.timer_handle[0])); BX_DEBUG(("s.timer.get_next_event_time=%x",BX_PIT_THIS s.timer.get_next_event_time())); BX_DEBUG(("s.last_next_event_time=%d",BX_PIT_THIS s.last_next_event_time)); } ------------------------------------------------------------------------------------------------------------------------------------------ 周期动作 periodic() ------------------------------------------------------------------------------------------------------------------------------------------ 该函数用来对定时器数值进行更新(包括对 8254 发时钟脉冲),在 3 个地方被调用: „ Read()函数中,读寄存器之前被调用 „ write()函数中,写寄存器之前被调用 „ time_handler()函数中,被周期调用(每 100 个指令周期) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 172 bx_bool bx_pit_c::periodic( Bit32u usec_delta ) { bx_bool prev_timer0_out = BX_PIT_THIS s.timer.read_OUT(0); bx_bool want_interrupt = 0; Bit32u ticks_delta = 0; #ifdef BX_SCHEDULED_DIE_TIME if (bx_pc_system.time_ticks() > BX_SCHEDULED_DIE_TIME) { BX_ERROR (("ticks exceeded scheduled die time, quitting")); BX_EXIT (2); //退出 bochs } #endif BX_PIT_THIS s.total_usec += usec_delta; ticks_delta=(Bit32u)((USEC_TO_TICKS((Bit64u)(BX_PIT_THIS s.total_usec)))-BX_PIT_THIS s.total_ticks); BX_PIT_THIS s.total_ticks += ticks_delta; while ((BX_PIT_THIS s.total_ticks >= TICKS_PER_SECOND) && (BX_PIT_THIS s.total_usec >= USEC_PER_SECOND)) { BX_PIT_THIS s.total_ticks - = TICKS_PER_SECOND; //1193181HZ, 为 8254 输入晶振频率 BX_PIT_THIS s.total_usec -= USEC_PER_SECOND; //=1000,000 } while(ticks_delta>0) { Bit32u maxchange=BX_PIT_THIS s.timer.get_next_event_time(); Bit32u timedelta=maxchange; if((maxchange==0) || (maxchange>ticks_delta)) { timedelta=ticks_delta; } BX_PIT_THIS s.timer.clock_all(timedelta); //输入 8254 timedelta 个脉冲 if ( (prev_timer0_out==0) ) //若上一次为低电平 { if ((BX_PIT_THIS s.timer.read_OUT(0))==1) { //读 8251 的 Tout0 管脚输出,该管脚连接到 IRQ0, //跳延触发 DEV_pic_raise_irq(0); prev_timer0_out=1; } } else { //上次为高电平 if ((BX_PIT_THIS s.timer.read_OUT(0))==0) { DEV_pic_lower_irq(0); prev_timer0_out=0; } } prev_timer0_out=BX_PIT_THIS s.timer.read_OUT(0); //保存当前电平值供下一次使用 ticks_delta-=timedelta; } return(want_interrupt); } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 173 6.3.3 关于类 bx_virt_timer_c 变量 register_timer 决定是否使用 virt timer,初始化为 BX_USE_VIRTUAL_TIMERS(Bochs 默认为 1)。 当 register_timer=FALSE 时,该类实际上是对 PC system 类中的部分函数进行了封装,供 PIT 类 bx_pit_c 进行间接调用。 6.4 IDE 设备 相关的数据结构定义 6.4.1 磁盘控制器 controller_t typedef struct { struct { bx_bool busy; bx_bool drive_ready; bx_bool write_fault; bx_bool seek_complete; bx_bool drq; bx_bool corrected_data; bx_bool index_pulse; unsigned index_pulse_count; bx_bool err; } status; //状态寄存器 Bit8u error_register; //错误寄存器 Bit8u head_no; union { Bit8u sector_count; struct { unsigned c_d : 1; unsigned i_o : 1; unsigned rel : 1; unsigned tag : 5; } interrupt_reason; }; Bit8u sector_no; union { Bit16u cylinder_no; Bit16u byte_count; }; Bit8u buffer[2048]; Bit32u buffer_index; Bit32u drq_index; Bit8u current_command; Bit8u sectors_per_block; Bit8u lba_mode; struct { bx_bool reset; // 0=normal, 1=reset controller bx_bool disable_irq; // 0=allow irq, 1=disable irq } control; Bit8u reset_in_progress; Bit8u features; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 174 } controller_t; 6.4.2 IDE 驱动器描述类 bx_hard_drive_c class bx_hard_drive_c : public bx_hard_drive_stub_c { public: bx_hard_drive_c(void); virtual ~bx_hard_drive_c(void); virtual void close_harddrive(void); virtual void init(); virtual void reset(unsigned type); virtual Bit32u get_device_handle(Bit8u channel, Bit8u device); virtual Bit32u get_first_cd_handle(void); virtual unsigned get_cd_media_status(Bit32u handle); virtual unsigned set_cd_media_status(Bit32u handle, unsigned status); #if BX_SUPPORT_PCI virtual bx_bool bmdma_read_sector(Bit8u channel, Bit8u *buffer); virtual bx_bool bmdma_write_sector(Bit8u channel, Bit8u *buffer); virtual void bmdma_complete(Bit8u channel); #endif virtual Bit32u virt_read_handler(Bit32u address, unsigned io_len) { return read_handler (this, address, io_len); } virtual void virt_write_handler(Bit32u address, Bit32u value, unsigned io_len) { write_handler(this, address, value, io_len); } #if !BX_USE_HD_SMF //YES Bit32u read(Bit32u address, unsigned io_len); void write(Bit32u address, Bit32u value, unsigned io_len); #endif static Bit32u read_handler(void *this_ptr, Bit32u address, unsigned io_len); static void write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len); static void iolight_timer_handler(void *); BX_HD_SMF void iolight_timer(void); private: BX_HD_SMF bx_bool calculate_logical_address(Bit8u channel, off_t *sector) BX_CPP_AttrRegparmN(2); BX_HD_SMF void increment_address(Bit8u channel) BX_CPP_AttrRegparmN(1); BX_HD_SMF void identify_drive(Bit8u channel); BX_HD_SMF void identify_ATAPI_drive(Bit8u channel); BX_HD_SMF void command_aborted(Bit8u channel, unsigned command); BX_HD_SMF void init_send_atapi_command(Bit8u channel, Bit8u command, int req_length, int alloc_length, bool lazy = false) BX_CPP_AttrRegparmN(3); BX_HD_SMF void ready_to_send_atapi(Bit8u channel) BX_CPP_AttrRegparmN(1); BX_HD_SMF void raise_interrupt(Bit8u channel) BX_CPP_AttrRegparmN(1); BX_HD_SMF void atapi_cmd_error(Bit8u channel, sense_t sense_key, asc_t asc, bx_bool show); BX_HD_SMF void init_mode_sense_single(Bit8u channel, const void* src, int size); BX_HD_SMF void atapi_cmd_nop(Bit8u channel) BX_CPP_AttrRegparmN(1); BX_HD_SMF bx_bool bmdma_present(void); // FIXME: // For each ATA channel we should have one controller struct // and an array of two drive structs Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 175 struct channel_t { //定义 ATA 通道 struct drive_t { //定义 ATA 设备 device_image_t* hard_drive; //指向磁盘数据的指针 device_type_t device_type; // 512 byte buffer for ID drive command // These words are stored in native word endian format, as // they are fetched and returned via a return(), so // there's no need to keep them in x86 endian format. Bit16u id_drive[256]; controller_t controller; cdrom_t cdrom; sense_info_t sense; atapi_t atapi; Bit8u model_no[41]; int statusbar_id; int iolight_counter; } drives[2]; //每通道可以连接两个 IDE 设备 unsigned drive_select; Bit16u ioaddr1; Bit16u ioaddr2; Bit8u irq; } channels[BX_MAX_ATA_CHANNEL]; //最多 4 个通道,每通道可以连接两个 IDE 设备 int iolight_timer_index; }; ------------------------------------------------------------------------------------------------------------------------------------------ 6.4.3 读写磁盘映象 Bochs 支持多种磁盘映象格式 定义了抽象类 device_image_t 作为调用的统一接口(只提供 一系列虚函数),描述不同格式的子类从 device_image_t 派生出,在子类中完成函数的具体 实现。如描述 flat 格式的子类为 default_image_t 类,包含的操作有磁盘映象文件的打开关 闭以及定位和读写。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 176 class device_image_t { public: // Open a image. Returns non-negative if successful. virtual int open (const char* pathname) = 0; // Close the image. virtual void close () = 0; // Position ourselves. Return the resulting offset from the // beginning of the file. virtual off_t lseek (off_t offset, int whence) = 0; // Read count bytes to the buffer buf. Return the number of // bytes read (count). virtual ssize_t read (void* buf, size_t count) = 0; // Write count bytes from buf. Return the number of bytes // written (count). virtual ssize_t write (const void* buf, size_t count) = 0; unsigned cylinders; unsigned heads; unsigned sectors; }; // FLAT MODE class default_image_t : public device_image_t { public: // Open a image. Returns non-negative if successful. int open (const char* pathname); // Open an image with specific flags. Returns non-negative if successful. int open (const char* pathname, int flags); // Close the image. void close (); // Position ourselves. Return the resulting offset from the // beginning of the file. off_t lseek (off_t offset, int whence); // Read count bytes to the buffer buf. Return the number of // bytes read (count). ssize_t read (void* buf, size_t count); // Write count bytes from buf. Return the number of bytes // written (count). ssize_t write (const void* buf, size_t count); private: int fd; }; 6.4.4 ATA/IDE 控制器(通道)和设备初始化 初始化 ATA 控制器及设备时,使用了全局配置变量 bx_options,该变量是从 bochsrc.txt 中 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 177 读取 ATA 通道控制器和 ATA 设备信息。 Bochsrc.txt 的 ATA 控制器部分片断如下: ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 ata1: enabled=0, ioaddr1=0x170, ioaddr2=0x370, irq=15 它们定义了 ATA 控制器的开启状态、io 地址和中断请求 IRQ 号。 Bochsrc.txt 的 ATA 设备部分片断如下: ata0-master: type=disk, mode=flat, path=10M.sample, cylinders=306, heads=4, spt=17 ata0-slave: type=disk, mode=flat, path=20M.sample, cylinders=615, heads=4, spt=17 ata1-master: type=cdrom, path=D:, status=inserted ata1-slave: type=cdrom, path=/dev/cdrom, status=inserted 这些信息包括设备类型(磁盘或 CDROM 驱动),模式、参数(若为磁盘)、状态以及数据 来源等。 ATA 控制器和设备的初始化过程包括: 9 从读取的配置参数初始化 ATA 控制器,包括 IO 地址,IRQ 及相关内部寄存器;注册 I/O 读写 handler。 9 从读取的配置参数初始化 ATA 设备,先初始化设备控制器,然后分硬盘和 CDROM 两 种类型分别初始化,包括加载映象文件,设定参数(磁头、柱面、扇区数)。 9 将相关参数写入 CMOS,供以后使用。参数包括磁盘参数和系统引导参数。 9 注册 LED(指示灯)定时器 =============================================================================== Init 函数代码注释 =============================================================================== void bx_hard_drive_c::init(void) { Bit8u channel; char string[5]; char sbtext[8]; //初始化 ATA 控制器参数: IO 地址和 IRQ for (channel=0; channelget() == 1) { BX_HD_THIS channels[channel].ioaddr1 = bx_options.ata[channel].Oioaddr1->get(); BX_HD_THIS channels[channel].ioaddr2 = bx_options.ata[channel].Oioaddr2->get(); BX_HD_THIS channels[channel].irq = bx_options.ata[channel].Oirq->get(); // Coherency check if ( (BX_HD_THIS channels[channel].ioaddr1 == 0) || (BX_HD_THIS channels[channel].ioaddr2 == 0) || (BX_HD_THIS channels[channel].irq == 0) ) { BX_PANIC(("incoherency for ata channel %d: io1=0x%x, io2=%x, irq=%d", channel, Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 178 BX_HD_THIS channels[channel].ioaddr1, BX_HD_THIS channels[channel].ioaddr2, BX_HD_THIS channels[channel].irq)); } } else { BX_HD_THIS channels[channel].ioaddr1 = 0; BX_HD_THIS channels[channel].ioaddr2 = 0; BX_HD_THIS channels[channel].irq = 0; } } //a.在 bochs 的 I/O 空间注册读写 handler //b.在 bochs 中注册 IRQ for (channel=0; channelget()) { continue; } // Make model string strncpy((char*)BX_HD_THIS channels[channel].drives[device].model_no, bx_options.atadevice[channel][device].Omodel->getptr(), 40); while (strlen((char *)BX_HD_THIS channels[channel].drives[device].model_no) < 40) { strcat ((char*)BX_HD_THIS channels[channel].drives[device].model_no, " "); } // 以下根据类型分为磁盘和 CD-ROM 讨论 if (bx_options.atadevice[channel][device].Otype->get() == BX_ATA_DEVICE_DISK) { BX_DEBUG(( "Hard-Disk on target %d/%d",channel,device)); BX_HD_THIS channels[channel].drives[device].device_type = IDE_DISK; sprintf(sbtext, "HD:%d-%s", channel, device?"S":"M"); BX_HD_THIS channels[channel].drives[device].statusbar_id = bx_gui->register_statusitem(sbtext); int cyl = bx_options.atadevice[channel][device].Ocylinders->get (); int heads = bx_options.atadevice[channel][device].Oheads->get (); int spt = bx_options.atadevice[channel][device].Ospt->get (); Bit64u disk_size = (Bit64u)cyl * heads * spt * 512; /*针对不同的磁盘格式进行分类 */ switch (bx_options.atadevice[channel][device].Omode->get()) { case BX_ATA_MODE_FLAT: BX_INFO(("HD on ata%d-%d: '%s' 'flat' mode ", channel, device, bx_options.atadevice[channel][device].Opath->getptr ())); channels[channel].drives[device].hard_drive = new default_image_t(); break; case BX_ATA_MODE_CONCAT: BX_INFO(("HD on ata%d-%d: '%s' 'concat' mode ", channel, device, bx_options.atadevice[channel][device].Opath->getptr ())); channels[channel].drives[device].hard_drive = new concat_image_t(); break; case BX_ATA_MODE_SPARSE: BX_INFO(("HD on ata%d-%d: '%s' 'sparse' mode ", channel, device, bx_options.atadevice[channel][device].Opath->getptr ())); channels[channel].drives[device].hard_drive = new sparse_image_t(); break; case BX_ATA_MODE_VMWARE3: Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 180 BX_INFO(("HD on ata%d-%d: '%s' 'vmware3' mode ", channel, device, bx_options.atadevice[channel][device].Opath->getptr ())); channels[channel].drives[device].hard_drive = new vmware3_image_t(); break; case BX_ATA_MODE_UNDOABLE: BX_INFO(("HD on ata%d-%d: '%s' 'undoable' mode ", channel, device, bx_options.atadevice[channel][device].Opath->getptr ())); channels[channel].drives[device].hard_drive = new undoable_image_t(disk_size, bx_options.atadevice[channel][device].Ojournal->getptr()); break; case BX_ATA_MODE_GROWING: BX_INFO(("HD on ata%d-%d: '%s' 'growing' mode ", channel, device, bx_options.atadevice[channel][device].Opath->getptr ())); channels[channel].drives[device].hard_drive = new growing_image_t(disk_size); break; case BX_ATA_MODE_VOLATILE: BX_INFO(("HD on ata%d-%d: '%s' 'volatile' mode ", channel, device, bx_options.atadevice[channel][device].Opath->getptr ())); channels[channel].drives[device].hard_drive = new volatile_image_t(disk_size, bx_options.atadevice[channel][device].Ojournal->getptr()); break; default: BX_PANIC(("HD on ata%d-%d: '%s' unsupported HD mode : %s", channel, device, bx_options.atadevice[channel][device].Opath->getptr (), atadevice_mode_names[bx_options.atadevice[channel][device].Omode->get()])); break; } //磁盘参数:柱面,磁头和扇区 BX_HD_THIS channels[channel].drives[device].hard_drive->cylinders = cyl; BX_HD_THIS channels[channel].drives[device].hard_drive->heads = heads; BX_HD_THIS channels[channel].drives[device].hard_drive->sectors = spt; if (cyl == 0 || heads == 0 || spt == 0) { BX_PANIC(("ata%d/%d cannot have zero cylinders, heads, or sectors/track", channel, device)); } /* 打开磁盘映象文件 */ if ((BX_HD_THIS channels[channel].drives[device].hard_drive ->open(bx_options.atadevice[channel][device].Opath->getptr ())) < 0) { BX_PANIC(("ata%d-%d: could not open hard drive image file '%s'", channel, device, bx_options.atadevice[channel][device].Opath->getptr ())); } } else if (bx_options.atadevice[channel][device].Otype->get() == BX_ATA_DEVICE_CDROM) { BX_DEBUG(( "CDROM on target %d/%d",channel,device)); BX_HD_THIS channels[channel].drives[device].device_type = IDE_CDROM; BX_HD_THIS channels[channel].drives[device].cdrom.locked = 0; BX_HD_THIS channels[channel].drives[device].sense.sense_key = SENSE_NONE; BX_HD_THIS channels[channel].drives[device].sense.asc = 0; BX_HD_THIS channels[channel].drives[device].sense.ascq = 0; sprintf(sbtext, "CD:%d-%s", channel, device?"S":"M"); BX_HD_THIS channels[channel].drives[device].statusbar_id = bx_gui->register_statusitem(sbtext); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 181 // Check bit fields BX_CONTROLLER(channel,device).sector_count = 0; BX_CONTROLLER(channel,device).interrupt_reason.c_d = 1; if (BX_CONTROLLER(channel,device).sector_count != 0x01) BX_PANIC(("interrupt reason bit field error")); BX_CONTROLLER(channel,device).sector_count = 0; BX_CONTROLLER(channel,device).interrupt_reason.i_o = 1; if (BX_CONTROLLER(channel,device).sector_count != 0x02) BX_PANIC(("interrupt reason bit field error")); BX_CONTROLLER(channel,device).sector_count = 0; BX_CONTROLLER(channel,device).interrupt_reason.rel = 1; if (BX_CONTROLLER(channel,device).sector_count != 0x04) BX_PANIC(("interrupt reason bit field error")); BX_CONTROLLER(channel,device).sector_count = 0; BX_CONTROLLER(channel,device).interrupt_reason.tag = 3; if (BX_CONTROLLER(channel,device).sector_count != 0x18) BX_PANIC(("interrupt reason bit field error")); BX_CONTROLLER(channel,device).sector_count = 0; // allocate low level driver #ifdef LOWLEVEL_CDROM BX_HD_THIS channels[channel].drives[device].cdrom.cd = new LOWLEVEL_CDROM(bx_options.atadevice[channel][device].Opath->getptr ()); BX_INFO(("CD on ata%d-%d: '%s'",channel, device, bx_options.atadevice[channel][device].Opath->getptr ())); if (bx_options.atadevice[channel][device].Ostatus->get () == BX_INSERTED) { if (BX_HD_THIS channels[channel].drives[device].cdrom.cd->insert_cdrom()) { BX_INFO(( "Media present in CD-ROM drive")); BX_HD_THIS channels[channel].drives[device].cdrom.ready = 1; BX_HD_THIS channels[channel].drives[device].cdrom.capacity = BX_HD_THIS channels[channel].drives[device].cdrom.cd->capacity(); } else { BX_INFO(( "Could not locate CD-ROM, continuing with media not present")); BX_HD_THIS channels[channel].drives[device].cdrom.ready = 0; bx_options.atadevice[channel][device].Ostatus->set(BX_EJECTED); } } else { #endif BX_INFO(( "Media not present in CD-ROM drive" )); BX_HD_THIS channels[channel].drives[device].cdrom.ready = 0; #ifdef LOWLEVEL_CDROM } #endif } } } //将磁盘参数写入 CMOS,供软件以后使用 // generate CMOS values for hard drive if not using a CMOS image if (!bx_options.cmos.OcmosImage->get ()) { DEV_cmos_set_reg(0x12, 0x00); // start out with: no drive 0, no drive 1 if (BX_DRIVE_IS_HD(0,0)) { // Flag drive type as Fh, use extended CMOS location as real type DEV_cmos_set_reg(0x12, (DEV_cmos_get_reg(0x12) & 0x0f) | 0xf0); DEV_cmos_set_reg(0x19, 47); // user definable type // AMI BIOS: 1st hard disk #cyl low byte DEV_cmos_set_reg(0x1b, (bx_options.atadevice[0][0].Ocylinders->get () & 0x00ff)); // AMI BIOS: 1st hard disk #cyl high byte DEV_cmos_set_reg(0x1c, (bx_options.atadevice[0][0].Ocylinders->get () & 0xff00) >> 8); // AMI BIOS: 1st hard disk #heads DEV_cmos_set_reg(0x1d, (bx_options.atadevice[0][0].Oheads->get ())); // AMI BIOS: 1st hard disk write precompensation cylinder, low byte Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 182 DEV_cmos_set_reg(0x1e, 0xff); // -1 // AMI BIOS: 1st hard disk write precompensation cylinder, high byte DEV_cmos_set_reg(0x1f, 0xff); // -1 // AMI BIOS: 1st hard disk control byte DEV_cmos_set_reg(0x20, (0xc0 | ((bx_options.atadevice[0][0].Oheads->get () > 8) << 3))); // AMI BIOS: 1st hard disk landing zone, low byte DEV_cmos_set_reg(0x21, DEV_cmos_get_reg(0x1b)); // AMI BIOS: 1st hard disk landing zone, high byte DEV_cmos_set_reg(0x22, DEV_cmos_get_reg(0x1c)); // AMI BIOS: 1st hard disk sectors/track DEV_cmos_set_reg(0x23, bx_options.atadevice[0][0].Ospt->get ()); } //set up cmos for second hard drive if (BX_DRIVE_IS_HD(0,1)) { BX_DEBUG(("1: I will put 0xf into the second hard disk field")); // fill in lower 4 bits of 0x12 for second HD DEV_cmos_set_reg(0x12, (DEV_cmos_get_reg(0x12) & 0xf0) | 0x0f); DEV_cmos_set_reg(0x1a, 47); // user definable type // AMI BIOS: 2nd hard disk #cyl low byte DEV_cmos_set_reg(0x24, (bx_options.atadevice[0][1].Ocylinders->get () & 0x00ff)); // AMI BIOS: 2nd hard disk #cyl high byte DEV_cmos_set_reg(0x25, (bx_options.atadevice[0][1].Ocylinders->get () & 0xff00) >> 8); // AMI BIOS: 2nd hard disk #heads DEV_cmos_set_reg(0x26, (bx_options.atadevice[0][1].Oheads->get ())); // AMI BIOS: 2nd hard disk write precompensation cylinder, low byte DEV_cmos_set_reg(0x27, 0xff); // -1 // AMI BIOS: 2nd hard disk write precompensation cylinder, high byte DEV_cmos_set_reg(0x28, 0xff); // -1 // AMI BIOS: 2nd hard disk, 0x80 if heads>8 DEV_cmos_set_reg(0x29, (bx_options.atadevice[0][1].Oheads->get () > 8) ? 0x80 : 0x00); // AMI BIOS: 2nd hard disk landing zone, low byte DEV_cmos_set_reg(0x2a, DEV_cmos_get_reg(0x24)); // AMI BIOS: 2nd hard disk landing zone, high byte DEV_cmos_set_reg(0x2b, DEV_cmos_get_reg(0x25)); // AMI BIOS: 2nd hard disk sectors/track DEV_cmos_set_reg(0x2c, bx_options.atadevice[0][1].Ospt->get ()); } DEV_cmos_set_reg(0x39, 0); //写入 CMOS 参数 DEV_cmos_set_reg(0x3a, 0); for (channel=0; channelget()) { if (BX_DRIVE_IS_HD(channel,device)) { Bit16u cylinders = bx_options.atadevice[channel][device].Ocylinders->get(); Bit16u heads = bx_options.atadevice[channel][device].Oheads->get(); Bit16u spt = bx_options.atadevice[channel][device].Ospt->get(); Bit8u translation = bx_options.atadevice[channel][device].Otranslation->get(); Bit8u reg = 0x39 + channel/2; Bit8u bitshift = 2 * (device+(2 * (channel%2))); // Find the right translation if autodetect if (translation == BX_ATA_TRANSLATION_AUTO) { if((cylinders <= 1024) && (heads <= 16) && (spt <= 63)) { translation = BX_ATA_TRANSLATION_NONE; } else if (((Bit32u)cylinders * (Bit32u)heads) <= 131072) { translation = BX_ATA_TRANSLATION_LARGE; } else translation = BX_ATA_TRANSLATION_LBA; BX_INFO(("translation on ata%d-%d set to '%s'",channel, device, translation==BX_ATA_TRANSLATION_NONE?"none": Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 183 translation==BX_ATA_TRANSLATION_LARGE?"large": "lba")); } // FIXME we should test and warn // - if LBA and spt != 63 // - if RECHS and heads != 16 // - if NONE and size > 1024*16*SPT blocks // - if LARGE and size > 8192*16*SPT blocks // - if RECHS and size > 1024*240*SPT blocks // - if LBA and size > 1024*255*63, not that we can do much about it switch(translation) { case BX_ATA_TRANSLATION_NONE: DEV_cmos_set_reg(reg, DEV_cmos_get_reg(reg) | (0 << bitshift)); break; case BX_ATA_TRANSLATION_LBA: DEV_cmos_set_reg(reg, DEV_cmos_get_reg(reg) | (1 << bitshift)); break; case BX_ATA_TRANSLATION_LARGE: DEV_cmos_set_reg(reg, DEV_cmos_get_reg(reg) | (2 << bitshift)); break; case BX_ATA_TRANSLATION_RECHS: DEV_cmos_set_reg(reg, DEV_cmos_get_reg(reg) | (3 << bitshift)); break; } } } } } //设置 CMOS 启动参数 // Set the "non-extended" boot device. This will default to DISKC if cdrom if ( bx_options.Obootdrive[0]->get () != BX_BOOT_FLOPPYA) { // system boot sequence C:, A: DEV_cmos_set_reg(0x2d, DEV_cmos_get_reg(0x2d) & 0xdf); } else { // 'a' // system boot sequence A:, C: DEV_cmos_set_reg(0x2d, DEV_cmos_get_reg(0x2d) | 0x20); } // Set the "extended" boot sequence, bytes 0x38 and 0x3D (needed for cdrom booting) BX_INFO(("Using boot sequence %s, %s, %s", bx_options.Obootdrive[0]->get_choice(bx_options.Obootdrive[0]->get () - 1), bx_options.Obootdrive[1]->get_choice(bx_options.Obootdrive[1]->get ()), bx_options.Obootdrive[2]->get_choice(bx_options.Obootdrive[2]->get ()) )); DEV_cmos_set_reg(0x3d, bx_options.Obootdrive[0]->get () | (bx_options.Obootdrive[1]->get () << 4)); // Set the signature check flag in cmos, inverted for compatibility DEV_cmos_set_reg(0x38, bx_options.OfloppySigCheck->get() | (bx_options.Obootdrive[2]->get () << 4)); BX_INFO(("Floppy boot signature check is %sabled", bx_options.OfloppySigCheck->get() ? "dis" : "en")); } // register timer for HD/CD i/o light //注册 IDE 设备指示灯 if (BX_HD_THIS iolight_timer_index == BX_NULL_TIMER_HANDLE) { BX_HD_THIS iolight_timer_index = DEV_register_timer(this, iolight_timer_handler, 100000, 0,0, "HD/CD i/o light"); } } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 184 6.4.5 ATA 控制器的寄存器读写 ATA 控制器的寄存器列表 地址 名称 属性 1F0 Data register (Read and Write) 1F1 Error Register (Read) 1F1 Features Register (Write) 1F2 Sector Count Register (Read and Write) 1F3 LBA Low Register (Read and Write) 1F4 LBA Mid Register (Read and Write) 1F5 LBA High Register (Read and Write) 1F6 Drive/Head Register (Read and Write) 1F7 Status Register (Read) 1F7 Command Register (Write) 3F6 * Alternate Status Register (Read) 3F6 * Device Control Register (Write) [*]read()在处理这个地址时,为 port 加上了偏移量 0x10,因此 port 的值为 0x16 有用的宏定义 #define BX_SELECTED_CONTROLLER(c) (BX_CONTROLLER((c),BX_HD_THIS channels[(c)].drive_select)) #define BX_SELECTED_DRIVE(c) (BX_DRIVE((c),BX_HD_THIS channels[(c)].drive_select)) #define BX_MASTER_SELECTED(c) (!BX_HD_THIS channels[(c)].drive_select) #define BX_SLAVE_SELECTED(c) (BX_HD_THIS channels[(c)].drive_select) #define BX_SELECTED_IS_PRESENT(c) (BX_DRIVE_IS_PRESENT((c),BX_SLAVE_SELECTED((c)))) #define BX_SELECTED_IS_HD(c) (BX_DRIVE_IS_HD((c),BX_SLAVE_SELECTED((c)))) #define BX_SELECTED_IS_CD(c) (BX_DRIVE_IS_CD((c),BX_SLAVE_SELECTED((c)))) #define BX_SELECTED_MODEL(c) (BX_HD_THIS channels[(c)].drives[BX_HD_THIS channels[(c)].drive_select].model_no) #define BX_SELECTED_TYPE_STRING(channel) ((BX_SELECTED_IS_CD(channel)) ? "CD-ROM" : "DISK") =============================start of code===================================== Bit32u bx_hard_drive_c::read(Bit32u address, unsigned io_len) { Bit8u value8; Bit16u value16; Bit32u value32; Bit8u channel = BX_MAX_ATA_CHANNEL; Bit32u port = 0xff; // undefined 访问端口 for (channel=0; channel 0x03f7)) { BX_PANIC(("read: unable to find ATA channel, ioport=0x%04x", address)); } else { channel = 0; port = address - 0x03e0; } } switch (port) { case 0x00: // hard disk data (16bit) 0x1f0 if (BX_SELECTED_CONTROLLER(channel).status.drq == 0) { BX_ERROR(("IO read(0x%04x) with drq == 0: last command was %02xh", address, (unsigned) BX_SELECTED_CONTROLLER(channel).current_command)); return(0); } BX_DEBUG(("IO read(0x%04x): current command is %02xh", address, (unsigned) BX_SELECTED_CONTROLLER(channel).current_command)); switch (BX_SELECTED_CONTROLLER(channel).current_command) { case 0x20: // READ SECTORS, with retries case 0x21: // READ SECTORS, without retries if (BX_SELECTED_CONTROLLER(channel).buffer_index >= 512) BX_PANIC(("IO read(0x%04x): buffer_index >= 512", address)); #if BX_SupportRepeatSpeedups //yes if (DEV_bulk_io_quantum_requested()) { unsigned transferLen, quantumsMax; quantumsMax = (512 - BX_SELECTED_CONTROLLER(channel).buffer_index) / io_len; if ( quantumsMax == 0) BX_PANIC(("IO read(0x%04x): not enough space for read", address)); DEV_bulk_io_quantum_transferred() = DEV_bulk_io_quantum_requested(); if (quantumsMax < DEV_bulk_io_quantum_transferred()) DEV_bulk_io_quantum_transferred() = quantumsMax; transferLen = io_len * DEV_bulk_io_quantum_transferred(); memcpy((Bit8u*) DEV_bulk_io_host_addr(), &BX_SELECTED_CONTROLLER(channel).buffer[BX_SELECTED_CONTROLLER( channel).buffer_index], transferLen); DEV_bulk_io_host_addr() += transferLen; BX_SELECTED_CONTROLLER(channel).buffer_index += transferLen; value32 = 0; // Value returned not important; } else #endif { value32 = 0L; switch(io_len){ case 4: value32 |= (BX_SELECTED_CONTROLLER(channel).buffer[BX_SELECTED_CONTROLLER(channel).buffer_index+3] << 24); value32 |= (BX_SELECTED_CONTROLLER(channel).buffer[BX_SELECTED_CONTROLLER(channel).buffer_index+2] << 16); case 2: value32|= Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 186 (BX_SELECTED_CONTROLLER(channel).buffer[BX_SELECTED_CONTROLLER(channel).buffer_index+1] << 8); value32 |= B X_SELECTED_CONTROLLER(channel).buffer[BX_SELECTED_CONTROLLER(channel).buffer_index]; } BX_SELECTED_CONTROLLER(channel).buffer_index += io_len; } // if buffer completely read if (BX_SELECTED_CONTROLLER(channel).buffer_index >= 512) { // update sector count, sector number, cylinder, // drive, head, status // if there are more sectors, read next one in... // BX_SELECTED_CONTROLLER(channel).buffer_index = 0; increment_address(channel); BX_SELECTED_CONTROLLER(channel).status.busy = 0; BX_SELECTED_CONTROLLER(channel).status.drive_ready = 1; BX_SELECTED_CONTROLLER(channel).status.write_fault = 0; if (bx_options.OnewHardDriveSupport->get ()) BX_SELECTED_CONTROLLER(channel).status.seek_complete = 1; else BX_SELECTED_CONTROLLER(channel).status.seek_complete = 0; BX_SELECTED_CONTROLLER(channel).status.corrected_data = 0; BX_SELECTED_CONTROLLER(channel).status.err = 0; if (BX_SELECTED_CONTROLLER(channel).sector_count==0) { BX_SELECTED_CONTROLLER(channel).status.drq = 0; } else { /* read next one into controller buffer */ off_t logical_sector; off_t ret; BX_SELECTED_CONTROLLER(channel).status.drq = 1; BX_SELECTED_CONTROLLER(channel).status.seek_complete = 1; if (!calculate_logical_address(channel, &logical_sector)) { BX_ERROR(("multi-sector read reached invalid sector %lu, aborting", (unsigned long)logical_sector)); command_aborted (channel, BX_SELECTED_CONTROLLER(channel).current_command); GOTO_RETURN_VALUE ; } ret = BX_SELECTED_DRIVE(channel).hard_drive->lseek(logical_sector * 512, SEEK_SET); if (ret < 0) { BX_ERROR(("could not lseek() hard drive image file")); command_aborted (channel, BX_SELECTED_CONTROLLER(channel).current_command); GOTO_RETURN_VALUE ; } /* set status bar conditions for device */ if (!BX_SELECTED_DRIVE(channel).iolight_counter) bx_gui->statusbar_setitem(BX_SELECTED_DRIVE(channel).statusbar_id, 1); BX_SELECTED_DRIVE(channel).iolight_counter = 5; bx_pc_system.activate_timer( BX_HD_THIS iolight_timer_index, 100000, 0 ); ret = BX_SELECTED_DRIVE(channel).hard_drive->read((bx_ptr_t) BX_SELECTED_CONTROLLER(channel).buffer, 512); if (ret < 512) { BX_ERROR(("logical sector was %lu", (unsigned long)logical_sector)); BX_ERROR(("could not read() hard drive image file at byte %lu", (unsigned long)logical_sector*512)); command_aborted (channel, BX_SELECTED_CONTROLLER(channel).current_command); GOTO_RETURN_VALUE ; } BX_SELECTED_CONTROLLER(channel).buffer_index = 0; raise_interrupt(channel); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 187 } } GOTO_RETURN_VALUE ; break; case 0xec: // IDENTIFY DEVICE case 0xa1: if (bx_options.OnewHardDriveSupport->get ()) { unsigned index; BX_SELECTED_CONTROLLER(channel).status.busy = 0; BX_SELECTED_CONTROLLER(channel).status.drive_ready = 1; BX_SELECTED_CONTROLLER(channel).status.write_fault = 0; BX_SELECTED_CONTROLLER(channel).status.seek_complete = 1; BX_SELECTED_CONTROLLER(channel).status.corrected_data = 0; BX_SELECTED_CONTROLLER(channel).status.err = 0; index = BX_SELECTED_CONTROLLER(channel).buffer_index; value32 = BX_SELECTED_CONTROLLER(channel).buffer[index]; index++; if (io_len >= 2) { value32 |= (BX_SELECTED_CONTROLLER(channel).buffer[index] << 8); index++; } if (io_len == 4) { value32 |= (BX_SELECTED_CONTROLLER(channel).buffer[index] << 16); value32 |= (BX_SELECTED_CONTROLLER(channel).buffer[index+1] << 24); index += 2; } BX_SELECTED_CONTROLLER(channel).buffer_index = index; if (BX_SELECTED_CONTROLLER(channel).buffer_index >= 512) { BX_SELECTED_CONTROLLER(channel).status.drq = 0; if (bx_dbg.disk || (BX_SELECTED_IS_CD(channel) && bx_dbg.cdrom)) BX_INFO(("Read all drive ID Bytes ...")); } GOTO_RETURN_VALUE; } else BX_PANIC(("IO read(0x%04x): current command is %02xh", address, (unsigned) BX_SELECTED_CONTROLLER(channel).current_command)); case 0xa0: { unsigned index = BX_SELECTED_CONTROLLER(channel).buffer_index; unsigned increment = 0; // Load block if necessary if (index >= 2048) { if (index > 2048) BX_PANIC(("index > 2048 : 0x%x",index)); switch (BX_SELECTED_DRIVE(channel).atapi.command) { case 0x28: // read (10) case 0xa8: // read (12) #ifdef LOWLEVEL_CDROM if (!BX_SELECTED_DRIVE(channel).cdrom.ready) { BX_PANIC(("Read with CDROM not ready")); } /* set status bar conditions for device */ if (!BX_SELECTED_DRIVE(channel).iolight_counter) bx_gui->statusbar_setitem(BX_SELECTED_DRIVE(channel).statusbar_id, 1); BX_SELECTED_DRIVE(channel).iolight_counter = 5; bx_pc_system.activate_timer( BX_HD_THIS iolight_timer_index, 100000, 0 ); BX_SELECTED_DRIVE(channel).cdrom.cd->read_block(BX_SELECTED_CONTROLLER(channel).bu ffer, Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 188 BX_SELECTED_DRIVE(channel).cdrom.next_lba); BX_SELECTED_DRIVE(channel).cdrom.next_lba++; BX_SELECTED_DRIVE(channel).cdrom.remaining_blocks--; if (bx_dbg.disk || (BX_SELECTED_IS_CD(channel) && bx_dbg.cdrom)) if (!BX_SELECTED_DRIVE(channel).cdrom.remaining_blocks) BX_INFO(("Last READ block loaded {CDROM}")); else BX_INFO(("READ block loaded (%d remaining) {CDROM}", BX_SELECTED_DRIVE(channel).cdrom.remaining_blocks)); // one block transfered, start at beginning index = 0; #else BX_PANIC(("Read with no LOWLEVEL_CDROM")); #endif break; default: // no need to load a new block break; } } value32 = BX_SELECTED_CONTROLLER(channel).buffer[index+increment]; increment++; if (io_len >= 2) { value32 |= (BX_SELECTED_CONTROLLER(channel).buffer[index+increment] << 8); increment++; } if (io_len == 4) { value32 |= (BX_SELECTED_CONTROLLER(channel).buffer[index+increment] << 16); value32 |= (BX_SELECTED_CONTROLLER(channel).buffer[index+increment+1] << 24); increment += 2; } BX_SELECTED_CONTROLLER(channel).buffer_index = index + increment; BX_SELECTED_CONTROLLER(channel).drq_index += increment; if (BX_SELECTED_CONTROLLER(channel).drq_index >= (unsigned)BX_SELECTED_DRIVE(channel).atapi.drq_bytes) { BX_SELECTED_CONTROLLER(channel).status.drq = 0; BX_SELECTED_CONTROLLER(channel).drq_index = 0; BX_SELECTED_DRIVE(channel).atapi.total_bytes_remaining -= BX_SELECTED_DRIVE(channel).atapi.drq_bytes; if (BX_SELECTED_DRIVE(channel).atapi.total_bytes_remaining > 0) { // one or more blocks remaining (works only for single block commands) if (bx_dbg.disk || (BX_SELECTED_IS_CD(channel) && bx_dbg.cdrom)) BX_INFO(("PACKET drq bytes read")); BX_SELECTED_CONTROLLER(channel).interrupt_reason.i_o = 1; BX_SELECTED_CONTROLLER(channel).status.busy = 0; BX_SELECTED_CONTROLLER(channel).status.drq = 1; BX_SELECTED_CONTROLLER(channel).interrupt_reason.c_d = 0; // set new byte count if last block if (BX_SELECTED_DRIVE(channel).atapi.total_bytes_remaining < BX_SELECTED_CONTROLLER(channel).byte_count) { BX_SELECTED_CONTROLLER(channel).byte_count = BX_SELECTED_DRIVE(channel).atapi.total_bytes_remaining; } BX_SELECTED_DRIVE(channel).atapi.drq_bytes = BX_SELECTED_CONTROLLER(channel).byte_count; raise_interrupt(channel); } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 189 else { // all bytes read if (bx_dbg.disk || (BX_SELECTED_IS_CD(channel) && bx_dbg.cdrom)) BX_INFO(("PACKET all bytes read")); BX_SELECTED_CONTROLLER(channel).interrupt_reason.i_o = 1; BX_SELECTED_CONTROLLER(channel).interrupt_reason.c_d = 1; BX_SELECTED_CONTROLLER(channel).status.drive_ready = 1; BX_SELECTED_CONTROLLER(channel).interrupt_reason.rel = 0; BX_SELECTED_CONTROLLER(channel).status.busy = 0; BX_SELECTED_CONTROLLER(channel).status.drq = 0; BX_SELECTED_CONTROLLER(channel).status.err = 0; raise_interrupt(channel); } } GOTO_RETURN_VALUE; break; } //以下是一些 ATA 规范中定义的命令,但 Bochs 当前未支持 case 0x08: BX_ERROR(("read cmd 0x08 (DEVICE RESET) not supported")); command_aborted(channel, 0x08); break; case 0x10: BX_ERROR(("read cmd 0x10 (RECALIBRATE) not supported")); command_aborted(channel, 0x10); break; case 0x22: BX_ERROR(("read cmd 0x22 (READ LONG) not supported")); command_aborted(channel, 0x22); break; case 0x23: BX_ERROR(("read cmd 0x23 (READ LONG NO RETRY) not supported")); command_aborted(channel, 0x23); break; case 0x24: BX_ERROR(("read cmd 0x24 (READ SECTORS EXT) not supported")); command_aborted(channel, 0x24); break; case 0x25: BX_ERROR(("read cmd 0x25 (READ DMA EXT) not supported")); command_aborted(channel, 0x25); break; case 0x26: BX_ERROR(("read cmd 0x26 (READ DMA QUEUED EXT) not supported")); command_aborted(channel, 0x26); break; case 0x27: BX_ERROR(("read cmd 0x27 (READ NATIVE MAX ADDRESS EXT) not supported")); command_aborted(channel, 0x27); break; case 0x29: BX_ERROR(("read cmd 0x29 (READ MULTIPLE EXT) not supported")); command_aborted(channel, 0x29); break; case 0x2A: BX_ERROR(("read cmd 0x2A (READ STREAM DMA) not supported")); command_aborted(channel, 0x2A); break; case 0x2B: BX_ERROR(("read cmd 0x2B (READ STREAM PIO) not supported")); command_aborted(channel, 0x2B); break; case 0x2F: BX_ERROR(("read cmd 0x2F (READ LOG EXT) not supported")); command_aborted(channel, 0x2F); break; case 0x30: BX_ERROR(("read cmd 0x30 (WRITE SECTORS) not supported")); command_aborted(channel, 0x30); break; case 0x31: BX_ERROR(("read cmd 0x31 (WRITE SECTORS NO RETRY) not supported")); command_aborted(channel, 0x31); break; case 0x32: BX_ERROR(("read cmd 0x32 (WRITE LONG) not supported")); command_aborted(channel, 0x32); break; case 0x33: BX_ERROR(("read cmd 0x33 (WRITE LONG NO RETRY) not supported")); command_aborted(channel, 0x33); break; case 0x34: BX_ERROR(("read cmd 0x34 (WRITE SECTORS EXT) not supported")); command_aborted(channel, 0x34); break; case 0x35: BX_ERROR(("read cmd 0x35 (WRITE DMA EXT) not supported")); command_aborted(channel, 0x35); break; case 0x36: BX_ERROR(("read cmd 0x36 (WRITE DMA QUEUED EXT) not supported")); command_aborted(channel, 0x36); break; case 0x37: BX_ERROR(("read cmd 0x37 (SET MAX ADDRESS EXT) not supported")); command_aborted(channel, 0x37); break; case 0x38: BX_ERROR(("read cmd 0x38 (CFA WRITE SECTORS W/OUT ERASE) not supported")); command_aborted(channel, 0x38); break; case 0x39: BX_ERROR(("read cmd 0x39 (WRITE MULTIPLE EXT) not supported")); command_aborted(channel, 0x39); break; case 0x3A: BX_ERROR(("read cmd 0x3A (WRITE STREAM DMA) not supported")); command_aborted(channel, 0x3A); break; case 0x3B: BX_ERROR(("read cmd 0x3B (WRITE STREAM PIO) not supported")); command_aborted(channel, 0x3B); break; case 0x3F: BX_ERROR(("read cmd 0x3F (WRITE LOG EXT) not supported")); command_aborted(channel, 0x3F); break; case 0x40: BX_ERROR(("read cmd 0x40 (READ VERIFY SECTORS) not supported")); command_aborted(channel, Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 190 0x40); break; case 0x41: BX_ERROR(("read cmd 0x41 (READ VERIFY SECTORS NO RETRY) not supported")); command_aborted(channel, 0x41); break; case 0x42: BX_ERROR(("read cmd 0x42 (READ VERIFY SECTORS EXT) not supported")); command_aborted(channel, 0x42); break; case 0x50: BX_ERROR(("read cmd 0x50 (FORMAT TRACK) not supported")); command_aborted(channel, 0x50); break; case 0x51: BX_ERROR(("read cmd 0x51 (CONFIGURE STREAM) not supported")); command_aborted(channel, 0x51); break; case 0x70: BX_ERROR(("read cmd 0x70 (SEEK) not supported")); command_aborted(channel, 0x70); break; case 0x87: BX_ERROR(("read cmd 0x87 (CFA TRANSLATE SECTOR) not supported")); command_aborted(channel, 0x87); break; case 0x90: BX_ERROR(("read cmd 0x90 (EXECUTE DEVICE DIAGNOSTIC) not supported")); command_aborted(channel, 0x90); break; case 0x91: BX_ERROR(("read cmd 0x91 (INITIALIZE DEVICE PARAMETERS) not supported")); command_aborted(channel, 0x91); break; case 0x92: BX_ERROR(("read cmd 0x92 (DOWNLOAD MICROCODE) not supported")); command_aborted(channel, 0x92); break; case 0x94: BX_ERROR(("read cmd 0x94 (STANDBY IMMEDIATE) not supported")); command_aborted(channel, 0x94); break; case 0x95: BX_ERROR(("read cmd 0x95 (IDLE IMMEDIATE) not supported")); command_aborted(channel, 0x95); break; case 0x96: BX_ERROR(("read cmd 0x96 (STANDBY) not supported")); command_aborted(channel, 0x96); break; case 0x97: BX_ERROR(("read cmd 0x97 (IDLE) not supported")); command_aborted(channel, 0x97); break; case 0x98: BX_ERROR(("read cmd 0x98 (CHECK POWER MODE) not supported")); command_aborted(channel, 0x98); break; case 0x99: BX_ERROR(("read cmd 0x99 (SLEEP) not supported")); command_aborted(channel, 0x99); break; case 0xA2: BX_ERROR(("read cmd 0xA2 (SERVICE) not supported")); command_aborted(channel, 0xA2); break; case 0xB0: BX_ERROR(("read cmd 0xB0 (SMART DISABLE OPERATIONS) not supported")); command_aborted(channel, 0xB0); break; case 0xB1: BX_ERROR(("read cmd 0xB1 (DEVICE CONFIGURATION FREEZE LOCK) not supported")); command_aborted(channel, 0xB1); break; case 0xC0: BX_ERROR(("read cmd 0xC0 (CFA ERASE SECTORS) not supported")); command_aborted(channel, 0xC0); break; case 0xC4: BX_ERROR(("read cmd 0xC4 (READ MULTIPLE) not supported")); command_aborted(channel, 0xC4); break; case 0xC5: BX_ERROR(("read cmd 0xC5 (WRITE MULTIPLE) not supported")); command_aborted(channel, 0xC5); break; case 0xC6: BX_ERROR(("read cmd 0xC6 (SET MULTIPLE MODE) not supported")); command_aborted(channel, 0xC6); break; case 0xC7: BX_ERROR(("read cmd 0xC7 (READ DMA QUEUED) not supported")); command_aborted(channel, 0xC7); break; case 0xC8: BX_ERROR(("read cmd 0xC8 (READ DMA) not supported")); command_aborted(channel, 0xC8); break; case 0xC9: BX_ERROR(("read cmd 0xC9 (READ DMA NO RETRY) not supported")); command_aborted(channel, 0xC9); break; case 0xCA: BX_ERROR(("read cmd 0xCA (WRITE DMA) not supported")); command_aborted(channel, 0xCA); break; case 0xCC: BX_ERROR(("read cmd 0xCC (WRITE DMA QUEUED) not supported")); command_aborted(channel, 0xCC); break; case 0xCD: BX_ERROR(("read cmd 0xCD (CFA WRITE MULTIPLE W/OUT ERASE) not supported")); command_aborted(channel, 0xCD); break; case 0xD1: BX_ERROR(("read cmd 0xD1 (CHECK MEDIA CARD TYPE) not supported")); command_aborted(channel, 0xD1); break; case 0xDA: BX_ERROR(("read cmd 0xDA (GET MEDIA STATUS) not supported")); command_aborted(channel, 0xDA); break; case 0xDE: BX_ERROR(("read cmd 0xDE (MEDIA LOCK) not supported")); command_aborted(channel, 0xDE); break; case 0xDF: BX_ERROR(("read cmd 0xDF (MEDIA UNLOCK) not supported")); command_aborted(channel, 0xDF); break; case 0xE0: BX_ERROR(("read cmd 0xE0 (STANDBY IMMEDIATE) not supported")); command_aborted(channel, 0xE0); break; case 0xE1: BX_ERROR(("read cmd 0xE1 (IDLE IMMEDIATE) not supported")); command_aborted(channel, 0xE1); break; case 0xE2: BX_ERROR(("read cmd 0xE2 (STANDBY) not supported")); command_aborted(channel, 0xE2); break; case 0xE3: BX_ERROR(("read cmd 0xE3 (IDLE) not supported")); command_aborted(channel, 0xE3); break; case 0xE4: BX_ERROR(("read cmd 0xE4 (READ BUFFER) not supported")); command_aborted(channel, 0xE4); break; case 0xE5: BX_ERROR(("read cmd 0xE5 (CHECK POWER MODE) not supported")); command_aborted(channel, 0xE5); break; case 0xE6: BX_ERROR(("read cmd 0xE6 (SLEEP) not supported")); command_aborted(channel, 0xE6); break; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 191 case 0xE7: BX_ERROR(("read cmd 0xE7 (FLUSH CACHE) not supported")); command_aborted(channel, 0xE7); break; case 0xE8: BX_ERROR(("read cmd 0xE8 (WRITE BUFFER) not supported")); command_aborted(channel, 0xE8); break; case 0xEA: BX_ERROR(("read cmd 0xEA (FLUSH CACHE EXT) not supported")); command_aborted(channel, 0xEA); break; case 0xED: BX_ERROR(("read cmd 0xED (MEDIA EJECT) not supported")); command_aborted(channel, 0xED); break; case 0xEF: BX_ERROR(("read cmd 0xEF (SET FEATURES) not supported")); command_aborted(channel, 0xEF); break; case 0xF1: BX_ERROR(("read cmd 0xF1 (SECURITY SET PASSWORD) not supported")); command_aborted(channel, 0xF1); break; case 0xF2: BX_ERROR(("read cmd 0xF2 (SECURITY UNLOCK) not supported")); command_aborted(channel, 0xF2); break; case 0xF3: BX_ERROR(("read cmd 0xF3 (SECURITY ERASE PREPARE) not supported")); command_aborted(channel, 0xF3); break; case 0xF4: BX_ERROR(("read cmd 0xF4 (SECURITY ERASE UNIT) not supported")); command_aborted(channel, 0xF4); break; case 0xF5: BX_ERROR(("read cmd 0xF5 (SECURITY FREEZE LOCK) not supported")); command_aborted(channel, 0xF5); break; case 0xF6: BX_ERROR(("read cmd 0xF6 (SECURITY DISABLE PASSWORD) not supported")); command_aborted(channel, 0xF6); break; case 0xF8: BX_ERROR(("read cmd 0xF8 (READ NATIVE MAX ADDRESS) not supported")); command_aborted(channel, 0xF8); break; case 0xF9: BX_ERROR(("read cmd 0xF9 (SET MAX ADDRESS) not supported")); command_aborted(channel, 0xF9); break; default: BX_PANIC(("IO read(0x%04x): current command is %02xh", address, (unsigned) BX_SELECTED_CONTROLLER(channel).current_command)); } break; //-------------------------------------------------读普通寄存器--------------------------------------------------------------- case 0x01: // hard disk error register 0x1f1 BX_SELECTED_CONTROLLER(channel).status.err = 0; value8 = (!BX_SELECTED_IS_PRESENT(channel)) ? 0 : BX_SELECTED_CONTROLLER(channel).error_register; goto return_value8; break; case 0x02: // hard disk sector count / interrupt reason 0x1f2 value8 = (!BX_SELECTED_IS_PRESENT(channel)) ? 0 : BX_SELECTED_CONTROLLER(channel).sector_count; goto return_value8; break; case 0x03: // sector number 0x1f3 value8 = (!BX_SELECTED_IS_PRESENT(channel)) ? 0 : BX_SELECTED_CONTROLLER(channel).sector_no; goto return_value8; case 0x04: // cylinder low 0x1f4 // -- WARNING : On real hardware the controller registers are shared between drives. // So we must respond even if the select device is not present. Some OS uses this fact // to detect the disks.... minix2 for example value8 = (!BX_ANY_IS_PRESENT(channel)) ? 0 : (BX_SELECTED_CONTROLLER(channel).cylinder_no & 0x00ff); goto return_value8; case 0x05: // cylinder high 0x1f5 // -- WARNING : On real hardware the controller registers are shared between drives. // So we must respond even if the select device is not present. Some OS uses this fact // to detect the disks.... minix2 for example value8 = (!BX_ANY_IS_PRESENT(channel)) ? 0 : BX_SELECTED_CONTROLLER(channel).cylinder_no >> 8; goto return_value8; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 192 case 0x06: // hard disk drive and head register 0x1f6 // b7 Extended data field for ECC // b6/b5: Used to be sector size. 00=256,01=512,10=1024,11=128 // Since 512 was always used, bit 6 was taken to mean LBA mode: // b6 1=LBA mode, 0=CHS mode // b5 1 // b4: DRV // b3..0 HD3..HD0 value8 = (1 << 7) | ((BX_SELECTED_CONTROLLER(channel).lba_mode>0) << 6) | (1 << 5) | // 01b = 512 sector size (BX_HD_THIS channels[channel].drive_select << 4) | (BX_SELECTED_CONTROLLER(channel).head_no << 0); goto return_value8; break; //BX_CONTROLLER(channel,0).lba_mode case 0x07: // Hard Disk Status 0x1f7 case 0x16: // Hard Disk Alternate Status 0x3f6 if (!BX_ANY_IS_PRESENT(channel)) { // (mch) Just return zero for these registers value8 = 0; } else { value8 = ( (BX_SELECTED_CONTROLLER(channel).status.busy << 7) | (BX_SELECTED_CONTROLLER(channel).status.drive_ready << 6) | (BX_SELECTED_CONTROLLER(channel).status.write_fault << 5) | (BX_SELECTED_CONTROLLER(channel).status.seek_complete << 4) | (BX_SELECTED_CONTROLLER(channel).status.drq << 3) | (BX_SELECTED_CONTROLLER(channel).status.corrected_data << 2) | (BX_SELECTED_CONTROLLER(channel).status.index_pulse << 1) | (BX_SELECTED_CONTROLLER(channel).status.err) ); BX_SELECTED_CONTROLLER(channel).status.index_pulse_count++; BX_SELECTED_CONTROLLER(channel).status.index_pulse = 0; if (BX_SELECTED_CONTROLLER(channel).status.index_pulse_count >= INDEX_PULSE_CYCLE) { BX_SELECTED_CONTROLLER(channel).status.index_pulse = 1; BX_SELECTED_CONTROLLER(channel).status.index_pulse_count = 0; } } if (port == 0x07) { DEV_pic_lower_irq(BX_HD_THIS channels[channel].irq); } goto return_value8; break; case 0x17: // Hard Disk Address Register 0x3f7 // Obsolete and unsupported register. Not driven by hard // disk controller. Report all 1's. If floppy controller // is handling this address, it will call this function // set/clear D7 (the only bit it handles), then return // the combined value value8 = 0xff; goto return_value8; break; default: BX_PANIC(("hard drive: io read to address %x unsupported", (unsigned) address)); } BX_PANIC(("hard drive: shouldnt get here!")); return(0); return_value32: BX_DEBUG(("32-bit read from %04x = %08x {%s}", Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 193 (unsigned) address, value32, BX_SELECTED_TYPE_STRING(channel))); return value32; return_value16: BX_DEBUG(("16-bit read from %04x = %04x {%s}", (unsigned) address, value16, BX_SELECTED_TYPE_STRING(channel))); return value16; return_value8: BX_DEBUG(("8-bit read from %04x = %02x {%s}", (unsigned) address, value8, BX_SELECTED_TYPE_STRING(channel))); return value8; } bx_hard_drive_c::write(Bit32u address, Bit32u value, unsigned io_len) { //省略 } 6.4.6 CD ROM 设备 Bochs 的 CD ROM 设备由 cdrom_interface 类描述 主要成员变量 int fd char *path; CD ROM 盘符名称或映象文件的路径 主要成员函数 insert_cdrom() 插入 CD 光盘 eject_cdrom() 弹出 CD 光盘 read_toc() 读取 TOC capacity() 读取容量 read_block() 读取数据块 这些函数在 harddrv.cpp 中被调用 =============================================================================== 函数 insert_cdrom() =============================================================================== bx_bool cdrom_interface::insert_cdrom(char *dev) { unsigned char buffer[BX_CD_FRAMESIZE]; //2048 ssize_t ret; // Load CD-ROM. Returns false if CD is not ready. if (dev != NULL) path = strdup(dev); BX_INFO (("load cdrom with path=%s", path)); #ifdef WIN32 char drive[256]; if ( (path[1] == ':') && (strlen(path) == 2) ) //如果是类似 ‘E:’得名字,则访问 CD 驱动器 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 194 { if(osinfo.dwPlatformId == VER_PLATFORM_WIN32_NT) { // Use direct device access under windows NT/2k/XP // With all the backslashes it's hard to see, but to open D: drive // the name would be: \\.\d: sprintf(drive, "\\\\.\\%s", path); BX_INFO (("Using direct access for cdrom.")); // This trick only works for Win2k and WinNT, so warn the user of that. } else { BX_INFO(("Using ASPI for cdrom. Drive letters are unused yet.")); bUseASPI = TRUE; } } Else //否则是存在磁盘上得 CDROM 映象文件 { strcpy(drive,path); using_file = 1; BX_INFO (("Opening image file as a cd")); } if(bUseASPI) { DWORD d; UINT cdr, cnt, max; UINT i, j, k; SRB_HAInquiry sh; SRB_GDEVBlock sd; if (!hASPI) { hASPI = LoadLibrary("WNASPI32.DLL"); if (hASPI) { //加载 ASPI 驱动 SendASPI32Command = (DWORD(*)(LPSRB))GetProcAddress( hASPI, "SendASPI32Command" ); GetASPI32DLLVersion = (DWORD(*)(void))GetProcAddress( hASPI, "GetASPI32DLLVersion" ); GetASPI32SupportInfo = (DWORD(*)(void))GetProcAddress( hASPI, "GetASPI32SupportInfo" ); d = GetASPI32DLLVersion(); BX_INFO(("WNASPI32.DLL version %d.%02d initialized", d & 0xff, (d >> 8) & 0xff)); } else { BX_PANIC(("Could not load ASPI drivers, so cdrom access will fail")); return false; } } cdr = 0; bHaveDev = FALSE; d = GetASPI32SupportInfo(); cnt = LOBYTE(LOWORD(d)); for(i = 0; i < cnt; i++) { memset(&sh, 0, sizeof(sh)); sh.SRB_Cmd = SC_HA_INQUIRY; sh.SRB_HaId = i; SendASPI32Command((LPSRB)&sh); if(sh.SRB_Status != SS_COMP) continue; max = (int)sh.HA_Unique[3]; for(j = 0; j < max; j++) { for(k = 0; k < 8; k++) { memset(&sd, 0, sizeof(sd)); sd.SRB_Cmd = SC_GET_DEV_TYPE; sd.SRB_HaId = i; sd.SRB_Target = j; sd.SRB_Lun = k; SendASPI32Command((LPSRB)&sd); if(sd.SRB_Status == SS_COMP) { if(sd.SRB_DeviceType == DTYPE_CDROM) { cdr++; if(cdr > cdromCount) { hid = i; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 195 tid = j; lun = k; cdromCount++; bHaveDev = TRUE; } } } if(bHaveDev) break; } if(bHaveDev) break; } } fd=1; } else { //通过调用 WIN32 API 函数将 CDROM 驱动器映射成文件,后面用 ReadFile() 访问 hFile=CreateFile((char *)&drive, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL); if (hFile !=(void *)0xFFFFFFFF) fd=1; if (!using_file) { DWORD lpBytesReturned; DeviceIoControl(hFile, IOCTL_STORAGE_LOAD_MEDIA, NULL, 0, NULL, 0, &lpBytesReturned, NULL); } } #elif defined(__APPLE__) if(strcmp(path, "drive") == 0) { mach_port_t masterPort = NULL; io_iterator_t mediaIterator; kern_return_t kernResult; BX_INFO(( "Insert CDROM" )); kernResult = FindEjectableCDMedia( &mediaIterator, &masterPort ); if ( kernResult != KERN_SUCCESS ) { BX_INFO (("Unable to find CDROM")); return false; } kernResult = GetDeviceFilePath( mediaIterator, CDDevicePath, sizeof( CDDevicePath ) ); if ( kernResult != KERN_SUCCESS ) { BX_INFO (("Unable to get CDROM device file path" )); return false; } // Here a cdrom was found so see if we can read from it. // At this point a failure will result in panic. if ( strlen( CDDevicePath ) ) { fd = open(CDDevicePath, O_RDONLY); } } else { fd = open(path, O_RDONLY); } #else // all platforms except win32 fd = open(path, O_RDONLY); #endif if (fd < 0) { BX_ERROR(( "open cd failed for %s: %s", path, strerror(errno))); return(false); } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 196 // I just see if I can read a sector to verify that a // CD is in the drive and readable. 通过对尝试对 CD 的读取才判断 CD 是否存在 #ifdef WIN32 if(bUseASPI) { return ReadCDSector(hid, tid, lun, 0, buffer, BX_CD_FRAMESIZE); } else { if (!ReadFile(hFile, (void *) buffer, BX_CD_FRAMESIZE, (unsigned long *) &ret, NULL)) { CloseHandle(hFile); fd = -1; BX_DEBUG(( "insert_cdrom: read returns error." )); return(false); } } #else // do fstat to determine if it's a file or a device, then set using_file. struct stat stat_buf; ret = fstat (fd, &stat_buf); if (ret) { BX_PANIC (("fstat cdrom file returned error: %s", strerror (errno))); } if (S_ISREG (stat_buf.st_mode)) { using_file = 1; BX_INFO (("Opening image file %s as a cd.", path)); } else { using_file = 0; BX_INFO (("Using direct access for cdrom.")); } ret = read(fd, (char*) &buffer, BX_CD_FRAMESIZE); if (ret < 0) { close(fd); fd = -1; BX_DEBUG(( "insert_cdrom: read returns error: %s", strerror (errno) )); return(false); } #endif return(true); } =============================================================================== 函数 read_block() =============================================================================== void BX_CPP_AttrRegparmN(2) cdrom_interface::read_block(Bit8u* buf, int lba) { // Read a single block from the CD #ifdef WIN32 //Windows 平台 LARGE_INTEGER pos; #else off_t pos; #endif ssize_t n; #ifdef WIN32 // Windows 平台 if(bUseASPI) { Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 197 ReadCDSector(hid, tid, lun, lba, buf, BX_CD_FRAMESIZE); n = BX_CD_FRAMESIZE; } else { pos.QuadPart = (LONGLONG)lba*BX_CD_FRAMESIZE; pos.LowPart = SetFilePointer(hFile, pos.LowPart, &pos.HighPart, SEEK_SET); //调用 API 函数,将读指针移到第 lba 个 block 位置 if ((pos.LowPart == 0xffffffff) && (GetLastError() != NO_ERROR)) { BX_PANIC(("cdrom: read_block: SetFilePointer returned error.")); } ReadFile(hFile, (void *) buf, BX_CD_FRAMESIZE, (unsigned long *) &n, NULL); //读取 block 数据 } #elif defined(__APPLE__) //苹果电脑 #define CD_SEEK_DISTANCE kCDSectorSizeWhole if(using_file) { pos = lseek(fd, lba*BX_CD_FRAMESIZE, SEEK_SET); if (pos < 0) { BX_PANIC(("cdrom: read_block: lseek returned error.")); } n = read(fd, buf, BX_CD_FRAMESIZE); } else { // This seek will leave us 16 bytes from the start of the data // hence the magic number. pos = lseek(fd, lba*CD_SEEK_DISTANCE + 16, SEEK_SET); if (pos < 0) { BX_PANIC(("cdrom: read_block: lseek returned error.")); } n = read(fd, buf, CD_FRAMESIZE); } #else //其它平台 pos = lseek(fd, lba*BX_CD_FRAMESIZE, SEEK_SET); if (pos < 0) { BX_PANIC(("cdrom: read_block: lseek returned error.")); } n = read(fd, (char*) buf, BX_CD_FRAMESIZE); #endif if (n != BX_CD_FRAMESIZE) { BX_PANIC(("cdrom: read_block: read returned %d", (int) n)); } } =============================================================================== 函数 capacity() =============================================================================== Bit32u cdrom_interface::capacity() { // Return CD-ROM capacity. I believe you want to return // the number of blocks of capacity the actual media has. #if !defined WIN32 //非 windows 平台 // win32 has its own way of doing this if (using_file) { Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 198 // return length of the image file struct stat stat_buf; int ret = fstat (fd, &stat_buf); if (ret) { BX_PANIC (("fstat on cdrom image returned err: %s", strerror(errno))); } BX_INFO (("cdrom size is %lld bytes", stat_buf.st_size)); if ((stat_buf.st_size % 2048) != 0) { BX_ERROR (("expected cdrom image to be a multiple of 2048 bytes")); } return stat_buf.st_size / 2048; } #endif #ifdef __BEOS__ //BEOS 平台 return GetNumDeviceBlocks(fd, BX_CD_FRAMESIZE); #elif defined(__sun) //Sun 平台 { struct stat buf = {0}; if (fd < 0) { BX_PANIC(("cdrom: capacity: file not open.")); } if( fstat(fd, &buf) != 0 ) BX_PANIC(("cdrom: capacity: stat() failed.")); return(buf.st_size); } #elif (defined(__NetBSD__) || defined(__NetBSD_kernel__) || defined(__OpenBSD__))//BSD { // We just read the disklabel, imagine that... struct disklabel lp; if (fd < 0) BX_PANIC(("cdrom: capacity: file not open.")); if (ioctl(fd, DIOCGDINFO, &lp) < 0) BX_PANIC(("cdrom: ioctl(DIOCGDINFO) failed")); BX_DEBUG(( "capacity: %u", lp.d_secperunit )); return(lp.d_secperunit); } #elif defined(__linux__) //linux 操作系统 { // Read the TOC to get the data size, since BLKGETSIZE doesn't work on // non-ATAPI drives. This is based on Keith Jones code below. // 21 June 2001 int i, dtrk_lba, num_sectors; int dtrk = 0; struct cdrom_tochdr td; struct cdrom_tocentry te; if (fd < 0) BX_PANIC(("cdrom: capacity: file not open.")); if (ioctl(fd, CDROMREADTOCHDR, &td) < 0) BX_PANIC(("cdrom: ioctl(CDROMREADTOCHDR) failed")); num_sectors = -1; dtrk_lba = -1; for (i = td.cdth_trk0; i <= td.cdth_trk1; i++) { te.cdte_track = i; te.cdte_format = CDROM_LBA; if (ioctl(fd, CDROMREADTOCENTRY, &te) < 0) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 199 BX_PANIC(("cdrom: ioctl(CDROMREADTOCENTRY) failed")); if (dtrk_lba != -1) { num_sectors = te.cdte_addr.lba - dtrk_lba; break; } if (te.cdte_ctrl & CDROM_DATA_TRACK) { dtrk = i; dtrk_lba = te.cdte_addr.lba; } } if (num_sectors < 0) { if (dtrk_lba != -1) { te.cdte_track = CDROM_LEADOUT; te.cdte_format = CDROM_LBA; if (ioctl(fd, CDROMREADTOCENTRY, &te) < 0) BX_PANIC(("cdrom: ioctl(CDROMREADTOCENTRY) failed")); num_sectors = te.cdte_addr.lba - dtrk_lba; } else BX_PANIC(("cdrom: no data track found")); } BX_INFO(("cdrom: Data track %d, length %d", dtrk, num_sectors)); return(num_sectors); } #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) //FreeBSD 系统 { // Read the TOC to get the size of the data track. // Keith Jones , 16 January 2000 #define MAX_TRACKS 100 int i, num_tracks, num_sectors; struct ioc_toc_header td; struct ioc_read_toc_entry rte; struct cd_toc_entry toc_buffer[MAX_TRACKS + 1]; if (fd < 0) BX_PANIC(("cdrom: capacity: file not open.")); if (ioctl(fd, CDIOREADTOCHEADER, &td) < 0) BX_PANIC(("cdrom: ioctl(CDIOREADTOCHEADER) failed")); num_tracks = (td.ending_track - td.starting_track) + 1; if (num_tracks > MAX_TRACKS) BX_PANIC(("cdrom: TOC is too large")); rte.address_format = CD_LBA_FORMAT; rte.starting_track = td.starting_track; rte.data_len = (num_tracks + 1) * sizeof(struct cd_toc_entry); rte.data = toc_buffer; if (ioctl(fd, CDIOREADTOCENTRYS, &rte) < 0) BX_PANIC(("cdrom: ioctl(CDIOREADTOCENTRYS) failed")); num_sectors = -1; for (i = 0; i < num_tracks; i++) { if (rte.data[i].control & 4) { /* data track */ num_sectors = ntohl(rte.data[i + 1].addr.lba) - ntohl(rte.data[i].addr.lba); BX_INFO(( "cdrom: Data track %d, length %d", rte.data[i].track, num_sectors)); break; } } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 200 if (num_sectors < 0) BX_PANIC(("cdrom: no data track found")); return(num_sectors); } #elif defined WIN32 //Windows 平台 { if(bUseASPI) { return (GetCDCapacity(hid, tid, lun) / 2352); } Else if(using_file) { ULARGE_INTEGER FileSize; FileSize.LowPart = GetFileSize(hFile, &FileSize.HighPart); return (Bit32u)(FileSize.QuadPart / 2048); } else { /* direct device access */ ULARGE_INTEGER FreeBytesForCaller; ULARGE_INTEGER TotalNumOfBytes; ULARGE_INTEGER TotalFreeBytes; GetDiskFreeSpaceEx( path, &FreeBytesForCaller, &TotalNumOfBytes, &TotalFreeBytes); return (Bit32u)(TotalNumOfBytes.QuadPart / 2048); } } #elif defined __APPLE__ //苹果电脑 // Find the size of the first data track on the cd. This has produced // the same results as the linux version on every cd I have tried, about // 5. The differences here seem to be that the entries in the TOC when // retrieved from the IOKit interface appear in a reversed order when // compared with the linux READTOCENTRY ioctl. { // Return CD-ROM capacity. I believe you want to return // the number of bytes of capacity the actual media has. BX_INFO(( "Capacity" )); struct _CDTOC * toc = ReadTOC( CDDevicePath ); if ( toc == NULL ) { BX_PANIC(( "capacity: Failed to read toc" )); } size_t toc_entries = ( toc->length - 2 ) / sizeof( struct _CDTOC_Desc ); BX_DEBUG(( "reading %d toc entries\n", toc_entries )); int start_sector = -1; int data_track = -1; // Iterate through the list backward. Pick the first data track and // get the address of the immediately previous (or following depending // on how you look at it). The difference in the sector numbers // is returned as the sized of the data track. for ( int i=toc_entries - 1; i>=0; i-- ) { BX_DEBUG(( "session %d ctl_adr %d tno %d point %d lba %d z %d p lba %d\n", (int)toc->trackdesc[i].session, (int)toc->trackdesc[i].ctrl_adr, (int)toc->trackdesc[i].tno, (int)toc->trackdesc[i].point, MSF_TO_LBA( toc->trackdesc[i].address ), (int)toc->trackdesc[i].zero, MSF_TO_LBA(toc->trackdesc[i].p ))); if ( start_sector != -1 ) { start_sector = MSF_TO_LBA(toc->trackdesc[i].p) - start_sector; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 201 break; } if ((toc->trackdesc[i].ctrl_adr >> 4) != 1) continue; if ( toc->trackdesc[i].ctrl_adr & 0x04 ) { data_track = toc->trackdesc[i].point; start_sector = MSF_TO_LBA(toc->trackdesc[i].p); } } free( toc ); if ( start_sector == -1 ) { start_sector = 0; } BX_INFO(("first data track %d data size is %d", data_track, start_sector)); return start_sector; } #else BX_ERROR(( "capacity: your OS is not supported yet." )); //其它操作系统,bochs 不支持 return(0); #endif } =============================================================================== 函数 read_toc() =============================================================================== 由于函数源码较长,只保留了 Windows 平台相关的部分 bx_bool cdrom_interface::read_toc(Bit8u* buf, int* length, bx_bool msf, int start_track, int format) { unsigned i; // Read CD TOC. Returns false if start track is out of bounds. if (fd < 0) { BX_PANIC(("cdrom: read_toc: file not open.")); return false; } #if defined(WIN32) if (!isWindowsXP || using_file) { // This is a hack and works okay if there's one rom track only #else if (using_file) { #endif Bit32u blocks; int len = 4; switch (format) { case 0: // From atapi specs : start track can be 0-63, AA if ((start_track > 1) && (start_track != 0xaa)) return false; buf[2] = 1; buf[3] = 1; Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 202 if (start_track <= 1) { buf[len++] = 0; // Reserved buf[len++] = 0x14; // ADR, control buf[len++] = 1; // Track number buf[len++] = 0; // Reserved // Start address if (msf) { buf[len++] = 0; // reserved buf[len++] = 0; // minute buf[len++] = 2; // second buf[len++] = 0; // frame } else { buf[len++] = 0; buf[len++] = 0; buf[len++] = 0; buf[len++] = 16; // logical sector 0 } } // Lead out track buf[len++] = 0; // Reserved buf[len++] = 0x16; // ADR, control buf[len++] = 0xaa; // Track number buf[len++] = 0; // Reserved blocks = capacity(); // Start address if (msf) { buf[len++] = 0; // reserved buf[len++] = (Bit8u)(((blocks + 150) / 75) / 60); // minute buf[len++] = (Bit8u)(((blocks + 150) / 75) % 60); // second buf[len++] = (Bit8u)((blocks + 150) % 75); // frame; } else { buf[len++] = (blocks >> 24) & 0xff; buf[len++] = (blocks >> 16) & 0xff; buf[len++] = (blocks >> 8) & 0xff; buf[len++] = (blocks >> 0) & 0xff; } buf[0] = ((len-2) >> 8) & 0xff; buf[1] = (len-2) & 0xff; break; case 1: // multi session stuff - emulate a single session only buf[0] = 0; buf[1] = 0x0a; buf[2] = 1; buf[3] = 1; for (i = 0; i < 8; i++) buf[4+i] = 0; len = 12; break; default: BX_PANIC(("cdrom: read_toc: unknown format")); return false; } *length = len; return true; } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 203 // all these implementations below are the platform-dependent code required // to read the TOC from a physical cdrom. #ifdef WIN32 if (isWindowsXP) //Windows XP 操作系统 { // This only works with WinXP CDROM_READ_TOC_EX input; memset(&input, 0, sizeof(input)); input.Format = format; input.Msf = msf; input.SessionTrack = start_track; // We have to allocate a chunk of memory to make sure it is aligned on a sector base. UCHAR *data = (UCHAR *) VirtualAlloc(NULL, 2048*2, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE); unsigned long iBytesReturned; DeviceIoControl(hFile, IOCTL_CDROM_READ_TOC_EX, &input, sizeof(input), data, 804, &iBytesReturned, NULL); // now copy it to the users buffer and free our buffer memcpy(buf, data, iBytesReturned); VirtualFree(data, 0, MEM_RELEASE); *length = iBytesReturned; return true; } else { return false; } #elseif 以下略。。。 #else BX_INFO(("read_toc: your OS is not supported yet.")); return(false); // OS not supported yet, return false always. #endif } 6.5 PCI 子系统 6.5.1 PCI 概述 (1) PCI总线拓扑结构 PCI(外围设备互连)是一种将系统中外部设备以结构化与可控制方式连接到起来的总线标准,由 PCI 局部总线 规范规定。该规范还包括系统部件连接的电气特性及机械特性。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 204 CPU Host-PCI桥 PCI-ISA桥 ISA总线 ISA设备 PCI视频卡 PCI设备PCI-PCI桥 PCI总线0 PCI总线1 PCI设备 一个基于 PCI 的系统示意图 图 1.1 是一个基于 PCI 的系统示意图。PCI 总线和 PCI-PCI 桥接器在连接系统中设备到上起关键作用,在这个系 统中,PCI 视频设备被连到 PCI bus 0 上,它是系统中的主干 PCI 总线。而 PCI-PCI 桥接器这个特殊 PCI 设备 将主干总线 PCI bus 0 与下级总线 PCI bus 1 连接到一起。PCI 标准术语中,PCI bus 1 是 PCI-PCI 桥接器 的下级总线而 PCI bus 0 是此桥接器的上级总线。以太网设备通过二级 PCI 总线连接到这个系统中。而在物理实 现上,桥接器和二级 PCI 总线被集成到一块 PCI 卡上。而 PCI-ISA 桥接器用来支持古老的 ISA 设备。 (2) PCI 地址空间 CPU 和 PCI 设备需要存取在它们之间共享的内存空间。这块内存区域被设备驱动用来控制 PCI 设备并在 CPU 与 PCI 设备之间传递信息。最典型的共享内存包括设备的控制与状态寄存器。这些寄存器用来控制设备并读取其 信 息。例如 PCI SCSI 设备驱动可以通过读取其状态寄存器,找出已准备好将一块数据写入 SCSI 磁盘的 SCSI 设备。 同时还可以在设备加电后,通过对控制寄存器写入信息来启动设备。 CPU 的系统内存可以被用作这种共享内存,但是如果采用这种方式,则每次 PCI 设备访问此内存块时,CPU 将 被迫停止工作以等待 PCI 设备完成此操作。这种方式将共享内存限制成每次只允许一个系统设备访问。该策略会 大大降低系统性能。但如果允许系统外设不受限制地访问主存也不是好办法。它的危险之处在于一个有恶意行为 的设备将使整个系统置于不稳定状态。 外设有其自身的内存空间。CPU 可以自由存取此空间,但设备对系统主存的访问将处于 DMA(直接内存访问) 通道的严格控制下。ISA 设备需要存取两种地址空间:ISA I/O(输入输出)和 ISA Memory 空间。而 PCI 设备需 要访问三种地址空间:PCI I/O、PCI Memory 和 PCI 配置空间。那么,这些空间的地址范围分别是什么? PCI 的配置空间为 256 字节大小的一段存储空间(也叫配置头),这段空间不是属于 CPU 的寻址空间范围,而是 通过两个特殊端口来访问的。在 IBM PC 中,它们是地址为 0xCF8 和 0xCFC 的两个 32bit I/O 端口。至于如何访 问配置空间以及 PCI 的 I/O 和内存地址范围,在介绍完配置空间的结构后说明。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 205 PCI 配置空间结构 (3) PCI 配置头 系统中每个 PCI 设备,包括 PCI-PCI 桥接器在内,都有一个配置数据结构,它通常位于 PCI 配置地址空间中。PCI 配置头允许系统来标识与控制设备。配置头在 PCI 配置空间的位置取决于系统中 PCI 设备的拓扑结构。例如将一 个 PCI 视频卡插入不同的 PCI 槽,其配置头位置会变化。但对系统没什么影响,系统将找到每个 PCI 设备与桥接 器并使用它们配置头中的信息来配置其寄存器。 典型的办法是用 PCI 槽相对主板的位置来决定其 PCI 配置头在配置空间中的偏移。比如主板中的第一个 PCI 槽的 PCI 配置头位于配置空间偏移 0 处,而第二个则位于偏移 256 处(所有 PCI 配置头长度都相等,为 256 字节), 其它槽可以由此类推。系统还将提供一种硬件相关机制以便 PCI 设置代码能正确的辨认出对应 PCI 总线上所有存 在的设备的 PCI 配置头。通过 PCI 配置头中的某些域来判断哪些设备存在及哪些设备不存在(这个域叫厂商标志 域: Vendor Identification field)。对空 PCI 槽中这个域的读操作将得到一个值为 0xFFFFFFFF 的错误信息。 上图给出了 256 字节 PCI 配置头的结构,它包含以下域: 厂商标识(Vendor Id) 用来唯一标识 PCI 设备生产厂家的数值。Digital 的 PCI 厂商标识为 0x1011 而 Intel 的为 0x8086。 这个地址你可以根据 ID 查询厂商 http://www.pcidatabase.com/vendors.php?sort=id 设备标识(Device Id) 用来唯一标识设备的数值。如 Digital 21141 快速以太设备的设备标识为 0x0009。 状态(Status) 此域提供 PCI 标准定义中此设备的状态信息。 命令(Command) 通过对此域的写可以控制此设备,如允许设备访问 PCI I/O 内存。 分类代码(Class Code) 此域标识本设备的类型。对于每种类型的视频,SCSI 等设备都有标准的分类代码。如 SCSI 设备分类代码为 0x0100。 这就是为什么 Windows 可以识别出还未安装驱动的 PCI 卡是属于那种类型。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 206 基地址寄存器(Base Address Registers) 这些寄存器用来决定和分配此设备可以使用的 PCI I/O 与 PCI 内存空间的类型,数量及位置。 中断引脚(Interrupt Pin) PCI 卡上的四个物理引脚可以将中断信号从插卡上带到 PCI 总线上。这四个引脚标准的标记分别为 A、B、C 及 D。 中断引脚域描叙此 PCI 设备使用的引脚号。通常特定设备都是采用硬连接方式。这也是系统启动时,设备总使用 相同中断引脚的原因。中断处理子系统用它来管理来自该设备的中断。 中断连线(Interrupt Line) 本设备配置头中的中断连线域用来在 PCI 初始化代码、设备驱动以及 Linux 中断处理子系统间传递中断处理过程。 虽然本域中记录的这个数值对于设备驱动毫无意义。但是它可以将中断处理过程从 PCI 卡上正确路由到 Linux 操 作系统中相应的设备驱动中断处理代码中。在 interrupt 一章中将详细描叙 Linux 中断处理过程。 (4) 如何存取 PCI 配置空间 IBM PC 中访问 PCI 配置空间的两个固定地址的 32 端口,分别为 0xCF8(地址端口)和 0xCFC (数据端口)。具体做法是先往 I/O 地址 0xCF8 地址处写入 32 位配置空间的地址;然后从 0xCFC 地址处读出或写入 32 位数据。 往 0x0CF8 处写入的 32 位配置地址格式如下: 1 000 0000 Bus Device Function offset 0 0 0781011151623243031 12 最高位(MSB)固定为 1,bit30 到 bit24 为 0,从 bit23 向下分别是总线号(bus)、设备号(device) 和功能号(function),offset 是 256 字节配置空间的偏移地址(注意:该偏移量是以 DWORD 为单位,offset 最大值为 0x3c)。 PCI I/O 和内存空间 这两个空间由 PCI 配置空间里的 6 个 Base Address Register(BAR)域决定。32bit 的意义如下图所示,其中 bit0 决定是 IO 空间还是内存空间 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 207 当配置完毕后,PCI 主要用这两个地址空间用来实现设备和 CPU 之间交换数据。例如 DEC21141 快速以太网设备 的内部寄存器被映射到CPU的I/O 空间上时,其对应的设备驱动程序可以通过对这些寄存器的读写来控制此设备。 而 PCI 视频卡通常使用大量的 PCI 内存空间来存储视频信息。 在 PCI 系统建立并通过用 PCI 配置头中的命令域来打开这些地址空间前,系统决不允许对它们进行存取。值得注 意的是只有 PCI 配置代码读取和写入 PCI 配置空间,设备驱动只读写 PCI I/O 和 PCI 内存地址。 (5) PCI 的可扩展性 PCI 总线的扩展性体现在可以在总线上挂载桥接设备,将总线范围进行向下延伸。 (a) PCI-ISA 桥 这种桥通过将 PCI I/O 和 PCI 内存空间的存取转换成对 ISA I/O 和 ISA 内存的存取来支持古老的 ISA 设备。市 场上许多主板中同时包含几个 ISA 总线槽和 PCI 槽。但今后对 ISA 设备的向后兼容支持将逐渐减弱,最终主板上 只会有 PCI 槽。早期的 Intel 8080 PC 就将 ISA 设备的 ISA 地址空间固定了下来。即使在价值 5000 美圆的 Alpha AXP 系统中其 ISA 软盘控制器地址也和最早 IBM PC 上的相同。PCI 标准将 PCI I/O 和 PCI 内存的低端部分保 留给系统中的 ISA 外设,另外还使用 PCI-ISA 桥接器实现从 PCI 内存访问到 ISA 内存访问的转换。 (b) PCI-PCI 桥 PCI-PCI 桥是一种将系统中所有 PCI 总线连接起来的特殊 PCI 设备。在简单系统中只存在一条 PCI 总线,由 于受电气特性的限制,它所连接的 PCI 设备个数有限。引入 PCI-PCI 桥接器后系统可以使用更多的 PCI 设备。 对于高性能服务器这是非常重要的。Linux 提供了对 PCI-PCI 桥接器的全面支持。 (6)小结 以上只对 PCI 和桥设备作了简单介绍,实际上多了桥设备后给软件配置增加了复杂性,关 于这方面的详细资料,可以参考 Linux 代码分析文章(PCI 设备)。 现代 PC 中,PCI 主控制器(主桥)被集成在主板的南桥芯片中,PCI 在整个 PC 系统中占 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 208 据很主要的地位,除了 PCI 插槽上的 PCI 设备外,主板上的很多其他设备寄存器(如芯片 组寄存器)都是通过 PCI 配置端口访问(除了一下遗留下来的设备如中断控制器等有固定 的 I/O 端口)。 这是我的电脑中的 PCI 子系统,总共有 4 个总线,每个总线上挂载了很多设备,可以看出 里面包含了很多系统设备。 如果你有兴趣,可以通过穷举的方法来找出这些设备,下面是参考代码: #define MAX_BUS 4 DWORD addr, dat; int bus,dev,func,reg,vendor_id; for (bus=0;busget() && !strcmp(name, bx_options.pcislot[i].Odevname->getptr())) { *devfunc = (i + 2) << 3; BX_PCI_THIS slot_used[i] = 1; BX_INFO(("PCI slot #%d used by plugin '%s'", i+1, name)); break; } } if (*devfunc == 0x00) { BX_ERROR(("Plugin '%s' not connected to a PCI slot", name)); } } /* check if device/function is available */ if (BX_PCI_THIS pci_handler_id[*devfunc] == BX_MAX_PCI_DEVICES) { if (BX_PCI_THIS num_pci_handles >= BX_MAX_PCI_DEVICES) { BX_INFO(("too many PCI devices installed.")); BX_PANIC((" try increasing BX_MAX_PCI_DEVICES")); return false; } handle = BX_PCI_THIS num_pci_handles++; BX_PCI_THIS pci_handler[handle].read = f1; BX_PCI_THIS pci_handler[handle].write = f2; BX_PCI_THIS pci_handler[handle].this_ptr = this_ptr; BX_PCI_THIS pci_handler_id[*devfunc] = handle; BX_INFO(("%s present at device %d, function %d", descr, *devfunc >> 3, *devfunc & 0x07)); return true; // device/function mapped successfully } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 212 else { return false; // device/function not available, return false. } } Bit32u bx_pci_c::read(Bit32u address, unsigned io_len) { switch (address) { case 0x0CF8: // PCI 配置地址端口 { return BX_PCI_THIS s.i440fx.confAddr; } break; case 0x0CFC: // PCI 配置数据端口,从 0x0CFC 到 0x0CFF case 0x0CFD: case 0x0CFE: case 0x0CFF: { Bit32u handle, retval; Bit8u devfunc, regnum; if ((BX_PCI_THIS s.i440fx.confAddr & 0x80FF0000) == 0x80000000) { devfunc = (BX_PCI_THIS s.i440fx.confAddr >> 8) & 0xff; regnum = (BX_PCI_THIS s.i440fx.confAddr & 0xfc) + (address & 0x03); handle = BX_PCI_THIS pci_handler_id[devfunc]; if ((io_len <= 4) && (handle < BX_MAX_PCI_DEVICES)) retval = (* BX_PCI_THIS pci_handler[handle].read) //调用已注册的读 handler (BX_PCI_THIS pci_handler[handle].this_ptr, regnum, io_len); else retval = 0xFFFFFFFF; } else retval = 0xFFFFFFFF; BX_PCI_THIS s.i440fx.confData = retval; //保存一份在 440fx 的数据寄存器中 return retval; } } BX_PANIC(("unsupported IO read to port 0x%x", (unsigned) address)); return(0xffffffff); } void bx_pci_c::write(Bit32u address, Bit32u value, unsigned io_len) { switch (address) { case 0xCF8: { BX_PCI_THIS s.i440fx.confAddr = value; if ((value & 0x80FFFF00) == 0x80000000) { BX_DEBUG(("440FX PMC register 0x%02x selected", value & 0xfc)); } else if ((value & 0x80000000) == 0x80000000) { BX_DEBUG(("440FX request for bus 0x%02x device 0x%02x function 0x%02x", (value >> 16) & 0xFF, (value >> 11) & 0x1F, (value >> 8) & 0x07)); } Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 213 } break; case 0xCFC: case 0xCFD: case 0xCFE: case 0xCFF: { Bit32u handle; Bit8u devfunc, regnum; if ((BX_PCI_THIS s.i440fx.confAddr & 0x80FF0000) == 0x80000000) { devfunc = (BX_PCI_THIS s.i440fx.confAddr >> 8) & 0xff; regnum = (BX_PCI_THIS s.i440fx.confAddr & 0xfc) + (address & 0x03); handle = BX_PCI_THIS pci_handler_id[devfunc]; if ((io_len <= 4) && (handle < BX_MAX_PCI_DEVICES)) { if (((regnum>=4) && (regnum<=7)) || (regnum==12) || (regnum==13) || (regnum>14)) { (* BX_PCI_THIS pci_handler[handle].write) //调用已注册的写 handler (BX_PCI_THIS pci_handler[handle].this_ptr, regnum, value, io_len); BX_PCI_THIS s.i440fx.confData = value << (8 * (address & 0x03)); //保存一份在 440fx 的数据寄存器中 } else BX_DEBUG(("read only register, write ignored")); } } } break; default: BX_PANIC(("IO write to port 0x%x", (unsigned) address)); } } ------------------------------------------------------------------------------------------------------------------------------------------ 函数 pci_set_base_mem()和 pci_set_base_io()为 PCI 设备分配内存空间和 IO 空间(它 们在 PCI 空间的 Base Register Address 域中),函数通过调用 mem 空间注册函数和 io 空间注册函数实现。 void bx_pci_c::pci_set_base_mem(void *this_ptr, memory_handler_t f1, //读 handler 指针 memory_handler_t f2, //写 handler 指针 Bit32u *addr, Bit8u *pci_conf, unsigned size) { Bit32u baseaddr = *addr; if (baseaddr > 0) { DEV_unregister_memory_handlers(f1, f2, baseaddr, baseaddr + size - 1); } Bit32u mask = ~(size - 1); pci_conf[0x00] &= (mask & 0xf0); pci_conf[0x01] &= (mask >> 8) & 0xff; pci_conf[0x02] &= (mask >> 16) & 0xff; pci_conf[0x03] &= (mask >> 24) & 0xff; ReadHostDWordFromLittleEndian(pci_conf, baseaddr); Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 214 if (baseaddr > 0) { DEV_register_memory_handlers(f1, this_ptr, f2, this_ptr, baseaddr, baseaddr + size - 1); } *addr = baseaddr; } void bx_pci_c::pci_set_base_io(void *this_ptr, bx_read_handler_t f1, //读 handler 指针 bx_write_handler_t f2, //写 handler 指针 Bit32u *addr, Bit8u *pci_conf, unsigned size, const Bit8u *iomask, const char *name) { unsigned i; Bit32u baseaddr = *addr; if (baseaddr > 0) { for (i=0; i 0) { DEV_unregister_ioread_handler(this_ptr, f1, baseaddr + i, iomask[i]); DEV_unregister_iowrite_handler(this_ptr, f2, baseaddr + i, iomask[i]); } } } Bit16u mask = ~(size - 1); pci_conf[0x00] &= (mask & 0xfe); pci_conf[0x01] &= (mask >> 8); pci_conf[0x02] = 0x00; pci_conf[0x03] = 0x00; ReadHostDWordFromLittleEndian(pci_conf, baseaddr); pci_conf[0x00] |= 0x01; if (baseaddr > 0) { for (i=0; i 0) { DEV_register_ioread_handler(this_ptr, f1, baseaddr + i, name, iomask[i]); DEV_register_iowrite_handler(this_ptr, f2, baseaddr + i, name, iomask[i]); } } } *addr = baseaddr; } =============================================================================== 函数 pci_read()和 pci_write()是主桥的读写 handler,也要在 PCI 空间注册表中注册 Bit32u bx_pci_c::pci_read(Bit8u address, unsigned io_len) { Bit32u val440fx = 0; if (io_len <= 4) { for (unsigned i=0; i= 0x10) && (address < 0x34)) return; if (io_len <= 4) { for (unsigned i=0; i> (i*8)) & 0xFF; switch (address+i) { case 0x06: case 0x0c: break; default: BX_PCI_THIS s.i440fx.pci_conf[address+i] = value8; BX_DEBUG(("440FX PMC write register 0x%02x value 0x%02x", address,value8)); } } } } 6.6 VGA 设备模拟 6.6.1 模拟 VGA 的类 有两个 VGA 类 bx_svga_cirrus_c 和 bx_vga_c class bx_svga_cirrus_c : public bx_vga_c 在 Svga_cirrus.cpp 中 libvga_LTX_plugin_init(plugin_t *plugin, plugintype_t type, int argc, char *argv[]) { theSvga = new bx_svga_cirrus_c (); //初始化一个 bx_svga_cirrus_c 对象 libvga_set_smf_pointer(theSvga); //把 theSvga 赋给全局变量 theVga bx_devices.pluginVgaDevice = theSvga; // pluginVgaDevice 是在工程中被调用 BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theSvga, BX_PLUGIN_VGA); return(0); // Success } (1)#define BX_REGISTER_DEVICE_DEVMODEL(a,b,c,d) pluginRegisterDeviceDevmodel(a,b,c,d) (2)函数 libvga_set_smf_pointer 是在 vga.cpp 中定义的 bx_vga_c *theVga = NULL; //全局变量 #if BX_SUPPORT_CLGD54XX //如果支持 CLGD54XX(CLGD5446), 则使用 svga_cirrus 类 void Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 216 libvga_set_smf_pointer(bx_vga_c *theVga_ptr) { theVga = theVga_ptr; } #else // BX_SUPPORT_CLGD54XX 否则, 使用 bx_vga_c 类 int libvga_LTX_plugin_init(plugin_t *plugin, plugintype_t type, int argc, char *argv[]) { theVga = new bx_vga_c (); bx_devices.pluginVgaDevice = theVga; BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theVga, BX_PLUGIN_VGA); return(0); // Success } void libvga_LTX_plugin_fini(void) { } #endif // BX_SUPPORT_CLGD54XX 6.6.2 VGA 在 svga_timer 中的更新 bx_svga_cirrus_c::svga_timer(void) { #else // !BX_USE_CIRRUS_SMF UNUSED(this_ptr); #endif // !BX_USE_CIRRUS_SMF BX_CIRRUS_THIS svga_update(); //用这个函数来更新屏幕上的显示 bx_gui->flush(); } 关于 VGA 设备,请参考附录 VBE 编程获取更多有关寄存器操作的信息。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 217 Appendix ============================================== Appendix A.Bochs 配置文本说明 ============================================== Bochs 从本文文件 bochsrc.txt 读取配置,以下就文本内容给出简要说明。 符号# 用于注释一行内容,Bochs 会忽略该行的读取 #======================================================================= # CONFIG_INTERFACE 配置界面 # # NOTE: if you use the "wx" configuration interface, you must also use # the "wx" display library. #======================================================================= #config_interface: textconfig 文本方式 #config_interface: wx wx 界面 #======================================================================= # DISPLAY_LIBRARY 选择显示库,对应于不同操作系统对图形的支持 # Windows平台上选择 win32 # # The choices are: # x use X windows interface, cross platform # win32 use native win32 libraries # carbon use Carbon library (for MacOS X) # beos use native BeOS libraries # macintosh use MacOS pre-10 # amigaos use native AmigaOS libraries # sdl use SDL library, cross platform # svga use SVGALIB library for Linux, allows graphics without X11 # term text only, uses curses/ncurses library, cross platform # rfb provides an interface to AT&T's VNC viewer, cross platform # wx use wxWidgets library, cross platform # nogui no display at all # # NOTE: if you use the "wx" configuration interface, you must also use # the "wx" display library. # # Specific options: # Some display libraries now support specific option to control their # behaviour. See the examples below for currently supported options. #======================================================================= #display_library: amigaos #display_library: beos Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 218 #display_library: carbon #display_library: macintosh #display_library: nogui #display_library: rfb, options="timeout=60" # time to wait for client #display_library: sdl, options="fullscreen" # startup in fullscreen mode #display_library: term display_library: win32, options="legacyF12" # use F12 to toggle mouse #display_library: wx #display_library: x #======================================================================= # ROMIMAGE: 选择 BIOS ROM 文件 #======================================================================= #romimage: file=$BXSHARE/BIOS-bochs-latest, address=0xf0000 romimage: file=BIOS-bochs-latest, address=0xf0000 #romimage: file=bios/BIOS-bochs-2-processors, address=0xf0000 #romimage: file=bios/BIOS-bochs-4-processors, address=0xf0000 #romimage: file=bios/rombios.bin, address=0xf0000 #======================================================================= # MEGS 设定虚拟机内存容量,单位 MByte #======================================================================= #megs: 256 #megs: 128 #megs: 64 megs: 32 #megs: 16 #megs: 8 #======================================================================= # OPTROMIMAGE[1-4]: 可选的 ROM,如网卡上的 ROM #======================================================================= #optromimage1: file=optionalrom.bin, address=0xd0000 #optromimage2: file=optionalrom.bin, address=0xd1000 #optromimage3: file=optionalrom.bin, address=0xd2000 #optromimage4: file=optionalrom.bin, address=0xd3000 #======================================================================= # VGAROMIMAGE VGA ROM,即视频 BIOS # You now need to load a VGA ROM BIOS into C0000. #======================================================================= #vgaromimage: file=bios/VGABIOS-elpin-2.40 vgaromimage: file=VGABIOS-lgpl-latest #vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest-cirrus Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 219 #======================================================================= # VGA: 视频模式选择,一般来说,VBE 能够很好的工作 # Here you can specify the display extension to be used. With the value # 'none' you can use standard VGA with no extension. Other supported # values are 'vbe' for Bochs VBE and 'cirrus' for Cirrus SVGA support. #======================================================================= #vga: extension=cirrus vga: extension=vbe #======================================================================= # FLOPPYA: 选择软盘 A 的格式并设定状态,软盘可以是一个 IMG 映象文件 #======================================================================= floppya: 1_44=m078.IMG, status=inserted #floppya: image=../1.44, status=inserted #floppya: 1_44=/dev/fd0H1440, status=inserted #floppya: 1_2=../1_2, status=inserted #floppya: 1_44=a:, status=inserted #======================================================================= # FLOPPYB: 选择软盘 B 的格式并设定状态,软盘可以是一个 IMG 映象文件 # See FLOPPYA above for syntax #======================================================================= #floppyb: 1_44=b:, status=inserted #floppyb: 1_44=b.img, status=inserted #======================================================================= # ATA0, ATA1, ATA2, ATA3 设置 ATA 控制器,一般来说,PC 机上只有 2 个,BOCHS 可 扩展至 4 个 #======================================================================= ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 #ata1: enabled=0, ioaddr1=0x170, ioaddr2=0x370, irq=15 #ata2: enabled=0, ioaddr1=0x1e8, ioaddr2=0x3e0, irq=11 #ata3: enabled=0, ioaddr1=0x168, ioaddr2=0x360, irq=9 #======================================================================= # ATA[0-3]-MASTER, ATA[0-3]-SLAVE 配置 ATA 设备,包括主设备和从设备 配置内容较多,最常用的是设定一个硬盘映象 文件 # # This defines the type and characteristics of all attached ata devices: # type= type of attached device [disk|cdrom] # mode= only valid for disks [flat|concat|external|dll|sparse|vmware3] Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 220 # mode= only valid for disks [undoable|growing|volatile] # path= path of the image # cylinders= only valid for disks # heads= only valid for disks # spt= only valid for disks # status= only valid for cdroms [inserted|ejected] # biosdetect= type of biosdetection [none|auto], only for disks on ata0 [cmos] # translation=type of transation of the bios, only for disks [none|lba|large|rechs|auto] # model= string returned by identify device command # journal= optional filename of the redolog for undoable and volatile disks # # Examples: # ata0-master: type=disk, mode=flat, path=10M.sample, cylinders=306, heads=4, spt=17 # ata0-slave: type=disk, mode=flat, path=20M.sample, cylinders=615, heads=4, spt=17 # ata1-master: type=disk, mode=flat, path=30M.sample, cylinders=615, heads=6, spt=17 #======================================================================= #ata0-master: type=disk, path="E:\bochs\Bochs-2.2.1win\c_win98.img", mode=flat, cylinders=609, heads=16, spt=63 #ata0-master: type=disk, path="E:\bochs\Bochs-2.2.1win\c.img", mode=flat, cylinders=609, heads=16, spt=63 ata0-master: type=disk, path="E:\bochs\Bochs-2.2.1win\c_win95.img", mode=flat, cylinders=609, heads=16, spt=63 #======================================================================= # BOOT: 设置启动顺序 # boot: floppy 从软盘引导 # boot: disk 从硬盘引导 # boot: cdrom, floppy, disk 多重配置 #======================================================================= #boot: floppy boot: disk #======================================================================= # IPS: 为你的虚拟机估计一个 IPS,用于换算成虚拟机的时钟振荡器速度 # #======================================================================= ips: 10000000 #======================================================================= Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 221 # FLOPPY_BOOTSIG_CHECK: disabled=[0|1] 软盘引导记录(0xaa55) 检测 #======================================================================= #floppy_bootsig_check: disabled=1 floppy_bootsig_check: disabled=0 #======================================================================= # LOG: 指定一个日志文件 #======================================================================= #log: /dev/null log: bochsout.txt #======================================================================= # LOGPREFIX: 配置日志输出格式 # This handles the format of the string prepended to each log line. # You may use those special tokens : # %t : 11 decimal digits timer tick # %i : 8 hexadecimal digits of cpu0 current eip # %e : 1 character event type ('i'nfo, 'd'ebug, 'p'anic, 'e'rror) # %d : 5 characters string of the device, between brackets # # Default : %t%e%d # Examples: # logprefix: %t-%e-@%i-%d # logprefix: %i%e%d #======================================================================= #logprefix: %t%e%d #======================================================================= # LOG CONTROLS 配置运行中发生的错误和警告采用何种方式汇报 #======================================================================= panic: action=ask error: action=report info: action=report debug: action=ignore #pass: action=fatal #======================================================================= # COM1, COM2, COM3, COM4: 配置虚拟机串口 # Examples: # com1: enabled=1, mode=null # com1: enabled=1, mode=mouse # com2: enabled=1, mode=file, dev=serial.out # com3: enabled=1, mode=raw, dev=com1 #======================================================================= #com1: enabled=1, mode=term, dev=/dev/ttyp9 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 222 #======================================================================= # PARPORT1, PARPORT2: 配置虚拟机并口 # Examples: # parport1: enabled=1, file="parport.out" # parport2: enabled=1, file="/dev/lp0" # parport1: enabled=0 #======================================================================= parport1: enabled=1, file="parport.out" #======================================================================= # SB16: 配置虚拟机的声卡 #======================================================================= #sb16: midimode=1, midi=/dev/midi00, wavemode=1, wave=/dev/dsp, loglevel=2, log=sb16.log, dmatimer=600000 #======================================================================= # VGA_UPDATE_INTERVAL: 配置显示器刷新频率 #======================================================================= vga_update_interval: 300000 # using for Winstone '98 tests #vga_update_interval: 100000 #======================================================================= # KEYBOARD_SERIAL_DELAY: 配置键盘延时 #======================================================================= keyboard_serial_delay: 250 #======================================================================= # KEYBOARD_PASTE_DELAY: 配置键盘延时 # keyboard_paste_delay: 100000 #======================================================================= keyboard_paste_delay: 100000 #======================================================================= # FLOPPY_COMMAND_DELAY: 配置软驱命令延时 #======================================================================= floppy_command_delay: 500 #======================================================================= # MOUSE: 配置鼠标 # Examples: # mouse: enabled=1 # mouse: enabled=1, type=imps2 # mouse: enabled=1, type=serial # mouse: enabled=0 #======================================================================= mouse: enabled=0 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 223 #======================================================================= # private_colormap: 配置显示器颜色映射 #======================================================================= private_colormap: enabled=0 #======================================================================= # fullscreen: 配置全屏使能,目前只在 AMIGA 系统上实现 # Examples: # fullscreen: enabled=0 # fullscreen: enabled=1 #======================================================================= #fullscreen: enabled=0 #screenmode: name="sample" #======================================================================= # ne2k: 配置虚拟机安装的 NE2000 兼容以太网卡 # # Examples: # ne2k: ioaddr=IOADDR, irq=IRQ, mac=MACADDR, ethmod=MODULE, ethdev=DEVICE, script=SCRIPT #======================================================================= # ne2k: ioaddr=0x240, irq=9, mac=fe:fd:00:00:00:01, ethmod=fbsd, ethdev=en0 #macosx # ne2k: ioaddr=0x240, irq=9, mac=b0:c4:20:00:00:00, ethmod=fbsd, ethdev=xl0 # ne2k: ioaddr=0x240, irq=9, mac=b0:c4:20:00:00:00, ethmod=linux, ethdev=eth0 # ne2k: ioaddr=0x240, irq=9, mac=b0:c4:20:00:00:01, ethmod=win32, ethdev=MYCARD # ne2k: ioaddr=0x240, irq=9, mac=fe:fd:00:00:00:01, ethmod=tap, ethdev=tap0 #======================================================================= # KEYBOARD_MAPPING: 设定键盘映射 #======================================================================= keyboard_mapping: enabled=0, map= #======================================================================= # KEYBOARD_TYPE: 设定键盘类型 # keyboard_type: mf #======================================================================= #keyboard_type: mf #======================================================================= # I440FXSUPPORT: 是否支持 440FX 芯片组 # Example: # i440fxsupport: enabled=1, slot1=pcivga, slot2=ne2k #======================================================================= #i440fxsupport: enabled=1 #======================================================================= # USB1: 配置 USB 控制器 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 224 # Example: # usb1: enabled=1, ioaddr=0xFF80, port1=mouse, port2=keypad #======================================================================= #usb1: enabled=1, ioaddr=0xFF80 #------------------------- # PCI 主设备映射 #------------------------- #pcidev: vendor=0x1234, device=0x5678 #======================================================================= # GDBSTUB: 配置 GDBSTUB # Enable GDB stub. See user documentation for details. # Default value is enabled=0. #======================================================================= #gdbstub: enabled=0, port=1234, text_base=0, data_base=0, bss_base=0 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 225 ============================================== Appendix B.ATA 和 ATAPI 编程介绍 ============================================== 当今大多数的主板有两个内建的 IDE 控制器(每个控制器可以连接两个设备),它们使用 下面标志 IO 地址: 第一个 IDE 控制器: 1F0h-1F7h , 3F6h-3F7h 第二个 IDE 控制器: 170h-177h, 376h-377h 每个 IO 地址对应于 IDE 控制器的一个寄存器,以下是寄存器列表和说明(第一个 IDE) 地址 名称 属性 1F0 Data register (Read and Write) 1F1 Error Register (Read) 1F1 Features Register (Write) 1F2 Sector Count Register (Read and Write) 1F3 LBA Low Register (Read and Write) 1F4 LBA Mid Register (Read and Write) 1F5 LBA High Register (Read and Write) 1F6 Drive/Head Register (Read and Write) 1F7 Status Register (Read) 1F7 Command Register (Write) 3F6 Alternate Status Register (Read) 3F6 Device Control Register (Write) 状态寄存器(status register)是一个 8bit 寄存器,各 bit 的含义是(left to right): BSY (busy) [MSB] DRDY (device ready) DF (device Fault) DSC (seek complete) DRQ (Data Transfer Requested) CORR (data corrected) IDX (index mark) ERR (error) [LSB] 错误寄存器是一个 8-bit 寄存器,各 bit 的含义如下(left to right): BBK (Bad Block) [MSB] UNC (Uncorrectable data error) MC (Media Changed) IDNF (ID mark Not Found) MCR (Media Change Requested) ABRT (command aborted) TK0NF (Track 0 Not Found) AMNF (Address Mark Not Found) [LSB] 向 ATA 命令寄存器中写入 ATA 命令,通常有以下几个步骤: 1. 查询状态寄存器,直到设备不忙为止 (BUSY 位为 0) 2. 关闭中断 (使用 "cli" 指令) 3. 查询状态寄存器直到设备为就绪状态 (DRDY 位为 1) 4. 向命令寄存器写入操作码(opcode) 5. 重新打开中断 (使用"sti" 指令) 下面的一段程序相对比较简单,调用 ATA 命令"IDENTIFY DRIVE",把结果显示在屏幕上: MOV DX, 1F7h ;status register LOOP1: IN AL, DX ;sets AL to status register (which is 8 bits) ;If the first bit of the status register (BUSY) isn't 0, the device is busy, ;so keep looping until it isn't. AND AL, 10000000xB JNE LOOP1 ;---------------------------------------------------------------------------- Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 226 ;Clear interrupts so something doesn't interrupt the drive or controller ;while this program is working. CLI ;---------------------------------------------------------------------------- MOV DX, 1F7h ;status register again LOOP2: IN AL, DX ;sets AL to status register again ;If the second bit of the status register (DRDY) isn't 1, the device isn't ;ready, so keep looping until it is. AND AL, 01000000xB JE LOOP2 ;---------------------------------------------------------------------------- MOV DX, 1F6h ;device/head register MOV AL, 0 ;0 selects device 0 (master). 10h would select device 1 (slave). OUT DX, AL ;选择 master 设备 MOV DX, 1F7h ;command register MOV AL, 0ECh ;"IDENTIFY DRIVE" command OUT DX, AL ;发送命令 ;---------------------------------------------------------------------------- MOV DX, 1F7h ;status register LOOP3: IN AL, DX AND AL, 00001000xB ;if DRQ is not high, the device doesn't have data for us JE LOOP3 ;yet, so keep looking until it does! ;---------------------------------------------------------------------------- MOV DX, 1F0h ;data register MOV DI, OFFSET buff ;points DI to the buffer we're using MOV CX, 256 ;256 decimal. This controls the REP command. CLD ;clear the direction flag so INSW increments DI (not decrements it) REP INSW ;---------------------------------------------------------------------------- ;We now have the string data in buff, so let's re-enable interrupts. STI ;---------------------------------------------------------------------------- ;...And now we can display the contents of buff! MOV ES, SEG buff MOV BX, OFFSET buff MOV CX, 256 ;256 decimal MOV AH, 2 ; "display output" option for INT 21 LOOP4: MOV DL, [BX] ;moves the contents of the byte from "buff" into DL INT 21h INC BX LOOPNZ LOOP4 ;does this 256 times, because CX was set to 256 mov ah,004C ;terminate program int 21h buff db 256 DUP(?) ; buffer to hold the drive identification info ATAPI 是 ATA 指令集的一个扩展,允许向 ATA 设备发送 SCSI 命令,ATAPI 特别针对 CD ROM 驱动器,因为 CD ROM 刚出来的时候都是 SCSI 设备。因为 SCSI 控制器价格昂贵, SCSI 命令被 IDE 控制器兼容,用在 CS ROM 驱动器上,即 ATAPI。ATAPI 使用包(和 TCPIP 网络中的数据包类似)来传送数据和指令。发送到 ATAPI 的数据包如果包含一条命令,称 为命令包。它们通过 ATA 接口写入数据寄存器,ATAPI 设备接收到命令。命令包有 12 个 字节的标志格式,第一个字节是真正的操作代码(余下的 11 个字节为参数)。虽然说命令 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 227 包的长度为 12 个字节,发送给 ATAPI 设备时使用 16bit 写操作而不是单字节写入。PC 汇 编中 16bit 被称为’word’,因此发送一条命令只需要 6 个写周期。 包含在 ATAPI 命令中的操作码其实是 SCSi 的命令代码,不能对 ATAPI 设备使用 ATA 命令; ATAPI 设备只能使用 SCSI 命令。比如,弹出 CDROM 的 SCSI 命令为"START/STOP UNIT", 代码 1BH。因此,如果要想弹出 CDROM,发送 1BH 开头的命令包。 ATAPI 包含多个命令,最基本的命令是 PACKET,操作码为 A0H。在 对 ATAPI 设备发送命 令的第一步就是发送 PACKET 命令,就象上面的程序发送 IDENTIGY DEVICE 命令一样。 PACKET 命令发送后,ATAPI 设备会进入一个被称为 HPD0 的状态,即 Check_Status_A 状态。意味着 ATA 控制器需要等待 400ns,然后查询状态寄存器直到 BSY 位为 0。如果 BSY 为 1,CPU 需要继续查询。 当 BSY 变为 0(DRQ 设置为 1),ATAPI 设备转到状态 HPD1:Send_Packet 状态。 该 状态下,CPU 可以对 ATA 控制器的数据寄存器发送命令包,每次一个字节。当命令包的所 有字节发送完毕,转换到 HPD2:Check_Status_B 状态(如果 nIEN 设为 1)或者 HPD3: INTRQ_Wait 状态(如果 nIEN 设为 0)。nIEN 是 ATA 设备控制寄存器的 bit1。该寄存器 为只写,在发送 ATAPI 包之前就将 nIEN 位设定好。 如果转换到 HPD3:INTRQ_Wait 状态,唯一可做的事只有等待 INTRQ 被使能。当 INTRQ 被使能后,CPU 过渡到 HPD2: Check_Status_B 状态。 在 HPD2: Check_Status_B 状态下还有一些琐碎的事需要注意。如:ATAPI 规范上说如果 从 HPD1 进入 HPD2,CPU 必须等待一个 PIO 周期后才能读状态寄存器,因此,可以先进 行一次读状态寄存器的操作,但忽略结果,第二次再读的时候对结果进行判断。 此时查看 BUSY,如果是 1 说明没有离开 HPD2 状态。一旦 BUSY 为 0,查看 DRQ。若 DRQ 被设 置为 1,说明此时转换到状态 HPD4:Transfer_Data 状态。若 DRQ 为 0,可以跳过 HPD4。 如果 DRQ 和 BUSY 都为 0,说明命令已完成。 以下是一段未整理的代码,完成的功能是弹出 CD-ROM 驱动器,需要在实模式下运行(或 称为 "DOS 模式). ;---------------------查询状态寄存器 BUSY 标志位------------------- MOV DX, 177h ;status register LOOP1: IN AL, DX ; sets AL to status register (which is 8 bits) ;If the first bit of the status register (BUSY) isn't 0, the device is busy, ;so keep looping until it isn't. AND AL, 10000000xB JNE LOOP1 ;---------------------------------关闭 CPU 中断-------------------------------------------- ;Clear interrupts so something doesn't interrupt the drive or controller ;while this program is working. CLI ;-------------------------------- 查询状态寄存器 BUSY 标志位--------------------------- MOV DX, 177h ;status register again LOOP2: IN AL, DX ; sets AL to status register again ;If the second bit of the status register (DRDY) isn't 1, the device isn't ;ready, so keep looping until it is. AND AL, 01000000xB JE LOOP2 ;-------------------------------设置相关寄存器,发送 PACKET 命令-------------------- MOV DX, 176h ; device/head register MOV AL, 0 ; 0 selects device 0 (master). 10h would select device 1 (slave). Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 228 OUT DX, AL ; selects master device 选择主 IDE 设备 ;IMPORTANT: Set nIEN before you send the PACKET command! ;Let's set nIEN to 1 so we can skip the INTRQ_Wait state. MOV DX, 3F6h ; Device Control register MOV AL, 00001010xB ; nIEN is the second bit from the right here OUT DX, AL ; nIEN is now one! MOV DX, 177h ; command register MOV AL, 0A0h ; PACKET command OUT DX, AL ; sends the command! ;After sending the PACKET command, the host is to wait 400 nanoseconds before ;doing anything else. MOV CX,0FFFFh WAITLOOP: LOOPNZ WAITLOOP ;-------------------------------- 查询状态寄存器 BUSY 标志位--------------------------- MOV DX, 177h ; status register again LOOP3: IN AL, DX ; sets AL to status register again ;Poll until BUSY bit is clear. AND AL, 10000000xB JNE LOOP3 ;-------------------------------- 查询状态寄存器 DRQ 标志位--------------------------- ;Also, poll until DRQ is one. MOV DX, 177h ; status register again LOOP4: IN AL, DX AND AL, 00001000xB JE LOOP4 ;-------------------------------- 发送 ATAPI 命令包--------------------------------------- ;NOW WE START SENDING THE COMMAND PACKET!!! MOV CX, 6 ; do this 6 times because it's 6 word writes (a word is 2 bytes) MOV DS, SEG buff MOV SI, OFFSET buff ;DS:SI now points to the buffer which contains our ATAPI command packet CLD ; clear direction flag so SI gets incremented, not decremented COMPACKLOOP: ; command packet sending loop MOV DX, 170h ; data register ;Because we're going to need to write a word (2 bytes), we can't just use an ;8-bit register like AL. For this operation, we'll need to use the full width ;of the 16-bit accumulator AX. We'll use the LODSW opcode, which loads AX ;with whatever DS:SI points to. Not only this, but if the direction flag is ;cleared (which we did a few lines above with the CLD instruction), LODSW ;also auto-increments SI. LODSW OUT DX, AX ; send the current word of the command packet!!! MOV DX, 3F6h ; Alternate Status Register IN AL, DX ; wait one I/O cycle LOOPNZ COMPACKLOOP Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 229 ;-------------------------------- 发送完毕,等待一个 PIO 周期--------------------------- ;Once again, let's read the Alternate Status Register and ignore the result, ;since the spec says so. ;执行一个无意义的操作,实际上是为了等待一个 PIO 周期 MOV DX, 3F6h IN AL, DX ;Okay... That's done. ;Time to poll the status register until BUSY is 0 again. ;-------------------------------- 查询状态寄存器 BUSY 标志位--------------------------- MOV DX, 177h ; status register LOOP5: IN AL, DX AND AL, 10000000xB JNE LOOP5 ;BUSY is zero here. ;We're also supposed to check DRQ, but hey, screw it. STI ;-------------------------------- 结束程序------------------------------------------------------- mov ah,004C ; terminate program int 21h buff db 1Bh, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 ;//ATAPI 命令,长度 12 字节 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 230 ============================================== Appendix C.Sound Blaster 16 编程 ============================================== 介绍 Sound blaster 16(以下简称 SB16)可以回放 FM 合成声音和数字声音,它支持多种格式数 字声音,从 5000HZ 8bit 单声道到 44khz 16bit 立体声。本文档对 SB16 DSP CT1341 进行编 程,包括录制和回放数字声音。读者需要了解基本的 Sound blaster 编程知识。 Sound Blaster 16 DSP I/O 端口 SB16 的 DSP 芯片由 I/O 端口访问,端口基地址可以用跳线设置。SB16 上共有 16 个 I/O 端 口,用作 FM 音乐合成、混音器设置、DSP 编程以及 CDROM 访问。其中 5 个端口用于 DSP 访问,如下: • 2x6h - DSP Reset • 2xAh - DSP Read • 2xCh - DSP Write (Command/Data), DSP write-buffer status (Bit 7) • 2xEh - DSP Read-buffer status (Bit 7), DSP interrupt acknowledge • 2xFh - DSP 16-bit interrupt acknowledge 复位 DSP 在对 DSP 编程之前先要复位 DSP,使用下面步骤复位 DSP: 1. 向 reset 端口(2x6)写 1 2. 等待 3 microseconds 3. 向 reset 端口(2x6)写 0 4. 查询读缓冲状态端口(2xE)直到 bit 7 被置位 5. 查询读数据端口(2xA)直到收到 AA DSP 芯片通常需要 100ms 来初始化。这段时间过后,如果不是返回 AA 或根本没有返回值, 可能是因为 SB 卡没有安装或者发生 I/O 错误。 写入 DSP 向 SB16 写入一个字节,需要安装以下步骤 : 1. 读取写缓冲状态端口(2xC) 直到 bit 7 为 0 2. 向写端口(2xC)写入数据 读取 DSP 从 SB16 读取一个字节,需要安装以下步骤 : Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 231 1. 读取读缓冲状态端口(2xE)直到 bit 7 被置位 2. 从读端口(2xA)读取数据 对 DMA 控制器编程 DMA(Direct Memory Access)控制器在 I/O 和存储器之间传送数据而不占有 CPU 资源。Intel 8237 DMAC 是一个典型的 DMA 控制器芯片。IBM 兼容计算机有两个 DMA 控制器,一个 用作 8-bit 传输另外一个用作 16-bit 传输。DMA 控制器和外部页寄存器一起使用,可以向 SB16 传送 64k 的数据块。以下是一些相关的 I/O 端口和寄存器设置 I/O port addresses for the DMA Address and Count Registers 控制寄存器的 I/O 端口地址 lower page 寄存器的 I/O 端口地址 Mode register bit assignments Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 232 Write single mask bit assignments DMAC2 用于 16-bit I/O 传输, DMAC1 用于 8bit。 启动一次传输过程较为复杂,下面给出了步骤: 1. 计算缓冲区的绝对线性地址(实际上指是物理地址)。 LinearAddr := Seg(Ptr^)*16 + Ofs(Ptr^)); 2. 设置掩码,禁止声卡 DMA 通道 Port[MaskPort] := 4 + (Channel mod 4); 3. 清除字节指针 flip-flop Port[ClrBytePtr] := AnyValue; 4. 写入 DMA 传输模式 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 233 Port[ModePort] := Mode + (Channel mod 4); 几种常用的模式是: o 48h+Channel - Single-cycle playback o 58h+Channel - Auto-initialized playback o 44h+Channel - Single-cycle record o 54h+Channel - Auto-initialized recording 5. 写入缓冲区的偏移量,先低字节后高字节。16bit 数据,偏移量是从 128kb 大小的页 起始算(以 word 为单位)。 6. if 16-bit mdoe 7. then 8. begin 9. BufOffset := (LinearAddr div 2) mod 65536; 10. Port[BaseAddrPort] := Lo(BufOffset); 11. Port[BaseAddrPort] := Hi(BufOffset); 12. end 13. else 14. begin 15. BufOffset := LinearAddr mod 65536; 16. Port[BaseAddrPort] := Lo(BufOffset); 17. Port[BaseAddrPort] := Hi(BufOffset); 18. end; 19. 写入传输长度,先低字节后高字节。对于 8-bit 传输,写入(byte 数-1),对于 16bit, 写入(word 数-1) 20. Port[CountPort] := Lo(TransferLength-1); Port[CountPort] := Hi(TransferLength-1); 21. 向 DMA 页寄存器中写入缓冲区页的值 Port[PagePort] := LinearAddr div 65536; 22. 清除相关的标志位,使能声卡的 DMA 通道 23. Port[MaskPort] := DMAChannel mod 4; 设置采样率 不同于早期的声卡,SB16 可以编程为实际的采样率而不是用时间常数。采样率通过 DSP 的 命令 41h 和 42h 设定。41h 用于输出而 42h 用于输入。设置采样率的过程如下: 1. 写入命令 (41h for output rate, 42h for input rate) 2. 写入采样率高字节(56h for 22050 hz) 3. 写入采样率低字节 (22h for 22050 hz) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 234 数字声音 I/O 为录制或回放数字声音,需要以下步骤: 1. 分配缓冲区,注意不要跨越 64k 物理页边界 2. 安装中断服务程序 3. 编程 DMA 控制器为后台传输 4. 设定采样率 Set the sampling rate 5. 向 DSP 写入 I/O 命令 6. 向 DSP 写入 I/O 传输模式 7. 向 DSP 写入块大小 (Low byte/high byte) 使用 single-cycle DMA 时中断处理需要完成: 1. 编程 DMA 控制器为下一个 block 传输作准备 2. 编程 DSP,为下一个传输准备 3. 如果使用双缓冲,拷贝下一个数据块 4. 从 2xE 端口(8bit)进行一次读取用来响应 SB 的中断。(16-bit 则对于与端口 2xF) 5. 向端口 20h 写入 20h,用来响应 PIC 的中断;如果声卡使用 IRQ8-IRQ15(8259 从片), 还必须向端口 A0h 写入 20h。 FIFO 用来减少传输中的间断问题(当需要传输时请求 DMA 失败)。如果禁用 FIFO,则需 要精确的时刻得到 DMA 使用权,如果此时有高优先级的设备占用了 DMA,采样将会被中 断。 使用 FIFO 可以允许 DMA 周期不固定,但声音的质量不会受影响。 如何一个命令发送到 DSP 都会清空 FIFO。在 Single-cycle 模式下, DSP 会被经常的重新编程。此时 FIFO 也会还 有没输出完的数据。为了防止这个问题, 在 single-cycle 模式下关闭 FIFO。当使用 auto-initialized 模式,DSP 永远不被重新编程。这是使用 FIFO 可以提高声音质量。 自动初始化(auto-initialized)DMA 模式 当使用 single-cycle DMA 模式,在每个 block 结束后声音会停止下来(发出中断)。利用中 断程序可以开始下一个传输,但这之间还是有了停顿。结果会使输出的声音中出现”click” 声。 如果使用 auto-initialized DMA 模式,声音输出会在 block 结束后循环,DMA 控制器继续传 输缓冲区的数据。当到达 buffer 的末尾时,DMA 会从寄存器中重新加载设定值(页和 offset), 开始下一轮传输。将 DMA 编程为整个 buffer 长度,而 SB16 编程为 block 长度(buffer 的一 半长)。每一个 block 将会发生一次声卡中断,整个 buffer 传输完毕将会发生两次中断。一 次在中点一次在末尾。中断服务程序负责拷贝数据到刚结束的 block 位置,因而输出数据总 是就绪的。auto-initialized 和 single-cycle 传输编程方式系统,除了 DMA 模式寄存器 bit4 和 DSP 命令 bit3 需置位外。 发生中断时的处理(使用 auto-initialized DMA 模式) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 235 1. 拷贝下一个 chunk 到刚完成的 buffer block 的位置 2. 从 2xE 端口(8bit)进行一次读取用来响应 SB 的中断。(16-bit 则对于与端口 2xF) 3. 向端口 20h 写入 20h,用来响应 PIC 的中断;如果声卡使用 IRQ8-IRQ15(8259 从片), 还必须向端口 A0h 写入 20h。 立即停止播放: • 8-bit – 写入 DSP 命令 D0h (Pause 8-bit DMA mode digitized sound I/O) • 16-bit – 写入 DSP 命令 D5h (Pause 16-bit DMA mode digitized sound I/O) 以上命令会立即中止声音播放,不会产生中断。 在当前 block 结束后停止播放: • 8-bit – 写入 DSP 命令 DAh (Stop 8-bit auto-init DMA sound I/O) • 16-bit – 写入 DSP 命令 D9h (Stop 16-bit auto-init DMA sound I/O) 以上命令会在播放完当前 block 结束后停止。 你也可以通过对 DSP 重编程为 single-cycle 模 式而中止 auto-initialized 模式。下一个中断后,声卡会从 A/I 模式切换到 S/C 模式。 附加内容 :Sound Blaster 16 模式与 DSP 命令集 SB16 可用的模式 Sound blaster 上的 DSP 版本决定了如何向 sound blaste 输出声音。4.0 版本的发行增加了许多 回放模式,包括 16 bit Stereo Auto Init playback。下表是 DSP 版本对应的模式 DMA 操作模式 1.xx 2.00 2.01+ 3.xx 4.xx 8 Bit Mono PCM Single Cycle Y Y Y Y Y 8 Bit Mono PCM Auto-Init Y Y Y Y 8 Bit Mono ADPCM Single Cycle Y Y Y Y Y 8 Bit Mono ADPCM Auto-Init Y Y Y Y 8 Bit Mono High Speed PCM Single Cycle Y Y 8 Bit Mono High Speed PCM Auto-Init Y Y 8 Bit Stereo High Speed PCM Single Cycle Y 8 Bit Stereo High Speed PCM Auto-Init Y Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 236 8/16 Bit Mono PCM Single Cycle Y 8/16 Bit Mono PCM Auto-Init Y 8/16 Bit Stereo PCM Single Cycle Y 8/16 Bit Stereo PCM Auto-Init Y 声音回放能力 下表列出了不同版本 DSP 支持的声音回放能力 DSP 版本 传输模式 格式 采样率 Mono 8 bit unsigned 5000 to 44100 Hz Mono 16 bit unsigned 5000 to 44100 Hz Stereo 8 bit unsigned 5000 to 44100 Hz 4.xx Stereo 16 bit unsigned 5000 to 44100 Hz Mono 8 bit unsigned 4000 to 23000 Hz Mono/High Speed 8 bit unsigned 23000 to 44100 Hz 3.xx Stereo/High Speed 8 bit unsigned 11025 to 22050 Mono 8 bit unsigned 4000 to 23000 Hz 2.01+ Mono/High Speed 8 bit unsigned 23000 to 44100 Hz 2.00 and 1.xx Mono 8 bit unsigned 4000 to 23000 Hz ALL Mono 8 bit to 4 bit ADPCM 4000 to 12000 Hz ALL Mono 8 bit to 3 bit ADPCM 4000 to 13000 Hz ALL Mono 8 bit to 2 bit ADPCM 4000 to 11000 Hz IO 端口列表 IO 地址 描述 访问 Base + 0h FM Music Status Port Read Base + 0h FM Music Register Address Port Write Base + 1h FM Music Data Port Write Only Base + 2h Advanced FM Music Status Port Read Base + 2h Advanced FM Music Register Address Port Write Base + 3h Advanced FM Music Data Port Write Only Base + 4h Mixer Chip Register Address Port Write Only Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 237 Base + 5h Mixer Chip Data Port Read / Write Base + 6h DSP Reset Write Only Base + 8h FM Music Status Port Read Base + 8h FM Music Register Port Write Base + 9h FM Music Data Port Write Only Base + Ah DSP Read Data Port Read Only Base + Ch DSP Write Command/Data Write Base + Ch DSP Write Buffer Status (Bit 7) Read Base + Eh DSP Read Buffer Status (Bit 7) Read Only Base + 10h CD-ROM Command or Data Register Read / Write Base + 11h CD-ROM Status Register Read Only Base + 12h CD-ROM Reset Register Write Only Base + 13h CD-ROM Enable Register Write Only 内存范围列表 Base Address Memory Regions Used 220h 220h to 233h 240h 240h to 253h 260h 260h to 273h 280h 280h to 293h DSP 命令集 10h____8-bit Direct mode single byte digitized sound output Input : 10h, Data (1 byte) Notes : The user is responsible for calling this function at a relivant frequency. The user has to call this function and immediately follow it with the unsigned byte. Repeat this process until the end of the sound. 14h____8-bit Single-Cycle DMA mode digitized sound output Input : 14h, LoByte(length-1), HiByte(length-1)Data (1 byte) Notes : length is 1 WORD in length and is the number of 8-bit samples to be played. 1Ch____8-bit Auto-Init DMA mode digitized sound output Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 238 Input : 1Ch Notes :There are 2 ways of stopping Auto-Init Mode: 1) Switch to Single-Cycle DMA mode. The DSP after finishing its current block will process this command. 2) Send the exit Auto-Init mode command.(DAh) 20h____8-bit Direct mode single byte digitized sound input Input : 20h Notes :The procedure to fetch data from the card is to 1)send command 0x20, 2) read a byte of digitized data from the DSP, 3) wait for the correct time period and repeat steps 1&2. 24h____8bit Single Cycle DMA mode digitiezed sound input Input : 24h,Lo(length-1),Hi(length-1) Notes : Length is a word giving the number of 8-bit samples. 2Ch____8-bit Auto-Init DMA mode digitized sound input Input : 2Ch Notes : Send 2Ch and GO! 40h____Set digitized sound transfer Time Constant Input : 40h,TimeConst Notes : TimeConst is calculated as follows: TimeConst=65536 - (256 000 000/(channels * sampling rate)) channels is 1 for mono, and 2 for stereo. Only the high byte is sent to the DSP. 41h____Set digitized sound output sampling rate Input : 41h,Hi(Sr),Lo(Sr) Notes : Sr is the raw sampling rate from 5khz -> 45khz 42h____Set digitized sound input sample rate Input : 42h,Hi(Sr),Lo(Sr) Notes : See 41h 48h____Set DSP block transfer size Input : 48h, Lo(Bs-1),Hi(Bs-1) Notes : The DSP will generate an interrupt after each block of size Bs has been transfered. This command is used in high speed mode I/O and Auto-Init mode I/O 80h____Pause DAC for a duration Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 239 Input : 80h,Lo(Time-1),Hi(Time-1) Notes : Time is the number of sampling periods. After the DAC has restarted it will generate an interrupt. D0h____Pause 8-bit DMA mode digitizes sound output Input : D0h Notes : This will make the DSP to stop sending DMA requests. Continue output with command D4h. D1h____打开 speaker Input : D1h Notes : On version 1.xx, the DSP will pause after executing this command. With version 4.xx, this serves only to set the speaker on/off flag so that the command D8 (get speaker status) works correctly. Command D3 will turn off the speaker. D8h____读取 Speaker 状态 Input : D8h Notes : After sending this command, read a byte from the DSP. A value of FFh indicates that the speaker is on, and 00h that it is off. E1h____读取 DSP 版本号 Input : E1h Notes : After sending this command, read 2 bytes from the DSP. The first is the major version number, and the second is the minor version. It has been my experience that this command has to be executed immediately after the DSP has been reset to work. Examples Lesson 1 : Initilizing the DSP and Getting Environment Settings Lesson 2 : Setting the Sound Blaster Mixer Levels Lesson 3 : Reading the .Voc Format Lesson 4 : Playback using Direct Mode Lesson 5 : Playback using Single Cycle Mode Lesson 6 : Playback using Auto-Init Mode 主要资料来源: [1]游戏编程爱好者 Justin Deltener 的个人网站 www.inversereality.org [2]Sound Blaster 16 Programming Document ,作者 Ethan Brodsky Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 240 ============================================== Appendix D.VESA 编程介绍 ============================================== 说明: 以下这篇文章来自互联网,地址 www.monstersoft.com,是一篇介绍 VESA 编程的较为 详细的文章。后面部分内容没有翻译完成,但为了保持完整性,保留了英文原文。 目的 为了能让 super VGA 适配器能有一个统一的编程接口,以便应用程序可以访问先进的 VGA 产品。 概述 该标准提供了一组函数,应用程序可以使用它们做以下事: 获取 Super VGA 的性能和指标信息 控制硬件的基本操作如设置视频模式、初始化以及显寸访问。 这些函数提供了 VGA BIOS 视频服务扩展,通过中断 10h 访问。 内容 目录 1. Introduction 2. Goals and Objectives 2.1 Video environment information 2.2 Programming support 2.3 Compatibility 2.4 Scope of standard 3. Standard VGA BIOS 4. Super VGA Mode Numbers 5. CPU Video Memory Control 5.1 Hardware design consideration 5.1.1 Limited to 64k/128k of CPU address space 5.1.2 Crossing CPU video memory window boundaries 5.1.3 Operating on data from different areas 5.1.4 Combining data from two different windows 5.2 Different types of hardware windows 5.2.1 Single window systems 5.2.2 Dual window systems 6. Extended VGA BIOS 6.1 Status Information 6.2 00h - Return super VGA information Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 241 6.3 01h - Return super VGA mode information 6.4 02h - Set super VGA mode 6.5 03h - Return super VGA mode 6.6 04h - Save/restore super VGA video state 6.7 05h - Super VGA video memory window control 6.8 06h - Set/get logical scan line length 6.9 07h - Set/get display start 6.10 08h - Set/get DAC palette control 7. Application Example 1. 介绍 本文档包含了一个用来访问扩展 VGA 的标准。既包含了由 VESA 委员会提出了扩展视频模 式标准,也有一些个别显卡支持的非标准视频模式。 主要讨论的问题是扩展 VGA 视频模式和供应用程序调用的视频函数。 读这篇文档之前,先要熟悉在硬件层上进行 VGA 编程和 Intel iAPX 实模式汇编。 2. 目标 IBM VGA 已经成为 PC 图形世界的标准。市面上的众多显卡产品都提供了 BIOS 和 IBM VGA 兼容。越来越多的显卡在功能上进行了扩展,如高分辨率,更多的颜色数以及图形处理能力。 激烈的竞争使性价比大大提高。 尽管如此,如果使用这些高级功能给软件开发人员带来了问题,因为各家厂商都有自己的一 套标准。而没有形成公共的统一接口,除了由厂家提供 OEM 的驱动,软件设计者若想使用 它们,需要投入大量的精力且有一定难度。 VESA VGA BIOS 出现的目的就是为了缓解这个问题。为 super VGA 提供统一的软件编程接 口。以便应用程序可以方便使用它们的一系列扩展功能。 特别的,VESA BIOS 扩展重点解决以下问题: Ô 返回给应用程序视频环境的信息 Ô 辅助应用程序初始化和控制硬件 2.1 视频环境信息 今天,应用程序已经无法通过某种标准手段得知自己究竟运行在哪个 super VGA 硬件上。 仅仅可以通过访问 I/O 端口得到有限的视频卡 OEM 信息,而无法得知究竟支持哪些扩展功 能。 VESA BIOS 控制提供了一系列函数,它们可以返回视频环境信息。这些函数返回系统信息 以及支持的模式。功能 00h 返回系统级信息概述,包括 OEM 字符串。同时,函数返回一个 指针指向支持的视频模式。功能 01h 可以用来得到支持的模式。功能 03h 返回当前正在使 用的视频模式。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 242 2.2 编程支持 因为不同的 super VGA 产品都由不同的硬件实现,软件很难适应不同的环境。尽管如此, 它们还是基于 VGA 的硬件架构,不同的只是初始化和内存映射。还有很多相同的如:访问 寄存器的 I/O 端口地址、视频缓冲区在 CPU 地址空间的位置、DAC 位置以及其它等。 VESA BIOS 扩展提供了针对不同 super VGA 硬件的接口。最重要的是功能 02h-设置 VGA 模式。该功能大大简化了软件对显卡的配置。功能 05h 提供了内存映射的接口。功能 04 允 许应用程序保存和恢复 super VGA 状态。 2.3 兼容性 设计 VESA BIOS 的一个目的就是为标准 VGA 环境提供尽可能大的兼容性,并减小对现有 VGA BIOS 的改动。 2.4 标准的范围 制定VESA BIOS扩展的目的是为了提供一个标准的扩展VGA编程环境。图形软件驱动super VGA 显卡的方式和标准 VGA 显卡的方式是一样的,包括读写显示缓冲区,操作控制寄存器, 调色板编程。而不包含一些特殊的图形处理过程在里面,因此,VESA BIOS 扩展并不提供 绘图函数如 BitBlt,画线和画圆。 另外,由于 VESA BIOS 扩展的函数都是放在显卡 ROM 空间内的,因此 ROM 容量限制了 BIOS 功能数量。 3. 标准 VGA BIOS 设计 VESA BIOS 扩展时一个重要的问题是尽可能小的对标准 VGA BIOS 产生影响。 但还是有两个标准 VGA BIOS 功能受到了影响,它们是 00h(设置视频模式)和 0Fh(读 当前视频状态)。VESA 程序 However, two standard VGA BIOS functions are affected by the VESA extension. These are Function 00h (Set video mode) and Function 0Fh (Read current video state). VESA-aware applications will not set the video mode using VGA BIOS function 00h. Nor will such applications use VGA BIOS function 0Fh. VESA BIOS functions 02h (Set Super VGA mode) and 03h (Get Super VGA mode) will be used instead. To make such applications work, VESA recommends that whatever value returned by VGA BIOS function 0Fh (it is up to the OEM to define this number) should be used to reinitialize the video mode through VGA BIOS function 00h. Thus, the BIOS should keep track of the last Super VGA mode in effect. It is recommended, but not mandatory, to support output functions (such as TTY-output, scroll, set pixel, etc.) in Super VGA modes. If the BIOS extension doesn't support such output functions, bit D2 (Output functions supported) of the ModeAttributes field (returned by VESA BIOS function 01h) should be clear. 4. Super VGA 模式数量 Standard VGA mode numbers are 7 bits wide and presently range from 00h to 13h. OEMs Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 243 have defined extended video modes in the range 14h to 7Fh. Values in the range 80h to FFh cannot be used, since VGA BIOS function 00h (Set video mode) interprets bit 7 as a flag to clear/not clear video memory. Due to the limitations of 7 bit mode numbers, VESA video mode numbers are 15 bits wide. To initialize a Super VGA mode, its number is passed in the BX register to VESA BIOS function 02h (Set Super VGA mode). The format of VESA mode numbers is as follows: D0-D8 = 模式号 If D8 == 0, this is not a VESA defined mode If D8 == 1, this is a VESA defined mode D9-D14 = 保留为 VESA 以后扩展用 (= 0) D15 = 保留 (= 0) Thus, VESA mode numbers begin at 100h. This mode numbering scheme implements standard VGA mode numbers as well as OEM-defined mode numbers as subsets of the VESA mode number. That means that regular VGA modes may be initialized through VESA BIOS function 02h (Set Super VGA mode), simply by placing the mode number in BL and clearing the upper byte (BH). OEM-defined modes may be initialized in the same way. To date, VESA has defined a 7-bit video mode number, 6Ah, for the 800x600, 16-color, 4-plane graphics mode. The corresponding 15-bit mode number for this mode is 102h. VESA 定义了以下视频模式: 图形 文本 15-bit 7-bit Resolution Colors 15-bit 7-bit Columns Rows mode mode mode mode number number number number ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 100h - 640x400 256 108h - 80 60 101h - 640x480 256 109h - 132 25 102h 6Ah 800x600 16 10Ah - 132 43 103h - 800x600 256 10Bh - 132 50 10Ch - 132 60 104h - 1024x768 16 105h - 1024x768 256 106h - 1280x1024 16 107h - 1280x1024 256 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 244 10Dh - 320x200 32K (1:5:5:5) 10Eh - 320x200 64K (5:6:5) 10Fh - 320x200 16.8M (8:8:8) 110h - 640x480 32K (1:5:5:5) 111h - 640x480 64K (5:6:5) 112h - 640x480 16.8M (8:8:8) 113h - 800x600 32K (1:5:5:5) 114h - 800x600 64K (5:6:5) 115h - 800x600 16.8M (8:8:8) 116h - 1024x768 32K (1:5:5:5) 117h - 1024x768 64K (5:6:5) 118h - 1024x768 16.8M (8:8:8) 119h - 1280x1024 32K (1:5:5:5) 11Ah - 1280x1024 64K (5:6:5) 11Bh - 1280x1024 16.8M (8:8:8) 11Ch - 1600x1200 256 5. CPU 显存窗口 一个标准 VGA 子系统提供 256k 字节显存和相应的访问地址。Super VGA 的某些模式需要 多于 256k 字节的显存但系统只能提供给它标准的 256k 地址范围(为了兼容的目的)。为了 解决这个问题,利用 CPU 显存窗口机制提供了对标准地址空间的访问扩展。 下面一段摘自 Bob Pendleton 写的关于显存窗口的文字 When the original IBM PC was designed 64k was a lot of memory and a megabyte was so big that slang for a meg was "Moby" as in the size of the great white whale. Designers of the PC allowed for 640k of RAM, 64k for color graphics and 64k for monochrome graphics. They were planning for the future. But, the future came and went and the sizes that seemed huge and very forward looking in the early 1980s seem tiny and short sighted today. We live in the future. We live way past what the designers planned for. VESA video mode 11Bh lets you display 1280 pixels by 1024 pixels with 24 bits (3 bytes) of color per pixel. An SVGA that supports mode 11Bh must have 3.75 megabytes of video memory. That's 6 times the 640k the original PC was designed to support and 60 times the 64k that is allocated for color graphics. Even a simple 1 meg card that supports the 1024x768 256 color mode has 16 times 64k of display memory. So, how do you pack a megabyte of display memory into the 64k allocated for color graphics? You break up the big memory into a bunch of pages that you can map into the 64k you have, one chunk at a time. The VESA document uses the term "memory windows" to describe this technique. I prefer to use the term "memory banks" to avoid confusion with graphics windowing systems like Windows and X. Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 245 Every SVGA provides some way to map its video memory into the PCs 64k graphics space one or more chunks at a time. The trouble is that they all do it in different ways. This is the most important problem the VBE solves. VBE gives you a way to select which bank is mapped in without having to now how each different SVGA does the mapping. It also tells you how many banks are available. To give an example of how the banks work consider how you draw a pixel at location x=537, y=323 in the 640 x 400 256 color mode. The address of the pixel in video memory is given by (width * y) + x. In this case that's (640 * 323) + 537 = 207,257. Assuming 64k byte banks this pixel is at byte 10649 in bank 3. So, to draw the pixel you use function 05h to map bank 3 into the PCs graphics space and store a byte 10649 bytes into the bank. Look in vg.cpp at the "pixel" function for details of how to do this. Not all SVGA cards support 64k banks. Having to deal with several different sizes and layouts of memory banks complicates programming with banks. But, Bennett's UNIVBE makes all banks look like they are 64k long. This is one of the nice features of UNIVBE. 5.1 硬件 5.1.1 64k/128k CPU 地址空间的限制 16 色图形模式下,标准的 VGA CPU 地址空间位于地址 A000 起始的 64k。 The standard VGA CPU address space for 16 color graphics modes is typically at segment A000h for 64k. This gives access to the 256k bytes of a standard VGA, i.e. 64k per plane. Access to the extended video memory is accomplished by mapping portions of the video memory into the standard VGA CPU address space. 每个 Super VGA 硬件都提供了将哪一段显寸映射到 CPU 地址空间的功能,通过设置显存的 起始 offset 来设定。 5.1.2 跨越 CPU 显寸范围 The organization of most software algorithms which perform video operations consists of a pair of nested loops: and outer loop over rows or scan lines and an inner loop across the row or scan line. The latter is the proverbial inner loop, which is the bottle neck to high performance software. 如果目标矩形太大或处在一个不好的位置,可能发生部分在 CPU 地址空间而部分在外面的 情况。带来的问题是软件不可能通过单一的二重循环来填充。 This is typically accomplished by selecting the mapping offset of the start of video memory to the start of the CPU address space so that at least one entire row or scan line can be processed without changing the video memory mapping. There are currently no Super VGAs that allow this offset to be specified on a byte boundary and there is a wide range among Super VGAs in the ability to position a desired video memory location at the start of the CPU address space. Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 246 The number of bytes between the closest two bytes in video memory that can be placed on any single CPU address is defined as the granularity of the window function. Some Super VGA systems allow any 4k video memory boundary to be mapped to the start of the CPU address space, while other Super VGA systems allow any 64k video memory boundary to be mapped to the start of the CPU address space. These two example systems would have granularities of 4k and 64k, respectively. This concept is very similar to the bytes that can be accessed with a 16 bit pointer in an Intel CPU before a segment register must be changed (the granularity of the segment register or mapping here is 16 bytes). Notes If the granularity is equal to the length of the CPU address space, i.e. the least significant address bit of the hardware mapping function is more significant than the most significant bit of the CPU address, then the inner loop will have to contain the test for crossing the end or beginning of the CPU address space. This is because if the length of the CPU address space (which is the granularity in this case) is not evenly divisible by the length of a scan line, then the scan line at the end of the CPU address will be in two different video memory which cannot be mapped into the CPU address space simultaneously. 5.1.3 Operating on data from different areas It is sometimes required or convenient to move or combine data from two different areas of video memory. One example of this is storing menus in the video memory beyond the displayed memory because there is hardware support in all VGAs for transferring 32 bits of video data with an 8 bit CPU read and write. Two separately mappable CPU video memory windows must be used if the distance between the source and destination is larger than the size of the CPU video memory window. 5.1.4 Combining data from two different windows The above example of moving data from one CPU video memory window to another CPU video memory only required read access to one window and only required write access to the other window. Sometimes it is convenient to have read access to both windows and write access to one window. An example of this would be a raster operation where the resulting destination is the source data logically combined with the original destination data. 5.2 不同类型的硬件窗口 Different hardware implementations of CPU video memory windows can be supported by the VESA BIOS extension. The information necessary for an application to understand the type of hardware implementation is provided by the BIOS to the application. There are three basic types of hardware windowing implementations and they are described below. The types of windowing schemes described below do not include differences in granularity. Also note that is possible for a VGA to use a CPU address space of 128k starting at segment A000h. Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 247 5.2.1 单窗口系统 Single window systems Some hardware implementations only provide a single window. This single window will be readable as well as writeable. However, this causes a significant performance degradation when moving data in video memory a distance that is larger than the CPU address space. 5.2.2 双窗口系统 Dual window systems Many Super VGAs provide two windows to facilitate moving data within video memory. There are two separate methods of providing two windows. 5.2.2.1 重叠窗口 Overlapping windows Some hardware implementations distinguish window A and window B by determining if the CPU is attempting to do a memory read or a memory write operation. When the two windows are distinguished by whether the CPU is trying to read or write they can, and usually do, share the same CPU address space. However, one window will be read only and the other will be write only. 5.2.2.2 非重叠窗口 Non-overlapping windows Another mechanism used by two window systems to distinguish window A and window B is by looking at the CPU address within the total VGA CPU address space. When the two windows are distinguished by the CPU address within the VGA CPU address space the windows cannot share the same address space, but they can each be both read and written. 6. 扩展的 VGA BIOS 定义了一些新的 BIOS 调用以支持 Super VGA 模式。为了寻求和标准 VGA BIOS 的最大兼 容,这些调用被归组成一个功能号,在调用 INT10h 的时候放在 AH 中。 Super VGA 扩展函数的功能号被定为 4Fh。标准 VGA BIOS 对 4Fh 的调用不作如何反应。 Super VGA 标准 VS900602 定义了子功能 00h 到 07h。子功能 08h 到 0ffh 保留供以后使用。 6.1 状态信息 每个函数(功能)都会返回一条状态信息存放在 AX 寄存器中,意义如下: AL == 4Fh: 函数被支持 Al != 4Fh: 函数不被支持 AH == 00h: 函数调用成功 AH == 01h: 函数调用失败 软件应当检查 AH 是否为 0 以确定调用是否成功。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 248 6.2 功能 00h – 返回 Super VGA 信息 该功能的作用是提供给调用程序关于 super VGA 的主要环境信息。返回的这些信息填充在 参数指定的数据结构(信息块)中。该数据结构大小为 256bytes。 Input: AH = 4Fh Super VGA support AL = 00h Return Super VGA information ES:DI = 指向返回信息块的指针 Output: AX = Status (All other registers are preserved) 信息块有以下结构: VgaInfoBlock STRUC VESASignature db 'VESA' ; 4 signature bytes VESAVersion dw ? ; VESA version number OEMStringPtr dd ? ; Pointer to OEM string Capabilities db 4 dup(?) ; capabilities of the video environment VideoModePtr dd ? ; pointer to supported Super VGA modes TotalMemory dw ? ; Number of 64kb memory blocks on board Reserved db 236 dup(?) ; Remainder of VgaInfoBlock VgaInfoBlock ENDS 若 VESASignature 域包含 'VESA' 字符串则说明块信息有效。 The VESAVersion is a binary field which specifies what level of the VESA standard the Super VGA BIOS conforms to. The higher byte specifies the major version number. The lower byte specifies the minor version number. The current VESA version number is 1.2. Applications written to use the features of a specific version of the VESA BIOS Extension, are guaranteed to work in later versions. The VESA BIOS Extension will be fully upwards compatible. The OEMStringPtr is a far pointer to a null terminated OEM-defined string. The string may used to identify the video chip, video board, memory configuration, etc. to hardware specific display drivers. There are no restrictions on the format of the string. The Capabilities field describes what general features are supported in the video environment. The bits are defined as follows: D0 = DAC is switchable 0 = DAC is fixed width, with 6-bits per primary color 1 = DAC width is switchable D1-31 = Reserved The VideoModePtr points to a list of supported Super VGA (VESA-defined as well as Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 249 OEM-specific) mode numbers. Each mode number occupies one word (16 bits). The list of mode numbers is terminated by a -1 (0FFFFh). Please refer to chapter 2 for a description of VESA mode numbers. The pointer could point into either ROM or RAM, depending on the specific implementation. Either the list would be a static string stored in ROM, or the list would be generated at run-time in the information block (see above) in RAM. It is the application's responsibility to verify the current availability of any mode returned by this Function through the Return Super VGA mode information (Function 1) call. Some of the returned modes may not be available due to the video board's current memory and monitor configuration. The TotalMemory field indicates the amount of memory installed on the VGA board. Its value represents the number of 64kb blocks of memory currently installed. 6.3 功能 01h – 返回 Super VGA 模式信息 该函数返回的信息会填充一个由参数指定地址的数据块。模式信息块大小最多为 256 bytes. Some information provided by this function is implicitly defined by the VESA mode number. However, some Super VGA implementations might support other video modes than those defined by VESA. To provide access to these modes, this function also returns various other information about the mode. Input: AH = 4Fh Super VGA support AL = 01h Return Super VGA mode information CX = Super VGA video mode (mode number must be one of those returned by Function 0) ES:DI = Pointer to 256 byte buffer Output: AX = Status (All other registers are preserved) 模式信息块的结构如下: ModeInfoBlock STRUC ; mandatory information ModeAttributes dw ? ; mode attributes WinAAttributes db ? ; window A attributes WinBAttributes db ? ; window B attributes WinGranularity dw ? ; window granularity WinSize dw ? ; window size WinASegment dw ? ; window A start segment WinBSegment dw ? ; window B start segment WinFuncPtr dd ? ; pointer to windor function BytesPerScanLine dw ? ; bytes per scan line Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 250 ; formerly optional information (now mandatory) XResolution dw ? ; horizontal resolution YResolution dw ? ; vertical resolution XCharSize db ? ; character cell width YCharSize db ? ; character cell height NumberOfPlanes db ? ; number of memory planes BitsPerPixel db ? ; bits per pixel NumberOfBanks db ? ; number of banks MemoryModel db ? ; memory model type BankSize db ? ; bank size in kb NumberOfImagePages db ? ; number of images Reserved db 1 ; reserved for page function ; new Direct Color fields RedMaskSize db ? ; size of direct color red mask in bits RedFieldPosition db ? ; bit position of LSB of red mask GreenMaskSize db ? ; size of direct color green mask in bits GreenFieldPosition db ? ; bit position of LSB of green mask BlueMaskSize db ? ; size of direct color blue mask in bits BlueFieldPosition db ? ; bit position of LSB of blue mask RsvdMaskSize db ? ; size of direct color reserved mask in bits DirectColorModeInfo db ? ; Direct Color mode attributes Reserved db 216 dup(?) ; remainder of ModeInfoBlock ModeInfoBlock ENDS The ModeAttributes field describes certain important characteristics of the video mode. Bit D0 specifies whether this mode can be initialized in the present video configuration. This bit can be used to block access to a video mode if it requires a certain monitor type, and that this monitor is presently not connected. Prior to Version 1.2 of the VESA BIOS Extension, it was not required that the BIOS return valid information for the fields after BytesPerScanline. Bit D1 was used to signify if the optional information was present. Version 1.2 of the VBE requires that all fields of the ModeInfoBlock contain valid data, except for the Direct Color fields, which are valid only if MemoryModel field is set to a 6 (Direct Color) or 7 (YUV). Bit D1 is now reserved, and must be set to a 1. Bit D2 indicates whether the BIOS has support for output functions like TTY output, scroll, pixel output, etc. in this mode (it is recommended, but not mandatory, that the BIOS have support for all output functions). If bit D2 is 1 then the BIOS must support all of the standard output functions. The field is defined as follows: D0 = Mode supported in hardware 0 = Mode not supported in hardware Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 251 1 = Mode supported in hardware D1 = 1 (Reserved) D2 = Output functions supported by BIOS 0 = Output functions not supported by BIOS 1 = Output functions supported by BIOS D3 = Monochrome/color mode (see note below) 0 = Monochrome mode 1 = Color mode D4 = Mode type 0 = Text mode 1 = Graphics mode D5-D15 = 保留 Note: Monochrome modes have their CRTC address at 3B4h. Color modes have their CRTC address at 3D4h. Monochrome modes have attributes in which only bit 3 (video) and bit 4 (intensity) of the attribute controller output are significant. Therefore, monochrome text modes have attributes of off, video, high intensity, blink, etc. Monochrome graphics modes are two plane graphics modes and have attributes of off, video, high intensity, and blink. Extended two color modes that have their CRTC address at 3D4h are color modes with one bit per pixel and one plane. The standard VGA modes 06h and 11h would be classified as color modes, while the standard VGA modes 07h and 0Fh would be classified as monochrome modes. The BytesPerScanline field specifies how many bytes each logical scanline consists of. The logical scanline could be equal to or larger then the displayed scanline. The WinAAttributes and WinBAttributes describe the characteristics of the CPU windowing scheme such as whether the windows exist and are read/writeable, as follows: D0 = Window supported 0 = Window is not supported 1 = Window is supported D1 = Window readable 0 = Window is not readable 1 = Window is readable D2 = Window writeable 0 = Window is not writeable 1 = Window is writeable D3-D7 = 保留 If windowing is not supported (bit D0 = 0 for both Window A and Window B), then an application can assume that the display memory buffer resides at the standard CPU address appropriate for the MemoryModel of the mode. WinGranularity specifies the smallest boundary, in KB, on which the window can be placed in Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 252 the video memory. The value of this field is undefined if Bit D0 of the appropriate WinAttributes field is not set. WinSize specifies the size of the window in KB. WinASegment and WinBSegment address specify the segment addresses where the windows are located in CPU address space. WinFuncAddr specifies the address of the CPU video memory windowing function. The windowing function can be invoked either through VESA BIOS function 05h, or by calling the function directly. A direct call will provide faster access to the hardware paging registers than using Int 10h, and is intended to be used by high performance applications. If this field is Null, then Function 05h must be used to set the memory window, if paging is supported. The XResolution and YResolution specify the width and height of the video mode. In graphics modes, this resolution is in units of pixels. In text modes, this resolution is in units of characters. Note that text mode resolutions, in units of pixels, can be obtained by multiplying XResolution and YResolution by the cell width and height, if the extended information is present. The XCharCellSize and YCharSellSize specify the size of the character cell in pixels. The NumberOfPlanes field specifies the number of memory planes available to software in that mode. For standard 16-color VGA graphics, this would be set to 4. For standard packed pixel modes, the field would be set to 1. The BitsPerPixel field specifies the total number of bits that define the color of one pixel. For example, a standard VGA 4 Plane 16-color graphics mode would have a 4 in this field and a packed pixel 256-color graphics mode would specify 8 in this field. The number of bits per pixel per plane can normally be derived by dividing the BitsPerPixel field by the NumberOfPlanes field. The MemoryModel field specifies the general type of memory organization used in this mode. The following models have been defined: 00h = Text mode 01h = CGA graphics 02h = Hercules graphics 03h = 4-plane planar 04h = Packed pixel 05h = Non-chain 4, 256 color 06h = Direct Color 07h = YUV 08h-0Fh = Reserved, to be defined by VESA 10h-FFh = To be defined by OEM In Version 1.1 and earlier of the VESA Super VGA BIOS Extension, OEM defined Direct Color Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 253 video modes with pixel formats 1:5:5:5, 8:8:8, and 8:8:8:8 were described as a Packed Pixel model with 16, 24, and 32 bits per pixel, respectively. In Version 1.2 and later of the VESA Super VGA BIOS Extension, it is recommended that Direct Color modes use the Direct Color MemoryModel and use the MaskSize and FieldPosition fields of the ModeInfoBlock to describe the pixel format. BitsPerPixel is always defined to be the total memory size of the pixel, in bits. The NumberOfBanks field specifies the number of banks in which the scan lines are grouped. The remainder from dividing the scan line number by the number of banks is the bank that contains the scan line and the quotient is the scan line number within the bank. For example, CGA graphics modes have two banks and Hercules graphics mode has four banks. For modes that don't have scanline banks (such as VGA modes 0Dh-13h), this field should be set to 1. The BankSize field specifies the size of a bank (group of scan lines) in units of 1KB. For CGA and Hercules graphics modes this is 8, as each bank is 8192 bytes in length. For modes that don't have scanline banks (such as VGA modes 0Dh-13h), this field should be set to 0. The NumberOfImagePages field specifies the number of additional complete display images that will fit into the VGA's memory, at one time, in this mode. The application may load more than one image into the VGA's memory if this field is non-zero, and flip the display between them. The Reserved field has been defined to support a future VESA BIOS extension feature and will always be set to one in this version. The RedMaskSize, GreenMaskSize, BlueMaskSize, and RsvdMaskSize fields define the size, in bits, of the red, green, and blue components of a direct color pixel. A bit mask can be constructed from the MaskSize fields using simple shift arithmetic. For example, the MaskSize values for a Direct Color 5:6:5 mode would be 5, 6, 5, and 0, for the red, green, blue, and reserved fields, respectively. Note that in the YUV MemoryModel, the red field is used for V, the green field is used for Y, and the blue field is used for U. The MaskSize fields should be set to 0 in modes using a MemoryModel that does not have pixels with component fields. The RedFieldPosition, GreenFieldPosition, BlueFieldPosition, and RsvdFieldPosition fields define the bit position within the direct color pixel or YUV pixel of the least significant bit of the respective color component. A color value can be aligned with its pixel field by shifting the value left by the FieldPosition. For example, the FieldPosition values for a Direct Color 5:6:5 mode would be 11, 5, 0, and 0, for the red, green, blue, and reserved fields, respectively. Note that in the YUV MemoryModel, the red field is used for V, the green field is used for Y, and the blue field is used for U. The FieldPosition fields should be set to 0 in modes using a MemoryModel that does not have pixels with component fields. The DirectColorModeInfo field describes important characteristics of direct color modes. Bit D0 specifies whether the color ramp of the DAC is fixed or programmable. If the color ramp is fixed, then it can not be changed. If the color ramp is programmable, it is assumed that the red, green, and blue lookup tables can be loaded using a standard VGA DAC color registers BIOS call Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 254 (AX=1012h). Bit D1 specifies whether the bits in the Rsvd field of the direct color pixel can be used by the application or are reserved, and thus unusable. D0 = Color ramp is fixed/programmable 0 = Color ramp is fixed 1 = Color ramp is programmable D1 = Bits in Rsvd field are usable/reserved 0 = Bits in Rsvd field are reserved 1 = Bits in Rsvd field are usable by the application Notes Version 1.1 and later VESA BIOS extensions will zero out all unused fields in the Mode Information Block, always returning exactly 256 bytes. This facilitates upward compatibility with future versions of the standard, as any newly added fields will be designed such that values of zero will indicate nominal defaults or non-implementation of optional features (for example, a field containing a bit-mask of extended capabilities would reflect the absence of all such capabilities). Applications that wish to be backwards compatible to Version 1.0 VESA BIOS extensions should pre-initialize the 256 byte buffer before calling Return Super VGA mode information. 6.4 Function 02h - Set Super VGA video mode This function initializes a video mode. The BX register contains the mode to set. The format of VESA mode numbers is described in chapter 2. If the mode cannot be set, the BIOS should leave the video environment unchanged and return a failure error code. Input: AH = 4Fh Super VGA support AL = 02h Set Super VGA video mode BX = Video mode D0-D14 = Video mode D15 = Clear memory flag 0 = Clear video memory 1 = Don't clear video memory Output: AX = Status (All other registers are preserved) 6.5 功能 03h – 返回当前视频模式 返回当前视频模式号,结果存在在 BX 中。 Input: AH = 4Fh Super VGA support AL = 03h Return current video mode Output: AX = Status BX = Current video mode (All other registers are preserved) Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 255 Notes In a standard VGA BIOS, function 0Fh (Read current video state) returns the current video mode in the AL register. In D7 of AL, it also returns the status of the memory clear bit (D7 of 40:87). This bit is set if the mode was set without clearing memory. In this Super VGA function, the memory clear bit will not be returned in BX since the purpose of the function is to return the video mode only. If an application wants to obtain the memory clear bit, it should call VGA BIOS function 0Fh. 6.6 功能 04h – 保存/恢复 Super VGA 视频状态 该功能用来保存和恢复 Super VGA 视频状态。该功能是标准 VGA BIOS 功能 1CH 的 3 个子 功能的超集。完整的 Super VGA 状态通过设置请求状态掩码(CX 寄存器)为 000Fh 来保存 或恢复。 Input: AH = 4Fh Super VGA support AL = 04h Save/restore Super VGA video state DL = 00h Return save/restore state buffer size CX = Requested states D0 = Save/restore video hardware state D1 = Save/restore video BIOS data state D2 = Save/restore video DAC state D3 = Save/restore Super VGA state Output: AX = Status BX = Number of 64-byte blocks to hold the state buffer (All other registers are preserved) Input: AH = 4Fh Super VGA support AL = 04h Save/restore Super VGA video state DL = 01h Save Super VGA video state CX = Requested states (see above) ES:BX = Pointer to buffer Output: AX = Status (All other registers are preserved) Input: AH = 4Fh Super VGA support AL = 04h Save/restore Super VGA video state DL = 02h Restore Super VGA video state CX = Requested states (see above) ES:BX = Pointer to buffer Output: AX = Status Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 256 (All other registers are preserved) Notes Due to the goal of complete compatibility with the VGA environment, the standard VGA BIOS function 1Ch (Save/restore VGA state) has not been extended to save the Super VGA video state. VGA BIOS compatibility requires that function 1Ch returns a specific buffer size with specific contents, in which there is no room for the Super VGA state. 6.7 Function 05h - CPU Video Memory Window Control This function sets or gets the position of the specified window in the video memory. The function allows direct access to the hardware paging registers. To use this function properly, the software should use VESA BIOS Function 01h (Return Super VGA mode information) to determine the size, location, and granularity of the windows. Input: AH = 4Fh Super VGA support AL = 05h Super VGA video memory window control BH = 00h Select Super VGA video memory window BL = Window number 0 = Window A 1 = Window B DX = Window position in video memory (in window granularity units) Output: AX = Status (See notes below) Input: AH = 4Fh Super VGA support AL = 05h Super VGA video memory window control BH = 01h Return Super VGA video memory window BL = Window number 0 = Window A 1 = Window B Output: AX = Status DX = Window position in video memory (in window granularity units) (See notes below) Notes This function is also directly accessible through a far call from the application. The address of the BIOS function may be obtained by using VESA BIOS Function 01h, Return Super VGA mode information. A field in the ModeInfoBlock contains the address of this function. Note that this function may be different among video modes in a particular BIOS implementation, so the function pointer should be obtained after each set mode. Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 257 In the far call version, no status information is returned to the application. Also, the AX and DX registers will be destroyed. Therefore, if AX and/or DX must be preserved, the application must do so priot to making the far call. The application must load the input arguments in BH, BL, and DX (for set window) but does not need to load either AH or AL in order to use the far call version of this function. 6.8 功能 06h -设置/获取逻辑扫描线长度 This function sets or gets the length of a logical scan line. This function allows an application to set up a logical video memory buffer that is wider than the displayed area. Function 07h then allows the application to set the starting position that is to be displayed. Input: AH = 4Fh Super VGA support AL = 06h Logical Scan Line Length BL = 00h Select Scan Line Length CX = Desired width in pixels Output: AX = Status BX = Bytes Per Scan Line CX = Actual Pixels Per Scan Line DX = Maximum Number of Scan Lines Input: AH = 4Fh Super VGA support AL = 06h Logical Scan Line Length BL = 01h Return Scan Line Length Output: AX = Status BX = Bytes Per Scan Line CX = Actual Pixels Per Scan Line DX = Maximum Number of Scan Lines Notes The desired width in pixels may not be achieveable because of VGA hardware considerations. The next larger value will be selected that will accommodate the desired number of pixels, and the actual number of pixels will be returned in CX. BX returns a value that, when added to a pointer into video memory, will point to the next scan line. For example, in a mode 13h this would be 320, but in mode 12h this would be 80. DX returns the number of logical scan lines based upon the new scan line length and the total memory installed and useable in this display mode. This function is also valid in text modes. In text modes, the application should find out the current character cell width through normal BIOS functions, multiply that times the desired number of characters per line, and pass the value in the CX register. 6.9 功能 07h – 设置/获取显示起始位置 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 258 This function selects the pixel to be displayed in the upper left corner of the display from the logical page. This function can be used to pan and scroll around logical screens that are larger than the displayed screen. This function can also be used to rapidly switch between two different displayed screens for double buffered animation effects. Input: AH = 4Fh Super VGA support AL = 07h Display Start Control BH = 00h Reserved and must be 0 BL = 00h Select Display Start CX = First Displayed Pixel in Scan Line DX = First Displayed Scan Line Output: AX = Status Input: AH = 4Fh Super VGA support AL = 07h Display Start Control BL = 01h Return Display Start Output: AX = Status BH = 00h Reserved and will be 0 CX = First Displayed Pixel in Scan Line DX = First Displayed Scan Line Notes This function is also valid in text modes. In text modes, the application should find out the current character cell width through normal BIOS functions, multiply that times the desired starting character column, and pass that value in the CX register. It should also multiply the current character cell height times the desired starting character row, and pass that value in the DX register. 6.10 功能 08h - Set/Get DAC 调色板控制 This function queries and selects the operating mode of the DAC palette. Some DACs are configurable to provide 6-bits, 8-bits, or more of color definition per red, green, and blue primary color. The DAC palette width is assumed to be reset to standard VGA 6-bits per primary during a standard or VESA Set Super VGA Mode (AX = 4F02h) call. Input: AH = 4Fh Super VGA support AL = 08h Set/Get DAC Palette Control BL = 00h Set DAC palette width BH = Desired number of bits of color per primary (Standard VGA = 6) Output: AX = Status Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 259 BH = Current number of bits of color per primary (Standard VGA = 6) Input: AH = 4Fh Super VGA support AL = 08h Set/Get DAC Palette Control BL = 01h Get DAC palette width Output: AX = Status BH = Current number of bits of color per primary (Standard VGA = 6) Notes An application can find out if DAC switching is available by querying Bit D0 of the Capabilities field of the VgaInfoBlock structure returned by VESA Return Super VGA Information (AX = 4F00h). The application can then attempt to set the DAC palette width to the desired value. If the Super VGA is not capable of selecting the requested palette width, then the next lower value that the Super VGA is capable of will be selected. The resulting palette width is returned. 6. 应用举例 The following sequence illustrates how an application interface to the VESA BIOS Extension. The hypothetical application is VESA-aware and calls the VESA BIOS functions. However, the application is not limited to supporting just VESA-defined video modes. This it will inquire what video modes are available before setting up the video mode. The application would first allocate a 256 byte buffer. This buffer will be used by the VESA BIOS to return information about the video environment. Some applications will statically allocate this buffer, others will use system calls to temporarily obtain buffer space. The application would then call VESA BIOS function 00h (Return Super VGA information). If the AX register does not contain 004Fh on return from the function call, the application can determine that the VESA BIOS Extension is not present and handle such situation. If no error code is passed in AX, the function call was successful. The buffer has been filled by the VESA BIOS Extension with various information. The application can verify that indeed this is a valid VESA block by identifying the characters 'VESA' in the beginning of the block. The application can inspect the VESAVersion field to determine whether the VESA BIOS Extension ha sufficient functionality. The application may use the OEMStringPtr to locate OEM-specific information. Finally, the application can obtain a list of the supported Super VGA modes by using the VideoModePtr. This field points to a list of the video modes supported by the video environment. The application would then create a new buffer and call the VESA BIOS function 01h (Return Super VGA mode information) to obtain information about the supported video modes. Using the VideoModePtr obtained in step 2) above, the application would call this function with a new mode number until a suitable video mode is found. If no appropriate video mode is found, it is up to the application to handle this situation. Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 260 The Return Super VGA mode information function fills a buffer specified by the application with information describing the features of the video mode. The data block contains all the information an application needs to take advantage of the video mode. The application would examine the ModeAttributes field. To verify that the mode indeed is supported, the application would inspect bit D0. If D0 is clear, then the mode is not supported by the hardware. This might happen is a specific mode requires a certain type of monitor but that monitor is not present. After the application has selected a video mode, the next step is to initialize the mode. However, the application might first want to save the present video mode. When the application exits, this mode would be restored. To obtain the present video mode, the VESA BIOS function 03h (Get Super VGA mode) would be used. If a non-VESA (standard VGA or OEM-specific) mode is in effect, only the lower byte in the mode number is filled. The upper byte is cleared. To initialize the video mode, the application would use VESA BIOS function 02h (Set Super VGA mode). The application has from this point on full access to the VGA hardware and video memory. When the application is about to terminate, it would restore the prior video mode. The prior video mode, obtained in step 4) above could be either a standard VGA mode, OEM-specific mode, or VESA-supported mode. It would reinitialize the video mode by calling VESA BIOS function 02h (Set Super VGA mode). The application would then exit. Hard copy Click on the parchment below to download the above in ASCII and HTML formats. Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 261 ============================================== Appendix E.Bochs 上运行的操作系统示范 ============================================== 1. MS- DOS V7.1 安装步骤: (a).使用 bximage.exe 创建硬盘映象文件,如一个 300M 大小的硬盘文件 c.img,参数如下: ata0-master: type=disk, path="E:\bochs\Bochs-2.2.1win\c.img", mode=flat, cylinders=609, heads=16, spt=63 (b). 用安装软盘或软盘的映象文件启动 VM。 floppya: 1_44=dos71.IMG, status=inserted boot: floppy (c) 安装后运行,安装过程中会提示格式化,按提示操作 运行界面: 2. Windows 95 安装步骤: (a) 与安装 DOS 相同 (b) 设置 Bochs 访问的安装光盘路径 ata0-slave: type=cdrom, path=h:, status=inserted (c) 用软盘启动 DOS 后,切换到光驱目录下安装。安装时间较长,请耐心等待。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 262 (b) 安装完成以后就可从虚拟机硬盘启动,配置如下 boot: disk Windows95 启动时的画面: 3. Windows 98 安装步骤与 Win95 相同,安装过程需要花更长的时间。 Windows 运行时的画面: 从下面一张图可以看到,Bochs 虚拟机已经实现了绝大部分的常规设备。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 263 使用 Windows98 系统信息查看工具,看到内容与源码中的 IO 地址分配情况是一致的。 Bochs 项目源码分析与注释 MAY 2006 Understanding the source code of bochs 264 THE END

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 5 人已下载

下载文档

相关文档