GEM5 fs.py 代码解读 命令行解读

简介

兜兜转转最后还是回到了了fs.py。原因是:1.太多的老教程都用了它,2023年它归于废弃,但是新的能garnet+fs的默认config还没出现,在还需要看fs.py的初始阶段就要自己手写的话那不是何不食肉靡么。自带的ruby_random_test.py支持garnet但是不支持fs。相比之下,arm/ruby_fs.py非常友好u原生就支持ruby_fs,看的出来arm 对gem5的社区支持比较好。

启动命令行解读

输入的命令行

/build/X86/gem5.opt -d m5out/yzNov08_black_simsmall configs/deprecated/example/fs.py 、
--kernel=/home/yz/.cache/gem5/x86-linux-kernel-4.19.83 --disk=/home/yz/.cache/gem5/x86-parsec \
     --ruby --network=garnet   --num-cpus=4 --num-dirs=4   --num-l2caches=4 \
     --topology=Mesh_XY --mesh-rows=2

命令行分为两部分: 纯fs部分garnet无关 和 garnet相关部分

1 纯fs部分garnet无关
单独的运行fulls system,我们需要 gem.opt 可以制定输出目录-d,也要用到configpython 文件。 然后作为fullysystem,我们需要内核 和系统镜像。
2 garnet相关部分
2.1 然后,与ruby和garnet有关,我们需要多核心而且需要指定这些核心是怎么链接起来的,所以我们需要-- ruby --network,–num-cpus。 而且我们还需要 --num-dirs=4以及–num-l2caches。
2.2 有了数目以后,我们需要怎么链接起来的–topology需要制定。同时 是1x4还是2x2,需要通过行数–mesh-rows制定。

命令行具体细节解释

作为开始,我们不想更改gem5 ruby 或者garnet,我们只想运行起来。
我们之前运行过full system全系统的parsec,也运行过se模式下的garnet,我们这次想做的是,把fs的garnet跑起来,至少跑进系统。

  1. 1 /build/X86/gem5.opt:这应该是gem5可执行文件的路径。它表示gem5的编译版本是优化的(opt)版本,并且是针对x86架构的。
  2. -d m5out/yzNov08_black_simsmall:这个参数指定了输出目录。仿真的所有输出文件都会被保存在这个目录下。
  3. –kernel=/home/yz/.cache/gem5/x86-linux-kernel-4.19.83:这个参数指定了Linux内核镜像的位置,gem5将使用这个内核进行仿真。
  4. 2 --disk=/home/yz/.cache/gem5/x86-parsec:这个参数指定了磁盘镜像的路径,这个镜像将会被挂载到仿真的文件系统中。
  5. 3 --ruby:这表示使用Ruby内存模型。Ruby是gem5中的一个高级缓存和内存系统模型。
  6. 4 --network=garnet:这表示使用Garnet网络模型,用于模拟片上网络。
  7. 5 --num-cpus=4:这指定了仿真使用的CPU核心数目。
  8. 6 --num-dirs=4:这指定了directory节点的数量,在基于Ruby的系统中,directory节点用于维护缓存一致性。
  9. 7 --num-l2caches=4:这指定了L2缓存的数量。
  10. 8 --topology=Mesh_XY --mesh-rows=2:如果没有指定–topology 这个参数与网络拓扑有关,它指定了mesh网络中的行数。 而且,#XY routing is enforced (using link weights)。在Mesh_westfirst.py 中, West-first routing is enforced (using link weights)

–ruby发生了什么? Ruby.define_options(parser)

fs.py中是搜不到 garnet meshrows 等关键词的,那么他们是怎么加入arg中的呢?
fs.py中,如果有–ruby,则 Ruby.define_options(parser)。

# Add the ruby specific and protocol specific args
if "--ruby" in sys.argv:
   Ruby.define_options(parser)

我们看gem5/configs/ruby/Ruby.py,其中提供了很多parser但我们并没有用到,而且也没有“–network=garnet”一类的配置。
但是它在文件代码的末端再次define了parser: Network.define_options(parser)

def define_options(parser):
   # By default, ruby uses the simple timing cpu
   parser.set_defaults(cpu_type="TimingSimpleCPU")

   parser.add_argument(
       "--ruby-clock",
       action="store",
       type=str,
       default="2GHz",
       help="Clock for blocks running at Ruby system's speed",
   )

   parser.add_argument(
       "--access-backing-store",
       action="store_true",
       default=False,
       help="Should ruby maintain a second copy of memory",
   )

   # Options related to cache structure
   parser.add_argument(
       "--ports",
       action="store",
       type=int,
       default=4,
       help="used of transitions per cycle which is a proxy \
           for the number of ports.",
   )

   # network options are in network/Network.py

   # ruby mapping options
   parser.add_argument(
       "--numa-high-bit",
       type=int,
       default=0,
       help="high order address bit to use for numa mapping. "
       "0 = highest bit, not specified = lowest bit",
   )
   parser.add_argument(
       "--interleaving-bits",
       type=int,
       default=0,
       help="number of bits to specify interleaving "
       "in directory, memory controllers and caches. "
       "0 = not specified",
   )
   parser.add_argument(
       "--xor-low-bit",
       type=int,
       default=20,
       help="hashing bit for channel selection"
       "see MemConfig for explanation of the default"
       "parameter. If set to 0, xor_high_bit is also"
       "set to 0.",
   )

   parser.add_argument(
       "--recycle-latency",
       type=int,
       default=10,
       help="Recycle latency for ruby controller input buffers",
   )

   protocol = buildEnv["PROTOCOL"]
   exec(f"from . import {protocol}")
   eval(f"{protocol}.define_options(parser)")
   Network.define_options(parser)

Network.define_options(parser) 发生了什么

gem5/configs/network/Network.py 中定义了非常长的parser, 但是终于出现了 --topology --network 等参数。
也就是说,终于有了

def define_options(parser):
    # By default, ruby uses the simple timing cpu
    parser.set_defaults(cpu_type="TimingSimpleCPU")

    parser.add_argument(
        "--topology",
        type=str,
        default="Crossbar",
        help="check configs/topologies for complete set",
    )
    parser.add_argument(
        "--mesh-rows",
        type=int,
        default=0,
        help="the number of rows in the mesh topology",
    )
    parser.add_argument(
        "--network",
        default="simple",
        choices=["simple", "garnet"],
        help="""'simple'|'garnet' (garnet2.0 will be deprecated.)""",
    )
    parser.add_argument(
        "--router-latency",
        action="store",
        type=int,
        default=1,
        help="""number of pipeline stages in the garnet router.
            Has to be >= 1.
            Can be over-ridden on a per router basis
            in the topology file.""",
    )
    parser.add_argument(
        "--link-latency",
        action="store",
        type=int,
        default=1,
        help="""latency of each link the simple/garnet networks.
        Has to be >= 1. Can be over-ridden on a per link basis
        in the topology file.""",
    )
    parser.add_argument(
        "--link-width-bits",
        action="store",
        type=int,
        default=128,
        help="width in bits for all links inside garnet.",
    )
    parser.add_argument(
        "--vcs-per-vnet",
        action="store",
        type=int,
        default=4,
        help="""number of virtual channels per virtual network
            inside garnet network.""",
    )
    parser.add_argument(
        "--routing-algorithm",
        action="store",
        type=int,
        default=0,
        help="""routing algorithm in network.
            0: weight-based table
            1: XY (for Mesh. see garnet/RoutingUnit.cc)
            2: Custom (see garnet/RoutingUnit.cc""",
    )
    parser.add_argument(
        "--network-fault-model",
        action="store_true",
        default=False,
        help="""enable network fault model:
            see src/mem/ruby/network/fault_model/""",
    )
    parser.add_argument(
        "--garnet-deadlock-threshold",
        action="store",
        type=int,
        default=50000,
        help="network-level deadlock threshold.",
    )
    parser.add_argument(
        "--simple-physical-channels",
        action="store_true",
        default=False,
        help="""SimpleNetwork links uses a separate physical
            channel for each virtual network""",
    )

这个干了什么呢?

  1. 然后,代码导入了必要的模块和函数,这包括math模块和m5模块(这是gem5模拟器的一部分),以及其他一些从gem5导入的特定对象和函数。
  2. 接着,定义了一个define_options函数,它向命令行解析器添加了多个网络配置选项:
  3. –topology:定义网络的拓扑结构,默认是Crossbar。
    –mesh-rows:如果使用mesh拓扑,这个参数定义了mesh中的行数。
    –network:选择网络类型,可以是simple或者garnet。
    –router-latency:定义garnet路由器中的流水线阶段数。
    –link-latency:定义simple或garnet网络中每个链路的延迟。
    –link-width-bits:定义garnet网络中所有链路的宽度(以比特为单位)。
    –vcs-per-vnet:定义garnet网络中每个虚拟网络的虚拟通道数。
    –routing-algorithm:选择网络的路由算法。
    –network-fault-model:是否启用网络故障模型。
    –garnet-deadlock-threshold:定义网络级的死锁阈值。
    –simple-physical-channels:定义SimpleNetwork是否为每个虚拟网络使用单独的物理通道。 create_network函数基于提供的选项来创建和配置网络。根据用户选择的–network选项,这个函数决定了将要使用的网络类(GarnetNetwork或SimpleNetwork)以及相关的内部和外部链路类、路由器类和接口类
  4. init_network函数用于进一步初始化网络。根据options中的设置,它可以设置garnet网络的参数,如网格行数、虚拟通道数、网络接口的数据单元大小、路由算法、死锁阈值等。如果启用了网络故障模型,它还会配置故障模型。
  5. 代码中还包括了一些用于创建和连接网络桥的代码。这些桥是用于连接网络组件,如路由器、链路和控制器,使得数据可以在它们之间传输。
  6. 这段代码没有运行任何网络模拟,它只是设置了网络模拟环境的参数,并提供了创建和初始化网络对象的函数。实际的模拟将在这些对象和设置被加入到gem5模拟环境并运行模拟时发生。

更复杂的输入命令行

我们故意省略了一些常见的命令行:例如下面的 --script。因为我们希望他能进入系统后就停在刚开机状态,在我们可以通过m5term来互动操作,初步了解看看这个系统。

./build/X86/gem5.opt -d yzNov08_black_simsmall configs/deprecated/example/fs.py \
--ruby --network=garnet \
--kernel=/home/yz/.cache/gem5/x86-linux-kernel-4.19.83 --disk=/home/yz/.cache/gem5/x86-parsec\
 --script=/home/yz/myprojects/2024GEM5/parsec-tests/yzmodifiedgem5/configs/yz2023Nov/yzfs.script \
  --num-cpus=4 --num-dirs=4 --topology=Mesh_XY --mesh-rows=4 --mem-size=4GB  --mem-type=DDR4_2400_4x16

python fs.py的 import,是import哪里?

import config python

首先,gem5/configs/deprecated/example/fs.py自己的路径是知道的,它用addToPath(“…/…/”),然后引用了configs里自带的一些python文件:

addToPath("../../")

from ruby import Ruby

