使用了标准库头文件 <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

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

  1. 如何用 Swift 语言构建一个自定控件

    (via:破船之家,原文:How To Make a Custom Control in Swift)   用户界面控件是所有应用程序重要的组成部分之一.它们以图形组件的方式呈现给用户,用户可以通过它 ...

  2. 用C语言构建一个可执行程序的流程

    1.流程图 从用C语言写源代码,然后经过编译器.连接器到最终可执行程序的流程图大致如下图所示. 2.编译流程 首先,我们先用C语言把源代码写好,然后交给C语言编译器.C语言编译器内部分为前端和后端. ...

  3. 用java语言构建一个网络服务器,实现客户端和服务器之间通信,实现客户端拥有独立线程,互不干扰

    服务器: 1.与客户端的交流手段多是I/O流的方式 2.对接的方式是Socket套接字,套接字通过IP地址和端口号来建立连接 3.(曾经十分影响理解的点)服务器发出的输出流的所有信息都会成为客户端的输 ...

  4. c语言-构建一个静态二叉树

    第一.树的构建 定义树结构 struct BTNode { char data; struct BTNode* pLChild; struct BTNode* pRChild; }; 静态方式创建一个 ...

  5. 有了Jenkins,为什么还需要一个独立的部署系统

    需不需要一个独立的部署系统是很多企业用户在构建持续交付流程中经常困惑的一个问题.也经常有用户会问我们,现在已经有Jenkins,它自身提供了丰富的部署插件(如WebSphere部署插件.Tomcat部 ...

  6. Expo大作战(四)--快速用expo构建一个app,expo中的关键术语

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

  7. typescript+webpack构建一个js库

    依赖说明 入口文件 tsconfig配置 webpack配置文件 webpack入口文件配置 webpack为typescript和less文件配置各自的loader webpack的output配置 ...

  8. 如何轻松使用 C 语言实现一个栈?​

    什么是数据结构? 数据结构是什么?要了解数据结构,我们要先明白数据和结构,数据就是一些int char 这样的变量,这些就是数据,如果你是一个篮球爱好者,那么你的球鞋就是你的数据,结构就是怎么把这些数 ...

  9. 开源低代码平台开发实践二:从 0 构建一个基于 ER 图的低代码后端

    前后端分离了! 第一次知道这个事情的时候,内心是困惑的. 前端都出去搞 SPA,SEO 们同意吗? 后来,SSR 来了. 他说:"SEO 们同意了!" 任何人的反对,都没用了,时代 ...

  10. Lua的函数调用和协程中,栈的变化情况

    Lua的函数调用和协程中,栈的变化情况 1. lua_call / lua_pcall   对于这两个函数,对栈底是没有影响的--调用的时候,参数会被从栈中移除,当函数返 回的时候,其返回值会从函数处 ...

随机推荐

  1. 你以为这是MacOS ,其实这是我的 Linux 系统 Manjaro!

    对于如何将你的 Manjaro 系统美化成 MacOS 你需要做以下几件事情: 1.安装 WhiteSur-Gtk-theme 主题. 2.安装 Plank 软件. 3.安装 vala-panel-a ...

  2. 一文看完String的前世今生,内容有点多,请耐心看完!

    写在开头 String字符串作为一种引用类型,在Java中的地位举足轻重,也是代码中出现频率最高的一种数据结构,因此,我们需要像分析Object一样,将String作为一个topic,单独拿出来总结, ...

  3. 01_设计一个有getMin功能的栈

    01_设计一个有getMin功能的栈 [题目] 实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作. [要求] pop.push.getMin操作的时间复杂度都是O(1) 设计 ...

  4. idea安装并使用maven依赖分析插件:Maven Helper

    本文为博主原创,转载请注明出处: 在maven工程中,经常会查看maven的依赖树,在没使用该插件时,需要maven dependency:tree命令进行查看依赖树, 通过maven helper ...

  5. nginx.conf 配置解析及常用配置

    本文为博主原创,未经允许不得转载: nginx.conf 配置文件配置解析 #定义 Nginx 运行的用户和用户组.默认nginx的安装用户为 nobody user www www: #启动进程,通 ...

  6. 21-CMOS门电路的逻辑式

    CMOS门电路的逻辑式 通过CMOS门电路,写出门电路的表达式. 方法 只看下方,因为电路上下是对称的: 先找L(输出)的非,找的输出到地的通路,以原变量进行书写.最后将表达式取非,即可得到L.这种方 ...

  7. 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.12.26)

    一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...

  8. 精通 VS 调试技巧,学习与工作效率翻倍!

    ​ 欢迎大家来到贝蒂大讲堂 ​ 养成好习惯,先赞后看哦~ ​ 所属专栏:C语言学习 ​ 贝蒂的主页:Betty's blog ​ 1. 什么是调试 当我们写代码时候常常会遇见输出结果不符合我们预期的情 ...

  9. [转帖]MySQL数据库8.0.29-8.0.31版本使用 INSTANT 算法新增字段bug

    https://www.cnblogs.com/harda/p/17528512.html xxx下发MySQL数据库共性隐患排查通知,要求统一排查MySQL数据库8.0.29及以后版本使用 INST ...

  10. 阿里云龙蜥8.6部署SQLSERVER2022的过程

    阿里云龙蜥8.6部署SQLSERVER2022的过程 背景 之前总结过, 但是发现当时是preview版本. 这里想升级一下, 并且顺便抄一下他的部分说明 下载 wget https://packag ...