构建Linux系统(一):构建基本可运行的系统

为什么要构建Linux系统

从实用的角度出发,我们很少需要去定制一个LInux系统,多数情况下只是出于好玩。相信多数Linux爱好者、开发人员都经历多安装各种Linux发行版(ubuntu、centos、debian、Fedora==),然后用包管理器将那些哪怕只有十几KB的包卸载的过程。

Linux发行版 包管理器 archLinux gentoo LFS 很多默认安装的软件包是多余的 不如一开始就自己做主 base linux linux-firmware 轻松超过1GB 享受下自己编译和配置系统的乐趣 stage总的保留? 比gentoo占用空间大多- 我想当创世主 太麻烦了,还是回去吧 我还得用它做开发啊 得安装各种软件. Linux发行版 包管理器 archLinux gentoo LFS

为什么不是 LFS

做开发的时候,我们需要一个完善的环境,即便不是应有尽有,需要时也能提供快捷的安装方式。这个时候,趁手的Linux发行版是最好的选择。开始我是使用这些发行版提供的包管理器来精简系统,比如synaptic,后来发现这些包管理器本身带来的依赖项就很可观。直到LFS(Linux From Scratch),手把手教你如何从零手动构建一个可运行的Linux系统。但是可运行和实用是两个概念。我不希望将精力放在搞清楚包之间的依赖以及挨个下载、配置每个包上,这没有意义。毕竟如果我们需要某个软件,它依赖的包不会因为你深入了解了这些依赖关系就减少或者增加,我希望采用的工具/方法能最大化自动处理这些重复的机械化操作

选用构建工具

通常运行的Linux系统可看做由3部分组成:

  1. 启动管理器
  2. Linux内核
  3. 根文件系统

启动管理器(比如:grub)会加载内核,内核会挂载根文件系统,我们的软件/工具都在根文件系统中。这三个部分可以看做是独立的。我们选择自己构建系统的一个重要目的是精简运行环境,其中的关键是根文件系统要精简。但根文件系统的构建是很繁琐和复杂的,涉及Linux根文件目录结构、初始化、设备管理、实用工具等诸多问题,我们需要一种工具,比如Buildroot,来帮助我们构建这些组成。

In order to achieve this, Buildroot is able to generate a cross-compilation toolchain, a root filesystem, a Linux kernel image and a bootloader for your target. Buildroot can be used for any combination of these options, independently(you can for example usean existing cross-compilation toolchain, and build only your root filesystem with Buildroot).

它不但可以生成完整的Linux系统,而且可以用来单独或者组合生成以下部分:

  • toolchain (就是编译器,我们不可能凭空构建Linux系统)
  • root filesystem
  • Linux kernel
  • bootloader

构建之前

实践中我们需要构建实用或者体现我们意志的系统,而不是一个别人提供的可运行的demo,所以很少完全按照工具提供的预置系统配置来构建系统,当我们修改配置的时候,就是给自己挖坑的时候。另外,构建花费的时间通常会超出预期,特别是是在开发工具链这个环节,下图是Buildroot产生的一个很直观的时间占比图:

$make graph-build

在这里插入图片描述

关于开发工具链

Buildroot配置时提供了外部工具链选项,出于节省时间方面的考虑,我们一般都会尝试启用它。
在这里插入图片描述
但如果不使用Buildroot自己编译的工具链,就得小心选择,来看下官方文档:

Buildroot knows about a number of well-known cross-compilation toolchains (from Linaro for ARM, Sourcery CodeBench for ARM, x86-64, PowerPC, and MIPS,and is capable of downloading them automatically, or it can be pointed to a custom toolchain, either available for download or installed locally.
Our external toolchain support has been tested with toolchains from CodeSourcery and Linaro, toolchains generated by crosstool-NG, and toolchains generated by Buildroot itself. In general, all toolchains that support the sysroot feature should work. If not,do not hesitate to contact the developers.

也就是说,外部工具仅仅测试过来自以下的工具链:

  1. CodeSourcery
  2. Linaro
  3. crosstool-NG

如果你为PC构建系统时为了节省时间而选择系统中安装的gcc,接下来Buildroot的骚操作会让人叹为观止。很多时候想省事反而麻烦多了。

关于内核模块精简

内核的庞大主要集中在驱动。我们构建的系统不同于发行版,通常用于特定场合,面临的硬件变化很少。当我们接入所有外设后,内核加载的模块基本就是系统运行需要的最小的模块集合。为什么要加上基本呢?因为有些模块系统并没有使用到,是被强制加载的。

# lsmod 
Module                  Size  Used by
rfcomm                 81920  16
pci_stub               16384  0
vboxnetadp             28672  0
vboxnetflt             28672  1
vboxdrv               479232  3 vboxnetadp,vboxnetflt
cmac                   16384  1
bnep                   24576  2
snd_hda_codec_hdmi     61440  1
snd_sof_pci            20480  0
snd_sof_intel_hda_common    65536  1 snd_sof_pci
joydev                 24576  0
snd_soc_hdac_hda       24576  1 snd_sof_intel_hda_common
input_leds             16384  0
snd_sof_intel_hda      20480  1 snd_sof_intel_hda_common
snd_hda_ext_core       28672  3 snd_sof_intel_hda_common,snd_soc_hdac_hda,snd_sof_intel_hda
snd_sof_intel_byt      20480  1 snd_sof_pci
snd_hda_codec_realtek   126976  1

上面列出了我使用的系统加载的部分内核模块。Used by 一列表示被多少其他模块/进程引用,如果为0,一般都可以卸载。如果我们按照列出的模块名称一个个在内核配置中查找以及搞定依赖,可太费事了。幸运的是,内核的构建系统提供了一个配置参数,可以很方便的配置已经加载的内核模块:

localmodconfig - Update current config disabling modules not loaded

#make localmodconfig

如何得到目标机器上运行的Linux系统加载的模块呢?先保存目标平台的lsmod输出,然后将这个文件作为参数传递给开发系统上的内核构建系统。

$ lsmod > target.modules
$ scp target.modules user@host.ip:/kernel-source-path
$ make LSMOD=target.modules localmodconfig

LSMOD 变量好像是v4.3.3引入的新特性。记得以前google如何将lsmod的输出作为参数传递给内核构建系统,除了一个大牛提供了自己写的脚本,没发现官方的构建系统有应对方法。那时候我一般在目标机上安装个具备编译功能最小发行版Linux,然后挂载开发机器上的内核目录,再运行make localmodconfig,将产生的.config保存起来使用。

内核构建系统还提供了另外一个最小化配置:

tinyconfig - Configure the tiniest possible kernel

可以在此配置的基础上加入目标系统必须的模块,就是有点麻烦。

嵌入式系统构建

先看下Buildroot的官方介绍:

Buildroot is a tool that simplifies and automates the process of building a complete Linux system for an embedded system, using cross-compilation.

对,它是为构建嵌入式系统准备的。Buildroot内部预置了很多常见的嵌入式开发板配置,下面仅仅列出部分预制的系统,可以看到包括树莓派开发板和PC的x86_64系统。

$ make list-defconfigs
  Built-in configs:
  
  pc_x86_64_bios_defconfig            - Build for pc_x86_64_bios
  pc_x86_64_efi_defconfig             - Build for pc_x86_64_efi

  raspberrypi0_defconfig              - Build for raspberrypi0
  raspberrypi0w_defconfig             - Build for raspberrypi0w
  raspberrypi2_defconfig              - Build for raspberrypi2
  raspberrypi3_64_defconfig           - Build for raspberrypi3_64
  raspberrypi3_defconfig              - Build for raspberrypi3
  raspberrypi3_qt5we_defconfig        - Build for raspberrypi3_qt5we
  raspberrypi4_64_defconfig           - Build for raspberrypi4_64
  raspberrypi4_defconfig              - Build for raspberrypi4
  raspberrypi_defconfig               - Build for raspberrypi

它的使用非常简单,通常的步骤是先使用其预制的配置文件(使用make list-defconfigs 列出支持的系统),比如为树莓派3构建系统:

$make raspberrypi3_defconfig

Buildroot支持4种配置界面,

menuconfig - interactive curses-based configurator
nconfig - interactive ncurses-based configurator
xconfig - interactive Qt-based configurator
gconfig - interactive GTK-based configurator

可以选用一种自己习惯的界面来调整配置,比如

$make gconfig

在这里插入图片描述
如果厌烦为此安装gtk开发包,也可以使用终端形式:

$make menuconfig

在这里插入图片描述
配置完后,直接使用make -jN(N:根据你的cpu内核数量而定)就会生成系统的磁盘镜像。该工具并不会生成类似我们使用Linux发行版(如ubuntu)安装镜像。接下来需要将这个镜像写入sd卡,比如dd工具:

 $ sudo dd if=output/images/sdcard.img of=/dev/sdX bs=4M

其中/dev/sdX是SD卡的设备符号,不同的系统和读卡器,设备符号的标识会有差异,注意是将镜像写入磁盘,而不是某个磁盘分区,比如/dev/sdb1.

如果没有意外发生,你已经可以炫耀曾成功为某款嵌入式平台构建了Linux系统。

非嵌入式系统构建

现代所谓的嵌入式和非嵌入式系统并没有严格意义上的划分。Buildroot最终生成的镜像为固定容量的磁盘镜像,而且不是大家通常接触到的Linux发行版的安装盘(ISO)。即便如此,Buildroot还是能解决我们在构建Linux系统中大量程序化任务,而且我们也没有打算制作Linux发行版。这里的“非嵌入式”系统,指的是我们不需要也不能使用自动化系统构建套件产生的磁盘镜像以及根文件系统磁盘镜像直接写入目标机器磁盘的场景。 如果我们将这些磁盘镜像写入磁盘,会发现磁盘的容量被限制为这些磁盘镜像的大小,也就是你在Buildroot里的设置值,而这不是我们希望的。另外,也存在生成的磁盘镜像写入目标磁盘后无法启动,需要逐个环节、逐个选项排查原因,也不希望每次都构建整个系统。

如果按照Linux From Scratch (LFS) 的方法一步步手动构建系统,过程会非常冗长,让人厌烦,这个项目提供的自动化构建脚本(ALFS)也3年没有更新了,而且配置界面相比Buildroot简陋又难以使用。Buildroot问题是可靠性,比如下图的Kernel panic - not syncing : VFS: Unable to mount root fs on unknown-block :

在这里插入图片描述
有人会说这是内核缺乏磁盘驱动导致的。但我确定自己启用了该驱动,而且使用同样的配置,用开发机器的GCC编译内核,替换Buildroot产生的内核后,系统就能正常启动。所以可以断定Buildroot隐藏了大量中间细节中是有地方出问题了。有时也会出现启动到命令提示符后无法登录,对键盘输入没有反应的情况,最终也是替换Buildroot编译的内核后解决问题。所以,我觉得最好将Buildroot作为自己构建Linux系统的助手,而不是全权委托给他。我使用它构建系统的思路就是:

环节应对
toolchain使用Buildroot编译
启动可以使用一个Linux发行版来协助安装,分区完毕后安装Grub
内核使用Buildroot产生的toolchain自行编译
根文件系统使用Buildroot创建tar包,将其解压到目标系统的磁盘分区

实例目标平台

为了调试方便,使用virtualbox创建虚拟机作为目标平台。为什么不使用qemu?我想从模拟器和从虚拟机到真机还是有区别的,对于系统来说,虚拟机等同于真机。我们也不想构建一个只能在模拟器运行的玩具。

toolchain

前面已经提过,如果不使用Buildroot创建,也不要使用Linux发行版自带的,得使用经过它“认证”的工具链。我倾向于使用Buildroot创建。原因如下:

  1. 遇到过各种问题,但问题几乎没有出在toolchain
  2. 可以选用最新的GCC版本,使用C++的新特性,比如20
  3. 省得另外找toolchain下载了,一个界面内搞定

下图是我的部分toolchain配置:
在这里插入图片描述

C library选用glibc,如果要使用systemd管理系统,这是必须的,而且以后编译安装各种工具时会省下很多麻烦。C++支持也启用。

toolchain默认会安装在output/host中,如果要在Buildroot外使用,只需要将output/host/bin加入路径即可。本文不打算作为Buildroot的使用手册,所以更多的信息请参考它的官方文档。

根文件系统

在system configuration分支的配置中,启用systemd作为初始化系统。如果不是资源受限到必须使用busybox,建议使用systemd替代busybox作为初始化系统,特别是目标系统需要动态处理外设变化时,你就会知道它带来的好处。/bin/sh设置为bash是因为我对bash较为熟悉。

在这里插入图片描述

在Filesystem images分支中设置不需要其产生磁盘或者根分区的镜像,只产生根文件系统的tar包:
在这里插入图片描述

toolchain 和 根文件系统配置完毕后,保存配置,运行make -jN即可(N根据自己的cpu核心而定,但Buildroot在整体流程上并不支持多线程编译,毕竟包与包之间有依赖关系,只能按先后来,这个应该可以理解)。

Buildroot默认在构建后会执行产生磁盘镜像的脚本:
support/scripts/genimage.sh
如果不想看到下面的错误提示(其实它没什么影响,根文件系统的tar包已经产生了),

ERROR: file(rootfs.ext2): stat(/mnt/data/kernel/buildroot-2020.08.1/output/images/rootfs.ext2) failed: No such file or directory
ERROR: hdimage(disk.img): could not setup rootfs.ext2
Makefile:833: recipe for target 'target-post-image' failed
make: *** [target-post-image] Error 1

可以将这个选项设置为空:
system configuration->custom scripts to run after creating filesystem images
在这里插入图片描述

内核的编译和安装

前面已经提过如何来生成完整功能的最小化内核配置的方法,也使用Buildroot生成了toolchain,接下来就很简单了:

 make 
 ARCH=x86_64  
 CROSS_COMPILE="/mnt/data/kernel/buildroot-2020.08.1/output/host/bin/x86_64-buildroot-linux-gnu-" 
 INSTALL_MOD_PATH=/mnt/data/kernel/buildroot-2020.08.1/output/images/rootfs  
 INSTALL_PATH=/mnt/data/kernel/buildroot-2020.08.1/output/images/rootfs  
 all

其中/mnt/data/kernel/buildroot-2020.08.1/是Buildroot在我系统中的路径,可以得知这里使用的Buildroot版本为2020.08.1;/mnt/data/kernel/buildroot-2020.08.1/output/images/rootfs 为内核和模块的安装目录,可以任意更改为自己系统中为其预留的目录。

执行后在安装文件夹生成的文件结构如下:
在这里插入图片描述

这里解释下,使用Buildroot出现的启动问题,几乎都是跟内核相关。我对比过自己使用toolchain和Buildroot编译的内核,不是内核镜像的名字不同(bzImage和vmlinuz-version)的问题,模块目录下的文件也不同,比如modules.dep,Buildroot产生的大小为0,这显然不正常。与其搞明白同样的配置为什么生成会有差异,还不如自己动手编译内核。

系统启动

我们不能确定目标系统的硬盘配置,所以最好不要在使用Buildroot或者自己编译的启动管理工具中固定磁盘配置。我们可以使用某个Linux发行版启动目标机器:

  • 使用熟悉的分区工具给系统准备分区,如fdisk
  • 将Buildroot生成的根文件系统tar包解压到硬盘分区,比如/dev/sdb1(第2块磁盘,分区1)
  • 将内核以及模块也copy到该硬盘分区的boot目录(也可以是其他地方,就是启动配置会变)

示例流程如下:假设安装到第2块硬盘,分区1上

$ mkfs.ext4 /dev/sdb1
$ mkdir /mnt/rootfs
$ mount /dev/sdb1 /mnt/rootfs 【挂载分区】
$ scp user@server.ip:/rootfs.tar.file.path /mnt/rootfs 【拷贝根文件tar包】
$ tar xf /mnt/rootfs/rootfs.tar /mnt/rootfs  【将tar包解压到准备好的分区】
$ scp user@server.ip:/kernel.file.path /mnt/rootfs/boot 【拷贝内核】
$ scp user@server.ip:/modules.dir /mnt/rootfs/lib 【拷贝内核模块】
(注意内核模块路径为/lib/modules/kernel.version,不要搞错了)

方法很多,只要能将将内核及其模块揉进根文件系统就可以

  • 安装grub或者习惯的启动管理器到启动硬盘
$ grub-install /dev/sda (bios设置从那块硬盘启动就安装到那,如果是已经存在grub,就不用安装了)
$ update-grub/update-grub2

如果你使用某些发行版,可能会发现缺乏grub安装文件,比如archlinux、gentoo之类,虽然也能使用骚操作安装,但还是麻烦。所以作为工具盘的启动光盘要选个“臃肿”点的,比如ubuntu desktop、linux mint 之类的

如果是已经存在grub,也可以直接在原有的配置/boot/grub/grub.cfg增加启动项,内容示例如下:
在这里插入图片描述
启动时会新增一个菜单:
在这里插入图片描述

  • 修改根文件系统中的/etc/fstab:
    Buildroot生成的内容默认为/dev/root / rw 0 1
    我们需要将/dev/root根据安装位置修改,如本例中为/dev/sdb1

如果搞错了加载为根目录的磁盘分区,会导致内核启动后无法加载根文件系统。

如何加入图形支持(X11)

我们可以使用Buildroot内置的预设X11的配置,然后调整。也可以直接自行添加X11支持。后者可以参考:
https://agentoss.wordpress.com/2011/03/06/building-a-tiny-x-org-linux-system-using-buildroot/
当然,你可以自行在配置界面添加任何自己需要的包。

下面的界面是运行在虚拟机中的结果。可以看出界面非常简陋,离实用差的很远,但大小也到了300MB。而且pcmanfm显示英文也全是乱码。所以,如果想自行构建一个实用的适合开发的图形系统,这只是万里长征第一步:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值