Добавлен: 29.10.2018
Просмотров: 48056
Скачиваний: 190
10.8. Android
891
Платформа/Приложение
Определения интерфейса
Вызовы методов
Интерфейс/aidl
transact()
onTransact()
IBinder / Binder
Пользовательское пространство Binder
Получаемые коды
Коды команды
ioctl()
Модуль ядра Binder
Рис. 10.25. Архитектура IPC Binder
Модуль ядра Binder
Вместо использования таких имеющихся в Linux IPC-средств, как каналы, в Binder
включен специальный модуль ядра, реализующий его собственный IPC-механизм.
Модель Binder IPC сильно отличается от традиционных механизмов Linux тем, что
она не может быть эффективно реализована как надстройка над ними исключительно
в пользовательском пространстве. Кроме того, Android не поддерживает большинство
примитивов System V для межпроцессного взаимодействия (семафоры, сегменты об-
щей памяти, очереди сообщений), поскольку они не предоставляют надежной семан-
тики для очистки их ресурсов от содержащих ошибки или вредоносных приложений.
Основной IPC-моделью, используемой Binder, является удаленный вызов процедуры
(remote procedure call (RPC)). В этом случае отправляющий процесс передает ядру
полную IPC-операцию, которая выполняется в получающем процессе; отправитель
может заблокироваться, пока получатель выполняет свою работу, позволяя возвратить
результат вызова. (Кроме того, отправители могут указать, что они не блокируются,
продолжая свое выполнение параллельно с получателем.) Таким образом, механизм
Binder IPC основан не на потоке, как каналы Linux, а на обмене сообщениями, подоб-
но очереди сообщений в System V. Сообщение в Binder называется транзакцией и на
более высоком уровне может рассматриваться как вызов функции между процессами.
892
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
Каждая транзакция, отправляемая пользовательским пространством ядру, является
полноценной операцией: в ней определяется цель операции и идентифицируются полу-
чатель, в также все доставляемые данные. Ядро определяет соответствующий процесс
для получения этой транзакции и доставляет ее ожидающему потоку процесса.
На рис. 10.26 показано основное течение транзакции. Любой поток в порождающем
процессе может создать транзакцию, идентифицируя ее цель, и передать все это ядру.
Ядро делает копию транзакции, добавляя к ней идентичность отправителя. Оно опре-
деляет, какой процесс отвечает за цель транзакции, и пробуждает поток в получающем
ее процессе. Поскольку получающий процесс уже существует, он определяет цель
транзакции и осуществляет ее доставку.
Процесс 2
Для: Объект 1
От: Процесс 1
(Данные)
Объект 1
Пул потоков
Транзакция
Для: Объект 1
(Данные)
Пул потоков
Ядро
To: Object1
From: Process 1
(Data)
T1
T1
T2
T2
Ta
Процесс 1
Транзакция
Транзакция
Рис. 10.26. Элементарная транзакция Binder IPC
(Чтобы не усложнять здесь рассматриваемый вопрос, мы упростили способ прохождения
данных транзакции, показав две копии: для ядра и для адресного пространства процесса-
получателя. Реальная транзакция имеет дело с одной копией. Для каждого процесса, спо-
собного получать транзакции, ядро создает общую с ним область памяти. При обработке
ядром транзакции сначала определяется процесс, получающий эту транз акцию, а потом
данные копируются непосредственно в общее адресное пространство.)
Заметьте, что на рис. 10.26 у каждого процесса имеется пул потоков. Это один или не-
сколько потоков, созданных в пользовательском пространстве для обработки входящих
транзакций. Ядро будет отправлять входящую транзакцию по назначению — тому потоку,
который в данный момент ждет работу в пуле потоков процесса. А вот вызовы в ядро из
процессов-отправителей не нуждаются в направлении из пула потоков, инициировать
транзакцию волен любой из потоков процесса, например поток Ta (см. рис. 10.26).
Мы уже видели, что транзакции, передаваемые ядру, идентифицируют целевой объект,
но ядро должно определить процесс-получатель. Для выполнения этой задачи ядро от-
слеживает доступные объекты в каждом процессе и отображает их на другие процессы
(рис. 10.27). Рассматриваемые здесь объекты — это просто места в адресном простран-
стве заданного процесса. Ядро только отслеживает адреса этих объектов, без какого-
либо намерения связываться с ними. Это могут быть места структуры данных языка C,
объект C++ или что-либо еще, размещенное в адресном пространстве процесса.
10.8. Android
893
Процесс 1
Объект 1a
Описатель
2
Процесс 2
Ядро
Объект 1б
Процесс 1
Процесс 2
Объект 2a
Объект 2б
Объект 1a
Объект 2a
Объект 1б
Объект 2б
Описатель
1
Описатель
1
Описатель
2
Описатель
2
Описатель
2
Описатель
3
Описатель
3
Рис. 10.27. Отображение объектов между процессами, осуществляемое Binder
Ссылки на объекты в удаленных процессах идентифицируются с помощью целочис-
ленного описателя (handle), который во многом похож на описатель файла в Linux.
Рассмотрим, например, Объект 1 в Процессе 2. Ядру известно, что он связан с Про-
цессом 2, и более того, ядро связало с ним Описатель 2 в Процессе 1. Благодаря этому
Процесс 1 может отправить ядру транзакцию, нацеленную на его Описатель 2, и отсюда
ядро может определить, что она была отправлена Процессу 2, а конкретно Oобъекту 2а
в этом процессе.
Так же как и в описателях файлов, значение описателя в одном процессе не имеет точно
такого же смысла, как такое же значение в другом процессе. Например (см. рис. 10.27),
в Процессе 1 значение описателя 2 идентифицирует Объект 2а, но в Процессе 2 точно
такое же значение описателя 2 идентифицирует Объект 1а. Более того, невозможно,
чтобы один процесс получал доступ к объекту другого процесса, если в ядре отсутству-
ет назначенный для него описатель этого процесса. На этом же рисунке можно увидеть,
что Объект 2б, принадлежащий Процессу 2, известен ядру, но для него не был назначен
описатель для Процесса 1. Следовательно, пути, по которому Процесс 1 получил бы
доступ к этому объекту, не существует, даже при том, что в ядре есть описатели, пред-
назначенные для других процессов.
А как же с самого начала устанавливаются связи описателей с объектами? В отличие
от описателей файлов Linux, пользовательские процессы не запрашивают описатели
напрямую. Вместо этого ядро назначает описатели процессу по мере необходимости.
Этот процесс показан на рис. 10.28. Здесь мы видим, как может возникнуть ссылка на
Объект 1б из Процесса 2 к Процессу 1 с предыдущего рисунка. Ключом к этому явля-
ется способ прохождения транзакции по системе, показанный слева направо в нижней
части рисунка.
Здесь показаны следующие основные этапы:
1. Процесс 1 создает исходную структуру транзакции, содержащую локальный адрес
Объекта 1б.
2. Процесс 1 отправляет транзакцию ядру.
3. Ядро смотрит на данные в транзакции, находит адрес Объекта 1б и создает для
него новую запись, поскольку ранее оно об этом адресе ничего не знало.
4. Ядро использует цель транзакции, Описатель 2, для определения того, что это
предназначено для Объекта 2а, который находится в Процессе 2.
894
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
Процесс 1
Объект 1б
Описатель 1
1
3
4
6
5
6
8
Транзакция
Данные
Процесс 2
Ядро
7
2
Процесс 1
Процесс 2
Транзакция
Транзакция
Транзакция
Объект 1б
Объект 2a
Данные Данные
Данные
Данные
Данные
Данные
Данные
Описатель 1
Описатель 2
Описатель 2
Описатель 2
Описатель 3
Описатель 3
Описатель 3
Описатель 3
Объект 1б
Объект 1б
Для: Описатель 2
Для: Описатель 2
От: Процесс 1
Объект 2a
Для: Объект 2a
От: Процесс 1
Для: Объект 2a
От: Процесс 1
Рис. 10.28. Передача Binder-объектов между процессами
5. Теперь ядро переписывает заголовок транзакции, чтобы он соответствовал Про-
цессу 2, заменяя его цель адресом Объекта 2а.
6. Точно так же ядро переписывает данные транзакции для целевого процесса. Здесь
оно находит, что Объект 1б еще не известен Процессу 2, поэтому для него создается
новый Описатель 3.
7. Переписанная транзакция доставляется Процессу 2 для выполнения.
8. После получения транзакции процесс обнаруживает в ней новый Описатель 3
и добавляет его в свою таблицу доступных описателей.
Если объект внутри транзакции уже известен объекту-получателю, порядок ее прохож-
дения остается прежним, за исключением того, что теперь ядру нужно лишь переписать
транзакцию, чтобы в ней содержался ранее назначенный описатель или локальный
указатель на объект процесса-получателя. Это означает, что отправка того же объекта
процессу несколько раз всегда будет приводить к точно такой же идентичности, в от-
личие от описателей файлов Linux, где открытие одного и того же файла несколько
раз всегда будет приводить к назначению разных описателей. Система Binder IPC при
перемещении объектов между процессами поддерживает уникальные идентификаторы
объектов.
Архитектура Binder, по сути, вводит в Linux модель безопасности на основе возмож-
ностей. Возможностью является каждый Binder-объект. Отправка объекта другому
процессу предоставляет ему эту возможность. Процесс-получатель затем может вос-
пользоваться функциями, предоставляемыми объектом. Процесс может отправить
объект другому процессу, а позже получить объект от любого процесса и определить,
является ли этот объект тем самым, что был отправлен изначально.
API пользовательского пространства Binder
Основная часть кода пользовательского пространства не взаимодействует с модулем
ядра Binder напрямую. Вместо этого есть объектно-ориентированная библиотека
пользовательского пространства, предоставляющая простой API-интерфейс. Первый
уровень API-функций пользовательского пространства отображается непосредственно
на все рассмотренные до сих под подходы, используемые в ядре, в виде трех классов:
10.8. Android
895
1. IBinder — класса абстрактного интерфейса для объекта Binder. Его основным мето-
дом является transact. Этот метод отправляет объекту транзакцию. Получателем
транзакции может быть объект, находящийся либо в локальном процессе, либо
в каком-нибудь другом процессе. В том случае, если это другой процесс, транзак-
ция, как уже говорилось, будет ему доставлена через модуль ядра Binder;.
2. Binder — класса, представляющего конкретный объект Binder. Реализация под-
класса Binder предоставляет вам класс, который может быть вызван другими про-
цессами. Его основным методом является onTransact, получающий присланную ему
транзакцию. Главное предназначение подкласса Binder заключается в просмотре
получаемых здесь данных транзакции и выполнении соответствующей операции;
3. Parcel — класса-контейнера для считываемых и записываемых данных, находя-
щихся в транзакции Binder. В нем имеются методы для чтения и записи типизи-
рованных данных (целых чисел, строк, массивов), но его самой важной функцией
является возможность считывать и записывать ссылки на любой объект IBinder,
используя соответствующую структуру данных для ядра, чтобы эти ссылки можно
было распознавать и перемещать между процессами.
На рис. 10.29 показана совместная работа этих классов, вносящая изменения в схему
(см. рис. 10.27), которая ранее рассматривалась с используемыми классами пользо-
вательского пространства. Здесь показано, что Binder1b и Binder2a являются экзем-
плярами конкретных подклассов Binder. Для обмена данными между процессами
теперь процесс создает Parcel, содержащий нужные данные, и отправляет его через
еще один класс, который мы пока не рассматривали, BinderProxy. Этот класс созда-
ется при появлении в процессе нового описателя и, таким образом, предоставляет
реализацию IBinder, чей метод transact создает соответствующую транзакцию для
вызова и отправки ее ядру.
Процесс 1
Binder1б
Binder1б
Binder1б
Parcel
transact()
BinderProxy
(Handle 2)
BinderProxy
onTransact()
Parcel
Binder1б
Binder2б
Binder2a
Описатель 1
To: Binder2a
От: Процесс 1
Ядро
Процесс 2
Процесс 1
Процесс 2
Данные
Данные
Данные
Транзакция
Транзакция
Данные
Данные
Данные
Данные
Данные
Описатель 2
Описатель 1
Описатель 2
Описатель 3
Описатель 3
Описатель 3
Для: Описатель 2
От: Процесс 1
Рис. 10.29. API-функции пользовательского пространства Binder
Таким образом, рассмотренная ранее имеющаяся в ядре структура транзакций в API-
функциях пользовательского пространства раздваивается: цель представляется с по-
мощью BinderProxy, а данные содержатся в Parcel. Как мы уже видели, транзакция
проходит через ядро и после появления в пользовательском пространстве в процес-
се-получателе ее цель используется для определения получающего объекта Binder,