gem5

返回主页
返回上一页

gem5_source_code
gem5_source_code gem5_source_code

gem5::Process Class Refference

gem5本身是用C++编写的,并不直接支持Python作为脚本语言。为了增加灵活性和易用性,gem5可以通过Pybind将部分C++代码暴露给Python。这样,用户可以使用Python编写脚本来控制gem5的行为、设置模拟参数、执行实验等


Pybind

Pybind是一个用于将C++代码绑定到Python的开源库。它允许开发者通过简单的方式创建Python模块,将现有的C++代码暴露给Python解释器,使得这些C++代码可以像Python代码一样被调用和使用。

无需手动编写Python扩展模块

  • Pybind提供了一种简单的方法来将C++类、函数、变量等直接暴露给Python,而无需编写复杂的Cython或者手动编写Python扩展模块的代码。

类型安全和高效

  • Pybind生成的绑定代码保留了C++代码的类型安全性和性能,因此Python代码在调用C++函数时不会出现类型错误,并且可以享受到C++代码的高效执行速度。

支持C++11及以上标准

  • Pybind支持现代C++的特性,如lambda函数、智能指针等,这些特性可以直接在Python中使用。

无缝集成

  • Pybind可以与其他Python库(如NumPy、SciPy等)无缝集成,使得使用C++编写的高性能计算核心可以方便地被Python脚本调用。

广泛应用

  • Pybind在科学计算、机器学习、图形处理等领域得到了广泛应用,特别是在需要高性能计算或者现有C++代码库的项目中,Pybind提供了一个快速、便捷的Python接口。

 

示例1:

在这个示例中,add 函数将被绑定到Python模块 example 中,Python代码可以通过 import example 导入并调用 add 函数。

 

示例2:

 


page_table

gem5: mem/page_table.cc Source File

map

Parameters

vaddrThe starting virtual address of the range.
paddrThe starting physical address of the range.
sizeThe length of the range in bytes.
cacheableSpecifies whether accesses are cacheable.

 

clobber定义在Line98 in page_table.hh

Clobber(值为1):表示覆盖已经存在的映射。

Uncacheable(值为4):表示映射的内存区域是不可缓存的。

ReadOnly(值为8):表示映射的内存区域是只读的。

 

_pageSizeconst Addr 类型 定义在Line70 in page_table.hh

 

remap

用于重新映射虚拟地址范围,将原来的虚拟地址范围 vaddr 移动到新的虚拟地址范围 new_vaddr

 

getMappings

用于获取当前页表中的所有虚拟地址和物理地址的映射,并将这些映射存储在一个 std::vector

addr_maps 是一个指向 std::vector<std::pair<Addr, Addr>> 的指针,这个向量用于存储从虚拟地址到物理地址的映射对。

pTable 是一个 std::unordered_map<Addr, Entry>,其中 Addr 是虚拟地址,Entry 是一个包含映射信息的类。

iter 是一个引用,指向 pTable 中的当前元素,它是一个键值对(key-value pair)。

iter.first 是虚拟地址(键)。

iter.second 是一个 Entry 对象,包含映射信息。

iter.second.paddr 是物理地址。

使用 std::make_pair 创建一个包含虚拟地址和物理地址的 std::pair<Addr, Addr> 对象,然后将这个对象添加到 addr_maps 向量中。

 

unmap

用于从页表中取消映射一段虚拟地址范围

isUnmapped

用于检查给定的虚拟地址范围是否未映射

 

lookup

用于在页表中查找给定虚拟地址的映射,并返回指向该映射的指针。如果没有找到对应的映射,则返回 nullptr

这种查找操作对于模拟环境中的地址转换和内存管理非常有用

const EmulationPageTable::Entry * 表示返回一个指向 EmulationPageTable::Entry 类型的常量指针。

 

translate (bool)

用于将虚拟地址转换为物理地址。如果转换成功,返回 true 并通过引用参数 paddr 返回物理地址;如果转换失败,返回 false

 

translate (Fault)

用于将请求中的虚拟地址转换为物理地址。如果转换失败,则返回一个新的 GenericPageTableFault 对象;如果成功,则设置请求的物理地址,并检查请求是否跨越页面边界

  • Details

为什么(paddr & (_pageSize - 1))可以计算物理地址在页面中的偏移量 ?

在计算机系统中,内存通常是按页面(或页)来管理的,每一页有固定的大小,例如 4KB、8KB 或者其他大小。在这种情况下,页的大小通常用 _pageSize 表示。

  1. 页面大小和偏移量

  • 假设 _pageSize 是页面的大小(通常是 4KB,即 4096 字节)。

  • 页面大小为 4096 字节,其二进制表示为 1000000000000

  1. 位运算中的与操作(&)

  • 在二进制中,一个数减去 1,数字的最低有效位的1会变成0,而这个1之后的所有0都会变成1,其它位保持不变

  1. 应用

  • (paddr & (_pageSize - 1)),即 paddr 与页面大小减 1 的结果进行与运算,得到的是 paddr 在页面内偏移的部分,因为这个操作会清除掉 paddr 最高有效位(即大于等于页面大小的部分),只保留页面大小范围内的偏移部分。

e.g.

当页面大小为 4KB(4096 字节)时,假设物理地址 paddr0x12345,我们来计算它在页面中的偏移量。

_pageSize = 4096(1 0000 0000 0000)

paddr = 0x12345

页面大小减 1 的二进制表示是 0 1111 1111 1111,即十六进制表示是 0xFFF

(paddr & (_pageSize - 1)) 等价于 (0x12345 & 0xFFF)

 

translate (PageTableTranslationGen)

这段代码的主要功能是将给定的地址范围 range 进行页面对齐,然后尝试将每个地址翻译为物理地址。如果翻译失败,它将生成一个对应的错误对象,并将其记录在 range.fault

 

serialize

EmulationPageTable 对象的数据序列化到 CheckpointOut 对象 cp

这种序列化方法通常用于将对象的状态保存到文件或者网络传输中,以便稍后能够重新加载该对象的状态。

 

unserialize

CheckpointIn 对象 cp 中反序列化数据,恢复 EmulationPageTable 对象的状态

这种反序列化方法通常用于从文件或网络接收的数据中恢复对象的状态,以便后续使用。

 

externalize

EmulationPageTable 对象的状态外部化为一个字符串

通过迭代 pTable 中的每个表项,将虚拟地址和物理地址格式化为十六进制字符串,并用 ":" 和 ";" 分隔符组合成一个字符串序列

这种方法常用于将对象状态以文本格式输出,便于打印、调试或者与其他系统交互。

对每个表项执行以下操作:

  • std::hex:设置流输出为十六进制格式。

  • it->first:获取当前表项的虚拟地址(键)。

  • it->second.paddr:获取当前表项的物理地址。

  • 将虚拟地址和物理地址格式化为字符串,并用 ":" 分隔。

  • 使用 ";" 分隔每个表项的输出,形成一个格式化的字符串序列。

 

 

Process

gem5: sim/process.cc Source File

process.hh

 

process.cc

