Что такое binder driver

Обновлено: 18.05.2024

Как работает Android, часть 1


В этой серии статей я расскажу о внутреннем устройстве Android — о процессе загрузки, о содержимом файловой системы, о Binder и Android Runtime, о том, из чего состоят, как устанавливаются, запускаются, работают и взаимодействуют между собой приложения, об Android Framework, и о том, как в Android обеспечивается безопасность.

Немного фактов

Android — самая популярная операционная система и платформа для приложений, насчитывающая больше двух миллиардов активных пользователей. На ней работают совершенно разные устройства, от «интернета вещей» и умных часов до телевизоров, ноутбуков и автомобилей, но чаще всего Android используют на смартфонах и планшетах.

Компания Android Inc. была основана в 2003 году и в 2005 году куплена Google. Публичная бета Android вышла в 2007 году, а первая стабильная версия — в 2008, с тех пор мажорные релизы выходят примерно раз в год. Последняя на момент написания стабильная версия Android — 7.1.2 Nougat.


Основы безопасности операционной системы Android. Безопасность на уровне Application Framework. Binder IPC

После небольшого перерыва я продолжаю объяснять базовые принципы как обеспечивается безопасность в операционной системе Android. Сегодня я начну описывать безопасность на уровне Application Framework. Но чтобы понять данную тему, вначале необходимо рассмотреть как в Android реализован механизм межпроцессного взаимодействия (Inter-Process Communication (IPC)). Этот механизм называется Binder IPC, и сегодня мы будем рассматривать его особенности. Все, кому интересно, добро пожаловать!

Список статей

Зачем нужен Binder IPC?

Как я уже писал в первой статье цикла, каждое приложение в Android выполняется в своей собственной «песочнице» (Application Sandbox). Механизм «песочницы» в Android основан на присвоении каждому приложению уникального user ID (UID) и group ID (GID), таким образом каждому приложению (application или app) в этой операционной системе соответсвует свой уникальный непривилегированный пользователь. Мы рассматривали эту тему в первой статье цикла. В тоже время владельцами всех критических ресурсов системы являются более привилегированные пользователи, имена и идентификаторы которых жестко зашиты в систему (см. system/core/include/private/android_filesystem_config.h). Системные сервисы, которые запускаются от имени этих пользователей, имеют доступ к соответствующим критическим ресурсам системы (например, к GPS данным), в то время как процессы обычных приложений доступ к этим ресурсам получить не могут.

Однако приложения должны иметь возможность «попросить» системные сервисы предоставить им такую информацию, а последние, в свою очередь, должны иметь возможность предоставить такую информацию приложениям. Так как приложения и системные сервисы исполняются в разных процессах, то для организации такого обмена операционной системе необходимо предоставить механизм обмена информацией между процессами. Более того, даже обычные приложения иногда должны взаимодействовать друг с другом. Например, почтовый клиент может «попросить» просмотрщик изображений отобразить графическое приложение к письму.

В Android обмен сигналами и данными между процессами организован с помощью фреймворка межпроцессного взаимодействия (Inter-Process Communication) Binder. Стандартный System V IPC фреймворк не поддерживается данной операционной системой, в частности, в Bionic libc отсутствуют заголовочные файлы <sys/ipc.h>, <sys/sem.h>, <sys/shm.h>, <sys/msg.h>. В некоторых специфических случаях (например, для взаимодействия с демоном Zygote) используются Unix domain sockets. Все остальные способы взаимодействия между процессами, включая Intents, реализованы используя Binder IPC. Этот фреймворк позволяет синхронно и асинхронно вызывать методы удаленных объектов так, будто они локальные, обмениваться файловыми дескрипторами между процессами, link to death (автоматическое оповещение в случае если Binder определенного процесса был прерван), и т.д.

Как работает Binder IPC?

Взаимодействие между процессами организовано по синхронной клиент-серверной модели. Клиент инициирует соединение и ждет ответа со стороны сервера. Таким образом, взаимодействие между клиентом и сервером происходит последовательно. Такой дизайн позволяет разработчику вызывать удаленные методы словно они находятся в том же самом процессе. Стоит отметить, что это не рассходится с тем, что я писал выше по поводу асинхронного вызова методов: в случае асинхронного вызова, сервер сразу же возвращает пустой ответ клиенту. Схема взаимодействия процессов через Binder представлена на следующем рисунке: приложение (клиент), которое выполняется в процессе Process A, получает доступ к функцианальности, реализованной в сервисе, который выполняется в процессе Process B.


