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通道的完整状态转换路径。箭头上的标签表示触发转换的事件或操作:
关键观察: 状态转换是单向的——通道一旦离开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 状态机调试方法
实际工程中,状态机问题通常表现为“通道卡死”或“状态不一致”。以下是推荐的调试步骤:
- 查看当前状态: 通过debugfs读取通道状态:
cat /sys/kernel/debug/bluetooth/hci0/l2cap - 检查超时定时器: 使用
cat /proc/timer_list | grep l2cap查看是否有挂起的L2CAP定时器 - 抓取信令包: 使用
btmon -t -w l2cap_trace.log捕获完整的L2CAP信令交互 - 分析状态转换: 在
l2cap_chan_set_state()中添加动态调试:echo 'file l2cap_core.c line +p' > /sys/kernel/debug/dynamic_debug/control - 检查引用计数: 使用
cat /sys/kernel/debug/bluetooth/hci0/l2cap_le查看LE通道状态,确认是否有引用泄漏
实战案例: 某蓝牙耳机项目出现“连接后无法发送音频”问题。通过debugfs发现通道状态为BT_CONFIG(3),而非预期的BT_OPEN(4)。进一步抓包发现对端在配置协商中发送了无效的MTU参数(0x0000),导致内核配置协商循环无法完成。修复方案:在配置响应处理中增加参数合法性校验,拒绝非法MTU值并触发断开。