anonymous namespace

  1. namespace { ... }:这是一个匿名命名空间。匿名命名空间中的内容在该编译单元内是私有的,也就是说,它们不会与其他编译单元中的同名实体发生冲突。

  2. typedef std::vector<Process::Loader *> LoaderList;

    • 这里定义了一个类型别名LoaderList,它代表了一个std::vector,其中存储的是指向Process::Loader对象的指针。

  3. LoaderList & process_loaders() { ... }

    • 这是一个返回LoaderList引用的函数。

    • 该函数的作用是提供对一个静态局部变量loaders的引用。

  4. static LoaderList loaders;

    • 在函数内部定义了一个静态局部变量loaders,它的类型是LoaderList。静态局部变量在函数的所有调用中保持其值,且只在程序的生命周期内初始化一次。

  5. return loaders;

    • 函数返回对loaders变量的引用。

完整地看,这段代码的作用是定义了一个匿名命名空间,内部包含了一个LoaderList类型的静态变量loaders,以及一个函数process_loaders,该函数返回loaders的引用。由于匿名命名空间的存在,这些定义在该编译单元内是私有的,不会与其他编译单元中的同名实体冲突。

总结起来,这段代码可以实现一个单例模式的容器,用于存储Process::Loader的指针集合,并确保这个容器在整个程序运行期间是唯一且可访问的。

匿名命名空间(anonymous namespace)是一种在C++中用于限制命名空间中的标识符(变量、函数、类型等)的作用域仅在当前编译单元(通常是单个源文件)内的方法。使用匿名命名空间可以防止标识符与其他编译单元中的同名标识符发生冲突,增强封装性和代码模块化。

在C++中,匿名命名空间通过namespace { ... }语法来定义。匿名命名空间内的所有内容在当前编译单元内是私有的,不会暴露给其他编译单元。

下面是一个示例,帮助理解匿名命名空间的使用:

在这个示例中,secret_number变量和print_secret函数都定义在匿名命名空间内。它们在当前编译单元内是私有的,无法被其他源文件访问。

 

在这个示例中,有一个全局变量global_number,一个在MyNamespace命名空间中的变量namespace_number和函数print_namespace_number,以及一个在匿名命名空间中的变量local_number和函数print_local_number。匿名命名空间中的内容仅在当前编译单元内可见,其他源文件无法访问或引用这些内容。

  • 匿名命名空间适用于那些只在单个源文件中使用的实体,而普通命名空间适用于需要在多个源文件中共享的实体。

 

Loader::Loader()

Process::Loader 类的每个对象构造时,将对象的指针添加到一个静态 LoaderList 容器中。这种设计可以用于跟踪和管理所有已创建 Process::Loader 对象的集合,这在需要动态管理对象集合时非常有用。

 

tryLoaders

使用一组加载器(存储在 process_loaders() 返回的容器中),尝试加载给定的对象文件 obj_file 中的进程信息。它会逐个调用加载器的 load 函数,直到找到一个能够成功加载的加载器为止,然后返回相应的 Process 对象指针;如果所有加载器都无法成功加载,则返回 nullptr

这种设计适用于需要动态选择加载器,并且希望在加载成功时立即返回的情况。

  • Process * Process::tryLoaders(const ProcessParams &params, loader::ObjectFile *obj_file)Process 类的一个成员函数,返回类型为 Process*,接受两个参数:paramsProcessParams 类型的引用,obj_fileloader::ObjectFile 类型的指针。

  • for (auto &loader_it : process_loaders()):这是一个范围循环(range-based for loop),遍历了 process_loaders() 函数返回的 LoaderList 容器中的每一个元素。

    loader_it 是一个指向 Process::Loader* 的指针,即加载器对象的指针。

  • Process *p = loader_it->load(params, obj_file);:对当前加载器对象调用 load 函数,传入 paramsobj_file 作为参数。load 函数的目的是尝试从 obj_file 中加载进程信息,如果成功则返回一个 Process 对象指针,否则返回 nullptr

  • if (p):如果 load 函数返回非空指针 p,表示加载成功。

    • return p;:直接返回指向成功加载的 Process 对象的指针 p

 

normalize

规范化给定的目录路径 directory。它确保目录路径以斜杠 / 结尾,如果输入的 directory 参数的末尾没有斜杠,则在其末尾添加一个斜杠;如果已经以斜杠结尾,则直接返回原始的 directory 字符串。

 

Process::Process()

实现了 Process 类的构造函数 Process::Process,它接受多个参数并使用成员初始化列表(member initializer list)来初始化 Process 对象的各个成员变量

 

clone

用于复制一个进程的状态到另一个进程对象 np 中,根据传入的 flags 参数来选择性地复制不同的状态和资源

这种设计允许在多线程或进程复制场景下,根据需要选择性地复制不同的状态和资源,以实现进程或线程的克隆操作。

  • 共享指针

    • 自动内存管理

      共享指针提供自动内存管理功能,当所有引用指向同一对象的共享指针都销毁时,对象本身会被自动释放。这避免了手动管理内存的复杂性和潜在的内存泄漏问题。

    • 引用计数

      共享指针使用引用计数机制来跟踪有多少指针共享同一个对象。当一个新的共享指针被赋值给现有的对象时,引用计数会增加。当一个共享指针被销毁时,引用计数会减少到0,最后一个指针被销毁时,对象会被释放。

    • 共享资源

      在并发环境中(例如多线程或多进程),多个线程或进程可能需要访问和操作同一个资源(例如文件描述符数组)。共享指针允许这些资源在多个所有者之间安全地共享,而无需担心对象的生命周期问题。这样可以确保在所有引用对象的共享指针销毁之前,资源始终可用。

    • 简化代码

      使用共享指针可以简化代码逻辑,使代码更清晰。例如,在复制文件描述符数组时,使用共享指针可以直接赋值,而不需要考虑深拷贝或手动管理内存的问题。

在这个 clone 函数中,使用共享指针 (std::shared_ptr) 可以有效管理文件描述符数组 (FDArray) 和文件描述符条目 (FDEntry),确保资源在多线程或多进程环境中的安全共享和自动释放。这不仅提高了代码的安全性和可维护性,还减少了内存管理的复杂性。

 

  • FDEntry

    • FDEntry 是一个基类,用于表示文件描述符条目。这个类通常包含与文件描述符相关的基本功能,如打开、关闭、读写等操作。

  • HBFDEntry

    • HBFDEntry 继承了 FDEntry 类,并增加了一些特定于后备文件描述符(HBFD)的功能。例如,HBFDEntry 可能包含指向主机文件描述符的指针,并提供与之交互的方法。

 

revokeThreadContext

用于撤销(删除)进程中的一个线程上下文(ThreadContext)。函数的主要任务是从 contextIds 列表中找到给定的 context_id 并将其移除。

 

init

用于初始化进程。这个函数的主要任务是更新动态可执行文件的 ld_bias 和构建解释器(如果存在)的内存映像

 

initState

用于初始化进程的状态。它执行一系列操作以确保进程正确设置并准备好执行

 

drain

用于在进程的排空操作(drain operation)中更新文件描述符的文件偏移,并返回一个表示排空状态的值

 

