第7章 任务
核心主题就是:LiteOS 中任务如何创建、如何启动、如何调度、如何延时、挂起、恢复和删除。
一、本章总体框架
可以合起来理解成一条主线:
先学会“创建任务”,再学会“管理任务”。
也就是:
- 创建任务
- 硬件初始化;
- 创建单任务;
- 创建多任务;
- 理解任务控制块 TCB;
- 理解 LiteOS 启动流程。
- 任务管理
- 任务基本概念;
- 调度器基本概念;
- 任务状态与状态迁移;
- 常用任务函数;
- 常见错误代码;
- 任务设计要点;
- 任务管理实验。
本章最重要的几个关键词是:
任务、任务栈、任务控制块、任务ID、任务优先级、就绪态、运行态、阻塞态、调度器、PendSV、LOS_TaskCreate、LOS_TaskDelay、LOS_TaskSuspend、LOS_TaskResume、LOS_TaskDelete。
二、为什么 LiteOS 要使用任务?
在裸机程序中,我们通常把所有逻辑都写在 while(1) 里面。
比如:
while (1)
{
扫描按键();
控制LED();
读取传感器();
发送串口数据();
}这种方式简单,但问题是:
- 所有功能耦合在一起;
- 某个函数执行时间过长会影响其他功能;
- 很难保证实时性;
- 功能变多后代码很乱;
- 不方便维护。
LiteOS 使用任务机制后,可以把不同功能拆成不同任务:
LED_Task();
Key_Task();
Sensor_Task();
Uart_Task();
Network_Task();每个任务独立运行,由 LiteOS 调度器统一管理。
所以,任务机制的意义是:
把复杂程序拆成多个相对独立的执行单元,提高程序结构清晰度、实时性和可维护性。
三、任务的基本概念
在 LiteOS 中,任务可以理解为一个独立运行的函数。每个任务都在独立环境中运行,并且每个任务都有自己的任务栈。系统中任务越多,占用的 SRAM 就越多,因此一个系统能运行多少任务,取决于 SRAM 大小和任务栈配置。
一个任务通常具有以下内容:
| 内容 | 含义 |
|---|---|
| 任务入口函数 | 任务真正执行的函数 |
| 任务栈 | 保存任务运行过程中的局部变量、函数调用、上下文等 |
| 任务控制块 TCB | 保存任务状态、优先级、ID、栈指针等信息 |
| 任务ID | 任务创建成功后系统返回的唯一标识 |
| 任务优先级 | 决定任务被调度的先后顺序 |
| 任务状态 | 表示任务当前处于就绪、运行、阻塞或退出等状态 |
LiteOS 里通常认为:
一个任务就是一个线程。
四、任务函数应该怎么写?
LiteOS 中的任务主体通常是一个无限循环函数,不能直接返回。
任务示例:
static void Test1_Task(void)
{
while (1)
{
LED2_TOGGLE;
LOS_TaskDelay(1000);
}
}这里有两个重点。
1. 任务必须是死循环
任务一般写成:
while (1)
{
...
}因为任务是长期存在的执行单元,不应该像普通函数那样执行完就返回。
2. 任务循环中必须有阻塞或延时
例如:
LOS_TaskDelay(1000);如果任务中没有延时、信号量等待、消息队列等待等阻塞操作,那么这个任务会一直占用 CPU。
特别是高优先级任务,如果不主动阻塞,就会导致低优先级任务永远得不到执行机会。
所以要记住一句话:
每个任务的循环中必须有阻塞点,否则可能长期占用 CPU,影响其他任务运行。
五、硬件初始化:为什么先初始化硬件?
在创建任务之前, 先了解硬件初始化。
这是因为任务里要控制 LED、串口、按键等外设,所以必须先完成硬件初始化。
硬件初始化放在 BSP_Init() 函数中:
static void BSP_Init(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
LED_GPIO_Config();
}在后面的完整工程中,还会加入:
USART_Config();
Key_GPIO_Config();这里要注意:
BSP_Init()执行时,LiteOS 还没有启动,本质上仍然属于裸机初始化阶段。
也就是说:
- 先初始化硬件;
- 再初始化 LiteOS 内核;
- 再创建任务;
- 最后启动调度器。
六、为什么要先测试硬件?
在操作系统运行之前,先点亮 LED 测试硬件是否正常。
例如:
BSP_Init();
LED1_ON;
while (1);这一步的目的很重要:
先确认硬件本身没问题,再开始调试操作系统。
如果硬件初始化都不正确,后面任务不运行、LED 不闪烁时,就很难判断到底是硬件问题、任务问题还是调度器问题。
所以开发顺序应该是:
先裸机验证硬件,再加入 LiteOS 任务。
七、LiteOS 创建任务需要哪些东西?
创建一个任务通常需要四类内容:
- 任务函数;
- 任务ID变量;
- 任务参数结构体;
- 调用任务创建函数。
1. 任务函数
任务函数就是任务真正执行的内容。
例如:
static void Test1_Task(void)
{
while (1)
{
LED2_TOGGLE;
LOS_TaskDelay(1000);
}
}2. 任务ID变量
任务创建成功后,系统会返回一个任务ID。
用户需要提前定义变量保存它:
UINT32 Test1_Task_Handle;以后如果要删除、挂起、恢复、查询这个任务,都需要使用这个任务ID。
例如:
LOS_TaskSuspend(Test1_Task_Handle);
LOS_TaskResume(Test1_Task_Handle);
LOS_TaskDelete(Test1_Task_Handle);3. 任务参数结构体
LiteOS 使用 TSK_INIT_PARAM_S 配置任务参数。
常用字段包括:
TSK_INIT_PARAM_S task_init_param;
task_init_param.usTaskPrio = 5;
task_init_param.pcName = "Test1_Task";
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Test1_Task;
task_init_param.uwStackSize = 1024;含义如下:
| 参数 | 含义 |
|---|---|
usTaskPrio |
任务优先级 |
pcName |
任务名称 |
pfnTaskEntry |
任务入口函数 |
uwStackSize |
任务栈大小,单位是字节 |
uwArg |
任务参数 |
uwResved |
保留字段 |
注意:
LiteOS 中优先级数值越小,优先级越高。
例如:
优先级 4 > 优先级 54. 调用 LOS_TaskCreate 创建任务
任务创建函数是:
UINT32 LOS_TaskCreate(UINT32 *puwTaskID, TSK_INIT_PARAM_S *pstInitParam);使用方式:
uwRet = LOS_TaskCreate(&Test1_Task_Handle, &task_init_param);如果返回 LOS_OK,表示任务创建成功。
如果返回其他错误代码,说明创建失败。
八、任务控制块 TCB 是什么?
每个任务都有一个任务控制块,英文是:
Task Control Block,TCB。
它是操作系统用来管理任务的数据结构。
TCB 内容包括:
- 任务栈指针;
- 任务状态;
- 任务优先级;
- 任务栈大小;
- 任务栈顶;
- 任务ID;
- 任务入口函数;
- 任务阻塞在哪个信号量;
- 任务阻塞在哪个互斥锁;
- 任务参数;
- 任务名称;
- 事件;
- 事件掩码;
- 消息指针等。
可以这样理解:
TCB 就是任务的“身份证 + 状态表 + 调度信息表”。
操作系统通过 TCB 知道:
- 这个任务叫什么;
- 优先级是多少;
- 现在是就绪、运行还是阻塞;
- 栈在哪里;
- 入口函数是什么;
- 是否在等待信号量、互斥锁、事件或消息。
九、任务栈是什么?
每个任务都有独立的任务栈。
任务栈用于保存:
- 局部变量;
- 函数调用信息;
- 返回地址;
- CPU 寄存器上下文;
- 中断或任务切换时的现场信息。
如果任务栈太小,可能会出现:
- 栈溢出;
- 程序跑飞;
- HardFault;
- 任务异常。
如果任务栈太大,会浪费 SRAM。
所以任务栈大小要根据任务复杂度设置。
例如:
task_init_param.uwStackSize = 1024;表示该任务栈大小为 1024 字节。
十、LiteOS 中任务控制块和任务栈从哪里来?
LiteOS 支持动态内存分配,任务控制块和任务栈的内存空间都由 LiteOS 从 SRAM 中分配。系统通过 OS_SYS_MEM_SIZE 指定 LiteOS 可以管理的内存大小,这个宏通常在 target_config.h 中配置。创建任务时,系统会使用类似 LOS_MemAllocAlign 的函数从内存池中分配内存,并进行 8 字节对齐。
也就是说:
创建任务不是只创建一个函数,而是要给任务分配 TCB 和任务栈。
如果系统内存不足,任务就会创建失败,常见错误是:
LOS_ERRNO_TSK_NO_MEMORY十一、LiteOS 内核初始化做了什么?
在创建任务之前,必须先调用:
LOS_KernelInit();LOS_KernelInit() 主要完成以下工作:
- 初始化系统内存;
- 如果系统接管中断,则初始化中断入口;
- 初始化任务管理相关底层结构;
- 初始化任务控制块数组和链表;
- 初始化任务监视功能;
- 初始化信号量、互斥锁、消息队列等 IPC 资源;
- 如果使用软件定时器,则初始化软件定时器;
- 创建空闲任务。
这里最重要的是空闲任务。
空闲任务的作用
CPU 不能“什么都不执行”。
当系统中没有其他就绪任务时,必须有一个任务运行,这就是空闲任务。
空闲任务的优先级最低。 当用户任务就绪后,会抢占空闲任务。
所以:
空闲任务是系统兜底运行的任务,保证系统在没有用户任务可运行时仍然有任务可执行。
十二、创建单任务的完整流程
创建单任务的典型流程如下:
int main(void)
{
UINT32 uwRet = LOS_OK;
BSP_Init();
uwRet = LOS_KernelInit();
if (uwRet != LOS_OK)
{
return LOS_NOK;
}
uwRet = Creat_Test1_Task();
if (uwRet != LOS_OK)
{
return LOS_NOK;
}
LOS_Start();
while (1);
}它可以总结为:
硬件初始化 → LiteOS 内核初始化 → 创建任务 → 启动调度器。
其中任务创建函数类似:
static UINT32 Creat_Test1_Task(void)
{
UINT32 uwRet = LOS_OK;
TSK_INIT_PARAM_S task_init_param;
task_init_param.usTaskPrio = 5;
task_init_param.pcName = "Test1_Task";
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Test1_Task;
task_init_param.uwStackSize = 1024;
uwRet = LOS_TaskCreate(&Test1_Task_Handle, &task_init_param);
return uwRet;
}十三、创建多任务
创建两个任务:
Test1_Task:控制 LED2,每 1000 Tick 打印一次;Test2_Task:控制 LED1,每 500 Tick 打印一次。
两个任务的优先级不同:
Test1_Task 优先级 = 5
Test2_Task 优先级 = 4因为 LiteOS 中优先级数值越小,优先级越高,所以:
Test2_Task 的优先级高于 Test1_Task这意味着如果两个任务同时就绪,调度器会优先运行 Test2_Task。
多任务创建流程
多任务创建通常写在统一函数中:
static UINT32 AppTaskCreate(void)
{
UINT32 uwRet = LOS_OK;
uwRet = Creat_Test1_Task();
if (uwRet != LOS_OK)
{
return uwRet;
}
uwRet = Creat_Test2_Task();
if (uwRet != LOS_OK)
{
return uwRet;
}
return LOS_OK;
}这样做的好处是:
把所有任务创建代码集中管理,结构更清楚。
如果以后增加第 3 个、第 4 个任务,只需要继续在 AppTaskCreate() 中调用对应创建函数。
十四、创建多任务时容易出错的地方
创建更多任务时,最容易忽略的是:
- 任务栈大小;
- 任务优先级;
- 任务函数中是否有阻塞;
- 系统最大任务数量配置;
- SRAM 是否足够。
例如:
task_init_param.uwStackSize = 1024;如果任务内部调用函数较多、局部变量较大,1024 字节可能不够。
再比如:
task_init_param.usTaskPrio = 4;如果优先级设置不合理,高优先级任务一直运行,低优先级任务就可能得不到执行。
十五、LiteOS 启动流程
整体可以分成三层:
- 启动文件阶段;
main()函数阶段;- LiteOS 调度器阶段。
1. 启动文件阶段
系统上电后,首先执行启动文件中的复位函数:
Reset_Handler
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0也就是说:
上电后先执行
Reset_Handler,然后调用SystemInit,最后进入 C 运行环境并执行main()。
2. main 函数阶段
进入 main() 后,通常执行:
BSP_Init();
LOS_KernelInit();
Creat_Test1_Task();
Creat_Test2_Task();
LOS_Start();也就是:
硬件初始化 → 内核初始化 → 创建任务 → 启动调度器。
在 LOS_Start() 之前,任务虽然已经创建,但还不会真正运行。
3. 调度器启动阶段
调用:
LOS_Start();之后,LiteOS 开始调度任务。
PPT 中提到,LOS_Start() 会启动系统时钟节拍,并调用 LOS_StartToRun(),后者通过汇编实现真正进入任务运行。任务切换包括获取就绪列表中最高优先级任务、保存切出任务上下文、恢复切入任务上下文等。
可以总结为:
LOS_Start()一执行,系统就从“顺序执行 main 函数”进入“多任务调度运行”。
十六、LiteOS 的两种启动方式
PPT 中讲了两种启动方式。
第一种:main 中创建所有任务后启动调度器
流程是:
int main(void)
{
BSP_Init();
LOS_KernelInit();
Creat_Test1_Task();
Creat_Test2_Task();
LOS_Start();
}特点:
- 简单直观;
- 适合初学;
- 所有任务在调度器启动前创建完成。
第二种:先创建启动任务,再在启动任务中创建其他任务
流程是:
int main(void)
{
BSP_Init();
LOS_KernelInit();
Creat_App_Task();
LOS_Start();
}然后在启动任务中:
void App_Task_Start(void)
{
Creat_Test1_Task();
Creat_Test2_Task();
Delete_App_Task();
}这种方式的特点是:
- main 中只创建一个启动任务;
- 启动任务负责创建其他应用任务;
- 所有应用任务创建完成后,启动任务删除自己。
这种方式在复杂工程中更常见,因为系统初始化逻辑更集中。
十七、调度器是什么?
调度器是操作系统中决定“哪个任务运行”的核心模块。
PPT 中讲到,LiteOS 调度器是基于优先级的全抢占式调度器。除中断处理函数、调度器闭锁和临界段等特殊情况外,其他部分都可以被抢占。
也就是说:
如果一个更高优先级任务变为就绪态,它会立即抢占当前低优先级任务。
例如:
当前运行的是优先级 5 的任务。 这时优先级 3 的任务就绪了。 因为 3 的优先级更高,所以系统会立即切换到优先级 3 的任务。
十八、LiteOS 的任务优先级
LiteOS 默认支持 32 个优先级:
0 ~ 31其中:
- 0 是最高优先级;
- 31 是最低优先级;
- 31 通常分配给空闲任务,用户一般不建议使用。
这点非常容易考:
LiteOS 中优先级数值越小,优先级越高。
十九、任务状态
PPT 中列出了四种基本任务状态:
- 就绪态 Ready;
- 运行态 Running;
- 阻塞态 Blocked;
- 退出态 Dead。
1. 就绪态 Ready
任务已经具备运行条件,但还没有获得 CPU。
例如任务创建成功后,通常进入就绪态。
2. 运行态 Running
任务正在占用 CPU 执行。
在单核 MCU 上,同一时刻只有一个任务处于运行态。
3. 阻塞态 Blocked
任务暂时不能运行,正在等待某个条件。
例如:
- 调用了
LOS_TaskDelay(); - 等待信号量;
- 等待消息队列;
- 等待事件;
- 等待互斥锁。
4. 退出态 Dead
任务已经结束或被删除,不再参与调度。
二十、任务状态迁移
PPT 中列出了任务状态迁移关系:
- 创建任务 → 就绪态;
- 就绪态 → 运行态;
- 运行态 → 就绪态;
- 运行态 → 阻塞态;
- 阻塞态 → 就绪态;
- 就绪态 → 阻塞态;
- 运行态或阻塞态 → 退出态。
可以这样理解:
| 状态变化 | 触发原因 |
|---|---|
| 创建 → 就绪 | 创建任务成功 |
| 就绪 → 运行 | 被调度器选中 |
| 运行 → 就绪 | 被更高优先级任务抢占或时间片用完 |
| 运行 → 阻塞 | 调用延时、等待信号量、等待队列等 |
| 阻塞 → 就绪 | 等待条件满足或延时时间到 |
| 运行/阻塞 → 退出 | 任务被删除 |
二十一、任务切换是怎么发生的?
在 Cortex-M 处理器中,LiteOS 使用 PendSV 实现任务切换。
PPT 中提到,LiteOS 会向 0xE000ED04 地址写入 0x10000000,也就是将 SCB 寄存器对应位设置为 1,从而触发 PendSV 中断。真正的任务切换在 PendSV 中断中完成。
简单过程是:
- 系统发现需要任务切换;
- 触发 PendSV;
- 保存当前任务上下文;
- 从就绪列表选择最高优先级任务;
- 恢复新任务上下文;
- 新任务开始运行。
可以背成:
SysTick 提供系统节拍,调度器决定是否切换,PendSV 完成上下文切换。
二十二、LOS_TaskCreate:任务创建函数
LOS_TaskCreate() 用于创建任务。
函数原型:
UINT32 LOS_TaskCreate(UINT32 *puwTaskID, TSK_INIT_PARAM_S *pstInitParam);它做的事情大致是:
- 检查任务参数是否合法;
- 分配任务控制块;
- 分配任务栈;
- 设置任务入口函数、优先级、任务名、栈大小;
- 返回任务ID;
- 将任务加入就绪队列;
- 如果调度器已经启动,并且新任务优先级更高,则可能立即触发调度。
使用示例:
UINT32 Test1_Task_Handle;
TSK_INIT_PARAM_S task_init_param;
task_init_param.usTaskPrio = 5;
task_init_param.pcName = "Test1_Task";
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Test1_Task;
task_init_param.uwStackSize = 1024;
uwRet = LOS_TaskCreate(&Test1_Task_Handle, &task_init_param);二十三、LOS_TaskDelay:任务延时函数
任务延时函数是:
extern UINT32 LOS_TaskDelay(UINT32 uwTick);它的作用是:
让当前任务阻塞指定 Tick 数,让出 CPU。
例如:
LOS_TaskDelay(1000);表示当前任务延时 1000 个 Tick。
如果系统配置为:
LOSCFG_BASE_CORE_TICK_PER_SECOND = 1000那么 1000 Tick 大约是 1 秒。
LOS_TaskDelay 的注意事项
PPT 中明确提到:
- 不允许在中断中调用;
- 不允许在任务调度被锁定时调用;
- 传入 0 Tick 时,当前任务主动让出 CPU;
- 不允许在系统初始化之前调用;
- 延时不是绝对精确延时;
- 每个任务循环中最好要有延时或阻塞。
尤其要记住:
LOS_TaskDelay()会让任务从运行态进入阻塞态,延时时间到后再回到就绪态。
二十四、LOS_TaskDelete:任务删除函数
任务删除函数是:
UINT32 LOS_TaskDelete(UINT32 uwTaskID);作用是:
根据任务ID删除指定任务,并回收相关资源。
删除任务时,LiteOS 会根据任务状态做不同处理:
- 如果任务未创建,返回错误;
- 如果任务在就绪态,从就绪队列移除;
- 如果任务在阻塞态,从阻塞列表移除;
- 如果任务在延时状态,从延时列表移除;
- 如果删除的是当前正在运行的任务,不能立刻彻底删除,而是先加入回收列表,等调度完成后再回收资源;
- 如果删除的是非当前任务,则释放任务栈并回收任务控制块。
使用示例:
uwRet = LOS_TaskDelete(Test_Task_Handle);
if (uwRet != LOS_OK)
{
printf("任务删除失败\n");
}二十五、LOS_TaskSuspend:任务挂起函数
任务挂起函数是:
extern UINT32 LOS_TaskSuspend(UINT32 uwTaskID);作用是:
挂起指定任务,使其从就绪队列中移除,不再参与调度。
挂起任务后,即使它原本具备运行条件,也不会被调度器选中。
注意:
- 不能挂起未创建任务;
- 不能重复挂起已经挂起的任务;
- 不能挂起 IDLE 任务;
- 不能挂起软件定时器任务;
- 如果任务正在运行且调度器被锁定,也不能挂起。
二十六、LOS_TaskResume:任务恢复函数
任务恢复函数是:
extern UINT32 LOS_TaskResume(UINT32 uwTaskID);作用是:
恢复已经挂起的任务,使其重新参与调度。
如果恢复后的任务是当前就绪任务中优先级最高的任务,系统会立即进行任务切换。
简单记:
LOS_TaskSuspend() // 让任务暂停
LOS_TaskResume() // 让任务恢复二十七、任务挂起与恢复实验
PPT 中的任务管理实验创建了两个任务:
LED_TaskKey_Task
其中:
LED_Task用来控制 LED 闪烁,显示任务正在运行;Key_Task用来检测按键;- 按下 KEY1 时挂起 LED 任务;
- 按下 KEY2 时恢复 LED 任务。
核心逻辑是:
if (Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON)
{
LOS_TaskSuspend(LED_Task_Handle);
}
else if (Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON)
{
LOS_TaskResume(LED_Task_Handle);
}
LOS_TaskDelay(20);这说明任务管理函数可以通过任务ID控制任务状态。
二十八、常见 Task 错误代码
LiteOS 的任务相关函数大多有返回值。返回 LOS_OK 表示成功,返回其他值表示错误。
PPT 中列出了一些常见错误代码。这里按考试重点整理:
| 错误代码 | 含义 | 解决思路 |
|---|---|---|
LOS_ERRNO_TSK_NO_MEMORY |
内存不足 | 增大可管理内存,减小任务栈或任务数量 |
LOS_ERRNO_TSK_PTR_NULL |
任务参数为空 | 检查参数指针 |
LOS_ERRNO_TSK_PRIOR_ERROR |
任务优先级错误 | 检查优先级范围 |
LOS_ERRNO_TSK_ENTRY_NULL |
任务入口函数为空 | 定义任务函数 |
LOS_ERRNO_TSK_NAME_EMPTY |
任务名为空 | 设置任务名称 |
LOS_ERRNO_TSK_STKSZ_TOO_SMALL |
任务栈太小 | 增大任务栈 |
LOS_ERRNO_TSK_ID_INVALID |
任务ID无效 | 检查任务ID |
LOS_ERRNO_TSK_NOT_CREATED |
任务未创建 | 先创建任务 |
LOS_ERRNO_TSK_ALREADY_SUSPENDED |
任务已经挂起 | 不要重复挂起 |
LOS_ERRNO_TSK_NOT_SUSPENDED |
任务未挂起 | 先挂起再恢复 |
LOS_ERRNO_TSK_DELAY_IN_INT |
在中断中延时 | 不要在中断服务程序中调用延时 |
LOS_ERRNO_TSK_DELAY_IN_LOCK |
调度锁定时延时 | 解锁调度器后再延时 |
LOS_ERRNO_TSK_TCB_UNAVAILABLE |
没有空闲任务控制块 | 增加任务控制块数量 |
LOS_ERRNO_TSK_OPERATE_IDLE |
操作空闲任务 | 不要操作 IDLE 任务 |
LOS_ERRNO_TSK_STKSZ_TOO_LARGE |
任务栈过大 | 减小任务栈 |
考试一般不会要求全部背,但要理解常见错误原因。
二十九、任务设计要点
PPT 中任务设计要点包括四类:
- 中断服务程序;
- 普通任务;
- 空闲任务;
- 任务执行时间。
1. 中断服务程序要短
中断服务程序 ISR 应该尽量短小。
因为中断执行期间通常会影响任务调度,ISR 太长会影响系统实时性。
正确做法是:
中断中只做必要处理,把复杂工作交给任务完成。
例如:
- 中断中设置标志位;
- 中断中释放信号量;
- 中断中发送消息;
- 任务中处理具体业务。
2. 普通任务要有阻塞点
普通任务不能一直死循环空转。
错误写法:
while (1)
{
LED_TOGGLE;
}这种任务会一直占用 CPU。
正确写法:
while (1)
{
LED_TOGGLE;
LOS_TaskDelay(1000);
}3. 空闲任务不要随便操作
空闲任务是系统必须存在的最低优先级任务,通常不建议用户随意删除、挂起或占用。
系统没有其他任务运行时,空闲任务才运行。
4. 控制任务执行时间
任务执行时间过长会影响其他任务响应。
尤其是高优先级任务,应该避免长时间占用 CPU。
如果任务需要执行大量计算,可以考虑:
- 分段执行;
- 加入延时;
- 使用消息队列;
- 降低优先级;
- 优化算法。
三十、本章重点背诵版
下面是考前可以直接背的内容。
1. LiteOS 创建任务流程
LiteOS 创建任务的一般流程是:先进行板级硬件初始化,然后调用 LOS_KernelInit() 初始化内核,接着定义任务函数、任务ID变量和任务参数结构体,再调用 LOS_TaskCreate() 创建任务,最后调用 LOS_Start() 启动任务调度器。
2. 任务函数特点
LiteOS 中的任务函数通常是一个不带返回值的无限循环函数,不能直接返回。任务循环中必须包含延时、等待信号量、等待消息队列等阻塞操作,否则任务会一直占用 CPU,导致低优先级任务无法运行。
3. 任务控制块 TCB
任务控制块是操作系统用于管理任务的数据结构,保存任务栈指针、任务状态、任务优先级、任务ID、任务名称、任务入口函数、任务栈大小等信息。LiteOS 通过任务控制块掌握任务运行状态并实现任务调度。
4. 任务栈
每个任务都有独立的任务栈,用于保存局部变量、函数调用信息、返回地址和任务上下文等内容。任务栈太小可能导致栈溢出,任务栈太大则浪费 SRAM。
5. LiteOS 启动流程
LiteOS 启动流程为:系统上电后执行启动文件中的 Reset_Handler,调用 SystemInit 完成系统初始化,然后进入 main() 函数。在 main() 中完成硬件初始化、LiteOS 内核初始化、任务创建,最后调用 LOS_Start() 启动调度器,系统进入多任务运行状态。
6. LiteOS 调度器
LiteOS 调度器是基于优先级的全抢占式调度器。系统中优先级数值越小,任务优先级越高。当有更高优先级任务进入就绪态时,会立即抢占当前低优先级任务运行。
7. 任务状态
LiteOS 任务基本状态包括就绪态、运行态、阻塞态和退出态。任务创建成功后进入就绪态,被调度器选中后进入运行态,调用延时或等待资源时进入阻塞态,被删除后进入退出态。
8. 常用任务函数
LiteOS 常用任务函数包括:
LOS_TaskCreate(); // 创建任务
LOS_TaskDelete(); // 删除任务
LOS_TaskDelay(); // 任务延时
LOS_TaskSuspend(); // 挂起任务
LOS_TaskResume(); // 恢复任务9. LOS_TaskDelay 注意事项
LOS_TaskDelay() 的延时单位是 Tick,不允许在中断中调用,不允许在调度器锁定时调用。调用后当前任务会进入阻塞态,延时时间到达后重新进入就绪态。
10. 挂起与恢复
LOS_TaskSuspend() 根据任务ID挂起指定任务,使其不再参与调度;LOS_TaskResume() 恢复被挂起的任务,使其重新进入可调度状态。如果恢复后的任务优先级最高,系统会立即切换到该任务运行。
三十一、考试可能这样问
题目1:简述 LiteOS 创建任务的过程。
答:
LiteOS 创建任务的过程包括:首先进行硬件初始化,然后调用 LOS_KernelInit() 初始化 LiteOS 内核;接着定义任务函数、任务ID变量和任务参数结构体 TSK_INIT_PARAM_S,配置任务入口函数、任务名称、任务优先级和任务栈大小;然后调用 LOS_TaskCreate() 创建任务;最后调用 LOS_Start() 启动任务调度器,使系统进入多任务运行状态。
题目2:任务控制块 TCB 的作用是什么?
答:
任务控制块 TCB 是操作系统用于管理任务的数据结构,保存任务栈指针、任务状态、任务优先级、任务ID、任务名称、任务入口函数、任务栈大小等信息。LiteOS 通过 TCB 记录任务运行情况,并根据 TCB 中的信息完成任务调度、挂起、恢复和删除等操作。
题目3:为什么任务函数中必须有阻塞?
答:
LiteOS 是基于优先级的抢占式调度系统。如果任务函数中没有延时、信号量等待、消息队列等待等阻塞操作,该任务会一直占用 CPU,导致同优先级或低优先级任务无法获得运行机会。因此任务循环中必须设置适当的阻塞点,例如调用 LOS_TaskDelay()。
题目4:LiteOS 任务调度的原则是什么?
答:
LiteOS 采用基于优先级的全抢占式调度机制。优先级数值越小,任务优先级越高。当有比当前运行任务优先级更高的任务进入就绪态时,调度器会立即进行任务切换,使高优先级任务抢占 CPU 运行。对于同优先级任务,可以采用时间片轮转方式调度。
题目5:LiteOS 任务有哪些基本状态?
答:
LiteOS 任务基本状态包括就绪态、运行态、阻塞态和退出态。任务创建成功后进入就绪态;被调度器选中后进入运行态;调用延时函数或等待信号量、消息队列等资源时进入阻塞态;任务被删除后进入退出态。
题目6:LOS_TaskDelay 的作用和注意事项是什么?
答:
LOS_TaskDelay() 用于使当前任务延时指定 Tick 数,并让出 CPU。调用后任务进入阻塞态,延时时间到达后重新进入就绪态。该函数不允许在中断中调用,不允许在调度器锁定时调用,也不能在系统初始化前调用。任务延时并不是绝对精确延时。
题目7:LOS_TaskSuspend 和 LOS_TaskResume 有什么作用?
答:
LOS_TaskSuspend() 用于挂起指定任务,使其从就绪列表中移除,不再参与调度;LOS_TaskResume() 用于恢复被挂起的任务,使其重新进入可调度状态。如果恢复后的任务优先级高于当前运行任务,系统会立即进行任务切换。
题目8:LiteOS 是如何启动的?
答:
系统上电后首先执行启动文件中的 Reset_Handler,然后调用 SystemInit 完成系统初始化,并进入 main() 函数。在 main() 中先进行板级硬件初始化,再调用 LOS_KernelInit() 初始化 LiteOS 内核,然后创建用户任务,最后调用 LOS_Start() 启动调度器。调度器启动后,系统从就绪任务中选择最高优先级任务运行,进入多任务调度状态。
三十二、本章一句话总结
这一章可以这样总结:
第七章节主要讲 LiteOS 的任务创建与任务管理:创建任务时需要任务函数、任务ID、任务参数和任务栈,系统通过任务控制块 TCB 管理任务;LiteOS 采用基于优先级的抢占式调度,任务在就绪态、运行态、阻塞态和退出态之间转换,并通过
LOS_TaskCreate、LOS_TaskDelay、LOS_TaskSuspend、LOS_TaskResume、LOS_TaskDelete等函数完成任务的创建、延时、挂起、恢复和删除。