手把手教学自制任务调度系统

原创 大橙子疯嵌入式 2024-10-21 08:15

点击上方 蓝色字体 了解更多的嵌入式编程实用技能。
如果你觉得该文章对你有帮助,欢迎点赞+关注

1. 前言

setjmplongjmp是C语言标准库头文件中提供的函数。它们的功能是实现非局部跳转,可以在程序的不同位置之间进行跳转,类似于goto语句的扩展。这种非局部跳转的能力为我们构建查询式协作多任务系统提供了基础。

实现前先简单了解一下相关知识,方便后续开展实现。

跳转函数

  • setjmp函数:用于保存当前程序状态,创建一个可以供后续longjmp函数跳转的上下文环境。

  • longjmp函数:实现了对保存的上下文环境的跳转操作。通过传递之前由setjmp函数保存的jmp_buf标识符,longjmp函数会将程序的状态还原到对应的上下文环境,并且会返回到setjmp处继续执行。

  • 在调用setjmp时,程序会记录当前的程序计数器、寄存器和堆栈等状态信息,并将这些信息保存在一个jmp_buf结构中。同时,setjmp函数返回0作为普通调用的返回值,并将jmp_buf作为标识符存储起来。

  • 不同平台的jmp_buf的类型定义不一样,因为不同平台的相关寄存器等不一样,因此占用的大小也不同。

  • 栈指针:每个任务在运行时都有一个栈指针,指向其栈的顶部。任务切换时,需要保存这个指针(jmp_buf会保存),以便在任务恢复时能够正确访问该任务的栈数据。

  • 局部变量和返回地址:栈用于存储任务的局部变量、函数参数和返回地址。在上下文切换时,这些信息也需要被保存,以确保任务能够在恢复时继续执行。

  • 独立栈空间:每个任务都有自己的栈,确保任务之间的局部变量和状态不会相互干扰。这种隔离使得并发执行的任务能够独立运行,提高系统的稳定性。

汇编

这里需要用到一点点汇编,即设置栈顶的位置,不同平台使用的汇编不一样,这里可以在网上查到或者提供的demo中也能找到,只需要一条语句即可。
如 :

  • x86 平台:

    #define COT_OS_SET_STACK(p)        __asm__ volatile("mov %0, %%rsp" : : "r" (p) : "memory");
  • stm32

    #define COT_OS_SET_STACK(p)        __set_MSP(p);

功能实现

利用setjmplongjmp实现一个任务调度系统(协程),setjmp用于保存当前程序的执行环境,而longjmp用于跳转到之前保存的执行环境。

具体需要实现三个核心功能。

流程定义

创建任务

  1. 初始化任务相关变量:申请相关内存,后续储存任务栈信息等

  2. 保存新任务的入口环境:设置新任务栈顶指针后,保存该环境,方便后续任务启动时从这里开始执行

  3. 将新的任务添加到任务列表:任务调度使用

启动任务

  1. 保存当前启动函数的执行环境:当所有任务都结束后还可以跳转到这退出该函数

  2. 跳转到第一个任务函数的入口执行环境,开始执行任务

休眠任务

  1. 更新保存当前任务函数此时的执行环境:下次任务切换运行时可以恢复到当前位置继续往下执行

  2. 查询就绪任务并跳转到就绪任务函数的入口执行环境或者更新后的执行环境

流程图

任务函数的流程走向图:

代码实现

TCB等信息定义

typedef struct stTCB
{

    uint8_t state;
    uint32_t nextRunTime;
    jmp_buf env;
    cotOsTask_f pfnOsTaskEnter;
    struct stTCB *pNext;
} TCB_t;

#define COMMON_TASK_INTI                0
#define COMMON_TASK_RUN                 1

#define MAIN_TASK_INTI                  0
#define MAIN_TASK_EXIT                  1

#define TASK_STATUS_READY               0  // 就绪
#define TASK_STATUS_RUNNING             1  // 运行
#define TASK_STATUS_SUSPEND             2  // 挂起
#define TASK_STATUS_DELETED             3  // 删除

创建任务

在函数中,设置新的栈顶后,由于还需要函数中定义的变量,为了防止设置新的堆栈后相关变量生命周期失效,需要使用static修饰定义,保证其生命周期。

cotOsTask_t cotOs_CreatTask(cotOsTask_f pfnOsTaskEnter, void *pStack, size_t stackSize)
{
    // 防止设置新的堆栈后该变量生命周期失效
    static TCB_t *s_pNewTCB = NULL;
    static jmp_buf s_creatTaskEnv;

    if (pStack == NULL || stackSize == 0)
    {
        return NULL;
    }

    s_pNewTCB = CreatTCB(&sg_OsInfo);

    if (NULL == s_pNewTCB)
    {
        return NULL;
    }

    s_pNewTCB->pfnOsTaskEnter = pfnOsTaskEnter;
    s_pNewTCB->pNext = NULL;
    s_pNewTCB->state = TASK_STATUS_READY;
    s_pNewTCB->nextRunTime = 0;

    if (0 == setjmp(s_creatTaskEnv))
    {
        COT_OS_SET_STACK(((size_t)pStack + stackSize));

        if (COMMON_TASK_INTI == setjmp(s_pNewTCB->env))
        {
            // 设置新的栈顶后记录创建任务的入口后返回原来的任务栈继续运行
            longjmp(s_creatTaskEnv, 1);
        }
        else
        {
            // 任务入口位置
            sg_OsInfo.pCurTCB->state = TASK_STATUS_RUNNING;
            sg_OsInfo.pCurTCB->pfnOsTaskEnter(sg_OsInfo.pCurTCB->param);
            sg_OsInfo.pCurTCB->state = TASK_STATUS_DELETED;
            DestoryTask(&sg_OsInfo, sg_OsInfo.pCurTCB);

            if (GetTaskNum(&sg_OsInfo) > 0)
            {
                JumpNextTask(&sg_OsInfo);
            }
            else
            {
                // 没有任务则返回到启动任务的位置,可以退出
                longjmp(sg_OsInfo.env, MAIN_TASK_EXIT);
            }
        }
    }

    AddToTCBTaskList(&sg_OsInfo, s_pNewTCB);

    return s_pNewTCB;
}

启动任务

启动任务,保存该位置的执行环境,方便所有任务退出后这里可以正常退出函数。

int cotOs_Start(void)
{
    if (sg_OsInfo.pTCBList == NULL)
    {
        return -1;
    }

    int ret = setjmp(sg_OsInfo.env);

    if (MAIN_TASK_INTI == ret)
    {
        sg_OsInfo.pCurTCB = sg_OsInfo.pTCBList;
        longjmp(sg_OsInfo.pCurTCB->env, COMMON_TASK_RUN);
    }

    // 退出
    return 0;
}

休眠任务

任务休眠,则更新当前执行环境,并查询可运行的函数进行跳转

void cotOs_Wait(uint32_t time)
{
    sg_OsInfo.pCurTCB->nextRunTime = sg_OsInfo.pfnGetTimerMs() + time;
    sg_OsInfo.pCurTCB->state = TASK_STATUS_SUSPEND;

    if (COMMON_TASK_RUN != setjmp(sg_OsInfo.pCurTCB->env))
    {
        JumpNextTask(&sg_OsInfo);
    }
}

功能扩展

上述实现了基本功能,要求每个任务都有自己独立的栈空间。

为了适应内存资源少的平台,可以增加共享栈的任务调度,即多个任务使用同一个栈空间运行各自的任务。

共享栈任务的核心有:

  • 任务在运行时独享该共享栈:单线程运行的,只有待该任务休眠时则释放该栈空间给到下一个该类型的任务独享运行。

  • 每个任务都有自己的备用栈:主要用来储存任务休眠前时储存在共享栈的数据,不过该备用栈的大小较小,只需要几十或者上百字节即可。

  • 使用其他独立栈切换共享栈任务:共享栈任务互相切换时,由于需要将备份栈的数据恢复到共享栈空间,为了防止破环当前任务切换使用的栈数据,需要跳转到独立的栈空间中进行数据恢复并切换。

  • 轻量级任务:由于备用栈的空间较小,因此要求该类型任务尽量不在入口函数中定义局部变量(可以定义static修饰的变量,不会占用栈空间),同时只能在入口函数这一层中去休眠任务(在嵌套函数休眠,所使用的栈空间更多,那么需要备份的栈数据就更多)

代码实现

创建任务

  1. 初始化任务相关变量:申请相关内存,后续储存任务栈信息等

  2. 保存新任务的入口环境:设置新任务栈顶指针后,保存该环境,方便后续任务启 动时从这里开始执行

  3. 将新的任务添加到任务列表:任务调度使用

新增:

  1. 区分共享栈和独立栈的处理

