User:Pietinger/Tutorials/Initramfs Overview
Even though this page is in the user namespace, corrections and additions are much appreciated! This is simply wiki policy, this page can be moved to the main wiki as soon as it achieves critical mass more.
I wrote this article so that I can link to it in the Gentoo forums if users have questions about it.
Tutorial: Initramfs Overview
In my point of view we have the best descriptions of an initramfs in our Wiki here ... but it is all a little bit "mixed". This tutorial gathers all links and informations needed to understand initramfs a little bit better (hopefully). Only links to our Wiki will be used.
What is an initramfs ?
In very easy words: It is a small linux system your kernel can start before of your main linux system. If you use an initramfs, it MUST mount your root partition of your main system, because kernel will not do this job anymore. In most cases it will also start your /sbin/init as last step, so your usual system start will run afterwards (or - very seldom - initramfs do all steps by itself to boot up your system).
Why ? / When do I need an initramfs ?
Because you can do things at boot-time a kernel is not able to do. Most famous example is: Decrypting a root partition. But there are also other reasons to use it. I will give you later some links for these "Solutions".
Type of initramfs
You can have two types of an initramfs: Embedded into the kernel image or as an external CPIO archive. This leads to two questions:
1. What are the advantages and disadvantages ?
2. How to build them ?
My answer to the first question:
Advantage of an embedded: Very easy to build IF you have access to kernel configuratiion (= not possible if you use the bin-kernel).
Disadvantage: If you want to change something IN your initramfs you must compile the kernel again.
Advantage of an external CPIO archive: You can change/update to other kernel versions without needing to compile your kernel again (if you can use the initramfs for many different kernel versions). Used in binary distributions OR in Linux-Boot-CD.
Disadvantage: You must configure one additional kernel command line parameter (initrd=) so your kernel can find (and load) this CPIO archive.
What do I need to build an initramfs ?
You can use a tool to build it automatically. For this we have enough articles and it is not part of this description.
For building it manually we need at minimum two files: The "new" init = your personal script, which kernel starts now instead of /sbin/init, and a list of files which shall be included in your initramfs. Instead of this file-list you can also build a directory with all needed files inside. So, here you have also two options. Together with the type of an initramfs you will now have 4 options for building an initramfs ;-)
Initramfs and your main root partition
As said before, your initramfs must mount your root partition and therefore needs the same information as kernel has needed before: Where is my root partition ? Here you have some choices ... depending if you use a bootmanager or if you use a stub kernel which will be started from your UEFI directly. See more here: User:Pietinger/Tutorials/Kernel_Commandline_Parameter
Building an initramfs
I will begin with the easiest type ... but first I would like to give you a recommendation:
It happens to many users who program their own init-script that they make a mistake and see a kernel Panic. Therefore you should NOT produce an embedded intramfs until your init-script runs correctly. Because if you change something in your init-script (fix an error) you would have to take this into account with an embedded initramfs: Custom_Initramfs#Integrated_initramfs_does_not_always_update
With an external CPIO archive you only rebuild the archive without having to compile the kernel. If everything works correctly at the end, you can still switch to an embedded initrmafs (which is then also my recommendation).
Embedded with a file-list
1. Copy both files into a directory you like. In most articles here in our wiki /usr/src/initramfs is used. Your init-file MUST have the name "init". Your file-list can can have every name you like, but in most articles here in our wiki /usr/src/initramfs/initramfs_list is used.
2. Configure your kernel with:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/usr/src/initramfs/initramfs_list) Initramfs source file(s)
[*] Support initial ramdisk/ramfs compressed using gzip
Built-in initramfs compression mode (Gzip) --->
3. Build your kernel with "make" and install it - as you always do.
An example for this you will find here: Early_Userspace_Mounting#Building_as_an_embedded_Initramfs
Embedded with a directory
1. Copy your init AND all files you need into a directory you like. In most articles here in our wiki /usr/src/initramfs is used.
2. Configure your kernel with:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/usr/src/initramfs/) Initramfs source file(s)
[*] Support initial ramdisk/ramfs compressed using gzip
Built-in initramfs compression mode (Gzip) --->
( You see the difference in "Initramfs source file(s)", do you ? )
3. Build your kernel with "make" and install it - as you always do.
Now it is time to give you a link to a great Wiki article: Custom_Initramfs#Directory_structure
External CPIO archive with a file-list
1. Copy both files into a directory you like. In most articles here in our wiki /usr/src/initramfs is used. Your init-file MUST have the name "init". Your file-list can can have every name you like, but in most articles here in our wiki /usr/src/initramfs/initramfs_list is used.
2. Do these steps:
root #
make -C /usr/src/linux/usr/ gen_init_cpio
root #
cd /usr/src/linux
root #
chmod +x usr/gen_init_cpio usr/gen_initramfs.sh
root #
usr/gen_initramfs.sh -o /root/initram.cpio /usr/src/initramfs/initramfs_list
root #
gzip --best /root/initram.cpio
3. Copy this file either into your /boot directory OR into your efi-directory of your ESP (depending if you use a bootmanager or an UEFI boot).
4. You need this in your kernel configuration:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
() Initramfs source file(s)
[*] Support initial ramdisk/ramfs compressed using gzip
Built-in initramfs compression mode (Gzip) --->
5. Configure your bootmanager or your UEFI that kernel will get the needed command line parameter: See next chapter.
An example for this you will find here: Early_Userspace_Mounting#Building_as_an_external_CPIO_archive
External CPIO archive with a directory
1. Copy your init AND all files you need into a directory you like. In most articles here in our wiki /usr/src/initramfs is used.
2. Do these steps:
root #
cd /usr/src/initramfs
root #
find . -print0 | cpio --null --create --verbose --format=newc | gzip --best > /boot/custom-initramfs.cpio.gz
3. Copy this file either into your /boot directory OR into your efi-directory of your ESP (depending if you use a bootmanager or an UEFI boot).
4. You need this in your kernel configuration:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
() Initramfs source file(s)
[*] Support initial ramdisk/ramfs compressed using gzip
Built-in initramfs compression mode (Gzip) --->
5. Configure your bootmanager or your UEFI that kernel will get the needed command line parameter: See next chapter.
An example for this you will find here: Custom_Initramfs#Packaging
Special Case: Building an embedded initramfs with a CPIO archive
If you have already an external CPIO archive but you want an embedded initramfs then you can build it with a "make" and these kernel settings:
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/PATH/TO/MY/myinitram.cpio) Initramfs source file(s)
or:
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/PATH/TO/MY/myinitram.cpio.gz) Initramfs source file(s)
[*] Support initial ramdisk/ramfs compressed using gzip
Built-in initramfs compression mode (Gzip) --->
It is important to use suffix .cpio so "make" understand it is a CPIO file !
It is important to use suffix .cpio.gz so "make" understand it is an already gzipped CPIO file
(Both is explained in /usr/src/linux/usr/Makefile )
Telling the kernel where to find its external CPIO archive
This is done with the kernel commandline parameter "initrd=..." Read this first: User:Pietinger/Tutorials/Kernel_Commandline_Parameter#Parameter:_initrd.3D
There is also a description in Early_Userspace_Mounting#Bootloader_configuration
Telling initramfs where to find the "real" root partition
This is a little bit more complicated because it depends how your init evaluates a kernel command line parameter. In most cases a "root=/dev/sdXY" will always work. Because this can sometimes lead to troubles it is recommended to use the ID of your root partition. Here it could be we must change something.
In many cases you cannot use a "root=PARTUUID=..." anymore WHEN init is a busybox-shell-script and needs "root=UUID=..." instead. Just look into the description of your "Solution". This explains it in more detail: User:Pietinger/Tutorials/Confusion_with_root=PARTUUID=_and_root=UUID=
Of course there exists also a rare used option: You can hardcode the UUID of your root partition directly in your init. In this case your init doesnt need the parameter "root=.." anymore (this is only used for security reasons). An example you will find in my (german) forums post: https://forums.gentoo.org/viewtopic-t-1159297.html
Why do I need an additional line when using an embedded initramfs ?
You have read Early_Userspace_Mounting and you want to know why this line is additionally necessary:
nod /dev/console 0600 0 0 c 5 1
The answer is: The kernel needs ALWAYS /dev/console BEFORE starting an initramfs (even if your initramfs uses a "mount -t devtmpfs none /dev").
Next question is: But why I dont need it when building an external CPIO archive ?
Because our "make" will ALWAYS (*) build an embedded initramfs - even if you dont want ;-) For this it takes the file /usr/src/linux/usr/default_cpio_list and builds with this an (additional) embedded initramfs. Now look into this file ...
(* as soon as you have enabled [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support)
So, I have two initramfs ? Yes, your kernel merges this embedded initramfs with your external CPIO archive together (at boot-time).
Advanced rescue_shell
If you look at the init-script from Early_Userspace_Mounting you will find the following function:
rescue_shell() { echo "$@" echo "Something went wrong. Dropping you to a shell." busybox --install -s exec /bin/sh }
This is also used in many other examples. Normally you don't want to end up in this function, which is why you don't pay much attention to it. However, if you are still programming your init-script and you need to find errors, then this function has a disadvantage: You can no longer resume at the position of the init-script afterwards. You can change this so that you can make an "exit" which takes you back to your init-script. So, do you have to decide how this function behaves with an "exit" ? Do you want a "fix and resume" rescue shell, or a "take over everything" rescue shell ? In this forums post @Hu presented a function that allows both: [[1]]. So maybe you want use this function:
rescue_shell() { busybox --install -s printf 'Starting rescue subshell: %s\nExit with code 5 to replace /init with a shell running as pid 1\n' "$*" /bin/sh if [[ $? -eq 5 ]]; then exec /bin/sh; fi }
You can now "exit" and resume your script or type "exit 5" and end it.
Solutions
Simple Skeleton
The following can be used as a starting point to create an embedded initramfs with a file-list:
1. Get a static Busybox:
root #
USE="-pam static static-libs" emerge -pvD busybox
and check with:
root #
ldd /bin/busybox
not a dynamic executable
2. Create directory and init script:
root #
mkdir /usr/src/initramfs
root #
cd /usr/src/initramfs
root #
nano init
#!/bin/busybox sh rescue_shell() { # The symlinks are not required but it helps tab completion busybox --install -s printf 'Starting rescue subshell: %s\nExit with code 5 to replace /init with a shell running as pid 1\n' "$*" /bin/sh if [[ $? -eq 5 ]]; then exec /bin/sh; fi } # If you want to debug this script enable: # set -x ### Prepare # clear the screen clear echo "Mounting proc, sys and devtmpfs ..." mount -t devtmpfs devtmpfs /dev || rescue_shell "Error: mount /devtmpfs failed !" mount -t proc proc /proc || rescue_shell "Error: mount /proc failed !" mount -t sysfs sysfs /sys || rescue_shell "Error: mount /sysfs failed !" # If you do very special things (like IMA) maybe you will need also: # echo "Mounting securityfs ..." # mount -t securityfs securityfs /sys/kernel/security || rescue_shell "Error: mount /sys/kernel/securityfs failed !" # Suppress outputs of the kernel echo 0 > /proc/sys/kernel/printk ### INSERT here everything you want to do BEFORE mounting the root partition ! echo echo "Hello World !" echo sleep 3 ### END insert ### Now we must mount the root partition. We have 3 choices to do this: # 1. Examination of the kernel command line parameter root= # OR # 2. Using a hardcoded value # OR # 3. Doing very special things ;-) # Option 1: Because in many examples of an init script this is done in a function, I dont change it: uuidlabel_root() { for cmd in $(cat /proc/cmdline) ; do case $cmd in root=*) type=$(echo $cmd | cut -d= -f2) echo "Mounting rootfs" if [ $type == "LABEL" ] || [ $type == "UUID" ] ; then uuid=$(echo $cmd | cut -d= -f3) mount -o ro $(findfs "$type"="$uuid") /mnt/root else mount -o ro $(echo $cmd | cut -d= -f2) /mnt/root fi ;; esac done } # mounting rootfs on /mnt/root uuidlabel_root || rescue_shell "Error mounting root partition !" # Option 2: Using a hardcoded value # CHANGE THIS ! # myrootpartition="UUID=c75f64b1-a1b1-4527-b996-4b4b9d24456c" # echo "Searching root partition device name of $myrootpartition ..." # rootdev=`findfs $myrootpartition` || rescue_shell "Error with findfs !" # echo "Found $rootdev as your root partition. Will mount it now ..." # mount -o ro $rootdev /mnt/root || rescue_shell "Error mounting root partition !" # Option 3: ... you have to know that yourself ... ;-) ### END mounting root partition ### INSERT here everything you want to do AFTER mounting the root partition ! echo "Bye !" sleep 3 ### END insert ### Clean up. The init process will remount proc sys and dev later # Enable kernel messages echo 1 > /proc/sys/kernel/printk # If securityfs was also mounted: # echo "Unmounting securityfs ..." # umount /sys/kernel/security echo "Unmounting proc, sys and devtmpfs ..." umount /proc /sys /dev # switch to the real root and execute init exec /sbin/switch_root /mnt/root /sbin/init
3. Create a list of files you need in your initramfs:
root #
nano initramfs_list
dir /bin 755 0 0
dir /dev 755 0 0
dir /etc 755 0 0
dir /lib 755 0 0
dir /lib64 755 0 0
dir /mnt 755 0 0
dir /mnt/root 755 0 0
dir /proc 755 0 0
dir /root 700 0 0
dir /sbin 755 0 0
dir /sys 755 0 0
dir /usr 755 0 0
dir /usr/bin 755 0 0
dir /usr/lib64 755 0 0
dir /var 755 0 0
nod /dev/console 0600 0 0 c 5 1
file /init /usr/src/initramfs/init 755 0 0
file /usr/bin/busybox /usr/bin/busybox 755 0 0
(Of course you have to extend this list if you use external programs in your init-script.)
4. Configure your kernel with:
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/usr/src/initramfs/initramfs_list) Initramfs source file(s)
[*] Support initial ramdisk/ramfs compressed using gzip
Built-in initramfs compression mode (Gzip) --->
# This option is usually already enabled when using Gentoo Sources; just check it
Device Drivers --->
Generic Driver Options --->
-*- Maintain a devtmpfs filesystem to mount at /dev
and build and install this kernel as you always do (depending if you use a bootmanager or UEFI boot).