How Android Works, Part 1


    In this series of articles, I’ll talk about the internal device of Android - about the boot process, about the contents of the file system, about Binder and Android Runtime, about what the applications consist of, how they are installed, run, work and interact with each other, about the Android Framework, and how Android is secured.


    Series Articles:





    Some facts


    Android is the most popular operating system and application platform with more than two billion active users. It uses completely different devices, from the “Internet of things” and smart watches to TVs, laptops and cars, but most often Android is used on smartphones and tablets.


    Android is a free and open source project. Most source code (which can be found at https://source.android.com ) is distributed under the free Apache 2.0 license.


    Android Inc. was founded in 2003 and in 2005 was purchased by Google. The Android public beta was released in 2007, and the first stable version  was released in 2008, since then major releases are released approximately once a year. The latest stable version of Android at the time of writing is 7.1.2 Nougat.



    Android is Linux


    There was a lot of controversy about this wording, so I’ll immediately explain what I mean by this phrase: Android is based on the Linux kernel, but differs significantly from most other Linux systems.


    Among the original Android development team was Robert Love, one of the most famous developers of the Linux kernel, and now Google remains one of the most active contributors to the kernel, so it is not surprising that Android is built on Linux.


    Like other Linux systems, the Linux kernel provides such low-level things as memory management, data protection, support for multiprocessing and multithreading. But - with a few exceptions - you won’t find other familiar GNU / Linux-system components in Android: there is nothing from the GNU project, X.Org is not used, nor even systemd. All these components are replaced by analogues that are more suitable for use in conditions of limited memory, low processor speed and minimal power consumption - thus, Android is more like an embedded Linux system than GNU / Linux.


    Another reason that Android does not use GNU software is the well-known “no GPL in userspace” policy:


    We are sometimes asked why Apache Software License 2.0 is the preferred license for Android. For userspace (that is, non-kernel) software, we do in fact prefer ASL 2.0 (and similar licenses like BSD, MIT, etc.) over other licenses such as LGPL.

    Android is about freedom and choice. The purpose of Android is promote openness in the mobile world, and we don't believe it's possible to predict or dictate all the uses to which people will want to put our software. So, while we encourage everyone to make devices that are open and modifiable, we don't believe it is our place to force them to do so. Using LGPL libraries would often force them to do just that.

    The Linux kernel itself in Android is also slightly modified: several small components were added, including ashmem (anonymous shared memory), Binder driver (part of the large and important Binder framework, which I will discuss below), wakelocks (sleep management) and low memory killer. Initially, they were patches to the kernel, but their code was pretty quickly added back to the upstream kernel. However, you will not find them in “regular Linux”: most other distributions disable these components during assembly.


    Android does not use the GNU C library ( glibc ) as the libc (standard C language library ), but its own minimalistic implementation called bionic , optimized for embedded systems - it is much faster, less and less memory intensive than glibc, which has overgrown with many layers of compatibility.


    Android has a command line shell and many standard Unix-like command / program systems. In embedded systems, the Busybox package , which implements the functionality of many commands in a single executable file, is usually used for this ; Android uses its counterpart called Toybox . As with “regular” Linux distributions (and unlike embedded systems), the main way to interact with the system is through a graphical interface rather than a command line. Nevertheless, getting to the command line is very simple - just run the terminal emulator application. By default, it is usually not installed, but it is easy, for example, to download from the Play Store ( Terminal Emulator for Android , Material Terminal ,Termux ). In many "advanced" Android distributions - such as LineageOS (formerly CyanogenMod) - the terminal emulator is preinstalled.


    Android terminal emulator


    The second option is to connect to the Android device from the computer via Android Debug Bridge (adb). This is very similar to connecting via SSH:


    user@desktop-linux$ adb shell
    android$ uname
    Linux

    Of the other familiar components, Android uses the FreeType library (for displaying text), the graphical APIs of OpenGL ES , EGL and Vulkan , as well as the lightweight SQLite DBMS .


    In addition, the WebKit browser engine was previously used to implement WebView , but starting with version 7.0 , the installed Chrome application is used instead (or another; the list of applications that are allowed to act as a WebView provider is configured at the stage of system compilation). Inside, Chrome also uses the WebKit-based Blink engine , but unlike the system library, Chrome is updated through the Play Store - thus, all applications using WebView will automatically receive the latest improvements and bug fixes.


    Android technology stack


    It's all about apps


    As you can easily see, using Android is fundamentally different from using “regular Linux” - you don’t need to open and close applications, you just switch between them, as if all applications were always running. Indeed, one of the unique features of Android is that applications do not directly control the process in which they are running. Let's talk about this in more detail.


    The basic unit in Unix-like systems is the process. And low-level system services, and individual commands in the shell, and graphics applications are processes. In most cases, the process is a black box for the rest of the system - other components of the system do not know and do not care about its state. The process starts with a function call main()(in fact _start), and then implements some logic of its own, interacting with the rest of the system through system calls and the simplest interprocess communication (IPC).


    Since Android is also Unix-like, all this is also true for it, but while the low-level parts - at the Unix level - operate with the concept of a process, at a higher level - the Android Framework level - the main unit is the application . The application is not a black box: it consists of individual components that are well known to the rest of the system.


    Android applications do not have a function main(), there is no one entry point. In general, Android maximizes the concept of an application running both from the user and from the developer. Of course, the application process needs to be started and stopped, but Android does this automatically (I will discuss this in more detail in the following articles). The developer is invited to implement several separate components, each of which has its own life cycle.


    In Android, however, we explicitly decided we were not going to have a main () function, because we needed to give the platform more control over how an app runs. In particular, we wanted to build a system where the user never needed to think about starting and stopping apps, but rather the system took care of this for them ... so the system had to have some more information about what is going on inside of each app, and be able to launch apps in various well-defined ways whenever it is needed even if it is currentlynt running.

    To implement such a system, applications need to be able to communicate with each other and with system services - in other words, they need a very advanced and fast IPC mechanism.


    This mechanism is a binder.


    Binder


    Binder is a platform for fast, convenient and object-oriented interprocess communication.


    Binder development started at Be Inc. (for BeOS), then it was ported to Linux and open. The main developer of Binder, Dianne Hackborn, has been and remains one of the main developers of Android. During development, Android Binder has been completely rewritten.


    Binder does not work on top of System V IPC (which is not even supported by bionic), but uses its own small kernel module, which interacts with userspace through system calls (mainly ioctl) on a “virtual device” /dev/binder. On the part of userspace, low-level work with Binder, including interaction with /dev/binderand marshalling / unmarshalling data, is implemented in the libbinder library .


    The low-level parts of Binder operate in terms of objects that can be transferred between processes. In this case, reference-counting is used to automatically release unused shared resources and notification of the completion of the remote process (link-to-death) to free resources within the process.


    The high-level parts of Binder work in terms of interfaces, services, and proxy objects. The description of the interface provided by the program to other programs is written in a special language AIDL (Android Interface Definition Language), which looks very similar to the declaration of interfaces in Java. According to this description, a real Java interface is automatically generated, which can then be used by both clients and the service itself. In addition, .aidltwo special classes are automatically generated by  file: Proxy (for use on the client side) and Stub (on the service side) that implement this interface.


    For Java code in the client process, the proxy object looks like a regular Java object that implements our interface, and this code can simply call its methods. At the same time, the generated implementation of the proxy object automatically serializes the arguments passed, communicates with the service process via libbinder, deserializes the result of the call passed back, and returns it from the Java method.


    Stub works the other way around: it accepts incoming calls through libbinder, deserializes the arguments, calls the abstract implementation of the method, serializes the return value and passes it to the client process. Accordingly, to implement the service, it is enough for the programmer to implement abstract methods in the class inherited from Stub.


    Такая реализация Binder на уровне Java позволяет большинству кода использовать прокси-объект, вообще не задумываясь о том, что его функциональность реализована в другом процессе. Для обеспечения полной прозрачности Binder поддерживает вложенные и рекурсивные межпроцессные вызовы. Более того, использование Binder со стороны клиента выглядит совершенно одинаково, независимо от того, расположена ли реализация используемого сервиса в том же или в отдельном процессе.


    Для того, чтобы разные процессы могли «найти» сервисы друг друга, в Android есть специальный сервис ServiceManager, который хранит, регистрирует и выдаёт токены всех остальных сервисов.


    Binder широко используется в Android для реализации системных сервисов (например, пакетного менеджера и буфера обмена), но детали этого скрыты от разработчика приложений высокоуровневыми классами в Android Framework, такими как Activity, Intent и Context. Приложения могут также использовать Binder для предоставления друг другу собственных сервисов  —  например, приложение Google Play Services вообще не имеет собственного графического интерфейса для пользователя, но предоставляет разработчикам других приложений возможность пользоваться сервисами Google Play.


    Подробнее про Binder можно узнать по этим ссылкам:



    In the next article, I will talk about some of the ideas that build the higher-level parts of Android, several of its predecessors, and basic security mechanisms.


    Also popular now: