点击上方蓝色字体了解更多的嵌入式编程实用技能。
如果你觉得该文章对你有帮助,欢迎点赞+关注
在计算机科学领域,任务调度和协作是关键的概念。虽然传统的操作系统提供了各种任务调度算法和机制,但有时我们需要更灵活、个性化的任务管理方式。
即使采用定时器实现时间片论法任务调度,但是也必须等单个完整的任务执行完成后才能执行下一个完整的任务。
本文将介绍使用标准库头文件中的setjmp
和longjmp
函数构建一个简单的查询式协作多任务系统,无需使用定时器进行任务切换。
setjmp
和longjmp
是C语言标准库头文件中提供的函数。它们的功能是实现非局部跳转,可以在程序的不同位置之间进行跳转,类似于
goto
语句的扩展。这种非局部跳转的能力为我们构建查询式协作多任务系统提供了基础。
setjmp
函数用于保存当前程序状态,创建一个可以供后续longjmp
函数跳转的上下文环境。在调用setjmp
时,程序会记录当前的程序计数器、寄存器和堆栈等状态信息,并将这些信息保存在一个jmp_buf
结构中。同时,setjmp
函数返回0作为普通调用的返回值,并将jmp_buf
作为标识符存储起来。
不同平台的
jmp_buf
的类型定义不一样,大概占用不到30个字节,因为不同平台的相关寄存器等不一样,因此占用的大小也不同。
longjmp
函数则实现了对保存的上下文环境的跳转操作。通过传递之前由setjmp
函数保存的jmp_buf
标识符,longjmp函数会将程序的状态还原到对应的上下文环境,并且会返回到setjmp
处继续执行。
在协作式多任务调度下,当前任务需要通过主动放弃时间片提供给其他任务运行,而并非是被其他任务抢占,因此这里面并没有所谓的优先级概念之分。
虽然协作式没有所谓的优先级概念之分,但是可以通过一定的方式也能实现一个简单的优先级,比如当前任务主动放弃时间片后,查询更高优先级的任务运行。
时间片论法任务调度只能等任务运行完成才会给下一个任务时间片运行,并不存在主动放弃时间片的功能。
了解到setjmp
和longjmp
的功能和原理后,我们能不能通过它们来构建一个任务调度算法和机制呢?
虽然setjmp
可以记录当前的程序计数器、寄存器和堆栈等状态信息,但是实现多任务切换时堆栈里面的数据是会发生变化的。
jmp_buf
只记录堆栈指针,不记录堆栈指针指向的数据内容。
因此,如果要实现多任务切换,则需要为每个任务分配一定的堆栈预留空间,由于不使用堆,因此可以只考虑栈分配即可。
当前任务主动放弃时间片后,不断查询满足条件需要执行的其他任务。
创建任务,使用了setjmp
函数。
int cotOs_Creat(OsTask_cb pfnOsTaskEnter, size_t stack)
{
size_t oldsp;
if (sg_OsInfo.taskNum >= COT_OS_MAX_TASK || sg_OsInfo.pfnGetTimerMs == NULL)
{
return -1;
}
COT_OS_GET_STACK(oldsp);
COT_OS_SET_STACK(sg_OsInfo.stackTop);
if (0 == setjmp(sg_OsInfo.tcb[sg_OsInfo.taskNum].env))
{
COT_OS_SET_STACK(oldsp);
sg_OsInfo.tcb[sg_OsInfo.taskNum].pfnOsTaskEnter = pfnOsTaskEnter;
sg_OsInfo.taskNum++;
sg_OsInfo.stackTop -= stack;
}
else
{
sg_OsInfo.tcb[sg_OsInfo.taskId].pfnOsTaskEnter();
}
return 0;
}
放弃时间片,使用了longjmp
,这里集成了时间等待功能,即放弃时间片的时长(即使时长减至0也要等待其他任务主动放弃时间片才会运行)
void cotOs_WaitFor(uint32_t time)
{
uint32_t timer = sg_OsInfo.pfnGetTimerMs();
setjmp(sg_OsInfo.tcb[sg_OsInfo.taskId].env);
if (!(sg_OsInfo.pfnGetTimerMs() - timer > time))
{
sg_OsInfo.taskId++;
if (sg_OsInfo.taskId >= sg_OsInfo.taskNum)
{
sg_OsInfo.taskId = 0;
}
longjmp(sg_OsInfo.tcb[sg_OsInfo.taskId].env, 1);
}
}
除了继承时间等待功能外,还定义了一个等待条件的主动放弃放弃时间片功能,即条件不满足时主动放弃时间片(即使条件满足后也要等待其他任务主动放弃时间片才会运行)
#define cotOs_WaitFor_Cond(cond) do{\
extern jmp_buf *cotOs_GetTaskEnv1(void);\
setjmp((*cotOs_GetTaskEnv1()));\
if (!(cond)){\
extern void cotOs_RunNextTask(void);\
cotOs_RunNextTask();\
}\
}while (0)
目前已完成在STM32板子上的查询式协作多任务系统:
下载链接(点击阅读原文):
https://gitee.com/cot_package/cot_os
setjmp
和longjmp
除了实现一个多任务系统外,其实还可以有其他的用法,比如实现C++中的try catch
抛出异常处理功能。
由于其特性问题,在实际代码中,特别是应用程序代码上,建议少用
setjmp
和longjmp
,否则会影响代码阅读,当然,不排除封装成一个特殊的功能外