allocateMem

用于分配虚拟内存,并将其映射到物理内存。它处理内存页的对齐、查重和映射操作。

 

replicatePage

将一个虚拟地址对应的物理页面复制到新的物理页面,并在新的线程上下文中进行映射。函数支持条件性分配新的物理页面,并复制页面内容

 

fixupFault

 

serialize

用于将进程的状态序列化到检查点输出流 CheckpointOut &cp

 

unserialize

 

map

 

findDriver

 

checkPathRedirect

用于检查给定文件名 filename 是否需要进行路径重定向,并返回重定向后的路径

 

updateBias

主要用于更新进程的加载偏移量(bias),以便在加载可重定位的解释器时调整内存映射区域

适应可重定位解释器:用于在加载可重定位的解释器时,调整进程的地址空间,确保解释器能够正确加载并运行。

计算偏移量:根据解释器的大小和进程地址空间的可用空间,计算出解释器应该加载的偏移量。

更新地址空间:通过调整 mmap_end 和更新 memState,确保解释器能够在正确的地址范围内加载。

  • 这段代码关键在于处理进程在加载可重定位解释器时的内存管理问题,确保系统可以正确地调整和分配地址空间以支持解释器的加载和执行

 

getInterpreter

 

getBias

 

getStartPC

 

absolutePath

  • 主机文件系统与目标文件系统

    • 在虚拟化或模拟环境中,通常会涉及到两种不同的文件系统概念:主机文件系统(Host File System)和目标文件系统(Target File System)。它们的区别在于它们所处的上下文和角色

    • 主机文件系统(Host File System)

      1. 定义

        • 主机文件系统指的是运行虚拟化或模拟的物理机器上的实际文件系统。这是实际硬件或操作系统提供的文件系统,例如在服务器或个人电脑上的本地文件系统。

      2. 用途

        • 主机文件系统用于存储和管理主机上的所有文件和目录,包括操作系统文件、应用程序、用户数据等。它是物理机器上的实际存储结构,虚拟化或模拟的环境中可以访问和操作这些文件。

      3. 示例

        • 如果你在一台运行Linux操作系统的服务器上运行虚拟机,那么主机文件系统就是该服务器上的Linux文件系统,包括 /home/var/usr 等目录。

    • 目标文件系统(Target File System)

      1. 定义

        • 目标文件系统指的是在虚拟化或模拟环境中运行的虚拟或模拟系统内部的文件系统。这是虚拟化或模拟环境中提供给虚拟机或模拟器使用的文件系统抽象。

      2. 用途

        • 目标文件系统通常是一个虚拟的或模拟的文件系统,它可以是在内存中模拟的,也可以是在宿主文件系统上的一部分虚拟映像文件。虚拟机或模拟器中的应用程序和操作系统会认为它们在访问和操作真实文件系统一样访问和操作目标文件系统。

      3. 示例

        • 如果你在QEMU模拟器中运行一个ARM架构的Linux内核,那么目标文件系统就是QEMU虚拟出来的文件系统,这个文件系统可能是在宿主文件系统上的一个镜像文件,例如 .img 文件,包含模拟的根文件系统结构。

在模拟环境中,比如模拟器或虚拟机,需要处理这两种文件系统,以便正确地模拟和管理应用程序的文件访问和操作,从而确保在模拟的环境中能够正确地运行和调试应用程序。

 

ProcessParams::create()

 

 

 

packet

不包含所有的代码

getAddrRange

 

trySatisfyFunctional

用于尝试通过功能访问(functional access)满足对数据包的操作。该函数的主要功能是检查数据包与传入的数据范围是否有交集,并根据数据包的类型(读或写)来处理数据。

Printable *obj:一个可打印的对象,用于打印请求的状态。

Addr addr:起始地址。

bool is_secure:安全标志,表示是否是安全访问。

int size:数据的大小。

uint8_t *_data:指向数据的指针。

 

void* dest:目标地址的指针,表示数据将被复制到的内存地址。

const void* src:源地址的指针,表示数据将从这里开始复制。

std::size_t count:要复制的字节数。

返回目标地址 dest 的指针。

 

copyResponderFlags

嗅探协调: 嗅探(snoop)是多处理器系统中缓存一致性协议的一部分。在这种协议中,处理器或缓存会嗅探(检查)总线上传输的消息,以确定它们是否需要响应或采取行动。

CACHE_RESPONDING 标志用于指示缓存是否正在响应嗅探请求。如果某个缓存设置了这个标志,意味着它对当前嗅探请求做出了响应。

断言确保在执行 copyResponderFlags 方法时,不能有多个缓存同时对嗅探请求响应。这是为了避免缓存一致性协议中的冲突

 

pushSenderState

用于管理 Packet 对象的 SenderState 栈。SenderState 是与数据包相关联的一些状态信息,通常用于跟踪数据包在系统中的路径和状态变化。

Packet 类中,SenderState 通常用于保存和恢复与数据包相关联的状态信息。当数据包在系统中传递时,它可能经过多个组件,每个组件可能会修改数据包的状态。在这种情况下,使用 pushSenderState 方法可以将当前状态保存起来,以便在处理完成后恢复。

 

popSenderState

用于从 Packet 对象的状态栈中弹出一个 SenderState 对象,并返回该对象。这个方法与 pushSenderState 方法相对应,用于恢复之前保存的状态。

这个方法与 pushSenderState 方法一起使用,可以有效管理 Packet 对象的状态栈,保存和恢复数据包的状态信息。

 

getUintX

于从数据包中获取指定大小的无符号整数,并根据指定的字节序(大小端序)进行转换

  • 调用 gem5::getUintX 函数来从数据包中获取无符号整数。

  • getConstPtr<void>() 返回数据包的常量指针,指向数据包的内存起始地址。

  • getSize() 返回数据包的大小。

  • endian 是一个枚举值,表示字节序(大端或小端)。

这个方法依赖于 gem5 库中的 getUintX 函数,它的作用是根据指定的字节序从给定地址的内存中提取指定大小的无符号整数。具体实现可能会根据不同的字节序处理数据的排列顺序

例子:

假设 getConstPtr<void>() 返回指向内存地址 0x1000 的常量指针,getSize() 返回 4endianByteOrder::little_endian(小端序)。

调用 getUintX 方法将从地址 0x1000 开始的 4 个字节解析为一个小端序的无符号整数,并将其作为 uint64_t 类型返回。

  • 字节序(Byte Order)是指在存储或传输多字节数据时,字节的排列顺序。主要有两种常见的字节序,即大端序(Big Endian)和小端序(Little Endian),它们区别在于字节的高位(Most Significant Byte,MSB)和低位(Least Significant Byte,LSB)的存储顺序。

  • 在大端序中,数据的高位字节(MSB)存储在低地址,低位字节(LSB)存储在高地址。这意味着在内存中,数据的各个字节按照从高地址到低地址的顺序排列。例如,一个 32 位整数 0x12345678 在内存中的存储顺序如下(地址从低到高):

  • 在小端序中,数据的低位字节(LSB)存储在低地址,高位字节(MSB)存储在高地址。因此,数据的各个字节按照从低地址到高地址的顺序排列。以同样的 32 位整数 0x12345678 为例,在小端序中的存储顺序如下:

  • x86 架构使用小端序,而 PowerPC 和大多数 RISC 架构使用大端序。在网络上传输数据,可以使用网络字节序(通常是大端序),例如使用 htonl(主机到网络长整型)和 ntohl(网络到主机长整型)等函数

 

