电子商务网站建设方案目录,如果创建网站,西安成品网站建设,成都爱站网seo站长查询工具在之前写的另外一篇文章——从0到1写RT-Thread内核——线程定义及切换的实现中线程体内的延时使用的是软件延时#xff0c;即还是让CPU空等来达到延时的效果。RTOS中的延时叫阻塞延时#xff0c;即线程需要延时的时候#xff0c;线程会放弃CPU的使用权#xff0c;C… 在之前写的另外一篇文章——从0到1写RT-Thread内核——线程定义及切换的实现中线程体内的延时使用的是软件延时即还是让CPU空等来达到延时的效果。RTOS中的延时叫阻塞延时即线程需要延时的时候线程会放弃CPU的使用权CPU可以去干其他的事情当线程延时时间到重新获取CPU使用权线程继续运行这样就充分利用了CPU的资源而不是干等着。 当某个线程需要延时进入阻塞状态如果没有其他线程可以运行RTOS都会为CPU创建一个空闲线程这个时候CPU就运行空闲线程。在RT-Thread中空闲线程是系统在初始化的时候创建的优先级最低的线程空闲线程主要是做一些系统内存的清理工作。但为了简单起见这里我们的空闲线程只对一个全局变量进行计数。在实际应用中当系统进入空闲线程的时候可在空闲线程中让单片机进入休眠或者低功耗等操作。
我们把空闲线程与阻塞延时的实现分为以下两大步
一.实现空闲线程
1.定义空闲线程的栈
#include rtthread.h
#include rthw.h
#define IDLE_THREAD_STACK_SIZE 512
ALIGN(RT_ALIGN_SIZE)
static rt_uint8_t rt_thread_stack[IDLE_THREAD_STACK_SIZE];
2.定义空闲线程控制块
struct rt_thread idle;
3.定义空闲线程函数
rt_ubase_t rt_idletask_ctr 0;void rt_thread_idle_entry(void *parameter)
{parameter parameter;while (1){rt_idletask_ctr ;/* 进行系统调度这个是我自己后来加的野火官方的例程是没有调用rt_schedule *我觉得那样是错误的因为这里不加rt_schedule的话程序执行到空闲线程就回不去了*///rt_schedule();//这里补充一下野火的例程并没有错每次产生滴答定时器中断都会调用调度器所以这里不需要调用调度器的}
}
4.空闲线程初始化
/*** ingroup SystemInit** 初始化空闲线程启动空闲线程** note 当系统初始化的时候该函数必须被调用*/
void rt_thread_idle_init(void)
{/* 初始化线程 */rt_thread_init(idle,idle,rt_thread_idle_entry,RT_NULL,rt_thread_stack[0],sizeof(rt_thread_stack));/* 将空闲线程插入到就绪列表中优先级最低的链表中 */rt_list_insert_before( (rt_thread_priority_table[RT_THREAD_PRIORITY_MAX-1]),(idle.tlist) );
}
以上4步在之前的文章——从0到1写RT-Thread内核——线程定义及切换的实现有详细介绍过了这里就不再过多解释。
二.实现阻塞延时 阻塞延时的阻塞是指线程调用该延时函数后线程会被剥离CPU使用权然后进入阻塞状态直到延时结束线程会重新获取CPU使用权才可继续运行在线程阻塞的这段时间CPU可以去执行其他的线程如果其他的线程也在延时状态那么CPU就将运行空闲线程。我们定义一个延时函数rt_thread_delay函数其代码清单如下图 上面的代码中我们通过thread-remaining_tick来判断某个线程的延时是否结束thread-remaining_tick是在SysTick_Handler中递减的其代码如下
void SysTick_Handler(void)
{/* 进入中断 */rt_interrupt_enter();rt_tick_increase();/* 离开中断 */rt_interrupt_leave();
}
/* * rt_interrupt_nest为中断计数器是一个全局变量用来记录中断嵌套次数。* 每进入一个中断函数就会加一* 每离开一个中断函数就会减一*/
volatile rt_uint8_t rt_interrupt_nest;/*** 当BSP文件的中断服务函数进入时会调用该函数* * note 请不要在应用程序中调用该函数** see rt_interrupt_leave*/
void rt_interrupt_enter(void)
{rt_base_t level;/* 关中断 */level rt_hw_interrupt_disable();/* 中断计数器 */rt_interrupt_nest ;/* 开中断 */rt_hw_interrupt_enable(level);
}/*** 当BSP文件的中断服务函数离开时会调用该函数** note 请不要在应用程序中调用该函数** see rt_interrupt_enter*/
void rt_interrupt_leave(void)
{rt_base_t level;/* 关中断 */level rt_hw_interrupt_disable();/* 中断计数器-- */rt_interrupt_nest --;/* 开中断 */rt_hw_interrupt_enable(level);
}
//rt_tick 为系统时基计数器是一个全局变量用来记录产生了多少次SysTick中断
static rt_tick_t rt_tick 0;
extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];void rt_tick_increase(void)
{rt_ubase_t i;struct rt_thread *thread;rt_tick ;/* 扫描就绪列表中所有线程的remaining_tick如果不为0则减1 */for(i0; iRT_THREAD_PRIORITY_MAX; i){thread rt_list_entry( rt_thread_priority_table[i].next,struct rt_thread,tlist);if(thread-remaining_tick 0){thread-remaining_tick --;}}/* 系统调度 */rt_schedule();
} 下面我们来看一下main函数和线程1和线程2的函数体在main函数中我们分别初始化了空闲线程、线程1和线程2并将它们插入到对应就绪列表的链表中去然后就开始了系统调度rt_system_scheduler_start先从线程1开始执行这里不理解的话可以看另外一篇博客从0到1写RT-Thread内核——线程定义及切换的实现如果某个因为线程调用了rt_thread_delay函数而被阻塞了的话就运行另外的线程此时线程阻塞剩余时长remaining_tick在SysTick_Handler中不断递减如果非空闲线程都被阻塞了才运行空闲线程如果某个线程的remaining_tick递减到为0了则又继续运行该线程。
/************************************************************************* brief main函数* param 无* retval 无** attention*********************************************************************** */
int main(void)
{ /* 硬件初始化 *//* 将硬件相关的初始化放在这里如果是软件仿真则没有相关初始化代码 *//* 关中断在程序开始的时候把中断关闭是一个好习惯等系统初始化完毕线程创建完毕启动系统 * 调度的时候会重新打开中断在rt_hw_context_switch_to函数中会再开启中断并设置中断标位。* 如果一开始不关闭中断接下来SysTick初始化完成然后再初始化系统和创建线程如果系统初始化* 和线程创建的时间大于SysTick中断周期的话那么就会出现系统或者线程还没准备好的情况下就先执* 行了SysTick中断服务函数在该函数中进行了系统调度显示这是不合理的。*/rt_hw_interrupt_disable();/* 初始化SysTick调用固件库函数SysTick_Config来实现* 配置中断周期为10ms为100中断优先级为最低*/SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );/* 调度器初始化 */rt_system_scheduler_init();/* 初始化空闲线程 */ rt_thread_idle_init(); /* 初始化线程1 */rt_thread_init( rt_flag1_thread, /* 线程控制块 */rt_flag1_thread, /* 线程名字字符串形式 */flag1_thread_entry, /* 线程入口地址 */RT_NULL, /* 线程形参 */rt_flag1_thread_stack[0], /* 线程栈起始地址 */sizeof(rt_flag1_thread_stack) ); /* 线程栈大小单位为字节 *//* 将线程插入到就绪列表 */rt_list_insert_before( (rt_thread_priority_table[0]),(rt_flag1_thread.tlist) );/* 初始化线程2 */rt_thread_init( rt_flag2_thread, /* 线程控制块 */rt_flag2_thread, /* 线程名字字符串形式 */flag2_thread_entry, /* 线程入口地址 */RT_NULL, /* 线程形参 */rt_flag2_thread_stack[0], /* 线程栈起始地址 */sizeof(rt_flag2_thread_stack) ); /* 线程栈大小单位为字节 *//* 将线程插入到就绪列表 */rt_list_insert_before( (rt_thread_priority_table[1]),(rt_flag2_thread.tlist) );/* 启动系统调度器 */rt_system_scheduler_start();
} /* 线程1 */
void flag1_thread_entry( void *p_arg )
{for( ;; ){
#if 0flag1 1;delay( 100 ); flag1 0;delay( 100 );/* 线程切换这里是手动切换 */ rt_schedule();
#elseflag1 1;rt_thread_delay(2); flag1 0;rt_thread_delay(2);
#endif }
}/* 线程2 */
void flag2_thread_entry( void *p_arg )
{for( ;; ){
#if 0flag2 1;delay( 100 ); flag2 0;delay( 100 );/* 线程切换这里是手动切换 */rt_schedule();
#elseflag2 1;rt_thread_delay(2); flag2 0;rt_thread_delay(2);
#endif }
}
程序运行结果如下其实这种阻塞延时的原理和我们在单片机裸机中采用的前后台轮询是非常相似的。 最后声明一下我这里只是对学习的知识点进行总结本文章的大多数知识来自于野火公司出版的《RT-Thread 内核实现与应用开发实战—基于STM32》这本书非常不错有志学习RT-Thread物联网操作系统的人可以考虑一下。