4、嵌入式学习之uCOS-II基础入门

互斥条件

实现任务间通讯最简便到办法是使用共享数据结构。特别是当所有到任务都在一个单一地址空间下,能使用全程变量、指针、缓冲区、链表、循环缓冲区等,使用共享数据结构通讯就更为容易。虽然共享数据区法简化了任务间的信息交换,但是必须保证每个任务在处理共享数据时的排它性,以避免竞争和数据的破坏。与共享资源打交道时,使之满足互斥条件最一般的方法有:

  • l  关中断
  • l  使用测试并置位指令
  • l  禁止做任务切换
  • l  利用信号量

关中断和开中断

处理共享数据时保证互斥,最简便快捷的办法是关中断和开中断。所示:

Disable interrupts;                                     /*关中断*/
Access the resource (read/write from/to variables);     /*读/写变量*/
Reenable interrupts;                                    /*重新允许中断*/

   μC/OS-Ⅱ在处理内部变量和数据结构时就是使用的这种手段,即使不是全部,也是绝大部分。实际上μC/OS-Ⅱ提供两个宏调用,允许用户在应用程序的C代码中关中断然后再开中断:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()

void Function (void)
{
    OS_ENTER_CRITICAL();
    
       /*在这里处理共享数据*/
    
    OS_EXIT_CRITICAL();
}

可是,必须十分小心,关中断的时间不能太长。因为它影响整个系统的中断响应时间,即中断延迟时间。当改变或复制某几个变量的值时,应想到用这种方法来做。这也是在中断服务子程序中处理共享变量或共享数据结构的唯一方法。在任何情况下,关中断的时间都要尽量短。

如果使用某种实时内核,一般地说,关中断的最长时间不超过内核本身的关中断时间,就不会影响系统中断延迟。当然得知道内核里中断关了多久。凡好的实时内核,厂商都提供这方面的数据。总而言之,要想出售实时内核,时间特性最重要。

测试并置位

    如果不使用实时内核,当两个任务共享一个资源时,一定要约定好,先测试某一全程变量,如果该变量是0,允许该任务与共享资源打交道。为防止另一任务也要使用该资源,前者只要简单地将全程变量置为1,这通常称作测试并置位(Test-And-Set),或称作TASTAS操作可能是微处理器的单独一条不会被中断的指令,或者是在程序中关中断做TAS操作再开中断,如程序所示。

Disable interrupts;                             //关中断
if (‘Access Variable’ is 0)                     //如果资源不可用,标志为0
{                  
    Set variable to 1;                          //置资源不可用,标志为1
    Reenable interrupts;                        //重开中断
    Access the resource;                        //处理该资源
    Disable interrupts;                         //关中断
    Set the ‘Access Variable’ back to 0;        //清资源不可使用,标志为0
    Reenable interrupts;                        //重新开中断
} 
else                                            //否则
{                                        
    Reenable interrupts;                        //开中断
/* You don’t have access to the resource, try back later; *//* 资源不可使用,以后再试; */
}

    有的微处理器有硬件的TAS指令(如Motorola 68000系列,就有这条指令)

禁止,然后允许任务切换

 如果任务不与中断服务子程序共享变量或数据结构,可以使用禁止、然后允许任务切换。以μC/OS-Ⅱ的使用为例,两个或两个以上的任务可以共享数据而不发生竞争。注意,此时虽然任务切换是禁止了,但中断还是开着的。如果这时中断来了,中断服务子程序会在这一临界区内立即执行。中断服务子程序结束时,尽管有优先级高的任务已经进入就绪态,内核还是返回到原来被中断了的任务。直到执行完给任务切换开锁函数OSSchedUnlock (),内核再看有没有优先级更高的任务被中断服务子程序激活而进入就绪态,如果有,则做任务切换。虽然这种方法是可行的,但应该尽量避免禁止任务切换之类操作,因为内核最主要的功能就是做任务的调度与协调。禁止任务切换显然与内核的初衷相违。应该使用下述方法。

//程序清单4.11  用给任务切换上锁,然后开锁的方法实现数据共享.
void Function (void)
{
    OSSchedLock();
    
    /* You can access shared data in here (interrupts are recognized) */
    /*在这里处理共享数据(中断是开着的)*/
    OSSchedUnlock();
}

信号量(Semaphores)

 信号量实际上是一种约定机制,在多任务内核中普遍使用.信号量用于:

l  控制共享资源的使用权(满足互斥条件)

l  标志某事件的发生

l  使两个任务的行为同步

对信号量只能实施三种操作:初始化(INITIALIZE),也可称作建立(CREATE);等信号(WAIT)也可称作挂起(PEND);给信号(SIGNAL)或发信号(POST)。信号量初始化时要给信号量赋初值,等待信号量的任务表(Waiting list)应清为空。

l  等待信号量任务中优先级最高的,或者是

l  最早开始等待信号量的那个任务,即按先进先出的原则(First In First Out ,FIFO)