Все взаимодействия между клиентом и сервером в рамках Binder происходят через специальный Linux device driver (драйвер устройства) /dev/binder. В статье «Основы безопасности операционной системы Android. Native user space, ч.1» мы рассматривали процесс загрузки системы. Один из первых демонов, запускаемых процессом init, является ueventd — менеджер внешних устройств в Android. Этот сервис во время старта читает конфигурационный файл ueventd.rc и проигрывает события добавления внешних устройств. Эти события выставляют для устройств разрешения (permissions), а также владельца (owner) и группу (owning group). В следующем примере можно увидеть какие разрешения выставлены для /dev/binder.

Как можно заметить, разрешения для этого устройства выставлены в 0666. Это означает, что любой пользователь системы (а мы помним, что разные приложения в Android — это уникальные пользователи) имеет право писать в и читать из данного устройства. Для взаимодействия с этим драйвером была создана библиотека libbinder. Эта библиотека позволяет сделать процесс взаимодействия с драйвером прозрачным для разработчика приложений. В частности, взаимодействие между клиентом и сервером происходит через proxy (прокси) на стороне клиента и stub на стороне сервера (см. рисунок выше). Proxies и Stubs отвечают за маршалинг данных и комманд передаваемых через драйвер. Т.е. Proxy выстраивает (marshalling) данные и команды, полученные со стороны клиента таким образом, что они могут быть корректно прочитаны (unmarshalling) и однозначно поняты Stub'ом. Вообще, разработчики приложений даже не пишут Stub'ы и Proxy'и для своих приложений. Вместо этого они используют интерфейс, описанный с помощью языка AIDL. Во время компиляции приложения, на основании этого интерфейса генерируется код для Stub'ов и Proxy'и (можете поискать в своих проектах, чтобы увидеть как они выглядят).

Service Manager

Для этого клиент обращается к Binder context manager, который в случае Android называется Service Manager (servicemanager). Service Manager — специальный сервис, Binder токен которого известен всем заранее. Не удивительно, что значение токена для этого сервиса равно 0. Binder драйвер разрешает регистрацию только одного сервиса с таким токеном, поэтому servicemanager — один из первых сервисов, запускаемых в системе. Service Manager можно представить в виде справочника. Вы говорите, что хотите найти сервис с таким-то именем, а вам в ответ возвращают его уникальный токен-номер, который вы можете использовать после для взаимодействия с искомым сервисом. Естественно, сервис сперва должен зарегистрироваться в этом «справочнике».

Безопасность

Сам по себе Binder фреймворк не отвечает за безопасность в системе, но он предоставляет механизмы для её обеспечения. Во-первых, Binder драйвер в каждую транзакцию записывает PID и UID процесса, который инициирует транзакцию. Вызываемый сервис может использовать эту информацию чтобы решить, стоит ли выполнять запрос или нет. Вы можете получить эту информацию используя методы android.os.Binder.getCallingUid(), android.os.Binder.getCallingPid(). Во-вторых, так как Binder токен уникален в рамках системы и его значение не известно априори, то он сам может использоваться как маркер безопасности (смотрите, например, вот эту статью).

Заключение

В следующей части планирую написать о разрешениях (permissions). Материал уже есть, но надо его перевести и подработать. Как всегда буду благодарен за интересные вопросы и пояснения в комментариях, возможно некоторые моменты я неправильно для себя понял.

binder_mmap

mmap - это метод отображаемых в память файлов, то есть файл или другой объект сопоставляется с адресным пространством процесса, и реализуется взаимно-однозначное сопоставление между адресом диска файла и виртуальным адресом в виртуальном адресном пространстве процесса. Используя системный вызов mmap в пользовательском пространстве, ядро ​​вызовет функцию mmap соответствующего драйвера через указатель устройства. Реализация mmap в драйвере Binder фактически отображает виртуальный адрес в пространстве пользователя и виртуальный адрес в пространстве ядра на один и тот же физический адрес, чтобы уменьшить количество копий данных из пространства пользователя в пространство ядра в процессе IPC. Конкретный код выглядит следующим образом:

Приведенный выше код в основном вызываетbinder_alloc_mmap_handlerПараметры, передаваемые этой функцией:proc->allocтак же какvmaТипыbinder_allocтак же какvm_area_struct, Прежде чем конкретно анализировать эту функцию, сначала вспомните базовые знания, представленные ранее.

  • После открытия MMU пользовательское пространство и память доступа к пространству ядра используют виртуальные адреса, которые MMU должен преобразовывать в физические адреса для доступа
  • Обычно Kernel отображает первые 896M (3G-3G + 896M) своего виртуального адресного пространства на первые 896M (0-896M) физической памяти.
  • После виртуального адресного пространства ядра 128M не отображается фиксированным образом, а виртуальный адрес (в единицах страниц) отображается на любой физический адрес при необходимости

Кроме того, в ядре есть несколько структур и методов, которые необходимо ввести:

vm_area_structПредставляет раздел виртуальной памяти в пользовательском пространстве, где поле vm_mm указывает на структуру mm_struct процесса,vm_startс участиемvm_endУкажите начальный адрес и конечный адрес этой области виртуального адреса соответственно, гдеvm_startдаже еслиmmapАдрес, возвращаемый системным вызовом.

vm_structс участиемvm_area_structАналогично, он представляет непрерывную область виртуальной памяти в пространстве ядра, которая используется для сопоставления обратного адреса 120M пространства ядра с физическим адресом, где addr представляет начальный адрес этой области виртуальной памяти, а размер представляет область виртуальной памяти в течение этого периода. размер.

get_vm_areaИспользуется для применения к ядру для области виртуальной памяти, где размер - это размер виртуальной области, к которой применяется, а возвращаемое значениеvm_structуказатель.

map_kernel_range_noflushОн используется для отображения адреса в адресном пространстве ядра и некоторых конкретных физических страницах памяти. среди нихaddrПараметр является начальным адресом виртуального адреса ядра,sizeУказывает размер этого виртуального адреса,portЯвляется ли флаг защиты для физической страницы,pagesУказатель (из массива) на физическую страницу.

flush_cache_vmapОтносящиеся к конкретной архитектуре,map_kernel_range_noflushНе обновляет кеш,flush_cache_vmapОбычно используется для обновления кеша.

vm_insert_pageРаньше ставилpageФизическая страница, на которую указывает указатель, вставляется в виртуальное адресное пространство пользователя. Vma используется для описания этого виртуального управления адресами,addrИспользуется для описания виртуального адреса пользовательского пространства, в которое должна быть вставлена ​​физическая страница.

Следующий конкретный анализbinder_alloc_mmap_handlerРеализация функции:

Пункты, на которые необходимо обратить внимание в этой функции, были отмечены и объяснены следующим образом:

  1. Подать заявку на виртуальный адрес в пространстве ядра
  2. Скопируйте первый адрес этого виртуального адреса в alloc-> buffer, структура alloc инициализируется в binder_open
  3. Вычислить alloc-> user_buffer_offset, vma-> vm_start - это первый адрес виртуального адреса в пространстве пользователя, alloc-> buffer - первый адрес пространства ядра, user_buffer_offset - это разница между ними. После завершения вычисления любой адрес в двух виртуальных адресных пространствах vma и area может быть преобразован с помощью user_buffer_offset.
  4. Выделите массив страниц и вычислите размер_буфера
  5. Выделите память для структуры binder_buffer
  6. Вызовите функцию __binder_update_page_range, эта функция будет продолжать вводить подробно
  7. Инициализируйте этот binder_buffer, данные указывают на первый адрес сегмента виртуального адреса применяемого ядра и добавляют большое количество массивов alloc-> buffers, free устанавливается в 1
  8. Вызовите функцию binder_insert_free_buffer, чтобы вставить структуру буфера в красно-чёрный вид дерева alloc-> free_buffers, что удобно

Продолжить анализ__binder_update_page_rangeФункция основного кода выглядит следующим образом:

Основная логика этого кода находится в цикле for, а места, которые требуют внимания в цикле, отмечены комментариями, которые объясняются следующим образом:

  1. Подайте заявку на физическую страницу и сохраните возвращенный указатель структуры структуры страницы в массиве alloc-> pages
  2. Сопоставьте элемент управления виртуальным адресом ядра, на который указывает page_addr, и применяемой физической страницей
  3. Вставьте примененную физическую страницу в соответствующий виртуальный адрес пространства пользователя

Цикл for сначала инициализирует page_addr для запуска, каждый раз добавляя PAGE_SIZE, page_addr> = end заканчивается. Фактический эффект заключается в сопоставлении начального конечного виртуального адресного пространства и соответствующего виртуального управления адресами пользователя с одним и тем же набором физических страниц. Таким образом, и пользовательское пространство, и пространство ядра могут обращаться к одной и той же физической памяти через свои собственные виртуальные адреса. Результат сопоставления показан на рисунке ниже:

binder_init

Код инициализации Binder можно разделить на три части:

  1. Создайтеbinder_deferred_workqueue
  2. Создайте несколько узлов устройства в debugfs для отладки.
  3. Создать узел устройства

Здесь основное внимание уделяется третьей части кода

CONFIG_ANDROID_BINDER_DEVICES Конфигурация ядра выглядит следующим образом:

Приведенный выше код создает имена какbinder,hwbinder,vndbinderТри устройства. Мы продолжаем анализироватьinit_binder_deviceЭта функция

Эта функция в основном выполняет следующие три функции:

  1. инициализацияbinder_deviceструктура
  2. Использоватьmisc_registerМетод Зарегистрируйте узел устройства в системе. После успешного вызова функции она будет/device/binder (/ device / vndbinder, / device / hwbinder) генерирует узлы устройства
  3. binder_device * добавлено вbinder_devicesСвязанный список

binder_fopsСтруктура определяет указатели на различные функции, которые управляют устройством связывания:

Кроме того, этот тип должен представить два механизма данных в драйвере Binder.

binder_deviceПредставляет устройство Binder, см. Предыдущий кодhlist_node Укажите на узел устройства Bidner в связанном списке binder_devices,miscdeviceПредставляет разное устройство в ядре,binder_contextКонтекст текущего устройства Binder.

binder_contextИспользуется для представления контекста связующего устройства, контекст этого связующего устройства нам знакомServiceManagerПредставительство в ядре. среди них
binder_context_mgr_node указывает узел Binder в ядре ServiceManager, который вы хотите использовать. Другие структуры данных будут представлены позже при анализе последующего кода.

binder_open

Чтобы использовать устройство Binder для IPC в пространстве пользователя, сначала необходимо использовать системный вызов open для открытия устройства Binder. В это время ядро ​​вызовет функцию open, переданную при регистрации устройства через указатель. Функция открытия драйвера binder выглядит следующим образом:

В двух словах, эта функция выполнила только одну задачу: создалаbinder_proc Структурируйте и добавьте структуру кbinder_procs Связанный список.binder_procОт имени процесса, который открыл драйвер Binder, некоторые члены структуры binder_proc были инициализированы в функции binder_open:

  1. Блокировка инициализации
  2. Присвойте объект tsk дескриптору текущего процесса
  3. Инициализировать список задач
  4. Инициализировать информацию о приоритете процесса в приоритет потока
  5. Укажите контекст на контекст открытого устройства
  6. Инициализировать alloc
  7. Установите PID в PID текущего процесса
  8. Инициализируйте связанный список delivery_death и wait_threads

Шаг 6 инициализированbinder_allocОсновное содержание объекта:

Android is Linux

По поводу такой формулировки было много споров, так что сразу поясню, что именно я имею в виду под этой фразой: Android основан на ядре Linux, но значительно отличается от большинства других Linux-систем.

Среди исходной команды разработчиков Android был Robert Love, один из самых известных разработчиков ядра Linux, да и сейчас компания Google остаётся одним из самых активных контрибьюторов в ядро, поэтому неудивительно, что Android построен на основе Linux.

