TinyEMU源码分析之访存处理

TinyEMU源码分析之访存处理

  • 1 访存指令介绍
  • 2 指令译码
  • 3 地址转换
    • 3.1 VA与PA
    • 3.2 VA转PA
  • 4 判断地址空间范围
  • 5 执行访存操作
    • 5.1 访问RAM内存
    • 5.2 访问非RAM(设备)内存
  • 6 访存处理流程图

本文属于《 TinyEMU模拟器基础系列教程》之一,欢迎查看其它文章。

1 访存指令介绍

访存指令,主要有,如下这些:
在这里插入图片描述在这里插入图片描述
在RISC-V架构中,CPU在处理与内存访问相关的这些指令时,会发出对某地址的访问。这些指令通常涉及加载(Load)和存储(Store)操作,用于从内存中读取数据或将数据写入内存。

本文旨在,通过分析访存指令的执行,以理解CPU在执行指令时,是如何发出以及处理这些地址请求的。

2 指令译码

我们以ld指令(读取)为例,进行说明。

指令形式:ld rd, imm(rs1)
功能说明:rd = M[rs1+imm][0:63],表示从内存地址(rs1+imm)中,加载一个64位值到寄存器rd

取指译码,是在riscv_cpu_template.h的glue函数中完成,代码如下:

static void no_inline glue(riscv_cpu_interp_x, XLEN)(RISCVCPUState *s,int n_cycles1)
{for(;;) {...// 取指insn = get_insn32(code_ptr); // 译码执行funct3 = (insn >> 12) & 7;imm = (int32_t)insn >> 20;addr = s->reg[rs1] + imm;switch(funct3) {...case 3: /* ld */{uint64_t rval;if (target_read_u64(s, &rval, addr))goto mmu_exception;val = (int64_t)rval;}break;}...s->reg[rd] = val;}
}

我们在riscv_cpu.c的target_read_slow函数中,打断点:

b riscv_cpu.c:308 if addr>=0x80000000

再通过调用堆栈,回溯到glue函数中。
当TinyEMU对ld指令,进行译码执行时:

  • 从机器码insn中,提取imm;
  • 从机器码insn中,提取rs1,并计算出访存地址addr = reg[rs1] + imm。

然后,通过调用target_read_u64函数,读取addr地址内容,放入rd寄存器中。

这里,例子中,各值如下:

  • insn == 0xa767b783
  • rs1 == 0xf
  • imm == 0xfffffa76
  • addr == 0x80006d88

这里得到的addr,可能是虚拟地址,也可能是物理地址,我们统一当成虚拟地址看待即可,后续会进行转换。

3 地址转换

3.1 VA与PA

虚拟地址(Virtual Address):

  • 处理器生成的地址,用于在软件层面访问内存。虚拟地址空间是程序看到的内存视图,它可能远大于实际的物理内存大小。虚拟地址的主要目的是提供内存保护(通过隔离不同进程的地址空间)和简化内存管理(通过允许操作系统透明地管理物理内存)。

  • 在 RISC-V 系统中,虚拟地址通常通过内存管理单元(MMU)进行转换,以映射到物理地址。MMU 负责执行虚拟到物理地址的转换,同时检查访问权限和页面有效性。

物理地址(Physical Address):

  • 内存芯片实际使用的地址,用于定位特定的内存位置。物理地址空间是实际可用的 RAM 的大小,它受到硬件和操作系统的限制。

  • 在 RISC-V 中,当处理器需要访问内存时,它会生成一个虚拟地址。然后,MMU 会将这个虚拟地址转换为物理地址,处理器使用这个物理地址来访问实际的 RAM。

只有进入OS阶段,开启MMU后,才支持VA;在此之前,所有的访问全都为PA。

3.2 VA转PA

target_read_u64函数,是通过宏来定义的,在riscv_cpu_priv.h中:

#define TARGET_READ_WRITE(size, uint_type, size_log2)                   \
static inline __exception int target_read_u ## size(RISCVCPUState *s, uint_type *pval, target_ulong addr)                              \
{\uint32_t tlb_idx;\tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1);\if (likely(s->tlb_read[tlb_idx].vaddr == (addr & ~(PG_MASK & ~((size / 8) - 1))))) { \*pval = *(uint_type *)(s->tlb_read[tlb_idx].mem_addend + (uintptr_t)addr);\} else {\mem_uint_t val;\int ret;\ret = target_read_slow(s, &val, addr, size_log2);\if (ret)\return ret;\*pval = val;\}\return 0;\
}\
...TARGET_READ_WRITE(64, uint64_t, 3)

