蓝牙开发那些事(4)——关于流控
上一章,留了个尾巴。
先来个数据包格式的图,上图中,packet header和payload header中间都出现了流控(FLOW)位。
我们之间讲HCI命令的时候,提到了HCI transport层的流控。
其实HCI上层的L2CAP层,也提供了流控服务的。
是不是概念很搞?
接下来我们具体讲一下流控。
其实只要有传输就会有流控,在我们蓝牙的体系里,数据的流动有两个关键位置,一是介于controller和host之间的HCI,这个通过hci transport层的流控实现,我们前面讲过hci 命令,本章将继续讲到hci data的流控。
二是空中包,这个流控有可以分为LC层的流控和l2cap层的流控,LC层的流控是主要方式,通过数据包中packet header的流控位实现,l2cap层的流控通过一种特殊的s-frame(rr、rej)来实现,不过一般l2cap层的流控是不打开的,下文将展开讲。
先说packet header中的流控,packet header都是controller中的LC层自动填充的,那么这里的流控位是从哪里来的呢?
既然是LC层的东西,答案自然是controller层。
Controller接收也有一个buffer的概念,自然不可能无限制接收空中包,当controller中的buffer塞满的时候,packet header中的flow位便会置位为stop。
看上去是controller内部的行为,host改变不了对不对?
实际上我们可以做一个简单的实验的,因为数据的流动方向是,空中—>controlleràhost,假如host一直不去controller取数据的时候,空中包自然会把controller塞满。
我们可以利用hci层的流控机制,之前我们在第一章中已经讲述了hci command的流控,这里就要涉及到hci data的流控。
我们知道hci acl data是双向的,由controller流向host的是remote端发过来的数据,由host流向controller的是本地发给peer端的数据。
当打开controller到host方向上的流控的时候,首先host向controller发送host buffer size command通知到controller,大概host可以存放多少包数据,之后,controller向host上传acl data的时候,controller内部会计数,把host的buffer数减去1,当host处理掉一包数据之后,会向controller下发host number of complete packets command,来通知到controller,host释放掉一包数据的空间了,由controller内部计数,把host的buffer数加上1。
假如我们故意不下发host number of complete packets command的时候,controller的内部计数变成0了以后,就无法向host上传acl data数据了。
再过一段时间,controller的接收buffer就会满,此时我们如果看空中包的话,controller回复给远端的数据包,就会看到packet header中的flow位变成stop了。
笔者以前工作中就遇到过这样的情况,host的buffer空间捉襟见肘,只能通过流控的方式来减缓对端发送的速度。
一般来讲,HOST的buffer相对controller来说要大一些,需要使用流控的场合较少。
我们抓包的数据中,就没有controller向host方向的流控,但是host向controller方向的流控是有的。
Host向controller方向的流控的方式有packet based和data block based两种,也大同小异,只是计量方式不同而已,前者是包的个数,后者是包的大小。
我们这次抓的数据包是选择的packet based的方式:
首先host在连接阶段会向controller发送read buffer size的命令
随后controller回复了最多支持的acl data packets的数量是8个。
然后我们看看当host向controller下发acl data的时候会发生什么:
图中标出的两条数据,第一条是向远端发送的一条acl data数据,(protocol栏下面是l2cap的都是这种数据),这个时候协议栈内部是有个计数的,controller的buffer数量从8会减为7。
然后跨越千山万水,在第二个标志处,controller向host返回了number of completed packets event,其中number of completed packets的数目是1,这表示controller处理掉了一包数据,可以释放buffer了,于是controller的buffer数量会从7变为8。
好了,packet header的flow我们讲完了,payload header中也有一个flow位,这是什么鬼?
Lc层的流控相当于一个总阀,l2cap层以上,通过cid区分,建立了多个逻辑链路,每个逻辑链路也可能带有flow功能的,这个flow功能就是l2cap层提供的服务了,我们下一章再讲吧。
接下来,我们看看流控在btstack的体现。
对于controller向host发送的方向来说, 在收到acl data的时候:
static void acl_handler(uint8_t *packet, int size){
// log_info("acl_handler: size %u", size);
// get info
hci_con_handle_t con_handle = READ_ACL_CONNECTION_HANDLE(packet);
hci_connection_t *conn = hci_connection_for_handle(con_handle);
uint8_t acl_flags = READ_ACL_FLAGS(packet);
uint16_t acl_length = READ_ACL_LENGTH(packet);
// ignore non-registered handle
if (!conn){
log_error( "hci.c: acl_handler called with non-registered handle %u!" , con_handle);
return;
}
// assert packet is complete
if ((acl_length + 4) != size){
log_error("hci.c: acl_handler called with ACL packet of wrong size %d, expected %u => dropping packet", size, acl_length + 4);
return;
}
#ifdef ENABLE_CLASSIC
// update idle timestamp
hci_connection_timestamp(conn);
#endif
#ifdef ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL
hci_stack->host_completed_packets = 1;
conn->num_packets_completed++;
#endif
对于host_completed_packets置1,然后会回复hci_host_num_completed_packets给controller,再把host_completed_packet置0。
对于host向controller的方向来说,当发送acl data给对端的时候,调用hci_send_acl_packet_fragments函数,其中会执行
connection->num_packets_sent++;
将hci_connection_t 结构体的num_packets_sent加1,这个值很重要,因为在判断是否可以向controller发送数据的时候,hci_can_send_prepared_acl_packet_now中是调用hci_number_free_acl_slots_for_handle函数去判断的
for (it = (btstack_linked_item_t *) hci_stack->connections; it != NULL; it = it->next){
hci_connection_t * connection = (hci_connection_t *) it;
if (hci_is_le_connection(connection)){
num_packets_sent_le += connection->num_packets_sent;
}
if (connection->address_type == BD_ADDR_TYPE_ACL){
num_packets_sent_classic += connection->num_packets_sent;
}
}
log_debug("ACL classic buffers: %u used of %u", num_packets_sent_classic, hci_stack->acl_packets_total_num);
int free_slots_classic = hci_stack->acl_packets_total_num - num_packets_sent_classic;
根据controller报告给host的buffer大小(通过hci read buffer size command取得),减去这个num_packets_sent就是还剩下的buffer,如果buffer 为0自然就不能再发了。
当收到number of completed packets event的时候,这个num_packets_sent就可以释放。
case HCI_EVENT_NUMBER_OF_COMPLETED_PACKETS:{
if (size < 3) return;
uint16_t num_handles = packet[2];
if (size != (3 + num_handles * 4)) return;
uint16_t offset = 3;
for (i=0; i<num_handles;i++){
handle = little_endian_read_16(packet, offset) & 0x0fff;
offset += 2;
uint16_t num_packets = little_endian_read_16(packet, offset);
offset += 2;
conn = hci_connection_for_handle(handle);
if (!conn){
log_error("hci_number_completed_packet lists unused con handle %u", handle);
continue;
}
if (conn->num_packets_sent >= num_packets){
conn->num_packets_sent -= num_packets;
} else {
log_error("hci_number_completed_packets, more packet slots freed then sent.");
conn->num_packets_sent = 0;
}
推荐阅读