新闻详情

新闻详情

首页 / 资讯中心 / 详情

BitBake在OpenBMC中的核心作用与实战指南

发布时间:2026/10/2 1:06:20来源:尧图网络
BitBake在OpenBMC中的核心作用与实战指南
1. 为什么BitBake是OpenBMC开发绕不开的“编译中枢”OpenBMC这个项目从第一天起就不是靠手动敲命令、复制粘贴文件堆出来的。它背后有一套精密运转的构建系统——Yocto Project而BitBake就是这套系统的发动机、调度员和总指挥。你可能已经跑通过./build.sh看到终端里刷出成千上万行日志最后生成一个.iso或.img镜像也可能在meta-openbmc层里改过bbappend文件却始终没搞清楚那行inherit autotools到底触发了什么动作。这些都不是魔法全是BitBake在幕后一帧一帧调度执行的结果。BitBake不是Make也不是CMake更不是随便写个Shell脚本就能替代的工具。它的核心价值在于声明式构建Declarative Build你不用告诉它“先解压tar包、再打补丁、再configure、再make install”而是用.bb和.bbclass文件描述“我要一个叫phosphor-host-ipmid的软件包它依赖systemd和libipmid源码来自Git仓库某分支需要打3个补丁编译时加-DENABLE_DEBUG1”。BitBake读完这些“声明”自动推导出完整的依赖图、执行顺序、缓存策略和并行调度方案。这种抽象层级让OpenBMC能在ARM、x86_64、PowerPC等不同架构上用同一套配方recipe稳定产出可复现的固件镜像——这正是硬件移植能落地的前提。我第一次在ASPEED AST2600平台做OpenBMC移植时卡在phosphor-fan-control编译失败整整两天。错误日志只显示fan_sensor.hpp not found但翻遍源码目录文件明明存在。后来才发现是BitBake的do_unpack任务没正确触发git submodule update --init导致子模块没拉下来。而这个行为是由SRC_URI中git://协议后的;branchmaster;protocolhttps参数和BBMASK环境变量共同决定的。没有理解BitBake的执行模型你连问题该往哪查都不知道。所以别把BitBake当成“高级Make”它是一套构建领域的DSL领域特定语言掌握它等于拿到了OpenBMC工程体系的钥匙。2. BitBake核心机制深度拆解从任务调度到缓存命中的真实逻辑2.1 构建单元的本质Recipe、Class、Conf三层结构如何协同工作BitBake的构建世界由三类核心文件构成它们不是平级关系而是有明确的继承与覆盖逻辑.bb文件Recipe这是最表层的“需求说明书”。比如meta-phosphor/recipes-phosphor/fans/phosphor-fan-control_%.bb它定义了PN包名、PV版本、SRC_URI源码位置、DEPENDS编译依赖、do_compile()自定义编译步骤等。注意那个_%.bb后缀——%是通配符表示匹配任意版本号这样phosphor-fan-control_2.12.0.bb和phosphor-fan-control_2.13.0.bb都能被识别避免每次升级都要改文件名。.bbclass文件Class这是“标准化操作模板”。autotools.bbclass封装了./configure make make install的标准流程systemd.bbclass负责自动生成systemd服务文件并安装到正确路径pkgconfig.bbclass则确保pkg-config能正确找到库的头文件和链接参数。当你在recipe里写inherit autotools systemdBitBake会把对应class里定义的do_configure、do_compile、do_install等任务按优先级注入到你的recipe任务链中。这就像给一个空壳子装上标准发动机和变速箱。.conf文件Configuration这是全局“运行规则手册”。conf/local.conf里MACHINE romulus决定了目标硬件平台DISTRO openbmc-phosphor指定了发行版策略BB_NUMBER_THREADS 8设置了并行任务数而最关键的DL_DIR /srv/openbmc/downloads则统一了所有recipe下载源码的缓存目录。这里有个极易踩坑的点BBFILES变量定义了BitBake扫描recipe的路径模式如${TOPDIR}/meta-*/recipes-*/*/*.bb如果你新增了一个layer但没把它加进BBLAYERS或者BBFILES没覆盖到新路径BitBake根本“看不见”你的recipe报错永远都是Nothing PROVIDES your-package-name而不是“找不到文件”。这三层不是简单叠加而是通过变量覆盖Variable Override和任务追加Task Append动态组合。比如phosphor-fan-control.bb里写EXTRA_OEMAKE -DUSE_NEW_SENSOR_API1这个操作会在autotools.bbclass定义的EXTRA_OEMAKE默认值后面追加内容而do_install_append()函数则会在autotools.bbclass的do_install任务执行完后再运行你自定义的安装后处理逻辑。理解这种“声明继承覆盖”的模型是读懂OpenBMC构建日志的第一步。2.2 任务图Task Graph是如何动态生成并执行的BitBake启动后并不会立刻去编译代码。它先做一件关键事解析所有相关recipe、class、conf构建一张有向无环图DAG这张图的每个节点是一个任务task每条边代表依赖关系dependency。以bitbake phosphor-fan-control为例简化后的任务图如下do_fetch → do_unpack → do_patch → do_configure → do_compile → do_install → do_package → do_image_wic但实际远比这复杂。do_fetch本身还依赖do_fetch[depends]指定的其他任务do_configure要等do_unpack和do_patch都完成而do_package又依赖do_install的输出。BitBake的调度器会根据这张图结合BB_NUMBER_THREADS和任务本身的task属性如noexec、nostamp智能分配CPU核心并行执行。真正体现BitBake威力的是任务缓存sstate cache机制。默认情况下BitBake会为每个任务生成一个唯一哈希值stamp file这个哈希值由任务输入决定包括recipe内容、SRC_URI指向的源码SHA256、EXTRA_OEMAKE的值、甚至HOST_ARCH等构建主机信息。如果哈希值没变BitBake就直接复用之前tmp/sstate-cache/目录下对应的.tgz缓存包跳过整个任务执行。这就是为什么你改了一行C代码bitbake phosphor-fan-control只会重新运行do_compile和后续任务前面的do_fetch、do_unpack全走缓存几秒就结束。但如果你改了SRC_URI里的Git commit ID整个哈希链就断了do_fetch必须重跑接着do_unpack、do_patch也得重来——因为输入变了。我曾遇到一个诡异问题在Jenkins CI流水线里bitbake openbmc-image耗时从15分钟暴增到90分钟。排查发现是CI脚本里export LANGC被误删了导致do_configure任务的stamp哈希值因locale差异而改变所有任务缓存全部失效。加上LANGC后时间立刻回归正常。这说明BitBake的缓存不是黑盒它的确定性完全依赖于构建环境的严格一致性。2.3 Layer机制OpenBMC硬件移植的工程化基石OpenBMC支持数十种BMC芯片ASPEED、Nuvoton、AMD、Intel每种芯片的寄存器定义、GPIO映射、看门狗驱动都不同。如果把这些差异全塞进meta-openbmc主层代码会迅速变成一团乱麻。Layer机制就是为了解决这个问题——它把功能按关注点垂直切分形成可插拔、可复用的模块。一个典型的OpenBMC layer结构如下meta-aspeed/ ├── conf/ │ └── layer.conf # 声明layer优先级、BBPATH、BBFILES ├── recipes-core/ │ └── initrdscripts/ # ASPEED专用的initrd启动脚本 ├── recipes-kernel/ │ └── linux-aspeed/ # ASPEED定制Linux内核recipe └── recipes-phosphor/ └── hardware/ # ASPEED硬件抽象层HALrecipelayer.conf里的LAYER_PRIORITY 6至关重要。当多个layer定义了同一个recipe比如都提供了linux-yocto_%.bbBitBake会按priority从高到低选择——数字越大优先级越高。meta-aspeed设为6meta-openbmc主层设为5这样ASPEED的定制内核就会覆盖通用内核。同理BBFILE_PRIORITY_aspeed 6可以为特定layer设置recipe匹配优先级。硬件移植的核心就是新建一个meta-your-company层里面只放三样东西conf/machine/your-platform.conf定义SOC_FAMILY aspeed、KERNEL_FEATURES features/your-platform.scc等平台特性recipes-kernel/linux/linux-aspeed-your-platform.bbappend用bbappend机制在上游linux-aspeed.bb基础上追加公司专属补丁和配置recipes-phosphor/hardware/your-hw-interfaces.bb实现公司特有的传感器读取、风扇控制、LED管理等HAL接口。这样当执行MACHINEyour-platform bitbake openbmc-image时BitBake会自动加载meta-your-company层优先使用其中的machine定义和recipe而其他通用功能如Web UI、REST API仍来自meta-openbmc。整个过程无需修改上游代码升级OpenBMC主干时只需同步meta-your-company层即可——这才是企业级硬件移植的正确打开方式。3. 实操全流程从零开始构建一个可调试的OpenBMC镜像3.1 环境初始化避开Ubuntu/Debian的“包管理陷阱”OpenBMC官方推荐在Ubuntu 20.04 LTS上构建但这不意味着直接apt install完事。Yocto对Python版本、GCC版本、甚至tar命令的行为都有严格要求。我见过太多人卡在第一步# ❌ 错误示范直接用系统默认pip安装 sudo pip3 install bitbake # ✅ 正确做法用Yocto提供的setup脚本 git clone https://github.com/openbmc/openbmc.git cd openbmc ./setup -m romulus # romulus是ASPEED AST2500参考平台./setup脚本会检查python3是否为3.8Yocto 3.1要求验证gcc --version是否≥9.3旧版GCC在编译phosphor-dbus-interfaces时会报constexpr错误创建build/目录并生成conf/local.conf和conf/bblayers.conf自动添加meta-openbmc、meta-phosphor等必需layer到bblayers.conf。特别注意conf/local.conf里的两个关键配置# 必须启用sstate缓存否则每次构建都是全新编译 SSTATE_DIR ${TOPDIR}/sstate-cache # 设置DL_DIR避免下载源码重复占用磁盘 DL_DIR ${TOPDIR}/downloads # 调试必备生成完整的debug符号包 INHERIT rm_work IMAGE_INSTALL:append packagegroup-core-debugINHERIT rm_work是双刃剑它会让BitBake在每个任务完成后自动清理tmp/work/下的中间文件极大节省磁盘空间一个完整构建的tmp/目录可达30GB但也会让你失去调试时查看tmp/work/*/phosphor-fan-control/git/源码目录的能力。我的建议是日常开发保留rm_work遇到疑难编译问题时临时注释掉它再bitbake -c clean phosphor-fan-control bitbake phosphor-fan-control就能拿到完整的源码树进行gdb调试。3.2 Recipe编写实战为一款新传感器添加驱动支持假设你要为公司新采购的TMP102温度传感器添加OpenBMC支持。这不是简单写个C程序而是要让它无缝融入OpenBMC的D-Bus设备模型。完整流程如下第一步创建recipe骨架# 在meta-your-company/recipes-hardware/sensors/下创建 $ mkdir -p meta-your-company/recipes-hardware/sensors/tmp102/ $ touch meta-your-company/recipes-hardware/sensors/tmp102/tmp102_1.0.bb第二步编写tmp102_1.0.bbSUMMARY TMP102 I2C temperature sensor driver for OpenBMC HOMEPAGE https://www.ti.com/product/TMP102 LICENSE MIT LIC_FILES_CHKSUM file://LICENSE;md5xxx # 源码来自我们自己的Git仓库带公司定制补丁 SRC_URI git://git.your-company.com/drivers/tmp102.git;branchmain;protocolhttps \ file://0001-add-openbmc-dbus-interface.patch \ file://tmp102.service S ${WORKDIR}/git # 继承autotools但我们需要自己写configure inherit autotools pkgconfig systemd # 覆盖默认configure因为我们用meson EXTRA_OECONF do_configure() { meson ${S} ${B} \ --prefix/usr \ --libdirlib \ --sysconfdir/etc \ --localstatedir/var \ -Ddbus_interfacetrue } # 安装systemd服务文件 do_install:append() { install -m 0644 ${WORKDIR}/tmp102.service ${D}${systemd_system_unitdir}/tmp102.service systemctl enable tmp102.service } # 声明这是一个systemd服务 SYSTEMD_SERVICE:${PN} tmp102.service RDEPENDS:${PN} phosphor-dbus-interfaces第三步编写tmp102.service[Unit] DescriptionTMP102 Temperature Sensor Daemon Aftermulti-user.target [Service] Typesimple ExecStart/usr/bin/tmp102-daemon --bus-typesystem Restartalways RestartSec10 [Install] WantedBymulti-user.target第四步在image中启用编辑meta-your-company/recipes-core/images/openbmc-image.bbappendIMAGE_INSTALL:append tmp102执行bitbake openbmc-imageBitBake会自动从git.your-company.com克隆源码应用0001-add-openbmc-dbus-interface.patch运行meson配置编译生成tmp102-daemon安装二进制文件和tmp102.service在最终镜像的/lib/systemd/system/下生成服务文件。整个过程你不需要碰任何Makefile或systemd安装脚本BitBake全帮你搞定。这就是声明式构建的力量。3.3 构建调试技巧如何快速定位“找不到recipe”和“编译失败”问题BitBake报错信息往往很晦涩下面是我总结的高效排查路径问题1“Nothing PROVIDES phosphor-fan-control”✅ 第一步确认phosphor-fan-controlrecipe确实存在find meta-phosphor -name phosphor-fan-control_*.bb✅ 第二步检查bblayers.conf是否包含meta-phosphor路径grep meta-phosphor conf/bblayers.conf✅ 第三步验证BBFILES是否匹配该recipebitbake -e | grep ^BBFILES # 输出应包含类似/path/to/openbmc/meta-phosphor/recipes-*/*/*.bb问题2“do_compile failed”且错误指向某行C代码✅ 不要直接看终端最后一行BitBake的编译日志在tmp/work/*/phosphor-fan-control/git/temp/log.do_compile里里面包含完整的gcc命令和所有宏定义。✅ 快速复现进入工作目录手动执行log里记录的gcc命令cd tmp/work/armv7a-openbmc-linux-gnueabi/phosphor-fan-control/2.12.0-r0/ # 复制log.do_compile里的gcc命令去掉-c -o等参数加上-g -O0 arm-openbmc-linux-gnueabi-gcc -g -O0 -I... your_file.c -o your_file.o这样能获得带行号的详细错误比BitBake截断的日志清晰十倍。问题3构建中途卡住CPU占用100%但无日志输出✅ 这通常是do_fetch在下载大文件如Linux内核源码时网络超时。检查DL_DIR是否有足够空间然后强制重试bitbake -c fetchall phosphor-fan-control # 如果还是失败手动下载并放入DL_DIR对应目录提示bitbake -g your-package会生成task-depends.dot文件用Graphviz可视化依赖图。虽然对新手有点门槛但当你需要理解为什么改了一个小配置会导致整个镜像重建时这张图就是救命稻草。4. 常见问题与避坑指南那些官网文档绝不会告诉你的细节4.1 “Clean vs. Rebuild”决策树什么时候该清缓存BitBake的缓存机制虽好但用错了反而拖慢进度。以下是基于我三年OpenBMC开发经验的决策指南场景推荐操作原因修改了recipe的SRC_URI如更新Git commitbitbake -c clean your-package bitbake your-packagedo_fetch和do_unpack必须重跑但do_compile可能复用如果源码变化不大修改了conf/local.conf里的MACHINE或DISTROrm -rf build/ ./setup -m new-machine全局配置变更影响所有recipe的stamp哈希缓存全部失效硬清最省事仅修改了C源码中的一个函数直接bitbake your-packagedo_compile的stamp哈希只依赖源码内容其他任务全走缓存通常3秒内完成添加了新的bbappend文件bitbake -c clean your-package bitbake your-packagebbappend改变了recipe的解析结果必须清空相关任务缓存特别注意bitbake -c cleanall your-package会删除sstate-cache里的对应包导致其他依赖它的package也要重编。除非你确定要彻底清除否则优先用clean。4.2 Layer冲突的隐形杀手BBMASK与LAYERDEPENDS当你的meta-your-company层和上游meta-openbmc都定义了phosphor-rest-server时BitBake默认按LAYER_PRIORITY选择。但如果你只想禁用某个recipe而不是覆盖它BBMASK是更优雅的方案# conf/local.conf BBMASK meta-openbmc/recipes-phosphor/rest/phosphor-rest-server_.*\.bb这行配置会让BitBake完全忽略meta-openbmc里的phosphor-rest-server即使你的layer里没提供同名recipebitbake openbmc-image也不会报错——因为它认为这个package根本不存在自然也不需要提供。另一个易被忽视的点是LAYERDEPENDS。在meta-your-company/conf/layer.conf里应该明确声明依赖关系LAYERDEPENDS_your-company openbmc这确保BitBake在解析时会先加载meta-openbmc再加载你的layer。如果顺序反了bbappend文件可能无法找到被追加的原始recipe导致静默失效。4.3 硬件移植必踩的三个深坑及解决方案坑1MACHINE定义中遗漏SOC_FAMILY现象编译通过但生成的镜像在目标板上无法启动串口输出Failed to start Kernel。 原因SOC_FAMILY决定了meta-openbmc中recipes-kernel/linux/linux-aspeed.bb的SRC_URI和COMPATIBLE_MACHINE。如果没设BitBake会选错内核recipe导致驱动缺失。 ✅ 解决在conf/machine/your-platform.conf中必须包含SOC_FAMILY aspeed COMPATIBLE_MACHINE your-platform坑2IMAGE_FSTYPES未包含wic现象bitbake openbmc-image成功但tmp/deploy/images/your-platform/下只有.ext4文件没有.wic。 原因wic是OpenBMC标准烧录格式需要显式启用。meta-openbmc的conf/distro/openbmc.conf里默认启用了它但如果你的layer覆盖了IMAGE_FSTYPES就会丢失。 ✅ 解决在conf/local.conf中追加IMAGE_FSTYPES:append wic坑3phosphor-ipmi-host服务启动失败报No such file or directory现象镜像启动后IPMI命令无响应。 原因phosphor-ipmi-host依赖phosphor-host-ipmid而后者需要/dev/ipmi0设备节点。ASPEED平台需在内核配置中启用CONFIG_ASPEED_BT_IPMI_BMCy并在machine.conf中通过KERNEL_FEATURES追加。 ✅ 解决在conf/machine/your-platform.conf中添加KERNEL_FEATURES:append features/aspeed/aspeed-bt-ipmi-bmc.scc注意features/aspeed/aspeed-bt-ipmi-bmc.scc是Yocto feature文件它会自动修改内核.config无需手动编辑。这是OpenBMC硬件移植的黄金法则——尽可能用Yocto原生机制而非硬编码patch。5. 工具链与效率提升让BitBake开发不再“等待编译”5.1devtool交互式开发的瑞士军刀devtool是Yocto官方提供的开发加速工具专为“改一行代码马上验证”场景设计。在OpenBMC中它的典型用法如下# 1. 将phosphor-fan-control源码提取到workspace脱离BitBake构建树 $ devtool modify phosphor-fan-control # 2. 进入workspace目录直接用vim改代码此时用的是真实Git仓库 $ cd workspace/sources/phosphor-fan-control $ vim src/fan_control.cpp # 3. 构建修改后的版本只编译不打包、不生成镜像 $ devtool build phosphor-fan-control # 4. 将编译好的二进制拷贝到目标板测试 $ scp tmp/work/armv7a-openbmc-linux-gnueabi/phosphor-fan-control/2.12.0-r0/image/usr/bin/phosphor-fan-control root192.168.0.10:/usr/bin/devtool的魔力在于它会自动创建workspace/appends/phosphor-fan-control_2.12.0.bbappend记录你的修改在workspace/sources/下建立Git工作副本支持git diff、git commitdevtool build时跳过do_fetch、do_unpack等耗时任务直接从workspace/sources/编译。我团队用devtool将单次迭代周期从12分钟完整bitbake压缩到90秒工程师反馈“终于不用盯着终端发呆了”。5.2bitbake-layersLayer管理的可视化仪表盘当你的项目叠加了meta-aspeed、meta-intel、meta-your-company等多个layer时bitbake-layers是必备工具# 查看当前所有layer及其优先级 $ bitbake-layers show-layers # 查看哪个layer提供了phosphor-fan-control $ bitbake-layers show-recipes phosphor-fan-control # 查看layer间的依赖关系检测循环依赖 $ bitbake-layers show-deps最实用的功能是show-recipes它能告诉你phosphor-fan-control来自meta-phosphorpriority 10phosphor-fan-control.bbappend来自meta-your-companypriority 15因此最终生效的是bbappend里的内容。这比手动翻bblayers.conf和layer.conf高效百倍。5.3 自定义bitbake命令一键解决高频操作把以下函数加入~/.bashrc让日常操作快如闪电# 快速进入work目录用于调试 bb-work() { local pkg$1 local work_dir$(find build/tmp/work -name $pkg* | head -n1) if [ -n $work_dir ]; then cd $work_dir echo Entered: $work_dir else echo Package $pkg not found in work dir fi } # 清理并重建单个package带verbose日志 bb-rebuild() { bitbake -c clean $1 bitbake -v $1 } # 查看package的完整依赖树 bb-deps() { bitbake -g $1 cat pn-depends.dot | grep -E $1|- | head -20 }现在bb-work phosphor-fan-control直接跳转到源码目录bb-rebuild phosphor-fan-control一键清理重建bb-deps phosphor-fan-control快速查看前20行依赖。这些小技巧每天能为你省下15分钟无效等待。我在实际使用中发现最值得投入时间的是devtool的熟练度。一旦习惯用它做增量开发你就再也回不去bitbake wait test的原始模式了。那种“改完代码30秒后就能在目标板上验证”的流畅感才是嵌入式开发该有的样子。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

