super1 发表于 2022-12-10 19:15

Android gd -- Gabeldorsche Bluetooth

Gabeldorsche Architecture

谷歌宣布新版本的 Gabeldorsche,即自版本 11 以来在 Android 中使用的蓝牙堆栈,用Rust重写的BT Stack (准确的说是一些BT底层核心)

From: https://android.googlesource.com/platform//system/bt/+/ce7fbfcc74e7908aa733131df374ac2421d75d57/gd/docs/architecture/architecture.md#gabeldorsche-architecture
内容


本文档概述了开发 Gabeldorsche (GD) 蓝牙堆栈时所做的一些架构注意事项。
threading-model


首先,GD 堆栈不建立在线程的概念之上。相反,它适用于Handlers. 但是,由于GD最终运行在OS上,在实现Handler抽象之前,它仍然需要与进程和线程进行交互。
processes


一般来说。GD运行时环境中存在三种类型的进程:

应用程序进程:包括第三方应用程序、其他系统组件(例如音频和电信服务)与通过各种 RPC/IPC 方法(例如 Binder、Socket IPC、gRPC、DBUS)定义的蓝牙堆栈进程API 交互。等等,使用 AIDL 或 Protobuf 等语言。对于 Android 应用程序,尽管 API 是在 AIDL 中定义的,但一些样板代码包含在通过代码公开的 Java 库中,这些代码frameworks/base/core/java/android/bluetooth作为Android SDK发布给开发人员。

硬件抽象层 (HAL) 进程:来自供应商分区的一个或多个进程,因此依赖于硬件。它们通过Binder、Socket IPC、DBUS等RPC/IPC方法定义的一组硬件抽象API,使用HIDL等语言与蓝牙栈进程进行交互。在 Android 上,这将是实现 HIDL API(例如IBluetoothHci和IBluetoothAudioProvider )的 HAL 进程。

蓝牙堆栈进程:通常是在主机控制器接口 (HCI) 之上和蓝牙 SDK API 之下实现各种蓝牙协议和配置文件的单个进程。一方面,它为来自应用程序进程的请求提供服务;另一方面,它通过与HAL 进程的交互来转发这些请求。在 Android 上,此进程通常在 AID_BLUETOOTH(通常为 1002)下运行,进程名称为“com.android.bluetooth”。该过程在 Java 中启动,并通过 JNI 加载本机库。其他不使用Java虚拟机的系统可能有纯原生进程。由于各种原因,这个进程中可能存在多个线程。GD栈完全运行在这个进程中。
threads-in-bluetooth-stack-process蓝牙堆栈进程中的线程


目前,蓝牙堆栈中线程优化的目标是:
尽可能减少线程数以简化同步在单独的线程中执行阻塞 I/O 操作尝试将 I/O 操作移动到轮询模式,以便我们可以使用事件驱动方法在主线程上与其交互将警报和计时器机制移动到它们的调用线程以避免单独的警报线程隔离单个组件,以便每个组件都可以单独启动和停止,而无需终止主线程首选数据传递而不是线程间的数据共享,以减少锁定和竞争条件

经过上述优化后,我们在本机代码中留下了五种主要类型的线程:

主线程:蓝牙堆栈中的主力。线程的执行上下文被进一步划分为Handlers驻留在 individual 中的那些Modules。如果运行平台上的性能受到限制,则可以将该线程进一步划分为更小的线程。部署者只需要将处理程序绑定到不同的线程,这不应该影响整体操作。

JNI 线程:在原生线程中,我们将 Java 层视为一个单独的应用程序,因为它的线程模块是完全不同的。因此,我们在这两层之间放置了一个线程来缓冲任何阻塞操作。

HCI 线程(或其他 HW I/O 线程):该线程负责硬件 I/O 的死机,并且可能会阻塞。因此它有自己的独立线程以避免阻塞主线程。

Audio worker thread:负责对执行时序精度要求更高的音频编解码操作。这样的worker有自己独立的线程,避免被主线程影响。

Socket I/O线程:与使用该BluetootSocket接口的各种应用程序进行通信。由于潜在的 I/O 延迟,它有单独的线程。
data-flow-diagram数据流程图


不同组件之间的函数调用被抽象为通过队列传递的控制包(函数闭包)。组件之间的数据流是通过队列发送的数据包,使用Reactor. 它们将合并到每个组件的输入队列中。我们定义了三种类型的队列:

非阻塞队列:当用户试图在空的时候出队,或者满的时候入队,它会立即返回。线程内的所有排队都必须是非阻塞的,否则会死锁。

阻塞队列:当用户尝试在队列为空时出队,或在队列已满时入队,它将阻塞,直到其他线程使队列可写/可读。它可以用作流量控制机制,以避免来自用户线程的数据包过多。

Leaky queue:与非阻塞队列相同,但当它已满并且用户尝试入队时它会刷新。这对音频编码很有用。

image.png

building-blocks建筑模块

module模块


GD 中的代码被打包到名为Module. 一个模块标准化了 GD 代码的以下方面:
依赖关系:一个模块通过实现来提供自己对其他模块的依赖关系ListDependencies()生命周期:模块必须实现Start()和Stop()生命周期方法线程模块:Module基类Handler通过GetHandler()指标:AModule可以通过 dumpsys 转储其状态信息DumpState()

