用busybox制作initramfs并启动

Last Updated: 2023-04-18 09:44:08 Tuesday

-- TOC --

本文记录如何使用busybox制作可以启动的initramfs.img文件,并且用U盘将其启动。

busybox

busybox官网:https://www.busybox.net/

原理

将grub装入U盘,自己编译linux kernel,默认配置即可,手工制作基于busybox的rootfs(root ramdisk filesystem),配置grub.cfg,然后用启动!

initrd or initramfs

学习一点历史:

以前制作ramdisk,使用initrd方法(initrd method),这种方法需要创建一个固定大小的block device,现在网络上很多资料都是在说这个方法。

新的方法叫initramfs,用这种方法,不需要创建block device,创建方法更加简单。这种方法,可以直接将ramdisk编译进kernel,也可以通过initrd hook作为独立的文件加载(grub中的initrd指令)。

本文使用initramfs方法制作ramdisk,用initrd hook方式启动。

Linux kernel在自身初始化完成之后,需要能够找到并运行第一个用户程序(这个程序通常叫做“init”程序)。用户程序存在于文件系统之中,因此,内核必须找到并挂载一个文件系统才可以成功完成系统的引导过程。

在grub中提供了一个选项“root=”用来指定第一个文件系统,但随着硬件的发展,很多情况下这个文件系统也许是存放在USB设备,SCSI设备等等多种多样的设备之上,如果需要正确引导,USB或者SCSI驱动模块首先需要运行起来,可是不巧的是,这些驱动程序也是存放在文件系统里,因此会形成一个悖论。

为解决此问题,Linux kernel提出了一个RAM disk的解决方案,把一些启动所必须的用户程序和驱动模块放在RAM disk中,这个RAM disk看上去和普通的disk一样,有文件系统,有cache,内核启动时,首先把RAM disk挂载起来,等到init程序和一些必要模块运行起来之后,再切到真正的文件系统之中。

上面提到的RAM disk的方案实际上就是initrd。 如果仔细考虑一下,initrd虽然解决了问题但并不完美。 比如,disk有cache机制,对于RAM disk来说,这个cache机制就显得很多余且浪费空间;disk需要文件系统,那文件系统(如ext2等)必须被编译进kernel而不能作为模块来使用。

Linux 2.6 kernel提出了一种新的实现机制,即initramfs。顾名思义,initramfs只是一种RAM filesystem而不是disk。initramfs实际是一个cpio归档,启动所需的用户程序和驱动模块被归档成一个文件。因此,不需要cache,也不需要文件系统。

busybox简介

BusyBox - The Swiss Army Knife of Embedded Linux

busybox是一个可执行程序,它将linux下常用的命令工具,集成在一个bin文件中,以此来达到减少size和memory的效果。busybox为嵌入式linux系统而设计,因此对size和memory特别敏感。

BusyBox combines tiny versions of many common UNIX utilities into a single small executable. It provides minimalist replacements for most of the utilities you usually find in GNU coreutils, util-linux, etc. The utilities in BusyBox generally have fewer options than their full-featured GNU cousins; however, the options that are included provide the expected functionality and behave very much like their GNU counterparts.

busybox集成的这些linux命令行工具,并没有完整功能,但常用的基本都有,这可能也是busy的由来。

BusyBox has been written with size-optimization and limited resources in mind. It is also extremely modular so you can easily include or exclude commands (or features) at compile time. This makes it easy to customize your embedded systems. To create a working system, just add /dev, /etc, and a Linux kernel. BusyBox provides a fairly complete POSIX environment for any small or embedded system.

嵌入式系统资源有限,这是busybox的设计基本点。Linux启动首先会加载一个ramdisk到内存,然后再pivot_root到真正的rootfs,而嵌入式系统,很有可能ramdisk就是最终的rootfs,不会再切换。基于busybox制作这样的ramdisk,非常方便。

编译安装busybox

busybox的编译安装类似Linux kernel,也可以使用make menuconfig。在源码目录下:

$ make help
$ make defconfig
$ make menuconfig  # could be skipped
$ make && make install

make defconfig - Create the maximum "sane" configuration. This enables almost all features, minus things like debugging options and features that require changes to the rest of the system to work (such as selinux or devfs device names). Use this if you want to start from a full-featured Busybox and remove features until it's small enough.

make defconfig几乎选择了所有的busybox支持的cmd。然后再用make menuconfig,可以再去掉一些不需要的功能,这一步可以跳过。make install后,在源码目录下就生成了一个 _install 文件夹,这里面的内容,就已经是一个ramdisk的雏形了。而且,里面的busybox二进制文件,已经做了strip。

这部分更详细的内容,可以参考busybox官方的FAQ:https://www.busybox.net/FAQ.html

制作ramdisk

