(一)RT-Thread内核基础与线程管理

说明:RT-Thread作为开源的RTOS,网络上已经有很多的视频和文档教程有利于入门或是深入学习。本专栏设立目的仅为快速学习RT-Thread以便能在短时间内上手使用,需要系统、深入地学习可参阅以下资源链接(敬前辈、大佬):

博主学习环境配置:MDK Keil V5.3.1、RT-Thread Nano V3.1.3、无硬件板卡(软件仿真)


内核基础部分

一、RT-Thread 启动流程
系统启动流程

  MDK 的扩展功能 $Sub$$$Super$$:如果原本存在一个函数 fuc,就可以再定义一个带有$Sub$$前缀的 fuc(fuc’),在 fuc’ 的内部调用原来的 fuc 时,需要加上$Super$$前缀来进行声明(现在我要调用原来的那个 fuc 函数)。当然在 fuc’ 的内部除了调用原本的 fuc,也可以再做一些其他的处理或是调用其他的函数(如启动流程图中所示的 main 函数)。简单来说就是,组合起来使用可以起到一个类似函数重载的作用。

二、RT-Thread程序内存分布

  1. Code:代码段,存放程序的代码部分
  2. RO-data:只读数据段,存放程序中定义的常量
  3. RW-data:读写数据段,存放初始化为非 0 值的全局变量
  4. ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量

Keil编译完工程后生成的 .map 文件中有对工程中程序的整体说明:

Total RO Size (Code + RO Data) 53668 ( 52.41kB)
Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)
  1. RO Size 包含了 Code 及 RO-data,表示程序占用 Flash 空间的大小
  2. RW Size 包含了 RW-data 及 ZI-data,表示运行时占用的 RAM 的大小
  3. ROM Size 包含了 Code、RO Data 以及 RW Data,表示烧写程序所占用的 Flash 空间的大小
    RT-Thread内存分布

三、RT-Thread组件自动初始化机制(参考系统启动流程图进行说明)

  1. “board init functions”    为所有通过  INIT_BOARD_EXPORT(fn)     申明的初始化函数
  2. “pre-initialization functions”  为所有通过  INIT_PREV_EXPORT(fn)     申明的初始化函数
  3. “device init functions”    为所有通过  INIT_DEVICE_EXPORT(fn)     申明的初始化函数
  4. “components init functions”  为所有通过  INIT_COMPONENT_EXPORT(fn) 申明的初始化函数
  5. “enviroment init functions”  为所有通过  INIT_ENV_EXPORT(fn)       申明的初始化函数
  6. “application init functions”  为所有通过  INIT_APP_EXPORT(fn)      申明的初始化函数

组件初始化相关宏接口:

初始化顺序宏接口描述
1INIT_BOARD_EXPORT(fn)非常早期的初始化,此时调度器还未启动
2INIT_PREV_EXPORT(fn)主要是用于纯软件的初始化、没有太多依赖的函数
3INIT_DEVICE_EXPORT(fn)外设驱动初始化相关,比如网卡设备
4INIT_COMPONENT_EXPORT(fn)组件初始化,比如文件系统或者 LWIP
5INIT_ENV_EXPORT(fn)系统环境初始化,比如挂载文件系统
6INIT_APP_EXPORT(fn)应用初始化,比如 GUI 应用

四、常见宏定义说明(Keil环境)

1)rt_inline,定义如下,static 关键字的作用是令函数只能在当前的文件中使用;inline 表示内联,用 static 修饰后在调用函数时会建议编译器进行内联展开。

#define rt_inline                   static __inline

2)RT_USED,定义如下,该宏的作用是向编译器说明这段代码有用,即使函数中没有调用也要保留编译。例如 RT-Thread 自动初始化功能使用了自定义的段,使用 RT_USED 会将自定义的代码段保留。

#define RT_USED                     __attribute__((used))

3)RT_UNUSED,定义如下,表示函数或变量可能不使用,这个属性可以避免编译器产生警告信息。

#define RT_UNUSED                   __attribute__((unused))

4)RT_WEAK,定义如下,常用于定义函数,编译器在链接函数时会优先链接没有该关键字前缀的函数,如果找不到则再链接由 weak 修饰的函数。

#define RT_WEAK                     __weak

5)ALIGN(n),定义如下,作用是在给某对象分配地址空间时,将其存放的地址按照 n 字节对齐,这里 n 可取 2 的幂次方。字节对齐的作用不仅是便于 CPU 快速访问,同时合理的利用字节对齐可以有效地节省存储空间。

#define ALIGN(n)                    __attribute__((aligned(n)))

6)RT_ALIGN(size,align),定义如下,作用是将 size 提升为 align 定义的整数的倍数,例如,RT_ALIGN(13,4) 将返回 16。

#define RT_ALIGN(size, align)      (((size) + (align) - 1) & ~((align) - 1))

线程管理部分

一、基本概念及工作机制

  RT-Thread系统中共存在两种线程,一种为RT-Thread内核创建的系统线程,另一种则为应用程序创建的用户线程。系统线程包括空闲线程和主线程。

  • RT-Thread中的线程包含5种状态:初始、就绪(运行)、挂起、关闭
  • RT-Thread最大支持256个线程优先级,数值越小优先级越高
  • RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权
  • 如果是中断服务程序使一个高优先级的线程满足运行条件,中断完成时,被中断的线程挂起,优先级高的线程开始运行
  • 很明显普通线程不能陷入死循环操作,必须要有让出CPU使用权的动作(不然就跑死了)
  • 当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复
  • 每个线程都可以通过时间片参数来对线程的单次运行时长进行约束
  • 空闲线程是系统创建的最低优先级的线程,线程状态永远为就绪态。当系统中无其他就绪线程存在时,调度器将调度到空闲线程,它通常是一个死循环,且永远不能被挂起
  • 若某线程运行完毕,系统将自动删除线程,无特殊情况则无需用户给出删除指令
