RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)

更详细的参考链接
0 准备: 环境准备:
MDK-ARM 5.30 (正式版或评估版,5.14 版本及以上版本均可) + 破解软件
STM32F103 pack 库文件
百度云盘云盘文件
1.文件,工程和程序执行次序 RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

2 动态内存 RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

内存复位: 当我们每次申请到新的内存块之后,建议对所申请到的内存块进行清零操作
内存泄漏:内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果;
我们在使用动态内存时需要注意: rt_ malloc需要和rt_ free配套使用
另外两个函数
void *rt_ realloc(void *rmem, rt_ size_ _t newsize)
在已分配内存块的基础上重新分配内存块的大小(增加或缩小); 在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断)
void *rt_calloc(rt size_ t count, rt_ _size_ _t size)从内存堆中分配连续内存地址的多个内存块 =count个size大小的空间
3.线程 RT-Thread名为实时线程RTOS,那么什么叫线程?
人们在生活中处理复杂问题时,惯用的方法就是“分而治之”,即把一一个大问题分解成多个相对简单、比较容易解决的小问题,小问题逐个被解决了,大问题也就随之解决了。同样,在设计一个较为复杂的应用程序时,也通常把一个大型任务分解成多个小任务,然后通过运行这些小任务,最终达到完成大任务的目的。
在RT-Thread中,与上述小任务对应的程序实体就叫做“线程”(或任务) ,RT-Thread就是一个能对这些小“线程”进行管理和调度的多“线程”操作系统。线程是实现任务的载体,它是RT-Thread中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级。
两张代码结构
RT-Thread中,线程由三部分组成:线程代码(入口函数)、线程控制块、线程堆栈
(1) 线程代码常见的两种结构.
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

(2)线程控制块
线程控制块是操作系统用王管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包线程与线程之间连接用的链表结构,线程 等待事件集合等。
struct rt_ _thread
struct rt_ _thread *rt_ _thread_ t
(3)线程栈
RT-Thread每个线程都具有独立的栈空间,当进 行线程切换时,系统 会将当前线程的,上下文保存在线程栈中,当线程要恢复运行时,再从线程栈中读取上下文信息,恢复线程的运行。
线程上下文是指线程执行时的环境,具体来说就是各个变量和数据包括所有寄存器变量、堆栈信息、内存信息等。
线程栈在形式上是一段连续的内存空间,我们可以通过定义一个数组或者申请一段动态内存来作为线程的栈。
创建线程(静态和动态)
(参数为定义的线程控制块,线程名称,函数指针-执行线程入口代买,传入线程的参数,线程的栈空间起始地址,栈的总大小,线程优先级,tick线程时间片参数),动态线程第一个参数是线程的名称(不需要定义线程控制块),栈是不指定栈空间的起始地址
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

●启动线程
rt err trt_ _thread_ startup(rt_ _thread_ _t thread)
调用此函数后创建的线程会被加入到线程的就绪队列,执行调度
线程的效率對比
相关资源分配形式:资源是动态/静态的分配
运行效率:如果有外部ram的话,静态的效率会高一点(内部的效率会高),如果是都用的内部的ram,效率是一样的
线程状态切换
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

4.系统滴答时钟 每一个操作系统中都存在一个“系统心跳”时钟,是操作系统中最小的时钟单位。这个时钟负责系统和时间相关的-些操作。作为操作系统运行的时间尺度,心跳时钟是由硬件定时器的定时中断产生。
系统的心跳时钟我们也常称之为系统滴答或时钟节拍,系统滴答的频率需要我们根据cpu的处理能力来决定。
时钟节拍使得内核可以将线程延时若干个整数时钟节拍,以及线程等待事件发生时,提供等待超时的依据。
频率越快,内核函数介入系统运行的几率就越大,内核占用的处理器时间就越长,系统的负荷就变大;
频率越小,时间处理精度又不够; 我们在stm32平台上--般设置系统滴答频率为100Hz,即每个滴答的时间是10ms
GPIO驱动架构操作I0
I0初始化
void rt_ pin_ mode(rt_ base_ _t pin, rt_ base_ t mode)//比如F103ZET6 pin是0-144 ,模式有如下5种输入 /输出
PIN_ MODE_ OUTPUT\PIN_ MODE_ INPUT\PIN_ MODE_ INPUT_ PULLUP
PIN_ MODE_ _INPUT_ PULLDOWN\ PIN MODE_ OUTPUT_ OD
I0写入
void rt_ pin_ _write(rt_ _base_ _t pin, rt_ base_ t value)
PIN_ HIGH \ PIN_ _LOW
I0读出
int rt_ _pin_ read(rt_ base_ t pin)
线程栈大小分配
先将线程栈大小设置一- 个固定值(比如2048),在线程运行时通过查看线程栈的使用情况,了
解线程栈使用的实际情况,根据情况设置合理的栈大小 -- 一般设置为最大量的70%--比较省空间
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

