跳转至

AM与IO

📅 1.31:对am的探究(3h)🧠

(上午)宏观层面对am的理解+类行政架构的计算机体系

所谓体系 * NEMU (硬件/国土): 地位:最底层,物理法则。 特征:纷繁复杂,充满细节(寄存器、时序),是“死”的客观存在。 * AM (抽象机/六部官僚): 地位:中间层,接口标准。 本质:HAL——hardware abstraction layer 历史意义:解耦————将软件逻辑与硬件实现彻底分离。复杂度降低————从 $N$ 个软件适配 $M$ 个硬件的 $N \times M$ 噩梦,变为 $N + M$ 的优雅架构。现实映射————对应 Windows 的 hal.dll 或 Linux 的 arch/ 目录,是操作系统“一次编写,到处运行”的基石。 特征:不是操作系统。它是一个库,把底层的复杂操作封装成统一的 API(如 halt, io_write)。它是工具箱,谁调用它,它就帮谁办事。 * OS (操作系统/宰相): 地位:管理者,拥有实权。 特征:是“活体”。它手握资源(CPU时间片、内存),通过调用 AM 来管理硬件,通过调度策略来安排程序。 * App (应用程序/垂帘听政的皇帝): 地位:最高层,需求方。 特征:被隔离。不能直接触碰硬件(NEMU),也不能直接指挥官僚(AM),必须通过“下旨”(System Call)让宰相(OS)代为执行。

(下午)微观层面对 guest program ---> abstract machine ---> NEMU 的研究

abstract machine的组成(key:turing machine)
程序的新面目——文件串联后经makefile自动化运行(key:linker.ld)

📆2.11~2.14 输入输出

MMIO救赎设备🔓🔑

端口IO因指令编码格式锁死,导致寻址空间受限且依赖特定寄存器,如同给设备发“特供指令”。 而MMIO的精髓在于“去特殊化”:利用总线和译码器的硬件欺骗,将设备伪装成内存。 这不仅打破了寻址限制,更让设备操作搭上了通用指令(MOV/Load/Store)和通用寄存器的便车,实现了软硬件解耦。

好的接口设计,就是让特殊对象看起来和普通对象一模一样。

语义鸿沟与volatile⚙️👨‍🔧

C 语言编译器的优化逻辑是对计算结果的追求(As-If Rule)(编程为了计算结果),即认为中间状态若不影响最终内存值便是多余的。 但在底层系统开发中,过程有意义且至关重要(编程为了触发过程)。MMIO 赋予了访存指令“副作用”(Side Effects),每一次读写都对应着硬件的电平跳变或状态迁移。 逻辑: 编译器逻辑:编译器视内存为单纯的数据容器,判定重复读写为脱裤子放屁并予以剔除。 优化结果:这种“类人逻辑”的优化,导致驱动设备所需的指令序列(如握手信号、状态轮询)在汇编层面被直接抹除,造成设备行为丢失。 volatile意义:volatile 强行关闭编译器智能优化,强制编译器放弃对特定变量的推断,确保 C 代码的每一次操作都严格映射为机器指令,从而弥合软件逻辑与硬件行为之间的语义鸿沟。

映射是什么🎞️🗂️

映射在行为上体现为地址分发。将 CPU 统一视角(地址)与异构硬件实体(具体设备)对应的机制。 逻辑: 分发与拦截:映射逻辑拦截 CPU 的访存请求,通过判断地址归属,将请求导向内存(读写数据)或设备(触发回调)。 副作用模拟:设备映射的核心是回调函数,它模拟了硬件的 Side Effects(如屏幕发光),将单纯的“数据搬运”转化为“设备控制”。 统一性:PIO 与 MMIO 本质一致,都是建立 地址 -> 行为 的键值对。 结论: 映射是 CPU 逻辑地址空间与物理硬件实体之间的接线员,决定了访问一个地址是“存数据”还是“搞事情”

输入输出——抽象层视角🔑🔓

设备驱动(AM)与硬件模拟(NEMU)的边界与交互 通过对 ioe 系统的分析,厘清了 NEMU 项目中软件与硬件的根本界限: * 甲方(Client/App): 用户程序(如游戏、测试用例)。 行为: 发起需求,调用抽象接口 io_read(AM_TIMER_UPTIME)。 特点: 不关心底层实现,只关心结果。 * 中介(AM - Abstract Machine): 驱动程序(Driver)。 行为: 将甲方的需求翻译为硬件指令。例如,将“读时间”翻译为“读取物理地址 0xa0000048”。 代码位置: am/src/platform/nemu/ioe/。 * 乙方(NEMU - Emulator): 硬件模拟器(Provider)。 行为: 被动响应。它不主动调用 IO,而是通过拦截 CPU 的访存请求(MMIO),识别出特定地址(如 0xa0000048),然后伪造数据返回给 CPU。