setUintX

 

print

用于将 Packet 对象的信息输出到给定的输出流 o 中,以便调试或日志记录目的

    • o:一个 std::ostream 的引用,表示输出流,用于将信息输出到指定的输出设备(如控制台、文件)。

    • verbosity:一个整数,用于指定输出详细程度的级别,但在这段代码中并没有直接使用。

    • prefix:一个 std::string 的引用,表示输出信息的前缀,用于标识或区分不同的输出信息。

 

  • 创建了一个 std::ostringstream 对象 str,它是一个输出字符串流。std::ostringstream 类提供了一个内存缓冲区,可以将各种数据类型的数据以字符串的形式输出到其中。

  • 调用 Packet 类中的另一个 print 方法,传递 str 作为输出流。这里利用了函数重载的特性,将字符串输出的操作委托给了前面解释过的 print(std::ostream &o) 方法

 

matchBlockAddr

用于检查当前数据包是否匹配给定的块地址和安全性标志

  • getAddr():调用 Packet 类中的 getAddr 方法,获取当前数据包的地址

    blk_size - 1:计算块大小减一。

    Addr(blk_size - 1):将块大小减一转换为 Addr 类型,这里假设 Addr 是一个整数类型,通常用于表示地址。

    ~(Addr(blk_size - 1)):对 Addr(blk_size - 1) 取反,即按位取反操作,得到一个掩码,该掩码用于将地址的低位块地址部分清零

    getAddr() & ~(Addr(blk_size - 1)):使用按位与操作,将 getAddr() 的地址值与上述掩码进行按位与操作,从而得到块地址。

  • 假设 pkt 的地址是 0x12345678,并且 blockSize64,那么根据上述计算:

    • blk_size - 1 将是 63

    • ~(Addr(63)) 按位取反后的结果将是一个掩码,例如 0xFFFFFFFFFFFFFFC0

    • getAddr() & ~(Addr(63)) 将是 0x12345678 & 0xFFFFFFFFFFFFFFC0,结果将是 0x12345600这就是计算得到的块地址

 

matchBlockAddr

这段代码是 Packet 类中的 matchBlockAddr 方法的重载版本,接受一个 PacketPtr 类型的参数 pkt 和一个 int 类型的参数 blk_size。它用于检查当前数据包是否与另一个数据包 pkt 的块地址和安全性匹配。

pkt:一个 PacketPtr,即指向 Packet 对象的智能指针。

blk_size:一个 int,表示块大小

pkt->getBlockAddr(blk_size):调用 pkt 指向的 Packet 对象的 getBlockAddr 方法,获取 pkt 的块地址。

pkt->isSecure():调用 pkt 指向的 Packet 对象的 isSecure 方法,获取 pkt 的安全性。

matchBlockAddr(addr, is_secure, blk_size):调用当前对象的另一个 matchBlockAddr 方法,传递 pkt 的块地址、安全性和给定的块大小作为参数。

这段代码允许比较当前 Packet 对象与另一个 Packet 对象 pkt 的块地址和安全性。这在处理器缓存管理或其他涉及地址对齐和安全性匹配的系统级操作中非常有用。

 

matchAddr

用于比较当前数据包的地址和安全性标志是否与给定的地址和安全性标志匹配

这段代码通常用于数据包处理的逻辑中,特别是在需要确定数据包的目标地址和访问权限时非常有用。例如,在处理器或网络设备的数据包路由和权限控制中,可以使用这种方法来验证数据包是否符合预期的目标地址和访问权限。

 

matchAddr

这段代码是 Packet 类中的 matchAddr 方法的一个重载版本,接受一个 PacketPtr 类型的指针参数,并使用其指向的 Packet 对象的地址和安全性标志来调用另一个 matchAddr 方法进行比较。

这段代码允许比较当前 Packet 对象与另一个 Packet 对象 pkt 的地址和安全性标志。这在处理器缓存管理、网络数据包路由或其他需要比较地址和安全性的场景中非常有用.

 

PrintReqState::PrintReqState

段代码定义了 Packet::PrintReqState 类的构造函数

PrintReqStatePacket 类的一个嵌套类或成员类。这种结构在软件设计中常见,允许将相关的功能和数据组织在一起,提高代码的模块化和可维护性

  • curPrefixPtr(new std::string(""))

    • curPrefixPtr 是一个指向 std::string 的指针。

    • 通过 new std::string("") 创建了一个空的 std::string 对象,并将其地址赋给 curPrefixPtr

    • 这意味着 curPrefixPtr 指向了一个新分配的空字符串对象。

  • os(_os)

    • os 是一个引用类型的成员变量,初始化为传入的 _os(即外部传入的 std::ostream 对象)。

  • verbosity(_verbosity)

    • verbosity 是一个 int 类型的成员变量,初始化为传入的 _verbosity

 

PrintReqState::pushLabel

lbl:一个 const std::string& 类型的引用,表示要推入的标签。

prefix:一个 const std::string& 类型的引用,表示要添加到当前前缀字符串末尾的前缀内容。

PrintReqState::popLabel

PrintReqState::printLabels

 

makeHtmTransactionalReqResponse

用于将一个HTM(硬件事务内存)事务请求转换为响应,并设置相应的状态码

  • 硬件事务内存(Hardware Transactional Memory,HTM)是一种并行编程技术,它允许程序员使用事务的概念来管理内存操作,从而简化并发编程并提高性能。HTM的主要目标是提高多线程程序的性能,同时简化对共享数据的访问。

    • 事务

      • 类似于数据库中的事务,HTM中的事务是一组要么全部执行,要么全部回滚的操作。事务保证了操作的原子性和一致性。

      内存冲突检测

      • 在事务执行期间,硬件会监视内存访问以检测冲突。如果多个事务同时访问相同的内存位置并且至少有一个是写操作,硬件将检测到冲突并回滚其中的一个或多个事务。

      回滚和重试

      • 如果事务冲突或遇到其他问题(如缓存溢出),硬件会回滚事务的所有操作。程序可以选择重试事务。

      原子性

      • 事务中的所有操作要么全部成功并提交,要么全部回滚,保持系统状态的一致性

 

 

setHtmTransactionFailedInCache

用于设置 HTM(硬件事务内存)事务在缓存中失败的返回代码,并根据情况标记事务失败的标志

htm_return_code 是一个 HtmCacheFailure 枚举类型的参数,表示 HTM 事务在缓存中失败的具体原因

 

假设我们有一个 HTM 事务请求数据包 pkt,并且事务在缓存中失败,失败原因是 HtmCacheFailure::CACHE_MISS。可以这样调用该函数:

 

htmTransactionFailedInCache

检查数据包的 FAILS_TRANSACTION 标志是否被设置,判断事务是否在缓存中失败。