Другая причина того, что в Android не используется софт от GNU — известная политика «no GPL in userspace»:

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.

Само ядро Linux в Android тоже немного модифицировано: было добавлено несколько небольших компонентов, в том числе ashmem (anonymous shared memory), Binder driver (часть большого и важного фреймворка Binder, о котором я расскажу ниже), wakelocks (управление спящим режимом) и low memory killer. Исходно они представляли собой патчи к ядру, но их код был довольно быстро добавлен назад в upstream-ядро. Тем не менее, вы не найдёте их в «обычном линуксе»: большинство других дистрибутивов отключают эти компоненты при сборке.

В качестве libc (стандартной библиотеки языка C) в Android используется не GNU C library (glibc), а собственная минималистичная реализация под названием bionic, оптимизированная для встраиваемых (embedded) систем — она значительно быстрее, меньше и менее требовательна к памяти, чем glibc, которая обросла множеством слоёв совместимости.

В Android есть оболочка командной строки (shell) и множество стандартных для Unix-подобных систем команд/программ. Во встраиваемых системах для этого обычно используется пакет Busybox, реализующий функциональность многих команд в одном исполняемом файле; в Android используется его аналог под названием Toybox. Как и в «обычных» дистрибутивах Linux (и в отличие от встраиваемых систем), основным способом взаимодействия с системой является графический интерфейс, а не командная строка. Тем не менее, «добраться» до командной строки очень просто — достаточно запустить приложение-эмулятор терминала. По умолчанию он обычно не установлен, но его легко, например, скачать из Play Store (Terminal Emulator for Android, Material Terminal, Termux). Во многих «продвинутых» дистрибутивах Android — таких, как LineageOS (бывший CyanogenMod) — эмулятор терминала предустановлен.

Эмулятор терминала на Android

Второй вариант — подключиться к Android-устройству с компьютера через Android Debug Bridge (adb). Это очень похоже на подключение через SSH:

Из других знакомых компонентов в Android используются библиотека FreeType (для отображения текста), графические API OpenGL ES, EGL и Vulkan, а также легковесная СУБД SQLite.

Кроме того, раньше для реализации WebView использовался браузерный движок WebKit, но начиная с версии 7.0 вместо этого используется установленное приложение Chrome (или другое; список приложений, которым разрешено выступать в качестве WebView provider, конфигурируется на этапе компиляции системы). Внутри себя Chrome тоже использует основанный на WebKit движок Blink, но в отличие от системной библиотеки, Chrome обновляется через Play Store — таким образом, все приложения, использующие WebView, автоматически получают последние улучшения и исправления уязвимостей.

Стек технологий Android

It’s all about apps

Как легко заметить, использование Android принципиально отличается от использования «обычного Linux» — вам не нужно открывать и закрывать приложения, вы просто переключаетесь между ними, как будто все приложения запущены всегда. Действительно, одна из уникальных особенностей Android — в том, что приложения не контролируют напрямую процесс, в котором они запущены. Давайте поговорим об этом подробнее.

Основная единица в Unix-подобных системах — процесс. И низкоуровневые системные сервисы, и отдельные команды в shell’е, и графические приложения — это процессы. В большинстве случаев процесс представляет собой чёрный ящик для остальной системы — другие компоненты системы не знают и не заботятся о его состоянии. Процесс начинает выполняться с вызова функции main() (на самом деле _start ), и дальше реализует какую-то свою логику, взаимодействуя с остальной системой через системные вызовы и простейшее межпроцессное общение (IPC).

Поскольку Android тоже Unix-подобен, всё это верно и для него, но в то время как низкоуровневые части — на уровне Unix — оперируют понятием процесса, на более высоком уровне — уровне Android Framework — основной единицей является приложение. Приложение — не чёрный ящик: оно состоит из отдельных компонентов, хорошо известных остальной системе.

У приложений Android нет функции main() , нет одной точки входа. Вообще, Android максимально абстрагирует понятие приложение запущено как от пользователя, так и от разработчика. Конечно, процесс приложения нужно запускать и останавливать, но Android делает это автоматически (подробнее я расскажу об этом в следующих статьях). Разработчику предлагается реализовать несколько отдельных компонентов, каждый из которых обладает своим собственным жизненным циклом.

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 currently isn’t running.

