从源代码构建自定义最小 Linux 发行版,第二部分
按照这个循序渐进的指南来创建您自己的发行版。
在《Linux Journal》2018 年 6 月刊的一篇文章中,我介绍了一个从源代码包构建您自己的最小 Linux 发行版的基本方法。该指南从编译在您的主机系统上运行的交叉编译器工具链开始。使用该交叉编译器,我解释了如何构建通用的 x86-64 目标镜像,Linux Journal 操作系统 (LJOS) 由此诞生。
本指南建立在您从第一部分中学到的知识之上,因此如果您尚未这样做,请务必完成原始步骤,直到您即将打包目标镜像以进行分发。
词汇表以下是对本系列第一部分术语的快速回顾
- 主机:主机表示您将在其上完成绝大部分工作的机器,包括交叉编译和安装目标镜像。
- 目标:目标是您将从源代码包构建的最终交叉编译操作系统。您将在主机机器上使用交叉编译器构建它。
- 交叉编译器:您将构建和使用交叉编译器在主机机器上创建目标镜像。交叉编译器构建为在主机机器上运行,但用于为与目标机器不兼容的架构或微处理器进行编译。
为了继续进行,您需要以下内容
- busybox-1.28.3.tar.bz2(与第一部分中使用的软件包相同)。
- clfs-embedded-bootscripts-1.0-pre5.tar.bz2(与第一部分中使用的软件包相同)。
- Dropbear-2018.76.tar.bz2。
- Iana-etc-2.30.tar.bz2。
- netplug-1.2.9.2.tar.bz2。
- sysstat-12.1.1.tar.gz。
注意:我最终使用 4.19.1 Linux 内核重建了这个发行版。如果您想做同样的事情,请务必在您的主机机器上安装 OpenSSL 库的开发包,否则构建将失败。在 Debian 或 Ubuntu 等发行版上,此软件包名为 libssl-dev。
修复一些启动时错误在按照第一部分进行操作后,您会注意到在启动时会生成一些错误(图 1)。

图 1. 系统启动的 init 进程期间生成的错误。
让我们清除其中的一些错误。第一个错误与 BusyBox 中未包含的脚本有关:usbdisk_link
。为了本练习的目的(并且因为它对于本示例并不重要),请删除 ${LJOS}/etc/mdev.conf 文件中对 usbdisk_link
和 ide_link
的引用。请参考以下 diff
输出以了解我的意思(密切关注以 sd
和 hd
开头的行)
--- mdev.conf.orig 2018-11-10 18:10:14.561278714 +0000
+++ mdev.conf 2018-11-10 18:11:07.277759662 +0000
@@ -26,8 +26,8 @@ ptmx root:tty 0666
# ram.*
ram([0-9]*) root:disk 0660 >rd/%1
loop([0-9]+) root:disk 0660 >loop/%1
-sd[a-z].* root:disk 0660 */lib/mdev/usbdisk_link
-hd[a-z][0-9]* root:disk 0660 */lib/mdev/ide_links
+sd[a-z].* root:disk 0660
+hd[a-z][0-9]* root:disk 0660
tty root:tty 0666
tty[0-9] root:root 0600
现在,让我们解决与网络相关的错误。创建 ${LJOS}/etc/network/interfaces 文件
$ cat > ${LJOS}/etc/network/interfaces << "EOF"
> auto eth0
> iface eth0 inet dhcp
> EOF
现在创建具有以下内容的 ${LJOS}/etc/network.conf 文件
# /etc/network.conf
# Global Networking Configuration
# interface configuration is in /etc/network.d/
INTERFACE="eth0"
# set to yes to enable networking
NETWORKING=yes
# set to yes to set default route to gateway
USE_GATEWAY=no
# set to gateway IP address
GATEWAY=10.0.2.2
最后,创建 udhcpc
脚本。udhcpc
是一个小型 DHCP 客户端,主要为最小或嵌入式 Linux 系统编写。如果您按照本系列第一部分的步骤操作,它应该(或应该已经)与您的 BusyBox 安装一起构建。创建以下目录
$ mkdir -pv ${LJOS}/etc/network/if-{post-{up,down},
↪pre-{up,down},up,down}.d
$ mkdir -pv ${LJOS}/usr/share/udhcpc
现在,创建具有以下内容的 ${LJOS}/usr/share/udhcpc/default.script 文件
#!/bin/sh
# udhcpc Interface Configuration
# Based on http://lists.debian.org/debian-boot/2002/11/
↪msg00500.html
# udhcpc script edited by Tim Riker <Tim@Rikers.org>
[ -z "$1" ] && echo "Error: should be called from udhcpc"
↪&& exit 1
RESOLV_CONF="/etc/resolv.conf"
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
[ -n "$subnet" ] && NETMASK="netmask $subnet"
case "$1" in
deconfig)
/sbin/ifconfig $interface 0.0.0.0
;;
renew|bound)
/sbin/ifconfig $interface $ip $BROADCAST $NETMASK
if [ -n "$router" ] ; then
while route del default gw 0.0.0.0 dev
↪$interface ; do
true
done
for i in $router ; do
route add default gw $i dev
↪$interface
done
fi
echo -n > $RESOLV_CONF
[ -n "$domain" ] && echo search $domain >>
↪$RESOLV_CONF
for i in $dns ; do
echo nameserver $i >> $RESOLV_CONF
done
;;
esac
exit 0
更改文件的权限以启用所有用户的执行位
$ chmod +x ${LJOS}/usr/share/udhcpc/default.script
下次您启动目标镜像(在重新准备它之后)时,这些启动错误将消失。