首先,会查询TLB中,是否有addr(VA)缓存:

  • 有的话,直接取出addr对应的PA。
  • 没有的话,则调用target_read_slow函数,继续查询。

在target_read_slow函数中,再调用get_phys_addr函数查询。

int target_read_slow(RISCVCPUState *s, mem_uint_t *pval,target_ulong addr, int size_log2)
{...// part1if (get_phys_addr(s, &paddr, addr, ACCESS_READ)) {s->pending_tval = addr;s->pending_exception = CAUSE_LOAD_PAGE_FAULT;return -1;}...
}

如果查询失败,会触发page fault异常,处理器接受到该异常后,会自动创建VA对应的页表。

static int get_phys_addr(RISCVCPUState *s,target_ulong *ppaddr, target_ulong vaddr,int access)
{// 当前是否运行在M模式if (priv == PRV_M) {*ppaddr = vaddr;return 0;}// 读取satp寄存器mode = (s->satp >> 60) & 0xf;if (mode == 0) {/* bare: no translation */*ppaddr = vaddr;return 0;} else {/* sv39/sv48 */levels = mode - 8 + 3;pte_size_log2 = 3;vaddr_shift = MAX_XLEN - (PG_SHIFT + levels * 9);if ((((target_long)vaddr << vaddr_shift) >> vaddr_shift) != vaddr)return -1;pte_addr_bits = 44;}// 页表查询pte_addr = (s->satp & (((target_ulong)1 << pte_addr_bits) - 1)) << PG_SHIFT;pte_bits = 12 - pte_size_log2;pte_mask = (1 << pte_bits) - 1;for(i = 0; i < levels; i++) {vaddr_shift = PG_SHIFT + pte_bits * (levels - 1 - i);pte_idx = (vaddr >> vaddr_shift) & pte_mask;pte_addr += pte_idx << pte_size_log2;if (pte_size_log2 == 2)pte = phys_read_u32(s, pte_addr);elsepte = phys_read_u64(s, pte_addr);//printf("pte=0x%08" PRIx64 "\n", pte);if (!(pte & PTE_V_MASK))return -1; /* invalid PTE */paddr = (pte >> 10) << PG_SHIFT;xwr = (pte >> 1) & 7;if (xwr != 0) {if (xwr == 2 || xwr == 6)return -1;/* priviledge check */if (priv == PRV_S) {if ((pte & PTE_U_MASK) && !(s->mstatus & MSTATUS_SUM))return -1;} else {if (!(pte & PTE_U_MASK))return -1;}/* protection check *//* MXR allows read access to execute-only pages */if (s->mstatus & MSTATUS_MXR)xwr |= (xwr >> 2);if (((xwr >> access) & 1) == 0)return -1;need_write = !(pte & PTE_A_MASK) ||(!(pte & PTE_D_MASK) && access == ACCESS_WRITE);pte |= PTE_A_MASK;if (access == ACCESS_WRITE)pte |= PTE_D_MASK;if (need_write) {if (pte_size_log2 == 2)phys_write_u32(s, pte_addr, pte);elsephys_write_u64(s, pte_addr, pte);}vaddr_mask = ((target_ulong)1 << vaddr_shift) - 1;*ppaddr = (vaddr & vaddr_mask) | (paddr  & ~vaddr_mask);return 0;} else {pte_addr = paddr;}}return -1;
}

这里有2种情况:

  • 如果运行在M模式下(运行固件/bios/bootloader),尚未启用MMU时,VA==PA,无需转换。
  • 如果运行在S模式下(运行OS),MMU已启用,则读取satp寄存器,并根据该寄存器中,保存的第一级页表基址,进行页表查询,最后得到PA。

具体页表查询原理,我们暂时不关心,只需要理解:

  • OS启动过程中,会创建第一级页表,并将其基址保存到satp寄存器,开启MMU。
  • 页表,是由OS建立的,保存在物理内存中,我们查询页表,其实就是在遍历内存,以便得到VA对应的PA。

4 判断地址空间范围

