使用C语言构建一个独立栈协程和共享栈协程的任务调度系统

这篇具有很好参考价值的文章主要介绍了使用C语言构建一个独立栈协程和共享栈协程的任务调度系统。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

使用了标准库头文件 <setjmp.h>中的 setjmplongjmp两个函数,构建了一个简单的查询式协作多任务系统,支持独立栈共享栈两种任务。

  1. 其中涉及到获取和设置栈的地址操作,因此还需要根据不同平台提供获取和设置栈的地址操作(一般是汇编语言,因为涉及到寄存器)
  2. 该调度系统仅运行在一个实际的线程中,因此本质上属于协程
  3. 独立栈任务都有自己独立的运行栈空间,互不干扰;共享栈任务共用一个运行栈空间。

特点

  • 无任务优先级抢占的功能。

  • 任务切换的时机完全取决于正在运行的任务,体现协作

  • 支持独立栈共享栈两种任务,根据不同的应用场景决定。

  • 查询式的调度方式,当前任务切换时,查询下个任务是否需要执行。

  • 移植性强,只需要修改设置栈和获取当前栈地址的宏即可。

  • 相对于时间片论法的任务调度来说,查询式协作多任务系统有以下特点:

    • 无需使用定时器做为任务调度
    • 每个任务都可以使用while循环,用于执行任务并保持程序的运行,程序结构清晰
    • 每个任务都可以随时阻塞等待,甚至可以在嵌套的子函数中阻塞等待
    • 通过阻塞等待,无需使用状态机等较为复杂的方式来优化缩减每个任务的执行时长
  • 相对于RTOS操作系统来说,查询式协作多任务系统有以下特点:

    • 没有任务优先级抢占式的功能,因此临界资源(中断除外)和优先级反转的问题也不存在
    • 允许用户或应用程序根据需要自由地切换到下一个就绪任务
    • 通过自主调度和管理任务,查询式协作多任务系统可以提高工作效率
    • 没有操作系统的复杂

功能设计

运行栈空间:程序运行中发生函数调用等情况需要使用的栈内存空间

独立栈任务(有栈任务)

每个独立栈任务都拥有自己独立的运行栈空间,可以随时随地阻塞等待,保存上下文后切换到下一个任务执行

独立栈任务在切换下一个任务时,不会操作运行栈,只对上下文切换

共享栈任务(无栈任务)

每个共享栈任务都没有自己独立的运行栈空间,虽然也能阻塞等待,但是仅限于在任务入口函数中使用,禁止在任务的子函数(嵌套函数)中阻塞等待;并且在该任务入口函数中不建议定义相关变量。

  • 每个任务有自己的独立备份栈(用来备份运行栈的栈顶部分数据);运行栈通常比备份栈要大很多,否则任务函数无法正常运行多级嵌套的函数
  • 共享栈任务在切换下一个任务时会将当前运行栈(共享栈)提前设置好的备份栈大小(宏配置)拷贝到内存备份起来,等下次即将执行时再从内存中拷贝到运行栈(共享栈)进行恢复
  • 通过修改加大备份栈大小(宏配置)的值,可以在共享栈任务入口函数定义变量,这样可以避免这些变量的值没有备份导致丢失,或者通过 static 定义局部变量
  • 该类型任务适合于轻量的任务处理,一般都是调用封装好的函数即可

注:这里的共享栈任务和常规的实现有一些差异,常规的实现是使用堆申请内存保存栈的数据,用多少申请多少进行保存,而这里的实现仅仅保存了一部分数据。

任务创建

  1. 在调度系统启动前,至少要先创建一个任务,否则直接退出
  2. 可以在任务中创建新的任务,不管是独立栈任务还是共享栈任务
    • 独立栈任务中可以创建新的独立栈任务和共享栈任务
    • 共享栈任务中同样可以创建新的独立栈任务和共享栈任务,而且在创建共享栈任务时可以使用同一个共享栈
  3. 独立栈任务和共享栈任务一共可以创建最多32个任务(需要修改宏配置)

任务销毁

  • 没有提供该功能接口函数,任务入口函数主动退出则自动将任务销毁。
  • 可以通过等待任务退出接口函数在其他任务中等待该任务退出。

任务阻塞

当前任务阻塞提供两种方式:

  • 时间阻塞:需要阻塞多长时间,等时间满足后才会继续执行
  • 事件阻塞:通过事件阻塞,只有事件触发后才会继续执行

使用说明

任务创建/退出

对于创建独立栈任务还是共享栈任务的示例代码:


uint8_t g_task1Stack[1024 * 2];
uint8_t g_task2Stack[1024 * 2];
uint8_t g_task3Stack[1024 * 2];

uint8_t g_sharedStack[1024 * 2];

// 执行完成就退出的任务
void taskfunc3(int arg)
{
    ...
    cotOs_Wait(1000);
    ...
    cotOs_Wait(1000);
}