getHtmTransactionFailedInCacheRC

返回 HTM 事务在缓存中失败的原因。

setHtmTransactional

设置数据包为 HTM 事务包,并存储事务的唯一标识符(UID)。

isHtmTransactional

检查数据包是否是 HTM 事务包。

getHtmTransactionUid

返回 HTM 事务包的唯一标识符(UID)。

 

 

allocate

这段代码定义了 Packet 类中的 allocate 方法,用于为数据包分配内存。

  • 为什么需要确保在分配内存之前,数据包没有使用静态或动态数据?

    • 避免重复分配

      • 如果数据包已经使用了静态或动态数据(即已经分配了内存),再次调用 allocate 方法可能会导致内存泄漏或内存重复分配,从而造成程序性能下降或内存使用不稳定。

      数据包状态管理

      • 数据包对象可能会在其生命周期内多次调用 allocate 方法或进行其他操作,为了确保对象状态的一致性和正确性,需要在分配内存之前清除已有的数据状态。这样可以确保每次分配都是基于当前对象的状态和需求进行的。

      程序逻辑正确性

      • 程序设计上,通常会通过标志或状态来管理数据包是否已经分配了内存。在执行 allocate 方法之前清除这些标志或状态是良好的编程实践,可以确保操作的顺序和执行的逻辑是正确的。

      断言检查

      • 使用 assert(flags.noneSet(STATIC_DATA|DYNAMIC_DATA)); 的断言,可以在调试阶段检测和捕获潜在的错误状态。如果发现数据包已经使用了静态或动态数据,断言会触发,提醒开发者需要处理或调整代码逻辑。

 

deleteData

用于删除数据包中的数据,并清除相关的标志和指针

 


XBar

BaseXBar::BaseXBar

这段代码是一个构造函数实现,属于gem5模拟器中的 BaseXBar 类的构造函数。

ClockedObject(p):调用了基类 ClockedObject 的构造函数,并使用参数 p 进行初始化。这表明 BaseXBar 类继承自 ClockedObject,具有时钟相关的行为和属性。

 

~BaseXBar

这段析构函数的作用是释放XBar设备管理的所有端口对象所占用的内存。这种方式确保在销毁XBar对象时,所有动态分配的资源都被正确地释放,防止内存泄漏和资源泄露问题。

~BaseXBar():这是 BaseXBar 类的析构函数,用于释放对象在其生命周期中动态分配的资源。

  • 析构函数(Destructor)是一种特殊类型的成员函数,它在对象被销毁时自动调用,用于释放对象所持有的资源和执行清理工作。在C++中,析构函数的命名规则是在类名前加上波浪号 ~,例如 ~ClassName()

 

getPort

 

calPacketTiming [NOT COMPLETED]

 

MemCtrl

这段代码是 MemCtrl 类的构造函数实现

 

init

确保 MemCtrl 对象的端口(port)已经连接,并在连接后执行相应的操作

sendRangeChange()用于获取所有者负责的非重叠地址范围列表

 

startup

该方法的目的是在内存控制器启动时执行一些初始化操作,主要是针对内存操作模式进行设置和调整。

如果系统处于时序模式,将内存操作的下一个预计时间设置为当前模拟时钟周期加上一个命令偏移量,以确保在计算下一个请求的时间时不会出现负值,并在模拟开始时添加一个微小的延迟

 

recvAtomic

用于接收处理来自其他组件(可能是处理器或其他设备)发送的原子操作数据包。它首先检查数据包的地址是否在内存控制器管理的DRAM地址范围内,然后调用 recvAtomicLogic() 方法继续处理数据包。

 

recvAtomicLogic

用于实际处理接收到的原子操作数据包,它执行实际的内存访问操作,并根据数据包是否包含数据返回相应的访问延迟时间.

 

关于mem_intr->access(pkt);,下面给出相关的函数

负责处理各种类型的内存访问请求

 

recvAtomicBackdoor

处理一个原子请求,并且尝试获取该请求的后门访问(backdoor access)。后门访问通常用于快速路径访问,绕过正常的缓存和控制机制,以更直接地访问内存内容。

  • 后门访问(Backdoor Access)是一种绕过正常的内存访问路径,通过直接访问内存数据来提高访问速度和效率的技术。在计算机系统中,正常的内存访问路径通常会经过多个缓存层次和控制逻辑,而后门访问则直接与内存硬件进行交互,从而减少延迟和开销。

    提高访问速度

    • 通过绕过缓存和控制逻辑,后门访问可以直接读取或写入内存,提高数据访问速度。这对于需要快速访问大数据块的操作特别有用。

    调试和测试

    • 后门访问常用于调试和测试内存系统。在不干扰正常系统运行的情况下,可以直接检查和修改内存内容,帮助开发人员快速定位和解决问题。

    特殊操作

    • 一些特殊操作需要直接访问内存,例如快速的数据传输、DMA(直接内存访问)操作等。后门访问提供了一个有效的途径来执行这些操作。

  • 后门访问通常通过提供一个特殊的接口或方法来实现,该接口允许直接读取或写入内存。具体实现方式可能因系统和硬件架构而异。以下是一些常见的实现方式:

    1. 内存映射

      • 内存映射(Memory Mapping)是一种常见的后门访问方式。在这种方式中,特定的内存区域被映射到用户空间进程或硬件设备,允许直接访问内存数据。

    2. 专用接口

      • 有些系统提供专用的API或接口,允许开发人员通过这些接口直接与内存硬件交互。例如,在一些高性能计算系统中,提供了直接访问内存的API以提高数据传输效率。

    3. 硬件支持

      • 一些硬件设备提供了后门访问的支持。例如,某些内存控制器或存储器设备内置了后门访问功能,可以直接从外部访问内存数据。

 

readQueueFull

用于检查读队列是否已满。它接收一个参数 neededEntries,表示需要的条目数,并返回一个布尔值,表示读队列是否已满

totalReadQueueSize 表示当前读队列中的条目数。

respQueue.size() 返回响应队列的大小。

neededEntries 表示需要添加的条目数。

rdsize_new 是计算后的总大小,即当前读队列的大小加上响应队列的大小,再加上需要的条目数。

 

writeQueueFull

用于检查写队列是否已满

 

addToReadQueue

实现了向读队列添加请求的功能,涉及到处理内存访问请求、分配内存数据包以及将请求添加到读队列中的逻辑

  • 检查写队列的部分用于确定当前的读请求是否可以通过写队列来满足。如果写队列中已经有对应的数据,那么读请求可以直接从写队列中读取数据,而不需要访问内存

 

addToWriteQueue

将写请求添加到写队列

 

  • (addr | (burst_size - 1))进行按位或操作,这将 addr 向上舍入到最接近的 burst_size 的倍数,确保下一个地址处于突发边界上

  • 将结果加一,确保我们计算的是从当前地址开始的完整突发的大小

  • base_addr + pkt->getSize():计算写请求的结束地址

  • 取上述两个值的最小值,确保我们不超过写请求的实际大小

 

