转载:MQX任务同步(信号量)

 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);

消费产品;

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值