完全启动 Linux 所需的最少根文件系统应用程序是什么?

完全启动 Linux 所需的最少根文件系统应用程序是什么?

这是一个关于用户空间应用程序的问题,但请听我说完!

可以说,启动 Linux 的功能发行版需要三个“应用程序”:

  1. Bootloader - 对于嵌入式,通常是 U-Boot,尽管不是硬性要求。

  2. 内核——这非常简单。

  3. 根文件系统 - 没有它就无法启动到 shell。包含内核引导到的文件系统,其中init称为 form。

我的问题是关于#3 的。如果有人想构建一个极其最小的 rootfs(对于这个问题,我们说没有 GUI,只有 shell),启动到 shell 需要哪些文件/程序?

答案1

这完全取决于您希望在设备上使用哪些服务。

程式

你可以让Linux直接启动到。它在生产中不是很有用——谁只想有一个 shell 在那里——但当你有一个交互式引导加载程序时,它作为一种干预机制很有用:传递init=/bin/sh到内核命令行。所有 Linux 系统(以及所有 unix 系统)都有一个 Bourne/POSIX 风格的 shell,采用/bin/sh.

你需要一套外壳实用程序忙碌盒是一个很常见的选择;它包含一个 shell 和用于文件和文本操作 ( cpgrep、 ... )、网络设置 ( pingifconfig、 ... )、进程操作 ( psnice、 ... ) 和各种其他系统工具 ( fdiskmountsyslogd、 ... ) 的常用实用程序。 BusyBox 具有极高的可配置性:您可以在编译时选择所需的工具甚至单个功能,以获得适合您的应用程序的大小/功能折衷方案。除此之外sh,没有 、、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、、、 、 mount、 、 、 、 、 、 、umount、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、、 、 、 、、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、、BusyBox 作为名为 的单个二进制文件安装,每个实用程序都有一个符号链接。haltcatcpmvrmmkdirrmdirpssyncbusybox

普通 UNIX 系统上的第一个进程称为init。它的工作是启动其他服务。 BusyBox 包含一个初始化系统。除了init二进制文件(通常位于/sbin)之外,您还需要它的配置文件(通常称为/etc/inittab— 一些现代的 init 替换消除了该文件,但您不会在小型嵌入式系统上找到它们)来指示要启动哪些服务什么时候。对于BusyBox,/etc/inittab是可选的;如果缺少,您将在控制台上获得 root shell,并/etc/init.d/rcS在启动时执行脚本(默认位置)。

这就是您所需要的一切,当然,除了使您的设备执行有用操作的程序之外。例如,在我的家庭路由器上运行OpenWrt变体中,唯一的程序是 BusyBox nvram(用于读取和更改 NVRAM 中的设置)和网络实用程序。

除非所有可执行文件都是静态链接的,否则您将需要动态加载程序(ld.so,根据 libc 的选择和处理器架构,可能会用不同的名称来调用)和所有动态库( /lib/lib*.so,也许其中一些/usr/lib) 这些可执行文件需要。

目录结构

文件系统层次结构标准描述了Linux系统的通用目录结构。它适用于桌面和服务器安装:在嵌入式系统上可以省略很多内容。这是典型的最小值。

  • /bin:可执行程序(有些可能在/usr/bin其中)。
  • /dev:设备节点(见下文)
  • /etc:配置文件
  • /lib:共享库,包括动态加载器(除非所有可执行文件都是静态链接的)
  • /proc: 的挂载点进程文件系统
  • /sbin: 可执行程序。其区别在于/bin,它/sbin适用于仅对系统管理员有用的程序,但这种区别在嵌入式设备上没有意义。您可以建立/sbin到 的符号链接/bin
  • /mnt:在维护期间可以方便地将只读根文件系统作为临时挂载点
  • /sys: 的挂载点sysfs 文件系统
  • /tmp:临时文件的位置(通常是tmpfs挂载)
  • /usr: 包含子目录bin,libsbin./usr存在于不在根文件系统上的额外文件。如果没有,您可以创建/usr到根目录的符号链接。

设备文件

