第三章 协议栈源码结构解析:HCI层、L2CAP层、SM层、ATT/GATT层、GAP层的代码位置与核心文件说明
好,咱们直接进入正题。上一章我们把协议栈的整体框架搭起来了,这一章要动真格的了——把每一层对应的源码文件扒出来看看。说实话,我第一次接触蓝牙协议栈源码时,面对几百个文件也是一脸懵。后来摸清了门道,发现其实每层就那么几个核心文件。
我个人习惯把协议栈想象成一栋楼,HCI是地基,L2CAP是承重墙,SM是安保系统,ATT/GATT是房间里的插座和开关,GAP则是整栋楼的大门和前台。咱们一层层拆开看。
3.1 HCI层:协议栈的"翻译官"
HCI层,全称Host Controller Interface。说白了,它就是Host和Controller之间的翻译官。Host跑在CPU上,Controller是蓝牙芯片,两者通过HCI通信。
核心文件位置(以Zephyr为例):
subsys/bluetooth/host/hci_core.c— HCI核心逻辑,命令发送、事件处理都在这里subsys/bluetooth/host/hci_ecc.c— 加密相关的HCI命令,比如LE Secure Connectionsinclude/bluetooth/hci.h— HCI命令、事件、数据包格式的定义include/bluetooth/hci_vs.h— 厂商自定义的HCI命令(这个坑我踩过)
嗯,这里要注意。HCI层最容易出问题的地方是命令和事件的匹配。你发一个命令出去,Controller会回一个Command Complete或者Command Status事件。我曾经在项目中遇到一个bug,发出去的连接命令一直没收到Complete事件,查了两天才发现是HCI命令包的参数长度填错了。
我的小技巧:调试HCI层时,建议先打开HCI日志。Zephyr里配置CONFIG_BT_HCI_RAW_LOG=y,就能看到裸的HCI数据包。这比看应用层日志直观多了。
3.2 L2CAP层:数据的分包与重组
L2CAP,Logical Link Control and Adaptation Protocol。名字很长,功能其实就两个:把上层的大数据包拆成小片发给Controller,再把Controller收到的小片拼回大数据包给上层。
你想想看,蓝牙LE一次最多发251个字节,但应用层可能一次要发1KB的数据。没有L2CAP,这事就干不了。
| 文件 | 作用 |
|---|---|
subsys/bluetooth/host/l2cap.c |
L2CAP核心实现,包括连接建立、通道管理、分段重组 |
subsys/bluetooth/host/l2cap_br.c |
BR/EDR(经典蓝牙)的L2CAP实现,LE用不到 |
include/bluetooth/l2cap.h |
L2CAP通道ID、配置参数、API声明 |
我记得有一次做音频传输项目,数据总是断断续续的。后来发现是L2CAP的MTU(最大传输单元)协商出了问题。两端MTU不匹配,数据包被截断了。从那以后,我每次初始化L2CAP都会显式调用bt_l2cap_chan_connect()时指定MTU,而不是用默认值。
避坑指南:我曾经遇到过L2CAP重传超时导致断连的问题。默认的L2CAP重传超时是40秒,在某些实时性要求高的场景下太长了。建议根据业务需求调整CONFIG_BT_L2CAP_RTX_TIMEOUT。
3.3 SM层:蓝牙安全的"守门员"
SM,Security Manager。这一层负责配对、绑定、加密。说白了,就是确保你的蓝牙设备不会被别人偷听或冒充。
SM层的代码量不大,但逻辑复杂。尤其是配对过程中的密钥生成和交换,涉及很多加密算法。
subsys/bluetooth/host/smp.c— SM核心实现,包括配对流程、密钥分发subsys/bluetooth/host/smp_null.c— 空实现,用于不需要安全功能的场景include/bluetooth/smp.h— 配对能力、IO能力、密钥大小的定义
为什么SM层容易出问题?因为配对过程是个状态机,而且两端的状态必须同步。我曾经在调试一个智能锁项目时,手机和锁总是配对失败。打开日志一看,手机发起了Just Works配对,但锁端配置的是Passkey Entry。两边IO能力不匹配,自然配不上。
关键配置项:
CONFIG_BT_SMP=y— 开启SM支持CONFIG_BT_SMP_SC_ONLY=y— 只允许LE Secure Connections(推荐)CONFIG_BT_BONDABLE=y— 允许绑定,记住配对信息
3.4 ATT/GATT层:数据的"高速公路"
ATT,Attribute Protocol。GATT,Generic Attribute Profile。这两个经常一起说,因为GATT是在ATT之上定义的规范。
ATT负责数据的读写操作,GATT则定义了数据应该怎么组织——用Service、Characteristic、Descriptor这种层级结构。
| 文件 | 作用 |
|---|---|
subsys/bluetooth/host/att.c |
ATT协议实现,处理Read、Write、Notify、Indicate等操作 |
subsys/bluetooth/host/gatt.c |
GATT层实现,管理Service注册、属性表、CCC配置 |
include/bluetooth/att.h |
ATT操作码、错误码定义 |
include/bluetooth/gatt.h |
GATT API,包括bt_gatt_service_register()等 |
我个人觉得,GATT层是协议栈里最需要理解透彻的一层。因为大部分蓝牙应用开发,本质上就是在操作GATT的Service和Characteristic。
举个例子,你要做一个心率监测设备。需要注册一个Heart Rate Service,里面包含Heart Rate Measurement这个Characteristic。代码大概长这样:
// 定义心率测量Characteristic
BT_GATT_SERVICE_DEFINE(hrs_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_HRS),
BT_GATT_CHARACTERISTIC(BT_UUID_HRS_MEASUREMENT,
BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ,
NULL, read_hr_measurement, NULL),
BT_GATT_CCC(hrs_ccc_cfg_changed),
);
嗯,这里要注意CCC(Client Characteristic Configuration)的配置。CCC决定了设备是否允许发送通知。我见过很多新手忘记注册CCC回调,导致手机收不到数据。
3.5 GAP层:蓝牙设备的"社交礼仪"
GAP,Generic Access Profile。它定义了蓝牙设备如何被发现、如何建立连接、以及连接后的角色管理。
说白了,GAP就是蓝牙设备的社交礼仪——怎么打招呼(广播)、怎么交换名片(扫描)、怎么握手(连接)。
subsys/bluetooth/host/gap.c— GAP核心实现,广播、扫描、连接管理subsys/bluetooth/host/conn.c— 连接管理,包括连接参数更新、断开处理include/bluetooth/gap.h— GAP角色定义、广播类型、连接间隔等
GAP层有个容易忽略的点——广播间隔和连接间隔的配合。广播间隔太短,功耗高;太长,设备发现慢。连接间隔太小,带宽高但功耗大;太大,延迟高。
我的经验值:对于低功耗设备,广播间隔建议设在100ms到500ms之间。连接间隔则根据数据量来,如果只是传传感器数据,30ms到50ms就够用了。我曾经在一个穿戴设备项目里把连接间隔设成了7.5ms,结果电池一天就耗光了。
3.6 各层之间的协作关系
说了这么多,你可能觉得各层是独立的。其实不是,它们之间通过回调函数和消息队列紧密协作。
举个例子,当手机发来一个GATT读请求:
- Controller收到数据包,通过HCI上报给Host
- HCI层解析事件类型,发现是ACL数据,交给L2CAP
- L2CAP根据通道ID,判断是ATT通道,交给ATT层
- ATT层解析操作码,发现是Read Request,交给GATT层
- GATT层查找属性表,找到对应的Characteristic,调用注册的读回调函数
你看,一个简单的读操作,经过了5层。每一层都只做自己的事,然后通过接口传给下一层。这就是分层设计的好处——各层独立,便于移植和定制。
总结一下核心文件清单:
| 层级 | 核心源文件 | 核心头文件 |
|---|---|---|
| HCI | hci_core.c, hci_ecc.c | hci.h, hci_vs.h |
| L2CAP | l2cap.c | l2cap.h |
| SM | smp.c | smp.h |
| ATT | att.c | att.h |
| GATT | gatt.c | gatt.h |
| GAP | gap.c, conn.c | gap.h |
下一章,我们会动手搭建一个最小化的协议栈工程,把这些文件组织起来,让代码真正跑起来。到时候你会发现,理解了这些文件的位置和作用,移植工作其实没那么可怕。