MQX提供了轻量级信号量(LWSems )、信号量和互斥功能。
你可以使用两种信号量实现任务同步与互斥操作。任务等待信号量,如果信号量为0 ,则 MQX阻塞该任务;否则,MQX降低信号量,并给该任务一信号量,该任务继续运行。如果带有该信号量的任务结束运行时,则它会传递信号量;任务保持就绪状态。如果任务正在等待信号量,MQX将该任务置入就绪队列;否则,MQX增加信号量。
你可以使用互斥实现互斥操作。互斥有时也被称为二进制信号量,因为它的计数仅能是0 或1 。
主要介绍信号量
信号量可以用于同步任务和互斥操作。任务关于信号量的主要操作包括等待信号量和传递信号量。
声明:为了在目标平台上优化代码和数据存储器需求,信号量组件默认地不在MQX内核中编译。为了测试这一
特征,你首先需要在MQX用户配置文件中启用并重新编译MQX PSP、BSP 和其它核心组件。
1 信号量使用纵览
为了使用信号量,任务需要执行如下步骤:
(1)创建信号量组件(可选)
(2) 生成信号量
(3)打开与信号量的连接
(4)如果信号量是严谨的,它将等待信号量
(5)当完成使用信号量,它将及时传递信号量
(6)如果不再使用信号量,它将关闭与信号量的连接
(7)如果信号量正在保护一个共享资源,而该共享资源已经不存在或者不再能访问,则任务可以撤销该信号量
2 生成信号量组件
你可以显式地调用_sem_create_component()函数生成信号量组件。如果不显式地生成,MQX则在应用程序首次创建信号量时使用默认的参数创建组件。参数及其默认值与事件组件的参数相。
3 生成信号量
在使用信号量之前,任务需要创建信号量:
创建该类型信号量: 调用: 参数:
Fast _sem_create_fast() 索引,它必须在信号量组件
被创建时的指定的范围内。
Named _sem_create() 字符串名称
当任务创建信号量时,需要指定如下内容:
(1)计数初始值——信号量计数的初始值标识信号量拥有的锁数量(一个任务可以获得多个锁)。
(2)优先级队列——如果优先级队列被设置,等待信号量的任务队列必须按照优先级队列,而且MQX置信号量为最高优先级的等待任务。
(3)如果优先级队列没有被设置,则队列按照先来先服务顺序,而且MQX置信号量为最久等待的任务。
(4)优先级继承——如果优先级继承被设置,而且有更高优先级的任务在等待信号量,则MQX会提高任务的优先级至等待任务的优先级。为了使用优先级继承,信号量必须是严谨的。
(5)如果声明信号量是严谨的,则任务在能够传递信号量之前必须等待信号量。如果信号量是严谨的,初始值是信号量计数的最大值。如果信号量是不严谨的,则计数不受限制。
4 打开与信号量的连接
一个任务在使用信号量之前,必须先打开与信号量的连接。
打开一个与该类型信号量的连接: 调用: 参数:
Fast _sem_open_fast() 索引,它必须在信号量组件
被创建时的指定的范围内。
Named _sem_open() 字符串名称
以上两个函数都给信号量返回一个唯一句柄。
5 等待信号量与传递信号量
任务调用_sem_wait _ 系列函数之一等待信号量。如果信号量计数为 0 ,MQX阻塞该任务直到其它任务传递该信号量(_sem_post())或者特定的任务的定时时间到。如果计数不为 0 ,则MQX将计数减量,任务继续行。
当任务传递信号量并且有多个任务正在等待信号量时,MQX将它们置入就绪队列。如果没有任务在等待,则MQX增加信号量计数。在这两种情况下,传递信号量的任务保持就绪状态。
6 关闭与信号量的连接
当任务不再需要使用信号量时,它可以调用_sem_close()函数关闭与该信号量的连接。
7 撤销信号量
当信号量不再被需要时,任务可以撤销它。
销毁该类型信号量: 调用: 参数:
Fast _sem_destroy_fast() 索引,它必须在信号量组件
被创建时的指定的范围内。
Named _sem_destroy() 字符串名称
同样,任务可以确认是否强制销毁。如果强制销毁,MQX将等待该信号量的任务置为就绪,并在所有任务传递信号量之后撤销该信号量。
如果不是强制销毁方式,则 MQX在最后一个等待任务获得并传递信号量之后撤销该信号量。(如果信号量是严谨的,则通常采用这一方式)
例子:任务同步与互斥操作
该例子基于轻量级信号量例题,它给出信号量如何实现任务的同步与互斥操作。
该例子实现了多任务可以读、可以写的FIFO机制。访问FIFO数据结构时需要进行互斥操作。当FIFO满时写入数据任务,或者在FIFO空时读取数据任务,都要求实现任务同步操作。为此,
需要设置如下三个信号量:
1 索引信号量——为了在FIFO中实现互斥
2 读信号量——为了同步读任务
3 写信号量——为了同步写任务
该例子涉及到三个任务:主程序、读和写。主程序初始化信号量,创建读和写任务。
代码:
1 数据结构与定义
#define MAIN_TASK 5
#define WRITE_TASK 6
#define READ_TASK 7
#define ARRAY_SIZE 5
#define NUM_WRITERS 2
typedef struct
{
_task_id DATA[ARRAY_SIZE];
uint_32 READ_INDEX;
uint_32 WRITE_INDEX;
} SW_FIFO, _PTR_ SW_FIFO_PTR;
extern void main_task(uint_32 initial_data);
extern void write_task(uint_32 initial_data);
extern void read_task(uint_32 initial_data);
extern SW_FIFO fifo;
2 任务模板
#include <mqx.h>
#include "main.h"
TASK_TEMPLATE_STRUCT MQX_template_list[] =
{
{MAIN_TASK, main_task, 1000, 5, "main",
MQX_AUTO_START_TASK, 0L, 0}, //主函数,为自启动任务
{WRITE_TASK, write_task, 600, 5, "write", //写任务
0, 0L, 0},
{READ_TASK, read_task, 1000, 5, "read", //读任务
0, 0L, 0},
{ 0, 0, 0, 0, 0,
0, 0L, 0}
};
3 主程序任务的代码
主程序任务创建信号量组件、索引、读写信号量以及读写任务。
#include <mqx.h>
#include <bsp.h>
#include <sem.h>
#include "main.h"
SW_FIFO fifo;
void main_task(uint_32 initial_data)
{
_task_id task_id;
_mqx_uint i;
fifo.READ_INDEX = 0;
fifo.WRITE_INDEX = 0;
//_sem_create_component与_event_create_component() 显示地生成事件组件是一样的,参数如下:
// 参数 含义 默认值
// 初始值 能够创建的事件组的初始数量 8
// 增量 生成所有事件组时事件组的增量,直到达到最大值为止 8
// 最大值 如果增量非0,允许创建的事件组的最大数量 0
if (_sem_create_component(3 , 1, 6) != MQX_OK) {
printf("\nCreating semaphore component failed");
_mqx_exit(0); //创建一个包括三个初始量的事件组,增量为1,最大值为6
}
_sem_create函数参数说明:
if (_sem_create("write", ARRAY_SIZE, 0) != MQX_OK) { // #define ARRAY_SIZE 5
printf("\nCreating write semaphore failed"); //创建写信号量
_mqx_exit(0);
}
if (_sem_create("read", 0, 0) != MQX_OK) { //创建读信号量
printf("\nCreating read semaphore failed");
_mqx_exit(0);
}
if (_sem_create("index", 1, 0) != MQX_OK) { //创建索引信号量
printf("\nCreating index semaphore failed");
_mqx_exit(0);
}
for (i = 0; i < NUM_WRITERS; i++) {
task_id = _task_create (0, WRITE_TASK, i);
printf("\nwrite_task created, id 0x%lx", task_id);
}
task_id = _task_create(0, READ_TASK, 0);
printf("\nread_task created, id 0x%lx", task_id);
}
注释:_task_create 传递处理器编号、任务模板索引和任务创建参数。应用程序定义一个创建参数,通
常用于为子任务提供初始化信息。一个任务也可以创建一个在任务模板列表中没有定义过的任务,
指定任务模板索引为0 ,这时,MQX把任务创建参数当作一个指向任务模板的指针。
4 读任务的代码
#include <mqx.h>
#include <bsp.h>
#include <sem.h>
#include "main.h"
void read_task(uint_32 initial_data) //读任务
{
pointer write_sem; //定义三个指针变量
pointer read_sem;
pointer index_sem;
打开所有与信号量的连接
if (_sem_open("write", &w rite_sem) != MQX_OK) {
_sem_open函数说明:
sem_open
(
char_ptr name_ptr, 信号量的字符串名称
英文注释:
pointer _PTR_ returned_sem_ptr 一个指针变量的地址,是一个二级指针,能储存一个可访问的 值,这个值被需要所有需要使用信号量的函数。
)
printf("\nOpening write semaphore failed");
_mqx_exit(0);
}
if (_sem_open("index", &index_sem) != MQX_OK) {
printf("\nOpening index semaphore failed");
_mqx_exit(0);
}
if (_sem_open("read", &read_sem) != MQX_OK) {
printf("\nOpening read semaphore failed");
_mqx_exit(0);
}
while (TRUE) {
等待信号量
if (_sem_wait(read_sem, 0) != MQX_OK) {
printf("\nWaiting for read semaphore failed");
_mqx_exit(0);
}
if (_sem_wait(index_sem, 0) != MQX_OK) {
printf("\nWaiting for index semaphore failed");
_mqx_exit(0);
}
printf("\n 0x%lx", fifo.DATA[fifo.READ_INDEX++]);
if (fifo.READ_INDEX >=ARRAY_SIZE) {
fifo.READ_INDEX = 0;
}
_sem_post(index_sem);
_sem_post(write_sem);
}
}
_sem_wait函数定义说明:
This function waits for a semaphore to become available. If one is not the task is queued according to the queueing policy for this semaphore.
_mqx_uint _sem_wait
(
pointer users_sem_ptr, //_sem_open返回的信号量句柄
uint_32 timeout // 等待一个信号量的毫秒数,如果这个值为0,这个溢出时间将是无限的。
)
_sem_post
函数意义:This function returns a semaphore to the semaphore, so another task may use it.
函数参数
_mqx_uint _sem_post
(
pointer users_sem_ptr _sem_open返回的信号量句柄
)
5 写任务的代码
#include <mqx.h>
#include <bsp.h>
#include <sem.h>
#include "main.h"
void write_task(uint_32 initial_data) //写任务函数
{
pointer write_sem;
pointer read_sem;
pointer index_sem;
打开所有信号量的连接
if (_sem_open("write", &w rite_sem) != MQX_OK) {
printf("\nOpening write semaphore failed");
_mqx_exit(0);
}
if (_sem_open("index", &index_sem) != MQX_OK) {
printf("\nOpening index semaphore failed");
_mqx_exit(0);
}
if (_sem_open("read", &read_sem) != MQX_OK) {
printf("\nOpening read semaphore failed");
_mqx_exit(0);
}
while (TRUE) {
if (_sem_wait(write_sem, 0) != MQX_OK) {
printf("\nWaiting for wr ite semaphore failed");
_mqx_exit(0);
}
if (_sem_wait(index_sem, 0) != MQX_OK) {
printf("\nWaiting for index semaphore failed");
_mqx_exit(0);
}
fifo.DATA[fifo.WRITE_INDEX++] = _task_get_id();
if (fifo.WRITE_INDEX >=ARRAY_SIZE) {
fifo.WRITE_INDEX = 0;
}
传递这些信号量
_sem_post(index_sem);
_sem_post(read_sem);
}
}
补充:
一般操作系统的信号量说明:
信号量S也称为"信号灯"(semaphore),是一个记录型数据类型,它有两个数据项,定义如下:
struct semaphore
{
int value;
pointer_PCB queue;
}
semaphore S;
除初始化外,信号量仅能通过两个标准的原子操作P(S)和V(S)来访问。这两个操作定义为:
void P(S)
{
S.value = S.value--
if(S.value < 0)
block(S.queue)
}
一个进程q在给定信号量S上的P操作,首先将该信号量的值减1。若减1后其值小于0,则将调用P操作的进程q阻塞起来,将其PCB插入该信号量的等待队列中并转低级调度,否则,操作结束。
void V(S)
{
S.value = S.value ++;
if(S.value <= 0)
wakeup(S.queue) /*唤醒等待队列一个进程,变其状态为就绪态*/
}
一个在给定信号量S上的V操作,首先将该信号量的值加1。若加1后其值不大于0,则将该信号量等待队列中第1个进程移出,并将其状态变为就绪,否则,操作结束。
这里有两点值得注意,一是当执行P(S)操作出现进程阻塞时,被阻塞的进程应是调用P(S)的进程;而执行V(S)操作有进程被唤醒时,被唤醒的进程不是调用V(S)的进程,而是在S上阻塞的一个进程。二是P、V操作是在封中断的情况下执行的,就是说,当一个进程正在修改某信号量时,不会有别的进程“同时”修改该信号量。像P、V这样在执行上不可中断的操作称为“原子操作”,简称“原语”。
P、V操作的含义分别为:P(S)表示申请一个资源,S.value > 0表示有资源可用,其值为资源的数目;S.value = 0表示无资源可用;S.value < 0,则|S.value|表示S等待队列中的进程个数;V(S)表示释放一个资源。信号量的初始值应该大于等于0。
例子:生产者-消费者问题
单缓冲区的情况:有一个生产者进程P和一个消费者进程C共用一个缓冲区,P生产产品放入缓冲区,C从缓冲区取产品来消费。
这里有一个同步问题,只有生产者生产了产品,放在了缓冲区,消费进程才可以取出来。还有P进程不能往满的缓冲区放产品,C进程不能从空的缓冲区中取产品。还有互斥问题,即缓冲区不能同时被P和C使用。为解决这个问题,需设置两个信号量full,empty,full表示缓冲区是否有产品,初值为0;empty表示缓冲区是否为空,初值为1。
主程序如下:
semaphore empty = 1;
semaphore full = 0;
main()
{
cobegin
producer();
consumer();
coend
}
生产者——消费者进程的描述如下:
void producer()
{
while(ture)
{
生产一个产品;
P(empty);
送产品到缓冲区;
V(full);
}
}
void consumer()
{
while(ture)
{
P(full);
从缓冲区取产品;
V(empty);
消费产品;
}
}
}