rt_thread中的任务间同步及通信

学习目标:任务间同步及通信


学习内容:

在多任务的实时系统中,一项工作的完成往往需要多个任务协调的方式共同完成。

对于操作/访问同一块区域,称之为临界区。任务的同步方式有很多种,但其核心思想都是在访问临界区时只允许一个或一类任务运行。

关闭中断:

关闭和打开中断接口由两个函数完成:

rt_base_t  rt_hw_interrupt_disable(void);

rt_base_t   rt_hw_interrupt_enable(rt_base_t level);  level  可以记录前一次的中断状态

 

调度器锁:

void rt_enter_critical(void);/*进入临界区

使用上述函数后,调度器将被上锁,不在进行其他的调度任务,直道退出。但在此期间仍然会响应中断,如果通过中断唤醒了更高优先级的线程,调度器因为被锁住,所以也不会立即执行,直至解锁。

void  rt_exit_critical(void)  解锁与退出临界段

调度器锁能够方便地使用于一些线程与线程间同步的场合,由于轻型,它不会对系统中
断响应造成负担;但它的缺陷也很明显,就是它不能被用于中断与线程间的同步或通知,并
且如果执行调度器锁的时间过长,会对系统的实时性造成影响(因为使用了调度器锁后,系
统将不再具备优先级的关系,直到它脱离了调度器锁的状态)。

信号量:

信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或者释放它,从而达到同步或者互斥的目的。信号量就像一把钥匙,将一段临界段锁住,只允许有钥匙的线程进行访问。

每个信号量对象都有一个信号量值和一个线程线程等待队列,信号量的值对应了信号量对象的实例数目,资源数目

当信号量实例数目为0时,再申请该信号量的线程会被挂起在该信号量的等待队列中,等待可用的信号量。

信号量控制块:

struct rt_semaphore

{

  struct rt_ipc_object  parent;/继承自ipc_object(容器对象)的类

  rt_uint16_t value; //信号量的值

}

typedef struct rt_semaphore* rt_sem_t;

信号量相关的接口函数:

rt_sem_t rt_sem_create(const char * name,rt_uint32_t value,rt_uint8_t flag);

信号量的使用场合:

1.线程间的同步

2.锁

3.线程与中断之间的同步

互斥量

互斥量的概念为:二值性信号量,它支持互斥量间的所有权,递归访问以及防止优先级翻转

互斥量只有两种状态:开锁和闭锁。闭锁时线程获得他的所有权。开锁时失去所有权

持有该互斥量的线程可以再次获得这个锁而不被挂起,从而可以完成递归的调用。

优先级翻转问题的一个栗子:

优先级继承算法:

互斥量控制块:

struct rt_mutex

{

 struct rt_ipc_object parent;

rt_uint16_t     value;

rt_uint8_t       original_priority;//原始持有的优先级

rt_uint8_t       hold;  持有线程的持有次数

struct  rt_thread  *owner;    当前拥有互斥量的线程;

}

typedef struct rt_mutex* rt_mutex_t;

rt_mutex对象从rt_ipc_object中派生出来,由ipc容器管理。

使用场合:

互斥量的使用场景比较单一,因为他是信号量的一种,并且是以锁的形式存在。在初始化

的时候,互斥量永远都处于开锁的状态,而被线程持有时则处于闭锁的状态。互斥量更适合于

以下的场景:

1.线程多次持有互斥量的情况下。这样可以避免同一线程的多次递归造成的死锁问题。

2。隐藏的优先级翻转问题

3.互斥量不可以在中断服务例程中使用。

事件:

事件主要用于线程间的同步,与信号量不同,事件可以实现1对多,多对多的同步,即同一个线程可等待多个事件的触发,同样一个

事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个32位无符号整型变量来表示。每一位代表一个事件,线程可以

通过逻辑上的或与来和事件建立联系。形成一个事件集。

事件的特点:

• 事件只与线程相关,事件间相互独立:每个线程拥有32个事件标志,采用一个32 bit
无符号整型数进行记录,每一个bit代表一个事件。若干个事件构成一个事件集;
• 事件仅用于同步,不提供数据传输功能;
• 事件无排队性,即多次向线程发送同一事件(如果线程还未来得及读走),其效果等同
于只发送一次。

在rt_thread中,每个线程都有一个事件信息标记,它有三个属性分别是:

1.RT_EVENT_FLAG_AND(与)

2.RT_EVENT_FLAG_OR(或)

3.RT_EVENT_FLAG_CLEAR(清除标记)

在等待事件同步时,可以通过32个事件标志和事件信息来判断

当前接收的事件是否满足同步条件。

事件控制块:

struct rt_event

{

struct rt_ipc_object parent;

rt_uint32_t  set;//事件集合

}

typedef struct rt_event* rt_event;

rt_event对象从rt_ipc_object中派生

使用场合:

事件可以使用在多种场合,在一定程度上可以替代信号量,用于线程间的同步。一个线程

或者中断服务历程发送一个事件给事件对象,从而进行线程的唤醒处理。

但是它与信号量不同之处在于:

事件的的发送操作在事件未清除前是不可累计的,而信号量的释放动作是可以累计的。

事件可以实现多对一或者一对多,并按照逻辑与或条件来完成对于线程的触发或者事件的操作,但信号量只能识别

单一的释放操作。

邮箱:

邮箱服务是一种实时操作系统中典型的任务通讯方法,其特点是开销较低,效率较高。

邮箱中每个邮件都只能容纳固定的4字节内容(针对32位的处理系统),指针的大小即为4个字节,

所以每一封邮件只能容纳固定的4字节内容。邮箱也可以称其为交换消息。

某一个线程或者中断服务历程把一封长度为4字节的邮件发送到邮箱之中,从而一个或多个线程可以从该邮箱中获取信息。

RT-Thread操作系统的邮箱中可存放固定条数的邮件,邮箱容量在创建/初始化邮箱时设
定,每个邮件大小为4字节。当需要在线程间传递比较大的消息时,可以把指向一个缓冲区
的指针作为邮件发送到邮箱中

邮箱控制块:

struct rt_mailbox

{

struct rt_ipc_object  parent;

rt_uint32_t *  msg_pool;    //邮箱缓冲区的开始地址

rt_uint16_t    size;           //邮箱缓冲区大小

rt_uint16_t    entry;       //邮箱中的邮件的数目

rt_uint16_t in_offset,out_offset;   //邮箱缓冲的进出指针

rt_list_t suspend_sender_thread;   //发送线程的挂起等待队列

}

typedef  struct rt_maibox* rt_mailbox_t;

rt_mailbox对象从rt_ipc_object中派生,由IPC容器管理。

使用场合:

邮箱是一种简单的线程间消息传递方式,在RT-Thread操作系统的实现中能够一次传递
4字节邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数(邮件数由创建、初
始化邮箱时指定的容量决定)。邮箱中一封邮件的最大长度是4字节,所以邮箱能够用于不超
过4字节的消息传递,当传送的消息长度大于这个数目时就不能再采用邮箱的方式。最重要
的是,在32位系统上4字节的内容恰好适合放置一个指针,所以邮箱也适合那种仅传递指针
的情况,例如:

struct msg

{rt_uint8_t *data_ptr;

rt_uint32_r data_size;

};

对于这样一个消息结构体,其中包含了指向数据的指针data_ptr和数据块长度的变量data_size.

当一个线程需要把这个消息发送给另外一个线程时,所采用的操作为:

struct  msg* msg_ptr;

msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg));  、 、//开辟出动态内存

msg_ptr ->data_ptr = 指向相应的数据块地址;

msg_ptr->data_size = len;    //数据块长度

/发送这个消息指针给mb邮箱

rt_mb_send(mb,(rt_uint32_t)msg_ptr);

而在接收线程中,因为接收过来的是指针,而msg_ptr是一个新分配出来的内存块,所以

在接收线程处理完毕后,需要释放相应的内存块。

if (rt_mb_recv(mb, (rt_uint32_t*)&msg_ptr) == RT_EOK)
{
/* 在接收线程处理完毕后,需要释放相应的内存块*/
rt_free(msg_ptr);
}

 

