作者:IQBB_LongGang | 来源:互联网 | 2023-10-12 10:10
1.前言
一个行者问老道长:“您得道前,做什么?”老道长:“砍柴担水做饭。”行者问:“那得道后呢?”老道长:“砍柴担水做饭。”行者又问:“那何谓得道?”老道长:“得道前,砍柴时惦记着挑水,挑水时惦记着做饭;得道后,砍柴即砍柴,担水即担水,做饭即做饭。”
是不是茅塞顿开?生活中许多至高至深的道理往往都是含蕴在一些极其简单的思想中,正所谓大道至简。完美的常常是最简单的,简单就是聪明,简单是高级形式的复杂,简到极致,便是大智。厉害的人往往是把复杂的问题简单化,世上再大再难的事情,只要“一分为二”就可以分解成许多简单的事。
在软件设计五大原则中的单一原则,与大道至简思想不谋而合。单一原则的核心思想是让软件模式结构简单,每个模块只实现一个功能。
2.设计背景
在enuo v0.02版本中增加了task.c 和 interface.c两个文件:
1、task.c 中包含了与任务操作相关的函数,如任务创建函数task_create和系统启动调度函数enuo_schedule。
2、interface.c中包含了与处理器硬件相关的操作,增强了系统的移植性。
在enuo v0.02版本中增加了任务抽象对象,任务相关的数据全部封装到任务抽象对象中。与此同时,在enuo v0.02版本中增加了链表数据结构,任务链表的作用是将多个任务串联起来,能高效地实现任务检索和操作。
3.设计目标
目前enuo系统可以创建任务,创建的任务在就绪表中,系统定时器可以轮询调度就绪表中的任务。显然任务创建后除了处于运行状态,任务还需要支持停止。因此这个版本为enuo增加一个停止表。
任务有两种状态就绪状态和停止状态,处理器会轮流执行就绪状态下的任务,停止状态下的任务不会得到运行。
在enuo 系统中就绪表和停止表都需要进行链表操作,因此完善链表操作并将链表独立出一个C文件,遵守单一原则。
4.设计环境
硬件环境是使用STM32F401RE为核心的自制开发板,软件环境是使用的KEIL V5.2 开发工具。
5.设计过程
5.1链表操作
链表需要实现两个基本操作:
1、按照需顺序插入链表
2、从链表中移除一个节点
链表需要实现排序操作,因此在链表结构中增加一个排序值sort_value,新链表新数据结构如下:
struct list_node_def
{struct list_node_def * next; struct list_node_def * previous; void *owner; uint32_t sort_value;
};
链表的初始化,插入和删除函数如下:
void list_initialise( list_t * const list )
{list->sliding_pointer = &list->head ; list->head.next = &list->head; list->head.previous = &list->head;list->head.sort_value = MIN_VALUE;
}
uint8_t list_sort_insert( list_t * const list , list_node_t * const new_node)
{list_node_t *seek;const uint32_t current_value &#61; new_node->sort_value;uint16_t i &#61; 0;for( seek &#61; & list->head ; seek->next->sort_value <&#61; current_value; seek &#61; seek->next ) {if( (i&#43;&#43;) > 255 )return 0;}new_node->next &#61; seek->next;new_node->next->previous &#61; new_node;new_node->previous &#61; seek;seek->next &#61; new_node;return 1;
}
void list_remove( list_node_t * const remove_node )
{remove_node->next->previous &#61; remove_node->previous;remove_node->previous->next &#61; remove_node->next;
}
list_sort_insert插入是参考sort_value数值进行升序排列。
列表中有一个滑动指针sliding_pointer&#xff0c;滑动指针指向当前节点&#xff0c;我们构造一个操作&#xff1a;插入链表时&#xff0c;将数据插入滑动指针的末尾。这种操作可以简化轮询任务时将新任务加入到链表尾部&#xff0c;代码如下&#xff1a;
void list_insert_sliding_pointer_end( list_t * const list , list_node_t * const new_node )
{list_node_t * const seek &#61; list->sliding_pointer;new_node->next &#61; seek;new_node->previous &#61; seek->previous;seek->previous->next &#61; new_node;seek->previous &#61; new_node;}
使用list_insert_sliding_pointer_end函数插入一个节点后的链表关系图如下&#xff1a;
使用list_insert_sliding_pointer_end函数再插入节点后的链表关系图如下&#xff1a;
使用list_remove函数移除一个节点后的链表关系图如下&#xff1a;
5.2任务操作
任务有以下三个个基本操作&#xff1a;
1、创建一个任务
2、停止一个指定任务
3、恢复一个指定任务
为了实现停止任务&#xff0c;需要创建一个延时等待列表&#xff0c;需要停止的任务放在延时等待表中。到目前位置enuo将拥有两个列表&#xff1a;
就绪表存放就绪状态的任务&#xff0c;延时等待表存放需要停止的任务&#xff0c;&#xff0c;处理器会轮流执行就绪表中的任务&#xff0c;延时等待表中的任务不会得到运行。
任务的操作函数如下&#xff1a;
void task_create( task_tcb_t *task , task_function_t function , uint32_t *stack_space , uint32_t stack_number )
{list_node_t * const new_node &#61; &task->link; task->link.owner &#61; task; list_insert_sliding_pointer_end( &ready_list , new_node); task_stack_init( (uint32_t *)task, function , stack_space , stack_number );
}
void task_stop( task_tcb_t *task )
{list_node_t * const new_node &#61; &task->link;list_remove(new_node); list_insert_sliding_pointer_end( &delay_list , new_node);
}
void task_resume( task_tcb_t *task )
{list_node_t * const new_node &#61; &task->link;list_remove(new_node); list_insert_sliding_pointer_end( &ready_list , new_node);
}
使用task_create函数创建task0任务后的就绪表和延时表的关系图如下&#xff1a;
使用task_create函数创建task1任务后的就绪表和延时表的关系图如下&#xff1a;
使用task_stop函数停止task0任务&#xff0c;任务停止后的就绪表和延时表的关系图如下&#xff1a;
使用task_resume函数恢复task0任务&#xff0c;任务恢复后的就绪表和延时表的关系图如下&#xff1a;
5.3任务调度
任务调度使用轮询的方法&#xff0c;再链表中读取下一个任务&#xff0c;并使用滑动指针sliding_pointer指向下一个节点。任务调度函数如下&#xff1a;
void SysTick_Handler(void)
{ list_t * const const_list &#61; &ready_list ; ( const_list )->sliding_pointer &#61; ( const_list )->sliding_pointer->next;if( const_list ->sliding_pointer &#61;&#61; & const_list ->head ) { const_list->sliding_pointer &#61; const_list->sliding_pointer->next; }next_task &#61; const_list->sliding_pointer->owner;TSAK_SWITCH_REQUEST;return;
}
任务切换使用TSAK_SWITCH_REQUEST宏&#xff0c;宏定义使用linux宏常用的do-while形式&#xff0c;TSAK_SWITCH_REQUEST宏如下&#xff1a;
5.4功能测试
enuo系统增加了任务停止和任务恢复功能&#xff0c;在task0中设计一个任务控制逻辑&#xff0c;周期性停止和恢复task1和task2两个任务。task0任务设计如下&#xff1a;
void task0(void)
{static uint16_t clk &#61; 0;static uint16_t state &#61; 0;while(1){if( ( ( clk&#43;&#43; )%9999 ) &#61;&#61; 0 ) {task_debug_num0&#43;&#43;; test_function();switch( state ){case 0:task_stop( &my_task1 );task_stop( &my_task2 );break;case 50:task_resume( &my_task1 );break;case 75:task_resume( &my_task2 );break; default:break; }if( state&#43;&#43; > 100 )state &#61; 0; }}
}
使用state设计一个状态机&#xff1a;
当state为0时停止task1和task2&#xff1b;
当state为50时恢复task1&#xff1b;
当state为75时恢复task2 。
如果功能正常运行结果为task0&#xff0c;task1和task2运行次数监控task_debug_num的数值之比约为4&#xff1a;2&#xff1a;1 。
6.运行结果
代码仿真运行后的结果如下&#xff1a;
task0&#xff0c;task1和task2运行task_debug_num的数值之比约为4&#xff1a;2&#xff1a;1
证明enuo系统的停止任务和恢复任务的功能正常。
希望获取源码的朋友们在评论区里留言。
未完待续…
实时操作系统系列将持续更新
创作不易希望朋友们点赞&#xff0c;转发&#xff0c;评论&#xff0c;关注。
您的点赞&#xff0c;转发&#xff0c;评论&#xff0c;关注将是我持续更新的动力
作者&#xff1a;李巍
Github&#xff1a;liyinuoman2017
CSDN&#xff1a;liyinuo2017
今日头条&#xff1a;程序猿李巍