RT-Thread记录(三、RT-Thread 线程操作函数及线程管理与FreeRTOS的比较)_rtthread和freertos区别(1)

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

/\*
demo,用来接收动态线程返回的句柄
比如 led2\_thread = rt\_thread\_create(......);
\*/
static rt\_thread\_t led2_thread = RT_NULL; 

#ifdef RT\_USING\_HEAP //定义使用了HEAP才能动态创建线程
/\*
参数的含义,放在上面看起来更加方便,要不然太长了
1、线程的名称;线程名称的最大长度由 rtconfig.h 中的宏 RT\_NAME\_MAX 指定,多余部分会被自动截掉
2、线程入口函数
3、线程入口函数参数,没有就用 RT\_NULL
4、线程栈大小,单位是字节
5、线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT\_THREAD\_PRIORITY\_MAX 宏定义),
如果支持的是 256 级优先级,那么范围是从 0~255,数值越小优先级越高,0 代表最高优先级
6、线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。
当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。
这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行
返回值:
线程创建成功,返回线程句柄
线程创建失败,返回RT\_BULL 
\*/
rt\_thread\_t rt\_thread\_create(const char \*name, 
                             void (\*entry)(void \*parameter),
                             void       \*parameter,
                             rt\_uint32\_t stack_size,
                             rt\_uint8\_t  priority,
                             rt\_uint32\_t tick)

1.2 静态创建线程

static struct rt\_thread led1_thread;   //demo,用户定义的线程句柄
static char led1_thread_stack[256];    //demo,用户定义的静态线程大小
/\*
参数的含义
1、线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址,上面的led1\_thread。
2、线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT\_NAME\_MAX 宏指定,多余部分会被自动截掉
3、线程入口函数
4、线程入口函数参数,没有就用 RT\_NULL
5、线程栈起始地址,根据上面定义就是 &led1\_thread\_stack[0],
6、线程栈大小,单位是字节。根据上面定义就是 sizeof(led1\_thread\_stack),
在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐)
7、线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT\_THREAD\_PRIORITY\_MAX 宏定义),
如果支持的是 256 级优先级,那么范围是从 0~255,数值越小优先级越高,0 代表最高优先级
8、线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。
当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。
这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行
返回值:
线程创建成功,返回RT\_EOK
线程创建失败,返回RT\_ERROR 
\*/
rt\_err\_t rt\_thread\_init(struct rt\_thread\* thread,
                        const char\* name,
                        void (\*entry)(void\* parameter), void\* parameter,
                        void\* stack_start, 
                        rt\_uint32\_t stack_size,
                        rt\_uint8\_t priority, 
                        rt\_uint32\_t tick);

这里需要说明一下,为什么用户定义一个 char 类型的数组可以作为线程栈空间呢?

因为申请一个全局变量的数组,本质就是开辟了一段连续的内存空间!这是用户申请的,所以在编译的时候就被确定分配好了,这段内存空间申请出来,通过rt_thread_init函数,就分配给了这个线程使用。
如果知道了上面的话,但是还不能理解内存空间和线程有什么关系的时候,这个就得慢慢来……简单来说就是,线程运行需要占用一段内存空间,这段内存空间每个线程的都不一样,他们是用来线程运行的时候,函数调用线程切换保存现场用的。
反正先记住必须给每个线程单独的一片内存空间,RTOS才能正常运行,所有的RTOS都是。
动态创建同样的意思,只不过你看不到,由内核函数自动处理了就没那么直观。

在上面示例代码中,256个char类型的数组,就是占用256个字节(char类型占用1个字节),所以最后分配给线程的空间就是256个字节。

1.3 启动线程

创建完线程并不代表线程就运行了,在RT-Thread称为初始状态,要跑起来需要人为的给他“开”一下,这里与FreeRTOS创建任务后是不同的,FreeRTOS是直接创建完成就开始运行参与调度了。

创建的线程状态处于初始状态,并未进入就绪线程的调度队列,我们可以在线程创建成功后调用rt_thread_startup函数接口让该线程进入就绪态:

/\*
static rt\_thread\_t led2\_thread = RT\_NULL;
static struct rt\_thread led1\_thread;
上面的两个demo就是:
rt\_thread\_startup(&led1\_thread);
rt\_thread\_startup(led2\_thread);
\*/
rt\_err\_t rt\_thread\_startup(rt\_thread\_t thread);

