4. L2CAP通道状态机

L2CAP通道状态机是蓝牙协议栈中连接管理的核心机制。它定义了通道从创建到销毁的完整生命周期,每个状态对应一组允许的操作和事件处理函数。理解状态机是调试L2CAP连接问题的前提——90%的L2CAP故障都可以通过状态不一致或非法状态转换来解释。

4.1 状态定义与枚举

内核在 include/net/bluetooth/l2cap.h 中定义了 l2cap_chan_state 枚举,共包含7个状态:

枚举值 状态名 含义 典型停留场景
BT_CLOSED CLOSED 通道未建立或已关闭 创建后初始状态;断开后复位状态
BT_CONNECT WAIT_CONNECT 等待连接请求完成 主动发起连接后,等待对端响应
BT_CONNECT2 CONNECT 连接请求已到达,等待用户响应 被动收到连接请求,等待上层确认
BT_CONFIG CONFIG 配置协商进行中 交换MTU、Flush Timeout等参数
BT_OPEN OPEN 通道已建立,可收发数据 正常数据传输阶段
BT_DISCONN WAIT_DISCONNECT 等待断开完成 发起断开后,等待对端确认
BT_DISCONN2 WAIT_DISCONNECT2 断开请求已到达,等待用户确认 被动收到断开请求,等待上层处理
调试提示: 在 /sys/kernel/debug/bluetooth/hci0/l2cap 中可以查看每个通道的当前状态。状态值以数字形式显示,对应枚举顺序:0=CLOSED, 1=WAIT_CONNECT, 2=CONNECT, 3=CONFIG, 4=OPEN, 5=WAIT_DISCONNECT, 6=WAIT_DISCONNECT2。

4.2 状态转换图

下图展示了L2CAP通道的完整状态转换路径。箭头上的标签表示触发转换的事件或操作:

L2CAP通道状态转换图 L2CAP通道状态转换图 CLOSED WAIT_CONNECT CONNECT CONFIG OPEN WAIT_DISCONNECT WAIT_DISCONNECT2 l2cap_chan_connect() 收到L2CAP_ConnectReq 收到L2CAP_ConnectRsp(成功) l2cap_chan_accept() 配置协商完成 l2cap_chan_disconnect() 收到L2CAP_DisconnectReq 收到L2CAP_DisconnectRsp l2cap_chan_close() 说明: 虚线表示异步事件(收到对端报文),实线表示本地操作(API调用)。所有状态均可通过错误或超时直接回到CLOSED(图中未全部画出)。
关键观察: 状态转换是单向的——通道一旦离开CLOSED,必须经过完整的建立序列(WAIT_CONNECT/CONNECT → CONFIG → OPEN)才能进入数据传输阶段。不存在从OPEN直接跳回CLOSED的路径,必须经过WAIT_DISCONNECT或WAIT_DISCONNECT2。

4.3 内核代码中的状态枚举

在 net/bluetooth/l2cap_core.c 中,状态枚举定义如下:

/* include/net/bluetooth/l2cap.h */
enum l2cap_chan_state {
    BT_CLOSED,          /* 0: 通道关闭 */
    BT_CONNECT,         /* 1: 等待连接确认(主动方) */
    BT_CONNECT2,        /* 2: 等待用户接受(被动方) */
    BT_CONFIG,          /* 3: 配置协商中 */
    BT_OPEN,            /* 4: 通道已打开 */
    BT_DISCONN,         /* 5: 等待断开确认(主动方) */
    BT_DISCONN2,        /* 6: 等待用户确认断开(被动方) */
};

状态转换由 l2cap_chan_set_state() 函数执行,该函数会同时更新通道的 state 字段并触发状态变更通知:

/* net/bluetooth/l2cap_core.c */
static void l2cap_chan_set_state(struct l2cap_chan *chan, u32 state)
{
    u32 old_state = chan->state;

    chan->state = state;

    /* 状态变更通知回调 */
    if (chan->ops && chan->ops->state_change)
        chan->ops->state_change(chan, state, old_state);

    /* 特殊状态处理 */
    switch (state) {
    case BT_CLOSED:
        /* 清理资源,通知上层 */
        break;
    case BT_OPEN:
        /* 通知上层通道已就绪 */
        break;
    default:
        break;
    }
}

4.4 各状态的核心行为

每个状态对应一组允许的操作和超时处理。下表总结了关键行为:

状态 允许的输入事件 超时处理 常见调试问题
CLOSED 连接请求、监听请求 无超时 资源泄漏导致通道无法释放
WAIT_CONNECT 连接响应、断开请求 L2CAP_CONN_TIMEOUT (40秒) 对端无响应,超时后未正确回退
CONNECT 用户接受/拒绝、断开请求 L2CAP_CONN_TIMEOUT (40秒) 上层应用未及时调用accept()
CONFIG 配置请求/响应、断开请求 L2CAP_CONF_TIMEOUT (10秒) MTU或Flush Timeout协商失败
OPEN 数据帧、断开请求、配置请求 无(或由上层控制) 数据收发异常、流控问题
WAIT_DISCONNECT 断开响应、断开请求 L2CAP_DISC_TIMEOUT (10秒) 对端不响应断开请求
WAIT_DISCONNECT2 用户确认断开 L2CAP_DISC_TIMEOUT (10秒) 上层未及时处理断开通知

4.5 状态机与通道操作的关系

L2CAP通道的每个操作函数都会检查当前状态是否合法。例如,发送数据的核心函数 l2cap_chan_send() 只在 BT_OPEN 状态下执行:

int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
{
    /* 状态检查:只有OPEN状态才能发送数据 */
    if (chan->state != BT_OPEN)
        return -ENOTCONN;

    /* 后续处理:分段、加密、发送 */
    ...
}
常见陷阱: 在调试时,如果发现 l2cap_chan_send() 返回 -ENOTCONN,不要立即认为是链路断开。先检查通道状态——很可能通道卡在CONFIG阶段(MTU协商未完成)或WAIT_DISCONNECT阶段(断开流程未结束)。使用 btmon 或 hcidump 抓取L2CAP信令可以快速定位。

4.6 状态机调试方法

实际工程中,状态机问题通常表现为“通道卡死”或“状态不一致”。以下是推荐的调试步骤:

  1. 查看当前状态: 通过debugfs读取通道状态:cat /sys/kernel/debug/bluetooth/hci0/l2cap
  2. 检查超时定时器: 使用 cat /proc/timer_list | grep l2cap 查看是否有挂起的L2CAP定时器
  3. 抓取信令包: 使用 btmon -t -w l2cap_trace.log 捕获完整的L2CAP信令交互
  4. 分析状态转换: 在 l2cap_chan_set_state() 中添加动态调试:echo 'file l2cap_core.c line +p' > /sys/kernel/debug/dynamic_debug/control
  5. 检查引用计数: 使用 cat /sys/kernel/debug/bluetooth/hci0/l2cap_le 查看LE通道状态,确认是否有引用泄漏
实战案例: 某蓝牙耳机项目出现“连接后无法发送音频”问题。通过debugfs发现通道状态为BT_CONFIG(3),而非预期的BT_OPEN(4)。进一步抓包发现对端在配置协商中发送了无效的MTU参数(0x0000),导致内核配置协商循环无法完成。修复方案:在配置响应处理中增加参数合法性校验,拒绝非法MTU值并触发断开。