3. L2CAP 数据包格式:B-frame、G-frame、I-frame、S-frame 的详细结构
L2CAP(Logical Link Control and Adaptation Protocol)层位于 HCI 之上,负责为上层协议(如 RFCOMM、ATT)提供数据封装、多路复用、分段重组(SAR)及服务质量(QoS)管理。根据不同的传输需求,L2CAP 定义了四种核心帧格式:B-frame(基本帧)、G-frame(分组帧)、I-frame(信息帧)和 S-frame(监控帧)。其中 I-frame 和 S-frame 仅在启用增强型重传模式(ERTM)或流模式(SM)时使用。
3.1 B-frame(Basic Frame)—— 基础帧结构
B-frame 是 L2CAP 最基本的帧格式,适用于基本 L2CAP 模式(Basic Mode)。它仅包含一个简短的头部,用于标识数据长度和通道标识符(CID)。
| 字段 | 大小(字节) | 描述 |
|---|---|---|
| Length | 2 | 表示后续信息载荷的字节长度(不包括头部自身)。取值范围 0x0000 ~ 0xFFFF,最大 65535 字节。 |
| Channel ID (CID) | 2 | 通道标识符,用于多路复用。固定通道:0x0001(信令)、0x0005(SMP)。动态通道:0x0040 ~ 0xFFFF。 |
| Information Payload | 0 ~ 65535 | 上层协议数据单元(PDU),如 RFCOMM 帧、ATT 命令等。 |
3.2 G-frame(Group Frame)—— 分组帧结构
G-frame 用于无连接通道(Connectionless Channel),支持一对多广播通信。它在 B-frame 基础上增加了协议/服务复用层(PSM)字段,用于标识上层协议类型。
3.3 I-frame(Information Frame)—— 信息帧结构
I-frame 用于增强型重传模式(ERTM)和流模式(SM),在 B-frame 基础上增加了控制字(Control Word)、分段与重组标志(F&R)以及可选的帧校验序列(FCS)。I-frame 承载上层数据,并支持序列号确认和重传。
| 字段 | 大小(字节) | 描述 |
|---|---|---|
| Length | 2 | 载荷长度(不包括头部和 FCS)。 |
| Channel ID | 2 | 动态通道标识符。 |
| Control Word | 2 | 包含 TxSeq(发送序列号,6 bits)、ReqSeq(请求确认序列号,6 bits)、F&R 标志(4 bits)。 |
| FCS | 0 或 2 | 帧校验序列,可选。启用时使用 CRC-16 校验。 |
| Information Payload | 0 ~ 65535 | 上层协议数据。 |
3.4 S-frame(Supervisory Frame)—— 监控帧结构
S-frame 不携带上层数据,仅用于 L2CAP 层的控制信令,如确认(RR)、拒绝(REJ)、接收未就绪(RNR)等。S-frame 的头部结构与 I-frame 类似,但 Control Word 中的类型字段标识不同的监控功能。
3.5 分段与重组标志(F&R)详解
F&R(Fragmentation & Reassembly)标志位于 I-frame 和 S-frame 的 Control Word 中,占用 4 bits。它指示当前帧在分段序列中的位置,是 L2CAP 层实现大包传输的关键机制。
| F&R 值(二进制) | 含义 | 描述 |
|---|---|---|
| 00 | 完整帧(Unsegmented) | 当前帧是完整的 L2CAP PDU,未分段。 |
| 01 | 起始分段(First Segment) | 分段序列的第一个帧,后续还有更多分段。 |
| 10 | 中间分段(Continuation Segment) | 分段序列的中间帧,既不是第一个也不是最后一个。 |
| 11 | 结束分段(Last Segment) | 分段序列的最后一个帧,接收端应开始重组。 |
net/bluetooth/l2cap_core.c 的 l2cap_segment() 和 l2cap_reassemble() 函数中。频繁的分段会导致吞吐量下降,建议通过调整 MTU 大小来避免不必要的分段。
3.6 四种帧格式对比总结
| 特性 | B-frame | G-frame | I-frame | S-frame |
|---|---|---|---|---|
| 使用模式 | Basic Mode | Connectionless | ERTM / SM | ERTM / SM |
| 头部大小 | 4 字节 | 6~7 字节 | 6~8 字节 | 6~8 字节 |
| Control Word | 无 | 无 | 有(TxSeq + ReqSeq + F&R) | 有(Type + ReqSeq + F&R) |
| FCS | 无 | 无 | 可选(2 字节) | 可选(2 字节) |
| 数据载荷 | 有 | 有 | 有 | 无 |
| 重传支持 | 否 | 否 | 是(通过序列号) | 是(通过监控命令) |
| 典型用途 | 普通数据通信 | 广播/服务发现 | 可靠数据传输 | 流控与错误恢复 |
3.7 Linux 内核中帧格式的代码实现
在 Linux 蓝牙协议栈中,L2CAP 帧的构造和解析主要在 net/bluetooth/l2cap_core.c 中实现。以下是 I-frame 头部构造的核心代码片段:
/* net/bluetooth/l2cap_core.c - I-frame 头部构造 */
static struct sk_buff *l2cap_create_iframe(struct l2cap_chan *chan,
struct sk_buff *skb,
u16 control)
{
struct l2cap_hdr *lh;
int len;
/* 计算总长度:头部 + 控制字 + 可选 FCS + 载荷 */
len = L2CAP_HDR_SIZE + L2CAP_CTRL_SIZE + skb->len;
if (chan->fcs == L2CAP_FCS_CRC16)
len += L2CAP_FCS_SIZE;
/* 分配 sk_buff */
skb_push(skb, L2CAP_HDR_SIZE + L2CAP_CTRL_SIZE);
skb_reset_transport_header(skb);
/* 填充 L2CAP 头部 */
lh = l2cap_hdr(skb);
lh->len = cpu_to_le16(skb->len - L2CAP_HDR_SIZE);
lh->cid = cpu_to_le16(chan->dcid);
/* 填充 Control Word */
put_unaligned_le16(control, &skb->data[L2CAP_HDR_SIZE]);
/* 可选:计算并追加 FCS */
if (chan->fcs == L2CAP_FCS_CRC16)
l2cap_add_fcs(skb);
return skb;
}
btmon 或 hcidump 可以实时捕获 L2CAP 帧并解析其头部字段。例如,btmon -w trace.log 会将所有 HCI 和 L2CAP 数据包记录到文件,通过 btmon -r trace.log 回放分析。
3.8 性能调优:帧格式选择对吞吐量的影响
不同的帧格式在吞吐量、延迟和 CPU 开销方面有显著差异。以下是基于 Linux 内核实测数据的对比:
| 帧格式 | 最大吞吐量(理论) | CPU 开销 | 适用场景 |
|---|---|---|---|
| B-frame | ~2.1 Mbps(BR/EDR) | 低 | 音频、传感器数据 |
| I-frame(无 FCS) | ~1.8 Mbps | 中 | 文件传输、OTA 升级 |
| I-frame(有 FCS) | ~1.5 Mbps | 高 | 医疗设备、金融支付 |
| S-frame | N/A(控制帧) | 低 | 流控、错误恢复 |
echo 1 > /sys/kernel/debug/bluetooth/hci0/l2cap/fcs 动态开关 FCS。