前言
-
我们稍加查询资料,便可以知道,在ARM平台运行linux系统,需要bootloader,linux内核和根文件系统
-
我们查询资料,也可以知道,需要交叉编译,即在PC平台上,对以上源代码进行编译获取相应的文件,烧录到嵌入式平台的相应的位置上,嵌入式平台才能正常运行。
-
既然要编译源代码,那么我们就先要知道,从源代码中编译出什么样的文件是启动平台所必须的,Rockchip官网给出的RK3399的启动流程(Boot option - Rockchip open source Document (rock-chips.com)):
- 从图中可以看出,要从eMMC/CD启动系统,有两条路线,在图中分别由绿色箭头Boot Flow1和红色箭头BootFlow2指示,BootFlow2适用于大多数SOC,本文中我们选择BootFlow2的流程启动RK3399
- 从图中可以看出,系统启动过程需要4个文件
- idbloader.img
- u-boot.itb
- boot.img
- rootfs.img
- 从图中可以看出,编译出以上4个文件,每个阶段需要的文件来源不同,由颜色可以看出:
- 编译出idbloader.img有u-boot源码就可以
- 编译出u-boot.itb,需要u-boot源码、ARM信赖文件-Rockchip rkbin文件,即图中bl31.elf文件
- 编译出boot.img需要linux内核源码及其他文件
- rootfs.img也即根文件,这里选择使用busybox编译出根文件
-
相关平台
- Ubuntu18.04
- 交叉编译平台aarch64-linux-gnu-
- Firefly-RK3399开发板
准备编译环境
- 更新
sudo apt-get update
- 安装交叉编译工具
sudo apt-get install gcc-aarch64-linux-gnu
安装后路径/usr/aarch64-linu-gnu/
移植u-boot 2022.10
有前面描述可知,由U-boot源码和安全文件编译出两个启动文件:idbloader.img和u-boot.itb
获取安全文件
RK3399是Arm64,启动还需要ATF(Arm Trust Firmware),ATF主要负责在启动U-boot之前把CPU从安全的EL3切换到EL2,然后跳转到U-boot,并且在内核启动后负责启动其他的CPU,安全文件的名称是bl31.elf。
可以下载源代码自行编译,下载源代码执行以下命令:
git clone https://github.com/ARM-software/arm-trusted-firmware.git
也可以下载官方已经编译好的文件,下载地址https://github.com/rockchip-linux/rkbin/tree/master/bin/rk33/
文件名称为:rk3399_bl31_v1.36.elf,将该文件下载到本地
这里选择直接下载已经编译好的
获取U-boot源码
从U-boot的官方网站或代码仓库中获取U-boot 2022.10的源码:
wget https://ftp.denx.de/pub/u-boot/u-boot-2022.10.tar.bz2
解压源码包,解压后的文件夹名称为:u-boot-2022.10
tar -jxf u-boot-2022.10.tar.bz2
编译uboot
将rk3399_bl31_v1.36.elf文件移动到u-boot-2022.10文件夹内
进入u-boot-2022.10路径下,指定BL31文件
export BL31=rk3399_bl31_v1.35.elf
在u-boot-2022.10路径下编译(firefly-rk3399_defconfig
文件在u-boot/configs/
路径下,内容的意义参考[[RK3399_defconfig内语句的意义]]):
make firefly-rk3399_defconfig V=1
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
等待编译结束,中间若报错,多半是缺少依赖,依次安装就可以
注意:uboot-2022.10使用pyhon3,Ubuntu18.04默认的python环境指向python2,注意环境适配,如果不想更改系统的环境变量,可以采用低版本的uboot,或者采用高版本的Ubuntu来规避此问题
编译完成后在u-boot-2022.10文件夹内可以找到生成的idbloader.img和u-boot.itb,这两个文件连同后面编译的内核文件boot.img和根文件系统文件一同烧录
移植kernel 4.4
下载内核源码
这里采用瑞芯微官方提供的内核源码:https://github.com/rockchip-linux/kernel/,该内核源码含有测试过的设备树文件,方便后续驱动开发
配置环境
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- rockchip_linux_defconfig
编译内核
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j4
编译成功后会得到arch/arm64/boot/Image文件及arch/arm64/boot/dst/rockchip/xxx.dtb文件
准备文件
在解压的文件夹(内核源码文件夹)下,新建shell文件,输入以下内容:
#!/bin/sh
mkdir boot
cp arch/arm64/boot/dts/rockchip/rk3399-firefly-linux.dtb boot/rk3399.dtb
cp arch/arm64/boot/Image boot/
mkdir boot/extlinux
cat>boot/extlinux/extlinux.conf<<EOF
label rockchip-kernel-4.4.4
kernel /Image
fdt /rk3399.dtb
append earlycon=uart8250,mmio32,0xff1a0000 console=ttyS2,1500000n8 root=PARTUUID=B921B045-1D rw rootwait rootfstype=ext4 init=/sbin/init
EOF
- 执行以上shell脚本,在内核源码文件夹下新增boot文件夹,其内含有:extlinxu Image rk3399.dtb
创建ext2格式的镜像
genext2fs -b 32768 -B $((64*1024*1024/32768)) -d boot/ -i 8192 -U boot.img
其中:
-b
:表示文件系统块的数量;-B
: 表示文件系统块的大小,单位为字节;-d
:从指定目录开始创建文件系统-i 8192
;每个inode
使用的字节数;-U
:选项表示创建一个唯一的文件系统UUID
;
这里我们设置了32768
个块,每个块大小为64*1024*1024/32768
,所以文件系统总大小为64MB
,足够放下boot
文件夹内的所有文件。
- 执行成功后得到boot.img
busybox制作根文件系统
准备
- 下载busybox1.36.0源代码:
wget https://busybox.net/downloads/busybox-1.36.0.tar.bz2
- 将下载的busybox源代码复制到指定的文件加下,本文这里是~/rk3399/rootfs/
- 切换到root权限:
gump@gump:~/rk3399/rootfs$ sudo su
- 解压:
root@gump:/home/gump/rk3399/rootfs# tar -jxf busybox-1.36.0.tar.bz2
编译busybox
构建根文件在管理员权限下进行下面操作
- 进入解压路径下:
root@gump:/home/gump/rk3399/rootfs# cd busybox-1.36.0
- 使用默认配置:(注意交叉编译工具要与编译内核的相同)
root@gump:/home/gump/rk3399/rootfs/busybox-1.36.0# make defconfig
- 然后进入图像化配置页面(此配置会跳出参数配置窗口,保持终端界面尽量大):
root@gump:/home/gump/rk3399/rootfs/busybox-1.36.0# make menuconfig
- 进入Busybox Settings,配置如下参数(用键盘操作):
Settings --->
[ ] Build static binary (no shared libs)
编译方式有两种:
第一种是以静态方式编译,即生成的busybox不需要动态库的支持就能运行。这样做我们就不需要部署动态库了,缺点就是自己写的程序在这个根文件系统中是不能运行的,因为缺少动态库库的支持
第二种方式使用动态编译,这样的话我们就需要部署动态库了,在linux动态库文件是以so为后缀,而windows下文件是以dll为后缀。这里我们使用动态编译,下面指定编译工具链
Settings --->
() Cross compile prefix
这里是配置交叉编译工具链,这里要填上ubuntu上使用的交叉编译工具链前缀,笔者使用的交叉编译链前缀是aarch64-linux-gnu-
,位于/usr/bin/
路径下,如图
Settings --->
( -march=armv8-a) additional CFLAGS
这里是给编译器传递一个标志,rk3399是ARMv8架构,因此在这里添加此标志
Settings --->
[*] Build Shared libbusybox
- 继续配置:
Coreutils --->
[] sync
Linux System Utilities --->
[*] hexdump
[] nsenter
按Esc键退出时保存当前更改
- 编译安装
依次执行以下命令:
root@gump:/home/gump/rk3399/rootfs/busybox-1.36.0# make
root@gump:/home/gump/rk3399/rootfs/busybox-1.36.0# make install
make install的目的就是将编译生成的可执行程序及其依赖的库文件、配置文件、头文件安装到当前系统中指定(一般都可以自己指定安装到哪个目录下,如果不指定一般都有个默认目录)的目录下,默认被安装到_install 目录下,里面有5个文件:bin、sbin、usr这三个目录里都是二进制命令工具,lib里面是库文件,
如果要构成一个可用的根文件系统,还必须进行其它完善工作
构建根文件系统
- 准备文件夹
新建一个目录用来存放制作的根文件系统,可以命名为busybox_install。将利用BusyBox生成的二进制文件及目录,即_install目录下的所有文件及目录复制到busybox_install目录下:
root@gump:/home/gump/rk3399/rootfs/busybox-1.36.0# mkdir ../busybox_install
root@gump:/home/gump/rk3399/rootfs/busybox-1.36.0# cp -a _install/* ../busybox_install/
- 添加库文件
切换到busybox_install路径下:
root@gump:/home/gump/rk3399/rootfs/busybox-1.36.0# cd ../busybox_install/
找到交叉编译工具里的动态库复制到lib目录下:
root@gump:/home/gump/rk3399/rootfs/busybox_install# cp -a /usr/aarch64-linux-gnu/lib/*so* ./lib
-a保留权限,复制软链接本身,递归复制。
- 构建etc目录
初始化配置脚本放在/etc目录下,用于系统启动所需的初始化配置脚本。
(BusyBox提供了一些初始化范例脚本,在/busybox-1.36.0/examples/bootfloppy/etc/目录下,直接复制就可以)
在busybox_install目录下,创建etc文件夹:
root@gump:/home/gump/rk3399/rootfs/busybox_install# mkdir etc
将配置文件复制到新制作的根文件系统etc目录下:
root@gump:/home/gump/rk3399/rootfs/busybox_install# cp -a ../busybox-1.36.0/examples/bootfloppy/etc/* ./etc/
- 修改etc/inittab文件
etc/inittab文件是init进程解析的配置文件,通过这个配置文件决定执行哪个进程,何时执行:
root@gump:/home/gump/rk3399/rootfs/busybox_install/etc# vim inittab
将文件修改为:
# 系统启动时
::sysinit:/etc/init.d/rcS
# 系统启动按下Enter键时
::askfirst:-/bin/sh
# 按下Ctrl+Alt+Del键时
::ctrlaltdel:/sbin/reboot
# 系统关机时
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
# 系统重启时
::restart:/sbin/init
以上内容定义了系统启动时,关机时,重启时,按下Ctrl+Alt+Del键时等操作执行的进程。
- 修改etc/fstab
etc/fstab文件存放的是文件系统信息。在系统启动后执行/etc/init.d/rcS文件里/bin/mount -a命令时,自动挂载这些文件系统。
root@gump:/home/gump/rk3399/rootfs/busybox_install/etc# vim fstab
修改为:
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
这里我挂载的文件系统有三个proc、sysfs和tmpfs,在内核中proc和sysfs默认都支持,而tmpfs是没有支持的,我需要添加tmpfs的支持
- 修改/etc/profile文件
etc/profile文件作用是设置环境变量,每个用户登录时都会运行它,将文件内容修改:
root@gump:/home/gump/rk3399/rootfs/busybox_install/etc# vim profile
修改为:
# 主机名
export HOSTNAME=gump_rk3399
# 用户名
export USER=gump
# 用户目录
export HOME=/gump
# 终端默认提示符
export PS1="[$USER@$HOSTNAME:\$PWD]\# "
# 环境变量
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
# 动态库路径
export LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
- 新建S40network
S40network脚本主要用于配置网络接口的参数,如IP地址、子网掩码、网关等。这些配置确保开发板能够正确连接到网络,打开:
root@gump:/home/gump/rk3399/rootfs/busybox_install/etc# vim init.d/S40network
输入以下内容:
#!/bin/sh
#
# Start the network....
#
# Debian ifupdown needs the /run/network lock directory
mkdir -p /run/network
case "$1" in
start)
printf "Starting network: "
/sbin/ifup -a
[ $? = 0 ] && echo "OK" || echo "FAIL"
;;
stop)
printf "Stopping network: "
/sbin/ifdown -a
[ $? = 0 ] && echo "OK" || echo "FAIL"
;;
restart|reload)
"$0" stop
"$0" start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
esac
exit $?
- udhcpc配置
新建udhcpc文件夹
root@gump:/home/gump/rk3399/rootfs/busybox_install# mkdir -p ./usr/share/udhcpc
拷贝文件simple.script到根文件系统的/usr/share/udhcpc/
目录下,并更名为default.script
root@gump:/home/gump/rk3399/rootfs/busybox_install# cp ../busybox-1.36.0/examples/udhcp/simple.script ./usr/share/udhcpc/
root@gump:/home/gump/rk3399/rootfs/busybox_install# mv ./usr/share/udhcpc/simple.script ./usr/share/udhcpc/default.script
将default.script中RESOLV_CONF=”/etc/resolv.conf”更改为RESOLV_CONF=”/tmp/resolv.conf”
配置一下网络DHCP,系统启动以后就会自动设置好网络:
新建文件夹root@gump:/home/gump/rk3399/rootfs/busybox_install# mkdir -p etc/network/interfaces.d
新建文件root@gump:/home/gump/rk3399/rootfs/busybox_install# vim etc/network/interfaces
在文件内输入以下内容:
# interface file auto-generated by buildroot
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
新建文件夹root@gump:/home/gump/rk3399/rootfs/busybox_install# mkdir etc/network/if-down.d
新建脚本文件root@gump:/home/gump/rk3399/rootfs/busybox_install# vim etc/network/if-down.d/dhcp-down.sh
并输入以下内容
#!/bin/sh
# This file is executed by ifupdown in pre-up, post-up, pre-down and
# post-down phases of network interface configuration.
# run this script only for interfaces which have dhcp-down option
[ -z "$IF_DHCP_DOWN" ] && exit 0
$IF_DHCP_DOWN $IFACE
exit 0
新建文件夹root@gump:/home/gump/rk3399/rootfs/busybox_install# mkdir etc/network/if-up.d
新建脚本文件root@gump:/home/gump/rk3399/rootfs/busybox_install# vim etc/network/if-up.d/dhcp-up.sh
并输入以下内容
#!/bin/sh
# This file is executed by ifupdown in pre-up, post-up, pre-down and
# post-down phases of network interface configuration.
# run this script only for interfaces which have dhcp-up option
[ -z "$IF_DHCP_UP" ] && exit 0
$IF_DHCP_UP $IFACE&
exit 0
- 修改etc/init.d/rcS文件:
root@gump:/home/gump/rk3399/rootfs/busybox_install# vim etc/init.d/rcS
,输入以下内容:
#!/bin/sh
# 挂载 /etc/fstab 中定义的所有文件系统
/bin/mount -a
# 挂载虚拟的devpts文件系统用于用于伪终端设备
/bin/mkdir -p /dev/pts
/bin/mount -t devpts devpts /dev/pts
# 扫描并创建节点
/sbin/mdev -s
# 加载网卡驱动
/sbin/modprobe r8125
# 配置网络
/sbin/udhcpc
# Start all init scripts in /etc/init.d
# executing them in numerical order.
#
for i in /etc/init.d/S??* ;do
# Ignore dangling symlinks (if any).
[ ! -f "$i" ] && continue
case "$i" in
*.sh)
# Source shell script for speed.
(
trap - INT QUIT TSTP
set start
. $i
)
;;
*)
# No sh extension, so fork subprocess.
$i start
;;
esac
done
- 修改init.d文件权限:
root@gump:/home/gump/rk3399/rootfs/busybox_install# chmod -R 777 etc/init.d/*
- 构建dev目录
在busybox_install目录下,创建dev文件夹:
root@gump:/home/gump/rk3399/rootfs/busybox_install# mkdir dev
创建终端文件:
root@gump:/home/gump/rk3399/rootfs/busybox_install# mknod dev/console c 5 1
root@gump:/home/gump/rk3399/rootfs/busybox_install# mknod dev/null c 1 3
- 构建其他文件夹
在busybos_install路径下
root@gump:/home/gump/rk3399/rootfs/busybox_install# mkdir mnt proc tmp sys
制作ext4根文件系统镜像
建立根文件系统挂载点busybox_rootfs:
root@gump:/home/gump/rk3399/rootfs# mkdir busybox_rootfs
创建空镜像文件busybox_ext4_rootfs.img,大小300m,具体需要的大小可以通过du build -h查看我们根文件系统大小调整,在busybox_install同级目录下:
root@gump:/home/gump/rk3399/rootfs# dd if=/dev/zero of=busybox_ext4_rootfs.img bs=1M count=300
上面命令返回的结果:
300+0 records in
300+0 records out
314572800 bytes (315 MB, 300 MiB) copied, 0.14087 s, 2.2 GB/s
查看文件:root@gump:/home/gump/rk3399/rootfs# ll busybox_ext4_rootfs.img
上面命令返回结果:
-rw-rw-r-- 1 gump gump 314572800 12月 7 19:55 busybox_ext4_rootfs.img
创建文件系统:root@gump:/home/gump/rk3399/rootfs# mkfs.ext4 busybox_ext4_rootfs.img
上面命令返回结果:
mke2fs 1.44.1 (24-Mar-2018)
Discarding device blocks: done
Creating filesystem with 307200 1k blocks and 76912 inodes
Filesystem UUID: 46fe5c81-413b-4187-a69d-600c3a05d646
Superblock backups stored on blocks:
8193, 24577, 40961, 57345, 73729, 204801, 221185
Allocating group tables: done
Writing inode tables: done
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done
将该镜像文件挂载到busybox_rootfs:
root@gump:/home/gump/rk3399/rootfs# mount busybox_ext4_rootfs.img busybox_rootfs
将busybox_install的文件复制到该空文件夹中:
root@gump:/home/gump/rk3399/rootfs# cp ./busybox_install/* ./busybox_rootfs/ -af
使用df命令可以查看是否已经挂载:
root@gump:/home/gump/rk3399/rootfs$ df busybox_rootfs/
上面命令返回结果:
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/loop18 287237 12705 255076 5% /home/gump/rk3399/rootfs/busybox_rootfs
将之前挂载的卸载掉:
root@gump:/home/gump/rk3399/rootfs# umount busybox_rootfs
用e2fsck修复及检测镜像文件系统,resize2fs减小镜像文件的大小;
e2fsck -p -f busybox_ext4_rootfs.img
resize2fs -M busybox_ext4_rootfs.img
du -sh busybox_ext4_rootfs.img
最终得到的ext4根文件系统镜像busybox_ext4_rootfs.img,本文这里根文件大小是42.9M
烧录验证
将上面编译好的四个文件放到一个文件夹内,准备烧录
本文这里是在windows平台下烧录,利用瑞芯微官方提供的AndroidTool 2.65版本
连接好烧录线和串口线,本文这里使用MobaXterm作为串口通信的上位机,串口通信速度是1500000,如图
打开AndroidTool并导入上述四个文件,按照第一个图中所描述的地址对应好,同时使开发板进入loader模式,点击Run进行烧录,如图
系统启动后,MobaXterm将接收到系统启动的信息,如图u-boot正常启动:
内核正常启动
根文件系统正常
注意:
- 开发板启动时,网络一定要连接到路由器,由路由器分配IP地址,否则会一直卡在DHCPC
- 此根文件系统没有内核模块安装功能,若要安装内核模块,可以使用命令:
sudo apt-get install kmod
若uboot总是反复重启,可在计时至0前打断重启,如图
重写gpt分区:
gpt write mmc 0 $partitions
保存环境saveenv
后重启开发板