打印
[研电赛技术支持]

GD32实战__编写一个OS

[复制链接]
2365|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2021-11-3 17:00 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
OS功能列表
1. 任务切换
任务创建函数 OS_TASK_CreateTask
默认任务每10ms切换一次,可通过宏OS_TASK_SWITCH_INTERVAL配置
最多支持OS_TASK_ID_MAX个任务,可修改宏达到配置最多任务数,实际任务数是OS_TASK_MAX+1个,因为系统默认启动idle任务
任务优先级最多支持OS_TASK_PRIORITY_MAX,数值越小优先级最高,且不会出现相同优先级的两个任务
轮询式
2. 延时等待
延时函数 OS_TASK_TaskDelay
延时单位毫秒(ms)
考虑到优先级和任务切换,该函数表示任务至少需要延时多少毫秒
延时期间,任务阻塞
3. 任务通信
通过事件位图实现通信目的
每个任务最多支持32个事件
任务与任务之间,任务与中断之间都可以相互通信
事件的创建和清除必须手工处理
事件未到达之前,任务阻塞


使用特权

评论回复
沙发
tpgf|  楼主 | 2021-11-3 17:01 | 只看该作者
OS使用说明

​ 下面是操作系统任务相关的函数接口


/*==================================================================

* Function        : OS_TASK_CreateTask

* Description        : 创建新任务

* Input Para        :     

    IN U8 id,  任务号,同任务优先级,每个任务唯一

    IN TaskFunction_t taskHandle,  任务函数,任务入口

    IN U16 taskStackDeep,  任务的最大堆栈深度,sizeof(StackSize_t)*taskStackDeep=实际占内存字节数

    IN U32 *eventBitMap  任务事件位图,用于任务通信,不需要可以填NULL

* Output Para        : 无

* Return Value:

    OS_OK    创建成功

    OS_ERROR 创建失败

==================================================================*/

extern S32 OS_TASK_CreateTask

(

    IN U8 id,

    IN TaskFunction_t taskHandle,

    IN U16 taskStackDeep,

    IN U32 *eventBitMap

);


/*==================================================================

* Function        : OS_TASK_SchedulerTask

* Description        : 启动任务调度

* Input Para        : 无

* Output Para        : 无

* Return Value: 无

==================================================================*/

extern VOID OS_TASK_SchedulerTask(VOID);


/*==================================================================

* Function        : OS_TASK_TaskDelay

* Description        : 用于阻塞任务等待超时

* Input Para        : IN U16 ms  阻塞毫秒数

* Output Para        : 无

* Return Value: 无

==================================================================*/

extern VOID OS_TASK_TaskDelay(IN U16 ms);


/*==================================================================

* Function        : OS_TASK_WaitForEvent

* Description        : 用于阻塞任务等待事件

* Input Para        : 无

* Output Para        : 无

* Return Value: 无

==================================================================*/

extern VOID OS_TASK_WaitForEvent(VOID);



使用特权

评论回复
板凳
tpgf|  楼主 | 2021-11-3 17:02 | 只看该作者
OS原理分析
​ 首先,需要理解CPU是如何运行程序的,然后,理解操作系统是如何完成任务切换的。下面我们从这3个问题出发去理解:

代码编译后发生了什么呢?

答:当我们打开map文件时,我们就会理解,代码编译连接的过程其实是把我们写每一行代码都映射到代码空间上地址上的一个过程,最终生产的bin文件就是代码段的完全映射。如下图



把编译好的bin文件烧录到CPU上,CPU发生了什么?

答:烧录过程,只是把bin文件完整的写入到Flash上而已

上电后,CPU又产生了什么变化?

答:

从CPU的起始地址开始,如图,M3内核的CPU会从0x00000004开始运行代码



图中示范了一个最简单的例子,每步的执行,实际只是PC指针的调整和相应寄存器赋值取值的过程




上面的例子可以理解,单个任务时CPU是如何运作的,那么当多个任务时,我们只要把上面用到的寄存器和堆栈,每个任务复制一份,独立存储访问,然后切换PC指针和堆栈指针就可以完成任务的调度切换。如下图