Для реализации такой системы нужно, чтобы приложения имели возможность общатся друг с другом и с системными сервисами — другими словами, нужен очень продвинутый и быстрый механизм IPC.

Этот механизм — Binder.

Русские Блоги

Драйвер Binder - это специальный драйвер для Android, который поддерживает ту же платформу, что и обычный драйвер Linux. Драйвер Binder не задействует никаких периферийных устройств, по сути работает только с памятью и отвечает за передачу данных из одного процесса в другой. Код драйвера Binder хранится в следующих каталогах:

Вот некоторые ключевые функции в драйвере Binder:

Binder

Binder — это платформа для быстрого, удобного и объектно-ориентированного межпроцессного взаимодействия.

Разработка Binder началась в Be Inc. (для BeOS), затем он был портирован на Linux и открыт. Основной разработчик Binder, Dianne Hackborn, была и остаётся одним из основных разработчиков Android. За время разработки Android Binder был полностью переписан.

Binder работает не поверх System V IPC (которое даже не поддерживается в bionic), а использует свой небольшой модуль ядра, взаимодействие с которым из userspace происходит через системные вызовы (в основном ioctl ) на «виртуальном устройстве» /dev/binder . Со стороны userspace низкоуровневая работа с Binder, в том числе взаимодействие с /dev/binder и marshalling/unmarshalling данных, реализована в библиотеке libbinder.

Низкоуровневые части Binder оперируют в терминах объектов, которые могут пересылаться между процессами. При этом используется подсчёт ссылок (reference-counting) для автоматического освобождения неиспользуемых общих ресурсов и уведомление о завершении удалённого процесса (link-to-death) для освобождения ресурсов внутри процесса.

Высокоуровневые части Binder работают в терминах интерфейсов, сервисов и прокси-объектов. Описание интерфейса, предоставляемого программой другим программам, записывается на специальном языке AIDL (Android Interface Definition Language), внешне очень похожем на объявление интерфейсов в Java. По этому описанию автоматически генерируется настоящий Java-интерфейс, который потом может использоваться и клиентами, и самим сервисом. Кроме того, по .aidl -файлу автоматически генерируются два специальных класса: Proxy (для использования со стороны клиента) и Stub (со стороны сервиса), реализующие этот интерфейс.

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

Stub работает наоборот: он принимает входящие вызовы через libbinder, десериализует аргументы, вызывает абстрактную реализацию метода, сериализует возвращаемое значение и передаёт его процессу-клиенту. Соответственно, для реализации сервиса программисту достаточно реализовать абстрактные методы в унаследованном от Stub классе.

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

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

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

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

В следующей статье я расскажу о некоторых идеях, на которых построены высокоуровневые части Android, о нескольких его предшественниках и о базовых механизмах обеспечения безопасности.

Драйвер Binder механизма Binder межпроцессного взаимодействия в Android 【 】

Драйвер Binder - это программа для Android (ранее OpneBinder, но она перестала обновляться), поддерживающая ту же платформу, что и обычный драйвер Linux. Драйвер Binder не использует никаких периферийных устройств, по сути только управляет памятью и отвечает за передачу данных из одного процесса в другой процесс.

Мы знаем, что система Android основана на ядре Linux, поэтому драйвер Binder также является стандартным драйвером Linux, поэтому его операции регистрации и использования также совпадают со стандартным драйвером.

Драйвер Binder будет регистрироваться как разное устройство. Разное устройство в Linux означает разное устройство, что означает сборщик. Его регистрация и использование относительно просты, затем создайте / dev / binder устройство, узел Binder не будет соответствовать Реальное аппаратное оборудование в основном для управления памятью.

Шаги для использования полного драйвера Binder:
  • С помощью init () зарегистрируйте Binder как miscdevice и создайте узел устройства / dev / binder
  • Через open () откройте драйвер Binder и получите его файловый дескриптор
  • Через mmap () применить к памяти для достижения отображения памяти
  • Передайте данные в IPC в драйвер Binder через ioctl ()

