下载程序
- 使用RT-Thread Studio下载完毕程序后,需要重新复位STM32。
新版本的RT-Thread Studio 不需要再复位了
同步
- 同步的概念也可以理解为,满足什么条件后然后做某事
- 按下按键,然后打开灯,这可以叫做同步。
- 满足条件A,满足条件B,才能去做C,这也可以叫做同步。
系统时钟
- 采用滴答时钟
- 系统滴答频率通过
rt_config.c
中的RT_TICK_PER_SECOND
设置。【1000 表示 1000Hz,即1ms】
钩子函数
- 空闲线程向用户提供可钩子函数,空闲线程钩子函数可以让系统在空闲的时候执行一些非紧急事务,例如系统运行指示灯闪烁,CPU使用率统计等。
- 钩子函数不可以使用诸如
rt_thread_delay()、rt_sem_take()
等可能会导致线程挂起的阻塞类函数。 - 空闲线程可使用多个钩子函数
临界资源和临界区
- 临界资源 : 一次仅允许一个线程访问!
- 每个线程访问临界资源的那段代码成为临界区(critical section),每次只允许一个线程临界区
临界区保护的手段
- 关闭系统调度【禁止调度、关闭中断】
①、禁止调度(调度器上锁)
...
rt_enter_critical();//调度器上锁,上锁后不再切换到其他线程,仅响应中断
//进入临界区
rt_exit_critical();//调度器解锁
...
②、关闭中断【因为所有线程的调度都是建立在中断的基础上的】
rt_base_t level;
...
level = rt_hw_interrupt_disable();//关闭中断
//以下临界区
rt_hw_interrupt_enable(level);//打开中断
...
- 互斥特性保护临界区 【信号量、互斥量】
1、线程
-
线程的大小是在栈(STACK)中分配的
-
线程的各个状态转换图:
- 线程栈大小分配技巧:使得max used在70%左右
2、时间片
- 线程持有处理器长短的能力
- 时间片只有在相同优先级的就绪态线程中起作用。【系统对优先级相同的就绪态线程采用时间片轮转的调度方式时进行调度时,时间片启动约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick)】
3、软件定时器
- 软件定时器介绍【注意:以系统节拍
OS TICK
为单位,并且可以定时的是系统节拍的整数倍】
- 定时器模式【超时函数,即回调函数】
- 软件定时器控制块
- 定时器操作函数
RT_TIMER_FLAG_ONE_SHOT
- 一次性运行RT_TIMER_FLAG_PERIODIC
- 周期运行- 通常,上面两个FLAG会和下面的FLAG中搭配使用!
注意SOFT模式要开启RT_USING_TIMER_SOFT
这个宏! RT_TIMER_FLAG_HARD_TIMER
- HARD模式【中断】【默认】RT_TIMER_FLAG_SOFT_TIMER
- SOFT模式【线程】
4、线程间通信
- 嵌入式系统运行的代码主要包括线程和ISR(中断服务函数),在它们运行过程中,它们的运行步骤有时需要同步(按照预定的先后次序运行),它们访问的资源有时需要互斥(一个时刻只允许一个线程访问资源),它们之间有时需要彼此交换数据。这些需求,有的是因为应用需求,有的是多线程编程模型代码的需求。
- 操作系统必须提供相应的机制来完成这些功能,我们把这些机制统称为进(线)程间通信【IPC】
- RT-Thread中的IPC机制包括信号量、互斥量、邮箱、消息队列、事件集
4.1、信号量与互斥量
4.1.1、信号量
- 信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取和释放它,从而达到同步或互斥的目的。
- 上图为信号量的工作示意图,每个信号量对象都有一个信号量值和一个线程等待队列。
- 信号量的值对应信号量对象的实例数目,若信号量为N,则表示有N个信号量实例可以被使用。
- 当信号量实例数目为零时,该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例。
- 停车位理解信号量
- 信号量控制块代码实现如下
struct rt_semaphore
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint16_t value; /**< value of semaphore. */
};
typedef struct rt_semaphore *rt_sem_t;
-
定义静态信号量:
struct rt_semaphore static_sem
-
定义动态信号量:
rt_sem_t dynamic_sem
-
信号量操作函数
-
初始化与脱离【针对静态信号量】
-
创建与删除【针对动态信号量】 【注意动态的会有成功和失败,所以一定要判断】
flag
取值为RT_IPC_FLAG_FIFO
和RT_IPC_FLAG_PRIO
两种,前者表示先进先出排队,后者便是优先级排队。 -
获取动态信号量(信号量值-1)
- 其中,time表示当信号量为0时的等待时间,
time=0
表示此时立即返回,time=50
表示等待50ms后返回,time=RT_WAITING_FOREVER
表示永久等待! rt_sem_trytake
是rt_sem_take
中time=0的情况。等不到信号量时,两者都会返回RT_ETimeout
,当成功返回信号量,返回RT_OK
- 注意获取信号量会导致线程会挂起,故该信号量获取API不能再中断中调用,只能在线程中调用!
- 其中,time表示当信号量为0时的等待时间,
-
释放信号量(信号量值+1)
-
释放完信号量后,信号量值
+1
-
注意:释放信号量,可以在线程或者中断中!
-
4.1.2、互斥量
优先级的概念
- RT-Thread最大支持256个优先级(0-255),数值越小优先级越高,0为最高优先级,最低优先级预留给空闲线程
- 针对STM32默认设置最大支持32个优先级,可通过
rt_config.h
中的RT_THREAD_PRIORITY_MAX
来修改最大支持的优先级 - 具体应用中,线程总数是不受限制的(可以有多个相同的优先级),能创建的线程总数只和具体硬件平台的内存有关(栈)。
优先级反转问题描述
- 当一个高优先级试图通过某种互斥IPC(进程间通信)机制访问共享资源时,若该IPC对象已被一低优先级的线程所持有,而这个低优先级线程在运行过程中可能又被其他一些中等优先级的线程抢占,因此造成高优先级线程被许多具有低优先级的线程阻塞的情况 ------- 称为优先级反转。
- 以下图为例:优先级分别是A>B>C,而一开始只有C处于就绪状态,所以C可以访问共享资源M,当A就绪时,由于此时C正在访问共享资源M,所以A继续处于就绪态等待C执行。C在执行过程中B也处于了就绪态,但是B不需要访问共享资源M且B的优先级高于C,所以此时会先运行B,完毕后再运行C。等待C完毕后,最后运行A。这个例子便出现了高优先级A被B阻塞的情况。
- 信号量存在优先级反转的问题,而互斥量(稍后引入的特殊信号量)不存在。因为互斥量采用了优先级继承算法,可以有效解决优先级反转问题。即,提高某个占有某种共享资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,从而得到更快的执行后释放资源,而当这个低优先级线程释放该资源时,优先级重新回到初始设定值 ------- 称之为优先级继承。具体过程,如下图所示。
- 注意:优先级反转现象提醒我们对共享资源进行互斥访问的代码应尽可能的短!
互斥量
- 解决优先级反转问题,引入的互斥量,又称为互斥信号量。
- 互斥量是用于线程间互斥访问的IPC对象,它是一种特殊的二值性信号量。
- 两种状态:加锁(LOCKED)和开锁(UNLOCKED)。
- 一个线程持有它时,互斥量处于加锁状态,这个线程获得它的所有权。
- 互斥量控制块代码实现如下
struct rt_mutex
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint16_t value; /**< value of mutex */
rt_uint8_t original_priority; /**< priority of last thread hold the mutex */
rt_uint8_t hold; /**< numbers of thread hold the mutex */
struct rt_thread *owner; /**< current owner of mutex */
};
typedef struct rt_mutex *rt_mutex_t;
-
定义静态互斥量
struct rt_mutex static_mutex
-
定义动态互斥量
rt_mutex_t dynamic_mutex
-
互斥量操作函数
-
初始化与脱离【针对静态信号量】
flag
表示当互斥量不可用的时候,等待线程的排队方式FIFO
或者PRIO
-
创建与删除【动态】
-
获取互斥量【对互斥量的加锁操作】
time
表示等待被加锁时间- 注意:此处互斥量与信号量有不同的地方!支持递归使用,当互斥量已经成功获取到互斥量使用权,再去take操作的时候,该线程不会被挂起,同时其成员变量
hold
将会实现+1操作!
-
释放互斥量
- 只有获取互斥量的线程,才可以释放互斥量。
- 注意:take和release只能在线程中使用,不能再ISR中调用!【释放也只能在获取到互斥量的线程中释放!】
-
4.1.3、信号量VS互斥量
- 信号量可以由任何线程(及中断)释放,它用于同步的时候就像交通灯,线程只有在获得许可的时候才可以运行。强调的是运行步骤。
- 互斥量只能由持有它的线程释放。强调的是许可和权限。
- 信号量可能导致线程优先级反转,互斥量可通过优先级继承的方法解决优先级反转问题。
4.2、邮箱(数据交互)
- 一种线程间通信(IPC)方式
- 不同于之前的IPC对象,邮箱可以进行数据交互。
- 邮箱中的每封邮件,只能固定容纳4字节内容。【对于32位处理器,刚好是一个指针的大小】
- 邮箱控制块
struct rt_mailbox
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint32_t *msg_pool; /**< start address of message buffer */
rt_uint16_t size; /**< size of message pool */
rt_uint16_t entry; /**< index of messages in msg_pool */
rt_uint16_t in_offset; /**< input offset of the message buffer */
rt_uint16_t out_offset; /**< output offset of the message buffer */
rt_list_t suspend_sender_thread; /**< sender thread suspended on this mailbox */
};
typedef struct rt_mailbox *rt_mailbox_t;
-
定义静态邮箱:
struct rt_mailbox staic_mb
-
定义动态邮箱:
rt_mailbox_t dynamic_mb
-
邮箱操作函数
- 初始化和脱离【静态】
- 创建和删除【动态】
- 发送邮件
- value为邮件内容,若要发送的消息特别长,可以传入字符串指针!
rt_mb_send_wait
邮件发送函数,在timeout
内邮箱是满的,可以等待。若超过timeout仍然是满的,则会发送超时。- 注意:
rt_mb_send
可以在线程或中断中使用,而rt_mb_send_wait
只能在线程中使用。
- 接收邮件
- 初始化和脱离【静态】
4.3、消息队列(数据交互)
- 另一种线程间通信(IPC)方式,消息队列是对邮箱的扩展。
- 定义:能够接受来自线程或中断服务例程中发出的不固定长度的消息,并把消息缓存到自己的内存空间中,而其他线程能够从消息队列中读取相应的消息并进行相应的处理。
- 若是紧急信息,可直接链接到链表头,接收消息的时候优先接收到!!!
- 注意
msg_size
表示消息的长度,但是会根据系统中的字节对齐自动调整,比如系统的字节对齐为4,【rtconfig.h
中定义的RT_ALIGN_SIZE
】,若msg_size
赋值为1,则系统会自动改为4;若msg_size
赋值为5,则系统会自动改为8… max_msgs
表示消息队里的消息个数,若内存池大小1024,那么max_msgs
的计算方法为1024/(4+4)=128
。第一个4为系统的字节对齐长度,第二个字节长度为系统指针占的字节数!
- 注意
4.4、事件集(加入逻辑运算,与或)
-
以乘公交车为例说明事件集
-
信号量主要用于“一对一”的线程同步,当需要“一对多”、“多对一”、“多对多”的同步时,就需要事件集来处理了。
-
RT-Thread中的事件集用一个32位无符号整型变量来表示,变量中的一个位代表一个事件,线程通过“逻辑与”或“逻辑或”与一个或多个事件简历关联形成一个事件组合。
- 事件的“逻辑或”也称为独立型同步,指的是线程与任何事件之一发生同步,只要有一个事件发生,即满足条件。
- 事件的“逻辑与”也称为关联型同步,指的是线程与若干事件都发生同步,只有这些事件全部发生,才满足条件。
-
看图解释上述:线程或者中断发送一个32位无符号整型变量表示的事件集,分别为事件0~事件31,而线程1只对其中的事件1或者事件30感兴趣,只要事件1和事件30满足逻辑于或者逻辑或的关系即可满足条件。
-
事件集控制块
struct rt_event
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint32_t set; /**< event set */
};
typedef struct rt_event *rt_event_t;
-
定义静态事件集:
struct rt_event static_evt
-
定义动态事件集:
rt_event_t dynamic_evt
-
事件集操作函数
-
初始化和脱离【针对静态事件集】
-
创建和删除【针对动态事件集】
-
发送事件【注意:线程或者中断都可以发送】
- 其中set为0x01表示第0个事件发送了,0x08表示第3个事件发送了。
-
接收事件
- 其中的set往往是感兴趣的事件,通常是一些事件的组合。如:
0x01 | 0x08
,表示对第0个和第3个事件感兴趣。 - option表示事件标记,与(都发生)、或(发生一个)、清除(对应事件位清零)
- timeout表示接受等待事件,负数一直等待。
- recved表示接收到的事件,用十六进制表示。
- 其中的set往往是感兴趣的事件,通常是一些事件的组合。如:
-
5、内存池使用
- 介绍
- 工作机制
- 内存池控制块
- 内存池操作函数
-
- 注意:block_size同之前消息队列的
msg_size
,会根据系统对齐字节数,进行自动调整!假如内存大小为4096,block_size=80,那么会有4096/(80+4)=48个内存块!
- 注意:block_size同之前消息队列的