低功耗蓝牙ble开发(一)——bluez介绍及源码分析

bluez-5.56源码

https://mirrors.edge.kernel.org/pub/linux/bluetooth/bluez-5.64.tar.xz

0、bluez5开发

​ bluez5主要提供基于HCI和基于DBUS的接口,基于HCI的接口主要用于更细致控制蓝牙硬件模块,而基于DBUS的接口提供大量的蓝牙上层协议,能更好的管理蓝牙。

不使用glib、dbus开发的话,想要开发周边设备可以借鉴btmgmt.c和btgatt-server.c、btgatt-client.c

使用glib、dbus开发的话,可以参考gatt-service.c

user space

APP:上层应⽤程序

Pluseaudio/pipewire:A2DP的组件

Bluetoothd: 蓝⽛守护进程

Bluez: 包括Bluez tool跟Bluez lib

kernel space

内核代码包含以下⼏部分

driver/bluetooth

net/bluetooth

include/net/bluetooth

在应用层,bluetoothd进程管理蓝牙,bluetooctl提供用户操作命令,通过dbus守护进程进行IPC通信,使用基于dbus的bluez开发主要就是调用bluez提供的dbus接口方法以及属性。

1、bluez5源代码组织架构

BlueZ5源代码中有很多目录和文件:

android/ - 用于替代android中bluedroid的android版本bluez源码。

attrib/ - 包含gatttool 源码以及与gatt attribute相关的代码,gatttool程序入口为gatttool.c。

btio/ - 通过的标准socket接口与BlueZ5 kernel模块通信?。

client/ - bluetoothctl源码,用于与 bluetoothd 交互,程序入口为main.c。

doc/ - BlueZ5 API文档。

emulator/ - 与bluetooth虚拟controller工具相关的代码。从字面意思可以推测出它是用来仿真的,通过阅读代码发现它是仿真controller的,通过btdev_create函数来创建一个虚拟的蓝牙controller设备。 kernel也是支持的,"/dev/vhci"这个设备被虚拟为一个虚拟的蓝牙设备。

gdbus/ - BlueZ5自带的内部gdbus源码。

gobex/ - Blue5自带的内部gobex库源码。

lib/ - libbluetooth.so 源码,提供BlueZ4 API,用来支持某些第三方应用。

monitor/ - btmon源码, 程序入口为main.c,用于监视和调试蓝牙流量。

obexd/ - obexd源码,程序入口为src/main.c。

peripheral/ - 与蓝牙ble的GATT相关的代码?。

plugins/ - BlueZ5插件源码(neard,autopair等插件)。包含 BlueZ 插件的源代码,这些插件扩展了 BlueZ 的功能,例如支持不同的蓝牙协议和配置文件。

profiles/ - BlueZ5蓝牙上层协议(a2dp,hid等)源码。每个子目录实现一个特定的蓝牙配置文件。

src/ - bluetoothd源码,程序入口为main.c,包含核心 BlueZ 守护进程 bluetoothd 的源代码。这里是主要的实现逻辑,包括设备管理、服务发现、配置和连接管理等。

​ src/shared/ - 包含在多个模块中共享的源代码。

​ src/adapter - 包含适配器管理相关的代码。

test/ - 测试蓝牙功能的py脚本,通过dbus接口和bluetoothd进行通信。 dbus提供支持python和c语言的接口。

tools/ - Bluez5测试工具集源码。包含各种命令行工具的源代码,例如 hcitoolgatttoolbtmgmt 等。这些工具用于与蓝牙设备进行交互和调试。也算是使用实列代码,可以参照工具的功能参考源码。

unit/ - PTS测试相关的一些代码?。

README / INSTALL - 配置,编译,安装Bluez5的说明。

Makefile.obexd - 定义obexd编译规则,此文件被include于Makefile.am中。

Makefile.plugins - 定义BlueZ5的plugins(neard,autopair等)的编译规则, 此文件被include于Makefile.am中。

Makefile.tools - 定义BlueZ5测试工具集的编译规则,此文件被include于 Makefile.am中。

Makefile.am - 定义了Bluez5的编译规则。用于automake工具,生成 Makefile.in文件。

Makefile.in - 用于configure脚本,生成最终的Makefile文件。

configure.ac - 用于autoconf工具,生成configure脚本。

configure - 配置编译选项,生成最终的Makefile文件,以及config.h文件。

2、bluez5核心代码分析

​ bluez核心代码在src目录下,入口函数是main.c,bluez5编译后会生成bluetoothd可执行文件,加载配置文件bluetooth.conf,下面进入main.c函数开始分析(注:bluez源码很多地方是异步操作的,看源码时经常需要根据关键词到处搜索注册的回调函数在哪里被触发)

