Gabeldorsche Architecture
谷歌宣布新版本的 Gabeldorsche,即自版本 11 以來在 Android 中使用的藍牙堆棧,用Rust重寫的BT Stack (準確的說是一些BT底層核心)
內(nèi)容
本文檔概述了開發(fā) Gabeldorsche (GD) 藍牙堆棧時所做的一些架構(gòu)注意事項。
threading-model
首先,GD 堆棧不建立在線程的概念之上。相反,它適用于Handlers. 但是,由于GD最終運行在OS上,在實現(xiàn)Handler抽象之前,它仍然需要與進程和線程進行交互。
processes
一般來說。GD運行時環(huán)境中存在三種類型的進程:
應(yīng)用程序進程:包括第三方應(yīng)用程序、其他系統(tǒng)組件(例如音頻和電信服務(wù))與通過各種 RPC/IPC 方法(例如 Binder、Socket IPC、gRPC、DBUS)定義的藍牙堆棧進程API 交互。等等,使用 AIDL 或 Protobuf 等語言。對于 Android 應(yīng)用程序,盡管 API 是在 AIDL 中定義的,但一些樣板代碼包含在通過代碼公開的 Java 庫中,這些代碼frameworks/base/core/java/android/bluetooth作為Android SDK發(fā)布給開發(fā)人員。
硬件抽象層 (HAL) 進程:來自供應(yīng)商分區(qū)的一個或多個進程,因此依賴于硬件。它們通過Binder、Socket IPC、DBUS等RPC/IPC方法定義的一組硬件抽象API,使用HIDL等語言與藍牙棧進程進行交互。在 Android 上,這將是實現(xiàn) HIDL API(例如IBluetoothHci和IBluetoothAudioProvider )的 HAL 進程。
藍牙堆棧進程:通常是在主機控制器接口 (HCI) 之上和藍牙 SDK API 之下實現(xiàn)各種藍牙協(xié)議和配置文件的單個進程。一方面,它為來自應(yīng)用程序進程的請求提供服務(wù);另一方面,它通過與HAL 進程的交互來轉(zhuǎn)發(fā)這些請求。在 Android 上,此進程通常在 AID_BLUETOOTH(通常為 1002)下運行,進程名稱為“com.android.bluetooth”。該過程在 Java 中啟動,并通過 JNI 加載本機庫。其他不使用Java虛擬機的系統(tǒng)可能有純原生進程。由于各種原因,這個進程中可能存在多個線程。GD棧完全運行在這個進程中。
threads-in-bluetooth-stack-process藍牙堆棧進程中的線程
目前,藍牙堆棧中線程優(yōu)化的目標是:
- 盡可能減少線程數(shù)以簡化同步
- 在單獨的線程中執(zhí)行阻塞 I/O 操作
- 嘗試將 I/O 操作移動到輪詢模式,以便我們可以使用事件驅(qū)動方法在主線程上與其交互
- 將警報和計時器機制移動到它們的調(diào)用線程以避免單獨的警報線程
- 隔離單個組件,以便每個組件都可以單獨啟動和停止,而無需終止主線程
- 首選數(shù)據(jù)傳遞而不是線程間的數(shù)據(jù)共享,以減少鎖定和競爭條件
經(jīng)過上述優(yōu)化后,我們在本機代碼中留下了五種主要類型的線程:
主線程:藍牙堆棧中的主力。線程的執(zhí)行上下文被進一步劃分為Handlers駐留在 individual 中的那些Modules。如果運行平臺上的性能受到限制,則可以將該線程進一步劃分為更小的線程。部署者只需要將處理程序綁定到不同的線程,這不應(yīng)該影響整體操作。
JNI 線程:在原生線程中,我們將 Java 層視為一個單獨的應(yīng)用程序,因為它的線程模塊是完全不同的。因此,我們在這兩層之間放置了一個線程來緩沖任何阻塞操作。
HCI 線程(或其他 HW I/O 線程):該線程負責硬件 I/O 的死機,并且可能會阻塞。因此它有自己的獨立線程以避免阻塞主線程。
Audio worker thread:負責對執(zhí)行時序精度要求更高的音頻編解碼操作。這樣的worker有自己獨立的線程,避免被主線程影響。
Socket I/O線程:與使用該BluetootSocket接口的各種應(yīng)用程序進行通信。由于潛在的 I/O 延遲,它有單獨的線程。
data-flow-diagram數(shù)據(jù)流程圖
不同組件之間的函數(shù)調(diào)用被抽象為通過隊列傳遞的控制包(函數(shù)閉包)。組件之間的數(shù)據(jù)流是通過隊列發(fā)送的數(shù)據(jù)包,使用Reactor. 它們將合并到每個組件的輸入隊列中。我們定義了三種類型的隊列:
非阻塞隊列:當用戶試圖在空的時候出隊,或者滿的時候入隊,它會立即返回。線程內(nèi)的所有排隊都必須是非阻塞的,否則會死鎖。
阻塞隊列:當用戶嘗試在隊列為空時出隊,或在隊列已滿時入隊,它將阻塞,直到其他線程使隊列可寫/可讀。它可以用作流量控制機制,以避免來自用戶線程的數(shù)據(jù)包過多。
Leaky queue:與非阻塞隊列相同,但當它已滿并且用戶嘗試入隊時它會刷新。這對音頻編碼很有用。

