Skip to content

Latest commit

 

History

History
132 lines (100 loc) · 7.4 KB

PA2.3 Talk.md

File metadata and controls

132 lines (100 loc) · 7.4 KB

授课内容 PA2.3

作者:南京航空航天大学 计算机科学与技术学院 金航

未经作者允许,禁止转载

输入输出

我们到现在的NEMU,只能进行计算,无法进行任何和外界的交互,因为不具有输入输出功能。

本节的任务:加入I/O设备以及I/O指令

现实中计算机外部设备的工作方式

每个设备都位于微处理器的某个(或多个)端口上,微处理器通过这个端口和设备进行交互。

控制设备运行:由微处理器通过OUT指令将数据送到设备的端口上,然后设备送该端口读走数据,得知CPU想让自己做什么事情。

反馈设备信息:设备总是将处理好的信息放到端口上,然后CPU通过IN指令从该端口处读入数据。

由此可知,x86提供了一组专门用于读写外部设备的指令:INOUT

设备的编址方式

直接编址

如例子给出的代码:

movl $0x0, %al
movl $0x3f9, %edx        // 0x3f9串口地址
outb %al, (%dx)

即是直接将数据,即AL寄存器的内容,送到0x3f9号端口上。这种编址方式诞生于8086时期,那个时候的许多设备,比如早期的打印机,以及早期8086机的若干周边控制器设备(如8254,8259)等都是直接使用INOUT指令直接编程控制的(现在有了驱动程序,由驱动程序控制外设)。

内存映射

将内存地址空间与I/O地址空间相关联,最典型的例子就是我们的显存。这种情况下,我们仅需要用正常的内存读写指令对内存进行操作,即在不知不觉中完成了对外部设备的操作(如绘图)。这种编程方式深受程序开发者喜爱。

NEMU与设备

我们知道,NEMU的实质是模拟的计算机硬件,那么在这个层面上,外部设备理所当然是连接在硬件上的。因此,NEMU的框架中已包含好了同样是被用代码模拟好的硬件。位于nemu/src/device目录下。包括:串口、时钟、键盘、VGA

理解NEMU对I/O的模拟(阅读代码)

第一个重要的代码文件

nemu/src/device/io/port-io.c中有对端口I/O的模拟,请自行阅读。需要注意的有:

  • 结构体PIO_t,里面记录着端口I/O的映射关系,其实例化的结构体数组为maps[],指示当前有多少个映射关系的变量为nr_map
  • add_pio_map()函数用于注册一个映射关系,其参数的意义依次为:端口映射关系的首地址注册的端口号个数(连续的)回调函数(即对该设备读写的时候调用的函数)
  • pio_read()函数用于CPU微处理器从设备端口读取数据
  • pio_write()函数用于CPU微处理器向设备端口写入数据
  • pio_space数组用于存放端口上的数据。

第二个重要的代码文件

nemu/src/device/device.c是NEMU整个设备模块的核心。通过SDL库模拟计算机的标准输入输出(还记得SDL库吗?在PA0末尾我们首次编译NEMU的时候是不是很多人没有安装SDL组件?)需要注意的有:

  • init_device()函数用于对设备进行初始化,因此需要在初始化NEMU的阶段调用。
  • device_update()负责设备的模拟操作,是整个NEMU控制设备的核心函数,该函数在cpu_exec()中的每趟循环都被调用一次,以刷新设备。

第三个重要的代码文件

稍后再来谈nemu/src/device/io/mmio.c是对内存I/O的模拟,我们在下面讲VGA和显存的时候再讲。

需要完成的任务

加入设备

  1. 认真读讲义 & 看Talk & 读框架代码 & 读近期问题汇总
  2. 定义宏HAS_DEVICE
  3. 调用init_device()对设备进行初始化

串口

  1. 输出的原理:通过串口输出,串口的实现见nemu\src\device\serial.c
  2. 实现INOUT两条指令(在nemu\src\cpu\exec\system.c中)
  3. 通过正常执行_putc()函数(该函数定义在trm.c中)成功运行nexus-am\apps\hello项目(记得先定义宏HAS_SERIAL,然后再在该路径下使用make run

时钟

  1. 理解原理:nemu/src/device/timer.c中模拟好了时钟的功能,当CPU需要读取时即把当前的时间放在端口上等待CPU取走(这里和真实机器的情况不同,真是机器是随时都把数据放到端口上,并不是要读的时候才计算并放置)。
  2. 我们需要在nexus-am\am\arch\x86-nemu\src\ioe.c实现_uptime()函数,原始的样子:
unsigned long _uptime() {
  return 0;
}
  1. 提示:把0修改为返回系统启动后经过的毫秒数即为正确实现,思考一下:什么和什么做差就是系统启动后的毫秒数呢?系统启动的时间又记录在哪里呢?答案就在ioe.c中,请自行思考。
  2. 成功运行timetest项目,让其每秒输出一句话。
  3. 成功运行跑分项目dhrystonecoremarkmicrobench

键盘

  1. 理解键盘的键盘码通码断码的概念。
  2. 仔细阅读讲义关于NEMU中键盘设备的工作原理
  3. 理解NEMU中对键盘设备的实现(在``中)
  4. 实现_read_key()函数,框架已给出,在ioe.c中:
int _read_key() {
  return _KEY_NONE;
}
  1. 提示:最少三条语句就可以实现。参考操作:检查键盘状态寄存器状态,是否有按键按下;若没有,返回NONE;若有,从键盘的数据寄存器读取并返回键盘码。
  2. 成功运行测试项目keytest

VGA与显示的内存映射(第三个重要的代码文件)

nemu/src/device/io/mmio.c文件里面是对内存I/O的模拟,请自行阅读。需要注意的有:

  1. 函数add_mmio_map()vga.c中进行初始化的时候被调用,用于申请一段显存地址空间。
  2. mmio_read()mmio_write()是给CPU拿来访问内存I/O的空间使用的,需要在memory.cpaddr_read()paddr_wirte()函数中,先对要访问的地址进行判断(提示:使用is_mmio()函数进行判断),然后再决定是否对应调用这两个函数进行显存I/O的访问。
  3. 实现_draw_rect()函数,该函数用来画矩形。
  4. 成功运行测试项目videotest

我们本着鼓励大家做的目的,给大家提供画矩形函数的思路

  1. 首先了解概念:我们通过在fb指针上取偏移找到该显存地址。
  2. 设置一个指针p指向fb内存空间
  3. 首先让p指向坐标(x,y)处。
  4. 写一个循环,我们按行来写显存数据,即遍历h趟(当然,有兴趣和耐心的同学可以考虑按列来写)。
  5. 每一趟我们把一行长度的显存数据复制到显存中对应区域。
  6. 注意时刻判断不要让你的x坐标或y坐标指向了屏幕大小以外的区域
  7. 温馨提示:框架中带的那个画矩形函数中原有的内容可以全删了,然后再开始写。

本阶段最终目标:运行打字小游戏

打字小游戏位于nexus-am/apps/typing目录,使用命令

make ARCH=x86-nemu run

来运行吧!


会灵活使用的部分函数和宏

nexus-am\am\arch\x86-nemu\include\x86.h中提供了inb()inl()outb()outl()四个函数帮大家通过函数的形式用内联汇编封装好了INOUT指令,直接调用他们吧(他们在编译时会被编译成INOUT指令后给CPU进行执行指令)!

别忘了回答讲义中的思考题和章末必答题!

不要放弃