(1)src/main.c/main函数分析
c 复制代码
int main(int argc, char *argv[])
{
         ......
         //glib库函数创建一个死循环线程用于处理事件
         event_loop = g_main_loop_new(NULL, FALSE);
         //注册信号接收处理ctrl+c等中断信号
         signal = setup_signalfd();
         ......
         //gbus总线注册bluez服务端,该函数很重要,后面详细分析
         if (connect_dbus() < 0) {
                   error("Unable to get on D-Bus");
                   exit(1);
         }
         ......
         //bluez控制器初始化,该函数很重要,后面详细分析
         if (adapter_init() < 0) {
                   error("Adapter handling initialization failed");
                   exit(1);
         }
         //bluez设备及应用层协议注册
         btd_device_init();
         btd_agent_init();
         btd_profile_init();
         ......
         //开始死循环并处理事件
         g_main_loop_run(event_loop);
         ......
}
(2)src/main.c/connect_dbus函数分析
c 复制代码
static int connect_dbus(void)
{
         ......
         //dbus是linux多进程通信的一种方式,bluez在dbus守护进程上注册了一个连接,用于接收其它bluez用户进程的消息,处理后并返回应答消息。具体的dbus实现原理需要同志们自己找资料理解,因为bluez源码依赖dbus编程。
         //清除dbus错误标志
         dbus_error_init(&err);
         //向总线型dbus守护进程申请名为BLUEZ_NAME ("org.bluez")的连接,其它进程可以使用dbus的dbus_message_new_method_call函数发消息给这个名为BLUEZ_NAME的连接。该函数很重要,后面详细分析
         conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, BLUEZ_NAME, &err);
         ......
         //申请的连接的指针赋值给全局的连接指针
         set_dbus_connection(conn);
         //向dbus总线注册监听连接断开的信号,当连接断开时守护进程会发信号给这个已经注册的监听信号,然后回调disconnected_dbus函数处理断开连接
         g_dbus_set_disconnect_function(conn, disconnected_dbus, NULL, NULL);
         //向dbus总线注册bluez的管理对象,该对象的对象路径是"/",用户可以编程调用该接口下面的方法获取bluez创建的所有对象以及每个对象下面的所有方法,该函数很重要,后面详细分析
         g_dbus_attach_object_manager(conn);
         ......
}
(3)gdbus/mainloop.c/g_dbus_setup_bus函数分析
c 复制代码
DBusConnection *g_dbus_setup_bus(DBusBusType type, const char *name,
                                                                 DBusError *error)
{
         ......
         //向dbus总线申请一个连接
         conn = dbus_bus_get(type, error);
         ......
         //向dbus总线申请给该连接起一个名字,其它进程就可以向改名字的连接发送dbus消息了。另外注册了一些dbus的观察函数,具体用处不是特别清楚
         if (setup_bus(conn, name, error) == FALSE) {
         ......
}
(4)gdbus/object.c/g_dbus_attach_object_manager函数分析
c 复制代码
gboolean g_dbus_attach_object_manager(DBusConnection *connection)
{
         ......
         //向dbus总线的"/"路径的对象注册一个包含该对象的所有数据存放地址的指针,便于程序在其它地方使用dbus_connection_get_object_path_data函数通过"/"路径获取指针。该函数很重要,后面详细分析
         data = object_path_ref(connection, "/");
         ......
         //该对象添加名为DBUS_INTERFACE_OBJECT_MANAGER宏定义的接口,接口注册了一个方法和一个信号,当其它进程使用dbus_message_new_method_call函数发消息给该对象的该接口方法时,generic_table里的generic_message会被调用,然后manager_methods会被调用,具体的调用流程后面会详细介绍
         add_interface(data, DBUS_INTERFACE_OBJECT_MANAGER,
                                               manager_methods, manager_signals,
                                               NULL, data, NULL);
     ......
}
(5)gdbus/object.c/object_path_ref函数分析
c 复制代码
static struct generic_data *object_path_ref(DBusConnection *connection,const char *path) 
{
    ......
     //判断"/"路径的对象有没有已经申请存放该对象的存储空间,如果已经申请了,返回的data指针不为空
     if (dbus_connection_get_object_path_data(connection, path,
                                                    (void *) &data) == TRUE) {
     ......
     //向dbus总线注册该对象,当其它进程使用dbus_message_new_method_call函数发消息给该对象时,generic_table里的generic_message会被调用
     if (!dbus_connection_register_object_path(connection, path,
                                                    &generic_table, data)) {
     ......
     //把对象添加到对象链表data->objects里,便于程序在其它地方查找所有对象
     invalidate_parent_data(connection, path);
     ......
}
(6)gdbus/object.c/generic_message函数分析
c 复制代码
static DBusHandlerResult generic_message(DBusConnection *connection,
                                               DBusMessage *message, void *user_data)
{
	......
     Dbus接收从其他进程发来的消息,过滤出消息的接口名字
     interface = dbus_message_get_interface(message);
     //根据接口名字查找之前已经注册的对应接口
     iface = find_interface(data->interfaces, interface);
     ......
     //检查完参数后开始处理消息
     return process_message(connection, message, method,iface->user_data);
}
(7)gdbus/object.c/process_message函数分析
c 复制代码
static DBusHandlerResult process_message(DBusConnection *connection,
                            DBusMessage *message, const GDBusMethodTable *method,
                                                                 void *iface_user_data)
{ 
	......
     //回调已经注册的接口对应的方法,具体使用例子在后面详细介绍
     reply = method->function(connection, message, iface_user_data);
     ......
     //发送应答消息
     g_dbus_send_message(connection, reply);
     ......
}
(8)src/adapter.c/adapter_init函数分析
c 复制代码
int adapter_init(void)
{ 
	......
     //构造访问hci接口的结构体,该函数很重要,后面详细分析
     mgmt_master = mgmt_new_default();
     ......
	//hci接口向内核注册的蓝牙控制器发送命令,收到对应返回命令时调用read_version_complete回调函数进行处理。
	if (mgmt_send(mgmt_master, MGMT_OP_READ_VERSION,
	                                 MGMT_INDEX_NONE, 0, NULL,
                                 read_version_complete, NULL, NULL) > 0)
}
(9)src/shared/mgmt.c/mgmt_new_default函数分析
c 复制代码
 struct mgmt *mgmt_new_default(void)
{
	......
     //使用socket打开hci驱动接口,和open访问驱动设备类似。明白这个需要自己去了解linux蓝牙驱动的相关知识,这里不介绍
     fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                                                       BTPROTO_HCI);
     ......
     //构造发送hci命令的结构体。该函数很重要,后面详细分析
     mgmt = mgmt_new(fd);
}
(10)/src/shared/mgmt.c/mgmt_new函数分析
c 复制代码
struct mgmt *mgmt_new(int fd)
{
	......
     //使用glib库的io注册功能把fd描述符添加监控列表里,详情看bluez5.50/src/shared/io-glib.c/io_new函数的实现
     mgmt->io = io_new(fd);
     ......
     //使用glib库的io功能添加监控fd读变化的功能,当hci接口有应答数据时,fd读操作状态改变,can_read_data回调函数被调用。详情看bluez5.50/src/shared/io-glib.c/ io_set_read_handler函数的实现
     if (!io_set_read_handler(mgmt->io, can_read_data, mgmt, NULL)) 
}
(11)src/shared/mgmt.c/can_read_data函数分析
c 复制代码
static bool can_read_data(struct io *io, void *user_data)
{  
	......
     //根据hci接口返回的应答数据的事件类型进行不同处理
     switch (event) {
     ......
     //当返回的事件类型是命令完成时执行如下函数
     request_complete(mgmt, cc->status, opcode, index, length - 3,
                                                    mgmt->buf + MGMT_HDR_SIZE + 3);
     ......
     //当返回的事件类型是命令状态时执行如下函数
     request_complete(mgmt, cs->status, opcode, index, 0, NULL);
     ......
     //其它事件时执行如下的通知函数,作用是通知已经注册到mgmt->notify_list链表的通知操作
     process_notify(mgmt, event, index, length,
                                                    mgmt->buf + MGMT_HDR_SIZE);
}
(12)src/shared/mgmt.c/request_complete函数分析
c 复制代码
static void request_complete(struct mgmt *mgmt, uint8_t status,uint16_t opcode, uint16_t index,uint16_t length, const void *param) 
{ 
	 ......
     //执行在hci接口发送命令时已经注册的命令完成回调函数,例如read_version_complete就是一个注册的回调函数
     request->callback(status, length, param, request->user_data);
     ......
     //使用glib的io功能把fd的写功能添加进去,当fd能写时,把需要发送的hci命令发送给驱动
     wakeup_writer(mgmt);
}
(13)src/adapter.c/read_version_complete函数分析
c 复制代码
static void read_version_complete(uint8_t status, uint16_t length,
                                               const void *param, void *user_data)
{  
	......
     //注册蓝牙控制器个数增加时的通知信号,当驱动发现蓝牙控制器被插入时,发送事件通知命令,process_notify函数会被执行,在该函数中会调用已经注册的index_added函数申请新的适配器存储空间
     mgmt_register(mgmt_master, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE,
                                                    index_added, NULL, NULL);
     ......
     //发送hci命令获取蓝牙适配器(蓝牙控制器)的个数,收到响应命令时回调read_index_list_complete函数
     if (mgmt_send(mgmt_master, MGMT_OP_READ_INDEX_LIST,
                                 MGMT_INDEX_NONE, 0, NULL,
                                 read_index_list_complete, NULL, NULL) > 0)
     ......
}
(14)src/adapter.c/read_index_list_complete函数分析
c 复制代码
static void read_index_list_complete(uint8_t status, uint16_t length,
                                               const void *param, void *user_data)
{     
    ......

     //获取适配器的个数,然后根据index值注册所有的适配器

     index_added(index, 0, NULL, NULL);

     ......
}

(15)bluez5.50/src/adapter.c/index_added函数分析

c 复制代码
static void index_added(uint16_t index, uint16_t length, const void *param,
                                                                 void *user_data)
{  
    ......
     //查找适配器链表adapter_list里有没有编号为index的适配器存在
     adapter = btd_adapter_lookup(index);
     ......
     //根据index创建新的适配器
     adapter = btd_adapter_new(index);
     ......
     //把新创建的适配器指针添加到适配器链表里,方便其它地方查询
     adapter_list = g_list_append(adapter_list, adapter);
     ......
     //发送hci接口命令给驱动层,获取index对应的适配器的详细信息,读取到结果后会回调read_info_complete函数
     if (mgmt_send(mgmt_master, MGMT_OP_READ_INFO, index, 0, NULL,
                                           read_info_complete, adapter, NULL) > 0)
     ......
}
(16)src/adapter.c/read_info_complete函数分析
c 复制代码
static void read_info_complete(uint8_t status, uint16_t length,
                                               const void *param, void *user_data)
{   
	......
     //设置适配器的bdaddr等,然后为适配器注册dbus对象,对象路径为"/org/bluez/hci%d",对象接口名为"org.bluez.Adapter1",接口方法为adapter_methods
     err = adapter_register(adapter);
     ......
     //注册设备发现函数,当驱动层扫描到新的蓝牙设备时,发送通知给应用层,应用层会触发can_read_data函数,然后调用到process_notify函数,然后调用到mgmt->notify_list链表,而device_found_callback函数是添加在mgmt->notify_list链表上的,所以会被执行
     mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_FOUND,
                                                    adapter->dev_id,
                                                    device_found_callback,
                                                    adapter, NULL);
     ......
}
(17)src/adapter.c/adapter_register函数分析
c 复制代码
static int adapter_register(struct btd_adapter *adapter)
{
      ......
     //指定适配器注册到dbus总线上的对象的路径
     adapter->path = g_strdup_printf("/org/bluez/hci%d", adapter->dev_id);
     //为适配器注册dbus对象,对象路径为"/org/bluez/hci%d",对象接口名为"org.bluez.Adapter1",接口方法为adapter_methods,接口属性为adapter_properties
     if (!g_dbus_register_interface(dbus_conn,
                                           adapter->path, ADAPTER_INTERFACE,
                                           adapter_methods, NULL,
                                           adapter_properties, adapter,
                                           adapter_free)) {
     ......
     //把当前适配器添加到adapters链表
     adapters = g_slist_append(adapters, adapter);
     ......
}
相关推荐
做人不要太理性24 分钟前
【C++】深入哈希表核心:从改造到封装,解锁 unordered_set 与 unordered_map 的终极奥义!
c++·哈希算法·散列表·unordered_map·unordered_set
程序员-King.33 分钟前
2、桥接模式
c++·桥接模式
chnming198737 分钟前
STL关联式容器之map
开发语言·c++
程序伍六七1 小时前
day16
开发语言·c++
热爱生活的五柒1 小时前
vscode利用ofExtensions插件可以调试单进程Openfoam,但是不能调试mpi多进程案例
ide·vscode·编辑器
小陈phd1 小时前
Vscode LinuxC++环境配置
linux·c++·vscode
火山口车神丶1 小时前
某车企ASW面试笔试题
c++·matlab
qystca2 小时前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
是阿建吖!2 小时前
【优选算法】二分查找
c++·算法
Ajiang28247353044 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++