当前位置: 首页 > news >正文

RT_Thread——线程管理(下)

文章目录

  • 一、Delay 函数
    • 1.1 两个 Delay 函数
    • 1.2 Delay 函数使用示例
  • 二、空闲线程及其钩子函数
    • 2.1 介绍
    • 2.2 代码解析
    • 2.3 使用空闲线程钩子函数
  • 三、调度算法
    • 3.1 重要概念
    • 3.2 调度算法
    • 3.3 调度器钩子函数

一、Delay 函数

1.1 两个 Delay 函数

有 3 个 Delay 函数:

  • rt_thread_delay():以系统时钟节拍为单位,当前线程会阻塞让出 CPU 资源
  • rt_thread_mdelay():以 ms 为单位,当前线程会阻塞让出 CPU 资源
  • rt_hw_us_delay():以 us 为单位,当前线程不会阻塞,这个函数是"死等“指定时间

这 3 个函数原型如下:

rt_err_t rt_thread_delay(rt_tick_t tick); 
rt_err_t rt_thread_mdelay(rt_int32_t ms);
void rt_hw_us_delay(rt_uint32_t us);

系统时钟节拍是 RT-Thread 定时器的最小精度
1 OS Tick = 1/RT_TICK_PER_SECOND 秒RT_TICK_PER_SECOND 值在 rtconfig.h 文件中定义

rt_thread_mdelay 也是只能是时钟节拍的整数倍,使用 rt_thread_mdelay 可移植性更

在实际应用中,可以通过前 2 个延时函数挂起当前线程,让出 CPU。

1.2 Delay 函数使用示例

本节代码为:RT-Thread_06_taskdelay。

本程序会创建 2 个线程:

  • Task1:
    • 高优先级
    • 设置变量 flag 为 1,然后调用 rt_thread_mdelay();rt_hw_us_delay();
    • rt_thread_mdelay() 会让出 CPU
    • rt_hw_us_delay() 不会让出 CPU
  • Task2:
    • 低优先级
    • 设置变量 flag 为 0

程序一些开头的定义:

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>#define THREAD_PRIORITY         15  //设置线程优先级
#define THREAD_STACK_SIZE       512 //设置线程栈大小
#define THREAD_TIMESLICE        15  //设置线程时间片大小static struct rt_thread *thread1;   //定义线程1句柄指针
static struct rt_thread *thread2;   //定义线程2句柄指针static volatile int flag = 0;

main 函数代码如下:

