操作系统——FreeRTOS任务
#什么是任务
操作系统的主要任务是处理诸如:管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务。
操作系统的类型非常多样,不同机器安装的操作系统可从简单到复杂,可从移动电话的嵌入式系统到超级计算机的大型操作系统。
许多操作系统制造者对它涵盖范畴的定义也不尽一致,例如有些操作系统集成了图形用户界面,而有些仅使用命令行界面,而将图形用户界面视为一种非必要的应用程序。
操作系统的主要任务是处理诸如:管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。
以现代标准而言,一个标准PC的操作系统应该提供以下的功能:
1、进程管理(Processing management)
2、内存管理(Memory management)
3、文件系统(File system)
4、网络通信(Networking)
5、安全机制(Security)
6、用户界面(User interface)
7、驱动程序(Device drivers)
#任务状态
1.阻塞:通过函数vTaskSuspend()和vTaskResume()任务进入和退出阻塞状态,它没有超时规定。
2.挂起:一个任务正在等待一个外部事件,通俗说就是任务等待其他因素触发,然后此时就是挂起状态(超出规定时间退出阻塞状态)
3.就绪:准备运行的任务,此外现在有一个任务比这个任务优先级高正在运行,同时该任务不能被阻塞和挂起
4.运行:任务正在运行
##分析函数
pxCurrentTCB:记录现在运行的任务;
pxReadyTasksLists:记录处于ready状态,等待被调度运行的任务,这是一个链表数组,ready list安装优先级进行分类,这样在调度时就可以从优先级高的readylist中先进行调度,是调度器直接操作对象。
xDelayedTaskList1:定义了一个delay的task链表,这两个是阻塞表。
xDelayedTaskList2:定义了一个delay的task链表,delay的task链表是指调用了taskdelay()或者因为阻塞动作被延时的任务,延时的单位为tick。Delaylist按照delay的tick时间进行排序,之所以定义了两个,是为了防止xTickCount发生反转时,一个list无法完全标识。
xTickCount:无符号数字,标识运行后到现在的系统产生的tick数,每个tick发生时,xTickCount++,当xTickCount发生翻转时,pxDelayedTaskList和pxOverflowDelayedTaskList进行对调,Overflowlist变为正常的delay list。
pxDelayedTaskList和pxOverflowDelayedTaskList是链表指针,分包指向xDelayedTaskList1和xDelayedTaskList2。
xPendingReadyList:任务进入就绪状态,但是没有放入readylist链表。这种情况发生在调度器被停止时,有些任务进入到ready状态,这时就讲任务加入到xPendingReadyList,等待调度器开始时,从新进行一次调度。
我们知道任何操作系统都有临界区或者在执行某段代码时不想被打断,防止破坏某些关键操作的完整性。Freertos可以采取多种方式避免,如果是不希望被中断打断,需要调用:
xPendingReadyList就是在调度器被暂停时,新的任务进入了ready状态,因此先保存起来,在xTaskResumeAll时再加入到相应的ready list中。
xTasksWaitingTermination:表示等待结束的任务,注意是为了释放这些任务的资源,在任务删除时会有详细的介绍。
xSuspendedTaskList:表示被暂停的任务列表,调用tasksuspend函数。
xIdleTaskHandle:表示空闲任务的句柄,freertos默认为我们创建了一个IDLE函数,一般优先级为0,即最低。
uxCurrentNumberOfTasks:目前所有的任务数。
xTickCount:系统开始后所有的tick计数。
uxTopReadyPriority:记录当前ready list中优先级最高的任务,这个是个32位的无符号整形数据,如果你使能了configUSE_PORT_OPTIMISED_TASK_SELECTION,uxTopReadyPriority使用bit mask的方式标识就绪状态的最高优先级(freertos最大优先级数为32),前提是你的C运行库支持__builtin_clz(返回某个数据的前导0个数),这个功能一般会开启,因此就限制了最大优先级为32。如果不开启configUSE_PORT_OPTIMISED_TASK_SELECTION,最大优先级没有限制,但是会增大ready list占用的资源。
xSchedulerRunning:调度器是否运行。
uxPendedTicks:前面讲到的vTaskSuspendAll,暂停了调度器,如果这期间tick的timer发送中断,这时uxPendedTicks记录了未被处理的ticks个数。
xYieldPending:在某种临界状态下,任务状态发生改变,需要等待从新调度。
xNumOfOverflows:记录tick计数翻转的次数。
uxTaskNumber:用来记录全局任务数,为新建的任务分配一个task number。
xNextTaskUnblockTime:记录了延时链表中,第一个需要被唤醒的任务时间点。
uxSchedulerSuspended:调度器暂定标志。
##操作系统中挂起和阻塞的区别如下:
一:挂起是一种主动行为,因此恢复也应该要主动完成,而阻塞则是一种被动行为,是在等待事件或资源时任务的表现,你不知道他什么时候被阻塞(pend),也就不能确切 的知道他什么时候恢复阻塞。而且挂起队列在操作系统里可以看成一个,而阻塞队列则是不同的事件或资源(如信号量)就有自己的队列;
二:阻塞(pend)就是任务释放CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现。挂起(suspend)不释放CPU,如果任务优先级高就永远轮不到其他任务运行,一般挂起用于程序调试中的条件中断,当出现某个条件的情况下挂起,然后进行单步调试;
三:pend是task主动去等一个事件,或消息.suspend是直接悬挂task,以后这个task和你没任何关系,任何task间的通信或者同步都和这个suspended task没任何关系了,除非你resume task;
四:任务调度是操作系统来实现的,任务调度时,直接忽略挂起状态的任务,但是会顾及处于pend下的任务,当pend下的任务等待的资源就绪后,就可以转为ready了。ready只需要等待CPU时间,当然,任务调度也占用开销,但是不大,可以忽略。可以这样理解,只要是挂起状态,操作系统就不在管理这个任务了;
五:挂起是主动的,一般需要用挂起函数进行操作,若没有resume的动作,则此任务一直不会ready。而阻塞是因为资源被其他任务抢占而处于休眠态。两者的表现方式都是从就绪态里“清掉”,即对应标志位清零,只不过实现方式不一样。
#优先级
任务的优先级范围·:0-32
0是优先级最小的,32是优先级最大的。
#任务创建函数
##xTaskCreate()函数
举例说明:创建一个led0任务
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
返回值是创建成功或创建失败
##任务删除函数 vTaskDelete()
vTaskDelete(LED0Task_Handler);
LED0Task_Handler:任务的句柄
#创建两个应用任务task1_task和task2_task,当task1_task 运行5次后删除task2_task任务
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "FreeRTOS.h"
#include "task.h"
//任务优先级
#define START_TASK_PRIO 1
#define TASK1_TASK_PRIO 2
#define TASK2_TASK_PRIO 3
//任务堆栈大小
#define START_STK_SIZE 128
#define TASK1_STK_SIZE 128
#define TASK2_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
TaskHandle_t Task1Task_Handler;
TaskHandle_t Task2Task_Handler;
//任务函数
void start_task(void *pvParameters);
void task1_task(void *pvParameters);
void task2_task(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口
LED_Init(); //初始化灯
//开始创建任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //任务传递参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启调度
}
//开始任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建任务1
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建任务2
xTaskCreate((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler); //删除任务
taskEXIT_CRITICAL(); //退出临界区
}
//任务1函数
void task1_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //计数(0-255)
LED0=!LED0;
printf("执行任务1%d次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task2Task_Handler);//任务1执行5次删除任务2
printf("任务1删除任务2\r\n");
//task1_num=0;
}
vTaskDelay(1000); //延时1秒
}
}
//任务1函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
while(1)
{
task2_num++; //计数(0-255)
LED1=!LED1;
printf("执行任务2%d次\r\n",task2_num);
vTaskDelay(1000); //延时1秒
}
}
此实验在主函数实现的,最好新建两个文件task.c和task.h,便于规范,防止后期代码过多,看着混乱。