核心思想
访问临界区时,只允许一个(或一类)任务运行
4.1 关闭中断(中断锁)
4.1.1 使用函数及其作用
作用:禁止多任务访问临界区,API接口由BSP实现,用于关闭中断并且返回关闭中断前的中断状态,赋值给level用于关闭中断并且返回关闭中断前的中断状态,赋值给level
关闭中断:rt_base_t rt_hw_interrupt_disable(void);
作用:“使能中断”调用“关闭中断函数”前的中断状态
恢复中断:void rt_hw_interrupt_enable(rt_base_t level);
4.1.2 使用场合
中断关闭期间系统将不再响应任何中断,也就不能响应外部的事件
-
使用不当的时候会导致系统完全无实时性可言(可能导致系统完全偏离要求的时间需求)
-
使用得当,则会变成一种快速、高效的同步方式。 所以在使用关闭中断做为互斥访问临界区的手段时,首先必须需要保证关闭中断的时间非常短
4.2 调度器锁
4.1.1 使用函数及其作用
调度锁操作API
Void rt_enter_critical(void);/*进入临界区*/
解锁调度函数操作API
void rt_exit_critical(void);/*退出临界区*/
两函数可嵌套使用,需要同时出现,最大深度65535
4.1.2 使用场合
- 优点:轻型,不会对系统中断响应造成负担
- 缺陷:不能被用于中断于=与线程间的同步和通知,并且如果时间过长,会对系统的实时性造成影响
调度器锁住能让当前运行的任务不被换出,直到调度器解锁
对调度器上锁,系统仍然能影响外部中断,中断服务例程依然能进行相应的响应,如果唤醒了更高优先级的线程,并不会立即执行,而是在调用解锁函数时才会尝试进行下一次调度
应该考虑任务访问的临界资源是否会被中断服务例程所修改,如果可能会被修改,那么将不合适采用这种方法进行同步
4.3信号量
- 定义:轻型的用于解决线程间同步问题的内核对象,线程可以获取或者释放它(临界区钥匙)
- 工作示意图:
- 信号量对象:信号量值(信号量对象的实例数目,资源数目);线程等待队列;
4.3.1 信号量控制块
struct rt_semaphore
{
struct rt_ipc_object parent;/*继承自ipc_object类*/
rt_uint16_t value; /* 信号量的值 */
};
/* rt_sem_t是指向semaphore结构体的指针类型 */
typedef struct rt_semaphore* rt_sem_t;
rt_ipc_object用于处理进程间通信(IPC)
rt_semaphore对象从rt_ipc_object中派生,由IPC容器所管理。信号量的最大值是
65535。
4.3.2 信号量相关接口
Semaphore是一种同步工具,用于控制进程对共享资源的访问。它通常被用来实现进程间的同步和通信
创建信号量:
内核先创建信号量控制块,再对控制块进行基本的初始化工作。先分配semaphore对象,再初始化这个对象,初始化Ipc对象以及和semaphore相关部分。
函数接口:rt_sem_t rt_sem_creat(const char*name, rt_uint32_t value, rt_uint8_t flag);
参数 | 描述 | 作用 | 作用效果 |
---|---|---|---|
Name | 信号量名称 | ||
Value | 信号量初始值 | 决定容量大小 | |
Flag | 信号量标志 | 决定排队方式 | FIFO(先进先出,先进入的线程先获得等待信号量)PRIO(优先级等待,按优先级排队) |
创建成功:返回创建的信号量的指针;失败返回RT_NULL
示例:
创建动态信号量(初始为0),动态线程,
第一阶段,线程尝试以超时方式获取信号量,超超时返回的话,说明资源不可用,线程继续执行其他任务。
释放一次信号量,表示该资源已释放,其他线程可以尝试使用它,这样做可以避免线程长时间等待资源;
第二阶段,线程以永久等待方式获取信号量,只有当资源可用时才会获取成功并返回。可以确保线程在资源可用时能够及时获取并且使用它。
删除信号量:
函数接口:rt_err_t rt_sem_delete(rt_sem_t sem);
sem—rt_sem_create创建处理的信号量对象
删除时,如果有线程正在等待该信号量,那么删除操作会先唤醒该信号量上的线程,使线程返回RT_ERROR然后再释放信号量的内存资源。
初始化信号量
静态信号量对象:它的内存空间在编译时期就被编译器分配出来,放在数据段或ZI段上,此时使用信号量就不再需要rt_sem_create接口来创建它,而只需在使用前对它进行初始化即可
函数接口:rt_err_t rt_sem_init(rt_sem_t sem, const char* name,rt_uint32_t value,rt_uint8_t flag);
参数 | 描述 | 作用 | 取值(作用) |
---|---|---|---|
Sem | 信号量对象的句柄 | ||
Name | 信号量名称 | ||
Value | 信号量初始值 | 决定容量大小 | |
Flag | 信号量标志 | 决定排队方式 | FIFO(先进先出,先进入的线程先获得等待信号量) PRIO(优先级等待,按优先级排队) |
函数返回:RT_EOK
示例:这个例子中将创建一个静态信号量(初始值为0 )及一个静态线程,在这个静态线程中将试图采用超时方式去获取信号量,应该超时返回。然后这个线程释放一次信号量,并在后面继续采用永久等待方式去获取信号量, 成功获得信号量后返回。
脱离信号量
将信号量对象从内核对象管理器中移除掉,内核先唤醒挂在该信号量等待队列上的线程,然后将该信号量从内核对象管理器删除。等待线程将获得-RT_ERROR的返回值
函数接口:rtt_err_t rt_sem_detach(rt_sem_t sem);
函数返回:RT_EOK
获取信号量
线程通过获取信号量来获得信号量资源实例,信号量值大于0时,线程将获得信号量,并且相应的信号量的值都会-1.若是信号量的值=0,说明当前信号量资源实例不可用。申请该信号量的线程将根据time参数的情况选择直接返回,挂起等待一段时间,永久等待直到其他线程或者中断释放该信号量。如果在time指定的时间内依然得不到信号量,线程将会超时返回,返回值是-RT_ETIMEOUT
函数接口:
rt_err_t rt_sem_take(rt_sem_t,rt_int32_t time);
Time----指定的等待时间,单位:时钟节拍(OS Tick)
函数返回:
成功获得信号量:RT_EOK;
超时依然未获得信号量返回-RT_ETIMEOUT;
其他错误返回:-RT_ERROR
无等待获取信号量
当线程申请的信号量资源实例不可用时,他不会等待在该信号量上,而是直接返回-RT_ETIMEOUT
函数接口:
rt_err_t rt_sem_trytake(rt_sem_t sem);
函数返回
成功获取信号量:RT_EOK;
否则返回:RT_ETIMEOUT;
释放信号量
线程完成资源的访问后,释放他持有的信号量释放后,等待队列里有线程,则唤醒第一个线程,没有就信号量的值加一
函数接口:
Rt_err_t rt_sem_release(rt_sem_t sem);
函数返回:RT_EOK
动态线程与静态线程的区别
- 创建和销毁的时机:静态线程在程序开始时就已创建,一直存在,直到程序结束才销毁。而动态线程可以在程序运行的任何时刻创建和销毁
- 使用的内存:静态线程使用固定的内存空间,通常是在栈上分配。动态线程则使用动态内存分配,可以在堆上创建。此外,二者的联系主要体现在都是操作系统能够进行运算调度的最小单位,都被操作系统所管理。
4.3.3 使用场合
信号量是一种灵活的同步方式,运用在多种场合中,形成锁,同步,资源计数等关系。
线程同步
信号量用于工作完成标志
实现过程:将信号量的值初始化成具备0个信号量资源的实例。等待线程直接在信号量上等待,信号线程处理完工作后,释放信号量,唤醒等待线程,进入下一份工作。
锁(二值信号量)
应用于多线程对同一临界区的访问。信号量的值始终在1,0之间变动
使用过程:
将信号量资源实例初始化成1,线程访问临界资源时,需要先获得这个资源锁,此时信号量值变为0;
线程处理完毕时,退出临界区,释放信号量并解锁,唤醒挂在锁上的第一个等待线程;
临界资源被占用时,访问的线程被挂起在信号量上,等到获得信号量的线程处理完毕时,被唤醒,获得临界区的访问权。
中断与线程同步
应用于中断服务例程需要通知线程进行相应的数据处理
中断触发中,中断服务例程需要通知线程进行相应的数据处理,设置信号量初始值是0,线程在信号量上挂起直到信号量被释放。
中断触发时,先进行与硬件相关的工作,完成后释放信号量来唤醒相应线程以做后续数据处理。
注: 中断与线程间的互斥不能采用信号量(锁)的方式,而应采用中断锁。
资源计数
适用于线程间速度不匹配的场合。
信号量可以为前一线程工作完成计数,当调度到后一线程时,可以以一种连续的方式一次处理数个事件。
**注:**一般资源计数类型多是混合方式的线程间同步,因为对于单个的资源处理依然存
在线程的多重访问,这就需要对一个单独的资源进行访问、处理,并进行锁方式的互
斥操作。
4.4 互斥量
互斥量又叫相互排斥的信号量,是一种特殊的二值性信号量。
特性:
- 支持互斥量所有权,递归访问以及防止优先级翻转的特性
- 只有两种状态值,闭锁(被持有,此线程获得他的所有权);开锁(未被持有,此线程失去他的所有权)。
- 当一个线程持有互斥量时,其他线程不能获取该互斥量,直到原线程释放
- 确保了同一时间只有一个线程可以访问该资源,比米娜了数据竞争和不一致
静态互斥量与动态互斥量
区别 | 静态互斥量 | 动态互斥量 |
---|---|---|
分配方式 | 编译时分配,与程序生命周期相同 | 运行时动态分配,根据需要管理生命周期 |
初始化方式 | 通常为全局变量或静态变量,方便多个线程访问和操作 | 使用相关函数(如pthread_mutex_create)创建,使用相关函数(pthread_mutex_destroy)销毁 |
适用场景 | 适用于程序编译时已知需要保护的共享资源的情况 | 适用于需要根据程序运行情况动态管理的情况 |
管理方式 | 在编译时确定,无需额外的内存管理 | 需要额外的内存管理,需要注意内存释放和避免内存泄漏 |
动态互斥量在运行时动态分配,适用于需要根据程序运行情况动态管理的情况。在使用动态互斥量时,需要注意内存管理和释放资源,以避免内存泄漏和其他问题
优先级翻转问题
在信号量机制中,优先级翻转是一个常见问题:
- 当高优先级的线程试图获取已被低优先级线程持有的信号量时
- 高优先级线程可能会被挂起,导致优先级翻转
这意味着高优先级的任务可能被低优先级的任务阻塞,实时性难以保证。
RT-Thread中的优先级继承算法
当高优先级的线程A被低优先级线程C阻塞时,C的优先级会被临时提升到A的优先级。这样确保了C不会被B抢占,并允许A继续执行。
当C释放资源后,其优先级回到初始设定,从而避免了系统资源被中间优先级的线程抢占。
**注:**在获得互斥量后,应该尽快释放互斥量,并且在持有互斥量的过程中,不得再行
更改持有互斥量线程的优先级。
4.4.1 互斥量控制块
struct rt_mutex
{
struct rt_ipc_object parent; /* 继承自ipc_object类 */
rt_uint16_t value; /* 互斥量的值 */
rt_uint8_t original_priority; /* 持有线程的原始优先级 */
rt_uint8_t hold; /* 持有线程的持有次数 */
struct rt_thread *owner; /* 当前拥有互斥量的线程 */
};
/* rt_mutext_t为指向互斥量结构体的指针 */
typedef struct rt_mutex* rt_mutex_t;
rt_mutex对象从rt_ipc_object中派生,由IPC容器管理。
4.4.2互斥量关键接口
创建互斥量(动态互斥量)
先创建互斥量控制块,然后初始化。互斥量名字由name决定。创建的互斥量由于指定的flag不同,具有不同意义。
函数接口:rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag);
#define RT_IPC_FLAG_FIFO 0x00 /* IPC参数采用FIFO先进先出方式*/
#define RT_IPC_FLAG_PRIO 0x01 /* IPC参数采用优先级方式*/
函数返回
成功返回互斥量句柄
失败返回RT_NULL
删除互斥量
系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间,此时等待此互斥量的线程都被唤醒,返回值-RT_ERROR。
函数接口:rt_err_t rt_mutex_delete (rt_mutex_t mutex);
函数返回
RT_EOK
初始化互斥量(静态互斥量)
静态互斥量对象的内存是在系统编译时由编译器分配的,一般存放于数据段或者ZI段中。在使用这类静态互斥量对象前,需要对其先进行初始化。使用该函数接口时,需要指定互斥量的句柄,互斥量的名称以及互斥量的标志。
函数接口:rt_err_t rt_mutex_init (rt_mutex_t mutex,const char* name,rt_uint8_t flag);
函数返回
RT_EOK
脱离互斥量
将把互斥量对象从内核对象管理器中删除。内核先唤醒所有挂在该互斥量上的线程,返回值-RT_ERROR,然后系统将该互斥量从内核对象管理器链表删除。
函数接口:rt_err_t rt_mutex_detach (rt_mutex_t mutex);
函数返回
RT_EOK
获取互斥量
线程通过从互斥量申请服务获取互斥量的所有权。线程对互斥量的所有权是独占的。1V1
如果互斥量已经被其他线程控制,那么互斥量持有计数加1,当前线程也不会挂起等待。
如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间
函数接口:rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);
函数返回:
成功获得互斥量返回RT_EOK
超时返回-RT_ETIMEOUT
其他返回-RT_ERROR
释放互斥量
线程完成互斥资源访问后,应尽快释放他占据的互斥量使互斥量持有计数-1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。
函数接口:rt_err_t rt_mutex_release(rt_mutex_t mutex);
函数返回
RT_EOK
持续更新中…