基于深度学习的垃圾分类目标检测系统源码解析与实战避坑指南 2026/10/2 1:44:05

基于深度学习的垃圾分类目标检测系统源码解析与实战避坑指南

简介:这份资源是面向高校学生与深度学习入门者的垃圾分类目标检测系统完整项目包,可直接用于毕业设计、课程设计或工程实践参考。项目以Python为后端,涵盖从Anaconda虚拟环境搭建、conda换源配置到模型训练与检测的完整流程,帮助读…

阅读更多 →
Linux diff与patch命令详解:从生成补丁到应用补丁的完整流程 2026/10/2 1:44:05

Linux diff与patch命令详解:从生成补丁到应用补丁的完整流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Spring AOP 源码解析:AspectJAdvisorFactory 接口如何将注解切面转换为 Advisor 2026/10/2 1:44:05

Spring AOP 源码解析:AspectJAdvisorFactory 接口如何将注解切面转换为 Advisor

示例工程文档 【免费下载链接】spring-reading 涵盖了 Spring 框架的核心概念和关键功能,包括控制反转(IOC)容器的使用,面向切面编程(AOP)的原理与实践,事务管理的方式与实现,Spring…

阅读更多 →
华强北手表256G存储真相:ADB命令直击eMMC物理容量 2026/10/2 1:43:58

华强北手表256G存储真相:ADB命令直击eMMC物理容量

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
故障树分析(FTA)工程实践指南:从顶事件定义到最小割集落地 2026/10/2 1:43:57

故障树分析(FTA)工程实践指南:从顶事件定义到最小割集落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
NVMe-MI带外管理协议解析:从原理到实战踩坑指南 2026/10/2 1:43:57

NVMe-MI带外管理协议解析:从原理到实战踩坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