服务质量(QoS)的重要性

  • QoS 是用来区分和管理不同请求在系统中的优先级和重要性的指标。不同的请求可能具有不同的响应时间要求或者处理优先级。

  • 在内存控制器(MemCtrl)中,不同的请求者(requestor)可能会因为其角色或者应用场景而具有不同的 QoS 值。

分级管理

  • 通过使用 QoS 值来分级管理写请求,系统可以更精细地控制对内存访问的调度和响应。高优先级的请求可以更快地得到服务,而低优先级的请求则可以在系统负载较轻时处理。

  • writeQueue 设计为二维结构,可以根据 QoS 值的不同将请求分配到不同的队列中。这样做的好处是可以在不同的队列中独立管理和调度请求,以满足不同 QoS 级别的需求。

队列管理和调度

  • 每个 writeQueue[qos] 都是一个单独的队列,用于存储特定 QoS 值的写请求。当内存控制器需要处理写请求时,可以根据 QoS 值快速定位到合适的队列,并进行相应的操作。

  • 这种设计可以有效地管理系统中不同优先级的写请求,同时避免不同请求之间的干扰和竞争,提高系统整体的资源利用率和性能。

 

存储系统

  • 在存储系统中,QoS 常用于管理对存储资源的访问。例如,通过为不同应用程序或者用户组分配不同的带宽或者响应时间,以确保关键应用的数据访问性能。

内存控制器和缓存系统

  • 内存控制器和缓存系统中常根据 QoS 策略管理和调度内存访问。例如,将请求分为不同的优先级队列,以便在高负载时保证关键数据的及时访问。

 

 

PrintQs

实现了打印内存控制器中读队列(readQueue)、响应队列(respQueue)和写队列(writeQueue)的功能。这通常用于调试和跟踪内存控制器中当前正在处理的请求。

 

recvTimingReq

实现了内存控制器处理来自外部的定时请求(recvTimingReq)

 

processRespondEvent

用于处理响应事件的函数,主要功能是处理到达其就绪时间的请求

这段代码的主要目的是在请求包达到就绪时间后,处理其响应和后续的调度逻辑,并根据需要执行额外的刷新或重试操作.

 

chooseNext

用于在内存控制器中选择下一个要处理的请求包(MemPacket)

MemPacketQueue::iterator 是一个迭代器类型,用于访问 MemPacketQueue 中的元素。

queue 是一个 MemPacketQueue 类型的引用,表示待处理的请求队列。

extra_col_delay 是一个时钟周期延迟,用于额外的冲突延迟。

mem_intr 是一个 MemInterface 类型的指针,表示内存接口。

 

chooseNextFRFCFS

用于实现 FR-FCFS(First-Ready, First-Come, First-Serve)调度策略的具体函数,这个函数的主要目的是根据 FR-FCFS 调度策略选择下一个要处理的请求包,并返回其迭代器和允许的列命令时间.

返回类型是 std::pair<MemPacketQueue::iterator, Tick>,表示选择的下一个请求包迭代器和允许的列命令时间。

queue 是一个 MemPacketQueue 类型的引用,表示待处理的请求队列。

extra_col_delay 是一个额外的列命令延迟。

mem_intr 是一个 MemInterface 类型的指针,表示内存接口。

FR-FCFS(First-Ready First-Come, First-Serve)策略是内存调度中常见的一种策略,特别适用于多通道内存或多rank内存系统。下面是对FR-FCFS策略的详细解释:

FR-FCFS策略的设计目的是优化多通道内存或多rank内存系统中的请求处理。它结合了两种主要的调度原则:首先是“首就绪”(First-Ready),即优先选择已准备好进行处理的请求;其次是“先来先服务”(First-Come, First-Serve),即同一就绪状态下,优先处理最早到达的请求。

就绪状态优先

  • FR-FCFS首先检查哪些请求已经准备好(ready)。这通常意味着请求已经通过前端的处理,可以立即被发送到内存控制器进行处理。

  • 准备好的标准可以根据具体的系统设计而变化,但通常包括等待排队时间已满、依赖的前置操作已完成等条件。

先来先服务调度

  • 在所有准备好的请求中,FR-FCFS选择最早到达的请求进行处理。这种方式确保了请求的处理顺序是公平且有序的。

适用于多通道或多rank系统

  • FR-FCFS特别适用于多通道内存或多rank内存系统,这些系统允许同时处理来自多个通道或rank的内存请求。通过选择就绪且最早到达的请求,可以有效地利用并行处理能力,提高系统的整体性能。

 

在内存控制器的设计中,col_allowed_at 通常表示的是允许发出列命令的时间。这个时间点是根据当前系统的状态和内存控制器的调度策略计算出来的,具体可能涉及以下几个方面的考量:

  1. 额外列延迟(extra_col_delay):这个延迟是考虑到在某些系统中,可能需要在发出列命令之前预留一定的时间,以确保操作的顺畅性和稳定性。

  2. 内存接口的下一个突发时间(mem_intr->nextBurstAt):内存控制器可能会跟踪下一个突发传输的时间点,以便在适当的时候发出内存访问请求。

  3. 当前时钟周期(curTick()):这是当前的系统时钟周期,用于确保在正确的时间发出内存命令,以与系统的其他部分同步。

综合这些因素,col_allowed_at 可能是一个预测的时间点,表示在这个时间之后,内存控制器可以安全地发出下一个列命令,以执行内存访问操作。

 

accessAndRespond

内存控制器中用于处理内存访问并响应的关键函数

 

pruneBurstTick

用来清理存储在 burstTicks 中的已经过时的时钟周期值的函数

 

 

Chameleon 代码部分

项目结构"chameleon_ctrl.cc+chameleon.hh"通过gem5嵌入的pybind11与“Chameleon.py”绑定,通过SConsscript编译将ChameleonCtrl对象引入m5.object,可以在python文件中进行配置。

 

关于Segment

  • Each segment is 64 Byte in size. 每个段的大小为 64 字节。【代码中是这样注释的】

    • 论文中指出:The various segment granularities supported by the hardware in Chameleon can be easily detected by the OS during boot time

    • CAMEO这个工作的大小是64B 、PoM[25]的大小是2KB

  • segGrpEntry: 一组segments 组成为【1 HBM segment + 多个(设置成3)DDR Segment】元数据也包含在其中。

    • 实际上就是论文中的Segment Group的其中一组。

  • 参数:

    • Addr addr

      • Entry的偏移量

      • 例如:[addr = 318HBM 8GB , DDR 32GB]

        HBM address 就应该是3*8GiB + 318

        DDR Segment 0 address 就应该是0*8GiB + 318

        DDR Segment 1 address 就应该是1*8GiB + 318

        DDR Segment 2 address 就应该是2*8GiB + 318

    • ddrNum

      • segGrpEntry: 一组segments 组成为【1 HBM segment + 多个(设置成3)DDR Segment】,默认3

    • cacheMode

      • 是Cache【1】还是Flat(POM)【0】

    • dirty

      • 无需多言

    • busy

      • 当条目正在写回或进行其他操作时,设置其为忙碌状态。 将所有访问的数据包放入reqQueue中,并稍后处理它们。

    • reqQueue

      • 保留来自CPU的请求数据包。 当该条目准备好时,应清空所有队列

    • tags

      • 指示remap条件

      • 例如:tags[3]=1 , HBM【可能Segment编号是3】 address现在存着的是DDR Segment 1的数据。因此访问DDR Segment 1 的数据根据其他一些bits 【比如 xxx??? 】可能会被重定向到HBM Segment。

    • readTypes

      • 指示什么读数据需要被写入

      • -1 : 不发送

      • 例如: readType[1]=3 读数据包被送到1,读的数据被写入3 【指的应该是Segment 1/3 也就是DDR Segment 1 HBM Segment】

 