Вышеупомянутые методы - все методы пользовательского пространства. Если вы хотите вызвать метод пространства ядра, вам нужно задействовать системные вызовы. Например, если вы вызываете open в пользовательском пространстве, вы перейдете к _open () через системный вызов. Вызвать метод binder_open () драйвера связывателя в пространстве ядра, остальные вызовы аналогичны

Путь к коду ядра Linux выглядит следующим образом:

Итак, зарегистрируйтесь как miscdevice, посмотрите на параметр binder_miscdev, который является структурой miscdevice

Посмотрите на структуру file_operations еще раз

Эта структура содержит ряд указателей на функции, которые соответствуют работе пользовательского пространства при использовании устройства Binder: например, binder_open соответствует системному вызову _open, соответствует методу открытия пользовательского пространства, binder_mmap соответствует системе _mmap Позвони и т. Д.
Есть три функции, которые являются наиболее важными и наиболее используемыми: binder_open, binder_mmap, binder_ioctl; если вы хотите использовать процесс Binder, сначала необходимо открыть драйвер Binder через binder_open, а затем binder_mmap Отображение памяти, наконец, через binder_ioctl для фактической работы; Клиент запрашивает Сервер, а Сервер отвечает Клиенту через binder_ioctl

Драйвер Open Binder

Когда какой-либо процесс использует драйвер Binder, необходимо открыть узел / dev / binder, то есть открыть устройство Binder. Эта операция в конечном итоге будет реализована методом binder_open () в пространстве ядра.

Этот метод создает экземпляр объекта binder_proc. В этом объекте хранится информация о текущем процессе. Отсюда берется информация, необходимая для последующего IPC, а объект binder_proc сохраняется в указатель файла filp, так что binder_proc можно найти через filp; все используют Binder Процесс сохраняется в списке binder_procs, здесь драйвер Binder создал собственную сущность binder_proc для процесса, а затем процесс будет работать на устройстве binder на основе этого объекта.

Карта памяти

Концепция отображения памяти была описана в последнем блоге

После включения драйвера Binder процесс не выполняет операции с данными напрямую. В этом случае нет существенных отличий от других механизмов IPC, и механизм Binder выполняет очень важную операцию - отображение памяти, которое также повышает эффективность (только Копия данных была сделана)

Этот шаг соответствует пространству ядраbinder_mmap()Функция, соответствующий метод пользовательского пространства mmap (), эта функция может отображать память, указанную устройством, в пространство виртуальной памяти, используя его процесс, но Binder - это не реальное аппаратное устройство, а псевдо-аппаратное обеспечение на основе памяти, которое связывает Какая память отображается в процессе?

функция mmap
  • Первый параметр - дескриптор файла, который содержит структуру binder_proc, второй параметр - пространство виртуальной памяти пользователя.
  • Блокировка для обеспечения одновременного доступа к нескольким процессам
  • Определите размер отображаемой памяти и выделите непрерывное виртуальное пространство ядра с помощью функции get_vm_area, которая соответствует размеру виртуального пространства пользователя процесса, укажите начальный адрес пространства ядра процесса на этот адрес пространства, и в это время фактическая физическая память не была выделена
  • В функции binder_update_page_range подайте заявку на страницу физической памяти через функцию alloc_page и сопоставьте ее с виртуальным пространством пользователя и виртуальным пространством ядра одновременно, первый параметр - это объект binder_proc процесса обращения к памяти, второй параметр 1 означает приложение, 0 означает освобождение; Три параметра являются начальной точкой виртуальной памяти в Binder, четвертый параметр - конечной точкой виртуальной памяти в Binder, пятый параметр - виртуальная память пространства пользователя.


После завершения отображения памяти процесс связи в механизме связывания выглядит следующим образом

обработка данных

После того, как вышеупомянутые шаги выполнены, основа для связи процесса уже была установлена, и следующий шаг должен пройтиbinder_ioctl()Функция управляется в соответствии с системным вызовом ioctl (), этот метод в основном реализует командное взаимодействие между процессом приложения и драйвером Binder.Можно сказать, что большая часть бизнеса драйвера Binder выполняется. И write () и другие файловые операции, потому что binder_ioctl () может полностью заменить их

