bluetoothd: device cache
目录结构
关于蓝牙数据的存储,在bluez的doc目录下有一个文件(settings-storage.txt)作出了说明。
Adapter and remote device info are read form the storage during object initialization. Write to storage is performed immediately on every value
change.Default storage directory is /var/lib/bluetooth. This can be adjusted by the –localstatedir configure switch. Default is –localstatedir=/var.
翻译:在对象初始化期间,从存储中读取适配器和远程设备信息。每次值更改时都会立即写入存储。默认存储目录为 /var/lib/bluetooth
。可以通过 –localstatedir 配置开关进行调整。默认值为 --localstatedir=/var
。
首次运行bluetoothd的存储目录结构:
1 | $ tree bluetooth |
注意:40:80:E1:50:1D:47
是本地蓝牙适配器的MAC地址作为目录名称。若本地存在多个蓝牙适配器,那么就有多个这样的目录。
执行扫描、配对、连接后的存储目录结构:
1 | bluetooth/ |
注意:执行扫描操作后,cache
目录就被创建,同时扫描到的蓝牙设备也会以其MAC地址名称作为文件名。若配对、连接指定蓝牙设备成功后,则会创建以其MAC地址名称作为目录名,而且该目录下也会创建 attribute 和 info两个文件。
cache目录下蓝牙设备文件被创建的位置,通过device_store_cached_name()函数:
1 | void device_store_cached_name(struct btd_device *dev, const char *name) |
settings文件
settings文件包含了适配器的一个组的信息,例如:
1 | [General] |
- Alias:适配器的别名,默认值是主机名
- Discoverable:适配器的可被发现的属性
settings文件以及内容被创建位置,通过 src/adapter.c
中的store_adapter_info()函数
1 | static void store_adapter_info(struct btd_adapter *adapter) |
info文件
info文件包含了与蓝牙设备关联的多组信息,例如:
1 | [General] |
info文件的创建位置:
1 | static void store_link_key(struct btd_adapter *adapter, |
方法实现
在 org.bluez.Adapter1
接口文档中提供了三个接口函数:
- StartDiscovery()
- StopDiscovery()
- RemoveDevice()
当 StartDiscovery()
函数被执行时,cache目录下就会有新的蓝牙设备文件被创建,而当 StopDiscovery()
函数被执行时,cache目录下就会有蓝牙设备文件被删除,RemoveDevice()
函数被执行时,<controller address>
目录下有已配对或已连接过的蓝牙设备文件夹会被删除。
注意:当蓝牙设备被自动移除时,cache目录的蓝牙设备文件部分还是保留,为什么会保留呢?
src/adapter.c
关于适配器相关的方法实现:
1 | // 适配器注册的方法 |
StartDiscovery方法的实现
在src/adapter.c给出了StartDiscovery方法的实现:
1 | static DBusMessage *start_discovery(DBusConnection *conn, |
RemoveDevice方法的实现
在src/adapter.c给出了RemoveDevice方法的实现:
1 | // RemoveDevice方法的回调函数 |
StopDiscovery方法的实现
1 | // StopDiscovery方法的回调函数 |
临时定时器
在/src/main.conf文件中,有关于扫描到的设备临时(temporary)时间定义
1 | [General] |
在/src/main.c文件中,关于临时(temporary)时间也有宏定义:
1 |
/src/device.c源文件关于临时(temporary)定时器的记录
1 | static bool device_disappeared(gpointer user_data) |
btd_device_set_temporary()函数的参数temporary的值为 true时,device将被标记为临时设备,并通过 set_temporary_timer(device, btd_opts.tmpto)
启动了一个临时的定时器。
/src/adapter.c关于临时(temporary)定时器的使用
1 | void btd_adapter_remove_device(struct btd_adapter *adapter, |
但是最终的问题还是没有得到解决:bluetoothd程序接收到客户端(例如:bluetoothctl)的请求: StartDiscovery
方法时,蓝牙设备被发现后,通过什么样的方式通知了客户端呢?同样的,客户端发送 RemoveDevice
方法时,bluetoothd又是通过什么方式告知客户端?最后一点,当附近设备被扫描到后,会作为一个临时设备,当临时时间最大时间到达后,在哪里执行删除临时设备操作,以及删除临时设备后,以什么方式通知客户端?
疑难杂症
通过使用bluez提供的gdbus接口编写了一个客户端(从这边文章可以得知:{% post_link 'bluetoothctl-client-tool' %}
),并且为了测试连接的稳定性编译了一个自动化测试函数:
- 第一步:
bt_open()
打开蓝牙并进行初始化操作 - 第二步:
bt_start_discovery()
开始执行扫描操作 - 第三步:进入while() 循环体,完成指定的连接次数
- 等待目标蓝牙设备被扫描到
- 连接目标蓝牙设备
- 移除目标蓝牙设备,并进入下一轮循环体
- 第四步:退出while() 循环体,
bt_cancel_discovery()
停止扫描操作 - 第五步:
bt_close()
关闭蓝牙并释放相关的资源
执行测试次数为100次,总会出现那么几次以上的连接超时失败:Timeout was reached
。查看日志,发现执行connect操作后,立即出现了删除临时设备的操作(包括目标蓝牙设备),接下来就是连接超时报错了。
如果没有扫描到目标蓝牙设备,直接去连接,则会报这个错误:
1 | GDBus.Error:org.freedesktop.DBus.Error.UnknownObject: Method "Connect" with signature "" on interface "org.bluez.Device1" doesn't exist |
所以关键的问题是找到执行connect操作会不会导致bluetoothd进程执行清理临时设备操作!
查询 org.bluez.Device1
的 Connect 方法:
1 | static const GDBusMethodTable device_methods[] = { |
虽然不能100%确认设备删除的时间点,但是我查到了相关调用关系,最终设备被移除时一定会调用函数 g_dbus_unregister_interface()
,那么我需要向上找出关于这个函数被调用的位置:
1 | # src/adapter.c |
当然,最好将bluetoothd的日志保存到文件,但是也有一个烦恼的问题,就是日志文件太大了,动不动就是10M以上的大小,排查很困难。
1 | /usr/libexec/bluetooth/bluetoothd -d -n > bluetoothd.log 2>&1 & |