cotOsTask_t cotOs_CreatTask(cotOsTask_f pfnOsTaskEnter, CotOSStackType_e eStackType, void *pStack, size_t stackSize)
{
    // 防止设置新的堆栈后该变量生命周期失效
    static TCB_t *s_pNewTCB = NULL;
    static jmp_buf s_creatTaskEnv;

    if (eStackType == COT_OS_UNIQUE_STACK && (pStack == NULL || stackSize == 0))
    {
        return NULL;
    }

    if (eStackType == COT_OS_SHARED_STACK && sg_OsInfo.sharedStackTop == 0)
    {
        return NULL;
    }

    s_pNewTCB = CreatTCB(&sg_OsInfo);

    if (NULL == s_pNewTCB)
    {
        return NULL;
    }

    s_pNewTCB->pfnOsTaskEnter = pfnOsTaskEnter;
    s_pNewTCB->pNext = NULL;
    s_pNewTCB->state = TASK_STATUS_READY;
    s_pNewTCB->nextRunTime = 0;
    s_pNewTCB->pBakStack = eStackType == COT_OS_SHARED_STACK ? CreatTCBStack(&sg_OsInfo) : NULL;

    // 这里先判断类型再保存环境,防止先保存环境后再去判断类型分别设置栈顶,导致入栈数据错乱引发异常问题
    if (eStackType == COT_OS_UNIQUE_STACK)
    {
        if (0 == setjmp(s_creatTaskEnv))
        {
            COT_OS_SET_STACK(((size_t)pStack + stackSize));

            if (COMMON_TASK_INTI == setjmp(s_pNewTCB->env))
            {
                longjmp(s_creatTaskEnv, 1);
            }
            else
            {
                RunTask(&sg_OsInfo);
            }
        }
    }
    else
    {
        if (s_pNewTCB->pBakStack == NULL)
        {
            DestroyTCB(&sg_OsInfo, sg_OsInfo.pCurTCB);
            return NULL;
        }

        if (0 == setjmp(s_creatTaskEnv))
        {
            COT_OS_SET_STACK(sg_OsInfo.sharedStackTop);

            if (COMMON_TASK_INTI == setjmp(s_pNewTCB->env))
            {
                longjmp(s_creatTaskEnv, 1);
            }
            else
            {
                RunTask(&sg_OsInfo);
            }
        }
    }

    AddToTCBTaskList(&sg_OsInfo, s_pNewTCB);

    return s_pNewTCB;
}

启动任务

  1. 保存当前启动函数的执行环境:当所有任务都结束后还可以跳转到这退出该函数

  2. 跳转到第一个任务函数的入口执行环境,开始执行任务

新增:

  1. 共享栈任务需要运行时,先跳转到该位置,利用main主任务的未使用的栈空间进行任务切换(尽量充分利用未使用的栈空间),先将即将执行的共享栈任务备份数据恢复到共享栈上,然后跳转过去

这里主要防止共享栈任务切换到下一个共享栈任务,还没切换时共享栈就被覆盖破坏导致程序运行异常的问题。

int cotOs_Start(void)
{
    if (sg_OsInfo.pTCBList == NULL || sg_OsInfo.pfnGetTimerMs == NULL)
    {
        return -1;
    }

    int ret = setjmp(sg_OsInfo.env);

    if (MAIN_TASK_INTI == ret)
    {
        sg_OsInfo.pCurTCB = sg_OsInfo.pTCBList;
        longjmp(sg_OsInfo.pCurTCB->env, COMMON_TASK_RUN);
    }
    else if (MAIN_TASK_JUMP_SHARED_TASK == ret)
    {
        TcbMemcpy((uint8_t *)(sg_OsInfo.sharedStackTop - COT_OS_MAX_SHARED_BAK_STACK_SIZE), 
            sg_OsInfo.pCurTCB->pBakStack,  COT_OS_MAX_SHARED_BAK_STACK_SIZE);
        longjmp(sg_OsInfo.pCurTCB->env, COMMON_TASK_RUN);
    }
    return 0;
}

休眠任务

  1. 更新保存当前任务函数此时的执行环境:下次任务切换运行时可以恢复到当前位置继续往下执行

  2. 查询就绪任务并跳转到就绪任务函数的入口执行环境或者更新后的执行环境

新增:

  1. 查询前如果当前任务是共享栈任务,则先将栈空间保存到该任务的备份栈空间。

