35 min read

In this article by Ivan Morgillo and Stefano Viola, the authors of the book Learning Embedded Android N Programming, you will learn about the kernel customization to the boot sequence. You will learn how to retrieve the proper source code for Google devices, how to set up the build environment, how to build your first custom

(For more resources related to this topic, see here.)

version of the Linux kernel, and deploy it to your device. You will learn about:

  • Toolchain overview
  • How to configure the host system to compile your own Linux kernel
  • How to configure the Linux kernel
  • Linux kernel overview
  • Android boot sequence
  • The Init process

An overview of the Linux kernel

We learned how Android has been designed and built around the Linux kernel. One of the reasons to choose the Linux kernel was its unquestioned flexibility and the infinite possibilities to adjust it to any specific scenario and requirement. These are the features that have made Linux the most popular kernel in the embedded industry.

Linux kernel comes with a GPL license. This particular license allowed Google to contribute to the project since the early stages of Android. Google provided bug fixing and new features, helping Linux to overcome a few obstacles and limitations of the 2.6 version. In the beginning, Linux 2.6.32 was the most popular version for the most part of the Android device market. Nowadays, we see more and more devices shipping with the new 3.x versions.

The following screenshot shows the current build for the official Google Motorola Nexus 6, with kernel 3.10.40:

The Android version we created in the previouly was equipped with a binary version of the Linux kernel. Using an already compiled version of the kernel is the standard practice: as we have seen, AOSP provides exactly this kind of experience.

As advanced users, we can take it a step further and build a custom kernel for our custom Android system. The Nexus family offers an easy entry into this world as we can easily obtain the kernel source code we need to build a custom version. We can also equip our custom Android system with our custom Linux kernel and we will have a full-customized ROM, tailored for our specific needs.

In this book, we are using Nexus devices on purpose—Google is one of the few companies that formally make available the kernel source code. Even if every company producing and selling Android devices is forced by law to release the kernel source code, very few of them actually do it, despite all the GPL license rules.

Obtaining the kernel

Google provides the kernel source code and binary version for every single version of Android for every single device of the Nexus family.

The following table shows where the binary version and the source code are located, ordered by device code name:

Device Binary location Source location Build configuration
shamu device/moto/shamu-kernel kernel/msm shamu_defconfig
fugu device/asus/fugu-kernel kernel/x86_64 fugu_defconfig
volantis device/htc/flounder-kernel kernel/tegra flounder_defconfig
hammerhead device/lge/ hammerhead-kernel kernel/msm hammerhead_defconfig
flo device/asus/flo-kernel/kernel kernel/msm flo_defconfig
deb device/asus/flo-kernel/kernel kernel/msm flo_defconfig
manta device/samsung/manta/kernel kernel/exynos manta_defconfig
mako device/lge/mako-kernel/kernel kernel/msm mako_defconfig
grouper device/asus/grouper/kernel kernel/tegra tegra3_android_defconfig
tilapia device/asus/grouper/kernel kernel/tegra tegra3_android_defconfig
maguro device/samsung/tuna/kernel kernel/omap tuna_defconfig
toro device/samsung/tuna/kernel kernel/omap tuna_defconfig
panda device/ti/panda/kernel kernel/omap panda_defconfig
stingray device/moto/wingray/kernel kernel/tegra stingray_defconfig
wingray device/moto/wingray/kernel kernel/tegra stingray_defconfig
crespo device/samsung/crespo/kernel kernel/samsung herring_defconfig
crespo4g device/samsung/crespo/kernel kernel/samsung herring_defconfig

We are going to work with the Motorola Nexus 6, code name Shamu.

Both the kernel binary version and the kernel source code are stored in a git repository. All we need to do is compose the proper URL and clone the corresponding repository.

Retrieving the kernel’s binary version

In this section, we are going to obtain the kernel as a binary, prebuilt file. All we need is the previous table that shows every device model, with its codename and its binary location that we can use to compose the download of the URL. We are targeting Google Nexus 6, codename shamu with binary location:

device/moto/shamu-kernel

So, to retrieve the binary version of the Motorola Nexus 6 kernel, we need the following command:

$ git clone https://android.googlesource.com/device/moto/shamu-kernel

The previous command will clone the repo and place it in the shamu-kernel folder. This folder contains a file named zImage-dtb—this file is the actual kernel image that can be integrated in our ROM and flashed into our device.

Having the kernel image, we can obtain the kernel version with the following command:

$ $ dd if=kernel bs=1 skip=$(LC_ALL=C grep -a -b -o $'x1fx8bx08x00x00x00x00x00' kernel | cut -d ':' -f 1) | zgrep -a 'Linux version'

Output:

The previous screenshot shows the command output: our kernel image version is 3.10.40 and it has been compiled with GCC version 4.8 on October the the twenty-second at 22:49.

Obtaining the kernel source code

As for the binary version, the previous table is critical also to download the kernel source code. Targeting the Google Nexus 6, we create the download URL using the source location string for the device codename shamu:

kernel/msm.git

Once we have the exact URL, we can clone the GIT repository with the following command:

$ git clone https://android.googlesource.com/kernel/msm.git

Git will create an msm folder. The folder will be strangely empty—that’s because the folder is tracking the master branch by default. To obtain the kernel for our Nexus 6, we need to switch to the proper branch.

There are a lot of available branches and we can check out the list with the following command:

$ git branch -a

The list will show every single branch, targeting a specific Android version for a specific Nexus device. The following screenshot shows a subset of these repositories:

Now that you have the branch name, for your device and your Android version, you just need to checkout the proper branch:

$ git checkout android-msm-shamu-3.10-lollipop-release

The following screenshot shows the expected command output:

Setting up the toolchain

The toolchain is the set of all the tools needed to effectively compile a specific software to a binary version, enabling the user to run it. In our specific domain, the toolchain allows us to create a system image ready to be flashed to our Android device. The interesting part is that the toolchain allows us to create a system image for an architecture that is different from our current one: odds are that we are using an x86 system and we want to create a system image targeting an ARM (Advanced RISC Machine) device. Compiling software targeting an architecture different from the one on our host system is called cross-compilation.

The Internet offers a couple of handy solutions for this task—we can use the standard toolchain, available with the AOSP (Android Open Source Project) or we can use an alternative, very popular toolchain, the Linaro toolchain. Both toolchains will do the job—compile every single C/C++ file for the ARM architecture.

As usual, even the toolchain is available as precompiled binary or as source code, ready to be compiled. For our journey, we are going to use the official toolchain, provided by Google, but when you need to explore this world even more, you could try out the binary version of Linaro toolchain, downloadable from www.linaro.org/download. Linaro toolchain is known to be the most optimized and performing toolchain in the market, but our goal is not to compare toolchains or stubbornly use the best or most popular one. Our goal is to create the smoothest possible experience, removing unnecessary variables from the whole building a custom Android system equation.

Getting the toolchain

We are going to use the official toolchain, provided by Google. We can obtain it with Android source code or downloading it separately. Having your trusted Android source code folder at hand, you can find the toolchain in the following folder:

AOSP/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/

This folder contains everything we need to build a custom kernel—the compiler, the linker, and few more tools such as a debugger.

If, for some unfortunate reason, you are missing the Android source code folder, you can download the toolchain using the following git command:

$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8

Preparing the host system

To successfully compile our custom kernel, we need a properly configured host system. The requirements are similar to those we satisfied to build the whole Android system:

  • Ubuntu
  • Linux kernel source code
  • Toolchain
  • Fastboot

Ubuntu needs a bit of love to accomplish this task: we need to install the ncurses-dev package:

$ sudo apt-get install ncurses-dev

Once we have all the required tools installed, we can start configuring the environment variables we need. These variables are used during the cross-compilation and can be set via the console. Fire up your trusted Terminal and launch the following commands:

$ export PATH=<toolchain-path>/arm-eabi-4.8/bin:$PATH
$ export ARCH=arm
$ export SUBARCH=arm
$ export CROSS_COMPILE=arm-eabi-

Configuring the kernel

Before being able to compile the kernel, we need to properly configure it. Every device in the Android repository has a specific branch with a specific kernel with a specific configuration to be applied.

The table on page 2 has a column with the exact information we need—Build configuration. This information represents the parameter we need to properly configure the kernel build system. Let’s configure everything for our Google Nexus 6. In your terminal, launch the following command:

$ make shamu_defconfig

This command will create a kernel configuration specific for your device. The following screenshot shows the command running and the final success message:

Once the .config file is in place, you could already build the kernel, using the default configuration. As advanced users, we want more and that’s why we will take full control of the system, digging into the kernel configuration. Editing the configuration could enable missing features or disable unneeded hardware support, to create the perfect custom kernel, and fit your needs.

Luckily, to alter the kernel configuration, we don’t need to manually edit the .config file. The Linux kernel provides a graphical tool that will allow you to navigate the whole configuration file structure, get documentation about the single configurable item, and prepare a custom configuration file with zero effort.

To access the configuration menu, open your terminal, navigate to the kernel folder and launch the following command:

$ make menuconfig

The following screenshot shows the official Linux kernel configuration tool—no frills, but very effective:

In the upper half of the screenshot, you can see the version of the kernel we are going to customize and a quick doc about how you can navigate all those menu items: you navigate using the arrow keys, you enter a subsection with the Enter key, you select or deselect an item using Y/N or Spacebar to toggle.

With great power comes great responsibility, so be careful enabling and disabling features—check the documentation in menuconfig, check the Internet, and, most of all, be confident. A wrong configuration could cause a freeze during the boot sequence and this would force you to learn, to create a different configuration and try again.

As a real-world example, we are going to enable the FTDI support. Future Technology Devices International or FTDI is a worldwide known semiconductor company, popular for its RS-232/TTL to USB devices. These devices come in very handy to communicate to embedded devices using a standard USB connection. To enable the FTDI support, you need to navigate to the right menu by following these steps:

Device Drivers|USB support|USB Serial Converter support

Once you reach this section, you need to enable the following item:

USB FTDI Single Port Serial Driver

The following screenshot shows the correctly selected item and gives you an idea of how many devices we could possibly support (this screen only shows the USB Serial Converter support):

Once you have everything in place, just select Exit and save the configuration, as shown in the following screenshot:

With the exact same approach, you can add every new feature you want. One important note, we added the FTDI package merging it into the kernel image. Linux kernel gives you the opportunity to make a feature available also as a module. A module is an external file, with .ko extension, that can be injected and loaded in the kernel at runtime. The kernel modules are a great and handy feature when you are working on a pure Linux system, but they are very impractical on Android. With the hope of having a modular kernel, you should code yourself the whole module loading system, adding unnecessary complexity to the system. The choice we made of having the FTDI feature inside the kernel image penalizes the image from a size point of view, but relieves us from the manual management of the module itself. That’s why the common strategy is to include every new feature we want right into the kernel core.

Compiling the kernel

Once you have a properly configured environment and a brand new configuration file, you just need one single command to start the building process. On your terminal emulator, in the kernel source folder, launch:

$ make

The make command will wrap up the necessary configuration and will launch the compiling and assembling process. The duration of the process heavily depends on the performance of your system: it could be one minute or one hour. As a reference, an i5 2.40 GHz CPU with 8 GB of RAM takes 5-10 minutes to complete a clean build. This is incredibly quicker than compiling the whole AOSP image, as you can see, due to the different complexity and size of the code base.

Working with non-Google devices

So far, we have worked with Google devices, enjoying the Google open-source mindset. As advanced users, we frequently deal with devices that are not from Google or that are not even a smartphone. As a real-world example, we are going to use again a UDOO board: a single-board computer that supports Ubuntu or Android. For the time being, the most popular version of UDOO is the UDOO Quad and that’s the version we are targeting.

As for every other device, the standard approach is to trust the manufacturer’s website to obtain kernel source code and any useful documentation for the process: most of all, how to properly flash the new kernel to the system. When working with a custom kernel, the procedure is quite consolidated. You need the source code, the toolchain, a few configuration steps, and, maybe, some specific software package to be installed on to your host system. When it comes to flashing the kernel, every device can have a different procedure. This depends on how the system has been designed and which tools the manufacturing team provides. Google provides fastboot to flash our images to our devices. Other manufactures usually provide tools that are similar or that can do similar things with little effort.

The UDOO development team worked hard to make the UDOO board fully compatible with fastboot—instead of forcing you to adjust to their tools, they adjusted their device to work with the tools you already know. They tuned up the board’s bootloader and you can now flash the boot.img using fastboot, like you were flashing a standard Google Android device.

To obtain the kernel, we just need to clone a git repository. With your trusted terminal, launch the following command:

$ git clone http://github.com/UDOOBoard/Kernel_Unico kernel

Once we have the kernel, we need to install a couple of software packages in our Ubuntu system to be able to work with it. With the following command, everything will be installed and put in place:

$ sudo apt-get install build-essential ncurses-dev u-boot-tools

Time to pick a toolchain! UDOO gives you a few possibilities—you can use the same toolchain you used for the Nexus 6 or you can use the one provided by the UDOO team itself. If you decide to use the UDOO official toolchain, you can download it with a couple of terminal commands. Be sure you have already installed curl.

If not, just install it with the following command:

$ sudo apt-get install curl

Once you have curl, you can use the following command to download the toolchain:

$ curl http://download.udoo.org/files/crosscompiler/arm-fsl-linux-gnueabi.tar.gz | tar -xzf

Now, you have everything in place to launch the build process:

$ cd kernel
$ make ARCH=arm UDOO_defconfig

The following is the output:

/sites/default/files/Article-Images/B04293_05_09.png

The previous screenshot shows the output of the configuration process.

When the default .config file is ready, you can launch the build process with the following command:

$ make –j4 CROSS_COMPILE ../arm-fsl-linux-gnueabi/bin/arm-fsl-linux-gnueabi- ARCH=arm uImage modules

When the build process is over, you can find the kernel image in the arch folder:

$ arch/arm/boot/uImage

As for the Nexus 6, we can customize the UDOO kernel using menuconfig. From the kernel source folder, launch the following command:

$ make ARCH=arm menuconfig

The following screenshot shows the UDOO kernel configuration menu. It’s very similar to the Nexus 6 configuration menu. We have the same combination of keys to navigate, select and deselect features, and so on:

Working with UDOO, the same warnings we had with the Nexus 6 apply here too—be careful while removing components from the kernel. Some of them are just meant to be there to support specific hardware, some of them, instead, are vital for the system to boot. As always, feel free to experiment, but be careful about gambling!

This kind of development device makes debugging the kernel a bit easier compared to a smartphone. UDOO, as with a lot of other embedded development boards, provides a serial connection that enables you to monitor the whole boot sequence. This comes in handy if you are going to develop a driver for some hardware and you want to integrate it into your kernel or even if you are simply playing around with some custom kernel configuration. Every kernel and boot-related message will be printed to the serial console, ready to be captured and analyzed.

The next screenshot shows the boot sequence for our UDOO Quad board:

As you can see, there is plenty of debugging information, from the board power-on to the Android system prompt.

Driver management

Since version 2.6.x, Linux gives the developer the opportunity to compile parts of the kernel as separated modules that can be injected into the core, to add more features at runtime. This approach gives flexibility and freedom: there is no need to reboot the system to enjoy new features and there is no need to rebuild the whole kernel if you only need to update a specific module. This approach is widely use in the PC world, by embedded devices such as routers, smart TVs, and even by our familiar UDOO board.

To code a new kernel module is no easy task and it’s far from the purpose of this book: there are plenty of books on the topic and most of the skill set comes from experience. In these pages, you are going to learn about the big picture, the key points, and the possibilities.

Unfortunately, Android doesn’t use this modular approach: every required feature is built in a single binary kernel file, for practical and simplicity reasons. In the last few years there has been a trend to integrate into the kernel even the logic needed for Wi-Fi functionality, that was before it was loaded from a separated module during the boot sequence.

As we saw with the FTDI example in the previous pages, the most practical way to add a new driver to our Android kernel is using menuconfig and building the feature as a core part of the kernel.

