9.生产者消费者问题模型(验证代码producer_consumer.c)
生产者消费者问题是-一个经典的、多线程同步问题。
有两个线程: 一个生产者线程和一个消费者线程。两个线程共享二个初始为空、固定大小为n的缓存区。
生产者的工作是“生产”一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复;
同时,只有缓冲区非空时,消费者才能从中取出数据,一次消费-段数据,否则必须等待,如此反复。
问题的核心是:
1.要保证不让生产者在缓存还是满的时候仍然要向内写数据;
2.不让消费者试图从空的缓存中取出数据。
生产者消费者问题本质
解决生产者消费者问题实际上是要解决线程间互斥关系问题和同步关系问题
- 由于缓冲区是临界资源,它--个时刻只允许--个生产者放入消息,或者--个消费者从中取出消息,所以这里我们需要解决--个互斥访问的问题。
- 同时生产者和消费者又是-一个相互协作的关系,只有生产者生产之后,消费者才能消费,所以我们还需要解决--个同步的问题。
二值信号量--就是要么是0要么是1;
10.互斥量的使用(验证代码mutex_sample.c)
我们来看一个生活中的例子:现在大多银行ATM机都有一个特制的铁门。需要使用ATM的用户都需要在门前排队,进入铁门使用ATM机的用户进入后
会在里面将铁门锁住,以保障自身安全,这个时候,在门外排队的用户无法使用ATM机;当之前锁住ATM铁门的用户办理完业务,打开门以后,其他在外排队的用户
才可以进入铁门使用ATM,这位进入铁门的用户也会和前一个用户一样,将门锁住,保障自身的安全。
例子中ATM机就相当于系统中的兵享资源I需要使用ATM的用户相当于系统中的线程,而铁门,就起到了互斥量的作用。
互斥量 (在斥锁)是用于线程间诉访问的IPC对象,它是-种特殊的二值性信
号量。当某个线程访问系统中的共享资源时,通过引入互斥量机制,可以保证其他
线程无法取得对此共享资源的访问权。
斥量只有两种状态: LOCKED和UNLOCKED, 分别代表加锁和开锁的两种情况。
当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去对它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它。持有该互斥量的线程也能够再次获得这个“锁“(递归持有)而不被挂起
在RT-Thread中,互斥量控制块是操作系统用于管理互斥量的一个数据结构。
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 持有互斥量的线程控制块*/
}
定义静态互斥量: struct rt_ mutex static_ _mutex
定义动态互斥量: rt_ mutex_ _t dynamic_ mutex
互斥量的操作-验证代码mutex_sample.c
1.初始化与脱离--针对静态互斥量
rt_ _err_ _t rt_ mutex_ init(rt_ _mutex_ t mutex, const char *name, rt_ _uint8_ _t flag) //flag与信号量作用一样-RT _IPC. FIAG. FIFO RT _PC. _FLAG. PRIO
rt_ err_ _trt_ mutex_ detach(rt_ mutex_ t mutex)
2.创建与删除--针对动态互斥量
rt_ mutex_ _t rt_ mutex_ _create(const char *name, rt_ uint8_ _t flag)
rt_ err_ trt_ _mutex_ delete(rt_ mutex_ t mutex)
3.获取互斥量(互斥量只属于线程,仅在线程中使用,不像信号量一样开在中断中使用)
rt_ err_ trt_ _mutex_ take(rt_ mutex_ t mutex, rt_ int32_ t time)//获得等待获取互斥量参数,如果RT _WAITING FOREVER=-1,永久行等待
4.释放互斥量(互斥量只属于线程,仅在线程中使用,不像信号量一样开在中断中使用)
rt_ err_ _trt_ mutex_ release(rt_ mutex_ _t mutex)
信号量VS互斥量 区别
1、信号量可以由任何线程(以及中断)释放,它用于同步的时候就像交通灯,线程只有在获得许可的时候才可以运行,强调的是运行步骤;
互斥量只能由持有它的线程释放)即只有“锁上”它的那个线程才有“钥匙”打开它。它用于互斥的时候就像一把钥匙,只囿获得钥匙的线程才可以运行,强调的是许可和权限。
2、使用信号量可能导致线程优先级反转,而互斥量可通过优先级继承的方法解决优先级反转问题
11.优先级翻转 (代码分析-priority. inversion.c)
使用信号量会导致的另一个潜在问题是线程优先级翻转问题。所谓线程优先级翻转,即当一个高优先级线程试图通过某种互斥IPC对象机制访问共享资源时,
如果该IPC对象已被-~低优先级的线程所持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较
低优先级的线程阻塞的情况。优先级翻转会造成高优先级线程的实时性得不到保证。
优先级继承--(代码分析-priority. inversion.c)
在RT-Thread中,通过互斥量的优先级继承算法,可以有效的解决优先级翻转问题。
-一
优先级继承是指提高某个占有某种共享资源的低优先级线程的优先级,使之与
所有等待该资源的线程中优先级最高的那个线程的优先级相等,从而得到更快的执
行然后释放共享资源,而当这个低优先级线程释放该资源时,优先级重新回到初
始设定值。
继承优先级的线程避免了系统共享资源被任何中间优先级的线程抢占。
优先级翻转现象提醒编程人员对共享资源进行互斥访问的代码段应尽量短!
12事件集的使用 (代码event_sample.c)
例说事件集
以坐公交车为例说明事件集,在公交站等公交车时可能有以下几种情况:
①P1坐公交车去某地,只有一趟公交车可以到达目的地,等到此公交车即可出发。
②P1坐公交车去某地,有3趟公交车都可以到达目的地,等到其中任意一辆即可出发。
③P1约另--人P2--起去某地,则P1必须要等到“同伴P2到达公交站”与“公交车到达公交站”两个条件都满足后,才能出发。
这里,可以将P1去某地的这个行为视为线程,将“到达目的地的公交车到达公交站”、“同伴P2到达公交站”视为事件的发生
情况①是特定事件唤醒线程; 情况②是任意单个事件唤醒线程; 情况③是多个事件同时发生才能唤醒线程。
事件集工作机制
信号量主要用于“一对一”的线程同步;当需要.
“一对多”、
“多对一”
“多对多”的同步时,就需要事件集来处理了。
RT-Thread中的事件集用-一个32位无符号整型变量来表示,变量中的- -个位代
表一个事件,线程通过“逻辑与”或“逻辑或”与一个或多个事件建立关联形成-一个
事件组合。
事件的“逻辑或”也称为是独立型同步,指的是线程与任何事件之一发生同步,
只要有一个事件发生,即满足条件;
事件的“逻辑与”也称为是关联型同步,指的是线程与若干事件都发生同步,
只有这些事件全部发生,才满足条件。
事件集控制块
在RT-Thread中,事件集控制块是操作系统用于管理事件的一个数据结构。
struct rt_ event {
struct rt_ ipc_ object parent; /**< inherit from ipc_ object */
rt uint32 t set; /**< event set 每一位都表示一个时间,0表示无时间发生*/
};
typedef struct rt_ event *rt_ event_ _t; 结构体指针--定义动态时间集
定义静态事件集: structrt_ event static_ evt
定义动态事件集: rt_event_t dynamic_ evt
事件集的操作 (代码event_sample.c)
1. 初始化与脱离
rt_ err_ trt_ _event_ _init(rt_ _event_ t event, const char *name, rt_ _uint8_ .t flag) // RT. IPC. FLAG_FIFO时间 RT IPC_ FLAG_ PRIO优先级
rt_ err_ _t rt_ event_ _detach(rt_ event_ _t event)
2.创建与删除
rt_ event_ _t rt_ _event_ _create(const char *name, rt_ uint8_ t flag)
rt_ err_ _trt_ _event_ _delete(rt_ event_ _t event)
3.发送事件(线程或中断服务中都可以发送事件)
rt_ err_ _trt_ event_ _send(rt_ _event_ t event, rt_ uint32_ t set) //set=1,第一个事件 =8第三个事件
4.接收事件
//set=1,第一个事件 =8第三个事件; option = RT_ _EVENT_ _FLAG_ _OR 独立性 /RT_ _EVENT_ FLAG_ AND关联性 / RT_ EVENT_ FLAG_CLEAR唤醒是否自动清0
rt_ _err_ _trt_ _event_ _recv(rt_ event_ _t event, rt_ uint32_ _t set, rt_ _uint8_ t option,rt_ int32_t timeout,rt_ uint32_ _t *recved) //timeout等待时间
13.邮箱的使用mailbox sample.c
邮箱工作机制
RT-Thread操作系统的邮箱用子线程间通信,特点是开销比较低,效率较高。邮箱中的每--封邮件只能容纳固定的4字节丙容(针对32位处理系统,指针的大小即为
4个字节,所以一封邮件恰好能够容纳- -个指针)。
线程或中断服务例程把一封4字节长度的邮件发送到邮箱中,而其他需要的线程可以从邮箱中接收这些邮件并进行处理。
邮箱控制块
在RT-Thread中,邮箱控制块是操作系统用于管理邮箱的一个数据结构。
struct rt_ mailbox{
struct rt. ipc_ _object parent; /**< inherit from ipc_ object */
rt_ _uint32_ t_ *msg_ _pool; /**< start address of message buffer 消息缓冲区*/
rt_ uint16 tsize; /**< 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 static_ mb
定义动态邮箱: rt_ _mailbox_ _t dynamic_ _mb
邮箱的操作
初始化与脱离--静态
rt_ err_ trt mb_ init(rtmailbox_ t mb/const char *name, void *msgpool,rt_ size_ t size,rt uint8_ t flag)//msgpool缓冲区地址,size表示缓冲区可以存放邮件数量
rt_ err. _trt_ mb_ detach(rt_ mailbox_ _t mb) //flag RT_ _IPC. FLAG_ FIFO ,RT_ _IPC_ _FLAG_ PRIO
创建与删除 -动态
rt_ mailbox_ t rt_mb_ create(const char *name, rt_ size_ t size, rt_ uint8_t flag)//动态只需要指定大小size,和名字,已经方式
rt_ err. _trt_ mb_ delete(rt_ mailbox_ t mb)
发送邮件
rt_ err_ trt_ _mb_ send(rt_ mailbox_ t mb, rt_uint32_t value) //value消息方式四个字节--可以是具体的四个字节内容或者某一个数组或串的四个字节地址
rt_ err_ trt_ _mb_ send_ wait(rt_ mailbox_tmb, rt_uint32_t value, rt_int32_t timeout) //timeout当前满则等,过等待时间则返回错误 ----不可以在中断中使用--等待会造成阻塞
接收邮件
rt_ err_ trt_ _mb_ recv(rt_ mailbox_ _t mb, rt_ uint32_ _t *value, rt_ int32_ _t timeout) //timeout是邮箱为空的时候会等待;超过timeout就返回
14.消息队列工作机制(代码msgq sample.c)
消息队列是RT-Thread中另- -种常用的线程间通信方式,消息队列是对邮箱的扩展。
消息队列能够接收来自线程或中断服务例程中发出的不固定长度的消息,并把消息缓存在自己的内存空间中,而其他线程能够从消息队列中读取相应的消息并进行对应的处理。
消息队列控制块
在RT-Thread中,消息队列控制块是操作系统用于管理消息队列的一个数据结构。
struct rt_messagequeue{
struct rt_ ipc_ _object parent;/**< inherit from ipc_ object 继承*/
void *msg_ pool;/**< start address of message queue 开始地址*/
rt_ uint16_ t msg_ size; /**< message size of each message 每个消息队列大小 最小是4字节对齐 */
rt_ _uint16_ t max_ msgs; /**< max number of messages 消息队列的最大数目*/
rt_ uint16_ t entry; /**< index of messages in the queue 消息对垒当前最大idx*/
void *msg_ queue_ head; /**< list head 队列头指针*/
void *msg_ queue_ tail; /**< list tail 队列尾指针*/
void *msg_ queue_ free; /**< pointer indicated the free node of queue */
};
typedef struct rt_messagequeue *rt_ mq_t;
定义静态消息队列: struct rt_ _messagequeue static_ _mq
定义动态消息队列: rt_ _mq_ _t dynamic_ mq
消息队列的操作(代码分析见msgq_sample.c)
初始化与脱离--静态
rt_ err_ _trt_ mq_ _init(rt_ mq_ _t mq, const char *name, void *msgpool,rt_ size_ _t msg_ size, rt_ _size_ _t pool_ _size,rt_ uint8_ _t flag)//块,名称,每个消息长度4/8(如果给1默认为4);内存池大小,flag等待线程的处理方式RT_ IPC_ _FLAG_ _FIFO;RT_ IPC_ FLAG_ PRIO
rt_ _err_t rt _mq_ detach(rt_ _mq_ _t mq)
创建与删除--动态
rt_ mq_ _t rt_ _mq_ _create(const char *name, rt_ size_t msg_ size, rt_ _size_t max_ msgs,rt_ uint8_t flag) //名称,大小,最大规模和标志
rt_ err_ trt_mq_delete(rt_mq_t mq)
发送消息
rt_ err. _trt_ _mq_ send(rt_ mq_ _t mq, void *buffer, rt_ _size_ _t size) //普通消息发送,消息队列,队列首地址,和消息长度
rt_ err_ trt_ mq_ urgent(rt_mq_t mq, void *buffer,rt_ size_ t size) //紧急消息,消息会插队到队列队头
接收消息
rt_ err_ _trt_ _mq_ recv(rt_ mq_ _t mq, void *buffer, rt_ size_ t size, rt int32_ t timeout)//指定消息队列,队列首地址,和消息长度,等待超时时间
15.软件定时器的使用 (代码分析见 timer sample.c)
芯片一般是硬件定时器
软件定时器介绍
软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上(系统滴答定时器)。软件定时器使系统能够提供不受数目限制的定时器服务。RT-Thread操作系统提供的软件定时器,以系统节拍(OS Tick)的时间长度为定时单位,提供了基于系统节拍整数倍的定时能力,即定时数值是OSTick的整数倍。
例如一个OSTick是10ms,那么上层软件定时器只能提供10ms,20ms,100ms等时间精度的定时服务,而不能定时为15ms、25ms、 35ms等。
当软件定时器所设定的定时时间到了后,会调用用户设置的定时器timeout回调函数,用户需要定时运行的程序会在回调函数中得到处理。
定时器两种工作模式
➢HARDTIMER模式
HARDTIMER模式的定时器超时函数在中断上下文环境中执行,此模式在定时器初始化时指定。在中断上下文环境中执行时,对于超时函数的要求与中断服务例程的
要求相同:执行时间应该尽量短,执行时不应导致当前上下文挂起。HARD_ _TIMER 模式是RT-Thread软件定时器的默认方式。
➢SOFTTIMER模式
创建/初始化
SOFTTIMER模式的定时器超时函数在系统的timer线程的线程上下文中执行。通过宏定义RT_ _USING_ _TIMER_ SOFT 来决定是否启用该超时函数模式。当启用SOFTTIMER 模式后,我们可以在定时器初始化时指定定时器工作在SOFTTIMER模式。
软件定时器控制块
在RT-Thread中,软件定时器控制块是操作系统用于管理软件定时器的--个数据
结构。
struct rt_ _timer)
{
struct rt_ _object parent; /**< inherit from rt_ object */
rt_ list_t row[RT_ TIMER_ SKIP_ _LIST_ LEVEL]; //链表结点
void (* timeout_ func)(void *parameter); /**< timeout function 超时函数*/
void *parameter; /**< timeout function's parameter 超时函数输入参数,不给为0*/
rt_ _tick_t init_tick; /**< timer timeout tick */
rt_tick_t timeout_ tick; /**< timeout tick */
};
typedef struct rt_ timer *rt_ _timer_ _t
定义静态软件定时器: structrt_ _timer static_ timer
定义动态软件定时器: rt_ _timer_ _t dynamic_ _timer
软件定时器的操作 timer sample.c
初始化与脱离
void rt_timer_ init(rt_timer_t timer, const char * name, void (* timeout)(void * parameter), void * parameter,rt_ tick_ _t time,rt_ _uint8_ _t flag)
控制块,名字,超时回调函数参数,软件定时器超时时间,flag超时调用方式eg:RT_TIMER_FLAG_ONE_SHOT|RT_TIMER_ FLAG_ SOFT_ TIMER
RT_TIMER_FLAG_ONE_SHOT超时只调用一次 / RT. _TIMER_ FLAG_ PERIODIC 超时之后周期性调用回调函数
RT_TIMER_FLAG_HARD_TIMER 软件件定时器,RT_TIMER_ FLAG_ SOFT_ TIMER软件定时器
rt_ err. _t rt_ _timer_ _detach(rt_ _timer_ _t timer)
创建与删除
rt_ timer_ _t rt_ timer_ _create(const char *name, void (*timeout)(void * parameter), void *parameter,rt_ _tick_ _t time, rt_ _uint8_ _t flag) //同静态
rt_ err_ _trt_ _timer_ _delete(rt_ _timer_ t timer)
启动定时器
rt_ err_ _t rt_ _timer_ _start(rt_ timer_ _t timer)
停止定时器
rt_ err_ _trt_ _timer_ _stop(rt_ _timer_ _t timer)
16内存池介绍
动态内存堆可以分配任意大小的内存块,非常灵活和方便。但其存在明显的缺点:一是分配效率不高,在每次分配时,都要进行空闲内存块查找;二是容易产生内存碎片。
为了提高内存分配的效率,并且避免内存碎片,RT-Thread 提供了另外一种内存管理方法:内存池( Memory Pool)
内存池是- -种内存分配方式,用于分配大量大小相同的小内存块。使用内存池可以极大地加快内存分配与释放的速度,且能尽量避免内存碎片化。
RT-Thread的内存池支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的线程唤醒。基于这个特点内存池非常适合需要通过内存资源进行同步的场景。
16.内存池工作机制 代码分析见memp sample.c
内存池在创建时先从系统中获取一大块内存 (静态或动态),然后分成相同大小的多个小内存块,这些小内存块通过链表连接起来(此链表也称为空闲链表)。线程每次申请分配内存块的时候,系统从空闲链表中取出链头上第-一个内存块,提供给申请者。
内存池控制块
在RT-Thread中,内存池控制块是操作系统用于管理内存池的-一个数据结构。
struct rt_mempool{
struct rt object parent; /**< inherit from rt_ object */
void *start_ address; /**< memory pool start 内存池開始地址*/
rt_ _size_ _t_ size; / /**< size of memory pool */
rt_ _size_ _t block_ size; /**< size of memory blocks */
rt_ uint8_ _t *block_ list; /**< memory blocks list */
rt_ size_ _t block_ total_ count; /**< numbers of memory block */
rt_ _size_ _t block_ free_ count; /**< numbers of free memory block 空内存池的數目*/
rt_ list_ _t suspend_ _thread; /**< threads pended on this resource */
rt_ _size_ _t suspend_ _thread_ count; /**< num of thread pended on this resource 内存池挂上的數目*/
};
typedef struct rt_ mempool *rt_ mp_ _t;
定义静态内存池: struct rt_ mempool static_ mp;
定义动态内存池: rt_ mp_ t dynamic_ mp;
内存池的操作 代码分析见memp sample.c
初始化与脱离
rt_ err_ _trt_ _mp_ init(struct rt_ mempool *mp, const char *name, void *start, rt_ size_t size, rt_ _size_t block_size)
//控制块,名称,起始地址,单个大小,总大小
rt_ _err_ _trt_ _mp_ _detach(struct rt_ mempool *mp)
创建与删除
rt_mp_t rt_mp_create(const char *name, rt_ _size_t block_ count, rt _size_t block_size)
rt_ _err_t rt_ _mp_ _delete(rt_ mp_ _t mp)
电请内存块
void *rt_mp_ alloc(rt_mp_t mp, rt. _int32_t time) //申请可用的内存块,time=0直接返回,>0持续时间,<0一直申请
释放内存块
void rt_ mp_ free(void *block) 指定内存卡的地址