Linux System : Managing Linux Services - initramfs
https://github.com/carloscn/blog/issues/173
Linux System : Managing Linux Services - initramfs
The NXP-IMX6ULL is the NXP cortex-A7 (Armv7 arch) and the Xilinx ZYNQ is the cortex-A53 (Armv8 arch). We will use the cortex-A7 and cortex-A53 as initramfs examples to decribe how to setup the initramfs before booting the real rootfs. The article will introduce as follows:
What is the initramfs?
Build initramfs
Custom initramfs
Populate initramfs into kernel or uboot
1. What is the initramfs
1.1 initramfs/initrd introducation
The initramfs is the solution introduced for the 2.6 Linux kernel series. The idea is that there's a lot of initialization magic done in the kernel that could be just as easily done in userspace.[^1]
Initial RAM disk (INITRD) provides the capability to load a RAM disk by the boot loader during the Linux startup process. The Linux kernel mounts it as RootFS and starts the initialization process. This section describes the procedure to configure the INITRD boot[^3].
Initial RAM file system (INITRAMFS) is the successor of INITRD. It is a cpio
archive of the initial file system that gets loaded into memory during the Linux startup process. The Linux kernel mounts it as RootFS and starts the initialization process[^2].
At a first glance, it is only slightly different than a traditional initrd. Initramfs' are loaded quite a bit sooner than initrd's are. The key parts of initramfs are[^1]:
CPIO archive, so no filesystems at all are needed in kernel. The archive is simply unpacked into a ram disk.
This unpacking happens before do_basic_setup is called. This means that firmware files are available before in-kernel drivers load.
The userspace init is called instead of prepare_namespace. All finding of the root device, and md setup happens in userspace.
An initramfs can be built into the kernel directly by adding it to the ELF archive under the section name .init.ramfs
initramfs' can be stacked. Providing an initramfs to the kernel using the traditional initrd mechanisms causes it to be unpacked along side the initramfs' that are built into the kernel.
All magic naming of the root device goes away. Integrating udev into the initramfs means that the exact same view of the /dev tree can be used throughout the boot sequence. This should solve the majority of the SATA failures that are seen where an install can succeed, but the initrd cannot boot.
1.2 initramfs principle
The Linux kernel interacts with a file system while it’s running. The kernel supports a wide variety of memory storage devices where the filesystem can be stored. Most storage devices are persistent, which means they remember their state when powered off. The Linux kernel can also run in volatile memory, such as RAM. Some reasons for doing this include speed, since volatile memory is usually much faster, and the ability to modify a persistent file system while it’s not in use. The Linux kernel can be configured to run from RAM by using the initramfs feature[^5].
When using initramfs, a filesystem image is embedded into the kernel image. When the kernel boots, it extracts the filesystem image into RAM and runs an init script from there. The init script is usually a file called ‘init’ in the root of the ramfs filesystem, but this can be overridden by setting the ‘rdinit’ variable in the Linux command line from the boot loader. Configuring the Linux kernel to use initramfs is done through the menuconfig system. The option is “Initial RAM filesystem and RAM disk” in the General Setup section. The configuration requires the filename of the filesystem image to embed in the kernel. Creating the ramfs filesystem image must be done separately from building the kernel[^5].
After the initramfs finished tasks, initramfs will switch to the real root file system by switch_root
.
2. Build initramfs
An initramfs contains at least one file called /init
. This file is executed by the kernel as the main init process (PID 1). It has to do all the work. In addition, there can be any number of additional files and directories that are required by /init. They are usually files you'll also find on any other root filesystem, such as /dev for device nodes, /proc for kernel information, /bin for binaries, and so on. The structure of an initramfs can be simple, or it can be complicated, depending on what you are planning to do.
When the kernel mounts the initramfs, your target root partition is not yet mounted, so you can't access any of your files. That means there is nothing but the initramfs. So everything you need, everything you want, you have to include it in your initramfs. If you want a shell, you have to include it in your initramfs. If you want to mount something, you need a mount utility. If you need to load a module, your initramfs has to provide both the module, as well as a utility to load it. If the utility depends on libraries in order to work, you have to include the libraries as well. This seems complicated, and it is, because the initramfs has to function independently.
2.1 build source code
2.1.1 using the buildroot as ramdisk
Follow the next steps to enable the Kernel to support initrd
:
Then, mark the following packages in General setup:
Initial RAM filesystem and RAM disk (initramfs/initrd) support
Support initial ramdisk/ramfs compressed using gzip.
Compile the Kernel:
For building the initrd
on Buildroot file system, follow the next steps:
Then, mark the following option:
File system > initial RAM filesystem linked into linux kernel
Compile the file system:
The result of this compilation is a rootfs.ext2.gz file.
For converting the file system to the right format, it is required to use mkimage
tool. To install the tool use the following command:
Run the following command:
2.1.1 using the busybox as
BusyBox
BusyBox can be built either as a single static binary requiring no external libraries, or built requiring shared libraries such as GLIBC (default). This setting can be found under BusyBox Settings -> Build Options -> Build BusyBox as a static binary (no shared libs).
I generally choose to build BusyBox to require GLIBC as it is highly likely you will want to run additional applications that will require GLIBC sometime in the future.
At the menu, you can configure BusyBox options. Once configured, you can build BusyBox:
GLIBC
Some programs may require libgcc_s.so, otherwise you will receive an error:
error while loading shared libraries: libgcc_s.so.1: cannot open shared object file: No such file or directory
libgcc_s.so.1 can be copied over from your arm-linux-gnueabi installation:
cp /usr/lib/gcc-cross/arm-linux-gnueabi/4.7.3/libgcc_s.so.1 /home/export/rootfs/lib
Preparing RootFS
Once BusyBox and GLIBC has been cross-compiled, you will want to create the remainder of the root file system. Start by creating the necessary directory structure:
mkdir proc sys dev etc/init.d usr/lib
Now we must mount the /proc & /sys filesystem and populate the /dev nodes. This can be done at runtime by creating a file called etc/init.d/rcS and adding:
and make executable:
chmod +x etc/init.d/rcS
You should now have a basic, yet quite functional, BusyBox root file system.
DropBear (Optional)
DropBear requires RSA and DSS (Digital Signature Standard) encryption keys to be generated. I normally do this on the target, but you could generate the keys on the host if you have the dropbearkey executable installed.
To generate your keys:
You will also require users and passwords to validate login credentials:
Unless otherwise specified, root will be given a default home directory of /home/root. However as this doesn't exist, DropBear will close your connection immediately after successfully logging in. To address this, simply create a home directory for root:
mkdir /home /home/root
DropBear can now be started by running:
dropbear
and you should be able to remotely login to your system using the root user.
If you get an error after logging in, "Server refused to allocate pty" check you have Device Drivers > Character devices > Legacy (BSD) PTY support enabled in your kernel. (Especially applicable to Beaglebone kernels)
Alternatively, make sure you have mounted the /dev/pts filesystem (after /dev has been mounted)
ldconfig
ldconfig is used to configure dynamic linker run-time bindings. It creates symbolic links and a cache to the most recent shared libraries. As you build upon your root filesystem and add additional libraries, you may need to run ldconfig.
ldconfig will search for libraries in the trusted directory /lib. Additional search paths can be added to the ld.so.conf configuration file. ldconfig looks for a configuration file in /etc/ld.so.conf and generates a warning if this cannot be found. Suppress the warning and extend the search range to include /usr/lib by:
echo /usr/lib > etc/ld.so.conf
ldconfig will also generate a cache at /etc/ld.so.cache. If this file doesn't exist, it will be automatically generated.
Finally, to update the dynamic linker run-time bindings with verbose output, execute:
ldconfig -v
Read Only Filesystems
If you are using a root file system residing on flash memory, to improve longevity it may be desirable to either mount your rootfs as read only, or to move as many of the frequently written to files (for example /var) to a temporary volatile file system stored in RAM.
The following is an expanded etc/init.d/rcS file demonstrating this.
We mount /var and /dev as tmpfs. The device nodes are generated using mdev at boot and from hotplug, hence we move these to a temp file system otherwise they could not be generated on a read only root filesystem.
syslogd writes system messages to /var/log/messages and is something you probably don't want constantly writing to flash. After mounting /var as a temp file system, we create the /var/log directory for systemd.
2.2 get built ramdisk directly
The ARMv8 also can obtain by : https://drive.google.com/file/d/1N-0J3lmGCIeCO1_9zTNYmPMeVTZk3Ngh/view?usp=share_link
For making the uramdisk.gz
follows the next steps:
Change the /etc/inittab
file:
Add /dev/ttymxc0
Delete /dev/ttyPS0
3. Custom Script
3.1 INIT
The following example shows a minimalistic shell script, based on the busybox shell:
This example needs some device nodes to work, mainly the root block device. Change the script and copy the corresponding /dev/ node to fit the system needs.
Don't forget to make the /init file executable:
root # chmod +x /usr/src/initramfs/init
3.2 Packaging
3.2.1 Kernel configuration
With either method, there is a need to enable Initial RAM filesystem and RAM disk (initramfs/initrd) support.
3.2.2 Embedding into the Kernel
The initramfs to be embedded into the kernel image, set Initramfs source file(s) to the root of your initramfs, (e.g. /usr/src/initramfs) but this isn't necessary:
Now compile the kernel it will automatically put the files into a cpio archive and embed it into the kernel image. There will need to rebuild the kernel any time a changes is made to the initramfs.
3.2.3 Creating a separate file
To use a standalone archive file, adjust the kernel settings accordingly:
For this example gzip is sufficient.
Create a standalone archive file by running the following commands:
This will create a file called custom-initramfs.cpio.gz in /boot. Now instruct your bootloader to load this file along with the kernel.
3.3 uboot script setup
Set the u-boot script that is booting from ramdisk.
Ref
最后更新于