原文: OpenWrt:从源码到固件,编译自己的OpenWrt系统 | 小耸的博客 Spencer's Blog (xiaosong.fun)
本文说明如何一步一步地从源码编译出自己的OpenWrt镜像,并升级到设备上。
准备工作
准备一个GNU/Linux, BSD 或 MacOSX 操作系统。并且,在环境中准备好以下官方教程中要求的工具:
[OpenWrt Wiki] Build system setup
我的环境是Ubuntu22,执行
1 2 | sudo apt update sudo apt install build-essential clang flex bison g++ gawk gcc-multilib g++-multilib gettext git libncurses-dev libssl-dev python3-distutils rsync unzip zlib1g-dev file wget |
编译系统
下载源码
下载代码:
1 2 3 | git clone https://github.openwrt/openwrt.git cd openwrt git pull 注意:上述命令我测试失败,可以使用下面的命令从github下载 git clone https://github.com/openwrt/openwrt.git |
切换分支,一般选择最新的稳定版本:
1 2 3 | git branch -a git tag git checkout v23.05.0 |
下载软件包
运行./scripts/feeds update -a
命令,下载或更新在feeds.conf/feeds.conf.default
中定义的所有最新软件包。
受限于国内的访问国际互联网的环境中,feeds update这一步特别容易失败。可以通过以下几条方式来提高成功率:
git config --global http.postBuffer 524288000
git config --global http.lowSpeedLimit 1000
git config --global http.lowSpeedTime 600
以上配置的含义为:配置git缓冲区为500M,配置git访问超时的条件为:速率小于1KB/s,且持续600秒
运行./scripts/feeds install -a
命令使安装上述软件包在后续的make menuconfig
中生效。
配置编译选项
使用已有固件的编译配置
网络上已编译出的固件通常都会把编译配置一并提供(config.buildinfo或config.seed),可以直接使用。
我的目标机是一台小米WR30U,使用mtk的filogic芯片方案,从OpenWrt官网找到对应的编译配置并下载,置于OpenWrt工程根目录下的.config
文件中:
1 | wget https://downloads.openwrt.org/releases/23.05.0/targets/mediatek/filogic/config.buildinfo -O .config |
但这份配置中包含了filogic芯片方案的所有设备的配置,还需进行裁剪和修改。
menuconfig
在以上配置的基础上,运行make menuconfig
命令来完成进一步的自定义配置。完成后配置会更新至.config
文件中。
在Target Profile中,仅保留Xiaomi WR30U的设备支持(我的设备是Xiaomi Mi Router WR30U (112M UBI with NMBM-Enabled layout)),把其它设备的支持删除。
menuconfig
编译
使用make download
预先下载编译过程中需要的代码和依赖项等资源。
. . . . . . . 经过漫长的等待下载完成。到此为止,我们已做好了所有的编译准备。
正式开始编译吧,运行make
命令来构建固件。该命令将下载所有源代码和依赖项(即使之前已经make download
,也还有其它包需要下载),构建交叉编译工具链,然后为目标系统交叉编译出OpenWrt内核和应用程序。
1 | make -j4 V=s |
编译选项说明
-jN
: make命令可以加上-j参数用于指定使用多少cpu核编译,可以加速编译过程。例如:make download -j4
,make -j5
V=s
:make命令可以加上V=s可以输出更多的编译错误信息。
如果顺利的话,这里make完就可以编译出镜像了。但是实际不太可能完全顺利。每个人遇到的问题可能不一样。我遇到的问题和修复方法,我放在后面附录中。
编译输出
. . . . . . . 经过漫长的编译过程(我的环境中编了5个小时),编译结果存放于openwrt/bin/targets/
目录下。几类编译产出的镜像说明如下:
- factory:用于替换厂商的原厂固件,兼容原厂的安装包格式。通常使用原厂的web GUI进行升级。
- sysupgrade:用于升级替换已有的OpenWrt版本,这是最常用的镜像。
- initramfs-kernel:用于开发或特殊情况下的一次性引导,作为安装常规sysupgrade版本的过渡步骤。由于initramfs版本完全运行在RAM中,不会在闪存中存储任何设置,因此不适合用于操作性使用。
升级固件
主要参考刷机教程中提及的办法,在路由器断电后,用针按住 reset 不放,再接上电源,等待 10s 左右松开,就能进入 uboot。电脑用网线和 wr30u 的网口1连接,电脑在网络设置里将以太网设置为静态。IP地址:192.168.1.2,子网掩码:255.255.255.0,浏览器打开192.168.1.1访问uboot后台。
uboot下上传固件
现在选择编译出的openwrt主程序(openwrt-23.05.0-mediatek-filogic-xiaomi_mi-router-wr30u-112m-nmbm-squashfs-sysupgrade.bin),upload 后 update 更新即可。
升级完成后,把网络连到LAN口上(非1口),在控制台上用ssh root@192.168.1.1登陆,可见已成功升级至编译出的OpenWrt固件。
ssh登陆OpenWrt设备
也可以通过浏览器访问192.168.1.1,进入web管理页面,默认用户名密码是root/password
OpenWrt LuCI首页
将自己编译的固件与从官方网站上下载的固件https://downloads.openwrt.org/releases/23.05.0/targets/mediatek/filogic/openwrt-23.05.0-mediatek-filogic-xiaomi_mi-router-wr30u-112m-nmbm-squashfs-sysupgrade.bin 升级后的结果做了一下对比,路由器功能完全一致。
附录
参考资料
- [OpenWrt Wiki] Build system usage
- [OpenWrt Wiki] How much time is needed for compiling?
- [OpenWrt Wiki] What is the difference between the different image formats?
- 编译cjdns失败 · Issue #10817 · coolsnowwolf/lede · GitHub
编译失败记录
内核编译Warning
默认情况下,内核编译有加上-Werror选项,因此遇到Warning就会停下来。例如编译失败日志:
1 2 | /home/spencer/openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/dmx_usb_module-19.12.1/dmx_usb.c: In function 'dmx_usb_write': ./include/linux/kern_levels.h:5:25: error: format '%d' expects argument of type 'int', but argument 4 has type 'size_t' {aka 'long unsigned int'} [-Werror=format=] |
于是找到-Werror
添加的地方,删除掉。即在openwrt/build_dir/target-aarch64_cortex-a53_musl/linux-mediatek_filogic/linux-5.15.132/Makefile
中,把以下这行注释掉:
1 | #KBUILD_CFLAGS-$(CONFIG_WERROR) += -Werror |
acl编译失败
遇到acl编译失败,到社区上搜索,已有高人给出了答案:
acl build failed · Issue #21051 · openwrt/packages · GitHub
acl: fix compilation with musl 1.2.4 by 1715173329 · Pull Request #21031 · openwrt/packages · GitHub
cjdns编译失败
1 2 3 4 5 6 7 | cc1: note: someone does not honour COPTS correctly, passed 16 times net/SwitchPinger_admin.c: In function 'adminPing': net/SwitchPinger_admin.c:103:133: error: dangling pointer 'err' to an unnamed temporary may be used [-Werror=dangling-pointer=] 103 | Dict d = Dict_CONST(String_CONST("error"), String_OBJ(err), NULL); | ^ net/SwitchPinger_admin.c:82:26: note: unnamed temporary defined here 82 | err = String_CONST("path was not parsable."); |
解决办法:
编译cjdns失败 · Issue #10817 · coolsnowwolf/lede · GitHub