双 gem5 Host + SimBricks PCIe NIC + Switch的分布式仿真架构:
- 每个 gem5 实例模拟一个主机(Host),内部包含 PCIe Device Adapter,对接外部 SimBricks Adapter。
- SimBricks Adapter 将 gem5 的 PCIe 事务转换为 SimBricks 协议,连接到 NIC 模型。
- 两个 NIC 通过一个中央 Switch 实现网络互联,完成跨主机通信。
dist/:用于在多台物理主机上运行分布式 SimBricks 仿真时的代理。
在分布式系统(Distributed Systems)中,"Proxy" 的核心概念依然是**"代表"** 或**"中介"**。
在这个具体的 SimBricks 仿真场景中,它的作用通常是:
- 通信桥梁:当仿真任务分散在多台物理机器上时,这些机器需要互相交换数据(例如模拟的网络包或硬件信号)。Proxy 进程负责接收本地模拟的数据,并将其转发给远程主机上的其他模拟组件。
- 解耦与抽象:它让本地的模拟模块不需要知道远程机器的具体细节(如IP地址、复杂的网络拓扑),只需要把数据交给本地的 Proxy,由 Proxy 负责后续的传输和处理。
simbricks-runSimBricks 核心启动器是 SimBricks 框架的顶层脚本 / 二进制程序,负责解析配置、管理子进程(如 gem5/NIC/Switch)、协调组件启动顺序和通信,是运行 SimBricks 仿真的入口。
--verbose启用详细日志模式执行时会输出所有组件(gem5 主机、NIC、Switch、Adapter)的实时日志(包括调试信息、通信报文、时序同步数据),默认模式只输出关键错误 / 完成信息,调试时必开。
--force强制覆盖 / 清理旧状态并重新运行1. 清理上一次仿真残留的临时文件(如 socket 套接字、日志文件、进程 pid 文件);2. 忽略 "端口已占用""旧进程未退出" 等非致命提示,强制启动新仿真;3. 避免因前一次仿真异常退出导致的启动失败。
pyexps/simple_ping.py仿真任务的配置脚本路径SimBricks 的仿真逻辑(组件拓扑、参数、workload)都通过 Python 脚本定义,这个文件是官方示例:- 定义了 "两台 gem5 主机通过 SimBricks NIC + Switch 执行 ping 测试" 的完整拓扑;- 内置了组件启动参数、端口映射、时间同步规则等。
simbricks-run --verbose --force pyexps/simple_ping.py
- KVM:简单说就是 Linux 系统内置的 "硬件虚拟化加速引擎",能让软件模拟器(比如 gem5/QEMU)直接调用 CPU 的虚拟化指令(如 Intel VT-x/AMD-V),而不是纯软件模拟 CPU 指令,速度能提升几十倍甚至上百倍。
- /dev/kvm:是 KVM 给用户层程序(比如模拟器)提供的 "访问接口"(字符设备文件),模拟器需要读写这个文件才能调用 KVM 加速。
- 传入容器 :如果你的 SimBricks 是跑在 Docker / 容器里的,容器默认隔离了宿主机的设备文件,需要手动把
/dev/kvm映射到容器内,模拟器才能用这个接口。
SimBricks 里的 gem5 模拟器默认是纯软件模拟 CPU 指令,如果你跑的是 "全系统仿真"(带 Linux 内核),开启 KVM 后,gem5 会把 Guest OS 的非特权指令直接交给宿主机 CPU 执行,不用再逐行翻译,整个仿真的启动速度、网络 ping 测试的响应速度都会大幅提升。
如果你的 SimBricks 跑在 Docker 容器里,启动容器时加以下参数即可:
启动容器时映射/dev/kvm,并赋予权限
docker run -it --privileged -v /dev/kvm:/dev/kvm 你的-simbricks-image:tag
-
--privileged:赋予容器访问宿主机设备的权限(也可以用--device /dev/kvm更精细); -
-v /dev/kvm:/dev/kvm:把宿主机的/dev/kvm映射到容器内相同路径。 -
容器(Docker):把它想象成一个 "轻量级的独立小电脑"------ 有自己的文件系统、环境、程序,但它不是真的独立操作系统,而是共享宿主机(你真实的电脑 / 服务器)的内核和硬件。
-
跑在容器里的 SimBricks:就是把 SimBricks 的所有依赖(编译好的二进制、gem5、配置文件等)都打包在这个 "小电脑" 里运行,而不是直接装在你的真实系统上。
容器有自己独立的文件系统(相当于一个封闭的文件夹),宿主机的文件默认不在这个文件夹里,容器内的程序看不到。
"映射文件 / 设备到容器内" = 把宿主机的某个文件 / 设备,"链接" 到容器的文件系统里,让容器内的程序能像访问自己的文件一样访问它。
比如 /dev/kvm 是宿主机的 "硬件虚拟化接口",容器内的 SimBricks/gem5 要用到这个接口,就必须把宿主机的 /dev/kvm 映射到容器内的同一个路径(比如 /dev/kvm)。
SimBricks 的 sims/external/qemu 目录通常是通过 git submodule 拉取 QEMU 源码,但构建 Docker 镜像时:
- 要么是没有初始化 submodule ,导致
sims/external/qemu是空目录; - 要么是 submodule 拉取失败,QEMU 源码不完整;
- 要么是 Docker 构建上下文(
build context)没有包含 QEMU 源码目录。
jiake@Super:~/project/simbrick/simbricks$ git submodule update --recursive
jiake@Super:~/project/simbrick/simbricks$ git submodule init
jiake@Super:~/project/simbrick/simbricks$ git submodule status
7e7566532db183cf0000369f1de70939d6a8e2a7 sims/external/bmv2 (heads/main)
ee36df04362b958e0bde8d400aacba48f978a97b sims/external/femu (heads/main)
2c500a6a7527a1305e1a8e03f53ea11e90b71b73 sims/external/gem5 (heads/main)
1ce6dca3b68da284eb0ce4a47f7790d0a0e745d8 sims/external/ns-3 (ns-3.38-55-g1ce6dca3b)
c247fb0c7c5fd4e24b5f08f65857b7bf08ce20fe sims/external/qemu (heads/main)
jiake@Super:~/project/simbrick/simbricks$ cat .git/config | grep -A3 submodule
submodule "sims/external/bmv2"
active = true
url = https://github.com/simbricks/bmv2.git
submodule "sims/external/femu"
active = true
url = https://github.com/simbricks/femu.git
submodule "sims/external/gem5"
active = true
url = https://github.com/simbricks/gem5.git
submodule "sims/external/ns-3"
active = true
url = https://github.com/simbricks/ns-3.git
submodule "sims/external/qemu"
active = true
url = https://github.com/simbricks/qemu.git
SimBricks 的构建逻辑全部定义在项目根目录的 Makefile、docker/rules.mk 等文件中,所有 make 命令的执行都依赖这些规则文件
Linux/Shell 中,命令的输出分两种:
stdout(标准输出,正常日志,对应符号1>);stderr(错误输出,报错信息,对应符号2>)。
格式:命令 > 输出文件路径 simbricks-run --verbose --force pyexps/simple_ping.py > simbricks_output.log
覆盖写入:每次运行清空旧日志,保存新日志
make docker-images &> make_docker_build.log
追加写入:保留旧日志,新日志加在末尾(适合多次调试) make docker-images &>> make_docker_build.log
git submodule status 输出开头带 -(如 -c247fb0c7... sims/external/qemu),这是关键信号------ 它表示:
Submodule 已在
.gitmodules中配置,但本地并未实际检出(checkout)源码文件(仅记录了 commit ID,目录为空 / 不完整)。
简单说:
git submodule status带-→ Submodule 未真正初始化(只是 "登记了信息",但没下载源码);- Docker 构建时复制的是 "空的 / 不完整的
sims/external/qemu目录",自然找不到configure脚本
强制清理并重新拉取 QEMU Submodule(核心)
# 进入 SimBricks 项目根目录
cd ~/project/cxl/simbricks
# 1. 取消初始化 QEMU Submodule(清除旧配置)
git submodule deinit -f sims/external/qemu
# 2. 删除本地空的 QEMU 目录
rm -rf sims/external/qemu
# 3. 重新初始化并拉取 QEMU 源码(递归拉取所有依赖)
git submodule update --init --recursive sims/external/qemu
# 4. 验证 Submodule 状态(关键:开头无 `-`)
git submodule status
✅ 正常输出示例:c247fb0c7c5fd4e24b5f08f65857b7bf08ce20fe sims/external/qemu (heads/main)(无 - 前缀)。
Project
先通过git submodule update --init拉取外部模拟器模块进项目,然后再依据需求进行编译
安装gem5
make -j16 sims/external/gem5/ready


