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)时使用。

核心概念: L2CAP 帧格式的选择直接决定了通信的可靠性、实时性和吞吐量。B-frame 是基础,G-frame 用于无连接广播,I-frame 承载数据并支持重传,S-frame 负责控制信令。

3.1 B-frame(Basic Frame)—— 基础帧结构

B-frame 是 L2CAP 最基本的帧格式,适用于基本 L2CAP 模式(Basic Mode)。它仅包含一个简短的头部,用于标识数据长度和通道标识符(CID)。

B-frame 结构 Length (2 Bytes) 0x0000 - 0xFFFF Channel ID (2 Bytes) 0x0001 - 0x0040 Information Payload (0 - 65535 Bytes) 上层协议数据(如 RFCOMM 帧) B-frame 头部固定 4 字节,无控制位,无 FCS,适用于无重传需求的场景
字段 大小(字节) 描述
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)字段,用于标识上层协议类型。

G-frame 结构 Length 2 Bytes CID 0x0002 PSM 1-2 Bytes Information Payload 广播数据 G-frame 使用固定 CID = 0x0002(无连接通道),PSM 字段标识上层协议(如 0x0001 表示 SDP) 适用于蓝牙广播、服务发现等一对多场景
关键区别: G-frame 的 CID 固定为 0x0002,且必须包含 PSM 字段。B-frame 的 CID 可以是任意动态通道,且无 PSM 字段。

3.3 I-frame(Information Frame)—— 信息帧结构

I-frame 用于增强型重传模式(ERTM)和流模式(SM),在 B-frame 基础上增加了控制字(Control Word)、分段与重组标志(F&R)以及可选的帧校验序列(FCS)。I-frame 承载上层数据,并支持序列号确认和重传。

I-frame 结构(ERTM/SM 模式) Length 2B CID 2B Control Word 2 Bytes TxSeq(6b) + ReqSeq(6b) + F&R(4b) FCS 0/2B Information Payload 上层数据 Control Word 内部结构(2 Bytes = 16 bits) Bit 15-14: F&R标志 | Bit 13-8: TxSeq(发送序列号) | Bit 7-2: ReqSeq(请求序列号) | Bit 1-0: 保留 F&R = 00: 完整帧 | 01: 起始分段 | 10: 后续分段 | 11: 结束分段
字段 大小(字节) 描述
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 中的类型字段标识不同的监控功能。

S-frame 结构(监控帧,无数据载荷) Length 2 Bytes CID 2 Bytes Control Word 2 Bytes Type(2b) + ReqSeq(6b) + F&R(4b) FCS 0/2B S-frame 无 Information Payload,Length 固定为 0 或仅包含 FCS 长度 Type: 00=RR(接收就绪) | 01=REJ(拒绝) | 10=RNR(接收未就绪) | 11=SREJ(选择性拒绝)

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) 分段序列的最后一个帧,接收端应开始重组。
性能调优注意: 分段与重组会引入额外的 CPU 开销和内存拷贝。在 Linux 内核中,L2CAP 层的 SAR 操作位于 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(控制帧) 低 流控、错误恢复
调优建议: 对于高吞吐场景(如蓝牙音频 A2DP),建议使用 B-frame 并关闭 FCS 以降低 CPU 负载。对于可靠性要求高的场景(如蓝牙 HID 外设),启用 I-frame 和 FCS 以确保数据完整性。在 Linux 中,可通过 echo 1 > /sys/kernel/debug/bluetooth/hci0/l2cap/fcs 动态开关 FCS。