|
【RT-Thread读书笔记】11. RT-Thread 学习18-20章读后感
[复制链接]
本帖最后由 传媒学子 于 2019-5-11 22:29 编辑
第18章 消息队列
在学习这章之前,建议先复习一下队列的知识。
队列又称消息队列,是一种常用于线程间通信的数据结构,队列可以在线程与线程间、中断和线程间传送信息,实现了线程接收来自其他线程或中断的不固定长度的消息,并根据不同的接口选择传递消息是否存放在线程自己的空间。
通过消息队列服务,线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常是将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。同时RT-Thread中的消息队列支持优先级,也就是说在所有等待消息的线程中优先级最高的会先获得消息。
消息队列的运作机制:
创建消息队列时先创建一个消息队列对象控制块,然后给消息队列分配一块内存空间,组织成空闲消息链表,这块内存的大小等于[消息大小+消息头(用于链表连接)]与消息队列容量的乘积,接着再初始化消息队列,此时消息队列为空。
RT-Thread操作系统的消息队列对象由多个元素组成,当消息队列被创建时,它就被分配了消息队列控制块:消息队列名称、内存缓冲区、消息大小以及队列长度等。
发送消息并不带有阻塞机制;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。
接收消息有阻塞机制,线程在没有获取到自己的消息时,可以一直等待,直到获取相关消息。
运作过程:
消息队列控制块:
- /**
- * message queue structure
- */
- 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 */
- rt_uint16_t max_msgs; /**< max number of messages */
- rt_uint16_t entry; /**< index of messages in the queue */
- 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;
复制代码
消息队列可以应用于发送不定长消息的场合,包括线程与线程间的消息交换,以及在中断服务函数中给线程发送消息(中断服务例程不可能接收消息)。
常用消息队列的函数:
创建消息队列rt_mq_create。
写队列操作函数rt_mq_send。
读队列操作函数rt_mq_recv。
删除队列rt_mq_delete。
第19章 信号量
信号量(Semaphore)是一种实现线程间通信的机制,实现线程之间同步或临界资源的互斥访问,常用于协助一组相互竞争的线程来访问临界资源。
在操作系统中,我们使用信号量的目的是为了给临界资源建立一个标志,信号量表示了该临界资源被占用情况。这样,当一个线程在访问临界资源的时候,就会先对这个资源信息进行查询,从而在了解资源被占用的情况之后,再做处理,从而使得临界资源得到有效的保护。
信号量还有计数型信号量,计数型信号量允许多个线程对其进行操作,但限制了线程的数量。
在嵌入式操作系统中二值信号量是线程间、线程与中断间同步的重要手段。 用1和0来代表该信号量是否可以被可用。因为信号量资源被获取了,信号量值就是 0,信号量资源被释放,信号量值就是 1。
例如:某个线程需要等待一个标记,那么线程可以在轮询中查询这个标记有没有被置位,这样子做,就会很消耗CPU资源,其实根本不需要在轮询中查询这个标记,只需要使用二值信号量即可,当二值信号量没有的时候,线程进入阻塞态等待二值信号量到来即可,当得到了这个信号量(标记)之后,在进行线程的处理即可,这样子么就不会消耗太多资源了,而且实时响应也是最快的。
二值信号量在线程与线程中同步的应用场景:假设我们有一个温湿度的传感器,假设是1s采集一次数据,那么我们让他在液晶屏中显示数据出来,这个周期也是要1s一次的,如果液晶屏刷新的周期是100ms更新一次,那么此时的温湿度的数据还没更新,液晶屏根本无需刷新,只需要在1s后温湿度数据更新的时候刷新即可,否则CPU就是白白做了多次的无效数据更新,CPU的资源就被刷新数据这个线程占用了大半,造成CPU资源浪费,如果液晶屏刷新的周期是10s更新一次,那么温湿度的数据都变化了10次,液晶屏才来更新数据,那拿这个产品有啥用,根本就是不准确的,所以,还是需要同步协调工作,在温湿度采集完毕之后,进行液晶屏数据的刷新,这样子,才是最准确的,并且不会浪费CPU的资源。
同理,二值信号量在线程与中断同步的应用场景:我们在串口接收中,我们不知道啥时候有数据发送过来,有一个线程是做接收这些数据处理,总不能在线程中每时每刻都在线程查询有没有数据到来,那样会浪费CPU资源,所以在这种情况下使用二值信号量是很好的办法,当没有数据到来的时候,线程就进入阻塞态,不参与线程的调度,等到数据到来了,释放一个二值信号量,线程就立即从阻塞态中解除,进入就绪态,然后运行的时候处理数据,这样子系统的资源就会很好的被利用起来。
二值信号量的运作机制
创建二值信号量,为创建的信号量对象分配内存,并把可用信号量初始化为用户自定义的个数, 二值信号量的最大可用信号量个数为1。
信号量获取,从创建的信号量资源中获取一个信号量,获取成功返回正确。否则线程会等待其它线程释放该信号量,超时时间由用户设定。当线程获取信号量失败时,线程将进入阻塞态,系统将线程挂到该信号量的阻塞列表中。假如某个时间中断/线程释放了信号量,那么,由于获取无效信号量而进入阻塞态的线程将获得信号量并且恢复为就绪态。
计数型信号量的运作机制
计数型信号量与二值信号量其实都是差不多的,一样用于资源保护,不过计数信号量则允许多个线程获取信号量访问共享资源,但会限制线程的最大数目。访问的线程数达到信号量可支持的最大数目时,会阻塞其他试图获取该信号量的线程,直到有线程释放了信号量。
信号量控制块:
- /**
- * Semaphore structure
- */
- 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;
复制代码
常用的信号量函数有:rt_sem_create(), rt_sem_delete(), rt_sem_release(), rt_sem_take(),
第20章 互斥量
为了避免递归获取信号量时发生主动挂起引起死锁,在二值信号量中又规定了一种特殊的信号量,成为互斥信号量。它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。任意时刻互斥量的状态只有两种,开锁或闭锁。当互斥量被线程持有时,该互斥量处于闭锁状态,这个线程获得互斥量的所有权。当该线程释放这个互斥量时,该互斥量处于开锁状态,线程失去该互斥量的所有权。当一个线程持有互斥量时,其他线程将不能再对该互斥量进行开锁或持有。
优先级翻转是当一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,因此造成高优先级任务被许多具有较低优先级任务阻塞,实时性难以得到保证。低优先级占有信号量,但又无法及时得到执行,造成高优先级任务阻塞,因此必须想办法让低优先级任务执行。
在RT-Thread操作系统中为了降低优先级翻转问题利用了优先级继承算法。优先级继承算法是指,暂时提高某个占有某种资源的低优先级线程的优先级,使之与在所有等待该资源的线程中优先级最高那个线程的优先级相等,而当这个低优先级线程执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。
互斥量与二值信号量最大的不同是:互斥量具有优先级继承机制,而信号量没有。也就是说,某个临界资源受到一个互斥量保护,如果这个资源正在被一个低优先级线程使用,那么此时的互斥量是闭锁状态,也代表了没有线程能申请到这个互斥量,如果此时一个高优先级线程想要对这个资源进行访问,去申请这个互斥量,那么高优先级线程会因为申请不到互斥量而进入阻塞态,那么系统会将现在持有该互斥量的线程的优先级临时提升到与高优先级线程的优先级相同,这个优先级提升的过程叫做优先级继承。这个优先级继承机制确保高优先级线程进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”危害降低到最小。
需要注意的是互斥量不能在中断服务函数中使用。
互斥量控制块:
- /**
- * Mutual exclusion (mutex) structure
- */
- 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;
复制代码
相关接口函数: rt_mutex_create(), rt_mutex_delete(), rt_mutex_release(), rt_mutex_take().
使用互斥量时候需要注意几点:
1. 两个线程不能对同时持有同一个互斥量。如果某线程对已被持有的互斥量进行获取,则该线程会被挂起,直到持有该互斥量的线程将互斥量释放成功,其他线程才能申请这个互斥量。
2. 互斥量不能在中断服务程序中使用。
3. RT-Thread作为实时操作系统需要保证线程调度的实时性,尽量避免线程的长时间阻塞,因此在获得互斥量之后,应该尽快释放互斥量。
4. 持有互斥量的过程中,不得再调用rt_thread_control()等函数接口更改持有互斥量线程的优先级。
此内容由EEWORLD论坛网友传媒学子原创,如需转载或用于商业用途需征得作者同意并注明出处
|
|