- PVSM.RU - https://www.pvsm.ru -

Обзор особенностей ядра Андроида

“А я… карбюратор промываю!”
Анекдот

Введение

В детском садике мы с единомышленниками препарировали кузнечиков в надежде разобраться в их строении. В школе распаивали радиоприёмник “Россия”. В институте дошла очередь до автомобилей, гайки которых были многократно переставлены. Интересы поменялись, но желание “разбирать” иногда просыпается, и сегодня оно направлено на Андроид.

Сколько раз вас выручало наличие исходников Андроида? Меня — уже не счесть. Андроид — открытый проект, но, к сожалению, у нас есть возможность только читать; править код Андроида, не будучи сотрудником Google, практически невозможно. Погрустим над этим моментом и загрузим репозиторий. Как это сделать, отлично описано на официальном сайте [1].

Обзор особенностей ядра Андроида

Общая архитектура

Архитектуру Андроида можно схематично изобразить так:

Обзор особенностей ядра Андроида

Оригинальная [2] схема не содержит информации об особенностях ядра и не акцентирует внимание на Binder-е и системных сервисах. А ведь Binder является “клеем”, связывающим все компоненты системы.

Как правило, в книгах описывается верхний левый синий прямоугольник, то есть API, которое доступно разработчику прикладных приложений. Нас же интересует всё, что ниже. Сегодня мы рассмотрим только ядро.

Ядро

Ядро — центральная часть любого дистрибутива, называемого “Линукс”. Несмотря на доступность “чистого [3]” ядра, многие разработчики (Ubuntu, Fedora, SuSe и т.д.) добавляют к нему свои патчи перед включением в дистрибутив. Андроид идёт той же дорогой, только ценой потери прямой совместимости: на “чистом” ядре он не заведётся. В настоящее время есть намерения включить “андроидизмы” в основную версию ядра, в 2011 году Линус Торвальдс давал [4] на этот процесс 4-5 лет. Успех уже достигнут в рамках включения [5] механизма wakelocks в версии ядра 3.5.

Рассмотрим “андроидизмы” более подробно.

Wakelocks

История данного механизма эпична, потянет на сборник статей “Путь wakelock-ов в Линукс”: их обсуждение заняло порядка 2000 писем в рассылке LKML [6].

Настольные компьютеры и ноутбуки имеют устоявшуюся систему энергорежимов (у x86 процессоров таковых несколько): компьютер работает “на полных оборотах”, когда что-то делается, и уходит в энергоэффективный режим, когда система простаивает. Уход в “спящий” режим происходит либо после довольно длительного бездействия, либо вручную, например, при закрытии крышки ноутбука.

На телефонах требовался другой механизм: основное состояние системы — “спячка”, выход из него осуществляется только в случаях необходимости. Таким образом, система может уснуть, даже если какое-то приложение проявляет активность. В Андроиде был реализован механизм wakelock-ов: если приложение (или драйвер) выполняет что-то важное, что должно дойти до логического завершения, оно “захватывает” wakelock, предотвращая засыпание устройства.

Попытки портирования механизма wakelock-ов в ядро вызвали сопротивление многих разработчиков. Программисты Андроида решали конкретную проблему, решением которой стал определённый механизм. Условия задачи были весьма узки. Целевая платформа — ARM, поэтому использовались её особенности: ARM-процессоры изначально предполагают частую смену режимов работы “сна” и “бодрствования”, в отличие от x86. В Андроиде приложения общаются с системой управления питанием через PowerManager [7], а что делать клиентским Линукс-приложениям?

Разработчики Андроида даже не пытались найти общее решение “на будущее”, которое потом без проблем бы вливалось в основное ядро, не консультировались по этой проблеме с сообществом ядра Линукс. Можно ли их за это винить? Несмотря на все проблемы и обсуждения, как упоминалось выше, в ядре появилось API с идентичной функциональностью autosleep [8].

Программистам приложений под Андроид довольно редко приходится сталкиваться с wakelock-ами, так как платформа и драйверы обрабатывают возложенные на них обязательства с учётом “спящего” режима. Тем не менее, вмешаться в этот процесс поможет знакомый PowerManager [9]. Кстати, автору приходит в голову только один сценарий: не дать телефону уснуть при запуске сервиса из BroadcastReceiver-а, что решается вспомогательным классом из Android Support Library WakefulBroadcastReceiver [10].

Low Memory Killer

В стандартном ядре Линукса есть Out of Memory Killer [11], который на основании параметра badness определяет убиваемый процесс:

badness_for_task = total_vm_for_task / (sqrt(cpu_time_in_seconds) *
sqrt(sqrt(cpu_time_in_minutes)))

Таким образом, чем больше процесс потребляет памяти и чем меньше живёт, тем меньше ему повезёт.