请参阅其定义:https://android.googlesource.com/platform/system/bt/+/master/gd/module.h
handler处理程序


类似于android.os.Handler,bluetooth::os::Handler提供顺序执行上下文,同时从执行代码中隐藏线程的概念。

通过将执行上下文划分为更小的区域,Handler可以通过以下方式使开发受益:
由于顺序执行上下文,较少需要锁定较小的上下文导致更容易管理代码流与线程分离使系统部署者可以更自由地调整底层线程分配。例如,对于没有全线程实现的实时操作系统,aHandler可用于提供近线程执行上下文

当然,使用 也有缺点Handler,开发者应该谨慎对待:

警告:虽然多个Handler可以绑定到同一个线程,Handler但不保证代码在不同线程上的顺序执行Handler,即使它们在同一个线程上。

警告:Handlers绑定到同一线程的线程之间的锁定可能会导致死锁

警告:必须在两者之间复制数据Handler以避免死锁和竞争条件

请参阅其定义: https: //android.googlesource.com/platform/system/bt/+/master/gd/os/handler.h
reactor反应堆


bluetooth::os:Reactor实现Reactor 设计模式,其中并发事件由同步事件多路分解器多路分解为通过Dispatcher注册的请求处理程序列表。

在通用的 Linux 操作系统(例如 Android)中,我们使用文件描述符(例如eventfd for Handler、timerfd forAlarm和socketfd for 数据处理管道)来实现它。在文件描述符的上下文中,事件分为两种类型:
OnReadReady:表示多路分解器有一些事件供处理程序使用,并且处理程序可以从底层事件队列中读取至少一个事件。这通常与EPOLLIN、EPOLLHUP、EPOLLRDHUP和相关联EPOLLERR。OnWriteReady:表示多路分解器已准备好从该处理程序中消耗更多事件,并且该处理程序可以将至少一个事件写入底层队列。这通常与EPOLLOUT.

这种模式自然会产生从一个队列到另一个队列的背压,而无需任何额外的信号机制。当在像我们这样的网络堆栈中使用时,它简化了信令代码流。

请参阅其定义:https://android.googlesource.com/platform/system/bt/+/master/gd/os/reactor.h

的纯数据用例Reactor是 a Reactive Queue,请参阅其定义: https: //android.googlesource.com/platform/system/bt/+/master/gd/os/queue.h
Packet-Definition-Language-PDL


数据包解析和序列化一直是任何网络堆栈的重要组成部分。它通常是与远程设备交互的第一段代码。过去,这是使用STREAM_TO_UNIT8或 之类的宏手动实现的UINT8_TO_STREAM。这种手动方法既乏味又容易出错。为了解决这个问题,我们创建了一种数据包定义语言,将网络数据包结构定义为位级别。C++ 标头和 Python 绑定将从其代码生成器自动生成,对代码生成器的任何修复都将系统地应用于生成的所有数据包代码。

示例 PDL:
// Commentslittle_endian_packets // Whether this packet is big or small endian// Include header from other C++ header filescustom_field SixBytes : 48 "packet/parser/test/" // expect six_bytes.hcustom_field Variable "packet/parser/test/" // expect variable.h// A packetpacket Parent {_fixed_ = 0x12 : 8, // fixed field 0x12 that takes 8 bits_size_(_payload_) : 8, // Size field that takes 8 bits_payload_, // special payload field of variable sizefooter : 8, // fiexed size footer of 8 bits}packet Child : Parent {field_name : 16, // addition field append after Parent}// an enum of 4 bitsenum FourBits : 4 {ONE = 1,TWO = 2,THREE = 3,FIVE = 5,TEN = 10,LAZY_ME = 15,}
请参阅其文档:https://android.googlesource.com/platform/system/bt/+/master/gd/packet/parser/README
模块之间的调用约定

asynchronous-server_client-model异步服务器-客户端模型


对于模块之间的大多数通信,开发人员应该采用通用模型中的异步服务器-客户端模型,例如:
// Define callback function typeusing CallbackFunction = std::function<void(ParamType)>;// Asynchronous method definitionbool Foo(Parameter param, CallbackFunction callback);// A new callback is passed for each asynchronous call// Always prefer lambda over std::bindCallbackFunction callback = {// do something};Parameter param = {// something};if (Foo(param, callback)) {   // The callback will be invoked   // Callback must be invoked in the future} else {   // Failed, no need to wait}
许多协议和配置文件都适合这样的模型,例如AclManager和L2cap。
synchronous-database-model同步数据库模型


在某些情况下,异步服务器-客户端模型是不可行的。在这种情况下,开发人员可以考虑同步数据库模型。在这样的模型中,操作可以在互斥锁的帮助下同步发生。当方法返回时,更改必须反映到所有依赖项。内部状态的任何更改都必须以原子方式应用。
// Synchronous method definitionvoid Foo(Parameter param, Output* output);int Bar(Parameter param);Parameter param = {// something};Output output = {};Foo(param, &output);// output can be used immediatelyint bar_output = Bar(param);// bar_output can be used immediately
许多存储和信息模块都适合此模型,例如Metrics和Storage。
页: [1]
查看完整版本: Android gd -- Gabeldorsche Bluetooth