Altering the CPU frequency

Overclocking a CPU is one of the most loved topics among advanced users. The idea of getting the maximum amount of powerfrom your device is exciting. Forums and blogs are filled with discussions about overclocking and in this section we are going to have an overview and clarify a few tricky aspects that you could deal with on your journey.

Every CPU is designed to work with a specific clock frequency or within a specific frequency range. Any modern CPU has the possibility to scale its clock frequency to maximize performance when needed and power consumption when performance is not needed, saving precious battery in case of our beloved mobile devices. Overclocking, then, denotes the possibility to alter this working clock frequency via software, increasing it to achieve performance higher than the one the CPU was designed for.

Contrary to what we often read on unscrupulous forum threads or blogs, overclocking a CPU can be a very dangerous operation: we are forcing the CPU to work with a clock frequency that formally hasn’t been tested. This could backfire on us with a device rebooting autonomously, for its own protection, or we could even damage the CPU, in the worst-case scenario.

Another interesting aspect of managing the CPU clock frequency is the so-called underclock. Leveraging the CPU clock frequency scaling feature, we can design and implement scaling policies to maximize the efficiency, according to CPU load and other aspects. We could, for instance, reduce the frequency when the device is idle or in sleep mode and push the clock to the maximum when the device is under heavy load, to enjoy the maximum effectiveness in every scenario. Pushing the CPU management even further, lots of smartphone CPUs come with a multicore architecture: you can completely deactivate a core if the current scenario doesn’t need it.

The key concept of underclocking a CPU is adding a new frequency below the lowest frequency provided by the manufacturer. Via software, we would be able to force the device to this frequency and save battery. This process is not riskless. We could create scenarios in which the device has a CPU frequency so low that it will result in an unresponsive device or even a frozen device. As for overclocking, these are unexplored territories and only caution, experience and luck will get you to a satisfying result.

An overview of the governors

Linux kernel manages CPU scaling using specific policies called governors. There are a few pre-build governors in the Linux kernel, already available via menuconfig, but you can also add custom-made governors, for your specific needs.

The following screenshot shows the menuconfig section of Google Nexus 6 for CPU scaling configuration:

As you can see, there are six prebuild governors. Naming conventions are quite useful and make names self-explanatory: for instance, the performance governor aims to keep the CPU always at maximum frequency, to achieve the highest performance at every time, sacrificing battery life.

The most popular governors on Android are definitely the ondemand and interactive governors: these are quite common in many Android-based device kernels. Our reference device, Google Nexus 6, uses interactive as the default governor.

As you would expect, Google disallows direct CPU frequency management, for security reasons. There is no quick way to select a specific frequency or a specific governor on Android. However, advanced users can satisfy their curiosity or their needs with a little effort.

Customizing the boot image

So far, you learned how to obtain the kernel source code, how to set up the system, how to configure the kernel, and how to create your first custom kernel image. The next step is about equipping your device with your new kernel. To achieve this, we are going to analyze the internal structure of the boot.img file used by every Android device.

Creating the boot image

A custom ROM comes with four .img files, necessary to create a working Android system. Two of them (system.img and data.img) are compressed images of a Linux compatible filesystem.

The remaining two files (boot.img and recovery.img) don’t contain a standard filesystem. Instead, they are custom image files, specific to Android. These images contain a 2KB header sector, the kernel core, compressed with gzip, a ramdisk, and an optional second stated loader.

Android provides further info about the internal structure of the image file in the boot.img.h file contained in the mkbootimg package in the AOSP source folder.

The following screenshot shows a snippet of the content of this file:

As you can see, the image contains a graphical representation of the boot.img structure. This ASCII art comes with a deeper explanation of sizes and pages.

To create a valid boot.img file, you need the kernel image you have just built and a ramdisk. A ramdisk is a tiny filesystem that is mounted into the system RAM during the boot time. A ramdisk provides a set of critically important files, needed for a successful boot sequence. For instance, it contains the init file that is in charge of launching all the services needed during the boot sequence.

There are two main ways to generate a boot image:

  • We could use the mkbootimg tool
  • We could use the Android build system