消息队列:

消息队列是另一种常用的线程间通讯方式,它能够接收来自线程或中断服务例程中不固
定长度的消息,并把消息缓存在自己的内存空间中。

通过消息队列服务,线程或中断服务例程可以将一
条或多条消息放入消息队列中。同样,一个或多个线程可以从消息队列中获得消息。当有多
个消息发送到消息队列时,通常应将先进入消息队列的消息先传给线程,也就是说,线程先
得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。

在rt_thread中,一个消息队列是由多个元素组成的,当消息队列被创建时,他就被分配了消息队列控制块,

由以下几部分组成:消息队列名称,内存缓冲区,消息大小及队列长度等同时每
个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息;消息队列中的第一
个和最后一个消息框被分别称为消息链表头和消息链表尾,对应于消息队列控制块中的
msg_queue_head和msg_queue_tail;有些消息框可能是空的,它们通过msg_queue_free形
成一个空闲消息框链表。所有消息队列中的消息框总数即是消息队列的长度,这个长度可在
消息队列创建时指定。

消息队列控制块

struct rt_messagequeue

{

struct rt_ipc_object parent;

void* msg_pool;  //存放消息的消息池的开始地址

rt_uint16_t   mag_size;  //消息的长度

rt_uint16_t    max_msgs;  //最大容纳的消息数

rt_uint16_t   entry;//队列中已有的消息数

void* msg_queue_head;  //消息链表头

void* msg_queue_tail;   //消息链表尾

void*msg_queue_free;  //空闲消息链表

}

