对于嵌入式项目开发来说,往往使用Buildroot、Openwrt、Debian、Ubuntu等系统作为应用执行的平台,很少单独使用Busybox来构建系统。这是因为Busybox构建的系统只有基础目录和功能;为了满足应用开发需求,往往需要进行大量的第三方软件移植,这是十分低效的方式。不过从学习的角度来说,BusyBox又是比较适合入门的系统,可以理解构建系统需要的文件和执行流程,具体目录如下所示。
第一步当然是下载基础的busybox源码,然后编译,编译命令如下所示。
#下载busybox, 解压并进入
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xvf busybox-1.36.1.tar.bz2
cd busybox-1.36.1/
#关闭后保存,开启编译
make -j4 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- menuconfig
make -j4 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf-
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- install
#编译好的文件保存在"$PWD/_install"目录下,默认为
bin/
linuxrc/
sbin/
usr/
一般编译完成后,编译结果如下所示。
后面的Trying libraies是编译过程中出现的一些警告,不影响编译结果。这并不是报错,而是编译最终的库文件时libbusybox.so,需要提前剔除编译工具不支持的库或者链接,否则则会发生编译错误,详细可以看script/trylink中脚本。
while test "$LDLIBS"; do
$debug && echo "Trying libraries: $LDLIBS"
dropped_non_first_lib=false
first_lib=true
for one in $LDLIBS; do
without_one=`echo " $LDLIBS " | sed "s/ $one / /g" | xargs`
# "lib1 lib2 lib3" -> "-llib1 -llib2 -llib3"
l_list=`echo " $without_one $CONFIG_EXTRA_LDLIBS " | sed -e 's: \([^- ][^ ]*\): -l\1:g' -e 's/^ *//'`
test x"$l_list" != x"" && l_list="$START_GROUP $l_list $END_GROUP"
$debug && echo "Trying -l options: '$l_list'"
# 通过try命令,判断编译器是否支持相应的库,不支持则剔除,支持则保留
# 最终保留m,resolv,不链接crypt,rt
try $CC $CFLAGS $LDFLAGS \
-o $EXE \
$SORT_COMMON \
$SORT_SECTION \
$GC_SECTIONS \
$START_GROUP $O_FILES $A_FILES $END_GROUP \
$l_list
if test $? = 0; then
echo " Library $one is not needed, excluding it"
LDLIBS="$without_one"
$first_lib || dropped_non_first_lib=true
else
echo " Library $one is needed, can't exclude it (yet)"
first_lib=false
fi
done
# We can stop trying to drop libs if either all libs were needed,
# or we excluded only the _first_ few.
# (else: we dropped some intermediate lib(s), maybe now we can succeed
# in dropping some of the preceding ones)
$dropped_non_first_lib || break
done
至此,完成第一阶段工作,Busybox系统的编译工具完成。
第二步主要是创建目录,添加系统配置文件,执行启动流程。
具体文件目录可以参考:busybox配置文件目录
Linux文件系统需要一些关键目录的支持,这一步就是创建一些必要的目录,包含/dev、/etc、/lib、/proc、/root、/sbin、/sys、/tmp、/run等。
# 创建文件目录
mkdir dev etc lib proc root sys tmp run
mkidr etc/init.d
Busybox属于传统的SysV init系统, 启动的init进程会解析/etc/inittab文件,然后执行对应的脚本。/etc/inittab文件是Linux系统初始化过程中的一个重要配置文件,它负责控制系统的运行级别以及定义系统启动时的各种行为和进程。
#inittab启动信息
vim etc/inittab
################ 文件内容 ################
# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen <andersen@codepoet.org>
#
# Note: BusyBox init doesn't support runlevels. The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# sysvinit.
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# id == tty to run on, or empty for /dev/console
# runlevels == ignored
# action == one of sysinit, respawn, askfirst, wait, and once
# process == program to run
# Startup the system
::sysinit:/bin/mount -t proc proc /proc # 挂载proc文件系统,用于查看系统信息
::sysinit:/bin/mount -o remount,rw / # 重新挂载根目录,设置为可读写(设置为只读后后续则不能修改文件系统)
::sysinit:/bin/mkdir -p /dev/pts /dev/shm # 创建pts和shm目录
::sysinit:/bin/mount -a # 解析/etc/fstab,并挂载相应目录
::sysinit:/bin/mkdir -p /run/lock/subsys # 创建/run/lock/subsys目录
::sysinit:/sbin/swapon -a # swapon命令用于激活Linux系统中的交换空间
# 将系统关键文件软链接到设备文件目录
null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd
null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin
null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout
null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr
::sysinit:/bin/hostname -F /etc/hostname # 读取/etc/hostname文件并设置主机名
# now run any rc scripts
::sysinit:/etc/init.d/rcS # 执行rcS中的启动脚本
# Put a getty on the serial port
console::respawn:/sbin/getty -L console 0 vt100 # GENERIC_SERIAL,启用tty作为串口
ttymxc0::respawn:/sbin/getty -L ttymxc0 0 vt100 # 同时启用串口0作为输出
# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot
# Stuff to do before rebooting
::shutdown:/sbin/swapoff -a # 停止所有交换空间
::shutdown:/bin/umount -a -r # 停止所有挂载的分区
/etc/passwd文件是Linux系统中一个重要的配置文件,用于存储用户账户的信息。
# 用户密码和启动执行应用
vim etc/passwd
################ 文件内容 ################
root:x:0:0:root:/root:/bin/sh
daemon:x:1:1:daemon:/usr/sbin:/bin/false
bin:x:2:2:bin:/bin:/bin/false
sys:x:3:3:sys:/dev:/bin/false
sync:x:4:100:sync:/bin:/bin/sync
mail:x:8:8:mail:/var/spool/mail:/bin/false
www-data:x:33:33:www-data:/var/www:/bin/false
operator:x:37:37:Operator:/var:/bin/false
nobody:x:65534:65534:nobody:/home:/bin/false
/etc/fstab文件是Linux系统中一个至关重要的配置文件,它用于定义和管理文件系统的挂载信息。
# 文件系统信息
vim etc/fstab
################ 文件内容 ################
# <file system> <mount pt> <type> <options> <dump> <pass>
/dev/root / ext2 rw,noauto 0 1
proc /proc proc defaults 0 0
devpts /dev/pts devpts defaults,gid=5,mode=620,ptmxmode=0666 0 0
tmpfs /dev/shm tmpfs mode=0777 0 0
tmpfs /tmp tmpfs mode=1777 0 0
tmpfs /run tmpfs mode=0755,nosuid,nodev 0 0
sysfs /sys sysfs defaults 0 0
/etc/init.d/rcS是一个系统启动脚本文件,用于在Linux系统启动时执行一系列初始化操作。它通常位于根文件系统的/etc/init.d目录下。
# 系统启动时执行的脚本
vim /etc/init.d/rcS
################ 文件内容 ################
#!/bin/sh
# 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
# set global path
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
export LD_LIBRARY_PATH=/lib:/usr/lib:/usr/local/lib
增加rcS的启动脚本内容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 $?
增加静态IP文件,”/sbin/ifup -a”是用于激活网络接口的命令,会读取”/etc/network/interfaces”文件,文件内容如下所示。
vim /etc/network/interfaces
# 内容如下
# The primary network interface, address and gateway set as the
auto eth0
iface eth0 inet static
address 192.168.1.125
netmask 255.255.255.0
gateway 192.168.1.1
# 设置回环地址
auto lo
iface lo inet loopback
# 注意,address和gateway的设置根据路由器或者网关来定义,启动IP地址不能与当前局域网内设备冲突。
当Linux系统运行需要进行域名解析的命令,如ping、wget、curl时,系统会启动一个DNS查询,此过程会读取”/etc/resolv.conf”文件。
# 添加DNS服务器
vim /etc/resolv.conf
# 内容如下
nameserver 223.5.5.5
nameserver 8.8.8.8
nameserver 8.8.4.4
安装支持文件系统运行的动态库。前面提到,linux系统文件运行除了特殊编译全静态链接的,大部分都需要系统的C库支持,这部分直接复制编译工具下的lib目录即可,命令如下。
# 复制c++ lib
sudo cp -ravf $GCC_LIB/lib/*so* lib/
# 复制c lib到
sudo cp -ravf $GCC_LIB/libc/lib/*so* $NFS_PATH/lib/
至此,我们完成了构建最小文件系统的全部工作。如果后续需要扩展功能,就交叉编译相应的工具,将可执行文件和库放入对应的目录,就可以正常的被系统所访问。
不过对于下载工具或者Linux系统,可以按照需要打包成tar或img两种方式。
# 打包成tar格式
tar -vcjf rootfs.tar.bz2 *
export root_name=arm-busybox
export root_size=1024
# 创建打包文件*.img
dd if=/dev/zero of=${root_name}.img bs=1M count=${root_size}
mkfs.ext4 ${root_name}.img
# 导入rootfs信息到img
# 原理就是将img文件以硬盘的形式挂载到系统中,然后将上述编译好的文件系统复制到其中,再移除硬盘,此时
sudo mount -o loop ${root_name}.img arm/
sudo cp -arv rootfs/ arm/
sudo umount arm
打包好的硬盘img文件通过qemu-img文件管理
# 查看虚拟镜像的信息
qemu-img info *.img
# 调整硬盘容量
qemu-img resize *.img [size]
通过上述步骤,我们完成了最小文件系统的构建,具体流程如下所示。
另外,如果扩展第三方应用支持,还需要参考嵌入式Linux平台软件的交叉编译中的实现,进行第三方软件交叉编译和移植。对于一个完整的项目,往往需要大量的第三方软件库的支持,如openssh,curl,ffmpeg等;这时候就需要大量的交叉编译和移植工作,处理兼容性和不同版本组合后系统的稳定性,也是繁杂的工作。组合构建更简单的编译方式便称为迫切的需求;将编译需要的环境,库和软件通过脚本的形式打包,构成了自动编译工具;做到开箱即可编译,通过选项就可以添加第三方软件支持,Buildroot正是基于这个理念实现的项目。
直接开始下一章节说明: 基于buildroot构建文件系统