关于MemSidePort

MemSidePort的类,它继承自QueuedRequestPort,表示在内存端的一个端口,用于接收响应

  • 虚函数在面向对象编程中起着非常重要的作用,主要有以下几个方面:

    1. 实现多态性(Polymorphism)

      • 虚函数允许在基类中声明接口,并在派生类中重新定义该函数。通过基类指针或引用调用虚函数时,根据实际对象的类型来决定调用哪个版本的函数,实现了多态性。这种特性使得程序能够根据对象的实际类型来动态地选择合适的函数实现,从而提高了代码的灵活性和可维护性。

    2. 允许运行时绑定(Runtime Binding)

      • 虚函数通过在运行时确定要调用的函数版本,而不是在编译时确定。这种动态绑定的机制使得程序能够在运行时根据对象的实际类型来决定调用哪个函数,从而支持更灵活的程序行为。

    3. 实现接口和抽象类

      • 虚函数可以在基类中声明为纯虚函数(即没有实现),使得基类成为抽象类,无法直接实例化对象,但可以作为接口使用。派生类必须实现基类中的纯虚函数,从而实现接口的规范和强制约束。

    4. 方便的函数重写机制

      • 虚函数允许派生类重新定义基类的函数,以适应特定的需求或环境。这种机制使得继承关系更加灵活,子类可以根据需要定制自己的行为,同时还能够利用基类的通用实现。

    5. 支持动态内存分配和销毁

      • 虚函数使得通过基类指针或引用访问派生类对象成为可能。这种特性对于动态内存管理(如使用 newdelete 运算符)尤为重要,可以根据需要动态地创建和销毁对象,并确保正确调用派生类的函数。

 

关于CpuSidePort

用于在内存控制器与CPU之间的通信。它管理从CPU接收的数据包请求和响应,并提供处理和重试机制。该类具有以下主要功能:

  • 存储和管理响应数据包的队列。

  • 处理从CPU接收的定时和功能性请求。

  • 支持设置和清除阻塞状态,以控制端口的请求处理能力。

  • 提供地址范围查询和重试请求处理的机制。

 

chameleon_ctrl.cc/hh

Chameleon构造类和初始化部分

ChameleonCtrlParams类无需手动实现,只需要写好.cc和.hh以及.py文件,在执行过程中会自动生成

hbmSize(1*1024*1024*1024LL), ddrRatio(16)指定HBM容量和对应的DRAM比例,需要和后续python文件中匹配。这是.cc和.hh文件中其它出现的相关的参数的默认值。

 

ChameleonStats构造函数并定义统计信息

 

initSegGrps

初始化分段组(Segment Groups)

根据给定的块大小和地址范围(这里是从0到1MB)初始化一组段组(Segment Groups)。每个段组包含一个地址和DDR比率,然后将它们添加到 segGrps 中,以便后续使用。

这段原理可以看图

也就只能初始化HBM_Size / Segment Size大小个segGrpEntry

Segment Size = Block Size = 64 Bytes,segGrps需要在Chameleon刚开始初始化,一个for循环搞定。

 

getPort

 

CPUSidePort构造函数

CPUSidePort继承QueuedResponsePort

初始化为端口当前未阻塞,当前不需要发送重试请求

 

CPUSidePort::setBlcoked

 

CPUSidePort::clearBlocked

 

CPUSidePort::getAddrRanges

 

CPUSidePort::processSendRetry

 

functionalAccess

传入数据包,得到数据包地址和请求,计算得到:

  • 注:一组segments 组成为【1 HBM segment + 多个(设置成3)DDR Segment】 【 Segment size = 64B (e.g,)】

 

recvFunctional

处理从 CPU 端口接收到的功能性(functional)访问请求

 

tryTiming

用于判断是否可以接受传入的定时请求

  • 快速嗅探包是一种特殊的包类型,它需要快速传递,用于缓存一致性协议中的嗅探操作

 

recvTimingReq

Hybrid2从这里开始的实现复杂一些(当然主要是多了更多的统计输出)

用于接收和处理定时请求(Timing Request)

hybird2remap相关代码中增加了一些部分:

【Hybrid2 Addition 1】增加对owner内存足迹的修改,当然这也是因为chameleon的代码没有相关的统计输出

【Hybrid2 Addtion 2】增加对数据包的一些操作,在DPRINTF(ChameleonCtrl, "Request succeeded\n");之前

 

 

MemSidePort构造函数

 

recvTimingResp

 

recvRangeChange

 

access

处理数据包时调用该函数(Hybrid2 对此进行了一定注释:handleRequest函数时用,用于双向/单向交换时发出readpkt)。

【Hybrid2在这里实现是与Chameleon大不相同的】

如果访问到HBM地址,将一个cache模式的HBM段转换为PoM

所有挂在reqQueue上的数据包绝对不能被预处理。 因此,访问函数将在handleDeferredPacket中被调用。

如果数据包没有被延迟处理,则返回true。无论地址是否发生变化。

  • 注:一组segments 组成为【1 HBM segment + 多个(设置成3)DDR Segment】 【 Segment size = 64B (e.g,)】

 

sendReadPacket

 

handleRequest

 

writeBack

主要作用是处理接收到的响应数据包,并根据数据包中的地址和状态信息,将数据写回到指定的目标地址中

 

 

 

handleResponse

用于处理接收到的响应数据包。如果数据包是向CPU发送的响应,它会恢复数据包的状态和地址,并将响应发送回CPU

 

 

handleDeferredPacket

处理段组条目(segGrpEntry)中的延迟请求队列(reqQueue)。它逐个处理队列中的数据包,尝试访问并发送每个数据包,如果访问失败则返回当前发送时钟周期,以便稍后重新尝试发送。

 

createWbPacket

创建一个写回数据数据包,并将指定的数据指针 data_ptr 设置到该数据包中

RequestPtr req = std::make_shared<Request>(mem_addr, blockSize, 0, Request::wbRequestorId);

  • 创建一个新的写回请求 (Request) 对象。

  • mem_addr:目标内存地址。

  • blockSize:数据块的大小。

  • 0:请求标志,这里没有特别的标志。

  • Request::wbRequestorId:写回请求的请求者ID,通常用于标识该请求是由写回操作发起的。

PacketPtr new_pkt = new Packet(req, MemCmd::WritebackDirty, blockSize);

  • 创建一个新的数据包 (Packet) 对象。

  • req:之前创建的请求对象。

  • MemCmd::WritebackDirty:数据包命令类型,表示这是一个写回脏数据的操作。

  • blockSize:数据块的大小。