使用特权

评论回复
地板
tpgf|  楼主 | 2021-11-3 17:03 | 只看该作者
OS设计说明1. 任务状态切换

如下图,任务必须严格按这三个状态切换


2. 任务创建流程

该代码运行在系统特权级线程模式,堆栈使用MSP

申请任务控制块

申请堆栈内存,并赋值栈顶指针,注意:malloc的内存返回地址是内存的起始地址,而堆栈是向下生产的,所以栈顶指针赋值时必须加上堆栈深度

初始化堆栈空间,必须严格按Cortex M3的入栈要求执行

任务状态置为ready


static StackSize_t* TASK_TaskStackFirstInit(IN StackSize_t *topStack, IN TaskFunction_t func)

{

    /* 按堆栈地址顺序入栈,而非寄存器入栈顺序

     * PSR,PC,LR,R12,R3,R2,R1,R0 以上是芯片自动入栈的

     * R4,R5,R6,R7,R8,R9,R10,R11 以上手工入栈,入出栈顺序注意保持一致

     * 此处也可以增加计数,用于堆栈溢出检查

     */

    topStack--;

    *topStack = OS_TASK_INITIAL_XPSR;

    topStack--;

    *topStack = (((StackSize_t)func) & OS_TASK_START_ADDRESS_MASK);

    topStack--; /* 任务栈初次初始化,已是最上层了,返回即错,因此可以增加返回函数用户调试 */

    topStack -= 5; /* 可用于函数入参 */

    topStack -= 8;

    return topStack;

}


S32 OS_TASK_CreateTask

(

    IN U8 id,

    IN TaskFunction_t taskHandle,

    IN U16 taskStackDeep,

    IN U32 *eventBitMap

)

{

    TCB_S *newTcb = NULL;

    StackSize_t *topStack = NULL;

   

    if (id >= OS_TASK_MAX)

    {

        return OS_ERROR;

    }

   

    newTcb = (TCB_S*)malloc(sizeof(TCB_S));

    if (NULL == newTcb)

    {

        return OS_ERROR;

    }

   

    newTcb->state = TASK_INIT;

    topStack = (StackSize_t *)malloc(sizeof(StackSize_t)*taskStackDeep);

    if (NULL == topStack)

    {

        return OS_ERROR;

    }

    topStack += sizeof(StackSize_t)*taskStackDeep;


    newTcb->topStack = TASK_TaskStackFirstInit(topStack, taskHandle);

   

    newTcb->state = TASK_READY;

    newTcb->delay = 0;

    newTcb->delayMax = 0;

    newTcb->eventBitMap = eventBitMap;

    newTcb->id = id;


    gTaskTcbList[id] = newTcb;

   

    return OS_OK;

}



3. 任务首次调度流程
​ 该流程的目的是把CPU的控制权,有特权级线程模式切成用户级线程模式,根据OS原理分析得知,需要做如下处理

找到当前优先级最高且处于Ready状态的任务,即gCurrentTCB指向的任务

触发svc 0,进入SVC中断,此时处于handler模式

PSP指向当前任务的堆栈指针

利用LR寄存器异常返回特性,返回到线程模式使用线程堆栈,完成CPU控制权交接给当前任务,如图



__asm static VOID TASK_SvcHandler(VOID)
{
    extern gCurrentTCB;

    /* 任务相关内容映射到入线程栈 */
        ldr        r3, =gCurrentTCB
        ldr r1, [r3]
        ldr r0, [r1]
        ldmia r0!, {r4-r11}
    msr psp, r0
    isb

    /* 利用LR寄存器异常返回进入线程模式特性 */
        mov r14, #0xfffffffd
        bx r14
    nop
}

void SVC_Handler(void)
{
    TASK_GetCurrentTask();
    TASK_SvcHandler();
}

__asm static VOID TASK_StartFirstTask(VOID)
{
    /* 触发svc,在svc中断中通过修改LD寄存器值的方式进入线程模式 */
    svc 0
    nop
    nop
}