void taskfunc1(int arg)
{
   /* 不管taskfunc1是独立栈任务还是共享栈任务,都支持创建子任务 */
   cotOs_CreatTask(taskfunc3, COT_OS_UNIQUE_STACK, g_task3Stack, sizeof(g_task3Stack), 0);  // 创建独立栈任务
   cotOs_CreatTask(taskfunc3, COT_OS_SHARED_STACK, g_sharedStack, sizeof(g_sharedStack), 0); // 创建共享栈任务

    while (1)
    {
        ...
        cotOs_Wait(1000);
    }
}

void taskfunc2(int arg)
{
    while (1)
    {
        ...
        cotOs_Wait(10);
    }
}

int main(void)
{
    cotOs_Init(GetTimerMs);
#if 0
    /* 创建独立栈任务 */
    cotOs_CreatTask(taskfunc1, COT_OS_UNIQUE_STACK, g_task1Stack, sizeof(g_task1Stack), 0);
    cotOs_CreatTask(taskfunc2, COT_OS_UNIQUE_STACK, g_task2Stack, sizeof(g_task2Stack), 0);
#else
    /* 创建共享栈任务 */
    cotOs_CreatTask(taskfunc1, COT_OS_SHARED_STACK, g_sharedStack, sizeof(g_sharedStack), 0);
    cotOs_CreatTask(taskfunc2, COT_OS_SHARED_STACK, g_sharedStack, sizeof(g_sharedStack), 0);
#endif
    cotOs_Start();
}

任务限制

对于创建独立栈任务还是共享栈任务,共享栈任务有限制要求,禁止在任务入口函数的嵌套函数中阻塞


uint8_t g_task1Stack[1024 * 2];
uint8_t g_sharedStack[1024 * 2];

void func1_1(void)
{
    ...
    cotOs_Wait(1000);
    ...
    cotOs_Wait(1000);
}

/* 独立栈任务 */
void taskfunc1(int arg)
{
    int arr[10];   // 可以直接定义变量使用

    while (1)
    {
        func1_1();  // 可以在嵌套函数中使用阻塞等待
        ...
        cotOs_Wait(1000);
    }
}

void func2_1(void)
{
    ...
}

/* 共享栈任务 */
void taskfunc2(int arg)
{
    static int arr[10];  // 建议使用static定义任务内变量或者不定义变量

    while (1)
    {
        func2_1();  // 禁止在嵌套函数中使用阻塞等待
        ...
        cotOs_Wait(10);
    }
}

int main(void)
{
    cotOs_Init(GetTimerMs);

    /* 创建独立栈任务 */
    cotOs_CreatTask(taskfunc1, COT_OS_UNIQUE_STACK, g_task1Stack, sizeof(g_task1Stack), 0);

    /* 创建共享栈任务 */
    cotOs_CreatTask(taskfunc2, COT_OS_SHARED_STACK, g_sharedStack, sizeof(g_sharedStack), 0);

    cotOs_Start();
}

任务阻塞/退出

通过时间和事件的方式阻塞


uint8_t g_task1Stack[1024 * 2];
uint8_t g_task2Stack[1024 * 2];
uint8_t g_task3Stack[1024 * 2];

uint8_t g_sharedStack[1024 * 2];

CotOSCondition_t g_eventCv;

// 执行完成就退出的任务
void taskfunc3(int arg)
{
    ...
    cotOs_ConditionWait(&g_eventCv);
    ...
}

void taskfunc1(int arg)
{
   cotOsTask_t task = cotOs_CreatTask(taskfunc3, COT_OS_UNIQUE_STACK, g_task3Stack, sizeof(g_task3Stack), 0);

    while (1)
    {
        ...
        cotOs_Wait(1000);

        if (...)
        {
            // 等待 taskfunc3 任务运行结束后才退出 taskfunc1
            cotOs_Join(task);
            break;
        }
    }
}

void taskfunc2(int arg)
{
    while (1)
    {
        ...
        cotOs_Wait(10);

        if (...)
        {
            cotOs_ConditionNotify(&g_eventCv);  // 通知 taskfunc3 继续执行
        }
    }
}

int main(void)
{
    cotOs_Init(GetTimerMs);
    cotOs_CreatTask(taskfunc1, COT_OS_SHARED_STACK, g_sharedStack, sizeof(g_sharedStack), 0);
    cotOs_CreatTask(taskfunc2, COT_OS_SHARED_STACK, g_sharedStack, sizeof(g_sharedStack), 0);

    cotOs_Start();
}

