输入输出是计算机与外界交互的基本手段,只需要向设备发送一些有意义的数字信号,设备就会按照这些信号来工作。设备有自己的专属寄存器(如CPU的通用寄存器),也有自己的功能部件(如CPU的ALU)。以键盘外设为例,键盘有一个把按键的模拟信号转换成扫描码的部件,然后CPU根据扫描码就知道真实世界的用户按下了键盘的哪个按键了。除了纯粹的数据读写之外,我们还需要对设备进行控制,比如查看键盘是否有按键被按下。现在有个问题,CPU是如何访问设备的寄存器呢?答案是MMIO(內存映射I/O)

MMIO我个人理解就是把内存中一段固定的地址作为访问寄存器的接口,需要有控制判断访问的是否是这段地址,是的话就等价于访问对应的IO。这样的话CPU就可以通过普通的访存指令来访问设备。

map.h中定义了设备映射的结构体

typedef struct {
const char *name; // 设备名称
paddr_t low; // 映射区起始地址
paddr_t high; // 映射区结束地址
void *space; // 设备实际存储空间指针
io_callback_t callback; // 设备回调函数
} IOMap;

map.c中,实现了映射的管理,包括I/O空间的分配和映射,还有映射的访问接口。

在源码中定义了这样两边静态变量。

static uint8_t *io_space = NULL;
static uint8_t *p_space = NULL;

其中里面的io_space是指向整个IO设备映射空间的起始地址。

p_space是指向当前可分配空间的位置,每次分配设备空间后向后移动指针,类似堆指针。

paddr_read()和paddr_write()会判断地址addr落在物理内存空间还是设备空间, 若落在物理内存空间, 就会通过pmem_read()和pmem_write()来访问真正的物理内存; 否则就通过map_read()和map_write()来访问相应的设备. 从这个角度来看, 内存和外设在CPU来看并没有什么不同, 只不过都是一个字节编址的对象而已.map_read 和 map_write 可以用统一的方式模拟各种设备的寄存器访问,并通过回调函数实现设备的特殊行为,适合单线程仿真环境,非常方便地支持各种 I/O 设备的模拟。

设备

NEMU使用SDL库来实现设备的模拟, nemu/src/device/device.c含有和SDL库相关的代码. init_device()函数主要进行以下工作:

调用init_map()进行初始化.

对上述设备进行初始化, 其中在初始化VGA时还会进行一些和SDL相关的初始化工作, 包括创建窗口, 设置显示模式等;

然后会进行定时器(alarm)相关的初始化工作. 定时器的功能在PA4最后才会用到, 目前可以忽略它.

将输入输出抽象成IOE

IOE(抽象机 I/O 设备层)提供了三个统一的 API:

bool ioe_init();
用于初始化 IOE 相关的内容。 void ioe_read(int reg, void *buf);
用于从编号为 reg 的“抽象寄存器”读取内容到 buf。 void ioe_write(int reg, void *buf);
用于把 buf 的内容写入编号为 reg 的“抽象寄存器”。
void ioe_read (int reg, void *buf) { ((handler_t)lut[reg])(buf); }
void ioe_write(int reg, void *buf) { ((handler_t)lut[reg])(buf); }

可以看到ioe_read``ioe_write函数都调用了lut这个函数。

static inline void screen_refresh() {
io_write(AM_GPU_FBDRAW, 0, 0, NULL, 0, 0, true);
} static inline int screen_tile_height() {
return io_read(AM_GPU_CONFIG).height / TILE_W;
} static inline int screen_tile_width() {
return io_read(AM_GPU_CONFIG).width / TILE_W;
}

一般用法就是这样,根据amdev.h中定义的特殊寄存器及其该寄存器结构体中带的元素进行读取与写入等操作

AM_DEVREG( 1, UART_CONFIG,  RD, bool present);
AM_DEVREG( 2, UART_TX, WR, char data);
AM_DEVREG( 3, UART_RX, RD, char data);
AM_DEVREG( 4, TIMER_CONFIG, RD, bool present, has_rtc);
AM_DEVREG( 5, TIMER_RTC, RD, int year, month, day, hour, minute, second);
AM_DEVREG( 6, TIMER_UPTIME, RD, uint64_t us);
AM_DEVREG( 7, INPUT_CONFIG, RD, bool present);
AM_DEVREG( 8, INPUT_KEYBRD, RD, bool keydown; int keycode);
AM_DEVREG( 9, GPU_CONFIG, RD, bool present, has_accel; int width, height, vmemsz);
AM_DEVREG(10, GPU_STATUS, RD, bool ready);
AM_DEVREG(11, GPU_FBDRAW, WR, int x, y; void *pixels; int w, h; bool sync);
AM_DEVREG(12, GPU_MEMCPY, WR, uint32_t dest; void *src; int size);
AM_DEVREG(13, GPU_RENDER, WR, uint32_t root);
AM_DEVREG(14, AUDIO_CONFIG, RD, bool present; int bufsize);
AM_DEVREG(15, AUDIO_CTRL, WR, int freq, channels, samples);
AM_DEVREG(16, AUDIO_STATUS, RD, int count);
AM_DEVREG(17, AUDIO_PLAY, WR, Area buf);
AM_DEVREG(18, DISK_CONFIG, RD, bool present; int blksz, blkcnt);
AM_DEVREG(19, DISK_STATUS, RD, bool ready);
AM_DEVREG(20, DISK_BLKIO, WR, bool write; void *buf; int blkno, blkcnt);
AM_DEVREG(21, NET_CONFIG, RD, bool present);
AM_DEVREG(22, NET_STATUS, RD, int rx_len, tx_len);
AM_DEVREG(23, NET_TX, WR, Area buf);
AM_DEVREG(24, NET_RX, WR, Area buf);