building-blocks建筑模塊
module模塊
GD 中的代碼被打包到名為Module. 一個模塊標準化了 GD 代碼的以下方面:
-
依賴關(guān)系:一個模塊通過實現(xiàn)來提供自己對其他模塊的依賴關(guān)系
ListDependencies() -
生命周期:模塊必須實現(xiàn)
Start()和Stop()生命周期方法 -
線程模塊:
Module基類Handler通過GetHandler() -
指標:A
Module可以通過 dumpsys 轉(zhuǎn)儲其狀態(tài)信息DumpState()
請參閱其定義:https://android.googlesource.com/platform/system/bt/+/master/gd/module.h
handler處理程序
類似于android.os.Handler,bluetooth::os::Handler提供順序執(zhí)行上下文,同時從執(zhí)行代碼中隱藏線程的概念。
通過將執(zhí)行上下文劃分為更小的區(qū)域,Handler可以通過以下方式使開發(fā)受益:
- 由于順序執(zhí)行上下文,較少需要鎖定
- 較小的上下文導(dǎo)致更容易管理代碼流
- 與線程分離使系統(tǒng)部署者可以更自由地調(diào)整底層線程分配。例如,對于沒有全線程實現(xiàn)的實時操作系統(tǒng),a
Handler可用于提供近線程執(zhí)行上下文
當然,使用 也有缺點Handler,開發(fā)者應(yīng)該謹慎對待:
警告:雖然多個Handler可以綁定到同一個線程,Handler但不保證代碼在不同線程上的順序執(zhí)行Handler,即使它們在同一個線程上。
警告:Handlers綁定到同一線程的線程之間的鎖定可能會導(dǎo)致死鎖
警告:必須在兩者之間復(fù)制數(shù)據(jù)Handler以避免死鎖和競爭條件
請參閱其定義: https: //android.googlesource.com/platform/system/bt/+/master/gd/os/handler.h
reactor反應(yīng)堆
bluetooth::os:Reactor實現(xiàn)Reactor 設(shè)計模式,其中并發(fā)事件由同步事件多路分解器多路分解為通過Dispatcher注冊的請求處理程序列表。
在通用的 Linux 操作系統(tǒng)(例如 Android)中,我們使用文件描述符(例如eventfd for Handler、timerfd forAlarm和socketfd for 數(shù)據(jù)處理管道)來實現(xiàn)它。在文件描述符的上下文中,事件分為兩種類型:
-
OnReadReady:表示多路分解器有一些事件供處理程序使用,并且處理程序可以從底層事件隊列中讀取至少一個事件。這通常與
EPOLLIN、EPOLLHUP、EPOLLRDHUP和相關(guān)聯(lián)EPOLLERR。 -
OnWriteReady:表示多路分解器已準備好從該處理程序中消耗更多事件,并且該處理程序可以將至少一個事件寫入底層隊列。這通常與
EPOLLOUT.
這種模式自然會產(chǎn)生從一個隊列到另一個隊列的背壓,而無需任何額外的信號機制。當在像我們這樣的網(wǎng)絡(luò)堆棧中使用時,它簡化了信令代碼流。
請參閱其定義:https://android.googlesource.com/platform/system/bt/+/master/gd/os/reactor.h
的純數(shù)據(jù)用例Reactor是 a Reactive Queue,請參閱其定義: https: //android.googlesource.com/platform/system/bt/+/master/gd/os/queue.h
Packet-Definition-Language-PDL
數(shù)據(jù)包解析和序列化一直是任何網(wǎng)絡(luò)堆棧的重要組成部分。它通常是與遠程設(shè)備交互的第一段代碼。過去,這是使用STREAM_TO_UNIT8或 之類的宏手動實現(xiàn)的UINT8_TO_STREAM。這種手動方法既乏味又容易出錯。為了解決這個問題,我們創(chuàng)建了一種數(shù)據(jù)包定義語言,將網(wǎng)絡(luò)數(shù)據(jù)包結(jié)構(gòu)定義為位級別。C++ 標頭和 Python 綁定將從其代碼生成器自動生成,對代碼生成器的任何修復(fù)都將系統(tǒng)地應(yīng)用于生成的所有數(shù)據(jù)包代碼。
示例 PDL:
// Comments
little_endian_packets // Whether this packet is big or small endian
// Include header from other C++ header files
custom_field SixBytes : 48 "packet/parser/test/" // expect six_bytes.h
custom_field Variable "packet/parser/test/" // expect variable.h
// A packet
packet 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 size
footer : 8, // fiexed size footer of 8 bits
}
packet Child : Parent {
field_name : 16, // addition field append after Parent
}
// an enum of 4 bits
enum 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
模塊之間的調(diào)用約定
asynchronous-server_client-model異步服務(wù)器-客戶端模型
對于模塊之間的大多數(shù)通信,開發(fā)人員應(yīng)該采用通用模型中的異步服務(wù)器-客戶端模型,例如:
// Define callback function type
using CallbackFunction = std::function<void(ParamType)>;
// Asynchronous method definition
bool Foo(Parameter param, CallbackFunction callback);
// A new callback is passed for each asynchronous call
// Always prefer lambda over std::bind
CallbackFunction callback = [this] {
// 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
}
許多協(xié)議和配置文件都適合這樣的模型,例如AclManager和L2cap。
synchronous-database-model同步數(shù)據(jù)庫模型
在某些情況下,異步服務(wù)器-客戶端模型是不可行的。在這種情況下,開發(fā)人員可以考慮同步數(shù)據(jù)庫模型。在這樣的模型中,操作可以在互斥鎖的幫助下同步發(fā)生。當方法返回時,更改必須反映到所有依賴項。內(nèi)部狀態(tài)的任何更改都必須以原子方式應(yīng)用。
// Synchronous method definition
void Foo(Parameter param, Output* output);
int Bar(Parameter param);
Parameter param = {
// something
};
Output output = {};
Foo(param, &output);
// output can be used immediately
int bar_output = Bar(param);
// bar_output can be used immediately
許多存儲和信息模塊都適合此模型,例如Metrics和Storage。