这是一个关于用户空间应用程序的问题,但请听我说完!
可以说,启动 Linux 的功能发行版需要三个“应用程序”:
Bootloader - 对于嵌入式,通常是 U-Boot,尽管不是硬性要求。
内核——这非常简单。
根文件系统 - 没有它就无法启动到 shell。包含内核引导到的文件系统,其中
init
称为 form。
我的问题是关于#3 的。如果有人想构建一个极其最小的 rootfs(对于这个问题,我们说没有 GUI,只有 shell),启动到 shell 需要哪些文件/程序?
答案1
这完全取决于您希望在设备上使用哪些服务。
程式
你可以让Linux直接启动到壳。它在生产中不是很有用——谁只想有一个 shell 在那里——但当你有一个交互式引导加载程序时,它作为一种干预机制很有用:传递init=/bin/sh
到内核命令行。所有 Linux 系统(以及所有 unix 系统)都有一个 Bourne/POSIX 风格的 shell,采用/bin/sh
.
你需要一套外壳实用程序。忙碌盒是一个很常见的选择;它包含一个 shell 和用于文件和文本操作 ( cp
、grep
、 ... )、网络设置 ( ping
、ifconfig
、 ... )、进程操作 ( ps
、nice
、 ... ) 和各种其他系统工具 ( fdisk
、mount
、syslogd
、 ... ) 的常用实用程序。 BusyBox 具有极高的可配置性:您可以在编译时选择所需的工具甚至单个功能,以获得适合您的应用程序的大小/功能折衷方案。除此之外sh
,没有 、、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、、、 、 mount
、 、 、 、 、 、 、umount
、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、、 、 、 、、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、 、、BusyBox 作为名为 的单个二进制文件安装,每个实用程序都有一个符号链接。halt
cat
cp
mv
rm
mkdir
rmdir
ps
sync
busybox
普通 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
,lib
和sbin
./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)就可以了。您需要一个仅在循环中调用的/linuxrc
or/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 上测试。