这里又有一个小细节需要说明一下,动态和静态创建线程的rt_thread_startup使用的小区别!

上面代码的注释中,两个Demo:
一个是rt_thread_startup(&led1_thread);(静态)
一个是rt_thread_startup(led2_thread);(动态)

静态线程为什么需要取地址,动态可以直接用,不仔细看的话还不一定发现这个问题, 其实从他们的定义就已经不同了,只不过rt_thread_trt_thread 一眼看去还真可能傻傻分不清楚 = =!以前我刚用的时候也在这里迷糊了一会:
static struct rt_thread led1_thread 静态类型为struct rt_thread 类型就是线程控制块结构体
static rt_thread_t led2_thread 动态类型为rt_thread_t 类型是一个指针,如下解释:

rt_thread_t这个类型他是经过 typedef 重名命的:
在这里插入图片描述
所以回到开始的问题,搞清楚了rt_thread_startup 函数的参数是线程控制块结构体指针, 再结合动态静态创建线程的线程句柄定义,这么问题就清楚了!明白了这个,那么这里又可以说明一个细节问题!如下

线程创建的一个细节—创建和初始化?

在文中,我介绍API使用的标题是“动态创建线程” 和“静态创建线程”,个人认为看上去好理解,也没问题,但是这里注意官方的用语:

动态是 – 创建和删除线程
静态是 – 初始化和脱离线程

说白了都是新建线程,但是用词却不一样,为什么动态用创建,而静态用初始化呢?带着疑问我们回头再去看看两种方式的不同。
在使用rt_thread_init之前,我们需要定义两个东西,一个结构体,一个数组:

static struct rt\_thread led1_thread;   //demo,用户定义的线程句柄
static char led1_thread_stack[256];    //demo,用户定义的静态线程大小

在编译的时候,这个结构体和数组,就被分配了一定的内存空间,这段空间默认一般是初始化为0,就是空间给你留着了,但是等着你去放数据。不管在程序后面使不使用rt_thread_init,这段空间都已经存在了的! 这样来说,调用rt_thread_init只是对已经存在的一段内存空间的赋值,对一个存在的东西的设置,不就是叫做 初始化吗。所以使用静态的创建严格的来说,更应该称之为初始化线程!

而在使用rt_thread_create之前,我们只需要定义一个rt_thread_t 类型的指针,初始化是NULL就没有了,只有在调用rt_thread_create成功之后,才会开辟出一块存放线程控制块的内存空间,从无到有的一个过程,所以叫做 创建。

不得不佩服,官方还是用词严谨,其实想想也能更好的理解函数功能!

句柄是什么?

讲到这里,为了让有些小伙伴更容易看懂,我们再插一个细节,我们经常听到返回句柄,函数句柄,任务句柄,那么句柄是什么?
记住一句话:句柄其实就是指针,它是指向指针的指针。

在我们的rt_thread_create函数中,如果成功返回值是 线程句柄,类型为rt_thread_t ,我们前面又讲过rt_thread_t 是一个结构体指针,这个结构体是线程控制块结构体,所以 在上面示例代码中返回句柄的意思 ,就是返回了一个指针,这个指针指向线程控制块。

(如果指针,指向指针的指针不明白,这是C语言基础知识,可以查看相关资料,我有一篇博文也提到过一二:C语言学习点滴笔记 中 4、指针: 一种特殊的变量 和 多元指针,指向指针的指针)

1.4 删除线程和脱离线程

针对上面动态静态方法创建的线程,RT-Thread 有不同的删除函数:
对于使用rt_thread_create动态创建的线程,我们使用rt_thread_delete函数,如下:

/\*
参数:thread 要删除的线程句柄
返回值:
RT\_EOK 删除线程成功
-RT\_ERROR 删除线程失败
\*/
rt\_err\_t rt\_thread\_delete(rt\_thread\_t thread);