typedef struct rt_message* rt_mq_t;

使用场合:

消息队列可以应用于发送不定长消息的场合,包括线程与线程间的消息交换,以及中断
服务例程中发送给线程的消息(中断服务例程不可能接收消息)。

消息队列和邮箱的明显不同是消息的长度并不限定在4个字节以内,另外消息队列也包
括了一个发送紧急消息的函数接口。但是当创建的是一个所有消息的最大长度是4字节的消
息队列时,消息队列对象将蜕化成邮箱。这个不限定长度的消息,也及时的反应到了代码
编写的场合上,同样是类似邮箱的代码:

struct msg
{
rt_uint8_t *data_ptr; /* 数据块首地址*/
rt_uint32_t data_size; /* 数据块大小*/
};

和邮箱例子相同的消息结构定义,假设依然需要发送这么一个消息给接收线程。在邮箱
例子中,这个结构只能够发送指向这个结构的指针(在函数指针被发送过去后,接收线程能
够正确的访问指向这个地址的内容,通常这块数据需要留给接收线程来释放)。而使用消息
队列的方式则大不相同

void send_op(void *data, rt_size_t length)
{
struct msg msg_ptr;
msg_ptr.data_ptr = data; /* 指向相应的数据块地址*/
msg_ptr.data_size = length; /* 数据块的长度*/
/* 发送这个消息指针给mq消息队列*/
rt_mq_send(mq, (void*)&msg_ptr, sizeof(struct msg));
}

注意,上面的代码中,是把一个局部变量的数据内容发送到了消息队列中。在接收线程
中,同样也采用局部变量进行消息接收的结构体:

void message_handler()
{
struct msg msg_ptr; /* 用于放置消息的局部变量*/
/* 从消息队列中接收消息到msg_ptr中*/
if (rt_mq_recv(mq, (void*)&msg_ptr, sizeof(struct msg)) == RT_EOK)
{
/* 成功接收到消息,进行相应的数据处理*/
}
}

因为消息队列是直接的数据内容复制,所以在上面的例子中,都采用了局部变量的方式
保存消息结构体,这样也就免去动态内存分配的烦恼了(也就不用担心,接收线程在接收到
消息时,消息内存空间已经被释放

同步消息:

在一般的系统设计中会经常遇到要发送同步消息的问题,这个时候就可以根据当时的状
态选择相应的实现:两个线程间可以采用[消息队列+信号量或邮箱]的形式实现。发送线程
通过消息发送的形式发送相应的消息给消息队列,发送完毕后希望获得接收线程的收到确
认,工作示意图如图同步消息发送所示:

struct msg
{
/* 消息结构其他成员*/
struct rt_mailbox ack;

};

struct msg
{
/* 消息结构其他成员*/
struct rt_semaphore ack;
};

第一种类型的消息使用了邮箱来作为确认标志,而第二种类型的消息采用了信号量来作
为确认标志。邮箱做为确认标志,代表着接收线程能够通知一些状态值给发送线程;而信号
量作为确认标志只能够单一的通知发送线程,消息已经确认接收。

 


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值