8 Buildroot 根文件系统构建
一、根文件系统简介
根文件系统一般也叫做 rootfs,这个是属于 Linux 内核的一部分。
根文件系统首先是一种文件系统,该文件系统不仅具有普通文件系统的存储数据文件的功能,但是相对于普通的文件系统,它的特殊之处在于,它是内核启动时所挂载(mount)的第一个文件系统,内核代码的映像文件保存在根文件系统中,系统引导启动程序会在根文件系统挂载之后从中把一些初始化脚本(如rcS,inittab)和服务加载到内存中去运行。我们要明白文件系统和内核是完全独立的两个部分。在嵌入式中移植的内核下载到开发板上,是没有办法真正的启动Linux操作系统的,会出现无法加载文件系统的错误。
二、buildroot
Buildroot 是 Linux 平台上一个开源的嵌入式 Linux 系统自动构建框架,用交叉编译生成 Linux 系统。整个Buildroot 是由 Makefile 脚本和 Kconfig 配置文件构成的。
1. buildroot 源码下载
官方网站:Buildroot - 让嵌入式 Linux 变得简单
由于我的 Ubuntu 版本较低,所以用正点推荐的 2020.02.6 版本。
2. buildroot 构建根文件系统
① 配置 buildboot
先把 buildroot-2020.02.6.tar.bz2 拷贝到 Ubuntu 的 linux/tool 路径下。解压命令如下:
tar -vxjf buildroot-2020.02.6.tar.bz2
buildroot 也支持图形化配置:
make menuconfig
1、配置 Target options
需要配置的项目和其对应的内容如下(“=”号后面是配置项要选择的内容! ):
-> Target Architecture = ARM (little endian)
-> Target Binary Format = ELF
-> Target Architecture Variant = cortex-A7
-> Target ABI = EABIhf
-> Floating point strategy = NEON/VFPv4
-> ARM instruction set = ARM
# 目标架构:ARM
# 目标二进制格式:ELF
# 目标架构变体:cortex-A7
# 目标ABI:EABIhf
# 浮点策略:NEON/VFPv4
# ARM指令集:ARM
2、配置 Toolchain
此配置项用于配置交叉编译工具链,也就是交叉编译器,这里设置为我们自己所使用的交叉编译器即可。 需要配置的项目和其对应的内容如下:
Toolchain
-> Toolchain type = External toolchain
-> Toolchain = Custom toolchain // 用户自己的交叉编译器
-> Toolchain origin = Pre-installed toolchain // 预装的编译器
-> Toolchain path = /usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf
-> Toolchain prefix = $(ARCH) -none-linux-gnueabihf
-> External toolchain gcc version = 9.x
-> External toolchain kernel headers series = 4.20.x // 交叉编译器的 linux 版本号
-> External toolchain C library = glibc/eglibc
-> [*] Toolchain has SSP support? (NEW)
-> [*] Toolchain has RPC support? (NEW)
> [*] Toolchain has C++ support?
-> [*] Enable MMU support (NEW)
# 工具链类型:外部工具链
# 工具链:自定义工具链
# 工具链来源:预安装的工具链
# 工具链路径:/usr/local/arm/gcc-arm-9.2-2019.12-x86_64-arm-none-linuxgnueabihf
# 工具链前缀:$(ARCH)-none-linux-gnueabihf
# 外部工具链gcc版本:9.x
# 外部工具链内核头文件系列:4.20.x
# 外部工具链C库:glibc/eglibc
# 工具链是否支持SSP(堆栈保护):选中
# 工具链是否支持RPC:选中
# 工具链是否支持C++:选中
# 是否启用MMU支持:选中
3、配置 System configuration
此选项用于设置一些系统配置,比如开发板名字、欢迎语、用户名、密码等。需要配置的项目和其对应的内容如下:
System configuration
-> System hostname = ATK-stm32mp1 // 平台名字
-> System banner = Welcome Lxs // 欢迎语
-> Init system = BusyBox // 使用 busybox
-> /dev management = Dynamic using devtmpfs + mdev // 使用 mdev
-> [*] Enable root login with password (NEW) // 使能登录密码
-> Root password = 147258 // 登录密码
# 可以设置密码,但是每次重启都要输入密码很麻烦,所以我这里是失能的。
4、配置 Filesystem images
此选项配置我们最终制作的根文件系统为什么格式的,配置如下:
-> Filesystem images
-> [*] ext2/3/4 root filesystem // 如果是 EMMC 或 SD 卡的话就用 ext3/ext4
-> ext2/3/4 variant = ext4 // 选择 ext4 格式
-> exact size =1G // ext4 格式根文件系统 1GB(根据实际情况修改)
-> [*] ubi image containing an ubifs root filesystem // 如果使用 NAND 的话就用 ubifs
buildroot 可以直接制作出 ext4 格式的根文件系统,但是一般我们会自行往根文件系统里面添加很多其他的文件,所以产品开发完成以后需要自行打包根文件系统,然后烧写到开发板里面。不管是针对 EMMC 的 ext4 格式的根文件系统还是针对 NAND 的 ubi 格式的根文件系统,都要设置相应的大小,比如这里我们设置的 ex4 格式根文件系统大小为 1GB。
5、禁止编译 Linux 内核和 uboot
buildroot 不仅仅能构建根文件系统,也可以编译 linux 内核和 uboot。我们一般都不会使用 buildroot 下载的 linux 内核和 uboot,因为 buildroot 下载的 linux 和 uboot官方源码,里面会缺少很多驱动文件,而且最新的 linux 内核和 uboot 会对编译器版本号有要求,可能导致编译失败。因此我们需要配置 buildroot,关闭 linux 内核和 uboot 的编译,只使用buildroot 来构建根文件系统,首先是禁止 Linux 内核的编译,配置如下:
-> Kernel
-> [ ] Linux Kernel // 不要选择编译 Linux Kernel 选项!
-> Bootloaders
-> [ ] U-Boot // 不要选择编译 U-Boot 选项!
6、编译 Target packages
此选项用于配置要选择的第三方库或软件、比如 alsa-utils、 ffmpeg、 iperf 等工具,这里我们先只选择内核的模块加载相关软件,配置如下:
-> Target packages
-> System tools
-> [*] kmod // 使能内核模块相关命令
先编译最基础的根文件系统,一步一步来。
7、保存配置项
和 uboot、 kernel 一样,通过图形化界面配置好 buildroot 以后最好保存一下配置项,防止清除工程以后将配置项给删除掉。 buildroot 的默认配置项都保存在 configs 目录下,配置完成以后选择<Save>,然后输入要设置的配置项名字:
如果以后重新配置 builroot 可以直接输入:
make stm32mp1_atk_defconfig
保存完成后就可以看到 stm32mp1_atk_defconfig 这个文件。
② 编译 buildboot
输入以下命令:
make -j8 //多线程编译
# "-j"参数可以指定同时执行的并发任务数。"-j8"表示同时进行8个并发编译任务。
编译的时间是真的久,我直接FQ然后再编译快一点。
将 rootfs.tar 拷贝到在 nfs 目录下的 rootfs文件夹中并解压,命令如下:
cp rootfs.tar /home/alientek/linux/nfs/rootfs/ -f
cd ~
cd linux/nfs/rootfs/
tar -vxf rootfs.tar
rm rootfs.tar
这就是使用 buildroot 编译出来的根文件系统,我们可以通过 nfs 挂载到开发板上,然后对其进行测试。
③ buildboot 测试根文件系统
测试方法是通过 nfs 挂载的方式,启动 uboot,修改 bootargs 环境变量,设置 nfsroot 目录为 Ubuntu 中的rootfs 目录,命令如下:
setenv bootargs console=ttySTM0,115200 root=/dev/nfs nfsroot=192.168.1.105:/home/alientek/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.106:192.168.1.105:192.168.1.1:255.255.255.0::eth0:off''
saveenv
这里有密码登录,需要取消,输入以下命令:
cd ~
vim linux/nfs/rootfs/etc/inittab
修改成功后开发板 RESET 重复以下操作:
setenv bootargs console=ttySTM0,115200 root=/dev/nfs nfsroot=192.168.1.105:/home/alientek/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.106:192.168.1.105:192.168.1.1:255.255.255.0::eth0:off''
boot
3. buildroot 下的 busybox 配置
① busybox 配置
buildroot 会自动下载 busybox 压缩包, buildroot 下载的源码压缩包都存放在/dl 目录下,在 dl 目录下就有一个叫做“busybox”的文件夹,此目录下保存着 busybox 压缩包,如下图所示:
但是 buildroot 会将所有解压后的软件保存在 /output/build 目录中。
进入 busybox 图形化配置界面:
cd .. # 返回buildroot源码根目录下
make busybox-menuconfig
-> Settings
-> Build static binary (no shared libs)
# 不要选择这个
继续配置:
-> Settings
-> vi-style line editing commands
# 选择这个
继续配置:
-> Linux Module Utilities
-> Simplified modutils
# 不要选择这个
继续配置:
-> Linux System Utilities
-> mdev (17 kb) # 确保下面的全部选中,默认都是选中的
使能 busybox 的 unicode 编码以支持中文:
-> Settings
-> Support Unicode # 选中
-> Check $LC_ALL, $LC_CTYPE and $LANG environment variables # 选中
配置完成后还需要让busybox 支持中文:
cd /linux/tools/buildroot-2020.02.6/output/build/busybox-1.31.1
vim libbb/printable_string.c
修改 31~32 行和 45行:
修改 busybox-1.32.0/libbb/unicode.c 内容,修改 1022 行和 1031 行:
② 编译 busybox
cd /linux/tools/buildroot-2020.02.6 # 进入 buildroot 源码目录下
make show-targets # 当前 buildroot 所有 packages
如果我们想单独编译并安装 busybox 的话执行下面命令即可:
make busybox # 编译 busybox
编译完成以后重新编译 buildroot,主要是对其进行打包,输入如下命令:
make -j8
编译完成后查看 output/images 目录下的 rootfs.tar 创建时间是否是刚刚编译的,如果不是就删掉 rootfs.tar,然后重新执行“sudo make”重新编译一下即可。
4. buildroot 第三方软件和库的配置
① 使能 VSFTPD 服务
在开发板上搭建 FTP 服务器,这样可以使用 FileZilla 软件直接向开发板拷文件或把开发板文件拷贝到电脑中。
cd /linux/tools/buildroot-2020.02.6
make menuconfig
-> Target packages
-> Networking applications
-> [*] vsftpd
② 使能 SSH
有时候需要远程登录开发板,这个时候就可以通过网络登录,要用到 SSH 服务。
保存在 configs/stm32mp1_atk_defconfig。
之后重新编译 buildroot ,用 make -j8 。将 ouput/images/rootfs.tar 拷贝到 nfs目录下的 rootfs 目录中,然后重新解压。 命令如下:
tar -vxf rootfs.tar
解压完成以后就可以使用 FTP 和 SSH 等相关的软件了,由于 FTP 和 SSH 都是通过网络进行数据传输的,因此需要先配置网络,如果是通过 nfs 挂载的根文件系统,那么网络已经初始化完成了,因此可以直接使用。如果是烧写到 EMMC 里面的,那么就需要先配置网络相关功能。
4. buildroot 根文件系统测试
① 软件运行测试
编译的应用软件一般都使用动态库,使用动态库的话应用软件体积就很小,但是得提供库文件,库文件我们已经添加到了根文件系统中。我们编写一个小小的测试软件来测试一下库文件是否工作正常,在根文件系统下创建一个名为“drivers”的文件夹,以后我们学习 Linux 驱动的时候就把所有的实验文件放到这个文件夹里面。
#include <stdio.h>
#include <unistd.h>
int main(void)
{
while(1)
{
printf("hello world!\r\n");
sleep(2);
}
return 0;
}
sleep 相当于 Linux 的延时函数,单位为秒,所以 sleep(2)就是延时 2 秒。因为我们要在 ARM 芯片上运行,需要用交叉编译器编译:
arm-none-linux-gnueabihf-gcc hello.c -o hello
可以使用 file 命令查看文件类型和编码格式:
file hello
hello 是个 32 位的 LSB 可执行文件, ARM 架构的,并且是动态链接的。将拷贝到 rootfs/dervers 目录下。
终止 hello 运行按下 CTRL+C,“./hello &”就是让 hello 在后台运行。在后台运行的软件可以使用“kill -9 pid(进程 ID)”命令来关闭掉,首先使用“ps”命令查看要关闭的软件 PID 是多少, ps 命令用于查看所有当前正在运行的进程,并且会给出进程的 PID。
首先先让 hello 在后台运行,然后输入 ps 查看 PID 是多少,然后再用 kill -9 命令关闭。
./hello &
ps
kill -9 205
因为 hello 在不断的输出“hello world”所以我们的输入看起来会被打断,其实是没有的,因为我们是输入,而 hello 是输出。在数据流上是没有打断的,只是显示在 SecureCRT 上就好像被打断了。
② 中文字符测试
在 ubuntu 中向在 rootfs 目录新建一个名为“中文测试”的文件夹,然后在 MobaXterm 下查看中文名能不能显示正确。输入“ls”命令:
setenv bootargs console=ttySTM0,115200 root=/dev/nfs nfsroot=192.168.1.105:/home/alientek/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.106:192.168.1.105:192.168.1.1:255.255.255.0::eth0:off''
③ 开机自启动测试
进入根文件系统的时候会运行/etc/init.d/rcS 这个 shell 脚本,因此我们可以在这个脚本里面添加自启动相关内容。添加完成以后的/etc/init.d/rcS 文件内容如下:
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
runlevel=S
umask 022
export PATH LD_LIBRARY_PATH runlevel
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
# 开机自启动
cd /drivers # 进入 drivers 目录,因为要启动的软件存放在 drivers 目录下
./hello & # 以后台方式执行 hello 这个软件
cd / # 退出 drivers 目录,进入到根目录下
④ 外网连接测试
测试一下看开发板能不能上网,在 rootfs 中新建文件/etc/resolv.conf,然后在里面输入如下内容:
nameserver 114.114.114.114
nameserver 192.168.1.1
nameserver 表示这是个域名服务器,设置了两个域名服务器地址:114.114.114.114 和 192.168.1.1。之后重启开发板。
⑤ depmod 测试
Linux 驱动的时候需要使用此命令分析模块的依赖性,此命令需要在 busybox 中使能,路径如下:
-> Linux Module Utilities
-> [*] depmod
然后再重新编译,一般将驱动模块放到 lib/modules/5.4.31 目录下,既然当前根文件系统不存在这个目录,那么就手动创建此目录,命令如下:
mkdir /lib/modules/5.4.31 -p
系统会自动使用这三个文件。
⑥ vsftpd 测试
首先需要对vsftpd 进行配置,打开/etc/vsftpd.conf 文件,将下面两行前面的“#”去掉:
接下来把文件的所属用户改为 root,输入以下命令:
chown root:root /etc/vsftpd.conf
我们新建一个用户完成 FTP 登录。
打开 FileZilla,设置新站点。
连接就可以完成与开发版的 vsftpd 连接。
⑦ sshd 测试
SSHD 不需要进行配置,只需要创建一个登陆用户即可。我们需要修改/var/empty目录所属用户以及用户组,输入如下命令:
chown root:root /var/empty/
同样的,也可以在 ubuntu 下通过 ssh 命令登录开发板,输入如下命令:
ssh luoxuesong@192.168.1.106
# 其中 luoxuesong 是登录账号名字,192.168.1.106 是我们自己给开发板设的 IP 地址。用户名和 IP 地址用 @ 连接。
# 输入 exit 退出 SSH 会话
⑧ 创建自启动文件
默认情况下 buildroot 构建的根文件系统中 rcS 文件内容如下:
rcS 默认会在/etc/init.d 目录下查找所有以‘S’开头的脚本,然后依次执行这些脚本。所以我们可以自己创建一个以‘S’开头的自启动脚本文件,比如我创建一个名为 Sautorun 的自启动文件,命令如下:
cd /etc/init.d/
touch Sautorun
chmod 777 Sautorun
设置好以后重启开发板,此时 Sautorun 脚本就会被 rcS 调用,进而运行 test 软件。
⑨ 显示路径
使用 buildroot 构建的根文件系统启动以后会发现,输入命令的时候命令行前面一直都是“#”,如果我们进入到某个目录的话前面并不会显示当前目录路径。
这个是跟 PS1 环境变量有关,它是用于设置命令提示符格式,格式如下:
PS1 = ‘命令列表’
命令列表中可选的参数如下:
\!:显示该命令的历史记录编号。
\#:显示当前命令的命令编号。
\$:显示$符作为提示符,如果用户是 root 的话,则显示#号。
\\:显示反斜杠。
\d:显示当前日期。
\h:显示主机名。
\n:打印新行。
\nnn:显示 nnn 的八进制值。
\s:显示当前运行的 shell 的名字。
\t:显示当前时间。
\u:显示当前用户的用户名。
\W:显示当前工作目录的名字。
\w:显示当前工作目录的路径
我们打开 /etc/profile 文件,文件内容如下所示:
export PATH="/bin:/sbin:/usr/bin:/usr/sbin"
if [ "$PS1" ]; then
if [ "`id -u`" -eq 0 ]; then
export PS1='# '
else
export PS1='$ '
fi
fi
export EDITOR='/bin/vi'
# Source configuration files from /etc/profile.d
for i in /etc/profile.d/*.sh ; do
if [ -r "$i" ]; then
. $i
fi
done
unset i
# 3~9 行是设置 P1 环境变量,但是不建议修改,因为当我们重新编译了 buildroot 并解压后这个文件内容就会被替换。
# 14~17 行是 etc/profile 文件会遍历 etc/profile.d 目录下的 .sh 脚本文件,然后执行。所以我们可以自定义 .sh 脚本文件。
在/etc/profile.d 目录下新建一个名为“myprofile.sh”的 shell 脚本文件,并且给予此文件可执行权限,命令如下:
cd /etc/profile.d/
touch myprofile.sh
chmod 777 myprofile.sh
最后在 myprofile.sh 里面添加如下代码:
#!/bin/sh
PS1='[\u@\h]:\w$ '
export PS1
之后重启开发板,之后我们就可以看见当前路径和用户。
⑩ 使能 sysfs debug 目录
后续调试驱动的时候我们可能要用到/sys/kernel/debug 目录,默认我们没有挂载 debugfs 文件系统,所以/sys/kernel/debug 目录下没有任何文件,挂载方法很简单。 输入以下代码:
mount -t debugfs none /sys/kernel/debug
# 修改完重启
5. 烧写根文件系统到 EMMC 中
① 根文件系统打包
1、新建 ext4 格式磁盘
输入如下命令创建 ext4 磁盘:
cd ~
mkdir /home/alientek/linux/rootfs
cd /home/alientek/linux/rootfs
dd if=/dev/zero of=rootfs.ext4 bs=1M count=1024 # dd 命令创建一个名为 rootfs.ext4 的磁盘,由于根文件系统比较大,因此 count设置为 1024,也就是根文件系统总空间为 1GB
mkfs.ext4 -L rootfs rootfs.ext4
# "mkfs.ext4":这是用于创建Ext4文件系统的命令。
# "-L rootfs":这个选项用于设置文件系统的标签,将文件系统的标签设置为"rootfs"。
# "rootfs.ext4":指定要格式化的文件系统所在的设备或文件。在这个例子中,我们使用"rootfs.ext4"作为文件系统。
2、将系统镜像拷贝到 ext4 磁盘
首先创建一个目录用来挂载前面制作制作出来的 rootfs.ext4:
sudo mkdir /mnt/rootfs
然后使用 mount 命令将 rootfs.ext4 挂载到 /mnt/rootfs 目录下:
cd /home/alientek/linux/rootfs
mount rootfs.ext4 /mnt/rootfs/
挂载成功以后就将/home/alientek/linux/nfs/rootfs 目录下的所有根文件系统文件拷贝到/mnt/bootfs 目录下,命令如下:
cd /home/alientek/linux/nfs/rootfs/
sudo cp * /mnt/rootfs/ -drf
umount 卸载 /mnt/rootfs:
sudo umount /mnt/rootfs
② 烧写到 EMMC
根文件系统 rootfs.ext4 烧写到开发板的 EMMC里面,使用 STM32CubeProgrammer 软件完成此操作,并修改 Flashlayout。
③ EMMC 启动测试
烧写完成后,用 uboot 命令行,设置 bootcmd 和 bootargs 两个环境变量:
setenv bootcmd 'ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000 stm32mp157datk.dtb;bootm c2000000 - c4000000'
setenv bootargs 'console=ttySTM0,115200 root=/dev/mmcblk1p3 rootwait rw'
saveenv
boot
完成后系统之启动了lo网卡,eh0并没有启动,输入以下命令:
ifconfig eth0 up # 打开 eth0 网卡
但是此时开发板网络还不能使用,因为我们还没有配置 eth0 网卡地址信息输入以下命令,如果开发板连接交换机或电脑需要输入以下命令:w
ifconfig eth0 up # 打开 eth0
ifconfig eth0 192.168.1.250 netmask 255.255.255.0 # 设置 IP 地址
如果开发版直连路由器,直接输入 " udhcpc "命令直接从路由器获得动态IP地址即可。