int target_read_slow(RISCVCPUState *s, mem_uint_t *pval,target_ulong addr, int size_log2)
{...// part2pr = get_phys_mem_range(s->mem_map, paddr);...
}
PhysMemoryRange *get_phys_mem_range(PhysMemoryMap *s, uint64_t paddr)
{PhysMemoryRange *pr;int i;for(i = 0; i < s->n_phys_mem_range; i++) {pr = &s->phys_mem_range[i];if (paddr >= pr->addr && paddr < pr->addr + pr->size)return pr;}return NULL;
}

上面转换得到的PA,然后,再调用get_phys_mem_range函数,判断PA到底属于以下地址空间中哪个范围。

在这里插入图片描述
也就是说,上面得到的PA地址,可能为0x0~0x88000000范围内的任意地址。

5 执行访存操作

int target_read_slow(RISCVCPUState *s, mem_uint_t *pval,target_ulong addr, int size_log2)
{...// part3else if (pr->is_ram) {tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1);ptr = pr->phys_mem + (uintptr_t)(paddr - pr->addr);s->tlb_read[tlb_idx].vaddr = addr & ~PG_MASK;s->tlb_read[tlb_idx].mem_addend = (uintptr_t)ptr - addr;switch(size_log2) {...case 3:ret = *(uint64_t *)ptr;break;}} else {offset = paddr - pr->addr;if (((pr->devio_flags >> size_log2) & 1) != 0) {ret = pr->read_func(pr->opaque, offset, size_log2);}
#if MLEN >= 64else if ((pr->devio_flags & DEVIO_SIZE32) && size_log2 == 3) {/* emulate 64 bit access */ret = pr->read_func(pr->opaque, offset, 2);ret |= (uint64_t)pr->read_func(pr->opaque, offset + 4, 2) << 32;}
#endif}*pval = ret;
}

这里分为2个分支,看PA是否为RAM地址(Low Dram和High Dram)。

  • 访问RAM内存
  • 访问非RAM(设备)内存

5.1 访问RAM内存

若PA为RAM地址时,进行以下计算:

ptr = pr->phys_mem + (uintptr_t)(paddr - pr->addr);

(1)计算出欲访问物理地址paddr,相对于RAM物理基址pr->addr的偏移,也就是paddr - pr->addr
(2)然后,再加上模拟器分配给RAM的内存基址(其实是Host机器的虚拟地址),也就是pr->phys_mem + (uintptr_t)(paddr - pr->addr)
(3)得到的结果ptr,就是指令欲访问VA,对应的ram内存地址。此时,还会将VA与PA对应关系,更新到TLB中,下次就可以直接查询TLB,而不用查页表了。

最后,通过指针,就可以取出相应长度内容了,通过pval将值返回。

5.2 访问非RAM(设备)内存

若PA为设备地址时,执行以下操作:

  • 计算出欲访问物理地址paddr,相对于该设备物理基址pr->addr的偏移,也就是offset = paddr - pr->addr
  • 然后,调用该设备对应的read_func函数,ret = pr->read_func(pr->opaque, offset, ...),以便读取该offset处的内容。
  • 最后,读取到的值,通过pval返回。

比如CLINT,在riscv_machine_init函数中,划分地址空间时,就将clint_read、clint_write函数指针进行了保存(读写函数、设备地址范围等,进行了捆绑),以便在后续,需要处理该设备地址范围内访问时,可以直接调用该函数。

static VirtMachine *riscv_machine_init(const VirtMachineParams *p)
{...// 划分地址空间cpu_register_device(s->mem_map, CLINT_BASE_ADDR, CLINT_SIZE, s,clint_read, clint_write, DEVIO_SIZE32);cpu_register_device(s->mem_map, PLIC_BASE_ADDR, PLIC_SIZE, s,plic_read, plic_write, DEVIO_SIZE32);cpu_register_device(s->mem_map, HTIF_BASE_ADDR, 16,s, htif_read, htif_write, DEVIO_SIZE32);...
}

比如,PA为0x2004000,写操作的话,那么就是,表示向CLINT中mtimecmp(定时器比较寄存器)写入一个值。mtimecmp是一个内存映射寄存器,定义在CLINT中断控制器中。

clint_write函数定义,如下:

static void clint_write(void *opaque, uint32_t offset, uint32_t val,int size_log2)
{...switch(offset) {case 0x4000:m->timecmp = (m->timecmp & ~0xffffffff) | val;riscv_cpu_reset_mip(m->cpu_state, MIP_MTIP);break;...}
}

此时,计算出的offset为0x4000,然后调用riscv_cpu_reset_mip函数,将val值写入了mip寄存器。

其他的设备,也是类似的原理。

6 访存处理流程图

因此,归根到底,CPU在执行指令时,发出的访问地址:

  • 如果在RAM范围内,表示访问物理内存;
  • 如果在设备范围内,表示访问该设备内部的寄存器或内存资源。

关于访存的处理过程,整理为流程图,如下:
在这里插入图片描述

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

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

相关文章

【AI】什么是Ai Agent

什么是AI Agent&#xff1f; AI Agent是指人工智能代理&#xff08;Artificial Intelligence Agent&#xff09;是一种能够感知环境进行自主理解&#xff0c;进行决策和执行动作的智能体。AI Agent具备通过独立思考、调用工具逐步完成给定目标的能力。不同于大模型的区别在于&…

OpenHarmony实战开发-如何使用屏幕属性getDefaultDisplaySync、getCutoutInfo接口实现适配挖孔屏。

介绍 本示例介绍使用屏幕属性getDefaultDisplaySync、getCutoutInfo接口实现适配挖孔屏。该场景多用于沉浸式场景下。 效果图预览 使用说明 1.加载完成后顶部状态栏时间和电量显示位置规避了不可用区域。 实现思路 1.通过setWindowLayoutFullScreen、setWindowSystemBarEn…

代码随想录训练营

Day23代码随想录 669.修剪二叉搜索树 1.题目描述 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即&#xff0c;如果没有…

Hbase的简单学习一

一 Hbase的搭建与安装 1.1 安装 1.准备好文件&#xff0c;上传到Linux上 2.解压文件 tar zxvf hbase-2.2.7-bin.tar.gz -C ../ ../是解压到的路径 1.2 配置文件 1.配置环境变量 去etc/profile目录下 export HBASE_HOME/usr/local/soft/hbase-2.2.7 export PATH$PATH:$H…

.NET SignalR Redis实时Web应用

环境 Win10 VS2022 .NET8 Docker Redis 前言 什么是 SignalR&#xff1f; ASP.NET Core SignalR 是一个开放源代码库&#xff0c;可用于简化向应用添加实时 Web 功能。 实时 Web 功能使服务器端代码能够将内容推送到客户端。 适合 SignalR 的候选项&#xff1a; 需要从服…

AI预测福彩3D第38弹【2024年4月17日预测--第8套算法开始计算第6次测试】

今天咱们继续测试第8套算法和模型&#xff0c;今天是第5次测试&#xff0c;目前的测试只是为了记录和验证&#xff0c;为后续的模型修改和参数调整做铺垫&#xff0c;所以暂时不建议大家盲目跟买~废话不多说了&#xff0c;直接上结果&#xff01; 2024年4月17日3D的七码预测结果…

K8S node节点执行kubectl get pods报错

第一个问题是由第二个问题产生的&#xff0c;第二个问题也是最常见的 网上找的都是从master节点把文件复制过来&#xff0c;这样确实可以解决&#xff0c;但是麻烦&#xff0c;有一个node节点还好&#xff0c;如果有多个呢&#xff1f;每个都复制吗&#xff1f;下面是我从外网…

RabbitMQ-核心特性

已经不需要为RabbitMQ交换机的离去而感到伤心了&#xff0c;接下来登场的是RabbitMQ-核心特性!!! 文章目录 核心特性消息过期机制消息确认机制死信队列 核心特性 消息过期机制 官方文档&#xff1a;https://www.rabbitmq.com/ttl.html 可以给每条消息指定一个有效期&#xf…

Ubuntu 20.04.06 PCL C++学习记录(二十五)

[TOC]PCL中点云分割模块的学习 学习背景 参考书籍&#xff1a;《点云库PCL从入门到精通》以及官方代码PCL官方代码链接,&#xff0c;PCL版本为1.10.0&#xff0c;CMake版本为3.16&#xff0c;可用点云下载地址 学习内容 使用渐进形态滤波器分割识别地面回波&#xff0c;即执…

【一竞技CS2】VP战队官宣签下electroNic取代mir