5.线程优先级 优先级和时间片是线程的两个重要参数,分别描述了线程竞争处理器资源的能力和持有处理器时间长短的能力。
RT-Thread最大支持256个优先级(数值越小的优先级越高,0为最高优先级,最低优先级预留给空闲线程) ;
用户可以通过rt_ config.h中 的RT_ _THREAD_ PRIORITY_ _MAX宏来修改最大支持的优先级;
针对STM32默认设置最大支持32个优先级;
具体应用中,线程总数不受限制,能创建的线程总数只和具体硬件平台的内存有关
时间片只有在相同优先级的就绪态线程中起作用,系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick)假设有2个优先级相同的就绪态线程A与B,A线程的时间片设置为10,B线程的时间片设置为5,那么当系统中不存在比A优先级高的就绪态线程时,系统会在A、B线程间来回切换执行,并且每次对A线程执行10个节拍的时长,对B线程执行5个节拍的时长
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

线程调度规则 优先级抢占调度(stm32的抢占优先级)
操作系统总是让具有最高优先级的就绪任务优先运行:即当有任务的优先级高于 当前任务优先级并且处于就绪态后,就一定 会发生任务调度
通过优先级抢占机制,最大限度的满足了系统的实时性
时间片轮询调度(stm32的响应优先级)
当操作系统中存在相同优先级的线程时(优先级相同就不会抢占),操作系统会按照设置的时间片大小来轮流调度线程,时间片起到约束线程单次运行时长的作用,
其单位是一一个系统节拍(OS Tick)
通过时间片轮询,保证优先级相同的任务能够轮流占有处理器
6.空闲线程及两个常用的钩子函数(见idlehook sample.c)

  1. 空闲线程是一个比较特殊的系统线程,它具备最低的优先级。当系统中无其他就绪线程可运行时,调度器将调度到空闲线程。
  2. 空闲线程还负责一些系统资源回收以及将一些处于关闭态的线程从线程调度列表中移除的动作
  3. 空闲线程在形式上是一个无线循环结构,且永远不被挂起。
  4. 在RT-Thread实时操作系统中空闲线程向用户提供了钩子函数,空闲线程钩子函数可以让系统在空闲的时候执行一些非紧急事务,例如系统运行指示灯闪烁,CPU 使用率统计等等。
【RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)】设置钩子函数
rt_ err_ _t rt__thread_ idle_ sethook(void (*hook)(void))
删除钩子函数
rt_ err_ _trt_ .thread_ _idle_ delhook(void (*hook)(void))
空闲线程钩子函数使用注意
●空闲线程是=二个线程状态永远为就绪态的线程。所以钩子函数中执行的相关代码必须保证空闲线程在任何时刻都不会被挂起,例如rt_ _thread_ _delay()、
rt_sem__take()等可能会导致线程挂起的阻塞类函数都不能在钩子函数中使用(钩子函数不能阻塞或者不能挂起)
●空闲线程可以设置多个钩子函数
系统调度钩子函数(见scheduler hook.c)--查看线程切换顺序
系统的上下文切换是系统运行过程中最普遍的事件,有时用户可能会想知道在某--个时刻发生了什么样的线程切换,RT-Thread向用户提供了 一个系统调度钩子函数,这个钩子函数在系统进行任务切换时运行,通过这个钩子函数,我们可以了解到系统任务调度时的-些信息。
rt_ scheduler_ sethook(void (*hook)(struct rt_ thread *from, struct rt_ thread *to))
7.临界资源 临界资源是指一次仅允许=个线程访问的共享资源。它可以是一个具体的硬件设备,也可以是一个变量、-一个缓冲区。
不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它们进行访问
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

