从源代码构建自定义最小 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_linkide_link 的引用。请参考以下 diff 输出以了解我的意思(密切关注以 sdhd 开头的行)


--- 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

该块设备需要进行分区。单个分区应该足够了,您可以使用许多分区实用程序中的任何一个,包括 fdiskparted。一旦创建分区并被主机系统检测到,请使用 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 发行版添加了更多内容。但这不需要在这里结束。为其找到一个用途,并使用此处强调的示例,在其内部构建更多软件包。

资源

Petros Koutoupis,《Linux Journal》特约编辑,目前是 Cray Lustre 高性能文件系统部门的高级性能软件工程师。他还是 RapidDisk 项目的创建者和维护者。Petros 在数据存储行业工作了十多年,并帮助开创了当今广泛使用的许多技术。

加载 Disqus 评论