User:Pietinger/Tutorials/Initramfs Overview

From Gentoo Wiki
Jump to:navigation Jump to:search
Note
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:

KERNEL
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:

KERNEL
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:

KERNEL
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:

KERNEL
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:

KERNEL
    [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
    (/PATH/TO/MY/myinitram.cpio) Initramfs source file(s)

or:

KERNEL
    [*] 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:

FILE /usr/src/initramfs/initramfs_list
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
FILE /usr/src/initramfs/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    /bin/busybox                    /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:

KERNEL Kernels 6.6
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).