3. L2CAP协议数据单元(PDU)格式与通道管理
L2CAP(Logical Link Control and Adaptation Protocol)层位于HCI之上、上层协议(如RFCOMM、ATT、SMP)之下,承担数据封装、协议复用与服务质量(QoS)适配的核心职责。本章深入剖析L2CAP PDU的四种帧结构、通道标识符(CID)分配规则以及MTU/MPS概念,为后续的驱动调试与性能优化奠定基础。
工程提示:在Linux内核蓝牙驱动(
net/bluetooth/l2cap_core.c)中,PDU的解析与组装是L2CAP层最频繁执行的路径。理解帧格式的每一个字段,是定位“粘包”、“断帧”、“CID冲突”等经典问题的前提。
3.1 L2CAP PDU帧类型总览
L2CAP定义了四种PDU帧类型,分别用于不同的数据传输与控制场景:
| 帧类型 | 全称 | 主要用途 | CID范围 |
|---|---|---|---|
| B-frame | Basic Information Frame | 面向无连接/面向连接的数据传输(Basic Mode) | 动态CID(0x0040–0xFFFF) |
| I-frame | Information Frame | ERTM/SM模式下的数据重传与确认 | 动态CID |
| S-frame | Supervisory Frame | ERTM模式下的流量控制与状态报告(RR/RNR/REJ) | 动态CID |
| C-frame | Control Frame / Command Frame | 信令通道:连接建立、配置、断开(Command Reject, Connection Request, Configuration Request等) | 固定CID:0x0001(信令通道) |
关键区分:B-frame是L2CAP最基础的帧格式,所有模式都支持;I-frame和S-frame仅在增强重传模式(ERTM)和流模式(SM)下使用;C-frame始终通过固定CID 0x0001传输,不依赖动态通道。
3.2 B-frame(Basic Information Frame)详细结构
B-frame是L2CAP最基本的PDU格式,结构最为简洁,由固定长度头部和可变长度载荷组成:
B-frame 格式(字节序:Little-Endian):
+--------+--------+--------+--------+------------------+
| 长度 (2B) | CID (2B) | 载荷 (0–65535 B) |
+--------+--------+--------+--------+------------------+
| 头部 (4B) | 信息载荷 (Payload) |
+--------+--------+--------+--------+------------------+
字段说明:
- 长度(Length):2字节,表示后续载荷的字节数(不含头部自身)。
- CID(Channel Identifier):2字节,标识目标通道。
- 载荷(Payload):0–65535字节,上层协议数据(如RFCOMM帧、ATT PDU)。
边界条件:B-frame的“长度”字段为0是合法的,表示空载荷帧,用于某些协议层的保活或探测。驱动中必须正确处理零载荷帧,避免缓冲区越界。
3.3 I-frame 与 S-frame:ERTM模式下的增强帧
在ERTM(Enhanced Retransmission Mode)和SM(Streaming Mode)下,L2CAP使用I-frame和S-frame替代B-frame,以支持序列号、确认、重传和流控。
3.3.1 I-frame(Information Frame)
I-frame在B-frame基础上增加了控制字(Control Word),用于携带发送序列号(TxSeq)和接收序列号(RxSeq):
I-frame 格式:
+--------+--------+--------+--------+--------+--------+------------------+
| 长度 (2B) | CID (2B) | 控制字 (2B) | 载荷 (可变) |
+--------+--------+--------+--------+--------+--------+------------------+
| 头部 (4B) | 控制字段 (2B) | 信息载荷 |
+--------+--------+--------+--------+--------+--------+------------------+
控制字位域(16位):
Bit 15–14: 帧类型 (01 = I-frame)
Bit 13: 保留
Bit 12–8: TxSeq (发送序列号, 0–31)
Bit 7: SAR (分段与重组标志)
Bit 6–0: RxSeq (接收序列号, 0–31)
3.3.2 S-frame(Supervisory Frame)
S-frame不携带用户数据载荷,仅用于控制信令:
S-frame 格式:
+--------+--------+--------+--------+--------+--------+
| 长度 (2B) | CID (2B) | 控制字 (2B) | 无载荷 |
+--------+--------+--------+--------+--------+--------+
控制字位域(16位):
Bit 15–14: 帧类型 (10 = S-frame)
Bit 13–12: 功能码 (00=RR, 01=RNR, 10=REJ, 11=SREJ)
Bit 11–8: 保留
Bit 7–0: RxSeq (接收序列号)
功能码含义:
- RR (Receiver Ready): 确认已收到 N 号帧,准备接收更多。
- RNR (Receiver Not Ready): 确认已收到 N 号帧,但接收方忙。
- REJ (Reject): 拒绝 N 号帧,请求重传。
- SREJ (Selective Reject): 选择性拒绝 N 号帧,仅重传该帧。
调试要点:在Linux内核中,I-frame和S-frame的解析位于
l2cap_data_channel()函数。当遇到l2cap_ertm_reassembly_sdu()报错时,应优先检查控制字中的TxSeq/RxSeq是否连续,以及SAR标志是否正确。
3.4 C-frame(Control Frame / Command Frame)
C-frame用于L2CAP信令通道(CID = 0x0001),负责通道的建立、配置、断开等管理操作。其格式与B-frame类似,但载荷部分为命令/响应结构:
C-frame 格式(信令通道):
+--------+--------+--------+--------+------------------+
| 长度 (2B) | CID=0x0001 | 命令载荷 (可变) |
+--------+--------+--------+--------+------------------+
命令载荷内部结构(可包含多个命令):
+--------+--------+--------+--------+------------------+
| 代码 (1B) | 标识符 (1B) | 数据长度 (2B) | 命令数据 (可变) |
+--------+--------+--------+--------+------------------+
常用命令代码:
- 0x01: Command Reject
- 0x02: Connection Request
- 0x03: Connection Response
- 0x04: Configure Request
- 0x05: Configure Response
- 0x06: Disconnection Request
- 0x07: Disconnection Response
- 0x08: Echo Request / Response
- 0x0A: Information Request / Response
工程实践:在
l2cap_sig_channel()函数中,内核通过l2cap_build_conf_req()和l2cap_parse_conf_req()处理配置协商。调试时,可通过btmon或hcidump抓取C-frame,检查标识符(Identifier)是否匹配请求与响应。
3.5 CID(通道标识符)分配规则
CID是L2CAP层识别通道的唯一标识,分配规则严格遵循蓝牙核心规范:
| CID范围 | 分配类型 | 用途 | 示例 |
|---|---|---|---|
| 0x0000 | 保留 | 无效通道(NULL Identifier) | — |
| 0x0001 | 固定 | L2CAP信令通道(Signaling Channel) | C-frame传输 |
| 0x0002 | 固定 | 无连接通道(Connectionless Channel) | 广播数据 |
| 0x0003–0x000F | 保留 | 未来规范使用 | — |
| 0x0010–0x003F | 固定 | AMP(Alternate MAC/PHY)管理通道 | AMP Manager |
| 0x0040–0xFFFF | 动态 | 动态分配的数据通道(面向连接) | RFCOMM、ATT、SMP |
常见错误:在驱动开发中,若动态CID分配器(
l2cap_global_chan_by_scid())返回NULL,通常是因为CID已被占用或未正确注册。检查l2cap_chan_add()调用时是否传入了正确的本地SCID和远程DCID。
3.6 MTU与MPS概念
MTU(Maximum Transmission Unit)和MPS(Maximum PDU Payload Size)是L2CAP层两个极易混淆但至关重要的参数:
| 参数 | 全称 | 定义 | 作用范围 | 典型值 |
|---|---|---|---|---|
| MTU | Maximum Transmission Unit | L2CAP层单帧所能承载的最大上层SDU大小(含头部) | 通道级别(per-channel) | 默认672字节,最大65535字节 |
| MPS | Maximum PDU Payload Size | L2CAP层单帧中载荷(Payload)的最大字节数(不含头部) | 通道级别(per-channel) | 默认672字节,受Controller Buffer限制 |
核心区别:
- MTU 是上层视角:上层协议(如RFCOMM)一次最多能发送多少数据给L2CAP。
- MPS 是底层视角:L2CAP一次最多能封装多少数据放入一个PDU中发送给Controller。
- 当上层SDU大小 > MPS时,L2CAP必须进行分段(Segmentation)与重组(Reassembly)(SAR机制)。
配置协商:在L2CAP Configuration Request/Response中,双方交换MTU和MPS值。最终生效的MTU = min(本地MTU, 对端MTU);MPS = min(本地MPS, 对端MPS, Controller Buffer大小)。在
l2cap_chan结构体中,对应字段为imtu(入向MTU)、omtu(出向MTU)和mps。
3.7 工程实践:PDU解析与调试要点
- 帧类型识别:在
l2cap_chan_recv()中,通过bt_cb(skb)->l2cap.btf或直接解析控制字的高2位判断帧类型(01=I-frame, 10=S-frame, 00=B-frame)。 - CID合法性检查:在
l2cap_global_chan_by_scid()中,若返回NULL,打印scid值并检查是否在0x0040–0xFFFF范围内。 - MTU/MPS越界:在
l2cap_do_send()中,若SDU长度 >chan->omtu,应返回-EMSGSIZE;若PDU载荷 >chan->mps,必须触发SAR分段。 - 信令超时:C-frame的标识符(Identifier)必须唯一,且响应中的Identifier必须与请求一致。在
l2cap_sig_timeout()中,超时后应重发或断开通道。
内核调试命令:
# cat /sys/kernel/debug/bluetooth/hci0/l2cap — 查看当前所有L2CAP通道的CID、MTU、MPS、状态。# btmon -w trace.log — 抓取完整的HCI+L2CAP数据流,用于离线分析PDU格式。