图 2. 清理后的系统启动
我想解决的最后一件事是 root 用户的默认 shell。在我第一部分的说明中,我让您将 shell 设置为 ash
。由于某些奇怪的原因,当尝试 ssh
进入发行版(通过 Dropbear)时,这将给您带来问题。为了避免这种情况,请修改 ${LJOS}/etc/passwd 文件中的条目,使其读取
root::0:0:root:/root:/bin/sh
请注意用 sh
替换了 ash
。最终,它是同一个 shell,因为 sh
是指向 ash
的软链接。
来自上一篇文章的交叉编译构建目录和头文件不应已删除。导出以下变量(您可能可以将它们放入脚本文件中)
set +h
umask 022
export LJOS=~/lj-os
export LC_ALL=POSIX
export PATH=${LJOS}/cross-tools/bin:/bin:/usr/bin
unset CFLAGS
unset CXXFLAGS
export LJOS_HOST=$(echo ${MACHTYPE} | sed "s/-[^-]*/-cross/")
export LJOS_TARGET=x86_64-unknown-linux-gnu
export LJOS_CPU=k8
export LJOS_ARCH=$(echo ${LJOS_TARGET} | sed -e 's/-.*//'
↪-e 's/i.86/i386/')
export LJOS_ENDIAN=little
export CC="${LJOS_TARGET}-gcc"
export CXX="${LJOS_TARGET}-g++"
export CPP="${LJOS_TARGET}-gcc -E"
export AR="${LJOS_TARGET}-ar"
export AS="${LJOS_TARGET}-as"
export LD="${LJOS_TARGET}-ld"
export RANLIB="${LJOS_TARGET}-ranlib"
export READELF="${LJOS_TARGET}-readelf"
export STRIP="${LJOS_TARGET}-strip"
Dropbear
Dropbear 是一个轻量级的 SSH 服务器和客户端。它在最小或嵌入式 Linux 发行版中特别有用,这就是您将在此处安装它的原因。但在执行此操作之前,请从上一部分更改为 CLFS 启动脚本目录 (clfs-embedded-bootscripts-1.0-pre5) 并安装自定义的 init 脚本
$ make DESTDIR=${LJOS}/ install-dropbear
现在您已经安装了 Dropbear 的 init 脚本,请安装 SSH 服务器和客户端软件包。更改到软件包目录,并运行以下配置命令
CC="${CC} -Os" ./configure --prefix=/usr --host=${LJOS_TARGET}
编译软件包
$ make MULTI=1 PROGRAMS="dropbear dbclient dropbearkey
↪dropbearconvert scp"
安装软件包
$ make MULTI=1 PROGRAMS="dropbear dbclient dropbearkey
↪dropbearconvert scp" DESTDIR=${LJOS}/ install
确保创建了以下目录
$ mkdir -pv ${LJOS}/{etc,usr/sbin}
$ install -dv ${LJOS}/etc/dropbear
并软链接以下二进制文件
ln -svf /usr/bin/dropbearmulti ${LJOS}/usr/sbin/dropbear
ln -svf /usr/bin/dropbearmulti ${LJOS}/usr/bin/dbclient
ln -svf /usr/bin/dropbearmulti ${LJOS}/usr/bin/dropbearkey
ln -svf /usr/bin/dropbearmulti ${LJOS}/usr/bin/dropbearconvert
ln -svf /usr/bin/dropbearmulti ${LJOS}/usr/bin/scp
ln -svf /usr/bin/dropbearmulti ${LJOS}/usr/bin/ssh
BusyBox(重新访问)
在本教程的后面部分,我将查看 BusyBox 软件包中包含的 HTTP 守护程序。如果您尚未这样做,请自定义软件包的配置文件以确保选择并构建 httpd
$ make CROSS_COMPILE="${LJOS_TARGET}-" menuconfig