Функция определяется следующим образом

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)

  • Первый параметр - это дескриптор файла, такой же, как описанный выше метод
  • Второй параметр - команда, третий параметр - тип данных, эти два параметра - один

Команды и типы данных следующие:

  1. BINDER_WRITE_READ: Соответствующий тип данных struct binder_write_read, основная роль - операции чтения и записи, вы можете использовать эту команду для чтения и записи данных в Binder
  2. BINDER_SET_MAX_THREADS: соответствующий тип данных __u32; установить максимальное количество потоков Binder
  3. BINDER_SET_CONTEXT_MGR: соответствующий тип данных __s32; только для Service Manager, установите себя в качестве дворецкого Binder, в системе может быть только один SM
  4. BINDER_THREAD_EXIT: Соответствующий тип данных __s32; уведомлять Binder о выходе из потока; каждый поток должен уведомлять Binder о выходе, чтобы драйвер Binder мог освободить ресурсы, чтобы избежать утечки памяти
  5. BINDER_VERSION: соответствующий тип данных struct binder_version; получить номер версии Binder

Среди них BINDER_WRITE_READ является наиболее используемой и основной командой ioctl.

функция ioctl
  • Если переданная команда является BINDER_WRITE_READ, выполните метод binder_ioctl_write_read
  • В методе binder_ioctl_write_read данные пользовательского пространства ubuf копируются в пространство ядра bwr через функцию copy_from_user
  • Когда буфер записи (write_buffer) в структуре bwr содержит данные (write_size> 0), выполняется binder_thread_write, а при сбое записи данные bwr записываются обратно в пространство пользователя и завершаются;
  • Когда буфер чтения (read_buffer) в структуре bwr содержит данные (read_size> 0), выполняется binder_thread_read, а при сбое чтения данные bwr записываются обратно в пространство пользователя и завершаются;
  • Наконец, скопируйте данные ядра bwr в пользовательское пространство ubuf через функцию copy_to_user.


Здесь есть очень важная структура: binder_write_read, структура выглядит следующим образом

  • В операции Binder выделение кэша выполняется с помощью функции binder_alloc_buf. Структура binder_buffer используется для описания кэша. Транзакция Binder будет соответствовать binder_buffer.
  • Освобождение памяти достигается с помощью функции binder_free_buf, которая выполняет три вещи:
    - 1. Пересчитать размер свободного кэша процесса
    - 2. Освободить память через binder_update_page_range
    - 3. Обновите поля буферов binder_proc, free_buffers, alloc_buffers
Протокол связи Binder

Протокол Binder можно разделить на два типа: протокол управления и протокол привода:

Протокол управленияЭто протокол для процесса для связи с устройством Binder через ioctl ("/ dev / binder"). Протокол содержит команды BINDER_WRITE_READ, BINDER_SET_MAX_THREADS, BINDER_SET_CONTEXT_MGR, BINDER_THREAD_EXIT, BINDER_обработка данныхУже описано в этом шаге

Протокол приводаОписывает конкретное использование драйвера Binder. Протокол привода можно разделить на две категории:

Один тип - BINDER_COMMAND_PROTOCOL: код запроса связующего, начинающийся с «BC_», сокращенно обозначаемый как код BC, описывающий команду, отправленную процессом в драйвер связывателя.

Один тип - BINDER_RETURN_PROTOCOL: код ответа связывателя, начинающийся с "BR_", называемый кодом BR, описывает команду, отправленную драйвером связывателя процессу

Коды запроса связующего следующие:

Процесс обработки запроса осуществляется с помощью метода binder_thread_write (), который используется для обработки кода запроса в протоколе Binder. Когда в binder_buffer есть данные, операция записи потока связывания выполняется циклически.
Когда код запроса - BC_TRANSACTION или BC_REPLY, выполняется метод binder_transaction (), что является наиболее частой операцией.

Код ответа Binder выглядит следующим образом:


Запрос клиента для сервера и ответ сервера для клиента необходимо передать данные через драйвер Binder, в котором протокол Binder играет очень важную роль, см. Полный процесс связи Binder

В основном, одна сторона отправляет BC_XXX, затем драйвер управляет процессом связи, а затем отправляет соответствующую команду BR_XXX другой стороне в процессе связи.

Читайте также: