简介
兜兜转转最后还是回到了了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 /build/X86/gem5.opt:这应该是gem5可执行文件的路径。它表示gem5的编译版本是优化的(opt)版本,并且是针对x86架构的。
- -d m5out/yzNov08_black_simsmall:这个参数指定了输出目录。仿真的所有输出文件都会被保存在这个目录下。
- –kernel=/home/yz/.cache/gem5/x86-linux-kernel-4.19.83:这个参数指定了Linux内核镜像的位置,gem5将使用这个内核进行仿真。
- 2 --disk=/home/yz/.cache/gem5/x86-parsec:这个参数指定了磁盘镜像的路径,这个镜像将会被挂载到仿真的文件系统中。
- 3 --ruby:这表示使用Ruby内存模型。Ruby是gem5中的一个高级缓存和内存系统模型。
- 4 --network=garnet:这表示使用Garnet网络模型,用于模拟片上网络。
- 5 --num-cpus=4:这指定了仿真使用的CPU核心数目。
- 6 --num-dirs=4:这指定了directory节点的数量,在基于Ruby的系统中,directory节点用于维护缓存一致性。
- 7 --num-l2caches=4:这指定了L2缓存的数量。
- 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""",
)
这个干了什么呢?
- 然后,代码导入了必要的模块和函数,这包括math模块和m5模块(这是gem5模拟器的一部分),以及其他一些从gem5导入的特定对象和函数。
- 接着,定义了一个define_options函数,它向命令行解析器添加了多个网络配置选项:
- –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)以及相关的内部和外部链路类、路由器类和接口类 - init_network函数用于进一步初始化网络。根据options中的设置,它可以设置garnet网络的参数,如网格行数、虚拟通道数、网络接口的数据单元大小、路由算法、死锁阈值等。如果启用了网络故障模型,它还会配置故障模型。
- 代码中还包括了一些用于创建和连接网络桥的代码。这些桥是用于连接网络组件,如路由器、链路和控制器,使得数据可以在它们之间传输。
- 这段代码没有运行任何网络模拟,它只是设置了网络模拟环境的参数,并提供了创建和初始化网络对象的函数。实际的模拟将在这些对象和设置被加入到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)
- 其中 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)作为参数。
- 第一步直接引用的self = makeX86System(mem_mode, numCPUs, mdesc, X86FsLinux(), Ruby)函数来构建基本的x86系统。这个函数用于创建一个基本的x86系统,不特定于任何操作系统。它构建了系统的基础结构,包括内存、处理器、PCI设备等
- 然后对其进行额外的配置以适应Linux环境。
- 特定于Linux的配置可能包括设置启动参数、内存映射、BIOS信息、E820内存表等,这些都是Linux启动和运行所必需的。
小结
本文总结了fs.py的命令行输入,以及对应的fs.py的运行解读,以及fs.py和命令行没有直接说明的默认文件的路径和设置,避免新手一头雾水的读下来不知道到底是哪里进行了设置。