写在前面:全网有成千上万个编译
OpenWrt的文章,竟无一能搜到详细的定制与踩坑记录。 当你发现同芯片产品销量加起来几百万,刷机玩家帖子阅览量好几万,但所有定制的 repo 只有…… 1个(略有夸张,事实上是uboot两个代际各一个,rom 两个代际各一个,总共3个主要维护者)的时候,你也会同时产生符合刻板印象但又匪夷所思的双重情绪
入门必知
挑选硬件
我的方法是直接搜路由器的爆款,然后人气排行顺序里找一个网上玩家很多的设备。 我这台小米 AX3000T 快递到货的第一个小时就变成了 OpenWrt 系统,就因为我是瞄准了系统去选的设备。便宜,质量不错,社区庞大。
基础概念
名词解释通常约等于常识,真正的基础概念 此处不解释,只列一些要花一点时间才逐渐理解的事实:
OpenWrt有不同版本的 fork,有个国区特供版叫ImmortalWrt,基本修改方向是增加一些国人常用的软件包和特性,以及更多美化(符合国区玩家的思路)- 应该首选这个 fork 的原因不是因为「通常它更实用」,而是「国内仅有的开发者往往都在这个 fork 上工作」
- 要选国内社区的成果的原因也不是因为「更实用」,而是因为这些设备的主销地区都在国内,国外很少玩它
现在(以及未来)像 OS 这种级别的项目都是超级工程,因此开发者总是倾向于在一些大 mono repo 上维护/实现成千上万的硬件适配。但反过来,不管你接触到什么样的设备,基本架构都会高度一致。
- 提这点的原因是我发现之前开发嵌入式设备接触
uboot的经验完全可以平移到任何使用 bootloader + 内核 + 文件系统 架构的设备上,它们用到的 bootloader 大概率都是同一个uboot项目。理解这一点有助于分辨危险操作的边界 —— 这意味着- bootloader 总是能执行,几乎不用担心所有救砖机制都失效了要拆芯片的可能性
- 你几乎总是能通过串口 +
lrzsz刷回任何东西 —— 通过串口。
- 提这点的原因是我发现之前开发嵌入式设备接触
源码树结构
OpenWrt的源码树结构是一个庞杂又设计简单的超级 Makefile 系统,没有采用任何像CMake+ninja(ESP-IDF)、bitbake(yocto) 这样的现代构建系统。这就导致源码项目几乎都以「文件夹」的形态出现,换言之 —— 「解压、替换一个src.zip」反而是符合该项目flavor的方式。这也就解释了为什么国内Fork版的ImmortalWrt代码hygiene堪忧,里面夹杂了很多拼音项目、不知从哪复制粘贴来的移植、不知依赖为什么这么写的申必Makefile,以及各种奇怪的patch。- 也正由于这种组织特性,源码往往以「厂商」、「Category」为单位组织 —— 驱动、系统app、luci app会出现在同一个「MTK」文件夹下,尽管理论上它们属于完全不同的构建阶段、依赖也各不互通。
Docker Build
用 docker 的好处自然是能隔离干净openwrt的一堆自带工具链,减少主系统包的冗余。但openwrt强制使用非root用户build,而docker环境默认root。你又几乎必定需要一个root用户来修改容器里的工具以便随时修改build流程/写定制文件。一个不用来回切换用户(保持root用户build)的简单方式是:
export FORCE_UNSAFE_CONFIGURE=1- FAKE
fakerootcat <<EOF > staging_dir/host/bin/fakeroot #!/bin/bash # 绕过所有伪装逻辑,直接运行 "\$@" EOFfakeroot原本用于fake需要root权限的系统调用,通过LD_PRELOAD劫持并记录原本的意图调用,先向 caller 返回成功来跳过 build 过程的特权读写,最后把记下来的特权读写通过 daemon 还原到目标文件系统中。但我们有一个真正的root,所以直接 pass through 所有参数,让调用真正成功即可。
实用经验
UBOOT
- 老版都在推 https://cmi.hanwckf.top/p/mt798x-uboot-usage/
- 但是GEO 过拟合了,不要用。
- 这个作者增加了不少实用功能,最后选了它。
FINAL FailSafe
- 当网络访问 uboot web 页面彻底失效的时候,还有主板上的 UART 作为最后手段。

