In the previous article, we saw what Linux is, what it is suitable for, and what is the basic structure from a kernel view. In the present article we discussed, deeper, what is the structure of that called userland.
Normally, the application processors suitable to run Linux have an MMU (memory management unit) and (at least) two level of execution privileges:
The “userland” or “userspace” nomenclature, in opposition to “kernel land”, calling to “all things out of the kernel” refers to the set of code and data that are not executed as part of the kernel; this is: don’t have system privileges and they can't access the hardware except by making use of kernel services.
The exact manner to which userland is connected with kernel space is architecturally dependent but if the processor provides user mode execution, it also provides a special instruction or method to call kernel code in a controlled and secure way.
For example, in the old 80386, that did not have any special instruction to call the kernel, the operating system used a software interrupt for this (INT 0x80 in linux case, INT 0x2E in windows NT and similar). The i686 and later processors add the SYSCALL instruction for this purpose.
In any case, the transition from user space to kernel space is more expensive than normal instruction execution and should be minimized for a performant code.
For this reason, the major surface of the code is executed in userland by security reasons (if a userland code is malformed or writed by malicious intentions, the kernel space can catch the abnormal behavior and take actions like stop program or throws an error to other program)
In this diagram, you can see a simplified schema with the common userland components:
To illustrate the previous point, let's see an example of a minimal (but realistic) linux system:
In this example we can show any elements mentioned until this point:
In order to get a functional Linux system, the hardware needs to load the kernel, the device tree and additionally find the root filesystem with the userland and put all in a working state.
The way that this happens varies from platform to platform but, in general, the process is beastly standardized.
The processor is power on: At this point, the CPU executes some first boot code contained internally in the chip (normally as a masked ROM, FLASH or other internal non-volatile storage). This piece of code is known as a first stage bootloader.
The first stage bootloader, initialize the processor and peripherals and find a piece of code into external devices like microSD card, eMMC, external flash or other devices (in old desktop PCs this piece is equiparable to the BIOS)
The first stage bootloader loads a piece of code from external storage (normally a fixed-size of code or platform-defined file in a well defined filesystem) and executes it. This part is known as a second stage bootloader.
The second stage bootloader will be implemented as a own write program or use a standard piece of code provided by free software projects like u-boot. In particular, u-boot provides a second stage bootloader infrastructure for many platforms and allows programmers to define a new platform in an easy and standard form.
This second stage bootloader prepares media devices and other peripherals to load the real bootloader (like the main u-boot). This is required in some platforms because the boot-rom only initializes some minimal and fixed set of hardware (like MMC interface or someone) leaving others peripheral like main memory uninitialized.
When all of the required peripherals are initialized, the second stage bootloader loads the real bootloader and executes it. This ends the initialization of the hardware (some secondary peripherals like USB or ethernet) and finds the kernel, the root filesystem and the devicetree.
Normally the real bootloader (also called third stage bootloader) provides some basic console to interact with the user via serial terminal or screen (in case of devices that have one) and, additionally, more advance script capabilities and fallback boot (if the bootloader not found kernel or some others parts, this may try to boot from network or secondary devices like usb or SD card)
If the bootloader found the kernel, the devicetree (or a kernel with bundled devicetree), try to load all in RAM, link the devicetree to kernel (passing the address of this in a special register of the processor) and execute the kernel.
At this point, the kernel is loaded in RAM and all of the hardware is ready to operate with it (at least the main memory and processor clock). Now, the kernel init find in devicetree the drivers of the hardware and try to load and initialize them. If some driver fails to load (or is not compiled into the kernel), ignore it and process with the next entry.
When all drivers are initialized, the kernel tries to mount the root filesystem. The root filesystem is the place to find the ‘/’ directory and all other directories, files and other filesystems are attached to it.
The root filesystem needs to be located in an accessible device (interface initialized with functional driver, by example, SDCard, eMMC or PCIe NVMe) and contained in a known filesystem format (format with driver installed in the kernel and accessible at the moment of the load and compatible with linux requirements)
The name and location of root filesystem is configured in devicetree or pass in the command line of the kernel (yes! the kernel accepts a command line from the bootloader) with rootfs=... parameter. By example, to boot a second partition in PCIe NVMe formatted in ext4 format the command line must be: rootfs=/dev/nvme0n2
If the root filesystem can not be mount, the kernel stall with a fatal message informing it and wait for reboot some time (normally 5 seconds or so on, but this behavior will be changed or disabled completely)
Due to these limitations (and the variability of the systems) many kernels attach a fallback root filesystem that is loaded in RAM by the bootloader beside the kernel and contain the minimum root filesystem to find the rest of the system. This is called initramfs and will be covered later.
When the root filesystem is located and mounted, the kernel is ready to find the init program. This program is the first userspace program and is the root of other programs. If the “init” parameter is not specified in the command line, the kernel tries several names and locations until one is found and can be loaded and executed. In order: /sbin/init, /etc/init, /bin/init, /bin/sh.
If all of them fail, the kernel stalls with a panic message informing it and waits for several seconds for reboot.
At this point, the kernel leaves the control to the init process. Some simple linux systems use only a custom init process as the unique program in the system but others require more flexibility and use more complex init systems. This will be covered in the next article!
See you later!!!!
Martín Ribelotta
Embedded Linux Developer at Emtech S.A
Any Comments or questions, please feel free to contact us: info@emtech.com.ar