Наши с вами телефоны включались бессчётное количество раз. Возможно, вы прямо сейчас запустили смартфон, чтобы прочитать эту статью. Ну а для тех, кто занимается разработкой, процесс включения устройств — обычная часть работы, которая повторяется десятки раз в день.
Что же на самом деле происходит в момент загрузки? Какие этапы преодолевает система от простого нажатия кнопки включения до появления интерфейса? И как устройство готовится к полноценной работе?
Вместе с Android-разработчиком Артёмом разберёмся, что скрывается под капотом Android и проследим процесс его запуска. Не пропустим ни шагу!

Для начала посмотрим, что эта система из себя представляет.
ОС Android — это операционная система, основанная на ядре Linux, что определяет её архитектуру и основные принципы. Как и в Linux, в Android принято выделять три условных уровня — пользовательское пространство (User Space), пространство ядра (Kernel Space) и аппаратное обеспечение (Hardware).
Пользовательское пространство — это область памяти, где размещаются приложения и программы верхнего уровня. В общем, то, с чем сталкивается рядовой пользователь.
К аппаратному обеспечению относятся процессор, память и прочее. Эти два слоя друг о друге не знают и напрямую не взаимодействуют. За счёт этого обеспечивается абстрагирование работы с оборудованием для верхнеуровневых приложений, модульность, возможность запуска системы на разных устройствах с разными характеристиками.
Всё это достигается благодаря пространству ядра, которое выступает в роли посредника между аппаратной частью и пользовательским пространством.
Но такой «посреднический» подход делает невозможным «прямой» запуск системы — без подготовительных этапов. Нужно, чтобы оборудование было проинициализировано и подготовлено, а ядро получило информацию о нём, чтобы впоследствии с ним же работать.
Всё это ведёт нас к начальному этапу загрузки, который возьмёт на себя эти задачи.
Шаг 1. Boot ROM
Начальная инициализация и загрузка начинается с выполнения кода загрузочного ПЗУ — Boot ROM.
Это небольшой фрагмент памяти в процессорном чипе, который содержит тот самый инициализационный код. Задача этого кода — подготовить «железо» устройства к работе и выгрузить код во внешнюю оперативную память, когда все закончится. Этот код и загрузит нам ОС устройства.
Подготовка аппаратной части заключается в сбросе и запуске процессора, системной шины и внутренней оперативной памяти. А ещё — в выделении необходимого места для последующего запуска системы.
После этого из выделенного для каждого устройства места — это местоположение определяется типом используемой в телефоне системы на кристалле (SoC) — во внешнюю оперативную память (DRAM) скопируется загрузчик. Он же продолжит процесс запуска.
Этот загрузчик называется Bootloader.
Шаг 2. Bootloader
Bootloader — это небольшая программа, записанная во встроенную память. Она завершает настройку оборудования, запуск ядра Linux и передачу управления загрузки системы.
У встроенной памяти ROM, инициализированной Boot ROM, небольшой объём. Обычно он не превышает нескольких сотен килобайт и используется для выполнения начальных этапов загрузки.
Ядро Linux, даже в сжатом виде, занимает несколько мегабайт. При распаковке он значительно увеличивается, достигая десятков мегабайт. Получается, что объёма ROM недостаточно, поэтому для загрузки и работы ядра используется внешняя оперативная память.
Android, в частности, её ядро Linux, полагается на уже инициализированные устройства для корректного старта. Поэтому Bootloader должен подготовить их для ядра — чтобы оно могло сразу воспользоваться ими.
Это проходит в два этапа:
Начальная загрузка программы (Initial Program Load).
Настройка внешней оперативной памяти и вторичная загрузка программы.
Вторичная загрузка программы (Second Program Load).
Это загрузка драйверов дисплея, сенсоров, а также настройка файловой системы, виртуальной памяти (об устройстве памяти в Android мы поговорим дальше), сети и прочих систем.
Так, после того, как загрузчик найдёт ядро Linux, оно копируется во внешнюю оперативную память, после чего распаковывается и запускается.
Шаг 3. Ядро (Kernel)
Ядро в Linux можно сравнить с дирижёром в оркестре: оно координирует работу всех компонентов системы, обеспечивает взаимодействие между программами и любым аппаратным обеспечением. Оно управляет процессами, распределяет память, обрабатывает действия программ пользовательского пространства и контролирует доступ к аппаратной части. В общем, создаёт мост между «железом» и пространством пользователя.
Ядро — своего рода интерфейс между аппаратной частью и процессами. Без него каждому приложению приходилось бы напрямую взаимодействовать с памятью, процессором и другими системами. Хаос в этом случае был бы обеспечен.
Хотя у ядра Android и Linux есть ряд различий, в контексте запуска системы ядро Android запускается аналогично ядру Linux и действует точно так же.
До запуска основного процесса, происходит предварительная настройка: монтируется файловая система, загружаются драйверы.
Ядро Linux отвечает за управление ресурсами и аппаратным обеспечением, но не запускает приложения и пользовательские сервисы напрямую. Для этого нужно перейти в пространство пользователя.
Чтобы перейти из пространства ядра в пространство пользователя после подготовительных действий, ядро ищет и запускает корневой — самый главный — процесс в системе — init(). Именно он управляет запуском системных сервисов и приложений.
Шаг 4. init
Процесс init служит «архитектором» пользовательской среды: подготавливая ее, он запускает системные сервисы и демоны, а затем передает управление рабочим процессам и приложениям.
Прежде всего init готовит файловую систему к работе, монтируя разделы /sys, /dev, /proc. Они нужны для виртуальной памяти.
Виртуальная память — это механизм, который нужен, чтобы память устройства использовалась наиболее эффективно и безопасно при работе множества процессов. Для этого используется механизм пагинации (Paging). Пространство виртуальной памяти разбивается на множество блоков-страниц и каждому процессу выдаётся нужное число страниц, благодаря чему не возникает конфликтов.
Файловая система готова для запуска системных служб и конфигурации. Теперь init-процессу нужно получить информацию о настройке параметров ядра и запуске служб. Она лежит в файле init.rc, который находится в system/core/rootdir. В нём расположен список команд, которые нужно выполнить.
В процессе будут запущены:
Service Manager для управления системными службами через механизм Binder;
Media Server для воспроизведения аудио и видео, работы с камерой и другие сервисы.
И, наконец, в самом конце init доходит до запуска службы Zygote.
Шаг 5. Zygote
Zygote играет важную роль в запуске приложений и создании среды их исполнения. Название Zygote (зигота) довольно символично — вспомним курс школьной биологии. Зигота в биологии — основа для создания организма. А процесс Zygote — основа для создания процессов приложений в Android.
Zygote запускается на ранних этапах работы системы с помощью приложения /system/bin/app_process. Оно инициализирует виртуальную машину ART или Dalvik (в зависимости от версии Android), загружая её код из библиотеки /system/lib/libandroid_runtime.so.
Но зачем?
Обычно в Java-программе для каждого приложения создаётся отдельный экземпляр виртуальной машины JVM. Он загружает и инициализирует все классы и библиотеки с нуля.
Но для Android такой подход применить нельзя. Если бы Android создавал отдельную виртуальную машину для каждого приложения, это привело бы:
к высокому потреблению памяти — многие виртуальные машины дублировали бы одни и те же библиотеки и классы;
к длительному запуску — процесс загрузки и инициализации классов требует значительных затрат времени, а эти операции повторялись бы для каждого приложения.
Эту проблему и решает Zygote:
она заранее загружает и инициализирует основные классы и ресурсы Android SDK, которые понадобятся приложениям. Эти классы впоследствии будут доступны всем приложениям;
когда будет запускаться новое приложение, новая виртуальная машина не создастся. Вместо этого, для создания нового процесса для запускаемого приложения Zygote скопирует в него своё состояние с помощью системного вызова fork(). Созданный таким образом процесс наследует загруженные классы и ресурсы;
классы, загруженные Zygote, хранятся в памяти в виде read-only, что позволяет нескольким процессам одновременно обращаться к ним без дублирования.
Таким образом, с помощью Zygote формируется среда исполнения Java-приложений путем загрузки всех Java-классов для фреймворка.
Шаг 6. System Server
Первый процесс, создаваемый путем fork()
от Zygote, — процесс, в котором запускается System Server.
Это, по сути, завершающий этап загрузки системы. Именно служба System Server запускает большинство высокоуровневых сервисов, каждый из которых отвечает за часть функциональности Android. И без которых работа приложений была бы невозможна.
Запускается Activity Manager. Он отвечает за запуск, остановку и завершение приложений и обеспечивает взаимодействие между ними.
После его запуска отправится широковещательный интент Intent.CATEGORY_HOME. Он поможет определить Launcher-приложение, которое отвечает за отображение рабочего стола.
Launcher-приложение – это специальное приложение, у которого в манифесте есть соответствующий фильтр.
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name = "android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
Когда подходящее приложение обнаружено, Zygote создаёт новый процесс и запускает в нём Launcher-приложение.
В результате на экране появляется рабочий стол. Параллельно с этим System Server запускает службу Status Bar, которая отвечает за отображение статусной строки, и другие службы.
Запускается Window Manager. Он управляет всеми окнами на экране. Под окнами в данном случае понимаются базовые компоненты, используемые для отображения интерфейса приложения (или системы).
Так, когда в активности вызывается метод setContentView(), макет этой активности прикрепляется к соответствующему ей окну. Его дальнейшим размещением и отображением занимается Window Manager.
Если на устройстве открыто несколько приложений, Window Manager отображает окно активного приложения поверх других. Осуществляется это за счет управлением z-упорядоченной системы окон.
Z в данном случае означает глубину — как в трехмерной системе координат. Z-список определяет, какие окна располагаются поверх других на экране. Насколько низко или высоко будет расположено окно, определяется порядком его добавления, а также наличием или отсутствием фокуса.
Power Manager управляет энергопотреблением устройства. Помимо работы с яркостью экрана, ее автоматической регулировки, контроля за потреблением энергии Wi-Fi, Bluetooth и геолокации, Power Manager работает с wavelock-ами
И вот, когда основные системные службы запущены, базовые функции системы готовы.
Тогда система отправляет широковещательный интент Intent.ACTION_BOOT_COMPLETED. Это сообщение сигнализирует приложениям и системным компонентам, которые подписаны на него, что загрузка завершена.
Обрабатывать его может и пользовательское приложение, для этого достаточно разрешения:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
И интент-фильтр такого вида:
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
На этом процесс загрузки ОС Android можно считать завершенным.
Вот такой, поистине колоссальный, объём работы скрывается за привычным интерфейсом, который мы видим каждый день на экране Android-устройств.
Больше полезного про Android — в Telegram-канале Surf Mobile Team
Кейсы и лучшие практики в области системной и бизнес-аналитики, новости, вакансии и стажировки Surf. Присоединяйтесь!