Nimble controller 的Scheduler是由一个32K的timer来驱动的,一个tick是30.52us,完全执行在timer的isr里,所以timer callback里要做的事情不能太耗时(超过一个tick),否则scheduler就不准确了,如下为scheduler的主函数:1
2/* Initialize cputimer for the scheduler */
os_cputime_timer_init(&g_ble_ll_sched_timer, ble_ll_sched_run, NULL);
时间单位
- Tick:由clock决定,对于32K clock来说,一个tick就是 1000000us/(32*1024) = 30.52us.
- Slot:1250个毫秒
- Period:N * Slot,N是可配置的
- Epoch:M * Period,M是可配置的
Scheduler在调度的时候,有时要将Connection event放在Period的边界,scanning/initiating/advertising是尽可能发生在没有被用到的Period内。1
2
3/* Time per BLE scheduler slot */
#define BLE_LL_SCHED_USECS_PER_SLOT (1250)
#define BLE_LL_SCHED_32KHZ_TICKS_PER_SLOT (41) /* 1 tick = 30.517 usecs */
调度单位
每一个可被调度的单位叫做Sched item, 这些item放在FIFO的List中,每当timer到期后Scheduler就从List里拉一个出来做执行。
每一个item有如下几个属性:
- schedule type,类型
- enquened,是否入Q,入Q就代表是Ready,等待被执行
- remainder,剩余的执行时间
- start_time, 开始执行时间
- end_time, 结束执行时间
- cb_args, item执行函数的参数
- sched_cb, item被调度到的时候的执行函数
如下是相关的code segment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 struct ble_ll_sched_item
{
uint8_t sched_type;
uint8_t enqueued;
uint8_t remainder;
uint32_t start_time;
uint32_t end_time;
void *cb_arg;
sched_cb_func sched_cb;
TAILQ_ENTRY(ble_ll_sched_item) link;
};
/* Types of scheduler events */
#define BLE_LL_SCHED_TYPE_ADV (1)
#define BLE_LL_SCHED_TYPE_SCAN (2)
#define BLE_LL_SCHED_TYPE_CONN (3)
#define BLE_LL_SCHED_TYPE_AUX_SCAN (4)
#define BLE_LL_SCHED_TYPE_DTM (5)
#define BLE_LL_SCHED_TYPE_PERIODIC (6)
#define BLE_LL_SCHED_TYPE_SYNC (7)
调度原则
- Sched item之间没有优先级的关系,完全按照Sched item的start_time为序,一个一个的执行。
- 调度到某个item时就直接执行这个item的sched_cb,并将这个item从调度列表上删掉,callback执行完后,再起timer,等待下一个item的start_time到期。
- item在插入到调度列表的时候就已经排好序了。
- 在插入新的Adv Sched item的时候,新的Adv要避开每一个跟他有overlap的item,直到找到一个完全跟它没有overlap的位置,这意味着在有同样的start time的情况下,先入list的优先级高。
- 以插入一个Adv Schedule为例,假如Scheduler的ReadyList里有4个item,它们的排列如下。那么下图中绿色的item就是可以直接插入的item,而红色的item则需要一直往后移动,一直移到一个能插入>的地方,也许到list最后才能有它的一席之地。
- 在一个adv event执行完成后,需要再计算下一个adv event起来的时间,请参考下图。
Item中的Delay(0~10ms)指的是这个adv event最大允许delay的时间。
- 以插入一个Adv Schedule为例,假如Scheduler的ReadyList里有4个item,它们的排列如下。那么下图中绿色的item就是可以直接插入的item,而红色的item则需要一直往后移动,一直移到一个能插入>的地方,也许到list最后才能有它的一席之地。
- 但是对于type为CONN的Sched是有点优先级的
- 正在execute的type为CONN的Sched的优先级最高,要新建立的conn的sched (ble_ll_sched_slave_new())如果刚好跟正在execute的conn overlap,那直接就取消新的这个conn。
1
2
3
4
5/* The schedule item must occur after current running item (if any) */
if (ble_ll_sched_overlaps_current(sch)) {
OS_EXIT_CRITICAL(sr);
return rc;
}
- 正在execute的type为CONN的Sched的优先级最高,要新建立的conn的sched (ble_ll_sched_slave_new())如果刚好跟正在execute的conn overlap,那直接就取消新的这个conn。
- 在收到CONN_IND的时候,为了能尽快在windowOffset到达时发包出去回应对方,会将跟他overlap的type为CONN的sched 给盖掉
- 但这包如果type是其他的type,则会继续往后shift找合适的位置,但如果是回应CONN_IND的第一包的话,不在规定时间发包出去,link就建立不起来,这里还是有点问题。
1
2
3
4
5
6 if (ble_ll_sched_is_overlap(sch, entry)) {
/* If we overlap with a connection, we re-schedule */
if (ble_ll_sched_conn_overlap(entry)) {
break;
}
}
更正 #4
感谢jaydenh215的指导,上图中关于Adv reschedule的图例欠妥,正确的符合code行为的插入Sched的示意图如下。
如果超过了max_delay,那插入也是失败的,如下图,虽然在#3的前面有位置给sch插入,但是因为overdue了,所以插入会失败。