new_pkt->dataDynamic(data_ptr);

  • 将数据指针 data_ptr 设置为数据包的新数据。

  • dataDynamic 函数将动态分配和管理数据指针 data_ptr 的内存。

 

dataDynamic

将数据指针设置为一个应该用 delete [] 释放的值。动态数据是该数据包特有的,当数据包从源头传递到目的地时,转发的数据包将分配它们自己的数据。当一个数据包到达最终目的地时,它将填充该特定数据包的动态数据,并在返回源头的途中,在每一个创建新数据包的步骤中(例如在缓存中)都会调用 memcpy。最终,当响应到达源头时,需要进行最后一次 memcpy 来从数据包中提取数据,然后再释放数据包.

 

createReadPacket

创建一个读取数据包,并分配必要的内存

 

 

HBM_project映射实现的差异

access

首先是出现的不同的实现的结构体:

  • simpleTlbEntry : 用于表示简单的地址重映射条目,包含两个不同内存地址之间的映射信息及相关状态,相应的两个地址严格来自两种不同的内存

    • Addr mem1Addr 来自内存介质1的地址

      Addr mem2Addr 来自内存介质2的地址

    • oneWay 表示是否是单向迁移

    • mem1Ready 表示内存介质1是否已经准备好

      mem2Ready 表示内存介质2是否已经准备好

    • ready 指示两内存地址中的数据是否已准备好使用,当迁移完成时,该值应设为 true

    • mem1Data:存储内存1中的数据。

      mem2Data:存储内存2中的数据。

    • is_dram_to_hbm 指示当前的迁移方向是否是从 DRAM 到 HBM

    • needWait 用于表示是否需要等待。特别是在从地址A迁移到地址B(a->b)时,如果迁移尚未完成,则不能进行从B到A(b->a)的读取操作,否则读取到的数据将是旧数据,可能会导致错误

    • redQueue 保存来自 CPU 的请求包。 当这个条目准备好时,应清空所有队列。

    • 这个函数将 data 写入 mem1Data,并将 mem1Ready 标志设为 true,表示内存1的数据已准备好

    • 这个函数将 data 写入 mem2Data,并将 mem2Ready 标志设为 true,表示内存2的数据已准备好

    • 这个函数将所有相关标志和数据指针重置:

      • ready 设为 true,表示数据已准备好。

      • mem1Datamem2Data 设为 nullptr

      • mem1Readymem2Ready 设为 false

      • 注释掉的部分(mem1Wb, mem2Wb, valid)可能用于将来扩展或曾经用过的功能,现在暂时不用

  • 介绍一下这边出现的内存根据指定大小对齐的操作:

    • Addr block_addr_orig = ( (pkt->getAddr() >> cache_block_bits) << cache_block_bits );

      • pkt->getAddr()这部分获取了一个地址,假设该地址是 A

      • pkt->getAddr() >> cache_block_bits

        cache_block_bits 通常是与缓存块大小相关的一个值。如果缓存块大小是64字节,那么 cache_block_bits 应该是 6,因为 2^6 = 64。右移 cache_block_bits 位相当于除以 2^cache_block_bits。所以,这部分代码执行的操作是将地址 A 右移 6 位,即 A / 64,得到一个整数部分 B

      • (pkt->getAddr() >> cache_block_bits) << cache_block_bits

        将结果 B 再左移 6 位,即 B * 64,恢复到一个以64字节为单位对齐的地址

    • e.g.

      • 假设 pkt->getAddr() 返回的地址 A = 12345,并且 cache_block_bits = 6

      • 12345 >> 6 = 192 : 右移 6 位相当于除以 64(丢弃小数部分),结果是 192

      • 192 << 6 = 12288: 左移 6 位相当于乘以 64,结果是 12288

      • 1228812345 向下对齐到最近的 64 字节块的起始地址。换句话说,1228812345 所在的那个64字节块的起始地址

 

functionalaccess

代码和access几乎一模一样,区别在参数列表接收了一个simpleTlbEntry** 即指向指针的指针类型的参数传入

在 C++ 中,双指针用于以下几个场景:

  1. 函数修改指针的值:当你需要一个函数能够修改它所接收的指针的值(即指向不同的对象),而不仅仅是修改指针所指向的对象时,你可以使用双指针。

  2. 动态二维数组:双指针也常用于实现动态的二维数组。

simpleTlbEntry** 主要用于第一个场景,即传递一个指向指针的指针,以便在函数内修改指针的值并在函数外部反映这个修改。

 

 

AddSimpleEntry

mem1mem2:表示两个内存地址,用于创建新的 simpleTlbEntry 条目。

is_oneway:表示是否单向迁移。

is_mem1Readyis_mem2Ready:表示 mem1Datamem2Data 是否准备好。

is_ready:表示整个迁移过程是否完成。

mem1Datamem2Data:表示两个内存地址中的数据。

is_dram_to_hbm:表示数据迁移的方向。

block_size_blockSize:用于控制分块大小的参数。

 

 

gem5实现Design的思路

  • 最主体的部分是一个负责分发数据这种的内存控制器(例如remap_ctrl.cc/hh , chameleon_ctrl.cc/hh

  • 还有一个是负责真正Design部分的逻辑的部分,包括一些状态位怎么样变、数据应该怎么样迁移等(block_and_page_granularity_HBM_prior.cc/hh)。

    • 内存控制器通过头文件的引入#include block_and_page_granularity_HBM_prior.hh,引入包含处理逻辑的控制器类(e.g. 在Hybrid2中 class AddrController)作为design处理逻辑和控制器分发数据的媒介。

    • 通过媒介中计算(或其他方式)得到的类似set_id page_id page_offset page_adddr等数据,交给内存控制器进行处理。内存控制器也将通过媒介进行一些逻辑处理。

  • 相关的代码实现之后,无需手动实现内存控制器构造时的参数类(e.g. RemapCtrlParams 相应的文件 RemapCtrlParams.hh),这个文件将在编译后自动生成。

  • 在编译之前,还需要在内存控制器实现代码的相同目录下,实现一个相应的Python类,以最后用于实例化对象并加入到m5.object。最简单的python类实现,如下案例所示:(注意引入m5相关的包)

  • 在编写完这段代码之后,需要在SConscript(如果没有的话需要新建一个)里增加相关的python文件,以使它能够正确编译。

  • 后续进行系统配置时只需要在导入包的部分,增加代码行from m5.objects import *即可在后续进行使用

  • 在Hybrid2中进行的RemapCtrl配置,即可完成对象仿真。

  • 上面代码中出现的options.hbm_controller为命令行相关的操作

  • 如果需要在命令行新增类似--hbm-controller相关的命令,只需要在configs/common/Options.py中,在def addSEOptions(parser):中新增相关命令即可。

    • 例如我在命令行试图加入--parsec命令:

    • 只需要增加这样一段代码即可

 

 

Run_parsec.sh脚本说明

parsec解压出的benchmark有13种,每种对应5种模式,分别是simdev simlarge simmedium simsmall test。