make -j16 build-images > buildImage.log 2>&1

网络

Clash
Clash 本质是本地代理网关,核心作用是 "接管网络请求 → 按规则路由 → 加密转发 → 接收数据并返回"

- 监听端口:Clash 会在宿主机本地监听一个端口(如你的 53564),所有配置了代理的程序,都会把请求发到这个端口;
- 规则路由:不是所有请求都走代理(比如访问百度直连,访问 GitHub 走代理);
- 加密转发:Clash 把请求加密后发给远端代理服务器,避免被运营商拦截 / 限速。
Vscode的Docker容器

- 容器里的
Port: 46551→ 容器内的服务在46551端口监听 - 宿主机上的
Forwarded Address: 127.0.0.1:64712→ 你在宿主机浏览器里访问127.0.0.1:64712,流量会自动转发到容器的46551端口
端口转发是「容器→宿主机」,不是「宿主机→容器」
- 这个界面只显示容器内端口被转发到宿主机的条目(比如容器里跑了个 Web 服务,要让宿主机访问);
- 你的 Clash 代理
53564是宿主机上的端口,是「宿主机→容器」的方向(容器要访问宿主机的代理),不属于 "容器端口转发" 的范畴,所以不会出现在这个列表里。
Devcontainer
.devcontainer.json不是 "包装出 Image" ,也不是直接用配置里的image启动容器;- 它的逻辑是:基于你指定的基础镜像(
simbricks/simbricks-build:latest),临时构建一个 "定制化镜像"(就是你看到的vsc-simbricks-xxx-uid),再用这个定制镜像启动容器; - 你看到的
vsc-simbricks-xxx-uid:latest是 VS Code Dev Containers 工具自动构建的中间定制镜像,而非你配置里的原始基础镜像。
devcontainer自身就会产生一个中间的Image镜像