串口

serial.c函数中模拟了串口的功能。

static void serial_putc(char ch) {
MUXDEF(CONFIG_TARGET_AM, putch(ch), putc(ch, stderr)); //如果没用am那就用标准io库,如果用了am那就用自己实现的putch
}

调用了putch的函数。

putchtrm.c中定义了函数,

void putch(char ch) { //输出一个字符
outb(SERIAL_PORT, ch);
}
static inline void outb(uintptr_t addr, uint8_t  data) { *(volatile uint8_t  *)addr = data; }

时钟

timer.c模拟了i8253计时器的功能. 计时器的大部分功能都被简化, 只保留了"发起时钟中断"的功能(目前我们不会用到). 同时添加了一个自定义的时钟. i8253计时器初始化时会分别注册0x48处长度为8个字节的端口, 以及0xa0000048处长度为8字节的MMIO空间, 它们都会映射到两个32位的RTC寄存器. CPU可以访问这两个寄存器来获得用64位表示的当前时间.

amdev.h为时钟定义了两个特殊寄存器,分别叫做AM_TIMER_RTC``AM_TIMER_UPTIME分别用于读出AM实时时钟和AM系统启动时间可以用来读出系统启动的秒数。

dtrace-设备访问的痕迹

word_t map_read(paddr_t addr, int len, IOMap *map) {
assert(len >= 1 && len <= 8);
check_bound(map, addr);
paddr_t offset = addr - map->low; //将物理地址转换为映射区域内的相对偏移量
invoke_callback(map->callback, offset, len, false); // 如果map->callback存在,调用它并传入参数(offset、len、false 表示读操作)。
//callback用于模拟硬件设备的副作用(例如,读取某个寄存器可能自动清除状态位)。
//map->space + offset:定位到映射区域中的目标地址。
//host_read:从指针处读取 len 字节并返回 word_t 类型的地址。
word_t ret = host_read(map->space + offset, len);
//如果启用调试(CONFIG_DTRACE),记录读取操作的设备名、地址和长度。
IFDEF(CONFIG_DTRACE, Log("read device %s : address in = " FMT_PADDR ", len = %d\n", map->name , addr, len));
return ret;
}
//其中map_read()和map_write()用于将地址addr映射到map所指示的目标空间, 并进行访问.
//每次进行I/O读写的时候, 才会调用设备提供的回调函数(callback).
void map_write(paddr_t addr, int len, word_t data, IOMap *map) {
assert(len >= 1 && len <= 8);
check_bound(map, addr);
paddr_t offset = addr - map->low;
host_write(map->space + offset, len, data);
invoke_callback(map->callback, offset, len, true);
IFDEF(CONFIG_DTRACE, Log("write device %s : address in = " FMT_PADDR ", len = %d\n", map->name , addr, len));
}

在KCONFIG中定义变量然后在读写的时候LOG出设备名字即可。

键盘

学习native的写法即可。

#define KEYDOWN_MASK 0x8000

void __am_input_keybrd(AM_INPUT_KEYBRD_T *kbd) {
uint32_t kc = inl(KBD_ADDR);
kbd->keydown = kc & KEYDOWN_MASK ? true : false;
kbd->keycode = kc & ~KEYDOWN_MASK;
}

VGA

abstract-machine/am/include/amdev.h中为GPU定义了五个抽象寄存器, 在NEMU中只会用到其中的两个:

AM_GPU_CONFIG, AM显示控制器信息, 可读出屏幕大小信息width和height. 另外AM假设系统在运行过程中, 屏幕大小不会发生变化.

AM_GPU_FBDRAW, AM帧缓冲控制器, 可写入绘图信息, 向屏幕(x, y)坐标处绘制w*h的矩形图像. 图像像素按行优先方式存储在pixels中, 每个像素用32位整数以00RRGGBB的方式描述颜色. 若sync为true, 则马上将帧缓冲中的内容同步到屏幕上.

也就是这两个寄存器,他带了一下这些参数。

AM_DEVREG( 9, GPU_CONFIG,   RD, bool present, has_accel; int width, height, vmemsz);
AM_DEVREG(11, GPU_FBDRAW, WR, int x, y; void *pixels; int w, h; bool sync);

