背景:
最近在项目上遇到一个蓝牙的问题,项目基于嵌入式linux开发,系统上也没有集成bluetoothd服务,移植了一个Bluez的代码,开发了一个BLE的功能,与手机app端进行通信;基本功能没有什么问题,但根据反馈,连接稳定性不好,通常是有干扰,最大的问题就是,有概率在连接断开之后,手机APP再也连接不上,需要重跑嵌入式程序,手机APP才能重新建立连接;
我也没有接触过蓝牙协议栈的代码,带着问题去分析了一下源代码,也不知道理解得对不对,各位看官如果发现我理解不对的地方,还请告知在下;
分析:
从开发工程师上了解到,他使用的是开源的Bluez-5.63版本的代码,使用里面的peripheral目录下的例子修改的;我看了一下,peripheral下一共就没有几个文件:
其中, attach.c,通过名称可以大概看出应该是和蓝牙绑定相关的,里面实现了打开串口的操作,还有就是通过串口的fd进行ioctl,对蓝牙的一些标志的设置和复位。
static int open_serial(const char *path);
static int attach_proto(const char *path, unsigned int proto,
unsigned int flags);
void attach_start(void);
void attach_stop(void);
efivars.c是efi相关的变量操作,具体是干啥用的我也没搞懂,主要提供了对变量的读取和写入的两个接口;
int efivars_read(const char *name, uint32_t *attributes,
void *data, size_t size);
int efivars_write(const char *name, uint32_t attributes,
const void *data, size_t size);
log.c是类似提供了日志的功能,但是好像代码中也没有用上这个接口,所以这个好像也没什么用;
void log_open(void);
// 打开了/dev/kmsg节点,返回kmsg_fd
void log_close(void);
//关闭kmsg_fd
剩下的main.c、gap.c和gatt.c是这次分析的主要对象,大部分的功能都是在这几个文件中实现的,我这里根据main的流程去分析。
int main(int argc, char *argv[])
{
int exit_status;
if (getpid() == 1 && getppid() == 0)
is_init = true;
mainloop_init();
printf("Bluetooth periperhal ver %s\n", VERSION);
prepare_filesystem();
if (is_init) {
uint8_t addr[6];
if (efivars_read("BluetoothStaticAddress", NULL,
addr, 6) < 0) {
printf("Generating new persistent static address\n");
if (util_getrandom(addr, sizeof(addr), 0) < 0) {
perror("Failed to get random static address");
return EXIT_FAILURE;
}
/* Overwrite the MSB to make it a static address */
addr[5] = 0xc0;
efivars_write("BluetoothStaticAddress",
EFIVARS_NON_VOLATILE |
EFIVARS_BOOTSERVICE_ACCESS |
EFIVARS_RUNTIME_ACCESS,
addr, 6);
}
gap_set_static_address(addr);
run_shell();
}
log_open();
gap_start();
if (is_init)
attach_start();
exit_status = mainloop_run_with_signal(signal_callback, NULL);
if (is_init)
attach_stop();
gap_stop();
log_close();
return exit_status;
}
先看看大的流程,main里面调用了mainloop_init进行了初始化,这个mainloop_init没有在main里面定义,这里先不分析。
prepare_filesystem和efivars_read、efivars_write主要的作用就是看有没有设置BluetoothStaticAddress这个变量,如果有,就读取这个变量的内容来设置一个静态的MAC地址。设置静态MAC地址这里调用的是gap_set_static_address函数进行设置,gap_set_static_address函数是在gap.c里定义的,其实就是把addr赋值给gap.c里的全局变量而已,完全可以自己修改这个全局数组实现。
static uint8_t static_addr[6] = { 0x90, 0x78, 0x56, 0x34, 0x12, 0xc0 };
void gap_set_static_address(uint8_t addr[6])
{
memcpy(static_addr, addr, sizeof(static_addr));
printf("Using static address %02x:%02x:%02x:%02x:%02x:%02x\n",
static_addr[5], static_addr[4], static_addr[3],
static_addr[2], static_addr[1], static_addr[0]);
}
然后就是gap_start函数了,之后是mainloop_run_with_signal,看上去是进入主循环了,main函数会在这里阻塞住,直到signal的到来,然后函数返回,执行gap_stop函数。
重点看一下gap_start函数,这个函数在gap.c中定义
static struct mgmt *mgmt = NULL;
void gap_start(void)
{
mgmt = mgmt_new_default();
if (!mgmt) {
fprintf(stderr, "Failed to open management socket\n");
return;
}
if (!mgmt_send(mgmt, MGMT_OP_READ_COMMANDS,
MGMT_INDEX_NONE, 0, NULL,
read_commands_complete, NULL, NULL)) {
fprintf(stderr, "Failed to read supported commands\n");
return;
}
}
这个函数中,使用mgmt_new_default创建了mgmt,这个mgmt是个全局变量指针,文件中有大量的mgmt_send函数会使用这个指针作为句柄进行消息的发送,看上去像是一个类似socket的东西;
mgmt_new_default函数在src/shared/mgmt.c中定义
struct mgmt *mgmt_new_default(void)
{
struct mgmt *mgmt;
union {
struct sockaddr common;
struct sockaddr_hci hci;
} addr;
int fd;
fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
BTPROTO_HCI);
if (fd < 0)
return NULL;
memset(&addr, 0, sizeof(addr));
addr.hci.hci_family = AF_BLUETOOTH;
addr.hci.hci_dev = HCI_DEV_NONE;
addr.hci.hci_channel = HCI_CHANNEL_CONTROL;
if (bind(fd, &addr.common, sizeof(addr.hci)) < 0) {
close(fd);
return NULL;
}
mgmt = mgmt_new(fd);
if (!mgmt) {
close(fd);
return NULL;
}
mgmt->close_on_unref = true;
return mgmt;
}
从函数中可以看出,mgmt确实和socket有关,它绑定了一个socket的fd句柄,这个socket是使用PF_BLUETOOTH创建的,绑定的地址中,使用了HCI_CHANNEL_CONTROL作为hci的channel,从这些信息中分析,这个mgmt应该就是可蓝牙管理控制相关的句柄。
struct mgmt {
int ref_count;
int fd;
bool close_on_unref;
struct io *io;
bool writer_active;
struct queue *request_queue;
struct queue *reply_queue;
struct queue *pending_list;
struct queue *notify_list;
unsigned int next_request_id;
unsigned int next_notify_id;
bool need_notify_cleanup;
bool in_notify;
void *buf;
uint16_t len;
uint16_t mtu;
mgmt_debug_func_t debug_callback;
mgmt_destroy_func_t debug_destroy;
void *debug_data;
};
从mgmt的结构体定义中看到,除了这个fd之外,结构体中一个io的数据,还有几个队列的数据,还有mtu等等的参数。其中这个io是比较关键的数据,可以进一步分析一下这个数据:
mgmt->io = io_new(fd);
if (!mgmt->io) {
free(mgmt->buf);
free(mgmt);
return NULL;
}
mgmt初始化的过程中,把socket得到的fd进行了一次io_new操作,映射到这个mgmt->io上了,这个io_new函数在bluez中有好几个定义的地方,追查代码发现,应该是对应src/shared/io-mainloop.c中定义的:
struct io *io_new(int fd)
{
struct io *io;
if (fd < 0)
return NULL;
io = new0(struct io, 1);
io->fd = fd;
io->events = 0;
io->close_on_destroy = false;
if (mainloop_add_fd(io-&