- 基础镜像 :你配置里的
image: "simbricks/simbricks-build:latest"(原始镜像,只读); - 定制镜像 :
vsc-simbricks-xxx-uid:latest(Dev Containers 自动构建,包含你的个性化配置:安装 docker-compose 插件、创建软链接、调整权限等); - 容器:最终运行的是这个定制镜像的实例。

- 原始镜像只读 :你配置里的
simbricks/simbricks-build:latest是只读的,无法直接把postCreateCommand等操作写入; - 配置需要固化 :
apt install docker-compose-plugin、ln -s等操作需要在镜像层中固化,否则每次启动容器都要重新执行,效率低; - 用户权限适配 :
remoteUser: simbricks和 UID/GID 调整(-uid后缀的由来)需要修改镜像的用户配置,因此必须构建新镜像; - 简化使用:工具自动处理镜像构建,你只需要写配置,无需手动写 Dockerfile。
- 你运行
.devcontainer.json时,Dev Containers 工具会:- 检查是否已有对应的定制镜像(
vsc-simbricks-xxx-uid); - 如果没有,就基于基础镜像构建(这就是你在 Docker 终端看到这个镜像的原因);
- 用这个定制镜像启动容器,应用
runArgs/mounts等运行时配置;
- 检查是否已有对应的定制镜像(
- 这个定制镜像的命名规则是:
vsc-<项目名>-<随机哈希>-uid,其中uid是为了适配宿主机 / 容器的用户 UID 一致性(避免权限问题)。
核心逻辑:基础镜像 → 定制镜像 → 容器,而非直接用基础镜像启动容器。
网络相关
- 不会重新构建定制镜像 (
vsc-simbricks-xxx-uid):镜像的构建只和image、postCreateCommand、remoteUser等 "镜像层配置" 相关,--network=host是容器运行时参数(不写入镜像层); - 会重新创建容器 :修改
runArgs后,VS Code 会停止旧容器,用 "原定制镜像 + 新的运行时参数(--network=host)" 启动新容器(镜像不变,容器是新的)
要让 Dev Container 创建的容器直接使用宿主机网络栈,只需在 .devcontainer.json 的 runArgs 中添加 --network=host 参数即可。
核心原因:--network=host 不解决「GitHub 访问链路」问题
虽然你配置了 --network=host 让容器复用宿主机网络,但容器内拉取 Git 子模块慢的核心问题不是「网络隔离」,而是:GitHub 本身对国内网络的访问带宽限制 / 链路拥堵 (即使宿主机能访问,也可能是 "能通但极慢"),--network=host 只是让容器走宿主机的网络栈,无法改变「GitHub 国际链路慢」的本质。
补充:你可能的误解
--network=host 仅保证:
- 容器和宿主机 "走同一个网络出口"(比如同一个路由器、同一个代理);
- 但如果宿主机访问 GitHub 本身就慢(比如无代理、直连国际链路),容器也会同步这个 "慢",而非提速
为什么宿主机能访问但容器仍慢?(常见诱因)
1. 宿主机的代理未同步到容器(最常见)
宿主机可能通过代理访问 GitHub(速度快),但容器虽然共享宿主机网络,却未配置代理环境变量,导致容器仍直连 GitHub(慢):
- 宿主机代理是 "进程级配置",而非 "系统级转发",容器不会自动继承;
- 即使
--network=host,容器内也需手动配置代理才能复用宿主机的代理。
Docker
Docker 的核心是 dockerd(Docker 守护进程),所有 docker build/docker run 命令都是通过 /var/run/docker.sock(Docker 套接字)和 dockerd 通信的。
挂载套接字 就是把宿主机的 /var/run/docker.sock 文件映射到容器内,让容器里的 docker 命令直接和宿主机的 dockerd 通信 ------ 容器内只有 docker 客户端,没有 dockerd 服务
-v 是 docker run 命令的参数,全称是 --volume(卷),核心作用是:将宿主机的文件 / 目录,挂载(映射)到容器内的指定位置,实现宿主机和容器之间的文件双向同步。
你可以把它理解成:给容器开一个 "窗口",直接访问宿主机的文件 / 目录。
-v <宿主机路径>:<容器内路径>
<宿主机路径>:必须是绝对路径 (如/var/run/docker.sock);<容器内路径>:也必须是绝对路径;- 两者用冒号
:分隔,不能有空格。
"runArgs": ["-v", "/var/run/docker.sock:/var/run/docker.sock"]
这两种写法最终实现的效果完全一致 (都是把宿主机 /var/run/docker.sock 挂载到容器内同路径),只是语法形式不同:
- 第一种用
.devcontainer.json原生的mounts字段(结构化配置); - 第二种用
runArgs传递docker run -v参数(命令行参数形式)。
两者本质都是 Docker 的绑定挂载(bind mount)
Image
- 镜像(Image)本身确实是只读的,不能改、不能运行。
- Docker Desktop 里给镜像加的 "启动 / 暂停" 按钮,并不是在操作镜像,而是:一键用这个镜像,新建并启动一个容器。
- 界面上写的是对镜像的操作,但实际动作是:基于镜像 → 新建容器 → 启动容器。

Docker Desktop 里镜像的 Actions 一般包括:
- Run:运行(新建容器)
- Pull:拉取(从仓库下载更新)
- Push:推送到仓库
- Tag:打标签(改名)
- Delete:删除镜像
- View details:看层信息
VSCODE+DOCKER
在 VS Code 中通过 Ctrl+Shift+P 打开 Dev Container 后,VS Code 内置的终端(Terminal)直接就是容器内的终端(准确来说是容器内的 shell 会话),而非宿主机的终端
# 1. 查看当前主机名(容器 ID)
hostname
# 结果:一串随机字符(如 b4618f36007f),和 Docker 容器 ID 一致,而非宿主机名
# 2. 查看当前进程的 cgroup(容器隔离标识)
cat /proc/1/cgroup
# 结果:包含 docker 相关路径(如 /docker/xxx),宿主机执行则无此内容
# 3. 查看 Docker 容器列表(容器内执行会调用宿主机 Docker,但自身是容器)
docker ps
# 结果:能看到当前运行的 Dev Container 容器(自身),证明在容器内


容器内网络
容器内拉取 Git 子模块的网络情况
容器启动后执行 git submodule update --init 时,网络走的是容器自身的网络栈(而非宿主机直接代理):
- 容器默认使用 Docker 网桥网络(IP 段通常是
172.17.0.0/16); - 容器的 DNS、代理、MTU 等网络配置独立于宿主机;
- 你遇到的 "拉取慢 / 中断",本质是容器内访问 GitHub 的网络链路不稳定(DNS 解析慢、带宽受限、HTTP/2 协议兼容性差等)。
- 网络配置优先在容器内修改(无需重新构建定制镜像,即时生效);
- 若要永久生效,可修改
.devcontainer.json让配置自动注入,下次启动容器时无需重复改; - 不需要重新构建定制镜像(网络配置是容器运行时属性,而非镜像层属性)。
DIND

Simbrick
采用使用宿主机的方式,不再安装docker in docker
- 原来的 DinD(Docker in Docker):你在出租屋里(容器),自己买了一套完整的厨房设备(独立 Docker 环境),自己做饭(跑容器 / 建镜像);
- 挂载套接字:你在出租屋里,不买厨房设备,直接通过墙上的管道(套接字)用房东家(宿主机)的厨房做饭,做完的饭(镜像 / 容器)也留在房东家。
Docker 的核心是 dockerd(Docker 守护进程),所有 docker build/docker run 命令都是通过 /var/run/docker.sock(Docker 套接字)和 dockerd 通信的。
example2分布式TCP代理
把仿真组件拆分到两个 Fragment(物理机 / 进程组),并通过 TCP 代理实现跨 Fragment 的时序同步和数据通信。
example1同一网络下双主机PCIE连结,单机部署
# 1. 导入 SimBricks 核心模块
# 实例化模块:负责定义仿真组件的部署方式(比如 Fragment)
from simbricks.orchestration import instantiation as inst
# 仿真模块:负责定义仿真逻辑和模拟器类型(比如 QemuSim、I40eNicSim)
from simbricks.orchestration import simulation as sim
# 系统模块:负责定义核心仿真对象(主机、网卡、交换机、磁盘等)
from simbricks.orchestration import system
# 实例化辅助工具:提供简化实例化的函数(比如 simple_instantiation)
from simbricks.orchestration.helpers import instantiation as inst_helpers
# 仿真辅助工具:提供简化仿真配置的函数(比如 simple_simulation)
from simbricks.orchestration.helpers import simulation as sim_helpers
# 2. 创建系统根对象
# sys 是整个仿真系统的"总容器",所有组件(主机、网卡、交换机)都要挂载到这个对象下
sys = system.System()
# 3. 创建基础磁盘镜像
# DistroDiskImage:SimBricks 预定义的 Linux 发行版磁盘镜像(比如 Ubuntu/CentOS)
# 参数 "base" 表示使用默认的基础镜像(需提前通过 SimBricks 脚本构建)
distro_disk_image = system.DistroDiskImage(sys, "base")
# 4. 创建第一台主机(host0)
# 注释掉的 CorundumLinuxHost 是另一种网卡(Corundum)的主机模型,这里改用 I40E 网卡
# I40ELinuxHost:使用 Intel I40E 网卡驱动的 Linux 主机(Full System 全系统仿真)
# host0 = system.CorundumLinuxHost(sys)
host0 = system.I40ELinuxHost(sys)
# 给 host0 挂载基础磁盘镜像(系统盘)
host0.add_disk(distro_disk_image)
# 给 host0 挂载 Linux 配置磁盘镜像(用于自动配置 IP、网关等网络参数)
host0.add_disk(system.LinuxConfigDiskImage(sys, host0))
# 5. 创建第一块网卡(nic0)并挂载到 host0
# IntelI40eNIC:模拟 Intel I40E 万兆网卡
nic0 = system.IntelI40eNIC(sys)
# 给 nic0 分配 IPv4 地址 10.0.0.1/24(默认子网掩码 255.255.255.0)
nic0.add_ipv4("10.0.0.1")
# 通过 PCIe 总线将 nic0 挂载到 host0(模拟物理机插网卡的过程)
host0.connect_pcie_dev(nic0)
# 6. 创建第二台主机(host1)(和 host0 配置完全对称)
host1 = system.I40ELinuxHost(sys)
# 挂载相同的基础磁盘镜像
host1.add_disk(distro_disk_image)
# 挂载配置磁盘镜像
host1.add_disk(system.LinuxConfigDiskImage(sys, host1))
# 7. 创建第二块网卡(nic1)并挂载到 host1
nic1 = system.IntelI40eNIC(sys)
# 分配 IP 地址 10.0.0.2/24(和 host0 同网段)
nic1.add_ipv4("10.0.0.2")
# PCIe 挂载到 host1
host1.connect_pcie_dev(nic1)
# 8. 创建以太网交换机(switch0)并连接两台主机的网卡
# EthSwitch:模拟二层以太网交换机(无 IP,仅转发帧)
switch0 = system.EthSwitch(sys)
# 将 nic0 的以太网接口连接到 switch0
switch0.connect_eth_peer_if(nic0._eth_if)
# 将 nic1 的以太网接口连接到 switch0
switch0.connect_eth_peer_if(nic1._eth_if)
# 9. 配置主机上运行的应用程序
# 给 host0 配置 Ping 客户端:目标是 nic1 的 IP(10.0.0.2)
ping_client_app = system.PingClient(host0, nic1._ip)
# wait=True:表示仿真会等待 ping 完成后再结束(否则可能主机还没启动就退出)
ping_client_app.wait = True
# 将 ping 应用添加到 host0 的启动项中(主机启动后自动运行 ping 10.0.0.2)
host0.add_app(ping_client_app)
# 给 host1 配置无限睡眠应用:让 host1 一直运行(不退出),以响应 host0 的 ping
host1.add_app(system.Sleep(host1, infinite=True))
# 10. 定义仿真器映射(指定每个组件用什么模拟器运行)
# simple_simulation:简化的仿真配置函数,返回一个 Simulation 对象
simulation = sim_helpers.simple_simulation(
sys, # 传入系统总容器
# compmap:组件类型 → 模拟器类型的映射
compmap={
# 所有 FullSystemHost(包括 I40ELinuxHost)用 QemuSim 模拟器(QEMU 全系统仿真)
system.FullSystemHost: sim.QemuSim,
# Intel I40E 网卡用 I40eNicSim 模拟器(专门的网卡仿真器)
system.IntelI40eNIC: sim.I40eNicSim,
# 以太网交换机用 SwitchNet 模拟器(SimBricks 轻量级交换机仿真器)
system.EthSwitch: sim.SwitchNet,
},
)
# 11. 定义实例化配置(部署方式)
# simple_instantiation:简化的实例化配置函数,返回 Instantiation 对象
instantiation = inst_helpers.simple_instantiation(simulation)
# 创建一个 Fragment(部署单元):所有组件都放在这个 Fragment 里
fragment = inst.Fragment()
# 将仿真中所有模拟器(QEMU、I40eNicSim、SwitchNet)添加到这个 Fragment
fragment.add_simulators(*simulation.all_simulators())
# 将 Fragment 赋值给实例化配置(表示所有组件部署在同一个 Fragment/物理机)
instantiation.fragments = [fragment]
# 12. 最终输出实例化列表(供 SimBricks 调度器读取,启动仿真)
instantiations = [instantiation]
system.System()是 "根",所有硬件组件(主机、网卡、交换机)都属于这个系统;simulation是 "逻辑层",定义每个组件用什么模拟器运行;instantiation是 "部署层",定义模拟器在哪个 Fragment(物理机)上运行。
隐藏的关键逻辑:
LinuxConfigDiskImage会自动给主机配置网卡 IP、路由,无需手动在系统内配置;connect_pcie_dev(nic)不仅是 "挂载网卡",还会自动建立主机和网卡之间的 PCIe 通信通道;connect_eth_peer_if会自动建立网卡和交换机之间的以太网帧传输通道。
运行逻辑:当你执行这段配置后,SimBricks 会:
-
启动 QEMU 运行 host0 和 host1;
-
启动 I40eNicSim 运行 nic0 和 nic1;
-
启动 SwitchNet 运行 switch0;
-
所有组件在同一个进程组 / 物理机内时序同步运行;
-
host0 启动后自动 ping 10.0.0.2,host1 一直运行以响应 ping。
-
构建一个双主机 + 交换机的网络仿真系统,通过 QEMU 模拟主机、专用模拟器模拟网卡 / 交换机,实现 ping 连通性验证;
-
代码分为三层:硬件组件定义 → 模拟器映射 → 部署方式配置;
-
所有组件部署在同一个 Fragment 中,保证时序同步,确保仿真逻辑的时间一致
Fragment
在 SimBricks 中,Fragment 是模拟器实例的部署单元,你可以把它理解成:
-
一个「容器 / 进程组」,或者「一台物理机 / 虚拟机」
-
所有被加入同一个
Fragment的模拟器(QEMU Host、NIC、Switch),会被调度在同一台物理机器上启动和运行 -
不同
Fragment可以部署在不同物理机上(分布式仿真),而你代码中fragments = [fragment]表示所有组件都在一个 Fragment 里(单机部署)。fragment = inst.Fragment()
把所有模拟器(QEMU、NIC、Switch)都加入同一个 Fragment
fragment.add_simulators(*simulation.all_simulators())
instantiation.fragments = [fragment] # 仅一个 Fragment
仿真的核心问题是「时间一致性」------ 不同模拟器(比如 QEMU 模拟主机、NIC 模拟网卡、Switch 模拟交换机)必须基于统一的时间轴运行,否则会出现:
- 主机发了一个数据包,但网卡还没 "走到" 对应的时间点,导致数据包丢失
- 交换机转发延迟计算错误,出现 "未来的包先到" 的逻辑错误
SimBricks 的时序同步分为两种级别,核心差异在 Fragment 部署方式:
| 部署方式 | 同步机制 | 精度 / 效率 |
|---|---|---|
| 同一 Fragment(单机) | 基于共享内存 + 全局时间锁:所有模拟器运行在同一台机器,通过共享内存交换时间戳,由一个核心进程统一控制 "时间步进"(比如所有模拟器都必须走完 100ns,才能进入下一个 100ns) | 精度高(纳秒级)、延迟低 |
| 不同 Fragment(分布式) | 基于网络通信 + 时间戳同步:不同机器的模拟器通过网络传递时间信息,需要做网络延迟补偿,同步逻辑更复杂 | 精度略低、有网络开销 |
你的代码中所有组件在同一个 Fragment,因此能直接使用最高精度的「共享内存全局时间同步」,确保 QEMU 主机、I40E 网卡、Switch 交换机的时间轴完全一致:
- 当
host0(QEMU)在时间 T 发送一个 ping 包时,nic0(I40E NIC)会在同一时间 T 接收并处理 switch0(Switch)会在时间 T + 转发延迟 处理这个包,nic1会在对应时间接收,最终host1会在正确的时间响应
容器相关
⚠️ warning: redirecting to https://gitlab.com/... → 只是 Git 仓库地址重定向(从 qemu.org 跳转到 gitlab.com),非错误,不影响拉取。
本地加载的 Submodule 是否进入 Docker 容器,取决于Docker 构建上下文 和COPY/ADD 指令,和 "本地是否加载 Submodule" 无直接关联(但本地状态会影响构建上下文)。

simbricks/simbricks-build:latest 镜像中预装了 SimBricks 编译运行的所有依赖,本地默认没有,需要手动安装
容器通过 --device=/dev/kvm 和 postStartCommand 配置了 KVM 访问,但本地需要手动满足:
-
❌ 本地缺失 1:未确认 KVM 硬件支持(SimBricks 依赖 KVM 加速 QEMU 仿真);
-
❌ 本地缺失 2:普通用户无
/dev/kvm读写权限(默认只有 root 可访问); -
❌ 本地缺失 3:未加载 KVM 内核模块(部分系统默认未启用)。
-
✅ 代码更新:Git 上传 / 拉取仍在本地执行(容器内操作最终也同步到本地);
-
⚠️ 运行适配:代码更新后,容器环境可能需要重新编译 / 安装依赖(如新增依赖、修改编译规则);
-
❌ 容器本身:代码更新不会自动修改容器镜像,需手动触发 "环境重建"。
容器对 "上传" 的影响
- 无负面影响 :Git 是分布式版本控制,代码的提交 / 推送本质是操作本地
.git仓库,容器只是 "运行代码的环境",不改变 Git 核心逻辑; - 正向作用:可先在容器内验证修改后的代码是否能编译 / 运行(容器环境和团队一致,避免 "本地能跑、远程跑不了");
- 注意点 :
- 如果修改了
requirements.txt/Dockerfile/Makefile等 "环境相关文件",需在容器内验证环境适配(如make clean && make),再提交代码; - 容器内的代码修改会实时同步到本地(因为 Dev Container 通常挂载本地目录到容器),无需手动复制。
- 如果修改了


环境一致性:消除 "环境不一致导致的迭代问题"
- 无容器:团队成员本地环境不同(如 GCC 版本、依赖库版本),代码更新后可能 "你能跑、我跑不了";
- 有容器:所有人使用相同的
simbricks/simbricks-build镜像,代码更新后,只要容器内验证通过,团队其他成员拉取后也能直接运行(仅需同步代码,无需适配环境)。
-
容器是 "无状态" 的:容器内的代码修改会同步到本地(挂载目录),但如果删除容器,容器内的临时编译产物会丢失(需重新编译);
-
核心保障:代码更新必须通过 Git 提交(本地 / 容器内提交均可),否则容器删除后修改会丢失。
-
Reopen in Container 过程的日志保存:需通过 VS Code 终端重定向或 Docker 日志命令捕获;
-
容器内日志的保存位置 :
- 若保存到挂载的本地目录 → 退出容器后本地可直接查看;
- 若保存到容器内非挂载目录 → 退出容器后本地无法查看(需手动拷贝);
-
Dev Container 默认会将本地项目目录挂载到容器内,因此容器内保存到项目目录的日志,本地可直接访问。
VS Code 的 "Reopen in Container" 本质是调用 docker run 等命令
镜像是容器的 "模板 / 只读蓝图",容器是镜像的 "可运行实例" ------ 类比理解:
- 镜像 = 手机 App 的安装包(只读、不可运行,包含运行所需的所有代码 / 依赖 / 配置);
- 容器 = 安装后正在运行的 App(可读写、独立运行,基于安装包创建,多个实例可同时运行)。

- 镜像:只读、不可修改、可重复使用 ,包含运行程序的所有环境(如 SimBricks 的
simbricks-build:latest镜像包含 GCC、依赖库、Python 包等); - 容器:可读写、独立运行、生命周期可控(启动 / 停止 / 删除),基于镜像创建,运行时会在镜像的只读层上添加一层 "可写层",所有修改都在可写层(删除容器后,可写层消失,镜像不变)。

docker in docker
features/src/docker-in-docker at main · devcontainers/features
Docker in Docker = 在 Docker 容器里面,再运行一个完整的 Docker 环境 也就是:容器套容器。
- 外面一层:宿主机器上的 Docker
- 里面一层:容器内部又装了 Docker 服务,可以自己建镜像、跑容器
就像:
电脑里开虚拟机 → 虚拟机里再开一个虚拟机
真正的 Docker in Docker(DinD)
- 容器内部独立运行一个全新 dockerd 进程
- 有自己的镜像、容器存储
- 和宿主机 Docker 完全隔离
- 缺点:性能差、权限高、不稳定
② Docker from Docker(挂载方式,更常用)
plaintext
-v /var/run/docker.sock:/var/run/docker.sock
- 容器里用的还是宿主机的 Docker
- 只是把控制接口映射进去
- 更轻、更快、更稳定
报错
1
官方文档里 {"ghcr.io/devcontainers/features/docker-in-docker:2": {}} 的写法是语法正确的,但你遇到的报错是 "配置参数的默认值适配问题" + "网络访问失败" 的双重问题,和写法无关。
报错 1:Invalid compose_version value: 2 ------ 是 "参数名误解" 导致的误判
你看到的报错里 Invalid compose_version value: 2,不是你写的配置错了,而是:
- Dev Container 安装 DinD 特性时,内部脚本会读取
dockerDashComposeVersion参数(官方文档里的选项名),而非compose_version; - 你的配置里没显式指定
dockerDashComposeVersion,脚本会用默认值v2(这本身是对的); - 真正的问题是:网络失败导致脚本提前终止,返回了错误的参数提示 (脚本没拿到正确的参数值,误报
compose_version=2无效)。
简单说:compose_version 是报错信息的 "伪因",核心是网络问题导致脚本没正常读取默认参数。
DinD 特性安装脚本需要:
- 从 GitHub 拉取 Docker Compose、Buildx 等组件;
- 验证 TLS 证书、建立 HTTPS 连接;
你的环境中:
- 容器内访问 GitHub 时 TLS 连接超时 / 中断(网络不稳定、DNS 解析失败、证书过期等);
- 脚本下载组件失败 → 后续参数校验逻辑混乱 → 抛出错误的
compose_version提示; - 最终 DinD 特性安装失败,Dev Container 启动终止。
simbricks/simbricks-build:latest 镜像可能:
- 预装了部分 Docker 组件(如 moby),和 DinD 特性的安装脚本冲突;
- 镜像内的 CA 证书过期,导致 TLS 连接验证失败;
- 镜像的网络配置(如 DNS)未适配 DinD 特性的网络访问需求。
虽然官方默认写法没错,但显式指定参数可避免脚本误判,修改 .devcontainer.json:
"features": { "ghcr.io/devcontainers/features/docker-in-docker:2": { "dockerDashComposeVersion": "v2", // 显式指定 Compose 版本 "moby": true, // 用 Moby 替代 Docker CE(适配镜像) "azureDnsAutoDetection": false // 关闭 Azure DNS 自动检测(非 Azure 环境) }

graph TD A[开始安装 DinD] --> B[读取配置参数:dockerDashComposeVersion] B --> C{参数是否有效?} C -- 是 --> D[下载对应版本的 Compose] C -- 否 --> E[使用默认值 v2] D --> F{网络能否访问 GitHub?} F -- 能 --> G[安装 Compose] F -- 否 --> H[脚本中断,返回错误码] H --> I[错误处理逻辑混乱,误把默认值v2解析为2] I --> J[抛出 Invalid compose_version value: 2]
脚本中 dockerDashComposeVersion 的有效值是 v2/v1/none,但网络失败时:
- 脚本下载 Compose 失败 → 内部变量解析逻辑中断;
- 脚本的 "错误兜底代码" 会把
v2截断为2(去掉前缀v); - 而脚本的参数校验逻辑只认
v2/v1/none,不认纯数字2→ 抛出 "2无效" 的错误。
简单说:v2 是合法值,但脚本中断后把 v2 变成了 2,2 是非法值 → 报错指向 2。
因为 Compose 是 DinD 安装脚本的 "核心依赖",脚本执行顺序决定了:
-
DinD 安装的第一步是 "配置 Docker Engine",第二步就是 "安装 Docker Compose";
-
网络访问 GitHub 主要是为了下载 Compose、Buildx 等组件,而 Compose 是第一个需要网络下载的核心组件;
-
网络失败会最先卡在 "下载 Compose" 环节 → 脚本中断在 Compose 版本校验步骤 → 必然报 Compose 版本错误,而非其他字段(如
moby/buildx)。



moby: true→ 解决 "容器内装 Docker CE 太重、易冲突" 的问题;azureDnsAutoDetection: false→ 解决 "非 Azure 环境下 DNS 配置错误" 的问题;- 两者结合,都是为了让 DinD 在你的本地环境更稳定,避免和 SimBricks 基础镜像冲突。
2
你之前把 -v 和路径写在同一个字符串里("-v /var/run/docker.sock:/var/run/docker.sock"),JSON 解析后会变成:
docker run "-v /var/run/docker.sock:/var/run/docker.sock" ...
Docker 会把整个带空格的字符串当成 "卷名",而非 "参数 + 路径",卷名不允许开头有空格,因此报错。
修正后拆分成两个字符串("-v", "/var/run/docker.sock:/var/run/docker.sock"),解析后是:
docker run -v /var/run/docker.sock:/var/run/docker.sock ...
Docker 能正确识别 -v 是参数,后面的路径是挂载目标,因此正常工作。
其他

1 KiB/s = 1024 字节/秒;1 MiB = 1024 KiB;
多数代理 / 路由器的 TCP 连接超时时间是 1-5 分钟,超过后会主动关闭连接,导致 "下到一半断了";
5.86 MiB= 已下载的代码大小;13.00 KiB/s= 当前下载速度(极慢,是断连的核心原因);- 解决思路:用浅克隆减少传输量,或切换更快的 Clash 节点提升速度
# 容器内执行,先配置这些参数(解决小数据包断连)
git config --global core.compression 0 # 关闭压缩,减少数据包解码错误
git config --global http.postBuffer 1048576000 # 增大缓存到 1G,适配大文件
git config --global http.maxRequestBuffer 100M # 增大请求缓存
git config --global http.keepAlive true # 开启持久连接,避免频繁握手断连
git config --global http.lowSpeedLimit 0 # 关闭低速限流(避免误判超时)
git config --global http.lowSpeedTime 999999 # 延长超时时间
- 浅克隆(--depth 1):只拉最新提交的代码,数据量从 14069 个对象降到几百个,大幅降低断连概率;
- 增大缓存 + 关闭压缩:避免 Git 因 "缓存不足 / 压缩解码错误" 主动终止连接;
- 延长超时时间:给网络传输留足缓冲,避免代理 / 路由器提前掐断连接

OMNeT++
- 全称:Objective Modular Network Testbed in C++
- 是什么 :它是一个离散事件仿真框架。你可以把它理解为一个"底座"或"引擎",专门用来模拟网络系统。它提供了图形用户界面、底层调度机制和模块化架构,但它本身并不包含具体的网络协议实现。
INET
- 是什么 :它是 OMNeT++ 的核心模型库。如果说 OMNeT++ 是操作系统,INET 就是上面的"应用程序"。它实现了各种标准的网络协议(如 TCP/IP, UDP, Ethernet, WiFi 等)。
- 作用:如果你想用 OMNeT++ 仿真一个路由器或交换机,你不能从零开始写代码,而是直接使用 INET 里已经写好的模块(比如现成的 TCP 协议栈)。
SimBricks 的主要目的是将不同的模拟器(如 gem5 用于 CPU,Verilator 用于硬件,ns-3 用于网络)连接起来进行协同仿真。
这句话的提出者希望将 OMNeT++/INET 也集成进来,原因可能包括:
- 更丰富的网络模拟:虽然 SimBricks 已经支持 ns-3 进行网络仿真,但 OMNeT++/INET 在某些特定的网络协议细节或图形化分析上可能具有优势。
- 统一构建流程 :目前可能需要手动去下载和编译 OMNeT++,这句话要求将其纳入主仓库(可能是作为子模块),并添加类似
make omnetpp这样的命令,让用户能像构建 QEMU 或 gem5 一样方便地构建它。
Corundum 指的是一个开源的高性能 FPGA 网络接口卡(NIC)项目。
它由加州大学圣地亚哥分校(UCSD)的系统网络实验室开发,旨在为研究人员和工程师提供一个灵活、高效的平台,用于开发和测试高速网络协议。
你可以把它理解为一个**"软件定义的网卡蓝图"**,允许开发者在 FPGA 硬件上定制自己的网卡逻辑,而不是购买功能固定的商业网卡。
| 特性 | 详细规格 |
|---|---|
| 网络速率 | 支持 10G / 25G / 100G 以太网 |
| 接口总线 | PCI Express Gen 3 (x8/x16) |
| DMA 引擎 | 自定义的高性能 PCIe DMA 引擎,支持零拷贝和散列/聚合 |
| 队列管理 | 支持 1000+ 个传输、接收、完成和事件队列 |
| 时间同步 | 支持 IEEE 1588 PTP 硬件时间戳(亚纳秒级精度) |
| 软件支持 | 提供 Linux 内核驱动程序,与操作系统网络栈深度集成 |
英语
"even more reason to" 的意思是"更有理由去做某事"。它通常用在已经存在一个理由的基础上,强调当前提到的情况让做某件事的必要性或紧迫性变得更强了。
orchestration: fix overly broad exceptions raised