from common.FSConfig import *
from common.SysPaths import *
from common.Benchmarks import *
from common import Simulation
from common import CacheConfig
from common import CpuConfig
from common import MemConfig
from common import ObjectList
from common.Caches import *
from common import Options

这个…/…/就是gem5/configs/下的这些文件。
在这里插入图片描述
主要引用了common下很多文件以及ruby下一个文件
common:
在这里插入图片描述
ruby文件夹下的Ruby.py:
在这里插入图片描述

import build python

同时,gem5/build/X86/python/ 是默认的import路径。
这些都和源代码 gem5/src/python/ 强相关的文件。

import m5
from m5.defines import buildEnv
from m5.objects import *
from m5.util import addToPath, fatal, warn
from m5.util.fdthelper import *
from gem5.isas import ISA
from gem5.runtime import get_runtime_isa

源代码gem5/src/python/下可以看到有m5和gem5文件夹
在这里插入图片描述
实际运行的是,build好之后,gem5/build/X86/python/下的文件
在这里插入图片描述
比如

from gem5.isas import ISA
from gem5.runtime import get_runtime_isa

那么在gem5/build/X86/python/gem5/里面就有isas.py和runtime.py:在这里插入图片描述

args 的全部输入

输入的args

args: 这个参数通常是一个包含命令行参数的列表。在 gem5 中,许多模拟配置都可以通过命令行参数来设置,如模拟的类型(系统模式、全系统模式等)、缓存大小、处理器类型等。这些参数在启动模拟前被解析并用于配置模拟环境。
创建解析器:

parser = argparse.ArgumentParser()

这行代码创建了一个 argparse.ArgumentParser 对象,它是用于处理命令行参数的解析器。
添加通用选项:

Options.addCommonOptions(parser)

这行代码调用了一个函数来向解析器添加一些常见的选项。这可能包括一些 gem5 模拟器的标准参数,如日志级别、输出文件路径等。
添加文件系统相关的选项:

Options.addFSOptions(parser)

这里再次调用一个函数来添加与文件系统模拟相关的选项。这可能涉及到配置文件系统的类型、磁盘镜像等。
添加 Ruby 相关的选项:

if "--ruby" in sys.argv:
    Ruby.define_options(parser)

这段代码检查命令行参数中是否包含 --ruby。如果包含,它调用另一个函数来添加与 Ruby(一种 gem5 中的高级缓存和内存系统模拟工具)相关的特定选项。
解析命令行参数:

args = parser.parse_args()

最后,parse_args 函数被调用来解析命令行参数。这个函数解析传递给程序的参数(通常来自 sys.argv),并返回一个包含这些参数值的对象。
args 变量:
args 是一个包含所有命令行参数值的命名空间对象。每个命令行参数都成为这个对象的一个属性,可以通过 args.参数名 的形式访问。
在 gem5 的上下文中,这意味着 args 包含了模拟器运行所需的所有配置信息,如是否启用 Ruby 模拟、文件系统的配置等。
这样,脚本可以根据 args 中的值来调整模拟器的行为和设置。
在fs.py末尾,它定义了argparse解析器并添加了命令行参数。然后它解析了这些参数,并基于这些参数来设置CPU类和内存类。之后,它构建测试系统,可能还会构建驱动系统,最后创建一个root根对象来启动模拟。

本质上,fs.py只是运行这一步

Simulation.run(args, root, test_sys, FutureClass)
  1. 其中 root是仿真的硬件。fs.py 有很大一部分都是用来设置模拟的硬件板子(主要部分是cpu 以及cpu互联的细节)的。
    在这个脚本中,root对象是在不同条件下被创建的:
    如果模拟的是一个双系统(dual系统),那么root会通过调用makeDualRoot()函数被创建,这意味着会有两个系统(test_sys和drive_sys)并行运行在模拟中。
    如果启用了分布式模拟(dist模式),则通过调用makeDistRoot()函数创建root,此时root代表的系统会作为分布式模拟中的一个节点。
    如果模拟的是单个系统,那么root会简单地通过Root(full_system=True, system=test_sys)创建,其中test_sys是通过build_test_system()函数构建的测试系统。
    在gem5中,root是整个模拟环境的最顶层容器,它通常包含了模拟的计算机系统(如处理器、内存、总线等)以及与这些组件连接的所有设备和系统的配置。这样的结构设计允许gem5能够方便地访问和管理模拟的不同部分。
    在本文,我们没有root = Root(full_system=True, system=test_sys)。

test_sys的创建

fs.py中, test_sys中是通过makeLinuxX86System,创建一个基础的test_sys.

test_sys = build_test_system(np)
#在 build_test_system(np)中: 
elif isa == ISA.X86:
        test_sys = makeLinuxX86System(
            test_mem_mode, np, bm[0], args.ruby, cmdline=cmdline
        )

然后在指定一些细节。

# Set the cache line size for the entire system
    test_sys.cache_line_size = args.cacheline_size

    # Create a top-level voltage domain
    test_sys.voltage_domain = VoltageDomain(voltage=args.sys_voltage)

    # Create a source clock for the system and set the clock period
    test_sys.clk_domain = SrcClockDomain(
        clock=args.sys_clock, voltage_domain=test_sys.voltage_domain
    )

    # Create a CPU voltage domain
    test_sys.cpu_voltage_domain = VoltageDomain()

    # Create a source clock for the CPUs and set the clock period
    test_sys.cpu_clk_domain = SrcClockDomain(
        clock=args.cpu_clock, voltage_domain=test_sys.cpu_voltage_domain
    )

如果ruby,test_sys有更多细节

if args.ruby:
        bootmem = getattr(test_sys, "_bootmem", None)
        Ruby.create_system(
            args, True, test_sys, test_sys.iobus, test_sys._dma_ports, bootmem
        )

        # Create a seperate clock domain for Ruby
        test_sys.ruby.clk_domain = SrcClockDomain(
            clock=args.ruby_clock, voltage_domain=test_sys.voltage_domain
        )

        # Connect the ruby io port to the PIO bus,
        # assuming that there is just one such port.
        test_sys.iobus.mem_side_ports = test_sys.ruby._io_port.in_ports

        for (i, cpu) in enumerate(test_sys.cpu):
            #
            # Tie the cpu ports to the correct ruby system ports
            #
            cpu.clk_domain = test_sys.cpu_clk_domain
            cpu.createThreads()
            cpu.createInterruptController()
            test_sys.ruby._cpu_ports[i].connectCpuPorts(cpu)

我们重点关注dvfs会用到的 clk 和voltage:

  # Create a seperate clock domain for Ruby
        test_sys.ruby.clk_domain = SrcClockDomain(
            clock=args.ruby_clock, voltage_domain=test_sys.voltage_domain
        )

命令行没指明,但python文件提供的默认选项:

./build/X86/gem5.opt -d m5out/20231109_1438 configs/deprecated/example/fs.py --kernel=/home/yz/.cache/gem5/x86-linux-kernel-4.19.83 --disk=/home/yz/.cache/gem5/x86-parsec 

默认选项来自于的文件

提供了选择的文件

fs.py import了这些文件可供选择

from common.FSConfig import *
from common.SysPaths import *
from common.Benchmarks import *
from common import Simulation
from common import CacheConfig
from common import CpuConfig
from common import MemConfig
from common import ObjectList
from common.Caches import *
from common import Options

Ruby指定的默认类型

Ruby默认的CPU类型:TimingSimpleCPU

ruby和garnet,会默认使用某种类型的cpu。 我们的命令行的结果,从config.ini看是“TimingSimpleCPU”是确定的。问题是,这是哪里指明的呢?命令行里并没有指定–cputtype,所以它是有个默认的选项的。
fs.py中,使用了下面的代码

parser = argparse.ArgumentParser()
Options.addCommonOptions(parser)
Options.addFSOptions(parser)
# Add the ruby specific and protocol specific args
if "--ruby" in sys.argv:
    Ruby.define_options(parser)

而我们的命令行如下,激活了Ruby.define_options(parser)

/build/X86/gem5.opt -d m5out/yzNov08_black_simsmall configs/deprecated/example/fs.py 、
--kernel=/home/yz/.cache/gem5/x86-linux-kernel-4.19.83 --disk=/home/yz/.cache/gem5/x86-parsec \
     --ruby --network=garnet   --num-cpus=4 --num-dirs=4   --num-l2caches=4 \
     --topology=Mesh_XY --mesh-rows=2

gem5/configs/ruby/Ruby.py中,我们刚刚调用的“Ruby.define_options(parser)” 把cpu_type变成了Ruby默认的"TimingSimpleCPU"。

def define_options(parser):
    # By default, ruby uses the simple timing cpu
    parser.set_defaults(cpu_type="TimingSimpleCPU")

如果没有制定–Ruby,则会是默认的"AtomicSimpleCPU",来自与这下面这个文件: gem5/configs/common/Options.py
在这里插入图片描述
Options.py中的代码,默认的cpu type是

parser.add_argument(
        "--cpu-type",
        default="AtomicSimpleCPU",
        choices=ObjectList.cpu_list.get_names(),
        help="type of cpu to run with",
    )
Ruby默认的CPU类型:TimingSimpleCPU
Ruby默认的NoC频率/Ruby clock
def define_options(parser):
    parser.add_argument(
        "--ruby-clock",
        action="store",
        type=str,
        default="2GHz",
        help="Clock for blocks running at Ruby system's speed",
    )
Ruby默认选项:其他不太关心的部分

这些部分没怎么读到,放上来备用,可以跳过不读,只是瞅几眼。

 # Options related to cache structure
    parser.add_argument(
        "--ports",
        action="store",
        type=int,
        default=4,
        help="used of transitions per cycle which is a proxy \
            for the number of ports.",
    )

    # network options are in network/Network.py

    # ruby mapping options
    parser.add_argument(
        "--numa-high-bit",
        type=int,
        default=0,
        help="high order address bit to use for numa mapping. "
        "0 = highest bit, not specified = lowest bit",
    )
    parser.add_argument(
        "--interleaving-bits",
        type=int,
        default=0,
        help="number of bits to specify interleaving "
        "in directory, memory controllers and caches. "
        "0 = not specified",
    )
    parser.add_argument(
        "--xor-low-bit",
        type=int,
        default=20,
        help="hashing bit for channel selection"
        "see MemConfig for explanation of the default"
        "parameter. If set to 0, xor_high_bit is also"
        "set to 0.",
    )

    parser.add_argument(
        "--recycle-latency",
        type=int,
        default=10,
        help="Recycle latency for ruby controller input buffers",
    )

    protocol = buildEnv["PROTOCOL"]
    exec(f"from . import {protocol}")
    eval(f"{protocol}.define_options(parser)")
    Network.define_options(parser)

Ruby以外的默认选项

makeX86System

在makeX86System中
self = System()
创建一个新的系统对象 self。

self.m5ops_base = 0xFFFF0000
设置系统的m5ops基地址,用于特殊的内存映射I/O操作。

if workload is None:
检查是否提供了工作负载,如果没有,则创建一个新的。

workload = X86FsWorkload()
创建一个默认的x86文件系统工作负载。