Using mkbootimg gives you a lot of freedom, but comes with a lot of complexity.

You would need a serious amount of command-line arguments to properly configure the generating system and create a working image. On the other hand, the Android build system comes with the whole set of configuration parameters already set and ready to go, with zero effort for us to create a working image. Just to give you a rough idea of the complexity of mkbootimg, the following screenshot shows an overview of the required parameters:

Playing with something so powerful is tempting, but, as you can see, the amount of possible wrong parameters passed to mkbootimg is large. As pragmatic developers, dealing with mkbootimg is not worth the risk at the moment. We want the job done, so we are going to use the Android build system to generate a valid boot image with no effort.

All that you need to do is export a new environment variable, pointing to the kernel image you have created just a few pages ago. With your trusted terminal emulator, launch:

$ export TARGET_PREBUILT_KERNEL=<kernel_src>/arch/arm/boot/zImage-dtb

Once you have set and exported the TARGET_PREBUILT_KERNEL environment variable, you can launch:

$ make bootimage

A brand new, fully customized, boot image will be created by the Android build system and will be placed in the following folder:

$ target/product/<device-name>/boot.img

With just a couple of commands, we have a brand new boot.img file, ready to be flashed. Using the Android build system to generate the boot image is the preferred way for all the Nexus devices and for all those devices, such as the UDOO, that are designed to be as close as possible to an official Google device.

For all those devices on the market that are compliant to this philosophy, things start to get tricky, but not impossible. Some manufactures take advantage of the Apache v2 license and don’t provide the whole Android source code. You could find yourself in a scenario where you only have the kernel source code and you won’t be able to leverage the Android build system to create your boot image or even understand how boot.img is actually structured.

In these scenarios, one possible approach could be to pull the boot.img from a working device, extract the content, replace the default kernel with your custom version, and recreate boot.img using mkbootimg: easier said than done.

Right now, we want to focus on the main scenario, dealing with a system that is not fighting us.

Upgrading the new boot image

Once you have your brand new, customized boot image, containing your customized kernel image, you only need to flash it to your device. We are working with Google devices or, at least, Google-compatible devices, so you will be able to use fastboot to flash your boot.img file to your device.

To be able to flash the image to the device, you need to put the device in fastboot mode, also known as bootloader mode.

Once your device is in fastboot mode, you can connect it via USB to your host computer. Fire up a terminal emulator and launch the command to upgrade the boot partition:

$ sudo fastboot flash boot boot.img

In a few seconds, fastboot will replace the content of the device boot partition with the content of your boot.img file. When the flashing process is successfully over, you can reboot your device with:

$ sudo fastboot reboot

The device will reboot using your new kernel and, thanks to the new USB TTL support that you added a few pages ago, you will be able to monitor the whole boot sequence with your terminal emulator.

Android boot sequence

To fully understand all Android internals, we are going to learn how the whole boot sequence works: from the power-on to the actual Android system boot. The Android boot sequence is similar to any other embedded system based on Linux: in a very abstract way, after the power-on, the system initializes the hardware, loads the kernel, and finally the Android framework. Any Linux-based system undergoes a similar process during its boot sequence: your Ubuntu computer or even your home DSL router.

In the next sections, we are going to dive deeper in to these steps to fully comprehend the operating system we love so much.

Internal ROM – bios

When you press the power button on your device, the system loads a tiny amount of code, stored inside a ROM memory. You can think about this as an equivalent of the BIOS software you have in your PC. This software is in charge of setting up all the parameters for CPU clock and running the RAM memory check. After this, the system loads the bootloader into memory and launches it.

An overview of bootloader

So far, the bootloader has been loaded into the RAM memory and started. The bootloader is in charge of loading the system kernel into the RAM memory and launching it, to continue the boot sequence.

The most popular bootloader software for Android devices is U-Boot, the Universal Bootloader. U-Boot is widely used in all kinds of embedded systems: DSL routers, smart TVs, infotainment systems, for example. U-boot is open source software and its flexibility to be customized for any device is definitely one of the reasons for its popularity.

