【专题1:电子工程师】 之 【32.基于STM32从0到1写操作系统 - 【11.位图数据结构和多优先级任务】】

  笔者在嵌入式领域深耕6年,对嵌入式项目构建,BLDC电机控制,产品上位机开发以及产品量产和产品售后维护有多年工作经验。经验分享,从0到1, 让我带你从实际工作的角度走进嵌入式成长之路。

  原创不易欢迎大家关注我的微信公众号嵌入式工程师成长之路扫下面二维码
                     在这里插入图片描述
所有文章总目录:【电子工程师 qt工程师】

原创视频总目录:【电子工程师 qt工程师】

本文概述:

  对于有很多任务,如果只通过时间片来轮转调度,系统的实时性也不会很强,所有需要使用优先级,也就是如果高优先级的任务就绪了就先调度到高优先级,以保证系统更高的实时性。
  怎么实现这个机制呢?创建一个位图结构(位图结构存在的目的就是为了高效地获得就绪表中最高优先级的任务,位图的特性就是高效),任务准备好时,就以优先级作为索引设置就绪表对应位为1,调度函数只需要找到为1的最小索引,然后对比该任务是否和当前任务相同,如果不相同,直接切换过去即可。这里需要注意:只有调用系统延时函数,才能将当前任务对应的位图位清0。如果不调用系统延时函数,该任务会一直运行,假如任务3的优先级是0,也就是优先级最高,如果任务函数内部没有调用过系统延时函数,那么系统不会发生任何调度。

1.位图概述

位图是一组连续的标志位,每一位标志用于标识某种状态的有无。
【专题1:电子工程师】 之 【32.基于STM32从0到1写操作系统 - 【11.位图数据结构和多优先级任务】】_第1张图片

2.操作接口

  • 初始化:将所有位清0。
    【专题1:电子工程师】 之 【32.基于STM32从0到1写操作系统 - 【11.位图数据结构和多优先级任务】】_第2张图片
  • 置1操作:给定某个位置,可以将其设置为1。
  • 清0操作:给定某个位置,可以将其清0。
  • 查找第一个置位的位置(从第0位到最高位依次查找)。
    【专题1:电子工程师】 之 【32.基于STM32从0到1写操作系统 - 【11.位图数据结构和多优先级任务】】_第3张图片查找算法:
    (1)移位测试法,这种方法比较慢,但代码理解起来很简单。
    在这里插入图片描述(2)查表法,效率高,但代码理解起来比较难。

3.优先级的引入

  CPU、事件、资源都是有限的,但任务却有非常多,在同一个时刻,怎样分配CPU、事件以及各种资源呢?
  可以在初始化一个任务时,给每个任务设置一个优先级。在调度函数中实现一种基于优先级的分发机制。
【专题1:电子工程师】 之 【32.基于STM32从0到1写操作系统 - 【11.位图数据结构和多优先级任务】】_第4张图片

4.CPU优先级

  可以创建一个任务就绪表(位图),根据优先级来设置该位图中的位以表示这个任务可以调度了。在调度函数中,我们只要查表,从第0位查到最后一位(优先级也是从高到低)找到该位图中第一个为1的位,然后将CPU交给它。
【专题1:电子工程师】 之 【32.基于STM32从0到1写操作系统 - 【11.位图数据结构和多优先级任务】】_第5张图片

5.实现过程

(1)给任务添加优先级字段

// 任务结构:包含了一个任务的所有信息
typedef struct _tTask {
    tTaskStack * stack;

    // 任务延时计数器
    uint32_t delayTicks;

    // 任务的优先级
    uint32_t prio;
}tTask;

(2)添加优先级位图表

// 任务优先级的标记位置结构
tBitmap taskPrioBitmap;

tTask * taskTable[TINYOS_PRO_COUNT];

(3)修改调度算法

void tTaskSched (void) 
{   
    tTask * tempTask;

    // 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
    uint32_t status = tTaskEnterCritical();

    // 如何调度器已经被上锁,则不进行调度,直接退bm
    if (schedLockCount > 0) 
    {
        tTaskExitCritical(status);
        return;
    }

    // 找到优先级最高的任务,如果其优先级比当前任务的还高,那么就切换到这个任务
    tempTask = tTaskHighestReady();
    if (tempTask != currentTask) 
    {
        nextTask = tempTask;
        tTaskSwitch();   
    }

    // 退出临界区
    tTaskExitCritical(status); 
}