以下是一些典型的最小条目/dev

  • console
  • full(写入它总是报告“设备上没有剩余空间”)
  • log(程序用来发送日志条目的套接字),如果您有syslogd守护进程(例如 BusyBox 的)从中读取
  • null(就像一个始终为空的文件)
  • ptmx和一个pts目录,如果你想使用伪终端(即除控制台之外的任何终端)——例如,如果设备已联网并且您想通过 telnet 或 ssh 登录
  • random(返回随机字节,有阻塞风险)
  • tty(始终指定程序的终端)
  • urandom(返回随机字节,从不阻塞,但在新启动的设备上可能是非随机的)
  • zero(包含无限的空字节序列)

除此之外,您还需要硬件条目(网络接口除外,这些在 中没有条目/dev):串行端口、存储等。

对于嵌入式设备,您通常会直接在根文件系统上创建设备条目。高端系统有一个名为MAKEDEV创建/dev条目的脚本,但在嵌入式系统上,该脚本通常不捆绑到映像中。如果某些硬件可以热插拔(例如,如果设备具有 USB 主机端口),则应/dev乌德夫(您可能在根文件系统上仍然有一个最小的设置)。

启动时操作

除了根文件系统之外,您还需要安装一些文件系统才能正常操作:

  • 进程文件系统/proc(几乎不可或缺)
  • 系统文件系统/sys(几乎不可或缺)
  • tmpfs文件系统打开/tmp(允许程序创建位于 RAM 中的临时文件,而不是位于闪存或只读的根文件系统上)
  • 如果是动态的,则 tmpfs、devfs 或 devtmpfs /dev(请参阅上面“设备文件”中的 udev)
  • 开发者如果你想使用[伪终端],请打开/dev/pts(请参阅上面的注释pts

你可以做一个/etc/fstab文件并调用mount -a,或mount手动运行。

开始一个系统日志守护进程(以及klogd内核日志,如果syslogd程序不处理它),如果您有任何地方可以写入日志。

此后,设备就准备好启动特定于应用程序的服务。

如何制作根文件系统

这是一个漫长而多样的故事,所以我在这里要做的只是给出一些建议。

根文件系统可以保存在 RAM 中(从 ROM 或闪存中的(通常是压缩的)映像加载),或基于磁盘的文件系统(存储在 ROM 或闪存中),或从网络加载(通常通过网络加载)。TFTP) 如果适用的话。如果根文件系统位于 RAM 中,则将其设置为初始化文件系统— RAM 文件系统,其内容在启动时创建。

有许多框架可用于组装嵌入式系统的根映像。里面有几个提示BusyBox常见问题解答构建根是一种流行的镜像,允许您使用类似于 Linux 内核和 BusyBox 的设置构建整个根镜像。开放嵌入式是另一个这样的框架。

维基百科有一个(不完整的)流行列表嵌入式Linux发行版。您附近可能有的嵌入式 Linux 的一个例子是OpenWrt用于网络设备的操作系统系列(在修补匠的家用路由器上很流行)。如果你想通过经验学习,你可以尝试从零开始的Linux,但它面向的是业余爱好者的桌面系统,而不是面向嵌入式设备。

关于 Linux 与 Linux 内核的注释

Linux 内核中嵌入的唯一行为是在引导时启动第一个程序。 (我不会进入初始化程序初始化文件系统这里的微妙之处。)这个程序,传统上称为在里面,具有进程 ID 1 并具有某些特权(不受杀死信号)和责任(收获孤儿)。你可以运行一个带有 Linux 内核的系统,并启动任何你想要的进程作为第一个进程,但是你拥有的只是一个基于 Linux 内核的操作系统,而不是通常所说的“Linux”—— Linux,在该术语的常识中,是Unix类似操作系统,其内核是Linux内核。例如,Android是一个不是类Unix而是基于Linux内核的操作系统。

答案2

您所需要的只是一个静态链接的可执行文件,单独放置在文件系统上。您不需要任何其他文件。该可执行文件是 init 进程。它可以是 busybox。这为您提供了一个 shell 和许多其他实用程序,全部都在其中。您只需在 busybox 中手动执行命令即可进入功能齐全的系统,以读写方式挂载根文件系统、创建 /dev 节点、执行实际 init 等。

答案3