Все программисты, читавшие [12] документацию или проходившие собеседования, знают, что, во-первых, процесс может быть “убит” и при наличии свободных ресурсов, во-вторых, кандидат на вытеснение выбирается по другим критериям: наличие “живых” Андроид-компонент, видимость пользователю и так далее.

Механизм довольно простой: каждому процессу присваивается приоритет от -17 до 16, при этом чем выше приоритет, тем выше вероятность убивания процесса, и, в зависимости от количества свободной памяти, выбирается приоритет, начиная с которого процессы будут завершены. Приоритеты описаны в ProcessList.java [13]. Занимательно, что приоритет приложения домашнего экрана HOME_APP_ADJ довольно высок, а я-то думал: почему он постоянно перезапускается?

Массивы mOomAdj и mOomMinFreeLow/mOomMinFreeHigh как раз задают правила “когда что очистить”:

private final int[] mOomAdj = new int[] {FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ, BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ};

private final long[] mOomMinFreeHigh = new long[] {49152, 61440, 73728,86016, 98304, 122880};

Таким образом, приложение домашнего экрана вытесняется при остатке свободной памяти в 73728 КБ на телефоне с экраном 1280x800 и ОЗУ в 700 МБ.
ProcessList передаёт соответствующие значения в ядро [14], что можно видеть в его методе updateOomLevels.

Приоритеты процессам выставляет Activity Manager Service [15], один из многих системных сервисов, общаться с которым можно через Activity Manager [16].

Binder

Binder [17], наряду с другими решениями (Files, Sigmals, Sockets, Pipes, Semaphores, Shared Memory и т.д.), решает задачу межпроцессного взаимодействия. Ноги у данного решения растут из проекта OpenBinder [18], разработчики которого в своё время перешли в команду Андроида.

Boinic [19] (реализация libc) не использует System V IPC [20], так как в андроидовском окружении стандартные средства приведут к утечкам ресурсов.

Особенности:

  1. Управление потоками (мы все помним [21], что сервис, поддерживающий AIDL, должен работать в многопоточном окружении). Максимальное число потоков — 15 (ProcessState.c [22], метод open_driver), поэтому не стоит блокировать Binder-потоки в большом количестве без лишней необходимости.
  2. Механизм информирования о смерти процесса, держащего объект Binder “Link to Death [23]”. Например, через него Window Manager [24] узнаёт о смерти приложения и удаляет связанные с ним окна. Также LocationManager [25] при смерти всех своих слушателей перестаёт опрашивать GPS-приёмник. Lowmemorykiller доволен. :)
  3. 2 режима вызова: блокирующий и неблокирующий (oneway). В первом случае вызывающий поток блокируется и ждёт отработки метода в потоке процесса-обработчика. Программисты просто вызывают методы через точку, взаимодействие потоков берёт на себя платформа.
  4. Передача UID и PID для безопасности. Через них системные сервисы определяют [26], есть ли у вызывающего процесса права совершать запрашиваемые действия.
  5. Для Java-программистов — средства создания Proxy и Stub-ов для конвертирования вызовов Java-методов в транзакции Binder-а.

Рассмотрим как это работает на примере LocationManager-а.

Обзор особенностей ядра Андроида

Когда мы хотим получить информацию о GPS, происходит следующее:

  1. Наше приложение вызывает соответствующий метод у LocationManager-а.
  2. LocationManager делегирует вызов прокси-объекту, преобразующему Java-методы и объекты в Binder-транзакцию (прокси-объектом у LocationManager-а [27] является mService).
  3. Транзакция посылается драйверу ядра, который перенаправляет её LocationManagerService-у, отнаследованному от .LocationManager.Stub.
  4. .LocationManager.Stub делает обратные действия: разворачивает транзакцию в вызов Java-метода.
  5. .LocationManagerService обрабатывает запрос (используя, например, GPS-драйвер).
  6. Stub-объект пакует ответ в транзакцию, и процесс идёт в обратном направлении.
  7. Драйвер пересылает ответ обратно.
  8. Прокси-объект распаковывает результат вызова метода в Java-объекты.

Как видим, за вызовом методов системных сервисов скрывается довольно большая логика.

Ashmem

Anonymous Shared Memory (ashmem) — механизм разделяемой памяти. В Линуксе, как правило, данный механизм реализован через POSIX SHM [28]. Разработчики Андроида сочли его недостаточно защищённым, что могло сыграть на руку вредоносному ПО. Особенностями ashmem-а являются счётчик ссылок, при обнулении которого разделяемая память может быть освобождена (например, память освобождается при завершении всех процессов, использующих её), и сокращение разделяемого региона при нехватке памяти в системе.

Ярким примером использования ashmem-а является процесс zygote, в котором загружается стартовая версия Dalvik VM с загруженными базовыми классами и ресурсами, а остальные приложения просто ссылаются на эту память.