void cotOs_Wait(uint32_t time)
{
    sg_OsInfo.pCurTCB->nextRunTime = sg_OsInfo.pfnGetTimerMs() + time;
    sg_OsInfo.pCurTCB->pCondition = NULL;
    sg_OsInfo.pCurTCB->state = TASK_STATUS_SUSPEND;

    if (COMMON_TASK_RUN != setjmp(sg_OsInfo.pCurTCB->env))
    {
        if (sg_OsInfo.pCurTCB->pBakStack != NULL)
        {
            TcbMemcpy(sg_OsInfo.pCurTCB->pBakStack, 
                (uint8_t *)(sg_OsInfo.sharedStackTop - COT_OS_MAX_SHARED_BAK_STACK_SIZE), COT_OS_MAX_SHARED_BAK_STACK_SIZE);
        }

        JumpNextTask(&sg_OsInfo);
    }
}

总结

至此,已完成一个任务调度系统的实现。

想看完整代码实现的,程序源码:查询协作式多任务系统


评论 (0)
  • 为通过金融手段积极推进全球绿色发展,国际金融论坛(IFF)于2020年创立了“IFF全球绿色金融奖”,旨在对全球绿色金融领域取得突出成绩的机构及创新性的解决方案进行表彰和奖励。该奖项依托IFF“高层次、高水平、国际化”一流智库资源优势,积极促进绿色金融领域的国际交流合作和创新实践,助力联合国可持续发展目标的实现。“IFF全球绿色金融奖”重点关注和鼓励那些促进经济增长模式转型、防治环境污染、应对气候变化,以及致力于提高能效水平、强化节能减排实效的绿色金融创新解决方案。该奖项面向全球,是对政策创新、
    华尔街科技眼 2025-04-24 20:43 14浏览
  •   通用装备论证与评估系统平台解析   北京华盛恒辉通用装备论证与评估系统平台是服务军事装备全生命周期管理的综合性信息化平台,通过科学化、系统化手段,实现装备需求论证、效能分析等核心功能,提升装备建设效益。   应用案例   目前,已有多个通用装备论证与评估系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润通用装备论证与评估系统。这些成功案例为通用装备论证与评估系统的推广和应用提供了有力支持。   一、系统分层架构   (一)数据层   整合装备性能、作战、试验等多源异
    华盛恒辉l58ll334744 2025-04-24 16:14 130浏览
  • 引言在智能语音技术飞速发展的今天,语音交互已成为消费电子、智能家居、工业控制等领域的标配功能。传统的ISD系列录音芯片虽应用广泛,但其高成本与功能局限性逐渐难以满足市场对高性价比、高灵活性的需求。推出的WT2000P录音语音芯片,凭借其卓越性能、低功耗设计及高度可定制化特性,成为ISD系列芯片的理想替代方案,助力开发者突破产品创新瓶颈。一、WT2000P产品概述WT2000P是一款专为嵌入式语音场景设计的多功能录音芯片,采用ESOP8封装,体积小巧(尺寸仅4.9mm×3.9mm),集成度高,支持
    广州唯创电子 2025-04-25 08:44 19浏览
  • 引言:语音交互的智能化跃迁在全球化与智能化深度融合的今天,语音交互设备的应用场景已从单一提示功能向多语言支持、情感化表达及AI深度交互演进。传统离线语音方案受限于语种单一、存储容量不足等问题,而纯在线方案又依赖网络稳定性,难以满足复杂场景需求。WT3000A离在线TTS方案,通过“本地+云端”双引擎驱动,集成16国语种、7种方言切换、AI大模型对话扩展等创新功能,重新定义语音提示器的边界,为智能硬件开发者提供更灵活、更具竞争力的语音交互解决方案。一、方案核心亮点离在线双模融合,场景全覆盖离线模式
    广州唯创电子 2025-04-25 09:14 31浏览
  • 随着轻薄笔记本的普及,再加上电竞玩家对于高画质音视频体验的需求日益高涨,如何让轻薄笔记本在兼顾轻便携带性的同时,还能提供足以支持3A(AAA/Triple-A game)大作的良好运算性能,便成为各家品牌急欲突破的共同难题。然而,对于主打轻巧便携的轻薄笔记本而言,若要内置独立显卡,势必要先突破空间受限的瓶颈,同时还需解决散热问题,确实难以兼顾两全!对此,“Thunderbolt”与“OCuLink”这两项技术应运而生。用户可以通过这两种传输接口,再搭配外接显卡盒(eGPU)及高性能显卡(如NVI
    百佳泰测试实验室 2025-04-24 17:56 27浏览
  • 最近,途虎养车发布的2024年财报数据,可谓相当吸睛。全年营收达到147.59亿元,同比增长8.5%,这个数字直观地展现了途虎在市场上的强大吸金能力,在行业里稳稳占据前列。利润方面同样出色,毛利37.46亿元,毛利率提升0.7个百分点至25.4%;经调整净利润6.24亿元,同比增长 29.7%,经营利润同比更是增长104%至3.31亿元,盈利能力显著增强,这样的利润增长幅度,在同行业中十分亮眼。在用户规模上,途虎养车同样成绩斐然。累计注册用户近1.4亿,同比增长20.4%,交易用户数达2410万
    用户1742991715177 2025-04-24 19:12 28浏览
  •   有效样本分析决策系统平台全面解析   一、引言   北京华盛恒辉有效样本分析决策系统在当今数据驱动的时代,企业、科研机构等面临着海量数据的处理与分析挑战。有效样本分析决策系统平台应运而生,它通过对样本数据的精准分析,为决策提供有力支持,成为提升决策质量和效率的关键工具。   应用案例   目前,已有多个有效样本分析决策系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润有效样本分析决策系统。这些成功案例为有效样本分析决策系统的推广和应用提供了有力支持。   二、平台概述
    华盛恒辉l58ll334744 2025-04-24 11:13 114浏览
  •   高海拔区域勤务与装备保障调度系统平台解析   北京华盛恒辉高海拔区域勤务与装备保障调度系统平台专为高海拔特殊地理环境打造,致力于攻克装备适应、人员健康保障、物资运输及应急响应等难题。以下从核心功能、技术特点、应用场景及发展趋势展开全面解读。   应用案例   目前,已有多个高海拔区域勤务与装备保障调度系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润高海拔区域勤务与装备保障调度系统。这些成功案例为高海拔区域勤务与装备保障调度系统的推广和应用提供了有力支持。   一、核心
    华盛恒辉l58ll334744 2025-04-24 10:13 116浏览
  •   航空兵训练与战术对抗仿真平台系统解析   北京华盛恒辉航空兵训练与战术对抗仿真平台系统是现代军事训练的关键工具,借助计算机技术构建虚拟战场,支持多兵种协同作战模拟,为军事决策、训练及装备研发提供科学依据。   应用案例   目前,已有多个航空兵训练与战术对抗仿真平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润航空兵训练与战术对抗仿真平台。这些成功案例为航空兵训练与战术对抗仿真平台的推广和应用提供了有力支持。   一、系统架构与核心功能   系统由模拟器、计算机兵力生
    华盛恒辉l58ll334744 2025-04-24 16:34 141浏览
  •   陆地装备体系论证与评估综合平台系统解析   北京华盛恒辉陆地装备体系论证与评估综合平台系统是契合现代军事需求而生的专业系统,借助科学化、智能化手段,实现对陆地装备体系的全方位论证与评估,为军事决策和装备发展提供关键支撑。以下从功能、技术、应用及展望展开分析。   应用案例   目前,已有多个陆地装备体系论证与评估综合平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润陆地装备体系论证与评估综合平台。这些成功案例为陆地装备体系论证与评估综合平台的推广和应用提供了有力支持。
    华盛恒辉l58ll334744 2025-04-24 10:53 125浏览
  • 2025-4-25全球信息报告出版商Global Info Research(环洋市场咨询)发布了【2025年全球市场高介电常数材料总体规模、主要生产商、主要地区、产品和应用细分研究报告】,报告主要调研全球高介电常数材料总体规模、主要地区规模、主要生产商规模和份额、产品分类规模、下游主要应用规模以及未来发展前景预测。统计维度包括销量、价格、收入,和市场份额。同时也重点分析全球市场主要厂商(品牌)产品特点、产品规格、价格、销量、销售收入及发展动态。历史数据为2020至2024年,预测数据为2025
    用户1745398400862 2025-04-25 08:48 31浏览
  •   海上训练与保障调度指挥平台系统解析   北京华盛恒辉海上训练与保障调度指挥平台系统是现代海上作战训练的核心枢纽,融合信息技术、GIS、大数据及 AI 等前沿技术,旨在实现海上训练高效组织、作战保障科学决策。以下从架构功能、应用场景、系统优势及发展挑战展开解读。   应用案例   目前,已有多个海上训练与保障调度指挥平台在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润海上训练与保障调度指挥平台。这些成功案例为海上训练与保障调度指挥平台的推广和应用提供了有力支持。   一
    华盛恒辉l58ll334744 2025-04-24 15:26 127浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