调用该函数后,线程对象将会被移出线程队列并且从内核对象管理器中删除,线程占用的堆栈空间也会被释放。实际上,用 rt_thread_delete() 函数删除线程接口,仅仅是把相应的线程状态更改为 RT_THREAD_CLOSE 状态,然后放入到 rt_thread_defunct 队列中;而真正的删除动作(释放线程控制块和释放线程栈)需要到下一次执行空闲线程时,由空闲线程完成最后的线程删除动作。

对于使用rt_thread_init 静态创建的线程,我们使用rt_thread_detach 函数,如下:

/\*
参数:线程句柄,它应该是由 rt\_thread\_init 进行初始化的线程句柄。
返回值:
RT\_EOK 线程脱离成功
-RT\_ERROR 线程脱离失败
\*/
rt\_err\_t rt\_thread\_detach (rt\_thread\_t thread);

官方在介绍rt_thread_detach有一句话,同样,线程本身不应调用这个接口脱离线程本身。这句话我理解就是不管动态删除还是静态删除,不能在线程函数中自己把自己删除。
这里也与FreeRTOS任务后不同,FreeRTOS可以直接在任务中调用函数删除自己。

但是需要特别说明的是,在 RT-Thread 中执行完毕的线程系统会自动将其删除!用户无需多余操作,如何理解呢,看下面的例子:

我们一般线程函数都是死循环,通过延时释放CPU控制权,比如:

static void led1\_thread\_entry(void \*par){
    while(1){   		
        //do\_something
        rt\_thread\_mdelay(100);
    }
}

我们需要删除的线程往往只是为了做某一件事,某一次特殊的事情,比如:

static void this\_is\_a\_need\_delete\_task(void \*par){ 	
  			//do\_one\_time\_thing
}

其实这个线程是为了某一件特殊事情而创建的,它是需要删除的,我们并不需要做任何特殊处理,因为执行是没有循环的,执行完成以后,RT-Thread 内核会自动把线程删除!!

1.5 挂起和恢复线程

线程挂起和恢复,在官方有单独的说明:
在这里插入图片描述既然官方强烈不建议在程序中使用该接口,我们这里就不说明了,因为以应用为主,我们就不去用了。

需要说明的一点是,这里和FreeRTOS也是不同的,FreeRTOS用户可以随意用,最典型的就是使一段代码进入临界区挂起其他任务。

1.6 其他线程辅助函数

其他的线程辅助函数,除了线程睡眠函数,其他的在一般的应用中都可以不需要。所以我们简单的过一遍,引用一下官方的介绍。如果后期应用的时候有用到,再来加以详细说明:

1.6.1 获得当前线程

在程序的运行过程中,相同的一段代码可能会被多个线程执行,在执行的时候可以通过下面的函数接口获得当前执行的线程句柄,把下面的函数加在这段代码中的,哪个线程调用就返回哪个线程句柄:

/\*
返回值
thread 当前运行的线程句柄
RT\_NULL 失败,调度器还未启动
\*/
rt\_thread\_t rt\_thread\_self(void);

1.6.2 让出处理器资源
rt\_err\_t rt\_thread\_yield(void);

调用该函数后,当前线程首先把自己从它所在的就绪优先级线程队列中删除,然后把自己挂到这个优先级队列链表的尾部,然后激活调度器进行线程上下文切换(如果当前优先级只有这一个线程,则这个线程继续执行,不进行上下文切换动作)。

1.6.3 线程睡眠(延时函数)

线程睡眠,直白点说,就是延时函数,只不过RTOS中的延时函数,是会释放CPU使用权的,释放CPU使用权,就等于线程睡眠了。

/\*
参数:tick/ms 
线程睡眠的时间:sleep/delay 的传入参数 tick 以 1 个 OS Tick 为单位 ;
mdelay 的传入参数 ms 以 1ms 为单位;
返回
RT\_EOK 操作成功,一般不需要
\*/
rt\_err\_t rt\_thread\_sleep(rt\_tick\_t tick);
rt\_err\_t rt\_thread\_delay(rt\_tick\_t tick);
rt\_err\_t rt\_thread\_mdelay(rt\_int32\_t ms);

1.6.4 线程控制函数
/\*
参数说明:
1、thread 线程句柄
2、cmd 指示控制命令
cmd 当前支持的命令包括:
•RT\_THREAD\_CTRL\_CHANGE\_PRIORITY:动态更改线程的优先级;
•RT\_THREAD\_CTRL\_STARTUP:开始运行一个线程,等同于 rt\_thread\_startup() 函数调用;
•RT\_THREAD\_CTRL\_CLOSE:关闭一个线程,
等同于 rt\_thread\_delete() 或 rt\_thread\_detach() 函数调

用。
3、arg 控制参数
返回值:
RT\_EOK 控制执行正确
-RT\_ERROR 失败
\*/
rt\_err\_t rt\_thread\_control(rt\_thread\_t thread, rt\_uint8\_t cmd, void\* arg);

1.6.5 设置和删除空闲钩子

空闲钩子函数是空闲线程的钩子函数(不要和调度器钩子函数搞混了),如果设置了空闲钩子函数,就可以在系统执行空闲线程时,自动执行空闲钩子函数来做一些其他事情,比如系统指示灯。设置 / 删除空闲钩子的接口如下:

/\*
参数:
hook 设置的钩子函数,在函数中实现一些操作,但是不要有挂起操作
返回值:
RT\_EOK 设置成功
-RT\_EFULL 设置失败
\*/
rt\_err\_t rt\_thread\_idle\_sethook(void (\*hook)(void));
rt\_err\_t rt\_thread\_idle\_delhook(void (\*hook)(void));

官方有一段注意说明如下:在这里插入图片描述

1.6.6 设置调度器钩子

在整个系统的运行时,系统都处于线程运行、中断触发 - 响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事件。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数。在系统线程切换时,这个钩子函数将被调用:

/\*
参数:
hook 表示用户定义的钩子函数指针
\*/
void rt\_scheduler\_sethook(void (\*hook)(struct rt\_thread\* from, struct rt\_thread\* to));
/\*
钩子函数 hook() 的声明
参数说明:
1、from 表示系统所要切换出的线程控制块指针
2、to 表示系统所要切换到的线程控制块指针
\*/
void hook(struct rt\_thread\* from, struct rt\_thread\* to);

注:请仔细编写你的钩子函数,稍有不慎将很可能导致整个系统运行不正常(在这个钩子函数中,基本上不允许调用系统 API,更不应该导致当前运行的上下文挂起)。

二、RT-Thread线程创建示例

虽然上面介绍了有一部分的线程操作函数,但是正常需要也就前面几个,记住线程创建,启动,一般的应用就足够了,其他的一些辅助函数在实际中有很多情况是出了问题以后找 bug 的时候才会想起来。

所以我们演示起来也很简单,还记得在 RT-Thread记录 第一篇博文中:

RT-Thread记录(一、RT-Thread 版本、RT-Thread Studio开发环境 及 配合CubeMX开发快速上手)

在上面博文的最后一节:3.3 创建一个跑马灯任务 我上传了一段源码,这里我就不再重复上一边了,我们直接通过截图说明的方式讲解下示例:

2.1 静态创建线程示例

在这里插入图片描述

2.1 动态创建线程示例

在这里插入图片描述

三、RT-Thread线程管理简析

经过上面的说明,我们其实能够使用 RT-Thread 对于的函数创建线程进行一般的设计了,但是为了加深对RT-Thread的理解,我们还得聊聊 RT-Thread线程管理。

这一块在官网其实有详细的说明,官方的链接如下:

RT-Thread官方文档 RT-Thread内核线程管理
在这里插入图片描述

3.1 线程调度的基本特点

我这边按照自己的理解认知记录几个重要的点:

1、RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权。

调度器开启以后,就不停的在查询列表,所有的线程根据优先级,状态,在列表中排序,调度器总是找到排序“第一位”的线程执行。RTOS的核心就是链表,这个有时间会单独的介绍。

2、当一个运行着的线程使一个比它优先级高的线程满足运行条件,当前线程的 CPU 使用权就被剥夺了,或者说被让出了,高优先级的线程立刻得到了 CPU 的使用权。

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

链表,这个有时间会单独的介绍。

2、当一个运行着的线程使一个比它优先级高的线程满足运行条件,当前线程的 CPU 使用权就被剥夺了,或者说被让出了,高优先级的线程立刻得到了 CPU 的使用权。

[外链图片转存中…(img-RcfhkprL-1715903021621)]
[外链图片转存中…(img-1eUjB1oK-1715903021621)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)