U-boot’s main task is to read the kernel image from the boot partition, load it into the RAM memory, and run it. From this moment on, the kernel is in charge of finishing the boot sequence.

You could think about U-boot on Android like GRUB on your Ubuntu system: it reads the kernel image, decompresses it, loads it into the RAM memory, and executes it. The following diagram gives you a graphical representation of the whole boot sequence as on an embedded Linux system, an Android system, and a Linux PC:

The kernel

After the bootloader loads the kernel, the kernel’s first task is to initialize the hardware. With all the necessary hardware properly set up, the kernel mounts the ramdisk from boot.img and launches init.

The Init process

In a standard Linux system, the init process takes care of starting all the core services needed to boot the system. The final goal is to complete the boot sequence and start the graphical interface or the command line to make the system available to the user. This whole process is based on a specific sequence of system scripts, executed in a rigorous order to assure system integrity and proper configuration.

Android follows the same philosophy, but it acts in a different way. In a standard Android system, the ramdisk, contained in the boot.img, provides the init script and all the scripts necessary for the boot.

The Android init process consists of two main files:

  • init.rc
  • init.${ro.hardware}.rc

The init.rc file is the first initialization script of the system. It takes care of initializing those aspects that are common to all Android systems. The second file is very hardware specific. As you can guess, ${ro.hardware} is a placeholder for the reference of a particular hardware where the boot sequence is happening.

For instance, ${ro.hardware} is replaced with goldfinsh in the emulator boot configuration.

In a standard Linux system, the init sequence executes a set of bash scripts. These bash scripts start a set of system services. Bash scripting is a common solution for a lot of Linux systems, because it is very standardized and quite popular.

Android systems use a different language to deal with the initialization sequence: Android Init Language.

The Android init language

The Android team chose to not use Bash for Android init scripts, but to create its own language to perform configurations and services launches.

The Android Init Language is based on five classes of statements:

  • Actions
  • Commands
  • Services
  • Options
  • Imports

Every statement is line-oriented and is based on specific tokens, separated by white spaces. Comment lines start with a # symbol.

Actions

An Action is a sequence of commands bound to a specific trigger that’s used to execute the particular action at a specific moment. When the desired event happens, the Action is placed in an execution queue, ready to be performed.

This snippet shows an example of an Action statement:

on <trigger> [&& <trigger>]*
  <command>
  <command>
  <command>

Actions have unique names. If a second Action is created with the same name in the same file, its set of commands is added to the first Action commands, set and executed as a single action.

Services

Services are programs that the init sequence will execute during the boot. These services can also be monitored and restarted if it’s mandatory they stay up. The following snippet shows an example of a service statement:

service <name> <pathname> [ <argument> ]*
  <option>
  <option>
  ...

Services have unique names. If in the same file, a service with a nonunique name exists, only the first one is evaluated as valid; the second one is ignored and the developer is notified with an error message.

Options

Options statements are coupled with services. They are meant to influence how and when init manages a specific service.

Android provides quite an amount of possible options statements:

  • critical: This specifies a device-critical service. The service will be constantly monitored and if it dies more than four times in four minutes, the device will be rebooted in Recovery Mode.
  • disabled: This service will be in a default stopped state. init won’t launch it. A disabled service can only be launched manually, specifying it by name.
  • setenv <name> <value>: This sets an environment variable using name and value.
  • socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]: This command creates a Unix socket, with a specified name, (/dev/socket/<name>) and provides its file descriptor the specified service. <type> specifies the type of socket: dgram, stream, or seqpacket. Default <user> and <group> are 0. <seclabel> specifies the SELinx security context for the created socket.
  • user <username>: This changes the username before the service is executed. The default username is root.
  • group <groupname> [ <groupname> ]*: This changes the group name before the service is executed.
  • seclabel <seclabel>: This changes the SELinux level before launching the service.
  • oneshot: This disables the service monitoring and the service won’t be restarted when it terminates.
  • class <name>: This specifies a service class. Classes of services can be launched or stopped at the same time. A service with an unspecified
  • class value will be associated to the default class.
  • onrestart: This executes a command when the service is restarted.
  • writepid <file…>: When a services forks, this option will write the process ID (PID) in a specified file.

