Добавлен: 29.10.2018
Просмотров: 48054
Скачиваний: 190
886
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
Ожидания, выстраиваемые вокруг включения и выключения экрана мобильного
устройства, гораздо более значительны, чем во время работы на обычном компьютере.
Взаимодействия с мобильными устройствами в течение дня характеризуются множе-
ственностью и внезапностью: вы получаете сообщение и включаете устройство, чтобы
его просмотреть и, возможно, отправить ответ из одного предложения, вы подбегаете
к друзьям, выгуливающим свою собаку, и включаете устройство, чтобы сфотографи-
ровать пса. В таких типичных случаях использования мобильных устройств любая
задержка от момента извлечения устройства и до его готовности к использованию
создает у пользователя отрицательное впечатление.
Учитывая эти требования, решение может быть таким: не переводить центральный
процессор в спящее состояние при выключенном экране, чтобы устройство всегда было
готово к использованию. В конце концов, ядро получает информацию, когда у потоков
не остается запланированной работы, и Linux (как и большинство других операцион-
ных систем) автоматически переводит центральный процессор в режим ожидания,
экономя при этом электроэнергию.
Но ожидающий центральный процессор отличается от процессора в реальном спящем
режиме, например, следующим:
1. На многих наборах микросхем в режиме ожидания тратится намного больше энер-
гии, чем в реальном состоянии сна.
2. Центральный процессор в режиме ожидания может пробуждаться в момент по-
явления любой, даже не самой важной работы.
3. Наличие центрального процессора в режиме ожидания не означает, что можно
отключить другое оборудование, которое может не понадобиться в режиме ре-
ального сна.
Блокировки сна на Android позволяют системе входить в более глубокий спящий ре-
жим без привязки к явным действиям пользователя, подобным выключению экрана.
Исходное состояние системы с блокировками сна — устройство находится в спящем
состоянии. Когда устройство работает, то для того, чтобы оно опять не уснуло, нужны
какие-то меры, удерживающие блокировку сна.
Пока экран включен, система всегда удерживает блокировку сна, не позволяющую
устройству засыпать, поэтому, как мы и ожидали, оно продолжает работать. Но когда
экран выключен, сама система, как правило, не удерживает блокировку сна, поэтому
она не будет засыпать только до тех пор, пока эту блокировку не удерживает что-нибудь
еще. Когда ни одна из блокировок сна больше не удерживается, система засыпает и мо-
жет проснуться только благодаря аппаратному прерыванию.
Когда система спит, аппаратное прерывание ее снова разбудит, как и обычную опера-
ционную систему. Среди источников такого прерывания можно назвать будильники,
события от приемника сотовой связи (например, входящий звонок), входящий сетевой
трафик и нажатие на конкретную аппаратную кнопку (например, кнопку включения).
Обработчики прерываний для этих событий по сравнению с Linux требуют внесения
только одного изменения: у них должна быть исходная блокировка сна, чтобы система
находилась в рабочем состоянии после того, как они обработают прерывание.
Блокировка сна, приобретаемая обработчиком прерывания, должна удерживаться до-
статочно долго для того, чтобы управление было передано вверх по стеку тому драйверу
в ядре, который продолжит работу с событием. Затем этот драйвер ядра будет отвечать
10.8. Android
887
за приобретение собственной блокировки сна, после чего блокировка сна прерывания
может быть спокойно снята без опасений, что система снова заснет.
Если затем драйвер собирается доставить это событие вверх в пользовательское
пространство, требуется точно такое же рукопожатие. Драйвер должен обеспечить
удержание блокировки сна до тех пор, пока не доставит событие ожидающему поль-
зовательскому процессу, и гарантировать для этого процесса возможность получить
его собственную блокировку сна. Этот поток может также продолжать перемещаться
по подсистемам в пользовательском пространстве — пока что-либо удерживает бло-
кировку сна, нужная реакция на событие будет наступать. Но когда блокировок сна не
останется, заснет вся система и обработка остановится.
Устранение дефицита памяти
В Linux имеется механизм Out-Of-Memory Killer, который стремится восстановить
работоспособность при крайне низком объеме доступной памяти. Ситуация дефицита
памяти в современных операционных системах требует пояснения. При страничной
организации памяти и подкачке сами приложения сталкиваются со сбоями, связанны-
ми с дефицитом памяти, крайне редко. Но ядро все же может попасть в ситуацию, при
которой найти необходимые доступные страницы оперативной памяти невозможно
не только для нового выделения памяти, но и для замены или подкачки в некотором
используемом в данный момент диапазоне адресов.
В ситуации дефицита памяти стандартный механизм его устранения является по-
следней попыткой поиска оперативной памяти, позволяющей ядру продолжить теку-
щую работу. Это делается путем присвоения каждому процессу уровня «вредности»
и уничтожения процесса, считающегося самым «вредным». «Вредность» процесса
основывается на объеме используемой им оперативной памяти, продолжительности
его работы и других факторах, а цель состоит в том, чтобы уничтожить ресурсоемкие
процессы, которые, надо надеяться, не играют важной роли.
Android испытывает особую потребность в механизме устранения дефицита памя-
ти. У него нет пространства подкачки, поэтому он попадает в ситуации дефицита
памяти значительно чаще прочих: нет способа снижения этого дефицита, кроме
выделения чистых страниц оперативной памяти, отображенных из хранилища
и недавно использованных. Но даже при этом в Android используется стандартная
Linux-конфигурация для выделения памяти в условиях дефицита, то есть разрешение
адресному пространству быть выделенным в оперативной памяти без гарантии на-
личия доступной оперативной памяти для поддержки этого выделения. Выделение
памяти в условиях дефицита является особенно важным средством для оптимизации
использования памяти, поскольку отображение больших файлов (например, испол-
няемых) на память с помощью mmap там, где нужно будет лишь загрузить в опера-
тивную память небольшую часть общего объема данных, имеющихся в этом файле,
встречается довольно часто.
В данной ситуации Linux-механизм устранения дефицита памяти проявляется не с луч-
шей стороны, так как он больше подходит в качестве крайнего средства и затрудняется
правильно идентифицировать процессы, подходящие для удаления. Фактически как
выяснится при дальнейшем рассмотрении вопроса, Android часто полагается на ме-
ханизм устранения дефицита памяти, который регулярно запускается, чтобы собрать
сведения о процессах и сделать правильный выбор.
888
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
Для решения этой задачи в ядро Android введен собственный механизм устранения
дефицита памяти с другими семантикой и конечной целью. Устранение дефицита па-
мяти в Android выполняется намного агрессивнее и начинает действовать, как только
объем доступной оперативной памяти становится низким. Низкий уровень памяти
определяется настраиваемым параметром, показывающим приемлемый для ядра объем
доступной свободной оперативной памяти и кэш-памяти. Как только система опуска-
ется ниже этого предела, запускается механизм устранения дефицита памяти, чтобы
высвободить оперативную память где-то в другом месте. Задача — не допустить, чтобы
система оказалась в таких ситуациях, когда подкачка страниц негативно отра зится на
пользовательском восприятии, то есть когда приложения первого плана начнут конку-
рировать за получение оперативной памяти, в результате чего из-за постоянного сброса
и подкачки страниц сильно упадет скорость их выполнения.
Вместо попытки угадать, какой из процессов должен быть уничтожен, имеющийся
в Android механизм устранения дефицита памяти весьма строго полагается на ин-
формацию, предоставляемую ему пользовательским пространством. Обычный Linux-
механизм устранения дефицита памяти располагает для каждого процесса параметром
oom_adj, который можно направить к самому подходящему для уничтожения процессу
путем изменения совокупного показателя его «вредности». Android-механизм устране-
ния дефицита памяти использует точно такой же параметр, но как показатель строгой
упорядоченности: процессы с наивысшим значением oom_adj всегда будут уничто-
жаться перед процессами с более низким значением. Как система Android принимает
решение о назначении этих показателей, мы увидим чуть позже.
10.8.6. Dalvik
Виртуальная машина Dalvik реализует в Android среду языка Java, которая отвечает
за запуск приложений, а также за основную часть системного кода этой операционной
системы. Почти все в процессе system_service, начиная от диспетчера пакетов, переходя
к оконному диспетчеру и заканчивая диспетчером активностей, реализовано в коде
языка Java, выполняемом Dalvik.
Но в традиционном смысле Android не является платформой, построенной на языке
Java. Код Java в Android-приложении предоставляется в формате байт-кода Dalvik, по-
строенного вокруг регистр-ориентированной машины, а не в формате традиционного
байт-кода Java, построенного вокруг стек-ориентированной машины. Формат байт-кода
Dalvik позволяет осуществлять более быструю интерпретацию, сохраняя при этом под-
держку JIT-компиляции (Just-in-Time — к нужному моменту). Также байт-код Dalvik
более экономно расходует память как на диске, так и в оперативном пространстве
благодаря использованию строкового пула и других технологий.
При написании Android-приложений исходный код пишется на Java, а затем компили-
руется в стандартный байт-код Java с использованием традиционного инструментария
Java. Затем Android вводит новый шаг — преобразование этого байт-кода Java в более
компактный байт-код Dalvik. Именно байт-код Dalvik является версией приложения,
которое помещается в пакет в виде финального двоичного приложения и в конечном
итоге устанавливается на устройстве.
В области системных примитивов, включая управление памятью, безопасность и обмен
данными через границы безопасности, системная архитектура Android в значительной
степени опирается на Linux. В ней для основных представлений операционной системы
10.8. Android
889
язык Java не используется, его использование является небольшой попыткой абстраги-
роваться от этих важных аспектов, положенной в основу операционной системы Linux.
Особого внимания заслуживает использование операционной системой Android про-
цессов. Замысел Android заключается не в том, чтобы сделать язык Java средством
изоляции приложений от системы, вместо этого он предполагает применение к изоля-
ции процессов подходов, традиционных для операционной системы. Это означает, что
каждое приложение запускается в собственном Linux-процессе с собственной Dalvik-
средой, и то же самое касается system_server и других основных частей платформы,
написанной на языке Java.
Использование процессов для такой изоляции позволяет Android задействовать все
функции Linux, управляющие процессами, от изолирования памяти до очистки всех
ресурсов, связанных с процессом, когда этот процесс прекращает свою работу. Кроме
этих процессов Android может рассчитывать только на функции безопасности Linux,
а не на SecurityManager из Java.
Использование процессов и системы безопасности Linux существенно упрощает среду
Dalvik, поскольку она больше не отвечает за эти критические аспекты стабильности
и надежности системы. Неслучайно это также позволяет приложениям свободно ис-
пользовать при реализации внутренний код, что особенно важно для игр, которые
обычно построены на движках, написанных на языке C++.
Подобное перемешивание процессов и языка Java создает ряд проблем. На первое об-
ращение к свежей среде на языке Java даже на современном мобильном оборудовании
может уйти секунда. Вспомним: одна из целей разработки Android — возможность
быстрого запуска приложений, который не должен длиться более 200 мс. Требование
предоставления этому новому приложению свежего Dalvik-процесса приводит к вы-
ходу далеко за рамки этой нормы. На мобильных устройствах добиться запуска за
время не более 200 мс довольно трудно, даже если бы не нужно было инициализировать
новую среду языка Java.
Решением этой проблемы стало использование ранее упоминавшегося собственного де-
мона zygote. Этот демон отвечает за доставку инициализированной Dalvik-среды в точку,
где готов запуск системного кода или кода приложения, написанного на языке Java. Все
новые процессы, основанные на применении среды Dalvik (системные или прикладные),
ответвляются от zygote, что позволяет им начинать выполнение с уже готовой к работе
средой. Zygote не только доставляет Dalvik, он также осуществляет предварительную
загрузку многих частей Android-среды, которые обычно используются в системе и при-
ложениях, а также загружает ресурсы и другие часто востребуемые компоненты.
Заметьте, что для создания нового процесса из zygote используется Linux-функция
fork, но вызов функции exec не применяется. Новый процесс является точной копией
исходного процесса zygote, со всеми его ранее инициализированными и уже установ-
ленными состояниями, и он сразу же готов к работе. Связь нового Java-процесса с ис-
ходным процессом zygote показана на рис. 10.24. После ответвления в распоряжении
нового процесса оказывается собственная отдельная Dalvik-среда, таким образом,
он через страницы копирования при записи делит с zygote все заранее загруженные
и инициализированные данные. Теперь при наличии готового к работе нового процес-
са остается лишь дать ему правильную идентичность (UID и т. д.), завершить любые
инициализации Dalvik-среды, которые требуются запускаемым потокам, и загрузить
запускаемый код приложения или системный код.
890
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
Dalvik
Копирование
при записи
Dalvik
Заранее загруженные
классы
Заранее загруженные
ресурсы
Классы и ресурсы
приложения
Процесс приложения
Zygote
Заранее загруженные
ресурсы
Заранее загруженные
классы
Рис. 10.24. Создание нового Dalvik-процесса из zygote
Кроме скорости запуска zygote обеспечивает еще одно преимущество. Поскольку
для создания процессов из zygote используется только функция fork, zygote и все его
дочерние процессы могут совместно использовать множество уже задействованных
страниц оперативной памяти, необходимых для инициализации Dalvik-среды и пред-
варительной загрузки классов и ресурсов. Это совместное использование для Android-
среды, где подкачка недоступна, играет особо важную роль: можно лишь потребовать
подкачку чистых страниц (например, выполняемого кода) с диска (флеш-памяти). Но
любые уже задействованные страницы должны оставаться запертыми в оперативной
памяти — они не могут быть выгружены на диск.
10.8.7. Binder IPC
Замысел Android-системы в значительной степени выстроен вокруг изоляции про-
цессов, протекающих между приложениями, а также между различными частями
самой системы. Этот требует большого объема межпроцессного обмена данными
(interprocess-communication (IPC)) для координации работы разных процессов, что
может потребовать большого объема работы для реализации правильного функциони-
рования. Имеющийся в Android IPC-механизм Binder обладает высокой технологич-
ностью и универсальностью и служит основой для надстройки над ним большинства
Android-систем.
Архитектура Binder разделена на три уровня (рис. 10.25). На самом дне находится
модуль ядра, реализующий текущее межпроцессное взаимодействие через функции
ядра ioctl. (Функция ioctl является универсальным вызовом ядра для отправки команд
клиента драйверам и модулям ядра.) Поверх модуля ядра находится основной API-
интерфейс объектно-ориентированного пользовательского пространства, позволяющий
приложениям исопльзовать классы IBinder и Binder, чтобы создавать конечные точки
IPC и взаимодействовать с ними. На вершине находится основанная на интерфейсе
модель программирования, где приложения объявляют свои IPC-интерфейсы, совер-
шенно не заботясь о подробностях реализации IPC на более низких уровнях.