一生一芯学习:PA2:输入输出

news/2025/10/7 16:09:36/文章来源:https://www.cnblogs.com/yuweijie/p/19127154

一生一芯学习:PA2:输入输出

输入输出是计算机与外界交互的基本手段,只需要向设备发送一些有意义的数字信号,设备就会按照这些信号来工作。设备有自己的专属寄存器(如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 0x8000void __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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/930573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析:展会聚焦丨漫途科技亮相2025西北水务博览会!

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering() - 指南

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering() - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; d…

2025.10.7——2绿

普及+/提高- AT_agc031_b [AGC031B] Reversi 很简单的线性DP,很快就写出来了。 AT_abc406_f [ABC406F] Compare Tree Weights 树上的dfs序+树状数组,我被卡了好久……最后还因为树状数组写错功亏一篑了。

wordpress快站wordpress恢复边栏

问题描述&#xff0c;后台返回一个参数携带在url上面&#xff0c;发的时候是空格隔开的字符串&#xff0c;但是到了前端放到地址栏打开是一个号。 类似于 // 后台返回的url https://xxx.com?aaa bxxx // 打开浏览器后地址栏显示的 https://xxx.com?aaabxxx问了一下AI说是空…

完整教程:无人机避障——感知部分(Ubuntu 20.04 复现Vins Fusion跑数据集)胎教级教程

完整教程:无人机避障——感知部分(Ubuntu 20.04 复现Vins Fusion跑数据集)胎教级教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important…

姑苏区网站建设广州专业网站改版方案

企业们正在寻找新的方式来吸引和保持观众的注意力,一种新兴的解决方案就是使用Avatar虚拟形象技术&#xff0c;这种技术可以让用户在视频拍摄或直播场景中&#xff0c;以自定义的数字人形象出现&#xff0c;同时保持所有的表情和脸部驱动。美摄科技正是这个领域的领军者&#x…

我真的博了

其实标题指的是博弈论。 [AGC002E] Candy Piles 桌子上有 \(N\) 堆糖果。每堆糖果有 \(a_i\) 颗糖果。 Snuke 和 Ciel 正在玩游戏。他们轮流走。Snuke 先走。在每个回合中,当前玩家必须执行以下两个操作之一:选择剩余…

深入解析:人工智能-Chain of Thought Prompting(思维链提示,简称CoT)

深入解析:人工智能-Chain of Thought Prompting(思维链提示,简称CoT)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family:…

2025.10.6——1绿1蓝

普及+/提高- P3388 【模板】割点(割顶) Tarjan算法的模板题,但是代码不是我写的。 老师写的代码,有问题,我找出问题了。 提高+/省选- P1073 [NOIP 2009 提高组] 最优贸易 听完老师讲解后感觉这道题真的不难,很快…

红色网站呢网站备案查询 api

前言&#xff1a;预处理是我们的c语言源代码成为可执行程序的第一个步骤。而宏和预处理指令都是在这个阶段完成。本节内容就是关于宏和预处理指令相关知识点的解析。 目录 宏 预定义符号 #define定义常量 #define定义符号 #define定义宏 带副作用的宏参数 宏的替换规则…

深入解析:OpenCV CUDA模块图像处理------双边滤波的GPU版本函数bilateralFilter()

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

年龄排序

题目:样例:代码部分: include<stdio.h> int main() { int n; //人数 scanf_s("%d", &n); //结构体信息采集 struct patient { long id; int age; }; struct patient s1[10]; for (int i = 0;…

深入解析:Spring框架学习day3--Spring数据访问层管理(IOC)

深入解析:Spring框架学习day3--Spring数据访问层管理(IOC)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Conso…

转战web3远程工作的英语学习的路线规划 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

二分图最大匹配 输出具体方案

洛谷P2756 匈牙利算法: #include<bits/stdc++.h> using namespace std; const int N=110; int match[N],vis[N]; int n,m; vector<int> edges[N]; bool dfs(int u){for(int &v:edges[u]){if(vis[v])c…

我的联想小新潮7000笔记本的优化

500块钱,买了个笔记本,很卡。 优化一下: 1。内存升级,4G+16G=20G 2.硬盘升级:2G的固态+1G的机械 3.系统安装的时候,使用win10或者其他版本的纯净的版本 4.cpu频率增加。 注意:如果你不注意散热,这样可能电脑烧…

网站网页?问?高端营销网站定制

SHAP库概述 SHAP&#xff08;SHapley Additive exPlanations&#xff09;是一个Python库,用于解释任何机器学习模型的预测.它基于博弈论中的Shapley值概念,可以帮助用户理解模型预测中各个特征的贡献度. 安装与使用 # 命令安装SHAP库&#xff1a;pip install shap使用SHAP库…

Go语言之接口与多态 -《Go语言实战指南》 - 指南

Go语言之接口与多态 -《Go语言实战指南》 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", …

厦门网站建设费用嘉祥县建设局网站

阿里云服务器配置怎么选择&#xff1f;CPU内存、公网带宽和系统盘怎么选择&#xff1f;个人开发者或中小企业选择轻量应用服务器、ECS经济型e实例&#xff0c;企业用户选择ECS通用算力型u1云服务器、ECS计算型c7、通用型g7云服务器&#xff0c;阿里云服务器网aliyunfuwuqi.com整…

加强网站建设的请示英文建站平台

本专栏内容为&#xff1a;八大排序汇总 通过本专栏的深入学习&#xff0c;你可以了解并掌握八大排序以及相关的排序算法。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;八大排序汇总 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库…