3.21【A】

双 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 的构建逻辑全部定义在项目根目录的 Makefiledocker/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-pluginln -s 等操作需要在镜像层中固化,否则每次启动容器都要重新执行,效率低;
  • 用户权限适配remoteUser: simbricks 和 UID/GID 调整(-uid 后缀的由来)需要修改镜像的用户配置,因此必须构建新镜像;
  • 简化使用:工具自动处理镜像构建,你只需要写配置,无需手动写 Dockerfile。
  • 你运行 .devcontainer.json 时,Dev Containers 工具会:
    1. 检查是否已有对应的定制镜像(vsc-simbricks-xxx-uid);
    2. 如果没有,就基于基础镜像构建(这就是你在 Docker 终端看到这个镜像的原因);
    3. 用这个定制镜像启动容器,应用 runArgs/mounts 等运行时配置;
  • 这个定制镜像的命名规则是:vsc-<项目名>-<随机哈希>-uid,其中 uid 是为了适配宿主机 / 容器的用户 UID 一致性(避免权限问题)。

核心逻辑:基础镜像 → 定制镜像 → 容器,而非直接用基础镜像启动容器。

网络相关

  • 不会重新构建定制镜像vsc-simbricks-xxx-uid):镜像的构建只和 imagepostCreateCommandremoteUser 等 "镜像层配置" 相关,--network=host容器运行时参数(不写入镜像层);
  • 会重新创建容器 :修改 runArgs 后,VS Code 会停止旧容器,用 "原定制镜像 + 新的运行时参数(--network=host)" 启动新容器(镜像不变,容器是新的)

要让 Dev Container 创建的容器直接使用宿主机网络栈,只需在 .devcontainer.jsonrunArgs 中添加 --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 服务

-vdocker 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/kvmpostStartCommand 配置了 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 变成了 22 是非法值 → 报错指向 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

相关推荐
今儿敲了吗2 小时前
python基础学习笔记第九章——模块、包
开发语言·python
xyq20242 小时前
TypeScript 命名空间
开发语言
2301_810160952 小时前
C++与物联网开发
开发语言·c++·算法
sxlishaobin2 小时前
Java I/O 模型详解:BIO、NIO、AIO
java·开发语言·nio
cm6543202 小时前
基于C++的操作系统开发
开发语言·c++·算法
ArturiaZ2 小时前
【day57】
开发语言·c++·算法
wjs20242 小时前
XML 技术
开发语言
沪漂阿龙2 小时前
Python 面向对象编程完全指南:从新手到高手的进阶之路
开发语言·python·microsoft
chushiyunen2 小时前
python中的异常处理
开发语言·python