6.代码

(1)main.c

#include "tinyOS.h"
#include "ARMCM3.h"

// 当前任务:记录当前是哪个任务正在运行
tTask * currentTask;

// 下一个将即运行的任务:在进行任务切换前,先设置好该值,然后任务切换过程中会从中读取下一任务信息
tTask * nextTask;

// 空闲任务
tTask * idleTask;

// 任务优先级的标记位置结构
tBitmap taskPrioBitmap;

tTask * taskTable[TINYOS_PRO_COUNT];

// 调度锁计数器
uint8_t schedLockCount;

void tTaskInit (tTask * task, void (*entry)(void *), void *param, uint32_t prio, uint32_t * stack)
{
    *(--stack) = (unsigned long)(1<<24);                // XPSR, 设置了Thumb模式,恢复到Thumb状态而非ARM状态运行
    *(--stack) = (unsigned long)entry;                  // 程序的入口地址
    *(--stack) = (unsigned long)0x14;                   // R14(LR), 任务不会通过return xxx结束自己,所以未用
    *(--stack) = (unsigned long)0x12;                   // R12, 未用
    *(--stack) = (unsigned long)0x3;                    // R3, 未用
    *(--stack) = (unsigned long)0x2;                    // R2, 未用
    *(--stack) = (unsigned long)0x1;                    // R1, 未用
    *(--stack) = (unsigned long)param;                  // R0 = param, 传给任务的入口函数
    *(--stack) = (unsigned long)0x11;                   // R11, 未用
    *(--stack) = (unsigned long)0x10;                   // R10, 未用
    *(--stack) = (unsigned long)0x9;                    // R9, 未用
    *(--stack) = (unsigned long)0x8;                    // R8, 未用
    *(--stack) = (unsigned long)0x7;                    // R7, 未用
    *(--stack) = (unsigned long)0x6;                    // R6, 未用
    *(--stack) = (unsigned long)0x5;                    // R5, 未用
    *(--stack) = (unsigned long)0x4;                    // R4, 未用

    task->stack = stack;                                // 保存最终的值
    task->delayTicks = 0;
    task->prio = prio;                                  // 设置任务的优先级

    taskTable[prio] = task;                             // 填入任务优先级表
    tBitmapSet(&taskPrioBitmap, prio);                  // 标记优先级位置中的相应位
}

tTask * tTaskHighestReady (void) 
{
    uint32_t highestPrio = tBitmapGetFirstSet(&taskPrioBitmap);
    return taskTable[highestPrio];
}

void tTaskSchedInit (void)
{
    schedLockCount = 0;
}

void tTaskSchedDisable (void) 
{
    uint32_t status = tTaskEnterCritical();

    if (schedLockCount < 255) 
    {
        schedLockCount++;
    }

    tTaskExitCritical(status);
}

void tTaskSchedEnable (void) 
{
    uint32_t status = tTaskEnterCritical();

    if (schedLockCount > 0) 
    {
        if (--schedLockCount == 0) 
        {
            tTaskSched(); 
        }
    }

    tTaskExitCritical(status);
}

void tTaskSched (void) 
{   
    tTask * tempTask;

    // 进入临界区,以保护在整个任务调度与切换期间,不会因为发生中断导致currentTask和nextTask可能更改
    uint32_t status = tTaskEnterCritical();

    // 如何调度器已经被上锁,则不进行调度,直接退bm
    if (schedLockCount > 0) 
    {
        tTaskExitCritical(status);
        return;
    }

    // 找到优先级最高的任务,如果其优先级比当前任务的还高,那么就切换到这个任务
    tempTask = tTaskHighestReady();
    if (tempTask != currentTask) 
    {
        nextTask = tempTask;
        tTaskSwitch();   
    }

    // 退出临界区
    tTaskExitCritical(status); 
}