如果你需要这些资料,可以戳这里获取

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: rt-threadfreertos 都是实时操作系统(RTOS),旨在为嵌入式系统提供可靠的多任务处理能力。 rt-thread 是一款开源的 RTOS,专门设计用于低功耗、小容量的嵌入式系统。rt-thread 采用了轻量级线程实现,支持信号量、互斥锁、消息队列等同步机制,并提供了丰富的驱动支持和组件,如 TCP/IP 协议栈、文件系统、GUI 界面等。rt-thread 的内核代码非常精简,可运行在 RAM 或者 ROM 上。 相比之下,freertos 同样是一款开源的 RTOS,但是功能更为丰富,支持更多的处理器架构,包括 ARM、MIPS、x86 等。freertos 提供了多任务调度、内存管理、IPC 机制、软件定时器等功能,并且还有完善的文档和社区支持。与 rt-thread 相比,freertos 的内核代码更加复杂,但是在可移植性和跨平台性方面表现更好。 综上所述,rt-thread 适合用于资源受限的嵌入式系统,而 freertos 则适用于更为复杂的系统,需要更多的功能和处理器架构支持。 ### 回答2: RT-ThreadFreeRTOS都是开源的实时操作系统(RTOS),主要用于嵌入式系统中。它们都提供了许多标准的功能,如多任务处理、线程同步和通信、定时器和时间管理等。但是,它们在一些方面有所不同,下面将对它们进行详细的比较。 首先,RT-Thread是一个面向对象的RTSOS。在RT-Thread中,所有的线程、设备驱动以及其他的系统对象都是对象。这使得它在处理复杂系统时更加容易。而FreeRTOS则是一个面向任务的RTSOS,这意味着它在调度和处理多个任务时更加强大和灵活,但是处理复杂系统时会更加困难。 其次,RT-Thread内置了很多功能模块,如文件系统、TCP/IP协议栈、USB驱动等,这些模块可以很方便地进行移植和使用。而FreeRTOS则需要额外的组件来实现这些功能。这使得RT-Thread更加适合处理复杂系统,而FreeRTOS则更加适合轻量级和低功耗的系统。 另外,RT-Thread提供了C++接口以及Lua脚本语言支持,可以方便地进行自定义扩展和应用开发。而FreeRTOS则只支持C语言,扩展和应用开发需要更多的工作量。 总的来说,RT-Thread适合处理复杂系统和大型项目,FreeRTOS适合轻量级和低功耗的应用。选择哪一个取决于项目的需求和特点。 ### 回答3: rt-threadfreertos是两种嵌入式实时操作系统,都具有轻量级、开源、定位于小型嵌入式设备等特点。它们的主要区别体现在以下几个方面: 1.架构和设计 rt-thread是一个基于“内核+组件”的架构设计,内核负责任务调度、内存管理线程间通信等核心功能,组件则提供文件系统、网络协议栈等更高层次的服务;而freertos则采用了更为简单的内核设计,将任务抽象为优先级,通过优先级管理和调度任务。rt-thread的组件化设计使得系统功能更为完备,但也导致代码更为复杂,freertos则相对来说更为易于理解和实现。 2.可移植性 rt-thread支持多种平台和开发板,包括ARM Cortex-M、RISC-V、Xtensa等架构,同时也支持uClinux内核。这使得rt-thread具有很高的可移植性,能够适应不同类型的嵌入式设备;而freertos则针对特定芯片或板卡进行了优化,移植性相对来说较差。 3.资源占用情况 rt-thread在处理器内存和rom占用方面相对freertos更优,主要是因为rt-thread采用了更为灵活的组件化设计,能够根据实际需要选择加载不同的组件;而freertos则拥有更为紧凑的内核,能够在小型设备上运行。 4.社区支持和生态 rt-threadfreertos都是活跃的开源项目,拥有庞大的社区和完善的生态。但由于rt-thread的组件化设计和多平台支持,其组件库更丰富,社区支持也更活跃,可以提供更为全面的功能和应用支持。 综上所述,二者在应用场景上有着较为明显的区别rt-thread适用于需求更为复杂、功能更为完备的嵌入式设备,而freertos则更适合资源有限、对实时性要求不高的小型设备。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值