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

 更新时间:2024年02月05日 15:42:53   作者:大橙子疯  
这篇文章主要为大家详细介绍了如何基于C语言构建一个独立栈协程和共享栈协程的任务调度系统,文中的示例代码讲解详细,需要的可以参考下

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

其中涉及到获取和设置栈的地址操作,因此还需要根据不同平台提供获取和设置栈的地址操作(一般是汇编语言,因为涉及到寄存器)

该调度系统仅运行在一个实际的线程中,因此本质上属于协程

独立栈任务都有自己独立的运行栈空间,互不干扰;共享栈任务共用一个运行栈空间。

特点

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

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

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

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

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

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

  • 无需使用定时器做为任务调度
  • 每个任务都可以使用while循环,用于执行任务并保持程序的运行,程序结构清晰
  • 每个任务都可以随时阻塞等待,甚至可以在嵌套的子函数中阻塞等待
  • 通过阻塞等待,无需使用状态机等较为复杂的方式来优化缩减每个任务的执行时长

相对于RTOS操作系统来说,查询式协作多任务系统有以下特点:

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

功能设计

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

独立栈任务(有栈任务)

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

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

共享栈任务(无栈任务)

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

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

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

任务创建

在调度系统启动前,至少要先创建一个任务,否则直接退出

可以在任务中创建新的任务,不管是独立栈任务还是共享栈任务

  • 独立栈任务中可以创建新的独立栈任务和共享栈任务
  • 共享栈任务中同样可以创建新的独立栈任务和共享栈任务,而且在创建共享栈任务时可以使用同一个共享栈

独立栈任务和共享栈任务一共可以创建最多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

以上就是基于C语言构建一个独立栈协程和共享栈协程的任务调度系统的详细内容,更多关于C语言任务调度系统的资料请关注脚本之家其它相关文章!

相关文章

  • 将字符串str1复制为字符串str2的三种解决方法

    将字符串str1复制为字符串str2的三种解决方法

    以下是对将字符串str1复制为字符串str2的三种解决方法进行了详细的介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-10-10
  • C++短路求值(逻辑与、逻辑或)实例

    C++短路求值(逻辑与、逻辑或)实例

    这篇文章主要介绍了C++短路求值(逻辑与、逻辑或)实例,以实例形式讲述了逻辑或的短路与逻辑与的短路及相应的应用实例,需要的朋友可以参考下
    2014-10-10
  • C++ 中virtual 虚函数用法深入了解

    C++ 中virtual 虚函数用法深入了解

    这篇文章主要介绍了C++ 中virtual 虚函数用法深入了解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • C/C++中的名字空间与作用域示例详解

    C/C++中的名字空间与作用域示例详解

    这篇文章主要给大家介绍了关于C/C++中名字空间与作用域的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用c/c++具有一定的参考学习价值,需要的朋友们下面随着小编来一起看看吧。
    2017-09-09
  • C语言中正切的相关函数总结

    C语言中正切的相关函数总结

    这篇文章主要介绍了C语言中正切的相关函数总结,包括正切和反正切以及双曲线正切等的函数,需要的朋友可以参考下
    2015-08-08
  • vscode调试gstreamer源码的详细流程

    vscode调试gstreamer源码的详细流程

    在本文中主要介绍了如何使用vscode调试C++和python程序,并进一步分析了如何调试gstreamer源码,讲述了如何调试gstreamer源码的具体流程,感兴趣的朋友跟随小编一起看看吧
    2023-01-01
  • C++中memset函数用法详解

    C++中memset函数用法详解

    这篇文章主要介绍了C++中memset函数用法,结合实例形式详细分析了memset函数的功能、使用方法与相关注意事项,需要的朋友可以参考下
    2016-06-06
  • C++17中std::byte的具体使用详解

    C++17中std::byte的具体使用详解

    这篇文章主要为大家详细介绍了C++17中std::byte的具体使用,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2023-11-11
  • C++数据模型应用在QML委托代理机制中

    C++数据模型应用在QML委托代理机制中

    这篇文章主要介绍了在QML委托代理机制中使用C++数据模型,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • c++实现扫雷小游戏代码分享

    c++实现扫雷小游戏代码分享

    这篇文章主要介绍了c++实现扫雷小游戏并附上代码分享,代码不能够实现当所查坐标周围雷的数量为0时,直接展开周围坐标,但有一点的知识性参考价值,需要的小伙伴可以参考一下
    2022-02-02

最新评论