1、近日VP战队官宣签下electroNic&#xff0c;以取代阵容中的mir。 electroNic自己也表示&#xff1a;“VP是一支顶级队伍。阵容核心曾赢得Major冠军&#xff0c;所有队员都处于巅峰状态并且时刻准备着去争夺冠军。我们有着一样的雄心壮志。 此外我还对和Jame很感兴趣&#xf…

C++从入门到精通——const与取地址重载

const与取地址重载 前言一、const正常用法const成员函数问题const对象可以调用非const成员函数吗非const对象可以调用const成员函数吗const成员函数内可以调用其它的非const成员函数吗非const成员函数内可以调用其它的const成员函数吗总结 二、取地址及const取地址操作符重载概…

如何用Jenkins执行自动化测试构建

摘要 依据Jenkins官网介绍&#xff0c;Jenkins是一个流行的开源持续集成和交付工具&#xff0c;它提供了一个可扩展的插件生态系统&#xff0c;可以用于自动化构建、测试和部署软件项目。 本文介绍如何安装使用Jenkins、常见问题解决方案以及深入应用&#xff0c;为自动化测试…

艾迪比皮具携手工博科技SAP ERP公有云,打造数字化转型新标杆

4月1日&#xff0c;广州市艾迪比皮具有限公司&#xff08;以下简称“艾迪比”&#xff09;SAP S/4HANA Cloud Public Edition&#xff08;以下简称“SAP ERP公有云”&#xff09;项目正式启动。双方项目组领导、成员出席本次项目启动会&#xff0c;为未来项目的顺利实施打下坚实…

滚雪球学Java(74):深入理解JavaSE输入输出流:掌握数据流动的奥秘

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴 bug菌&#xff0c;今天又来给大家手把手教学Java SE系列知识点啦&#xff0c;赶紧出来哇&#xff0c;别躲起来啊&#xff0c;听我讲干货记得点点赞&#xff0c;赞多了我就更有动力讲得更欢哦&#xff01;所以呀&…

nginx学习记录-动静分离

1. 动静分离原理 我们在访问网站资源的时候&#xff0c;通常会将资源分成两种&#xff0c;一种是静态资源&#xff08;前端的固定界面&#xff0c;比如图片&#xff0c;html页面等&#xff09;&#xff0c;这些资源无需后台程序处理&#xff1b;另一种是动态资源&#xff0c;这…

分布式调度器

xxl-job介绍 xxl-job 是一个轻量级分布式任务调度框架&#xff0c;支持动态添加、修改、删除定时任务&#xff0c;支持海量任务分片执行&#xff0c;支持任务执行日志在线查看和分页查询&#xff0c;同时支持任务失败告警和重试机制&#xff0c;支持分布式部署和高可用。xxl-j…

阿里云、腾讯云、华为云优惠券领取入口整理汇总

阿里云、腾讯云、华为云作为国内领先的云服务提供商&#xff0c;一直以其稳定、高效、安全的服务赢得了广大用户的青睐。为了回馈用户&#xff0c;这些云平台经常会推出各种优惠活动&#xff0c;其中最为常见的便是优惠券。本文将为大家整理汇总阿里云、腾讯云、华为云优惠券的…

linux-centos虚拟机设置固定ip

环境准备 虚拟机版本&#xff1a;centos7 安装环境&#xff1a;vmware17 1、设置网络连接 虚拟机-设置-网络适配器-NAT模式 2、查看子网信息 编辑-虚拟网络编辑器-NAT模式-NAT设置 查看子网ip和网关ip 下一步要用 3、修改配置文件 vim /etc/sysconfig/network-scripts…

构建数据平台架构指导原则与平台核心组件说明

文章目录 前言什么是数据架构&#xff1f;数据架构如何帮助构建数据平台&#xff1f;数据平台核心组件数据源系统数据加载数据存储数据处理和转换提供使用数据的方式公共服务 前言 湖仓一体是最近几年非常流行的现代大数据架构&#xff0c;目前它已经成为设计数据平台架构的首…

Flask框架初探-如何在本机发布一个web服务并通过requests访问自己发布的服务-简易入门版

Flask框架初探 在接触到网络框架之前我其实一直对一个事情有疑惑&#xff0c;跨语言的API在需要传参的情况下究竟应该如何调用&#xff0c;之前做过的项目里&#xff0c;我用python做了一个代码使用一个算法得到一个结果之后我应该怎么给到做前端的同学或者同事&#xff0c;之前…