外观
简易协作式任务调度器
核心理念
这是一套协作式(非抢占)优先级调度器,用优先级就绪队列+优先级位图实现快速选出最高优先级任务;支持 ISR 安全地设置事件、主循环合并 ISR 事件、延时唤醒、任务挂起/恢复/删除与可选的任务看门狗。
1. 协作式
- 任务必须 主动让出 CPU(return 或 TaskYield),调度器才会调度下一个任务;
- 不做上下文切换(任务共享主栈),调度器只负责保存最小的任务状态(如事件标志、延时),不保存CPU寄存器和栈帧;
- 优点:实现简单,资源占用少,不存在中途打断任务的情况,同时非常适合资源受限的MCU进行移植;
- 缺点:任务执行时间过长会阻塞低优先级任务,实时性差,无法保证任务被精确调度;
2. 事件驱动
- 每个任务都有一组事件位 (event_flags),每一位代表一个可触发的事件,调度器每次调度时会清空并快照当前任务的事件位,把它作为参数传给任务函数;
- 任务只有收到事件时才会执行对应逻辑;
- 事件可以通过SchedOpt_SetEvent() 设置事件,使任务进入Ready队列,在中断中通过SchedOpt_SetEventFromISR()设置事件会在主循环合并时被唤醒。
3. 优先级调度+fifo轮转
- 优先级低数字 → 高优先级,每个任务在创建时固定优先级,不支持动态调整;
- 每个优先级都有独立的就绪队列(链表),存放当前Ready状态的任务;
- 在同一优先级下,任务按先进先出顺序执行,
- 调度器从队列头取出一个任务;
- 运行任务一次;
- 如果任务依然Ready,则重新入队到队尾,这可以保证同优先级的任务不会被饿死,执行机会均等。
- 通过 ready_bitmap 快速判断哪些优先级有就绪任务;通过__builtin_ctz()或遍历快速找到最低编号的非空优先级,实现 O(1) 级别的优先级调度。
4. 延时事件/TaskDelay
- 每个任务结构体维护:
- delay_until:记录唤醒时间(绝对 Tick 值,0 表示无延时)。
- delayed_evt:记录延时到期后要置位的事件位(可叠加)。
- 调用 SchedOpt_StartDelayedEvent(tid, evt, delay_ms):
- 计算唤醒时刻 delay_until = now + delay_ms。
- 设置 delayed_evt |= evt。
- 调度器每次循环检查任务是否到期,到期则置位 event_flags 并将任务放入 READY 队列。
- 调用 SchedOpt_TaskDelay(tid, delay_ms):
- 设置 delay_until = now + delay_ms。
- 将任务从 READY 队列移除,状态改为 WAITING。
- 到期后,调度器自动恢复任务 READY 状态,使其继续运行。
数据结构设计
数据结构 | 作用 | 详细说明 |
---|---|---|
s_task_t | 任务控制块(TCB) | 每个任务对应一个 s_task_t 结构体,保存:• task_func :任务函数指针• prio :任务优先级(数值越小优先级越高)• state :任务状态(READY / RUNNING / BLOCKED / SUSPENDED)• event_flags :当前待处理事件集合• delay_until :绝对唤醒时刻(Tick 计数)• delayed_evt :延时到期时要置位的事件位• next :用于插入优先级队列的链表指针 |
s_prio_head[] / s_prio_tail[] | 每个优先级的 FIFO 队列 | 数组索引对应优先级号,分别指向该优先级的首任务和尾任务,实现同优先级的轮转调度,避免饿死。 |
s_ready_bitmap | 就绪优先级位图 | 每一位对应一个优先级,当该优先级有至少一个 READY 任务时置 1,用于快速查找最高优先级任务。 |
event_flags | 主循环事件标志 | 用于记录任务在主循环上下文下需要处理的事件,任务函数根据此标志决定执行何种逻辑。 |
isr_event_flags | ISR 写入事件标志 | 中断上下文安全的事件标志,ISR 可直接写入,主循环合并后再更新 event_flags ,避免竞争问题。 |
delay_until / delayed_evt | 延时事件管理 | 调度器每个 Tick 扫描所有任务,若当前时间 ≥ delay_until ,则将 delayed_evt 置入 event_flags 并将任务恢复到 READY 队列,实现软定时器功能。 |
state | 任务状态机 | • READY:在就绪队列中等待调度 • RUNNING:当前正在执行 • BLOCKED:延时或等待事件 • SUSPENDED:任务被挂起,不参与调度 |
移植说明
在协作式任务调度器链接中下载调度代码,并放入自己的工程中,添加时基函数即可正常使用该调度器代码;
C
/_ 系统 tick 获取(默认 HAL_GetTick) _/
uint32_t SchedOpt_GetTick(void) {
return HAL_GetTick();
}
1
2
3
4
5
6
7
2
3
4
5
6
7
在完成系统外设初始化后,调用调度器初始化代码,创建任务、分配任务事件、运行调度器,后续调度器会按照任务优先级以及任务事件循环调度执行。
C
int main(void)
{
/* USER CODE BEGIN 1 */
tid_t init_task_id = 0;
tid_t test_task_id = 0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_SPI1_Init();
MX_TIM1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
SchedOpt_Init();
init_task_id = SchedOpt_CreateTask(init_task,0);
test_task_id = SchedOpt_CreateTask(test_task,0);
SchedOpt_SetEvent(init_task_id,Init_Evt);
SchedOpt_StartDelayedEvent(test_task_id,Test_Evt,500);
SchedOpt_RunForever();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52