$ cd ~
$ make ramdisk
$ cd ramdisk
$ cp -r ~/busybox/_install/* .
$ rm linuxrc
$ ln -s bin/busybox init

将编译安装好的_intall目录下的所有内容,copy到自定义的目录中,我这里用ramdisk来表示,然后删除linuxrc文件,这个文件没用了。创建 /init 指向bin/busybox。(如果没有/init,kernel panic,不知道为什么kernel不去找/sbin/init文件)

$ mkdir etc dev proc sys root lib
$ ln -s lib lib64
$ cd usr
$ ln -s ../lib lib
$ ln -s ../lib lib64

创建必要的目录。

创建etc/fstab文件,内容如下:

$ cat etc/fstab
# /etc/fstab: static file system information.
#
# <file system> <mount point>   <type>    <options>     <dump>  <pass>
proc            /proc           proc      defaults      0       0
sysfs           /sys            sysfs     defaults      0       0
devtmpfs        /dev            devtmpfs  defaults      0       0

创建etc/init.d/rcS,内容如下:

$ cat etc/init.d/rcS
#!/bin/sh
mount -av
echo 'start nabati...!!!'

#!行只能用/bin/sh,busybox中有ash和sh,我理解它们是一样的,然后 chmod +x etc/init.d/rcS 给这个文件可执行权限。

创建etc/inittab,内容如下:

 cat etc/inittab
# /etc/inittab init(8) configuration for BusyBox

::sysinit:/etc/init.d/rcS
tty1::respawn:/sbin/getty 38400 tty1

# Stuff to do when restarting the init process
::restart:/sbin/init

# Stuff to do before rebooting
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r

init是1号进程,它的内容,就在inittab文件中。

tty1::respawn:/sbin/getty 38400 tty1,这一行最开始,必须是ttyN。

接下来,我们需要准备lib目录中的内容,如果没有lib中的一些库,init会执行失败,kernel panic!init指向busybox,实际上就是要让busybox能够运行起来,用ldd命令看一下,就知道busybox还是有依赖的。

我准备将整个glibc放入/lib文件夹!下载glibc源码(2.34),编译时有坑:

glibc的编译,强制要求不能在源码目录下编译,这是有道理的,因为我觉得至少需要编译2遍!

$ cd glibc-source
$ mkdir build1
$ cd build1
$ ../configure --prefix=/usr/local/glibc
$ make && make install
$
$ cd ..
$ mkdir build2
$ cd build2
$ ../configure --prefix=
$ make  # no make install
$ cp elf/ld-linux-x86-64.so.2 ~/ramdisk/lib
$
$ strip ~/ramdisk/lib/*

第1次编译,需要make install,将/usr/local/glibc/lib下的文件,全部copy到ramdisk的/lib目录下!第2次编译,不需要make install,不用安装,我们只需要将新编译出来的ld-linux-x86-64.so.2文件覆盖源/lib下的此文件!最后strip,脱衣服,缩小size。

glibc编译的时候,--prefix指向的路径,控制了ld-linux.so的默认搜索路径,第1次编译指定这个路径,是为了install,然后可以方便的copy出我们需要的其它so文件。第2次编译,让--prefix为空,此时的搜索路径就为/lib(编译系统自动将lib加到--prefix后面,stupid)。如果没有第2次编译的ld-linux.so文件,制作的ramdisk系统在启动的时候,会出现/init执行错误,提示找不到busybox需要的libm.so.6文件,然后kernel panic!(另外的解决方式,需要修改makefile,https://blog.csdn.net/remme123/article/details/9250765)

此时,ramdisk系统(如何打包后面说)已经可以启动了,能够看到login提示符,但是还没有用户可以登录。最后,我们创建root用户登录!

创建用户,就是创建/etc/{passwd,shadow,group}这3个文件,具体参考这里,每个文件,只需要保留root那一行即可。

对于此root账户的密码,可以使用openssl passwd命令来生成。这里的-1表示使用md5加密,应该如何控制此处login时所使用的hash算法呢?

准备好这3个文件:

$ cat etc/passwd
root:x:0:0:root:/root:/bin/sh
$ cat etc/shadow
root:$1$xjTnNlAb$hGSg6kzRhno4R1mUjIrbh0:16907:0:99999:7:::
$ cat etc/group
root:x:0:

制作initramfs.img文件,在ramdisk根目录下:

$ find -print0| cpio -0 -oH newc | gzip -9 > ../initramfs.img

这个initramfs.img文件生成后,后面的事情,就是制作U盘启动的grub,copy bzImage和initramfs.img文件,编写一个grub.cfg,然后用VMware测试是否能够启动和登录。grub部分的请参考:如何把grub装入U盘

一路走来不容易,截个图庆祝一下:

login

最大的坑,就是编译glibc时遇到的...

命令细节可能跟一般linux环境不太一样,需要参考busybox官方页面说明。

静态编译busybox

前面介绍的步骤,采用的是动态编译busybox,这种编译方式,需要准备glibc的库,否则busybox在linux启动的时候,无法运行,会导致kernel panic。

还可以静态编译busybox,这种编译方式不需要前面烦人的编译glibc的步骤,制作ramdisk更加简单:

$ make distclean
$ make defconfig
$ make menuconfig

在menuconfig步骤,选择build busybox as a static binary (no shared libs),然后:

$ make && make install

这样,就得到了一个不依赖glibc的busybox:

$ ldd busybox
    not a dynamic executable

此时,制作ramdisk,只需要准备etc目录下的那几个文件即可!好简单,除了busybox文件体积稍大一些。

用uclibc编译busybox

在真正嵌入式场景下,相对于庞大复杂的glibc,也许uclibc是更好的选择,它会使得ramdisk的体积更小!

https://uclibc-ng.org

将Python3嵌入ramdisk

编译安装Python3strip,将整个Python3目录copy到ramdisk目录结构中,制作软链接,后面的步骤都一样。关键点是,Python3依赖的.so文件要都存在!

$ ldd /usr/bin/python3
    linux-vdso.so.1 (0x00007fff735e6000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff202de9000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff202dc6000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007ff202dc0000)
    libutil.so.1 => /lib/x86_64-linux-gnu/libutil.so.1 (0x00007ff202dbb000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007ff202c6c000)
    libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007ff202c3e000)
    libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007ff202c20000)
    /lib64/ld-linux-x86-64.so.2 (0x00007ff202fed000)

在ramdisk中嵌入其它程序同理!

本文链接:https://cs.pynote.net/sf/linux/sys/202111123/

-- EOF --

-- MORE --