图 3. Busybox 配置菜单
编译并安装软件包
$ make CROSS_COMPILE="${LJOS_TARGET}-"
$ make CROSS_COMPILE="${LJOS_TARGET}-" \
CONFIG_PREFIX="${LJOS}" install
Iana-Etc
Iana-Etc 软件包为您的发行版提供各种网络服务和协议的数据,因为它与文件 /etc/services 和 /etc/protocols 相关。软件包本身很可能带有过时的数据和 IANA(互联网号码分配机构),这就是为什么您需要应用 Andrew Bradford 编写的补丁来调整数据更新的下载位置的原因。
更改到软件包目录并应用补丁
$ patch -Np1 -i ../iana-etc-2.30-update-2.patch
更新软件包的数据
$ make get
将原始数据和 IANA 转换为其正确的格式
$ make STRIP=yes
安装新创建的 /etc/services 和 /etc/protocols 文件
make DESTDIR=${LJOS} install
Netplug
Netplug 守护程序检测网络电缆的插入和移除,并将通过启动或关闭相应的网络接口来做出反应。与 Iana-Etc 软件包类似,Andrew Bradford 也编写了一个 补丁来解决 Netplug 的一些问题。
更改到软件包目录并应用补丁
$ patch -Np1 -i ../netplug-1.2.9.2-fixes-1.patch
编译并安装软件包
$ make && make DESTDIR=${LJOS}/ install
Sysstat
这是一个简单的软件包,虽然您不一定需要此软件包,但无论如何让我们安装它,因为它提供了一个很好的示例,说明如何安装其他软件包(如果您选择自行安装更多软件包)。Sysstat 提供了一组监视实用程序,其中包括 sar、sadf、mpstat、iostat、tapestat、pidstat、cifsiostat 和 sa 工具。
更改到软件包目录并配置/编译/安装软件包
$ ./configure --prefix=/usr --disable-documentation
$ make
$ make DESTDIR=${LJOS}/ install
再次安装目标镜像
您需要创建一个暂存区来删除不必要的文件并剥离二进制文件中的任何和所有调试符号,但为了做到这一点,您需要将整个目标构建环境复制到一个新位置
$ cp -rf ${LJOS}/ ${LJOS}-copy
从副本中删除交叉编译器工具链和源代码/头文件
$ rm -rfv ${LJOS}-copy/cross-tools
$ rm -rfv ${LJOS}-copy/usr/src/*
生成所有静态库的列表并删除它们
$ FILES="$(ls ${LJOS}-copy/usr/lib64/*.a)"
$ for file in $FILES; do
> rm -f $file
> done
剥离每个二进制文件中的所有调试符号
$ find ${LJOS}-copy/{,usr/}{bin,lib,sbin} -type f -exec
↪sudo strip --strip-debug '{}' ';'
$ find ${LJOS}-copy/{,usr/}lib64 -type f -exec sudo
↪strip --strip-debug '{}' ';'
将每个文件的所有权更改为 root
$ sudo chown -R root:root ${LJOS}-copy
并更改以下三个文件的组和权限
$ sudo chgrp 13 ${LJOS}-copy/var/run/utmp
↪${LJOS}-copy/var/log/lastlog
$ sudo chmod 4755 ${LJOS}-copy/bin/busybox
创建以下字符设备节点
$ sudo mknod -m 0666 ${LJOS}-copy/dev/null c 1 3
$ sudo mknod -m 0600 ${LJOS}-copy/dev/console c 5 1
您需要更改到副本的目录并将所有内容压缩到 tarball 中
cd ${LJOS}-copy/
sudo tar cfJ ../ljos-build-10Nov2018.tar.xz *
现在您已将整个发行版存档到一个文件中,您需要将注意力转移到将要安装它的磁盘卷上。对于本教程的其余部分,您将需要一个空闲的磁盘驱动器,它需要枚举为一个传统的块设备(在我的例子中,它是 /dev/sdd)
$ cat /proc/partitions |grep sdd
8 48 256000 sdd
该块设备需要进行分区。单个分区应该足够了,您可以使用许多分区实用程序中的任何一个,包括 fdisk
或 parted
。一旦创建分区并被主机系统检测到,请使用 Ext4 文件系统格式化分区,将其挂载到暂存区并更改到该目录
$ sudo mkfs.ext4 /dev/sdd1
$ sudo mkdir tmp
$ sudo mount /dev/sdd1 tmp/
$ cd tmp/
将整个目标操作系统的操作系统 tarball 解压缩到暂存目录的根目录中
$ sudo tar xJf ../ljos-build-10Nov2018.tar.xz
现在运行 grub-install
以将所有必要的模块和引导记录安装到卷中
$ sudo grub-install --root-directory=/mnt/tmp/ /dev/sdd
--root-directory
参数定义了暂存目录的绝对路径,最后一个参数是不带分区标签的块设备。
完成后,将 HDD 安装到物理或虚拟机,并启动它(作为主磁盘驱动器)。在一秒钟内,您将到达操作系统的登录提示符。
注意:如果您计划将其加载到虚拟机中,如果虚拟机的网络接口桥接到主机机器的本地以太网接口,您的生活将变得更加轻松。
与第一部分的情况一样,您从未设置 root 密码。以 root 身份登录,您将立即进入 shell,而无需输入密码。您可以使用 BusyBox 的 passwd
命令更改此行为,该命令应该已内置到此镜像中。在继续之前,请更改您的 root 密码。
要测试 SSH 守护程序,您需要为您的以太网端口分配一个 IP 地址。如果您在命令行中键入 ip addr show
,您将看到 eth0
不存在地址。为了解决这个问题,运行
$ udhcpc
只有在之前创建的 udhcpc
脚本已创建并保存到您的发行版的目标区域时,上述命令才有效。如果成功,重新运行 ip addr show
将显示 eth0
的 IP 地址。在我的例子中,地址是 192.168.1.90。
在另一台机器上,通过 SSH 登录到您的 LJOS 发行版
$ ssh root@192.168.1.90
The authenticity of host '192.168.1.90 (192.168.1.90)'
↪can't be established.
RSA key fingerprint is SHA256:Jp64l+7ECw2Xm5JjTXCNtEvrh
↪YRZiQzgJpBK5ljNfwk.
Are you sure you want to continue connecting (yes/no)? Yes
root@192.168.1.90's password:
~ #
瞧!您已正式远程连接。
您可以在这里做更多的事情。还记得早些时候,当我要求您仔细检查 BusyBox 是否正在构建其轻量级 HTTP 守护程序时吗?让我们看一下。
为守护程序创建一个主目录
# mkdir /var/www
并使用 BusyBox 的轻量级 vi
程序,创建 /var/www/index.html 文件并确保它包含以下内容
<html>
<head><title>This is a test.</title></head>
<body><h1>This is a test.</h1></body>
</html>
保存并退出。然后手动启动 HTTP 守护程序,参数定义其主目录
# httpd -h /var/www
验证服务是否正在运行
# ps aux|grep http
1177 root 0:00 httpd -h /var/www
在另一台机器上,使用您的 Web 浏览器,连接到您的 Linux 发行版的 IP 地址(与您 SSH 连接的地址相同)。将出现由您的发行版托管的粗略 HTML 网页。

图 4. 访问从您的自定义发行版托管的 Web 服务器
总结本文建立在我之前的文章中的练习之上,并为最小和自定义 Linux 发行版添加了更多内容。但这不需要在这里结束。为其找到一个用途,并使用此处强调的示例,在其内部构建更多软件包。
资源