然而在这里感觉到一点逻辑的割裂:不可视的am与nemu交互,怎么能让客户需求经过am传递给nemu呢?ai说是通过地址,但是根本没有看到am层对nemu层的函数调用or变量传值啊`

于是引出下面的总结思考

编译链接的本质——从分层抽象到扁平指令🫓

突破: 理解软件分层与硬件执行的统一性 认知: 混乱与秩序的辩证 表象(秩序): 源代码层面,我们看到的是清晰的层级结构(User App -> Lib -> AM -> Hardware)。这是为了人类编写和维护方便。 本质(扁平): 编译链接层面,所有层级被暴力压扁。 User App 的高级请求被翻译成 AM 的函数调用。 AM 的函数实现被翻译成 裸机指令(Load/Store)。 最终产物: 一个线性的、连续的二进制指令流(Image)。 关键:以身入局 软件的宿命: 无论你在哪一层(App 还是 OS),最终都要变成指令,亲自跳进 CPU 的流水线里。 AM 的角色: 它不是在“呼唤”硬件,它是代表软件,把“读写设备”这个抽象动作,具象化为特定的访存指令(如 lw 0xa0000048) 交互的真相:指令即契约 没有神奇的“跨层通信(即我所谓的函数调用or变量传值)”: 只有 CPU 执行到特定指令(lw)时,触发了硬件(NEMU)预设的陷阱(MMIO)。 一句话总结: 此时,编译是将人类逻辑(分层)降维打击成机器逻辑(扁平)的过程;而运行则是硬件对这串扁平指令的忠实执行。

实现VGA过程中对软硬件协同的体会以及io链路的动态化认知🙏

  • 由 VGA 生发的“软硬协同”哲学体会 契约 (The Protocol): Downstream (软顺应硬): 软件必须遵守硬件已经定死的规则(比如高位是宽,低位是高)。 Upstream (硬服务软): 硬件必须提供机制来满足软件的需求(比如软件说“画完了”,硬件就得“刷新”)。
  • 对 I/O 核心链路(MMIO)的再认知 软件层发出需求,经过编译(细节可参考编译链接的本质——从分层抽象到扁平指令),给硬件读,读到地址以及指令中相关数据,分类(map路由),callback对应同步函数,同步函数(获得指令中参数)执行,对于执行的具体行为,不同设备之间存在差异
  • 路由机制 (The Routing): CPU 发出的每一个物理地址(Physical Address),都会经过一个“路由器”(在 NEMU 中是 mmio_space 链表)。 判断逻辑: if (addr >= map.low && addr <= map.high)---->命中! 回调的艺术 (The Callback): 一旦路由命中,硬件就不再把这当作普通的内存读写,而是劫持控制流,跳转到预设的 callback 函数(如 vga_ctl_io_handler)。 参数传递: 写入的数据(Data)变成了函数的参数,写入的地址偏移(Offset)变成了寄存器的索引。 全景图 (The Big Picture): 软件层: outl(addr, val) $\rightarrow$ 我以为我在写内存。 硬件层: map_write(addr, val) $\rightarrow$ 查表 $\rightarrow$ handler(offset, val) $\rightarrow$ 原来你在调函数!

对设备本身的一点理解(不是很重要)

时钟与时间🕛

物理基础:固定频率震动固定次数 晶振 (Crystal Oscillator):计算机计时的核心,一块石英晶体。利用压电效应,通电后会产生极其稳定的机械震动(例如 32.768 kHz 或 24 MHz)。 计数器 (Counter):负责数数。晶振每震动一次,计数器寄存器加 1。 从硬件看来,时间 = 震动次数 (Ticks)。 驱动程序的职责:在软件层抽象 软件定义的“秒”:操作系统或驱动程序通过读取硬件计数器的数值,结合已知的晶振频率 (Frequency),计算出人类可理解的时间。适配性:不同的硬件板卡可能使用不同频率的晶振。驱动程序必须根据硬件规格书(Spec)或设备树(Device Tree)来调整 NEMU 中的抽象 在 NEMU 模拟器中,硬件细节被抽象化。设计者规定了 RTC_ADDR 寄存器返回的数值单位固定为 微秒 (us)。 这相当于模拟了一个频率为 1 MHz 的完美晶振。 工程实现的严谨性 数据溢出:由于微秒数增长极快,32位整数只能存储约 71 分钟。必须使用 64 位整数存储。又为了设备能适应不同nemu架构,于是将时间相关数据的存取放在两个寄存器中来实现总64位(因为32位cpu无法一次访问64位的寄存器)

键盘的图形化窗口

在Windows中调用ubuntu终端本质上只是调用终端,相关图形化窗口等其他设备实现均在虚拟机上(虽然本质还是调用宿主机的硬件)