状态描述
初始状态当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT
就绪状态在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY
运行状态线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING
挂起状态也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND
关闭状态当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE

二、线程相关操作及部分操作函数使用说明
在这里插入图片描述
  关于创建/初始化:当目标线程对象为动态对象时,使用创建函数;当目标线程对象为静态对象时,使用初始化函数。删除/脱离起同样的对应效果。

  1. 线程创建函数 rt_thread_create()
 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);
参数描述
name线程名称,其最大长度由rtconfig.h中的宏 RT_NAME_MAX 指定,多余部分会被自动删除
entry线程入口函数
parameter线程入口函数的参数
stack_size线程的栈大小,单位是字节
priority线程的优先级
tick线程时间片的大小
返回描述
thread线程创建成功,返回线程句柄
RT_NULL线程创建失败
  1. 线程删除函数 rt_thread_delete()
rt_err_t rt_thread_delete(rt_thread_t thread);
参数描述
thread要删除的线程句柄
返回描述
RT_EOK删除线程成功
-RT_ERROR删除线程失败
  1. 线程初始化函数 rt_thread_init()
 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);
参数描述
thread线程句柄,用于指向对应的线程控制块
name线程名称
entry线程入口函数
parameter线程入口函数的参数
stack_start线程栈起始地址
stack_size线程的栈大小,单位是字节
priority线程的优先级
tick线程时间片的大小
返回描述
RT_EOK线程创建成功
RT_NULL线程创建失败

PS:对比创建和初始化函数的传入参数及返回值的差别可以更好地理解动态对象与静态对象之间的区别。
动态对象:我(程序员)要一块这么大的内存块来存数据,你(系统)去给我分配一下。
静态对象:跟你(系统)说一下,我(程序员)要把这块内存块拿来存数据,从这个位置开始存起,数据量不会超过这个范围。

  1. 线程脱离函数 rt_thread_detach()
 rt_err_t rt_thread_detach (rt_thread_t thread);
参数描述
thread要脱离的线程句柄
返回描述
RT_EOK线程脱离成功
-RT_ERROR线程脱离失败
  1. 线程启动函数 rt_thread_startup()
 rt_err_t rt_thread_startup(rt_thread_t thread);
参数描述
thread要启动的线程句柄
返回描述
RT_EOK线程启动成功
-RT_ERROR线程启动失败

  以上为线程管理的基本操作函数。除此之外还有诸多线程启动后的控制类函数可以使用,本文不在此赘述,详情可参阅开头的RT-Thread文档中心。

三、线程应用示例(Keil环境仿真)
  创建一个动态线程 thread1 ,初始化一个静态线程 thread2 。thread2 在运行完毕后自动被系统脱离,thread1 则一直打印计数:

/* 
 * Copyright (c) 2006-2018, RT-Thread Development Team 
 * 
 * SPDX-License-Identifier: Apache-2.0 
 * 
 * Change Logs: 
 * Date           Author       Notes 
 * 2018-08-24     yangjie      the first version 
 */ 

#include <rtthread.h>

#define THREAD_PRIORITY         25
#define THREAD_STACK_SIZE       512
#define THREAD_TIMESLICE        5

static rt_thread_t tid1 = RT_NULL;				//动态线程对象句柄,用于连接系统分配下来的动态内存堆

/* 线程 1 的入口函数 */
static void thread1_entry(void *parameter)
{
    rt_uint32_t count = 0;

    while (1)
    {
        /* 线程 1 采用低优先级运行,一直打印计数值 */
        rt_kprintf("thread1 count: %d\n", count ++);
        rt_thread_mdelay(500);
    }
}

ALIGN(RT_ALIGN_SIZE)							//将存放的地址按指定字节数对齐(内核基础部分第四节)
static char thread2_stack[1024];
static struct rt_thread thread2;				//这里定义的是一个线程对象
/* 线程 2 入口 */
static void thread2_entry(void *param)
{
    rt_uint32_t count = 0;

    /* 线程 2 拥有较高的优先级,以抢占线程 1 而获得执行 */
    for (count = 0; count < 10 ; count++)
    {
        /* 线程 2 打印计数值 */
        rt_kprintf("thread2 count: %d\n", count);
    }
    rt_kprintf("thread2 exit\n");
    /* 线程 2 运行结束后将自动被系统脱离 */
}

/* 线程示例 */
int thread_sample(void)
{
    /* 创建线程 1,名称是 thread1,入口是 thread1_entry*/
    tid1 = rt_thread_create("thread1",
                            thread1_entry, RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);

    /* 如果获得线程控制块,启动这个线程 */
    if (tid1 != RT_NULL)
        rt_thread_startup(tid1);

    /* 初始化线程 2,名称是 thread2,入口是 thread2_entry */
    rt_thread_init(&thread2,
                   "thread2",
                   thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(thread_sample, thread sample);	//前者为指令名称(对应示例函数名),后者为附加的指令说明。
												//想更加直观就在仿真系统里键入help就能看到了

运行结果如下图(因为 thread1 实际上是死循环所以没有让它一直跑下去):
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值