MDM*_*313 17 linux startup embedded architecture root-filesystem
这是一个关于用户空间应用程序的问题,但听我说!
可以这么说,启动 Linux 的功能性发行版需要三个“应用程序”:
Bootloader - 对于嵌入式,通常是 U-Boot,虽然不是硬性要求。
内核 - 这很简单。
根文件系统 - 没有它就无法引导到 shell。包含内核引导到的文件系统,其中init称为 form。
我的问题是关于#3。如果有人想构建一个极小的 rootfs(对于这个问题,我们假设没有 GUI,只有 shell),启动到 shell 需要哪些文件/程序?
Gil*_*il' 38
这完全取决于您希望在您的设备上拥有哪些服务。
您可以让 Linux 直接引导至shell。它在生产中不是很有用——谁只想在那里有一个 shell——但是当你有一个交互式引导加载程序时,它作为一种干预机制很有用:传递init=/bin/sh到内核命令行。所有 Linux 系统(和所有 unix 系统)在/bin/sh.
您将需要一组shell 实用程序。BusyBox是一个非常常见的选择;它包含一个 shell 和用于文件和文本操作 ( cp, grep, ...)、网络设置 ( ping, ifconfig, ...)、进程操作 ( ps, nice, ...) 和各种其他系统工具 ( fdisk, mount, syslogd, ...) 的通用实用程序。BusyBox 是极其可配置的:您可以在编译时选择您想要的工具,甚至是个别功能,以获得适合您的应用程序的大小/功能折衷。除此之外sh,最起码,你真的不能做任何事情,而不是mount,umount和halt,但它是非典型的不也有cat,cp,mv,rm,mkdir, rmdir, ps,sync等等。BusyBox 安装为名为 的单个二进制文件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: proc 文件系统的挂载点/sbin: 可执行程序。有区别/bin的是,/sbin对于那些只对系统管理员有用的程序,但这种区别并不在嵌入式设备上有意义的。您可以创建/sbin一个符号链接到/bin./mnt: 在维护期间将只读根文件系统作为临时挂载点很方便/sys: sysfs 文件系统的挂载点/tmp: 临时文件的位置(通常是tmpfs挂载)/usr: 包含子目录bin,lib和sbin. /usr存在于不在根文件系统上的额外文件。如果你没有那个,你可以建立/usr一个到根目录的符号链接。以下是最小的一些典型条目/dev:
consolefull (写入它总是报告“设备上没有剩余空间”)log(程序用来发送日志条目的套接字),如果您有一个syslogd守护进程(例如 BusyBox 的)从中读取null (就像一个总是空的文件)ptmx和一个pts目录,如果您想使用伪终端(即控制台以外的任何终端)——例如,如果设备已联网并且您想通过 telnet 或 ssh 登录random (返回随机字节,有阻塞的风险)tty (总是指定程序的终端)urandom (返回随机字节,从不阻塞,但在新启动的设备上可能是非随机的)zero (包含无限的空字节序列)除此之外,您还需要硬件条目(网络接口除外,这些条目不会在 中获取条目/dev):串行端口、存储等。
对于嵌入式设备,您通常会直接在根文件系统上创建设备条目。高端系统有一个被调用MAKEDEV来创建/dev条目的脚本,但在嵌入式系统上,该脚本通常不捆绑到图像中。如果某些硬件可以热插拔(例如,如果设备具有 USB 主机端口),那么/dev应该由udev管理(您可能仍然在根文件系统上有一个最小设置)。
除了根文件系统,您还需要挂载一些才能正常运行:
/proc(几乎不可或缺)/sys(几乎不可或缺)tmpfs文件系统打开/tmp(允许程序创建将在 RAM 中的临时文件,而不是在可能在闪存中或只读的根文件系统上)/devif dynamic(请参阅上面“设备文件”中的 udev)/dev/pts如果你想使用 [pseudo-terminals (见pts上面的评论)您可以创建一个/etc/fstab文件并调用mount -a,或者mount手动运行。
启动一个系统日志守护进程(以及klogd内核日志,如果syslogd程序不处理它),如果你有任何地方可以写入日志。
在此之后,设备准备启动特定于应用程序的服务。
这是一个漫长而多样的故事,所以我在这里要做的就是提供一些提示。
根文件系统可以保存在 RAM 中(从 ROM 或闪存中的(通常是压缩的)映像加载),或基于磁盘的文件系统(存储在 ROM 或闪存中),或者从网络加载(通常通过TFTP)(如果适用) . 如果根文件系统在 RAM 中,请将其设为 initramfs — 其内容在启动时创建的 RAM 文件系统。
有许多框架可用于为嵌入式系统组装根映像。BusyBox FAQ中有一些提示。Buildroot是一种流行的方法,它允许您使用类似于 Linux 内核和 BusyBox 的设置来构建整个根映像。OpenEmbedded是另一个这样的框架。
维基百科有一个(不完整的)流行的嵌入式 Linux 发行版列表。您可能拥有的嵌入式 Linux 的一个示例是用于网络设备的OpenWrt操作系统系列(在修补匠的家用路由器上很流行)。如果您想通过经验学习,您可以从零开始尝试Linux,但它面向业余爱好者的桌面系统而不是嵌入式设备。
融入 Linux 内核的唯一行为是在启动时启动的第一个程序。(我不会在这里讨论initrd和initramfs 的微妙之处。)这个程序,传统上称为init,具有进程 ID 1 并具有某些特权(对KILL 信号的免疫力)和责任(收割孤儿)。你可以运行一个带有 Linux 内核的系统,然后启动任何你想要的进程作为第一个进程,但是你拥有的是一个基于 Linux 内核的操作系统,而不是通常所说的“Linux”—— Linux,在常识中是一个类 Unix操作系统,其内核是Linux 内核. 例如,Android 是一个不是类 Unix 而是基于 Linux 内核的操作系统。
您只需要一个静态链接的可执行文件,单独放置在文件系统上。您不需要任何其他文件。该可执行文件是 init 进程。它可以是busybox。这为您提供了一个 shell 和许多其他实用程序,所有这些都在其本身中。您可以通过在 busybox 中手动执行命令来安装根文件系统读写、创建 /dev 节点、exec real init 等,从而进入一个功能齐全的系统。
如果您不需要任何 shell 实用程序,则mksh可以使用静态链接的二进制文件(例如,在 Linux/i386 上针对 klibc \xe2\x80\x93 130K)。您需要一个仅在循环中调用的/linuxrcor/init或脚本:/sbin/initmksh -l -T!/dev/tty1
#!/bin/mksh\nwhile true; do\n /bin/mksh -l -T!/dev/tty1\ndone\nRun Code Online (Sandbox Code Playgroud)\n\n该-T!$tty选项是最近添加的,mksh它告诉它在给定的终端上生成一个新的 shell 并等待它。(在此之前,只能-T-d\xc3\xa6monise 一个程序并-T$tty在终端上生成,但不能等待它。这不太好。)该-l选项只是告诉它运行一个登录 shell(其内容为/etc/profile,~/.profile和~/.mkshrc) 。
这假设您的终端是/dev/tty1,替换。(更神奇的是,终端可以自动被发现。/dev/console不会给你完全的作业控制权。)
您需要一些文件/dev才能使其工作:
使用内核选项引导devtmpfs.mount=1无需填充/dev,只需将其设置为空目录(适合用作挂载点)。
您通常需要一些实用程序(来自 klibc、busybox、beastiebox、toybox 或 toolbox),但实际上并不需要它们。
\n\n您可能想要添加一个~/.mkshrc文件,该文件设置 $PS1 和一些基本的 shell 别名和函数。
我曾经仅使用 mksh(及其示例 mkshrc 文件)和 klibc-utils 为 Linux/m68k 制作了 171K 压缩(371K 未压缩)initrd。(不过,这是在 -T! 添加到 shell 之前,因此它生成了登录 shell,/dev/tty2并向控制台回显一条消息,告诉用户切换终端。)它工作正常。
这确实是一个最低限度的设置。其他答案为更具特色的系统提供了极好的建议。这确实是一个特例。
\n\n免责声明:我是 mksh 开发人员。
\n最小初始化 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
Run Code Online (Sandbox Code Playgroud)
我们不能使用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"
Run Code Online (Sandbox Code Playgroud)
这将创建一个带有 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"
Run Code Online (Sandbox Code Playgroud)
你应该看到一行:
FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR
Run Code Online (Sandbox Code Playgroud)
在模拟器屏幕上!请注意,这不是最后一行,因此您必须进一步查看。
如果静态链接 C 程序,您也可以使用它们:
#include <stdio.h>
#include <unistd.h>
int main() {
printf("FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR FOOBAR\n");
sleep(0xFFFFFFFF);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
和:
gcc -static init.c -o init
Run Code Online (Sandbox Code Playgroud)
您可以在具有 USB 的真实硬件上运行,并且/dev/sdX:
make isoimage FDINITRD="$ROOTFS_PATH"
sudo dd if=arch/x86/boot/image.iso of=/dev/sdX
Run Code Online (Sandbox Code Playgroud)
关于这个主题的重要资料:http://landley.net/writing/rootfs-howto.html它还解释了如何使用gen_initramfs_list.sh,这是来自 Linux 内核源代码树的脚本,可帮助自动化该过程。
为您提供 shell 的最小设置
Buildroot 是我最喜欢的选项,请参阅以下讨论:What is the Small possible Linux implementation?
此时,您基本上必须处理标准库,谁会在sh没有标准库的情况下编写 shell?因此,您最好只使用一些自动化脚本来设置所有这些。
在 Ubuntu 16.10、QEMU 2.6.1 上测试。
| 归档时间: |
|
| 查看次数: |
14192 次 |
| 最近记录: |