Triggers

Triggers specify a condition that has to be satisfied to execute a particular action. They can be event triggersor property triggers. Event triggers can be fired by the trigger command or by the QueueEventTrigger() function. The example event triggers are boot and late-init. Property triggers can be fired when an observed property changes value. Every Action can have multiple Property triggers, but only one Event trigger; refer to the following code for instance:

on boot && property_a=b

This Action will be executed when the boot event is triggered and the property a is equal to b.

Commands

The Command statement specifies a command that can be executed during the boot sequence, placing it in the init.rc file. Most of these commands are common Linux system commands. The list is quite extensive. Let’s look at them in detail:

  • bootchart_init: This starts bootchart if it is properly configured.
  • Bootchart is a performance monitor and can provide insights about the boot performance of a device.
  • chmod <octal-mode-permissions> <filename>: This changes file permissions.
  • chown <owner> <group> <filename>: This changes the owner and the group for the specified file.
  • class_start <serviceclass>: This starts a service specified by its class name.
  • class_stop <serviceclass>: This stops and disables a service specified by its class name.
  • class_reset <serviceclass>: This stops a service specified by its class name. It doesn’t disable the service.
  • copy <src> <dst>: This copies a source file to a new destination file.
  • domainname <name>: This sets the domain name.
  • enable <servicename>: This starts a service by its name. If the service is already queued to be started, then it starts the service immediately.
  • exec [<seclabel>[<user>[<group> ]* ]] — <command> [ <argument> ]*: This forks and executes the specified command. The execution is blocking: no other command can be executed in the meantime.
  • export <name> <value>: This sets and exports an environment variable.
  • hostname <name>: This sets the hostname.
  • ifup <interface>: This enables the specified network interface.
  • insmod <path>: This loads the specified kernel module.
  • load_all_props: This loads all the system properties.
  • load_persist_props: This loads the persistent properties, after the successful decryption of the /data partition.
  • loglevel <level>: This sets the kernel log level.
  • mkdir <path> [mode] [owner] [group]: This creates a folder with the specified name, permissions, owner, and group. The defaults are 755 as permissions, and root as owner and group.
  • mount_all <fstab>: This mounts all the partitions in the fstab file.
  • mount <type> <device> <dir> [ <flag> ]* [<options>]: This mounts a specific device in a specific folder. A few mount flags are available: rw, ro, remount, noatime, and all the common Linux mount flags.
  • powerctl: This is used to react to changes of the sys.powerctl system parameter, critically important for the implementation of the reboot routing.
  • restart <service>: This restarts the specified service.
  • rm <filename>: This deletes the specified file.
  • rmdir <foldername>: This deletes the specified folder.
  • setpropr <name> <value>: This sets the system property with the specified name with the specified value.
  • start <service>: This starts a service.
  • stop <service>: This stops a service.
  • swapon_all <fstab>: This enables the swap partitions specified in the fstab file.
  • symlink <target> <path>: This creates a symbolic link from the target file to the destination path.
  • sysclktz <mins_west_of_gtm>: This sets the system clock.
  • trigger <event>: This programmatically triggers the specified event.
  • wait <filename > [ <timeout> ]: This monitors a path for a file to appear. A timeout can be specified. If not, the default timeout value is 5 seconds.
  • write <filename> <content>: This writes the specified content to the specified file. If the file doesn’t exist, it creates the file. If the file already exists, it won’t append the content, but it will override the whole file.

Imports

Imports specify all the external files that are needed in the current file and imports them:

import <path>

The previous snippet is an example of how the current init script can be extended, importing an external init script. path can be a single file or even a folder. In case path is a folder, all the files that exists in the first level of the specified folder will be imported. The command doesn’t act recursively on folders: nested folders must be imported programmatically one by one.

Summary

In this article, you learned how to obtain the Linux kernel for your device, how to set up your host PC to properly build your custom kernel, how to add new features to the kernel, build it, package it, and flash it to your device.

You learned how the Android boot sequence works and how to manipulate the init scripts to customize the boot sequence.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here