热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

从零开始构建嵌入式实时操作系统3——任务状态切换

1.前言一个行者问老道长:“您得道前,做什么?”老道长:“砍柴担水做饭。”行者问:“那得道后呢?

在这里插入图片描述


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; /* 链表排序数值*/
};

链表的初始化,插入和删除函数如下:

/*********************************************************************************************************
* @名称 : list_initialise
* @描述 : 列表初始化
**********************************************************************************************************/

void list_initialise( list_t * const list )
{/* 滑动指针指向表头 */list->sliding_pointer = &list->head ; /* 表头的前后指针设置为NULL */list->head.next = &list->head; list->head.previous = &list->head;/* 表头的排序值为0 */list->head.sort_value = MIN_VALUE;
}
/*********************************************************************************************************
* @名称 : list_sort_insert
* @描述 : 按照sort_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;/* 按照 sort_value值 找到对应位置 */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;
}
/*********************************************************************************************************
* &#64;名称 : list_remove
* &#64;描述 : 列表删除一个节点
**********************************************************************************************************/

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;

/*********************************************************************************************************
* &#64;名称 : list_insert_sliding_pointer_end
* &#64;描述 : 节点插入滑动指针指向的后
**********************************************************************************************************/

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;

/*********************************************************************************************************
* &#64;名称 : task_create
* &#64;描述 : 创建任务
**********************************************************************************************************/

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 );
}
/*********************************************************************************************************
* &#64;名称 : task_stop
* &#64;描述 : 暂停任务
**********************************************************************************************************/

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);
}
/*********************************************************************************************************
* &#64;名称 : task_resume
* &#64;描述 : 恢复任务
**********************************************************************************************************/

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;

/*********************************************************************************************************
* &#64;名称 : SysTick_Handler
* &#64;描述 : 系统中断服务程序
**********************************************************************************************************/

void SysTick_Handler(void)
{ list_t * const const_list &#61; &ready_list ; /* sliding_pointer 实现循环滑动指向列表中的下一个节点 */ ( 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; /* 若列表为ListEnd 则指向下一个 就是第一个 实现循环*/ }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();/* 控制任务1和任务2的运行状态*/switch( state ){case 0:/* 暂停任务1 任务2 */task_stop( &my_task1 );task_stop( &my_task2 );break;case 50:/* 恢复任务1 */task_resume( &my_task1 );break;case 75:/* 恢复任务2 */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;程序猿李巍



推荐阅读
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • 本文讨论了在数据库打开和关闭状态下,重新命名或移动数据文件和日志文件的情况。针对性能和维护原因,需要将数据库文件移动到不同的磁盘上或重新分配到新的磁盘上的情况,以及在操作系统级别移动或重命名数据文件但未在数据库层进行重命名导致报错的情况。通过三个方面进行讨论。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
author-avatar
IQBB_LongGang
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有