不同栈类型任务应用场景

  • 独立栈任务(有栈任务)

    • 重量级任务: 提供更多的控制,适用于需要更精确地管理任务状态的情况和执行计算密集型任务的场景
    • 更可预测的内存使用: 在创建时分配栈空间,可以更好地控制内存使用,适用于需要更可预测内存行为的场景
    • 递归调用: 更容易处理递归调用,因为每个任务都有独立的栈空间
  • 共享栈任务(无栈任务)

    • 轻量级任务: 通常更轻量,适用于大量小任务的场景。
    • 内存效率: 适用于内存受限的环境,因为不需要为每个任务分配各自的栈空间(备份栈除外)。

代码链接

cot_os文章来源地址https://www.toymoban.com/news/detail-825386.html

到了这里,关于使用C语言构建一个独立栈协程和共享栈协程的任务调度系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • unity 等待事件之协程和Invoke

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 提示:这里可以添加本文要记录的大概内容: 协程的用法 和 Invoke 的等待事件使用 提示:以下是本篇文章正文内容,下面案例可供参考 代码如下(示例): 好记性不如烂笔头!

    2024年04月13日
    浏览(39)
  • 【Unity每日一记】“调皮的协程”,协程和多线程的区别在哪里

    👨‍💻个人主页 :@元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏 : unity每日一记 ⭐【软件设计师高频考点暴击】 ⭐【Unityc#专题篇】之c#系统化大礼包】 ⭐【unity数据持久化】数据管理类_PlayerPrfs ⭐【u

    2024年02月05日
    浏览(46)
  • 浅谈Lua协程和函数的尾调用

    虽然不经常用到协程,但是也不能谈虎色变。同时,在有些场景,协程会起到一种不可比拟的作用。所以,了解它,对于一些功能,也会有独特的思路和想法。 概念 关于进程和线程的概念就不多说。 那么从多线程的角度来看,协程和线程有点类似:拥有自己的栈,局部变量

    2024年02月10日
    浏览(44)
  • unity协程 Start/StopCoroutine() 结束协程和再次启动协程存在的问题和解决方案

    仅用于记录遇到的问题和解决方案。 快速阅览: 一、结束协程无效: 协程方法需要单独存一份private IEnumerator myTest,再开始和结束不直接传入方法名,而是使用这份保存的myTest进行开始和结束。 二、再次开启协程时少跑了几行代码: 再次开始同一个方法名的协程时,不是从

    2024年02月15日
    浏览(41)
  • pytorch 40 分享从0开始构建一个独立的深度学习项目

    都2023年了,估计没有几个人会自己独立从0开始构建深度学习项目的了,全是依赖现有的开源库进行项目研制开发。这里回顾几年的工作经验,对构建深度学习项目进行初步梳理。 通常深度学习任务都被描述为:假设函数、损失函数和迭代函数。假设函数一般是我们的模型,

    2024年02月09日
    浏览(55)
  • 在51单片机中使用一个独立按键控制流水灯方向

    用独立按键控制流水灯转向,按下独立按键流水灯向右移动,再按下独立按键流水灯向左移动 此代码用到 逻辑左移:_crol_(移动对象,移动的位数) 逻辑右移:_cror_(移动对象,移动的位数)

    2024年02月07日
    浏览(40)
  • 如何用大语言模型构建一个知识问答系统

    打造  特定领域知识(Domain-specific Knowledge)   问答  系统,具体需求有: 通过自然语言问答的形式,和用户交互,同时支持中文和英文。 理解用户不同形式的问题,找到与之匹配的答案。可以对答案进行二次处理,比如将关联的多个知识点进行去重、汇总等。 支持上下文。有

    2024年02月09日
    浏览(51)
  • 用C语言构建一个数字识别深度神经网络

    接上一篇: 用C语言构建一个数字识别卷积神经网络 1. 深度神经网络 按照深度学习的理论,随着神经网络层数的增加,网络拟合复杂问题的能力也会增强,对事物特征的挖掘也会更加深入.这里尝试构建一个5层深度的神经网络,包括两个卷积层和两个池化层, 其中输出层为全

    2024年02月13日
    浏览(38)
  • 用C语言构建一个数字识别卷积神经网络

     卷积神经网络的具体原理和对应的python例子参见末尾的参考资料2.3. 这里仅叙述卷积神经网络的配置, 其余部分不做赘述,构建和训练神经网络的具体步骤请参见上一篇: 用C语言构建一个手写数字识别神经网路 卷积网络同样采用简单的三层结构,包括输入层conv_layer,中间层

    2024年02月14日
    浏览(43)
  • 用C语言构建一个手写数字识别神经网络

    (原理和程序基本框架请参见前一篇 \\\"用C语言构建了一个简单的神经网路\\\") 1.准备训练和测试数据集 从http://yann.lecun.com/exdb/mnist/下载手写数字训练数据集, 包括图像数据train-images-idx3-ubyte.gz 和标签数据 train-labels-idx1-ubyte.gz. 分别将他们解压后放在本地文件夹中,解压后文件名

    2024年02月14日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包