每个线程中访问(操作)临界资源的那段代码称为临界区(Critical Section) ,我们每次只准许一个线程进入临界区
RT-Thread提供了多种途径来进行临界区保护2种方法
  1. 关闭系统调度保护临界区:禁止调度、关闭中断
  2. 互斥特性保护临界区:信号量、互斥量
1.禁止调度
禁止调度,即是把调度器锁住,不让其进行线程切换。这样就能保证当前运行的任务不被换出,直到调度器解锁,所以禁止调度是常用的临界区保护方法
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

2.关闭中断(见interupt sample.c)
因为所有线程的调度都是建立在中断的基础上的
所以,当我们关闭中断后,系统将不能再进行调度,线程自身也自然不会被其他线程抢占了(但是也会影响到其他无关调度的正常使用)
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

8.信号量(属于进程间通信IPC内容)
  1. 在嵌入式系统中运行的代码主要包括线程和ISR,在它们的运行过程中,它们的运行步骤有时需要同步(按照预定的先后次序运行),它们访问的资源有时需要互斥(一个时刻只允许一一个线程访问资源),它们之间有时也要彼此交换数据。这些需求,有的是因为应用需求,有的是多线程编程模型带来的需求。
  2. 操作系统必须提供相应的机制来完成这些功能,我们把这些机制统称为进(线)程间通信( Internal Process Communication IPC) ,RT-Thread 中的IPC机制包括信号量、互斥量、事件、邮箱、消息队列
  3. 通过IPC机制,我们可以协调多个线程(包括ISR)“默契”的工作,从而共同完成一个整项工作。
例说信号量
以生活中的停车场为例来理解信号量的概念:
①当停车场空的时候,停车场的管理员发现有很多空车位,此时会让外面的车陆续进入停车场获得停车位;
②当停车场的车位满的时候,管理员发现已经没有空车位,将禁止外面的车进入停车场,车辆在外排队等候;
③当停车场内有车离开时,管理员发现有空的车位让出,允许外面的车进入停车场; 待空车位填满后,又禁止外部车辆进入。
在此例子中,管理员就相当于信号量; 管理员手中空车位的个数就是信号量的值;
停车位相当于公共资源,车辆相当于线程。车辆通过 获得管理员的允许取得停车位,就类似于线程通过获得信号量访问公共资源
信号量工作机制
信号量是=种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

信号量工作示意图如.上图所示,每个信号量对象都有一个信号量值和-个线程等待队列,信号量的值对应信号量对象的实例数目(资源数目),假如信号量值N,则表示共有N个信号量实例(资源)可以被使用,当信号量实例数目为零时,再请该信号量的线程就会被挂起(阻塞排队)在该信号量的等待队列上,等待可用的信号量实例(资源)

信号量控制块
在RT-Thread中,信号量控制块是操作系统用于管理信号量的--个数据结构。
RT-Thread|RT-Thread嵌入式操作系统 开发笔记(上)
文章图片

定义静态信号量: struct rt_ semaphore static_ sem
定义动态信号量: rt_ sem_ tdynamic_ sem
信号量的操作semaphore_sample.c
1.初始化与脱离(针对静态信号量)
rt_ err_ trt_ sem_ jinit(rt_ sem_ _tsem, const char*name, rt_ _uint32__tvalue, rt_ uint8__t flag) //信号量定义,名字,初值和标志
rt_ err. _trt_ _sem_ detach(rt_ sem_ t sem)
2.创建与删除(针对动态信号量)
rt_ sem_ _trt_ sem_ create(const char *name, rt_ _uint32_ t value, rt_ _uint8_ tflag) flag有:RT_ IPC. FLAG. _FIFO(阻塞排队时按照先进先出) RT. IPC _FLAG_ PRIO(信号量为0,阻塞排队时按照优先级)
rt_ err_ _trt_ sem_ _delete(rt_ _sem_ _t sem)
3.获取信号量
rt_ err _trt_ sem_ take(rt_ sem_ t sem, rt_ _int32_ _t time) //time时间参数-RT. WAITING. FOREVER返回等待等待时间,等于-1永等, 获得信号量 =0是不可用
rt_ _err_ _trt_ sem_ _trytake(rt_ sem_ _t sem) //尝试获取,查看信号量是否可以使用
4.释放信号量
rt_ err _trt_ sem_ release(rt_ sem_ t sem)

    推荐阅读