如果您不需要任何 shell 实用程序,静态链接的mksh二进制文件(例如 Linux/i386 上的 klibc – 130K)就可以了。您需要一个仅在循环中调用的/linuxrcor/init/sbin/init脚本:mksh -l -T!/dev/tty1

#!/bin/mksh
while true; do
    /bin/mksh -l -T!/dev/tty1
done

-T!$tty选项是最近添加的,mksh它告诉它在给定的终端上生成一个新的 shell 并等待它。 (在此之前,只能-T-对程序进行守护进程并-T$tty在终端上生成,但不能等待它。这不太好。)该-l选项只是告诉它运行登录 shell(读取/etc/profile~/.profile~/.mkshrc)。

这假设您的终端是/dev/tty1,替换。 (更神奇的是,终端可以自动被发现。/dev/console不会给你完全的作业控制权。)

您需要一些文件/dev才能使其工作:

  • /开发/控制台
  • /dev/空
  • /dev/tty
  • /dev/tty1

使用内核选项devtmpfs.mount=1引导无需填充/dev,只需将其设置为空目录(适合用作挂载点)。

您通常需要一些实用程序(来自 klibc、busybox、beastiebox、toybox 或 toolbox),但实际上并不需要它们。

您可能想要添加一个~/.mkshrc文件,该文件设置 $PS1 和一些基本的 shell 别名和函数。

我曾经仅使用 mksh(及其示例 mkshrc 文件)和 klibc-utils 为 Linux/m68k 制作了 171K 压缩(371K 未压缩)initrd。 (不过,这是在 -T! 添加到 shell 之前,因此它生成了登录 shell /dev/tty2,并向控制台回显一条消息,告诉用户切换终端。)它工作正常。

这是一个真的是最低限度设置。其他答案为更具特色的系统提供了极好的建议。这确实是一个特例。

免责声明:我是 mksh 开发人员。

答案4

最小初始化 hello world 程序一步一步

如这个答案所示,您所需要的只是一个静态链接的 ELF 文件,甚至不需要标准库,因此是一个具有单个文件的文件系统。

在此输入图像描述

编译一个没有任何依赖项的 hello world,并以无限循环结束。init.S:

.global _start
_start:
    mov $1, %rax
    mov $1, %rdi
    mov $message, %rsi
    mov $message_len, %rdx
    syscall
    jmp .
    message: .ascii "FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR\n"
    .equ message_len, . - message

我们不能使用sys_exit,否则内核会出现恐慌。

然后:

mkdir d
as --64 -o init.o init.S
ld -o init d/init.o
cd d
find . | cpio -o -H newc | gzip > ../rootfs.cpio.gz
ROOTFS_PATH="$(pwd)/../rootfs.cpio.gz"

这将创建一个带有 hello world 的文件系统/init,这是内核将运行的第一个用户态程序。我们还可以添加更多文件,并且当内核运行时d/可以从程序访问它们。/init

然后cd进入Linux内核树,像往常一样构建,并在QEMU中运行它:

git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
git checkout v4.9
make mrproper
make defconfig
make -j"$(nproc)"
qemu-system-x86_64 -kernel arch/x86/boot/bzImage -initrd "$ROOTFS_PATH"

你应该看到一行:

FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR

在模拟器屏幕上!请注意,这不是最后一行,因此您必须进一步查看。

如果静态链接 C 程序,您也可以使用它们:

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR\n");
    sleep(0xFFFFFFFF);
    return 0;
}

和:

gcc -static init.c -o init

您可以在具有 USB 的真实硬件上运行,/dev/sdX并且:

make isoimage FDINITRD="$ROOTFS_PATH"
sudo dd if=arch/x86/boot/image.iso of=/dev/sdX

关于这个主题的重要资料来源:http://landley.net/writing/rootfs-howto.html它还解释了如何使用gen_initramfs_list.sh,它是 Linux 内核源代码树中的一个脚本,可帮助自动化该过程。

为您提供 shell 的最小设置

Buildroot 是我最喜欢的选项,请参阅以下讨论:最小的 Linux 实现是什么?

此时,您基本上必须处理标准库,谁会sh在没有标准库的情况下编写 shell?因此,您最好只使用一些自动化脚本来设置所有这些。

在 Ubuntu 16.10、QEMU 2.6.1 上测试。

相关内容