有的内核有选择项,允许用户在信号量初始化时选定上述两种方法中的一种。但μC/OS-Ⅱ只支持优先级法。如果进入就绪态的任务比当前运行的任务优先级高(假设,是当前任务释放的信号量激活了比自己优先级高的任务)。则内核做任务切换(假设,使用的是可剥夺型内核),高优先级的任务开始运行。当前任务被挂起。直到又变成就绪态中优先级最高任务。

//通过获得信号量处理共享数据
OS_EVENT *SharedDataSem;
void Function (void)
{
    INT8U err;
    OSSemPend(SharedDataSem, 0, &err);
    
        /* You can access shared data in here (interrupts are recognized) */
        /*共享数据的处理在此进行,(中断是开着的)*/
    OSSemPost(SharedDataSem);
}

当诸任务共享输入输出设备时,信号量特别有用。可以想象,如果允许两个任务同时给打印机送数据时会出现什么现象。打印机会打出相互交叉的两个任务的数据。例如任务1要打印“I am Task!”,而任务2要打印“I am Task2!”可能打印出来的结果是:“I Ia amm T Tasask k1!2!”

    在这种情况下,使用信号量并给信号量赋初值1(用二进制信号量)。规则很简单,要想使用打印机的任务,先要得到该资源的信号量。图4.1两个任务竞争得到排它性打印机使用权,图中信号量用一把钥匙表示,想使用打印机先要得到这把钥匙。

                            图4.1用获取信号量来得到打印机使用权

    上例中,每个任务都知道有个信号表示资源可不可以使用。要想使用该资源,要先得到这个信号。然而有些情况下,最好把信号量藏起来,各个任务在同某一资源打交道时,并不知道实际上是在申请得到一个信号量。例如,多任务共享一个RS-232C外设接口,各任务要送命令给接口另一端的设备并接收该设备的回应。如图4.2所示。

    调用向串行口发送命令的函数CommSendCmd(),该函数有三个形式参数:Cmd指向送出的ASCII码字符串命令。Response指向外设回应的字符串。timeout指设定的时间间隔。如果超过这段时间外设还不响应,则返回超时错误信息。函数的示意代码如程序清单4.12所示。

//程序清单 4.12   隐含的信号量。
INT8U CommSendCmd(char *cmd, char *response, INT16U timeout)
{
    Acquire port's semaphore;
    Send command to device;
    Wait for response (with timeout);
    if (timed out) 
    {
        Release semaphore;
        return (error code);
    } 
    else 
    {
        Release semaphore;
        return (no error);
    }
}

    要向外设发送命令的任务得调用上述函数。设信号量初值为1,表示允许使用。初始化是在通讯口驱动程序的初始化部分完成的。第一个调用CommSendCmd()函数的任务申请并得到了信号量,开始向外设发送命令并等待响应。而另一个任务也要送命令,此时外设正“忙”,则第二个任务被挂起,直到该信号量重新被释放。第二个任务看起来同调用了一个普通函数一样,只不过这个函数在没有完成其相应功能时不返回。当第一个任务释放了那个信号量,第二个任务得到了该信号量,第二个任务才能使用RS-232口。

                    图4.2在任务级看不到隐含的信号量

    计数式信号量用于某资源可以同时为几个任务所用例如,用信号量管理缓冲区阵列(buffer pool),如图2.12所示。缓冲区阵列中共有10个缓冲区,任务通过调用申请缓冲区函数BufReq()向缓冲区管理方申请得到缓冲区使用权。当缓冲区使用权还不再需要时,通过调用释放缓冲区函数BufRel()将缓冲区还给管方。函数示意码如程序清单4.13所示

  

//程序清单 4.13 
BUF *BufReq(void)
{
   BUF *ptr;


   Acquire a semaphore;
   Disable interrupts;
   ptr         = BufFreeList;
   BufFreeList = ptr->BufNext;
   Enable interrupts;
   return (ptr);
}




void BufRel(BUF *ptr)
{
   Disable interrupts;
   ptr->BufNext = BufFreeList;
   BufFreeList  = ptr;
   Enable interrupts;
   Release semaphore;
}

                                    图4.3 计数式信号量的用法

缓冲区阵列管理方满足前十个申请缓冲区的任务,就好像有10把钥匙可以发给诸任务。当所有的钥匙都用完了,申请缓冲区的任务被挂起,直到信号量重新变为有效。缓冲区管理程序在处理链表指针时,为满足互斥条件,中断是关掉的(这一操作非常快)。任务使用完某一缓冲区,通过调用缓冲区释放函数BufRel()将缓冲区还给系统。系统先将该缓冲区指针插入到空闲缓冲区链表中(Linked list)然后再给信号量加1或释放该信号量。这一过程隐含在缓冲区管理程序BufReq()和BufRel()之中,调用这两个函数的任务不用管函数内部的详细过程。

信号量常被用过了头。处理简单的共享变量也使用信号量则是多余的。请求和释放信号量的过程是要花相当的时间的。有时这种额外的负荷是不必要的。用户可能只需要关中断、开中断来处理简单共享变量,以提高效率。

猜你喜欢

转载自blog.csdn.net/SherlockHolmess/article/details/88218208