Binder имеет ограничение на размер транзакции в 1МБ (иначе будет выброшено исключение TransactionTooLargeException [29]). Если нам надо передать из одного процесса в другой большой объём данных, можно как раз воспользоваться Ashmem-ом: создать MemoryFile [30] и передать дескриптор файла в другой процесс.

Logger

Обычные дистрибутивы, как правило, используют две системы логирования: лог ядра, доступный через команду dmesg, и системные логи, располагающиеся обычно в директории /var/log.

Система Андроида включает несколько циклических буферов для хранения сообщений пользовательских программ (что продлевает время жизни карт памяти, так как циклы чтения-записи не расходуются впустую) и не имеет дополнительных задержек от работы с сокетами, которые применяются в стандартном syslog-е [31].

Обзор особенностей ядра Андроида

На диаграмме представлена общая система логирования Андроида. Драйвер логирования предоставляет доступ к каждому буферу через /dev/log/*. Приложения имеют доступ к ним не напрямую, а через библиотеку liblog. С библиотекой liblog общаются классы Log [32], Slog [33] и EventLog [34]. Команда adb logcat показывает содержимое буфера “main”.

Заключение

В данной заметке мы кратко рассмотрели некоторые особенности Андроида как Линукс-системы. За скобками остались некоторые другие части (pmem, RAM console и т.д.), а также такие важные аспекты платформы в целом, как System Service, процесс запуска системы и другие. Если данная тема будет интересна, в следующих статьях мы рассмотрим и их.

Автор: Odnoklassniki_ru

Источник [35]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/linux/54418

Ссылки в тексте:

[1] сайте: http://source.android.com/source/building.html

[2] Оригинальная: http://developer.android.com/images/system-architecture.jpg

[3] чистого: http://kernel.org/

[4] давал: http://www.zdnet.com/blog/open-source/linus-torvalds-on-android-the-linux-fork/9426

[5] включения: http://kernelnewbies.org/Linux_3.5#head-e04ea6fe9005ea057124123d7834624cd445e124

[6] LKML: http://lkml.org/

[7] PowerManager: https://developer.android.com/reference/android/os/PowerManager.html

[8] autosleep: https://lwn.net/Articles/479841/

[9] PowerManager: http://developer.android.com/reference/android/os/PowerManager.html

[10] WakefulBroadcastReceiver: https://developer.android.com/reference/android/support/v4/content/WakefulBroadcastReceiver.html

[11] Out of Memory Killer: https://www.kernel.org/doc/gorman/html/understand/understand016.html

[12] читавшие: http://developer.android.com/guide/components/processes-and-threads.html

[13] ProcessList.java: https://android.googlesource.com/platform/frameworks/base/+/master/services/java/com/android/server/am/ProcessList.java

[14] ядро: https://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git/tree/drivers/staging/android/lowmemorykiller.c?id=HEAD

[15] Activity Manager Service: https://github.com/android/platform_frameworks_base/blob/master/services/java/com/android/server/am/ActivityManagerService.java

[16] Activity Manager: https://developer.android.com/reference/android/app/ActivityManager.html

[17] Binder: http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/drivers/staging/android/binder.c

[18] OpenBinder: http://www.angryredplanet.com/~hackbod/openbinder/docs/html/

[19] Boinic: http://en.wikipedia.org/wiki/Boinic_(software)

[20] System V IPC: https://android.googlesource.com/platform/ndk/+/4e159d95ebf23b5f72bb707b0cb1518ef96b3d03/docs/system/libc/SYSV-IPC.TXT

[21] помним: http://developer.android.com/guide/components/aidl.html

[22] ProcessState.c: https://github.com/cozybit/aosp-frameworks-base/blob/master/libs/binder/ProcessState.cpp

[23] Link to Death: https://developer.android.com/reference/android/os/IBinder.html#linkToDeath(android.os.IBinder.DeathRecipient,%20int)

[24] Window Manager: http://developer.android.com/reference/android/view/WindowManager.html

[25] LocationManager: https://developer.android.com/reference/android/location/LocationManager.html

[26] определяют: https://developer.android.com/reference/android/content/Context.html#checkPermission(java.lang.String,%20int,%20int)

[27] LocationManager-а: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/location/java/android/location/LocationManager.java

[28] POSIX SHM: http://www.kohala.com/start/unpv22e/unpv22e.chap12.pdf

[29] TransactionTooLargeException: https://developer.android.com/reference/android/os/TransactionTooLargeException.html

[30] MemoryFile: https://developer.android.com/reference/android/os/MemoryFile.html

[31] syslog-е: http://ru.wikipedia.org/wiki/Syslog

[32] Log: http://developer.android.com/reference/android/util/Log.html

[33] Slog: https://github.com/android/platform_frameworks_base/blob/master/core/java/android/util/Slog.java

[34] EventLog: http://developer.android.com/reference/android/util/EventLog.html

[35] Источник: http://habrahabr.ru/post/211663/