void tTaskSystemTickHandler () 
{
    // 检查所有任务的delayTicks数,如果不0的话,减1。
    int i;   
    uint32_t status = tTaskEnterCritical();

    for (i = 0; i < TINYOS_PRO_COUNT; i++) 
    {
				if (taskTable[i] == 0)
					continue;
        if (taskTable[i]->delayTicks > 0)
        {
            taskTable[i]->delayTicks--;
        }
        else 
        {
            tBitmapSet(&taskPrioBitmap, i);
        }
    }
    tTaskExitCritical(status);

    // 这个过程中可能有任务延时完毕(delayTicks = 0),进行一次调度。
    tTaskSched();
}

void tTaskDelay (uint32_t delay) {
   // 配置好当前要延时的ticks数
    uint32_t status = tTaskEnterCritical();
    currentTask->delayTicks = delay;
    tBitmapClear(&taskPrioBitmap, currentTask->prio);
    tTaskExitCritical(status);

    // 然后进行任务切换,切换至另一个任务,或者空闲任务
    // delayTikcs会在时钟中断中自动减1.当减至0时,会切换回来继续运行。
    tTaskSched();
}

void tSetSysTickPeriod(uint32_t ms)
{
  SysTick->LOAD  = ms * SystemCoreClock / 1000 - 1; 
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);
  SysTick->VAL   = 0;                           
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                   SysTick_CTRL_TICKINT_Msk   |
                   SysTick_CTRL_ENABLE_Msk; 
}

void SysTick_Handler () 
{
    tTaskSystemTickHandler();
}

int task1Flag;
void task1Entry (void * param) 
{
    tSetSysTickPeriod(10);
    for (;;) 
    {
        task1Flag = 1;
        tTaskDelay(1);
        task1Flag = 0;
        tTaskDelay(1);
    }
}

int task2Flag;
void task2Entry (void * param) 
{
    for (;;) 
    {
        task2Flag = 1;
        tTaskDelay(1);
        task2Flag = 0;
        tTaskDelay(1);
    }
}

// 任务1和任务2的任务结构,以及用于堆栈空间
tTask tTask1;
tTask tTask2;
tTaskStack task1Env[1024];
tTaskStack task2Env[1024];

// 用于空闲任务的任务结构和堆栈空间
tTask tTaskIdle;
tTaskStack idleTaskEnv[1024];

void idleTaskEntry (void * param) {
    for (;;)
    {
        // 空闲任务什么都不做
    }
}

int main () 
{
    // 优先初始化tinyOS的核心功能
    tTaskSchedInit();

    // 初始化任务1和任务2结构,传递运行的起始地址,想要给任意参数,以及运行堆栈空间
    tTaskInit(&tTask1, task1Entry, (void *)0x11111111, 0, &task1Env[1024]);
    tTaskInit(&tTask2, task2Entry, (void *)0x22222222, 1, &task2Env[1024]);

    // 创建空闲任务
    tTaskInit(&tTaskIdle, idleTaskEntry, (void *)0, TINYOS_PRO_COUNT - 1, &idleTaskEnv[1024]);
    
    // 这里,不再指定先运行哪个任务,而是自动查找最高优先级的任务运行
    nextTask = tTaskHighestReady();

    // 切换到nextTask, 这个函数永远不会返回
    tTaskRunFirst();
    return 0;
}

(2)switch.c和上一个版本一样
(3)tBitmap.c

#include "tLib.h"

/**********************************************************************************************************
** Function name        :   tBitmapInit
** Descriptions         :   初始化bitmap将所有的位全清0
** parameters           :   无
** Returned value       :   无
***********************************************************************************************************/
void tBitmapInit (tBitmap * bitmap) 
{
	bitmap->bitmap = 0;
}

/**********************************************************************************************************
** Function name        :   tBitmapPosCount
** Descriptions         :   返回最大支持的位置数量
** parameters           :   无
** Returned value       :   最大支持的位置数量
***********************************************************************************************************/
uint32_t tBitmapPosCount (void) 
{
	return 32;
}

/**********************************************************************************************************
** Function name        :   tBitmapSet
** Descriptions         :   设置bitmap中的某个位
** parameters           :   pos 需要设置的位
** Returned value       :   无
***********************************************************************************************************/
void tBitmapSet (tBitmap * bitmap, uint32_t pos)
{
	bitmap->bitmap |= 1 << pos;
}

/**********************************************************************************************************
** Function name        :   tBitmapClear
** Descriptions         :   清除bitmap中的某个位
** parameters           :   pos 需要清除的位
** Returned value       :   无
***********************************************************************************************************/
void tBitmapClear (tBitmap * bitmap, uint32_t pos)
{
	bitmap->bitmap &= ~(1 << pos);
}

/**********************************************************************************************************
** Function name        :   tBitmapGetFirstSet
** Descriptions         :   从位图中第0位开始查找,找到第1个被设置的位置序号
** parameters           :   无
** Returned value       :   第1个被设置的位序号
***********************************************************************************************************/
uint32_t tBitmapGetFirstSet (tBitmap * bitmap) 
{
	static const uint8_t quickFindTable[] =     
	{
	    /* 00 */ 0xff, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* 10 */ 4,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* 20 */ 5,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* 30 */ 4,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* 40 */ 6,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* 50 */ 4,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* 60 */ 5,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* 70 */ 4,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* 80 */ 7,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* 90 */ 4,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* A0 */ 5,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* B0 */ 4,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* C0 */ 6,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* D0 */ 4,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* E0 */ 5,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
	    /* F0 */ 4,    0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
	};

	if (bitmap->bitmap & 0xff)
    {
        return quickFindTable[bitmap->bitmap & 0xff];         
    }
    else if (bitmap->bitmap & 0xff00)
    {
        return quickFindTable[(bitmap->bitmap >> 8) & 0xff] + 8;        
    }
    else if (bitmap->bitmap & 0xff0000)
    {
        return quickFindTable[(bitmap->bitmap >> 16) & 0xff] + 16;        
    }
    else if (bitmap->bitmap & 0xFF000000)
    {
        return quickFindTable[(bitmap->bitmap >> 24) & 0xFF] + 24;
    }
    else
    {
        return tBitmapPosCount();
    }
}

(4)tLib.h

#ifndef TLIB_H
#define TLIB_H

// 标准头文件,里面包含了常用的类型定义,如uint32_t
#include 

// 位图类型
typedef struct 
{
	uint32_t bitmap;
}tBitmap;

/**********************************************************************************************************
** Function name        :   tBitmapInit
** Descriptions         :   初始化bitmap将所有的位全清0
** parameters           :   无
** Returned value       :   无
***********************************************************************************************************/
void tBitmapInit (tBitmap * bitmap);

/**********************************************************************************************************
** Function name        :   tBitmapPosCount
** Descriptions         :   返回最大支持的位置数量
** parameters           :   无
** Returned value       :   最大支持的位置数量
***********************************************************************************************************/
uint32_t tBitmapPosCount (void);

/**********************************************************************************************************
** Function name        :   tBitmapSet
** Descriptions         :   设置bitmap中的某个位
** parameters           :   pos 需要设置的位
** Returned value       :   无
***********************************************************************************************************/
void tBitmapSet (tBitmap * bitmap, uint32_t pos);

/**********************************************************************************************************
** Function name        :   tBitmapClear
** Descriptions         :   清除bitmap中的某个位
** parameters           :   pos 需要清除的位
** Returned value       :   无
***********************************************************************************************************/
void tBitmapClear (tBitmap * bitmap, uint32_t pos);

/**********************************************************************************************************
** Function name        :   tBitmapGetFirstSet
** Descriptions         :   从位图中第0位开始查找,找到第1个被设置的位置序号
** parameters           :   无
** Returned value       :   第1个被设置的位序号
***********************************************************************************************************/
uint32_t tBitmapGetFirstSet (tBitmap * bitmap);

#endif /* TLIB_H */

(5)tConfig.h

#ifndef TCONFIG_H
#define TCONFIG_H

#define	TINYOS_PRO_COUNT				32						// TinyOS任务的优先级序号

#endif /* TCONFIG_H */

你可能感兴趣的:(专题1:电子工程师)