Booting is highly device-dependent, but here is an example using U-Boot on a BeagleBone Black and QEMU:.
The following U-Boot commands show how to boot Linux on a BeagleBone Black:
U-Boot# fatload mmc 0:1 0x80200000 zImage reading zImage 4606360 bytes read in 254 ms (17.3 MiB/s) U-Boot# fatload mmc 0:1 0x80f00000 am335x-boneblack.dtb reading am335x-boneblack.dtb 29478 bytes read in 9 ms (3.1 MiB/s) U-Boot# setenv bootargs console=ttyO0,115200 U-Boot# bootz 0x80200000 - 0x80f00000 Kernel image @ 0x80200000 [ 0x000000 - 0x464998 ] ## Flattened Device Tree blob at 80f00000 Booting using the fdt blob at 0x80f00000 Loading Device Tree to 8fff5000, end 8ffff325 ... OK Starting kernel ... [ 0.000000] Booting Linux on physical CPU 0x0 ...
Note that we set the kernel command line to console=ttyO0,115200
. That tells Linux which device to use for console output which, in this case, is the first UART on the board, device ttyO0
, at a speed of 115,200 bits per second. Without this, we would not see any messages after Starting the kernel ...
and therefore would not know if it was working or not.
Assuming that you have already installed qemu-system-arm
, you can launch it with the multi_v7 kernel and the .dtb
file for the ARM Versatile Express, as follows:
$ QEMU_AUDIO_DRV=none \ qemu-system-arm -m 256M -nographic -M vexpress-a9 -kernel zImage -dtb vexpress-v2p-ca9.dtb -append "console=ttyAMA0"
Note that setting QEMU_AUDIO_DRV
to none
is just to suppress error messages from QEMU about missing configurations for the audio drivers, which we do not use.
To exit from QEMU, type Ctrl-A
then x
(two separate keystrokes).
While things started off well, they ended badly:
[ 1.886379] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) [ 1.895105] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0, 0)
This is a good example of a kernel panic. A panic occurs when the kernel encounters an unrecoverable error. By default, it will print out a message to the console and then halt. You can set the panic
command line parameter to allow a few seconds before it reboots following a panic.
In this case, the unrecoverable error is because there is no root filesystem, illustrating that a kernel is useless without a user space to control it. You can supply a user space by providing a root filesystem either as a ramdisk or on a mountable mass storage device. We will talk about how to create a root filesystem in the next chapter but, to get things up and running, assume that we have a ramdisk in the file uRamdisk
and you can then boot to a shell prompt by entering these commands into U-Boot:
fatload mmc 0:1 0x80200000 zImage fatload mmc 0:1 0x80f00000 am335x-boneblack.dtb fatload mmc 0:1 0x81000000 uRamdisk setenv bootargs console=ttyO0,115200 rdinit=/bin/sh bootz 0x80200000 0x81000000 0x80f00000
Here, I have added rdinit=/bin/sh
to the command line so that the kernel will run a shell and give us a shell prompt. Now, the output on the console looks like this:
... [ 1.930923] sr_init: No PMIC hook to init smartreflex [ 1.936424] sr_init: platform driver register failed for SR [ 1.964858] Freeing unused kernel memory: 408K (c0824000 - c088a000) / # uname -a Linux (none) 3.18.3 #1 SMP Wed Jan 21 08:34:58 GMT 2015 armv7l GNU/Linux / #
At last, we have a prompt and can interact with our device.
In order to transition from kernel initialization to user space, the kernel has to mount a root filesystem and execute a program in that root filesystem. This can be via a ramdisk, as shown in the previous section, or by mounting a real filesystem on a block device. The code for all of this is in init/main.c
, starting with the function rest_init()
which creates the first thread with PID 1 and runs the code in kernel_init()
. If there is a ramdisk, it will try to execute the program /init
, which will take on the task of setting up the user space.
If it fails to find and run /init
, it tries to mount a filesystem by calling the function prepare_namespace()
in init/do_mounts.c
. This requires a root=
command line to give the name of the block device to use for mounting, usually in the form:
root=/dev/<disk name><partition number>
root=/dev/<disk name>p<partition number>
For example, for the first partition on an SD card, that would be root=/dev/mmcblk0p1
. If the mount succeeds, it will try to execute /sbin/init
, followed by /etc/init
, /bin/init
, and then /bin/sh
, stopping at the first one that works.
The init
program can be overridden on the command line. For a ramdisk, use rdinit=
, (I used rdinit=/bin/sh
earlier to execute a shell) and, for a filesystem, use init=
.
Kernel developers are fond of printing out useful information through liberal use of printk()
and similar functions. The messages are categorized according to importance, 0 being the highest:
Level |
Value |
Meaning |
---|---|---|
|
0 |
The system is unusable |
|
1 |
Action must be taken immediately |
|
2 |
Critical conditions |
|
3 |
Error conditions |
|
4 |
Warning conditions |
|
5 |
Normal but significant conditions |
|
6 |
Informational |
|
7 |
Debug-level messages |
They are first written to a buffer, __log_buf
, the size of which is two to the power of CONFIG_LOG_BUF_SHIFT
. For example, if it is 16, then __log_buf
is 64 KiB. You can dump the entire buffer using the command dmesg
.
If the level of a message is less than the console log level, it is displayed on the console as well as being placed in __log_buf
. The default console log level is 7, meaning that messages of level 6 and lower are displayed, filtering out KERN_DEBUG
which is level 7. You can change the console log level in several ways, including by using the kernel parameter loglevel=<level>
or the command dmesg -n <level>
.
The kernel command line is a string that is passed to the kernel by the bootloader, via the bootargs
variable in the case of U-Boot; it can also be defined in the device tree, or set as part of the kernel configuration in CONFIG_CMDLINE
.
We have seen some examples of the kernel command line already but there are many more. There is a complete list in Documentation/kernel-parameters.txt
. Here is a smaller list of the most useful ones:
Name |
Description |
---|---|
|
Sets the console log level to the highest level, eight, to ensure that you see all the kernel messages on the console. |
|
The |
|
Sets the |
|
Behavior when the kernel panics: if it is greater than zero, it gives the number of seconds before rebooting; if it is zero, it waits forever (this is the default); or if it is less than zero, it reboots without any delay. |
|
Sets the console log level to one, suppressing all but emergency messages. Since most devices have a serial console, it takes time to output all those strings. Consequently, reducing the number of messages using this option reduces boot time. |
|
The |
|
Mounts the root device as read-only. Has no effect on a ramdisk which is always read/write. |
|
Device to mount the root filesystem. |
|
The number of seconds to wait before trying to mount the root device, defaults to zero. Useful if the device takes time to probe the hardware, but also see |
|
The filesystem type for the root device. In many cases, it is auto-detected during mount, but it is required for |
|
Waits indefinitely for the root device to be detected. Usually necessary with |
|
Mounts the root device as read-write (default). |
The lpj
parameter is often mentioned in connection with reducing the kernel boot time. During initialization, the kernel loops for approximately 250 ms to calibrate a delay loop. The value is stored in the variable loops_per_jiffy
, and reported like this:
Calibrating delay loop... 996.14 BogoMIPS (lpj=4980736)
If the kernel always runs on the same hardware it will always calculate the same value. You can shave 250 ms off the boot time by adding lpj=4980736
to the command line.