self.workload = workload
将创建的工作负载赋值给系统对象。

if not mdesc:
检查是否提供了系统描述(mdesc),如果没有,则创建一个默认的。

mdesc = SysConfig()
创建一个默认的系统配置。

self.readfile = mdesc.script()
设置系统读取的脚本文件。

self.mem_mode = mem_mode
设置系统的内存模式。

Physical memory setup
接下来的代码块设置物理内存。

excess_mem_size = convert.toMemorySize(mdesc.mem()) - convert.toMemorySize(“3GB”)
计算超过3GB的额外内存大小。

if excess_mem_size <= 0: 和 else:
根据是否有超过3GB的额外内存,执行不同的逻辑。

self.mem_ranges = [AddrRange(mdesc.mem())]
设置物理内存范围。

self.pc = Pc()
创建一个PC平台对象。

if Ruby: 和 else:
根据是否使用Ruby内存系统,连接系统的不同部分。

connectX86RubySystem(self) 或 connectX86ClassicSystem(self, numCPUs)
调用函数连接系统的组件。

disks = makeCowDisks(mdesc.disks())
创建模拟磁盘。

self.pc.south_bridge.ide.disks = disks
将模拟磁盘连接到南桥的IDE控制器。

Add in a Bios information structure.
接下来的代码块设置BIOS信息结构。

structures = [X86SMBiosBiosInformation()]
创建BIOS信息结构列表。

workload.smbios_table.structures = structures
将BIOS信息结构赋值给工作负载。

Set up the Intel MP table
设置Intel MP(多处理器)表。

base_entries = [] 和 ext_entries = []
创建基础和扩展条目的列表。

Populate base and extended entries
填充基础和扩展条目的代码。

workload.intel_mp_table.base_entries = base_entries
将基础条目设置到工作负载的Intel MP表。

workload.intel_mp_table.ext_entries = ext_entries
将扩展条目设置到工作负载的Intel MP表。

Root 在 Root(full_system=True, system=test_sys)

fs.py代码需要输入test_sys和root。阅读代码和打印结果,我们确认了root是下面的代码。

 print("yzzz print we are using  root = Root(full_system=True, system=test_sys) \n")#我自己加的,double check
 root = Root(full_system=True, system=test_sys)

我们fs.py是在m5.objects引用了Root的。这谁看的出来呢。。,这不是全都引用了么,只有一个*.

from m5.objects import *

是的,所以我们看别的python文件,发现x86-parsec-benchmarks.py 明确引用了单独的Root,终于找到了!

from m5.objects import Root

在gem5/src/sim/Root.py里,定义了

class Root(SimObject):

    _the_instance = None

    def __new__(cls, **kwargs):
        if Root._the_instance:
            fatal("Attempt to allocate multiple instances of Root.")
            return None
        # first call: allocate the unique instance
        #
        # If SimObject ever implements __new__, we may want to pass
        # kwargs here, but for now this goes straight to
        # object.__new__ which prints an ugly warning if you pass it
        # args.  Seems like a bad design but that's the way it is.
        Root._the_instance = SimObject.__new__(cls)
        return Root._the_instance
    @classmethod
    def getInstance(cls):
        return Root._the_instance
    def path(self):
        return "root"
    type = "Root"
    cxx_header = "sim/root.hh"
    cxx_class = "gem5::Root"
    # By default, root sim object and hence all other sim objects schedule
    # event on the eventq with index 0.
    eventq_index = 0
    # Simulation Quantum for multiple main event queue simulation.
    # Needs to be set explicitly for a multi-eventq simulation.
    sim_quantum = Param.Tick(0, "simulation quantum")
    full_system = Param.Bool("if this is a full system simulation")
    # Time syncing prevents the simulation from running faster than real time.
    time_sync_enable = Param.Bool(False, "whether time syncing is enabled")
    time_sync_period = Param.Clock("100ms", "how often to sync with real time")
    time_sync_spin_threshold = Param.Clock(
        "100us", "when less than this much time is left, spin"
    )

在这里插入图片描述

在 root.cc 文件中:
Root 类的定义和实现提供了用于创建和管理gem5模拟环境根对象的方法和属性。
类中包含的方法如 startup、serialize、unserialize 分别用于处理模拟的启动、序列化(保存状态)和反序列化(恢复状态)。
类中还定义了一些统计信息,如模拟的秒数、模拟的时钟周期数等。
Root::Root 构造函数初始化根对象。它设置了时间同步事件、序列化参数等,并断言只能创建一个根对象。

进入gem5仿真的系统后的命令行:

我们上面是启动gem5.opt,下面是 ./util/term/m5term localhost 3457
在这里插入图片描述

7分钟就可以看到welcome to ubuntu,但是30分钟过去了还不能输入命令行。
大概37分钟后,可以看到对gem5仿真的ubuntu系统进行操作。
在这里插入图片描述
反应很慢,一个ls都要等很久的程度。
我们在gem5仿真的ubuntu系统里,创建一个checkpoint然后退出。

root@gem5-host:~# m5 checkpoint
root@gem5-host:~# m5 exit

m5out的文件如下:

cpt文件夹里是:
在这里插入图片描述

garnet 版本全系统命令行

花了大概四个小时,终于进系统了。。

命令行和脚本文件

我用的是x86-parsec image ,自带有 /home/gem5/parsec-benchmark;
script的作用就是自动执行,我们也可以启动gem5时候不指定脚本文件,手动执行下面的代码。

cd /home/gem5/parsec-benchmark; source env.sh; parsecmgmt -a run -p blackscholes  -c gcc-hooks -i simsmall  -n 2; sleep 5; m5 exit;

代码解释:
1.进入目标文件夹。
2. 激活一下环境
3. 运行parsec benchmark。这里我选的是手动指定而不是argparse cpu核是2个。
4. 最后是退出部分,m5 exit 以及之前的sleep 5 为什么要睡眠5 不知道,猜测是给予一定的缓存。

check point 失败小插曲

我们想在启动完成进入系统的时候创建一个checkpoint,如果用默认的x85,是mesi_two_level,这时候执行 m5 check point,会报错 n “Invalid RubyRequestType” in the MESI_Two_Level-L1cache.sm file.
其他帖子里写的"ruby的checkpoint比较特别,是一定要是 moesi hammer的,然后恢复的时候可以用别的协议恢复。"并没有生效,可能是fs.py用了一些弃用的功能导致的冲突。

运行结束,获得parsec结果

不用check point以后,我们获得了仿真2x2的board上跑的parsec结果。 具体的结果分析放在另一篇博客 GEM5教程: stats.txt 结果分析 中。
在这里插入图片描述

fs.py 启动流程:从敲入命令行到gem5自动退出运行

这里的流程和代码会有些不一样,我们从上到下,由总到分来看,更符合人类的逻辑:

1. GEM5启动

在fs.py的最后,启动了仿真运行。

Simulation.setWorkCountOptions(test_sys, args)
Simulation.run(args, root, test_sys, FutureClass)

2. 仿真启动设置:Simulation.run(args, root, test_sys, FutureClass)

args 变量与命令行参数解析器

Simulation.run(args, root, test_sys, FutureClass)中地一个输入就是args

root整个模拟环境的根节点

Simulation.run(args, root, test_sys, FutureClass)第二个需要 root。
Root 对象是 gem5 的一个核心组件,用于表示整个模拟环境的根节点。所有其他模拟组件(如处理器、内存等)都挂载在这个根对象下。它通常在创建和配置模拟场景时用作起点。
阅读代码和打印结果,我们确认了这个命令行使用的root是下面的代码。如果命令行输入的参数是其他isa,那就会有不同的结果,我们用的是x86。

 print("yzzz print we are using  root = Root(full_system=True, system=test_sys) \n")#我自己加的,double check
 root = Root(full_system=True, system=test_sys) 

这部分创建了 Root 类的一个实例,命名为 root。这个实例代表了整个模拟环境的顶层节点。
full_system=True: 这个参数指示模拟器模拟的是一个完整的系统,包括处理器、内存、I/O设备等。这与仅模拟系统的某个部分(例如仅CPU或仅内存系统)相对。
system=test_sys: 这个参数将一个系统实例(在这个上下文中是 test_sys)附加到根节点。这个系统实例包含了模拟所需的具体硬件配置,如CPU类型、内存大小、设备配置等。
具体的代码定义之前介绍过了,Root在 gem5/src/sim/Root.py里定义。

class Root(SimObject):

test_sys 将要使用的系统配置

test_sys: 这个参数通常是指向一个系统对象的引用,该对象定义了模拟中将要使用的系统配置。这包括处理器、内存、I/O设备等。在不同的模拟脚本中,这个对象可能有不同的名称,但它的作用是相同的。

在fs.py中,test_sys是在build_test_system(np)中定义的 np是命令行输入的cpu num 比如2, 4 ,或者64. build_test_system(np): 这个函数构建一个测试系统。它首先确定使用哪个ISA,并根据ISA创建相应的系统。它也根据传入的参数设置缓存行大小、电压域、时钟域等。

下面是一长串代码与对应的解读

def build_test_system(np):
    cmdline = cmd_line_template()
    isa = get_runtime_isa()
	...#我们用的x86,省去其他riscv和arm的代码
    elif isa == ISA.X86:
        test_sys = makeLinuxX86System(
            test_mem_mode, np, bm[0], args.ruby, cmdline=cmdline
        )
    # Set the cache line size for the entire system
    test_sys.cache_line_size = args.cacheline_size
    # Create a top-level voltage domain
    test_sys.voltage_domain = VoltageDomain(voltage=args.sys_voltage)
    # Create a source clock for the system and set the clock period
    test_sys.clk_domain = SrcClockDomain(
        clock=args.sys_clock, voltage_domain=test_sys.voltage_domain
    )
    # Create a CPU voltage domain
    test_sys.cpu_voltage_domain = VoltageDomain()
    # Create a source clock for the CPUs and set the clock period
    test_sys.cpu_clk_domain = SrcClockDomain(
        clock=args.cpu_clock, voltage_domain=test_sys.cpu_voltage_domain
    )
    if buildEnv["USE_RISCV_ISA"]:
        test_sys.workload.bootloader = args.kernel
    elif args.kernel is not None:
        test_sys.workload.object_file = binary(args.kernel)
    if args.script is not None:
        test_sys.readfile = args.script
    test_sys.init_param = args.init_param
    # For now, assign all the CPUs to the same clock domain
    test_sys.cpu = [
        TestCPUClass(clk_domain=test_sys.cpu_clk_domain, cpu_id=i)
        for i in range(np)
    ]
    if args.ruby:
        bootmem = getattr(test_sys, "_bootmem", None)
        Ruby.create_system(
            args, True, test_sys, test_sys.iobus, test_sys._dma_ports, bootmem
        )
        # Create a seperate clock domain for Ruby
        test_sys.ruby.clk_domain = SrcClockDomain(
            clock=args.ruby_clock, voltage_domain=test_sys.voltage_domain
        )
        # Connect the ruby io port to the PIO bus,
        # assuming that there is just one such port.
        test_sys.iobus.mem_side_ports = test_sys.ruby._io_port.in_ports
        for (i, cpu) in enumerate(test_sys.cpu):
            #
            # Tie the cpu ports to the correct ruby system ports
            #
            cpu.clk_domain = test_sys.cpu_clk_domain
            cpu.createThreads()
            cpu.createInterruptController()
            test_sys.ruby._cpu_ports[i].connectCpuPorts(cpu)
    else:
        if args.caches or args.l2cache:
            # By default the IOCache runs at the system clock
            test_sys.iocache = IOCache(addr_ranges=test_sys.mem_ranges)
            test_sys.iocache.cpu_side = test_sys.iobus.mem_side_ports
            test_sys.iocache.mem_side = test_sys.membus.cpu_side_ports
        elif not args.external_memory_system:
            test_sys.iobridge = Bridge(
                delay="50ns", ranges=test_sys.mem_ranges
            )
            test_sys.iobridge.cpu_side_port = test_sys.iobus.mem_side_ports
            test_sys.iobridge.mem_side_port = test_sys.membus.cpu_side_ports
        # Sanity check
        if args.simpoint_profile:
            if not ObjectList.is_noncaching_cpu(TestCPUClass):
                fatal("SimPoint generation should be done with atomic cpu")
            if np > 1:
                fatal(
                    "SimPoint generation not supported with more than one CPUs"
                )
        for i in range(np):
            if args.simpoint_profile:
                test_sys.cpu[i].addSimPointProbe(args.simpoint_interval)
            if args.checker:
                test_sys.cpu[i].addCheckerCpu()
            if not ObjectList.is_kvm_cpu(TestCPUClass):
                if args.bp_type:
                    bpClass = ObjectList.bp_list.get(args.bp_type)
                    test_sys.cpu[i].branchPred = bpClass()
                if args.indirect_bp_type:
                    IndirectBPClass = ObjectList.indirect_bp_list.get(
                        args.indirect_bp_type
                    )
                    test_sys.cpu[
                        i
                    ].branchPred.indirectBranchPred = IndirectBPClass()
            test_sys.cpu[i].createThreads()
        # If elastic tracing is enabled when not restoring from checkpoint and
        # when not fast forwarding using the atomic cpu, then check that the
        # TestCPUClass is DerivO3CPU or inherits from DerivO3CPU. If the check
        # passes then attach the elastic trace probe.
        # If restoring from checkpoint or fast forwarding, the code that does this for
        # FutureCPUClass is in the Simulation module. If the check passes then the
        # elastic trace probe is attached to the switch CPUs.
        if (
            args.elastic_trace_en
            and args.checkpoint_restore == None
            and not args.fast_forward
        ):
            CpuConfig.config_etrace(TestCPUClass, test_sys.cpu, args)

        CacheConfig.config_cache(args, test_sys)

        MemConfig.config_mem(args, test_sys)
    if ObjectList.is_kvm_cpu(TestCPUClass) or ObjectList.is_kvm_cpu(
        FutureClass
    ):
        # Assign KVM CPUs to their own event queues / threads. This
        # has to be done after creating caches and other child objects
        # since these mustn't inherit the CPU event queue.
        for i, cpu in enumerate(test_sys.cpu):
            # Child objects usually inherit the parent's event
            # queue. Override that and use the same event queue for
            # all devices.
            for obj in cpu.descendants():
                obj.eventq_index = 0
            cpu.eventq_index = i + 1
        test_sys.kvm_vm = KvmVM()
    return test_sys
makeLinuxX86System 的输入

首先是判断isa,不同的isa,创建test_sys不一样。
x86创建的是test_sys = makeLinuxX86System( test_mem_mode, np, bm[0], args.ruby, cmdline=cmdline )
我们先看这些输入的值是什么:test_mem_mode, np, bm[0], args.ruby, cmdline=cmdline

test_mem_mode

第一个输入是 test_mem_mode,是Simulation.setCPUClass(args)定义的。

 # system under test can be any CPU
(TestCPUClass, test_mem_mode, FutureClass) = Simulation.setCPUClass(args)
#之前的import是: from common import Simulation

为了看 test_mem_mode先看右边的是三个值是怎么在setCPUclass里定义的。setCPUclass是gem5/configs/common/Simulation.py里的函数。

def setCPUClass(options):
    TmpClass, test_mem_mode = getCPUClass(options.cpu_type)
    CPUClass = None
	#...省略一些修正的代码
    return (TmpClass, test_mem_mode, CPUClass)

setCPUClass内, FutureClass是返回的CPUClass(none),暂时没有用到。TestCPUClass, test_mem_mode是返回的TmpClass, test_mem_mode,来自getCPUClass(options.cpu_type)。
对fs.py而言,(TestCPUClass, test_mem_mode, FutureClass) = Simulation.setCPUClass(args)中的TestCPUClass, test_mem_mode,是gem5/configs/common/Simulation.py/getCPUClass(options.cpu_type)的结果。

def getCPUClass(cpu_type):
    """Returns the required cpu class and the mode of operation."""
    cls = ObjectList.cpu_list.get(cpu_type)
    return cls, cls.memory_mode()

gem5/configs/common/ObjectList.py 中,有个实例cpu_list = CPUList(getattr(m5.objects, “BaseCPU”, None))
这个CPUList 类型,而它并没有“get()”函数

class CPUList(ObjectList):

但他的父类型ObjectList有get函数:

class ObjectList(object):
	 def get(self, name):
        """Get a sub class from a user provided class name or alias."""

        real_name = self._aliases.get(name, name)
        try:
            sub_cls = self._sub_classes[real_name]
            return sub_cls
        except KeyError:
            print(f"{name} is not a valid sub-class of {self.base_cls}.")
            raise

也就是说,它会通过objectList这个类型自带的get函数,返回baseCPU这个objectList的某一个子类/sub_class。依据这是“name”,也就是我们输入的字符串名字,例如我们这一篇博客提到的默认值,TimingSimpleCPU。
那么baseCPU
在这里插入图片描述因此,我们知道了cls是TimingSimpleCPU,而我们返回了TimingSimpleCPU和 TimingSimpleCPU.memory_mode()
gem5/src/cpu/simple/BaseTimingSimpleCPU.py中,定义了aseTimingSimpleCPU.memory_mode() 返回的是字符串“timing”。

class BaseTimingSimpleCPU(BaseSimpleCPU):
    type = "BaseTimingSimpleCPU"
    cxx_header = "cpu/simple/timing.hh"
    cxx_class = "gem5::TimingSimpleCPU"

    @classmethod
    def memory_mode(cls):
        return "timing"

    @classmethod
    def support_take_over(cls):
        return True
np nm[0]

bm[0]:

bm 变量代表了一个基准测试(benchmark)的配置,这通常是一个列表或数组。
bm[0] 指的是这个列表中的第一个元素。在gem5中,这个元素可能包含了与某个特定基准测试相关的配置信息,如磁盘映像、根设备、内存大小和操作系统类型等。
这个配置信息随后用于构建模拟系统,确保模拟环境符合预期的测试场景。
np:

np 变量通常代表了处理器的数量,即模拟环境中的CPU核心数量。
在gem5模拟器中,np 用于定义模拟系统中将要创建的CPU对象的数量。例如,在多核心系统模拟中,np 将决定有多少个CPU核心被初始化和模拟。

args.ruby 和 cmdline

args.ruby:

这是一个参数,可能来自命令行解析(使用 argparse 模块)或者从其他配置来源。
args.ruby 通常用于指定是否使用gem5的Ruby内存系统模拟。Ruby是gem5中用于高级缓存和内存系统模拟的一部分。它提供了一种灵活的方式来模拟复杂的内存系统,包括各种缓存协议和配置。
当 args.ruby 被设置为True时,它会触发脚本中的逻辑,以使用Ruby内存系统来构建模拟环境。
cmdline=cmdline:

这是一个函数调用中的命名参数。cmdline 在这里作为参数名(左侧)和变量(右侧)出现。
变量 cmdline 可能是一个字符串,包含了传递给模拟操作系统的命令行参数。这对于配置模拟环境中的操作系统非常重要,例如设置根文件系统、启动选项等。
在函数调用中,cmdline=cmdline 传递这个命令行字符串到函数内部,允许函数根据这些参数配置模拟系统。

makeLinuxX86System 的内容/作用

makeLinuxX86System 函数在gem5模拟器中用于创建并配置一个用于模拟的x86系统,特别是运行Linux操作系统的系统。这个函数的具体作用包括但不限于以下几点:

创建系统对象:它初始化一个代表x86体系结构的系统对象。这个对象包含了用于模拟的各种硬件组件的配置,例如处理器、内存、I/O设备等。

配置处理器:函数根据提供的参数(如CPU类型、数量)配置处理器。这可能包括设置CPU的特定模型、数量(核心数)、时钟频率等。

内存和缓存配置:它会根据需要设置系统的内存大小和布局,以及可能的缓存层次结构。这可能涉及到主内存的配置,以及一级、二级缓存等的设置。

I/O和外设:设置系统的输入/输出系统,包括磁盘、网络接口等。这也可能涉及到特定的驱动程序和接口的模拟。

操作系统特定配置:对于Linux系统,可能需要设置特定的启动参数,如内核映像、根文件系统、启动命令行参数等。

虚拟化支持:如果模拟环境需要,还会设置虚拟化相关的支持,如KVM支持等。

连接系统组件:最后,它会将所有这些组件连接起来,确保它们能够以一种协调的方式工作。这包括确保CPU、内存、I/O设备等正确地连接到系统的总线和互联网络上。

这个函数的实现可能会根据gem5的版本和配置有所不同。通常,它是gem5模拟器中的一个重要函数,用于确保能够精确地模拟特定类型的系统。在gem5的源代码中,这个函数的具体实现细节可以在与系统构建相关的Python文件中找到。
具体的说,在gem5/configs/common/FSConfig.py
makeLinuxX86System 函数在 FSconfig.py 脚本中用于构建和配置用于模拟运行Linux操作系统的x86系统。以下是函数中每一行代码的解释:

def makeLinuxX86System(mem_mode, numCPUs=1, mdesc=None, Ruby=False, cmdline=None):

定义函数 makeLinuxX86System,接受内存模式(mem_mode)、CPU数量(numCPUs)、系统描述(mdesc)、是否使用Ruby内存系统(Ruby)和启动命令行(cmdline)作为参数。

  1. 第一步直接引用的self = makeX86System(mem_mode, numCPUs, mdesc, X86FsLinux(), Ruby)函数来构建基本的x86系统。这个函数用于创建一个基本的x86系统,不特定于任何操作系统。它构建了系统的基础结构,包括内存、处理器、PCI设备等
  2. 然后对其进行额外的配置以适应Linux环境。
  3. 特定于Linux的配置可能包括设置启动参数、内存映射、BIOS信息、E820内存表等,这些都是Linux启动和运行所必需的。

小结

本文总结了fs.py的命令行输入,以及对应的fs.py的运行解读,以及fs.py和命令行没有直接说明的默认文件的路径和设置,避免新手一头雾水的读下来不知道到底是哪里进行了设置。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值