一生一芯学习:PA2:输入输出的更多相关文章

  1. C++学习43 输入输出有关的类和对象

    输入和输出是数据传送的过程,数据如流水一样从一处流向另一处.C++形象地将此过程称为流(Stream).C++的输入输出流是指由若干字节组成的宇节序列,这些宇节中的数据按顺序从一个对象传送到另一对象. ...

  2. Linux学习之输入输出重定向

    转自:http://www.cnblogs.com/chengmo/archive/2010/10/20/1855805.html 多谢分享 在了解重定向之前,我们先来看看linux 的文件描述符. ...

  3. [深入学习C#]输入输出安全性——可变类型形參列表的变化安全性

    可变类型形參列表(variant-type-parameter-lists) 可变类型形參列表(variant-type-parameter-lists )仅仅能在接口和托付类型上出现.它与普通的ty ...

  4. Python学习之输入输出、数据类型

    #coding=utf-8 # 输入 print'100+200=',100+200 # 输入 # name = raw_input('tell me your name:') # print'hel ...

  5. MyBatis学习之输入输出类型

    1.  传递pojo对象 Mybatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称,其中,#{}:占位符号,好处防止sql注入,${}:sql拼接符号, 简要说明 ...

  6. Shell基础学习(七) 输入输出重定向

    命令 说明 command>file 将输出重定向到file command<file 将输入重定向到file command >> file 将输出追加到file n > ...

  7. VASP学习笔记--输入输出文件

    一.VASP 全称Vienna Ab-initio Simulation Package,是维也纳大学Hafner小组开发的进行电子结构计算和量子力学-分子动力学模拟软件包. 它是目前材料模拟和计算物 ...

  8. Python学习之输入输出入门 A+B篇

    描述 求两个整数之和. 输入 输入数据只包括两个整数A和B. 输出 两个整数的和. 样例输入 1 2 样例输出  3 a=input().split() print(int(a[0])+int(a[1 ...

  9. Linux下学习FPGA

    声明(叠甲):鄙人水平有限,本文章仅供参考. 1.环境 推荐使用 Ubuntu20.04这是我使用多个版本中最好用的一个,相关安装教程可以自行上网搜索这不再赘述,但要补充的一点的是源推荐使用中科大的源 ...

  10. paper 53 :深度学习(转载)

    转载来源:http://blog.csdn.net/fengbingchun/article/details/50087005 这篇文章主要是为了对深度学习(DeepLearning)有个初步了解,算 ...

随机推荐

  1. 定时执行shell 程序

    转载 ::!(博客园大咖)[http://www.cnblogs.com/kaituorensheng/p/4494321.html] 阅读目录 cron服务[Ubuntu环境] crontab用法 ...

  2. latex 转 word

    简介 用到 latex 转 word ,使用 pandoc 工具. 参考链接 https://www.zhihu.com/question/31850346/answer/279270892 使用命令 ...

  3. ETL数据集成丨将SQL Server数据同步至Oracle的具体实现

    一.背景 在构建企业级数据架构时,将SQL Server数据库的数据同步至数仓数据库(如Oracle)是一项至关重要的任务.这一过程不仅促进了跨系统数据的一致性与可用性,还为数据分析.商业智能以及决策 ...

  4. Infinity: Set Theory is the true study of Infinity

    AN INTRODUCTION TO SET THEORY - Professor William A. R. Weiss, October 2, 2008 Infinity -> Set Th ...

  5. fantasy-talking:实现图片加音频生成对嘴数字人

    引言:一张图也能"说话"? 你有没有想过,一张静态的照片,配上一段音频,就能变成一段"对嘴"的视频?不是简单的口型同步,而是让图片中的人物"活过来&q ...

  6. Exchange 2010 SSL证书安装文档

    在Microsoft Exchange 2010中安装和配置SSL证书是一个关键步骤,以确保邮件服务器的安全通信.以下是一个详细的教程,指导你完成整个过程. 第一步:获取SSL证书 1.购买并获取SS ...

  7. CentOS 7 上部署 OpenLDAP 服务

    以下是在 CentOS 7 上部署 OpenLDAP 服务并完成初始化的详细操作步骤,整合了最佳实践和关键注意事项: ​​一.环境准备​​ ​​系统更新与依赖安装​​ sudo yum update ...

  8. mysql中写sql的好习惯

    1 写完SQL先explain 查看执行计划 写完SQL,用explain分析一下,尤其注意走不走索引 explain select userid,name,age from user where u ...

  9. linux5.8下oracle10g安装和配置详解

    1新建yum仓库 如果有外网,可以配置阿里云的源,没外网,可以把光驱里的系统碟作为源仓库来安装一些oracle依赖的包: mount /dev/cdrom /mnt/ sed -i 's/gpgche ...

  10. 开源之夏 2022 重磅来袭!欢迎报名 Casbin社区项目!

    01 活动简介 "开源之夏(英文简称 OSPP)" 是中科院软件所 "开源软件供应链点亮计划" 指导下的一项面向高校学生的暑期活动,由中国科学院软件研究所与 o ...