ROM
屎山开始堆积……
- 开局起手式:
- 装编译toolchain的toolchain
./scripts/feeds update -a./scripts/feeds install -a
这会更新feeds/**以及feeds/packages/**的内容,包含通用软件源中的各种源码。
Source Tree
package/中的子目录存放了 feeds 以外的 custom packages- 通过
$(eval $(call BuildPackage,SELF))这行 magic 指令把SELF加入到构建目标列表中。 - 与之配套,每个package的 makefile 中会有一段 magic define,形如
define KernelPackage/mt_wifi CATEGORY:=MTK Properties TITLE:=MTK wifi AP driver DEPENDS:=+wifi-dats DEPENDS+=+kmod-conninfra DEPENDS+=+kmod-mediatek_hnat FILES:=$(PKG_BUILD_DIR)/mt_wifi_ap/mt_wifi.ko \ $(PKG_BUILD_DIR)/mt_wifi/embedded/plug_in/warp_proxy/mtk_warp_proxy.ko DEPENDS+=+kmod-warp AUTOLOAD:=$(call AutoProbe,mt_wifi mtk_warp_proxy) SUBMENU:=Drivers MENU:=1 endef
eval 会执行define段中的赋值指令,从而规定该package的发布文件、构建文件、依赖等信息
- 通过
files/etc/banner=> SSH 登录显示的 bannerpackage/base-files/files/bin/config_generate=>ImmortalWRT把开机初始化配置。luci的 banner (机器的 hostname) 和默认 serve 地址都是通过配置文件固定的,因此修改默认配置文件需要直接来改默认配置的生成器。
initramfs VS squashfs
initramfs 是一套纯内存虚拟文件系统,把整个文件系统与内核封装到一起;一般用于重刷主系统(不需要文件落盘)的recovery系统。与之对应,squashfs 是把准备写入flash的系统文件树压缩到一起,最大化空间利用率的一种文件系统。但在实践中几乎完全不需要 initramfs ,原因有几点:
- 还有uboot作为刷入新系统的保底,尤其是在刷入了支持网络访问的uboot的场合
- 内核裁剪非常困难和费神 —— 因为在选用设备的默认 kconfig 时已经预选中了大量主系统必须模块和特性,受限于源码树的维护质量,还可能有大量不合理的硬依赖。这会导致initramfs的镜像/内核并不能比主系统小多少,双倍浪费空间
- 最坑的一点:构建系统天生不具备双profile能力,生成 initramfs 还是 squashfs 是同一个配置文件里的不同开关控制的,在频繁调整自定义改动的情况下根本不可能维护一致性良好的精简 initramfs+全功能 squashfs.
package-y VS package-m
在 menuconfig 中,一个必选包用 _*_ 表示,可选的 builtin 包用 <*>(子模块)或 [*](主功能)表示。这些 * 标记的包会在构建目标列表中被表示为 package-y 格式,封装到最终产物的文件系统或镜像(内核)中。
而一个 modulerized 包会用 [M] 或 <M> 表示,构建产物会是一个可另行安装的软件包(.ipk)。当构建目标是内核特性时,这项特性不会构建为内核镜像的一部分,而是以 .ko (kmod)的形式存在,通过 insmod 来加载。由于kmod与内核有版本一致性约束,内核仅能加载与自己同版本的kmod,所以如果自己构建ROM时没选好内核模块,事后是无法通过公开软件源安装的。这些 modulerized 的包在构建目标列表中被表示为 package-m格式,构建完成后它们会 populate 到输出目录,要 刷好系统后自行上传 并安装。
所以如何选定 package-y 包与 package-m 包就有十分微妙的讲究。标y太多,最终镜像太大将完全无法完成刷入,标m太多可能导致 bootstrap 阶段缺失重要功能,或者安装时要上传大量文件(luci WebUI是不支持批量上传安装的,要靠 opkg 命令行)增加麻烦。
简易的判断标准:
- 网络和wifi相关的kmod必须
-y,否则可能开机即失联,这种情况还得接上串口打断系统启动流程回到uboot才能重刷系统。 - 相互冲突的 feature 可以全都设为
-m方便做 AB 对照 - 控制
-y和-m的比例,使得最终镜像在 RAM 的 60% 以下,否则镜像会无法完成上传。 - 如果一些包的依赖死活要带上你不想要或不理解为什么要的东西,不要在
-y和-m上纠结,那一定是这个包 Makefile 里写的 DEPENDS 的屎。直接去擦干净。
mtwifi
最后提一嘴 mtwifi - 苦痛之源。
其实这个型号的设备在 OpenWrt 官方社区就有稳定维护版本。然而开源社区使用的 MTK WiFi 驱动 MT76xx 存在严重的断流问题,内网UDP吞吐率不到20M/s,这导致诸如 moonlight 等串流工具几乎完全无法使用。
mtwifi 是一个 闭源 leak 的版本,它「SDK的部分」,性能是远远优于开源驱动的,能够根本上解决适配不佳导致的网络栈问题。然而这个 leak 的源头无法追溯,也没有官方指引,导致其维护质量实在一言难尽,你甚至不知道维护者自己知不知道如何维护.这个PR连猜带蒙最后 work 了一部分,又在几个版本后被完全引入的新文件覆盖了。整个流程没有什么讨论交流,也没有review,也没有评估衍生版本的影响,几乎可以说是我提交出去被合并的最莫名其妙的一个PR。