VOID OS_TASK_SchedulerTask(VOID)
{   
    TASK_StartFirstTask();
    return;
}



使用特权

评论回复
5
tpgf|  楼主 | 2021-11-3 17:03 | 只看该作者
4. 任务调度切换流程
​ 当存在多个任务时,每隔任务都需要轮流取得CPU控制权,从而达到并行运作的效果,目前设计的是每隔10ms切换一次,实现流程如下:

配置systick为1ms触发一次中断
每10ms触发一次PendSV中断,使用PendSV进行任务上下文切换可以完美避开中断中发生任务切换的问题,必须注意:PendSV中断优先级必须最低
PendSV中断中,做如下事情
把当前任务入栈,主要是R4-R11,因为其它已自动入栈
切换任务上下文,注意堆栈保存,R3, r14需要重新恢复
使用新任务栈返回
#define TASK_NVIC_INT_CTRL_REG                ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define TASK_NVIC_PENDSVSET_BIT                ( 1UL << 28UL )
/* ICSR寄存器bit28置1,触发PendSV中断 */
#define OS_TASK_SWITCH  TASK_NVIC_INT_CTRL_REG = TASK_NVIC_PENDSVSET_BIT

VOID SysTick_Handler(VOID)
{
    TASK_DelayList(); /* 本例中忽略 */
    TASK_WaitForEventList(); /* 本例中忽略 */
    gTaskSysTickCount++;
    if ((gTaskSysTickCount%OS_TASK_SWITCH_INTERVAL) != 0)
    {
        return;
    }

    OS_TASK_SWITCH;
}

__asm VOID PendSV_Handler(VOID)
{
    extern gCurrentTCB;
    extern TASK_GetCurrentTask;

    /* 把当前任务入栈,主要是R4-R11,因为其它已自动入栈 */
    mrs r0, psp
    isb
    stmdb r0!, {r4-r11}
    dsb
    isb

    /* 把堆栈地址映射到TCB */
    ldr r3, =gCurrentTCB
    ldr r2, [r3]  /* r2 = gCurrentTCB*/
    str r0, [r2]  /* 把r0赋值给gCurrentTCB->topStack */

    /* 切换任务上下文,注意堆栈保存,R3, r14需要重新恢复*/
    stmdb sp!, {r3,r14}
    dsb
    isb
    bl TASK_GetCurrentTask
        ldmia sp!, {r3,r14}
    dsb
    isb

    /* 获取新任务栈 */
        ldr r1, [r3]
        ldr r0, [r1]
        ldmia r0!, {r4-r11}
    dsb
    isb
    msr psp, r0
    isb

        bx r14
    nop
}


使用特权

评论回复
6
tpgf|  楼主 | 2021-11-3 17:04 | 只看该作者
5. 任务超时阻塞等待流程
​ 经常需要某任务等待一段时间后再继续运行,即延时,其中等待的这段时间内,其它任务可以运行,从而充分利用CPU资源,流程如下:

保存需要等待的时间到任务控制块中,任务状态置成SUSPENDED,并释放CPU控制权

在systick 中断中,轮询每个任务,检测是否超时,

如果超时,则将任务状态置成READY,触发调度
如果没有到时间,则状态保持SUSPENDED

VOID OS_TASK_TaskDelay(IN U16 ms)
{
    if ((0 == gCurrentTCB->delay) && (0 == gCurrentTCB->delayMax))
    {
        gCurrentTCB->delayMax = ms;
        gCurrentTCB->delay = gTaskSysTickCount;
        gCurrentTCB->state = TASK_SUSPENDED;
        OS_TASK_SWITCH;
    }
}

static VOID TASK_DelayList(VOID)
{
    volatile TCB_S *tmpTcb = NULL;
    U8 id = 0;

    for (id = 0; id < OS_TASK_MAX; id++)
    {
        tmpTcb = gTaskTcbList[id];
        if (NULL == tmpTcb)
        {
            continue;
        }

        if (tmpTcb->delayMax != 0)
        {
            if ((gTaskSysTickCount - tmpTcb->delay) >= tmpTcb->delayMax)
            {
                tmpTcb->delay = 0;
                tmpTcb->delayMax = 0;
                tmpTcb->state = TASK_READY;
                OS_TASK_SWITCH;
                return;
            }
            else
            {
                tmpTcb->state = TASK_SUSPENDED;
            }
        }
    }

    return;
}


使用特权

评论回复
7
tpgf|  楼主 | 2021-11-3 17:04 | 只看该作者
6. 任务事件阻塞等待流程
​ 经常会遇到这样的需求,一个任务期待在另一个任务触发了某事件后,才执行后续操作,在等待期间,CPU控制权由其他任务占用,提供CPU利用率。流程如下:

事件需要用户申请一个U32类型的全局变量,注意是位图,在任务创建时,填入到任务创建接口的eventBitMap参数,注意,必须传地址。extern S32 OS_TASK_CreateTask(IN U8 id, IN TaskFunction_t taskHandle, IN U16 taskStackDeep, IN U32 *eventBitMap);

在当前任务中需要等待的地方,调用OS_TASK_WaitForEvent函数等待,当事件满足时,该函数后面的代码才会被执行

OS会在在systick 中断中,轮询每个任务,检测是否收到事件,

如果收到,则任务状态置成READY,并触发任务切换
如果没收到,则继续保持SUSPENDED状态

VOID OS_TASK_WaitForEvent(VOID)
{
    if (NULL == gCurrentTCB->eventBitMap)
    {
        return;
    }

    if (0 == *gCurrentTCB->eventBitMap)
    {
        gCurrentTCB->state = TASK_SUSPENDED;
        OS_TASK_SWITCH;
    }
}

static VOID TASK_WaitForEventList(VOID)
{
    volatile TCB_S *tmpTcb = NULL;
    U8 id = 0;

    for (id = 0; id < OS_TASK_MAX; id++)
    {
        tmpTcb = gTaskTcbList[id];
        if (NULL == tmpTcb)
        {
            continue;
        }

        if (NULL == tmpTcb->eventBitMap)
        {
            continue;
        }

        if (*tmpTcb->eventBitMap != 0)
        {
            tmpTcb->state = TASK_READY;
            OS_TASK_SWITCH;
        }
        else
        {
            tmpTcb->state = TASK_SUSPENDED;
        }
    }
}


使用特权

评论回复
8
tpgf|  楼主 | 2021-11-3 17:04 | 只看该作者
7. 任务优先级设计
​ 任务总有先后,优先级必不可少,期望当多个任务都进入READY时,可以优先执行优先级最高的任务,当最高优先级的任务转为SUSPENDED状态后,在执行次优先级的任务,用如此简单的设定实现优先调度的目的,如下:

在任务切换,获取任务上下文时,会调用下面的函数,该函数会从头开始变量任务,找到第一个READY状态的任务,使它进入RUNNING状态,获取CPU控制权。

可见,优先级同任务ID,ID越小优先级越高

VOID TASK_GetCurrentTask(VOID)
{
    volatile TCB_S *tmpTcb = NULL;
    U8 id = 0;

    for (id = 0; id < OS_TASK_MAX; id++)
    {
        tmpTcb = gTaskTcbList[id];
        if ((TASK_READY == tmpTcb->state) || (TASK_RUNNING == tmpTcb->state))
        {
            tmpTcb->state = TASK_RUNNING;
            gCurrentTCB = tmpTcb;
            break;
        }
    }

    return;
}


使用特权

评论回复
9
chenjun89| | 2021-11-3 18:58 | 只看该作者
这个讲的详细

使用特权

评论回复
10
sparrow054| | 2021-11-9 09:55 | 只看该作者
这是自己写的OS?
看着眼熟

使用特权

评论回复
11
两只袜子| | 2021-11-9 16:23 | 只看该作者
有空试着写一个,哈哈,练练手

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1923

主题

15596

帖子

11

粉丝