int main(void)
{/* 创建动态线程thread1,优先级为 THREAD_PRIORIT-1 = 14 */thread1 = rt_thread_create("thread1",          //线程名字thread1_entry,         //入口函数(void *)thread1_name,  //入口函数参数THREAD_STACK_SIZE,     //栈大小THREAD_PRIORITY-1,     //线程优先级THREAD_TIMESLICE);     //线程时间片大小/* 判断创建结果,再启动线程1 */if (thread1 != RT_NULL)rt_thread_startup(thread1);				   /* 创建动态线程thread2,优先级为 THREAD_PRIORIT = 15 */thread2 = rt_thread_create("thread2",          //线程名字thread2_entry,         //入口函数(void *)thread2_name,  //入口函数参数THREAD_STACK_SIZE,     //栈大小THREAD_PRIORITY,       //线程优先级THREAD_TIMESLICE);     //线程时间片大小/* 判断创建结果,再启动线程2 */if (thread2 != RT_NULL)rt_thread_startup(thread2);		return 0;
}

Thread1 的代码中使用条件开关来选择延时函数,把 #if 1 改为 #if 0 就可以使用
rt_hw_us_delay,代码如下:

/* 线程1的入口函数 */
static void thread1_entry(void *parameter)
{const char *thread_name = parameter;/* 线程1 */while(1){/* 打印线程的信息 */rt_kprintf(thread_name);flag = 1;#if 1		rt_thread_mdelay(50);
#else		rt_hw_us_delay(50000);
#endif	}
}

Thread2 的代码如下:

/* 线程2的入口函数 */
static void thread2_entry(void *parameter)
{const char *thread_name = parameter;/* 线程1 */while(1){/* 打印线程的信息 */rt_kprintf(thread_name);flag = 0;}
}

使用 Keil 的逻辑分析观察 flag 变量的 bit 波形,如下:

  • 使用 rt_thread_mdelay() 时,才会出现电平变化
    在这里插入图片描述
  • 使用 rt_hw_us_delay() 时,线程 1 会一直占用 CPU,线程 2 无法执行,不会出现电平变化
    在这里插入图片描述

二、空闲线程及其钩子函数

2.1 介绍

在 RT-Thread_03_delete_task 的实验里,我们提到空闲线程(Idle 线程)的作用:释放被删除的线程的内存。

除了上述目的之外,为什么必须要有空闲线程?

一个良好的程序,它的线程都是事件驱动的:平时大部分时间处于挂起状态。

有可能我们自己创建的所有线程都无法执行,但是调度器必须能找到一个可以运行的线程:所以,要提供空闲线程。

  • 空闲线程优先级最低:它不能阻碍用户线程运行
  • 空闲线程要么处于就绪态,要么处于运行态,永远不会挂起

空闲线程的优先级为最低,这意味着一旦某个用户的线程变为就绪态,那么空闲线程马上被切换出去,让这个用户线程运行。

在这种情况下,我们说用户线程"抢占"(pre-empt)了空闲线程,这是由调度器实现的。

要注意的是:如果某个线程被删除,那么需要确保后面空闲线程有机会执行,否则就
无法释放被删除线程的内存。

我们可以添加一个空闲线程的钩子函数,空闲线程的循环每执行一次,就会调用一次钩子函数。钩子函数的作用有这些:

  • 执行一些低优先级的、后台的、需要连续执行的函数
  • 测量系统的空闲时间:空闲线程能被执行就意味着所有的高优先级线程都停止了,所以测量空闲线程占据的时间,就可以算出处理器占用率
  • 让系统进入省电模式:空闲线程能被执行就意味着没有重要的事情要做,当然可以进入省电模式了。

2.2 代码解析

空闲线程的代码在文件 src\idle.c 中。

创建空闲线程的代码如下:

void rt_thread_idle_init(void)
{/* initialize thread */rt_thread_init(&idle, "tidle",rt_thread_idle_entry, RT_NULL,&rt_thread_stack[0], sizeof(rt_thread_stack), RT_THREAD_PRIORITY_MAX - 1,32);/* startup */rt_thread_startup(&idle);
}

空闲任务的入口函数如下:

  • 用户可以提供多个钩子函数:idle_hook_list[i],如果提供了就会被调用
  • 清理工作:rt_thread_idle_excute
static void rt_thread_idle_entry(void *parameter)
{while (1){#ifdef RT_USING_IDLE_HOOKrt_size_t i;for (i = 0; i < RT_IDLE_HOOK_LIST_SIZE; i++){if (idle_hook_list[i] != RT_NULL){idle_hook_list[i]();}}
#endifrt_thread_idle_excute();
#ifdef RT_USING_PMrt_system_power_manager();
#endif}
}

空闲线程的主要工作是调用 rt_thread_idle_excute() 来清理线程:

  • 那些使用的 rt_thread_create() 函数创建的线程,退出时都被放在 rt_thread_defunct 链表里
  • 从链表里把它们逐个取出来
  • 释放栈
  • 释放线程控制块

代码如下:
在这里插入图片描述

2.3 使用空闲线程钩子函数

在 src/idle.c 中,可以看到如下代码:

在这里插入图片描述

所以使用空闲线程钩子函数的前提是:

  • rt-config.h 里定义宏 RT_USING_IDLE_HOOK
  • 使用 rt_thread_idle_sethook 函数,设置钩子函数

在这里插入图片描述

要删除钩子函数,使用 rt_thread_idle_dehook 函数。

在这里插入图片描述

三、调度算法

3.1 重要概念

这些知识在前面都提到过了,这里总结一下。

正在运行的线程,被称为"正在使用处理器",它处于运行状态。在单处理器系统中,任何时间里只能有一个线程处于运行状态。

非运行状态的线程,它处于这 4 中状态之一:初始、就绪、挂起、关闭。

就绪态的线程,可以被调度器挑选出来切换为运行状态,调度器永远都是挑选最高优先级的就绪态线程并让它进入运行状态

挂起状态的线程,它在等待"事件",当事件发生时线程就会进入就绪状态。

事件分为两类:时间相关的事件、同步事件。

所谓时间相关的事件,就是设置超时时间:在指定时间内阻塞,时间到了就进入就绪状态。使用时间相关的事件,可以实现周期性的功能、可以实现超时功能。

同步事件就是:某个线程在等待某些信息,别的线程或者中断服务程序会给它发送信息。

怎么"发送信息"?方法很多,有:信号量(semaphores)、互斥量(mutexe)、事件集 (event)等。

这些方法用来发送同步信息,比如表示某个外设得到了数据。

3.2 调度算法

RT-Thread 的调度算法为基于优先级调度基于时间片轮转调度共存的策略。

RT-Thread 内核中存在多个线程优先级,同一个线程优先级下可以有多个线程。

优先级数目在 rtconfig.h 中以宏定义的方式配置:

#define RT_THREAD_PRIORITY_MAX 32

系统中可能有多个线程,它们的优先级可能相同、可能不同,RT-Thread 采用的调度策略是:

  • 可抢占:高优先级的就绪线程会“立刻”抢占低优先级的线程;
  • 时间片轮转:同优先级别的就绪线程,依次运行

3.3 调度器钩子函数

在整个系统运行过程中,会不断地切换线程:这由 rt_schedule()来实现。

我们还可以提供一个调度器的钩子函数,在 rt_schedule() 切换线程时它会调用该钩子函数:

  • 用户实现钩子函数,原型为 void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to)
  • 使用 rt_scheduler_sethook 设置钩子函数

rt_schedule() 代码如下:

在这里插入图片描述

本节代码为:RT-Thread_07_scheduler,main 函数代码如下:

int main(void)
{/* 设置调度器钩子 */rt_scheduler_sethook(hook_of_scheduler);/* 创建动态线程thread1,优先级为 THREAD_PRIORIT-1 = 14 */thread1 = rt_thread_create("thread1",          //线程名字thread1_entry,         //入口函数(void *)thread1_name,  //入口函数参数THREAD_STACK_SIZE,     //栈大小THREAD_PRIORITY-1,     //线程优先级THREAD_TIMESLICE);     //线程时间片大小/* 判断创建结果,再启动线程1 */if (thread1 != RT_NULL)rt_thread_startup(thread1);				   /* 创建动态线程thread2,优先级为 THREAD_PRIORIT = 15 */thread2 = rt_thread_create("thread2",          //线程名字thread2_entry,         //入口函数(void *)thread2_name,  //入口函数参数THREAD_STACK_SIZE,     //栈大小THREAD_PRIORITY,       //线程优先级THREAD_TIMESLICE);     //线程时间片大小/* 判断创建结果,再启动线程2 */if (thread2 != RT_NULL)rt_thread_startup(thread2);		return 0;
}

钩子函数内容如下:

static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to)
{rt_kprintf("Thread:%s -->  Thread:%s \r\n", from->name, to->name);
}

线程 1、线程 2 代码如下:

/* 线程1的入口函数 */
static void thread1_entry(void *parameter)
{const char *thread_name = parameter;/* 线程1 */while(1){/* 打印线程的信息 */rt_kprintf(thread_name);rt_thread_mdelay(10);}
}/* 线程2的入口函数 */
static void thread2_entry(void *parameter)
{const char *thread_name = parameter;/* 线程1 */while(1){/* 打印线程的信息 */rt_kprintf(thread_name);}
}

程序运行时:

  • 优先级高的线程 1 会先运行
  • 线程 1 调用 rt_thread_mdelay() 后挂起,将切换到线程 2
  • 切换时,钩子函数被调用,它打印调度信息

在这里插入图片描述

相关文章:

  • 高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
  • window 显示驱动开发-如何查询视频处理功能(三)
  • 从零手写Java版本的LSM Tree (八):LSM Tree 主程序实现
  • 华为云Flexus+DeepSeek征文 | MaaS平台避坑指南:DeepSeek商用服务开通与成本控制
  • HTML5实现简洁的体育赛事网站源码
  • Nosql之Redis集群
  • 多元隐函数 偏导公式
  • 【微服务基石篇】服务间的对话:RestTemplate、WebClient与OpenFeign对比与实战
  • 我的世界Java版1.21.4的Fabric模组开发教程(十二)方块状态
  • VRRP(虚拟路由冗余协议)深度解析
  • API网关Envoy的鉴权与限流:构建安全可靠的微服务网关
  • 算法思想之广度优先搜索(BFS)及示例(亲子游戏)
  • yolo模型精度提升策略
  • OpenHarmony标准系统-HDF框架之I2C驱动开发
  • Gemini 2.5 Pro (0605版本) 深度测评与体验指南
  • 如何将联系人从 iPhone 转移到 Android
  • 初探 OpenCV for Android:利用官方示例开启视觉之旅
  • 计算机技术、互联网与 IT 前沿:量子计算、Web3.0 等趋势洞察及行业应用
  • 生成对抗网络(GAN)损失函数解读
  • 【C++】红黑树的实现详解
  • 沙井网站建设/网络建站工作室
  • 泉州网站设计公司/外贸建站公司
  • 现在还有网站做校内网吗/seo优化网站的注意事项
  • 企业如何选择网站/百度的官方网站
  • 外贸网站建设价格怎么样/什么是网络营销平台
  • 邮箱wordpress/临沂seo建站