ucos实时操作系统学习笔记——任务间通信(互斥锁)

想讲一下ucos任务间通信中的mutex,感觉其设计挺巧妙,同sem一样使用的是event机制实现的,代码不每一行都分析,因为讲的没邵贝贝老师清楚,主要讲一下mutex的内核是如何实现的。可以理解互斥锁是设置信号量值为1时候的特殊情况,与之不同的地方是互斥锁为了避免优先级反转采用了优先级继承机制,本文主要讲一下互斥锁的创建,pend和post,对应的函数是OSMutexCreate,OSMutexPend,OSMutexPost,当然讲函数也不会所有的扩展功能都讲,只是讲一下主干部分,下面贴出来的代码也是简化的代码。

会通过分块的方式讲代码,在讲的过程中会把互斥锁的机制、功能以及具体实现与代码穿插讲一下。

首先,从互斥锁创建讲起,互斥锁同一样使用event机制来实现,不过与sem不同的是互斥锁在创建的时候会传递一个高一点的优先级给event,当低优先级任务获得互斥锁时用于优先级继承,而sem传递的参数是信号量的值。OSMutexCreate创建代码如下所示:

OS_EVENT  *OSMutexCreate (INT8U   prio,
                          INT8U  *perr)
{
    OS_EVENT  *pevent;
    if (OSIntNesting > 0u) {                               /* See if called from ISR ...               */
        *perr = OS_ERR_CREATE_ISR;                         /* ... can‘t CREATE mutex from an ISR       */
        return ((OS_EVENT *)0);
    }
    OS_ENTER_CRITICAL();
    if (OSTCBPrioTbl[prio] != (OS_TCB *)0) {               /* Mutex priority must not already exist    */
        OS_EXIT_CRITICAL();                                /* Task already exist at priority ...       */
        *perr = OS_ERR_PRIO_EXIST;                         /* ... inheritance priority                 */
        return ((OS_EVENT *)0);
    }
    OSTCBPrioTbl[prio] = OS_TCB_RESERVED;                  /* Reserve the table entry                  */
    pevent             = OSEventFreeList;                  /* Get next free event control block        */
    if (pevent == (OS_EVENT *)0) {                         /* See if an ECB was available              */
        OSTCBPrioTbl[prio] = (OS_TCB *)0;                  /* No, Release the table entry              */
        OS_EXIT_CRITICAL();
        *perr              = OS_ERR_PEVENT_NULL;           /* No more event control blocks             */
        return (pevent);
    }
    OSEventFreeList        = (OS_EVENT *)OSEventFreeList->OSEventPtr;   /* Adjust the free list        */
    OS_EXIT_CRITICAL();
    pevent->OSEventType    = OS_EVENT_TYPE_MUTEX;
    pevent->OSEventCnt     = (INT16U)((INT16U)prio << 8u) | OS_MUTEX_AVAILABLE; /* Resource is avail.  */
    pevent->OSEventPtr     = (void *)0;                                 /* No task owning the mutex    */
    OS_EventWaitListInit(pevent);
    *perr                  = OS_ERR_NONE;
    return (pevent);
}

互斥锁的创建同样不允许在中断中进行,mutex首先会检查传入优先级的任务TCB有没有已经被使用,如果传入优先级已经被使用,则创建失败,返回错误,如果参数传入的优先级的TCB没有被占用,则设置为reserved,保证该TCB不会被其他任务使用;然后是跟信号量一样,要取一个空闲的event结构体,判断如果没有空闲event则返回错误;取得event之后要对其进行初始化,OSEventType为OS_EVENT_TYPE_MUTEX类型,OSEventCnt的数值跟sem不同,在mutex中将高8位设置为需要继承的优先级(参数传入的优先级),低8位为OS_MUTEX_AVAILABLE,这个值为0x00FF,个人理解这个值相当于sem中信号量位1的作用,当pend中监测到低8位是这个值时,表示可以将任务本身的优先级放到低8位的位置上,如果低8位已经有优先级存在,则表示需要把想获得锁的任务挂起。之后pend中会讲到;然后会对OSEventPtr清0,具体目的不清楚,对mutex来说,好像没有用到这个参数;之后是对event group和table的清零初始化操作,表示当前没有等待互斥锁的任务在group和table中。

互斥锁创建完成之后就是有关互斥锁的pend和post操作了,这两个操作是成对存在的,两个函数分别是OSMutexPend和OSMutexPost。接下来从OSMutexPend的代码开始分析其主要的操作是怎么样的,其中有一部分的内容和sem是相同的,代码如下所示:

void  OSMutexPend (OS_EVENT *pevent, INT16U timeout, INT8U *perr)
{
    INT8U      pip;                                        /* Priority Inheritance Priority (PIP)      */
    INT8U      mprio;                                      /* Mutex owner priority                     */
    BOOLEAN    rdy;                                        /* Flag indicating task was ready           */
    OS_TCB    *ptcb;
    OS_EVENT  *pevent2;
    INT8U      y;

    if (pevent->OSEventType != OS_EVENT_TYPE_MUTEX) {      /* Validate event block type                */
        *perr = OS_ERR_EVENT_TYPE;
        return;
    }
    if (OSIntNesting > 0) {                                /* See if called from ISR ...               */
        *perr = OS_ERR_PEND_ISR;                           /* ... can‘t PEND from an ISR               */
        return;
    }
    if (OSLockNesting > 0) {                               /* See if called with scheduler locked ...  */
        *perr = OS_ERR_PEND_LOCKED;                        /* ... can‘t PEND when locked               */
        return;
    }
(1)=========================================================================================================
    OS_ENTER_CRITICAL();
    pip = (INT8U)(pevent->OSEventCnt >> 8);                /* Get PIP from mutex                       */
                                                           /* Is Mutex available?                      */
    if ((INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8) == OS_MUTEX_AVAILABLE) {
        pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8;       /* Yes, Acquire the resource                */
        pevent->OSEventCnt |= OSTCBCur->OSTCBPrio;         /*      Save priority of owning task        */
        pevent->OSEventPtr  = (void *)OSTCBCur;            /*      Point to owning task‘s OS_TCB       */
        if (OSTCBCur->OSTCBPrio <= pip) {                  /*      PIP ‘must‘ have a SMALLER prio ...  */
            OS_EXIT_CRITICAL();                            /*      ... than current task!              */
            *perr = OS_ERR_PIP_LOWER;
        } else {
            OS_EXIT_CRITICAL();
            *perr = OS_ERR_NONE;
        }
        return;
    }(2)========================================================================================================
    mprio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8);  /* No, Get priority of mutex owner   */
    ptcb  = (OS_TCB *)(pevent->OSEventPtr);                       /*     Point to TCB of mutex owner   */
    if (ptcb->OSTCBPrio > pip) {                                  /*     Need to promote prio of owner?*/
        if (mprio > OSTCBCur->OSTCBPrio) {
            y = ptcb->OSTCBY;
            if ((OSRdyTbl[y] & ptcb->OSTCBBitX) != 0) {           /*     See if mutex owner is ready   */
                OSRdyTbl[y] &= ~ptcb->OSTCBBitX;                  /*     Yes, Remove owner from Rdy ...*/
                if (OSRdyTbl[y] == 0) {                           /*          ... list at current prio */
                    OSRdyGrp &= ~ptcb->OSTCBBitY;
                }
                rdy = OS_TRUE;
            } else {
                pevent2 = ptcb->OSTCBEventPtr;
                if (pevent2 != (OS_EVENT *)0) {                   /* Remove from event wait list       */
                    if ((pevent2->OSEventTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) {
                        pevent2->OSEventGrp &= ~ptcb->OSTCBBitY;
                    }
                }
                rdy = OS_FALSE;                            /* No                                       */
            }
            ptcb->OSTCBPrio = pip;                         /* Change owner task prio to PIP            */
#if OS_LOWEST_PRIO <= 63
            ptcb->OSTCBY    = (INT8U)( ptcb->OSTCBPrio >> 3);
            ptcb->OSTCBX    = (INT8U)( ptcb->OSTCBPrio & 0x07);
            ptcb->OSTCBBitY = (INT8U)(1 << ptcb->OSTCBY);
            ptcb->OSTCBBitX = (INT8U)(1 << ptcb->OSTCBX);
#else
            ptcb->OSTCBY    = (INT8U)((ptcb->OSTCBPrio >> 4) & 0xFF);
            ptcb->OSTCBX    = (INT8U)( ptcb->OSTCBPrio & 0x0F);
            ptcb->OSTCBBitY = (INT16U)(1 << ptcb->OSTCBY);
            ptcb->OSTCBBitX = (INT16U)(1 << ptcb->OSTCBX);
#endif
            if (rdy == OS_TRUE) {                          /* If task was ready at owner‘s priority ...*/
                OSRdyGrp               |= ptcb->OSTCBBitY; /* ... make it ready at new priority.       */
                OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
            } else {
                pevent2 = ptcb->OSTCBEventPtr;
                if (pevent2 != (OS_EVENT *)0) {            /* Add to event wait list                   */
                    pevent2->OSEventGrp               |= ptcb->OSTCBBitY;
                    pevent2->OSEventTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
                }
            }
            OSTCBPrioTbl[pip] = ptcb;
        }
    }(3)==========================================================================================================
    OSTCBCur->OSTCBStat     |= OS_STAT_MUTEX;         /* Mutex not available, pend current task        */
    OSTCBCur->OSTCBStatPend  = OS_STAT_PEND_OK;
    OSTCBCur->OSTCBDly       = timeout;               /* Store timeout in current task‘s TCB           */
    OS_EventTaskWait(pevent);                         /* Suspend task until event or timeout occurs    */
    OS_EXIT_CRITICAL();
    OS_Sched();                                       /* Find next highest priority task ready         */
    OS_ENTER_CRITICAL();
    switch (OSTCBCur->OSTCBStatPend) {                /* See if we timed-out or aborted                */
        case OS_STAT_PEND_OK:
             *perr = OS_ERR_NONE;
             break;

        case OS_STAT_PEND_ABORT:
             *perr = OS_ERR_PEND_ABORT;               /* Indicate that we aborted getting mutex        */
             break;

        case OS_STAT_PEND_TO:
        default:
             OS_EventTaskRemove(OSTCBCur, pevent);
             *perr = OS_ERR_TIMEOUT;                  /* Indicate that we didn‘t get mutex within TO   */
             break;
    }
    OSTCBCur->OSTCBStat          =  OS_STAT_RDY;      /* Set   task  status to ready                   */
    OSTCBCur->OSTCBStatPend      =  OS_STAT_PEND_OK;  /* Clear pend  status                            */
    OSTCBCur->OSTCBEventPtr      = (OS_EVENT  *)0;    /* Clear event pointers                          */
    OS_EXIT_CRITICAL();
}

本文将OSMutexPend函数分为了如上所示的4个部分(使用“=”隔开),第一部分和最后一部分和sem的操作相同,第一部分主要就是检查一下当前的event类型是不是mutex以及检查当前的操作是不是在中断处理程序中进行的, 最后一部分讲的的就是讲任务挂起然后进行任务调度以及当任务重新获得互斥锁时的准备工作;与sem不同的主要是第二和第三部分,其实这两部分的内容是不会同时在一次操作中运行的,当第二部分运行的时候表示没有任务使用互斥锁,这时候运行该函数的任务将获得互斥锁并退出,第三部分则是表示一个新的任务在尝试获得互斥锁,并且互斥锁已经被占用时的操作。

第二部分的运行流程是这样的,之前创建的时候也说过OSEventCnt的低8位存放的是OS_MUTEX_AVAILABLE,如果检查结果仍为OS_MUTEX_AVAILABLE,则说明互斥锁没有被占用,这时候就要保存当前运行任务的优先级到OSEventCnt的低8位中,然后比较一下当前任务的优先级是否比创建时的继承优先级低(优先级值大),如果当前任务的优先级比继承优先级高(小),则返回错误,因为继承优先级是为了防止优先级翻转而设定,如果其优先级低则实现不了这一功能,反之,则返回正确值,此处的返回值是通过参数地址返回的;然后将当前获取互斥锁的任务的TCB保存到OSEventPtr指针中。

第三部分的运行流程是,首先获得占用互斥锁任务的优先级以及TCB,然后将当前想获取互斥锁的任务的优先级(1)和占有互斥锁任务的优先级(2)做比较,如果(1)比(2)的优先级高,则不做任何操作,直接执行第4部分,将当前任务挂起;如果(1)比(2)的优先级低,则需要提升(1)的优先级,同样要挂起(2)。对于(1)比(2)优先级低的情况,首先检查当前任务是不是处于运行状态,然后给占用互斥锁的任务提升优先级到继承优先级级别,然后根据检查任务的运行状态决定把任务继续放到ready table中还是放到任务的event等待列表中。将占有互斥锁的任务的TCB保存到系统的prio table中。

INT8U  OSMutexPost (OS_EVENT *pevent)
{
    INT8U      pip;                                   /* Priority inheritance priority                 */
    INT8U      prio;

    if (OSIntNesting > 0) {                           /* See if called from ISR ...                    */
        return (OS_ERR_POST_ISR);                     /* ... can‘t POST mutex from an ISR              */
    }
    if (pevent->OSEventType != OS_EVENT_TYPE_MUTEX) { /* Validate event block type                     */
        return (OS_ERR_EVENT_TYPE);
    }(1)====================================================================================================
    OS_ENTER_CRITICAL();
    pip  = (INT8U)(pevent->OSEventCnt >> 8);          /* Get priority inheritance priority of mutex    */
    prio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8);  /* Get owner‘s original priority      */
    if (OSTCBCur != (OS_TCB *)pevent->OSEventPtr) {   /* See if posting task owns the MUTEX            */
        OS_EXIT_CRITICAL();
        return (OS_ERR_NOT_MUTEX_OWNER);
    }
    if (OSTCBCur->OSTCBPrio == pip) {                 /* Did we have to raise current task‘s priority? */
        OSMutex_RdyAtPrio(OSTCBCur, prio);            /* Restore the task‘s original priority          */
    }
    OSTCBPrioTbl[pip] = OS_TCB_RESERVED;              /* Reserve table entry                          */
(2)========================================================================================================
    if (pevent->OSEventGrp != 0) {                    /* Any task waiting for the mutex?               */
                                                      /* Yes, Make HPT waiting for mutex ready         */
        prio                = OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MUTEX, OS_STAT_PEND_OK);
        pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8;  /*      Save priority of mutex‘s new owner       */
        pevent->OSEventCnt |= prio;
        pevent->OSEventPtr  = OSTCBPrioTbl[prio];     /*      Link to new mutex owner‘s OS_TCB         */
        if (prio <= pip) {                            /*      PIP ‘must‘ have a SMALLER prio ...       */
            OS_EXIT_CRITICAL();                       /*      ... than current task!                   */
            OS_Sched();                               /*      Find highest priority task ready to run  */
            return (OS_ERR_PIP_LOWER);
        } else {
            OS_EXIT_CRITICAL();
            OS_Sched();                               /*      Find highest priority task ready to run  */
            return (OS_ERR_NONE);
        }
    }
    pevent->OSEventCnt |= OS_MUTEX_AVAILABLE;         /* No,  Mutex is now available                   */
    pevent->OSEventPtr  = (void *)0;
    OS_EXIT_CRITICAL();
    return (OS_ERR_NONE);
}

对OSMutexPost同样进行分段描述,第一部分是检查event的type和是否在中断中,这跟sem是相同的;第二部分则是讲互斥锁的还原,首先是通过event获取继承优先级以及现在占有互斥锁的任务的优先级,之前讲过互斥锁的pend和post是成对存在的,当任务pend获取互斥锁之后,也需要相对应的任务post释放互斥锁,所以第二部分中会有一个判断当前post释放互斥锁的是否是占有互斥锁的任务,如果不是则会报错,如果是占有互斥锁的任务要释放互斥锁,则会判断任务在pend的时候有没有提升任务的优先级到继承优先级的级别,如果有的话需要把当前任务的优先级通过OSMutex_RdyAtPrio还原到任务自己原来的优先级级别。

第三部分的内容这是会判断有没有任务在等待当前的event互斥锁,如果有的话就通过OS_EventTaskRdy获取等待任务的优先级,在获取优先级的同时会把获取的任务从event等待列表中删除,可以从OS_EventTaskRdy中找到代码,然后设置event mutex相关参数如pend中的操作一样,同样需要判断任务的优先级是否比继承优先级高,如果高则报错,否则会任务调度到另外的任务中继续执行,相当于本任务的post过程完成,如果没有event mutex的等待任务,则会直接设置event的OSEventCnt位OS_MUTEX_AVAILABLE和OSEventPtr的清0操作。

在第三部分中一个比较巧妙的地方是,当有任务在event的等待列表时,会直接将互斥锁交给等待任务,等待任务使用完成时会做post释放操作,如果有多个任务在等待,则会一一释放掉互斥锁,当所有任务释放掉之后,返回到当前任务时,则互斥锁已经完全释放了,这时返回OS_ERR_NONE,如果没有等待任务在event的等待列表中,则需要当前任务自己释放,也就是第三部分最后的4行操作。

时间: 2024-12-12 06:04:13

ucos实时操作系统学习笔记——任务间通信(互斥锁)的相关文章

ucos实时操作系统学习笔记——任务间通信(信号量)

ucos实时操作系统的任务间通信有好多种,本人主要学习了sem, mutex, queue, messagebox这四种.系统内核代码中,这几种任务间通信机制的实现机制相似,接下来记录一下本人对核心代码的学习心得,供以后回来看看,不过比较遗憾的是没有仔细学习扩展代码的功能实现部分.ucos操作系统的内核代码实现相对简单,但是对理解其他操作系统内核相同功能有帮助. ucos的任务间通信机制主要是基于event实现的,其实理解这个event不用翻译成中文事件,就叫event感觉还更容易接收.下面是操

ucos实时操作系统学习笔记——任务间通信(消息)

ucos另一种任务间通信的机制是消息(mbox),个人感觉是它是queue中只有一个信息的特殊情况,从代码中可以很清楚的看到,因为之前有关于queue的学习笔记,所以一并讲一下mbox.为什么有了queue机制还要用mbox呢,只要设置queue的msg只有一个不就行了?其实很简单,就是为了节约资源,因为使用queue的话需要专门描述queue的机构体os_q,同时需要分配一段内存用来存放msg,而如果直接使用mbox机制的话,就好多了,节约..... 首先从mbox的创建开始,mbox创建的函

ucos实时操作系统学习笔记——操作系统在STM32的移植

使用ucos实时操作系统是在上学的时候,导师科研项目中.那时候就是网上找到操作系统移植教程以及应用教程依葫芦画瓢,功能实现也就罢了,没有很深入的去研究过这个东西.后来工作了,闲来无聊就研究了一下这个只有几千行代码的操作系统,也没所有的代码都看,只是看了其中部分内容.自己还自不量力的尝试着去写过简单的操作系统,最后写着写着就被带到了ucos的设计思路上了,后来干脆就“copy”代码了,虽说对操作系统内核的理解有很大的帮助,但是很是惭愧啊,智力不够,对操作系统内核的设计者更加仰慕,O(∩_∩)O哈哈

RTX51 Tiny实时操作系统学习笔记—初识RTX51 Tiny

 一,RTX51 Tiny简单介绍 RTX51 Tiny是一种实时操作系统(RTOS),能够用它来建立多个任务(函数)同一时候运行的应用(从宏观上看是同一时候运行的,但从微观上看,还是独立运行的).嵌入式应用系统常常有这样的需求.RTOS能够提供调度.维护.同步等功能. 实时操作系统能灵活的调度系统资源,像CPU和存储器,而且提供任务间的通信.RTX51 Tiny是一个功能强大的RTOS,且易于使用,它用于8051系列的微控制器.该RTOS最多支持16个任务,基于RTX51 Tiny构建的应用程

计算机操作系统学习笔记_1_操作系统概述

操作系统概述 一.操作系统的概念.特征.功能和提供的服务 1.操作系统的概念     操作系统是计算机系统中最重要.最基本的系统软件,操作系统位于硬件和用户程序之间.    对于用户来讲:它能向用户提供使用计算机的接口;    从资源管理角度来看:它能管理计算机软硬件资源,提高其利用率;    再者,利用虚拟机技术(如WMWare,VirtualBox,Java虚拟机等),扩展了计算机的功能和使用范围.     因此,操作系统的定义为:操作系统是控制和管理计算机软.硬件资源,以尽可能合理.高效的

操作系统学习笔记----进程/线程模型----Coursera课程笔记

操作系统学习笔记----进程/线程模型----Coursera课程笔记 进程/线程模型 0. 概述 0.1 进程模型 多道程序设计 进程的概念.进程控制块 进程状态及转换.进程队列 进程控制----进程创建.撤销.阻塞.唤醒.... 0.2 线程模型 为什么引入线程 线程的组成 线程机制的实现 用户级线程.核心级线程.混合方式 1. 进程的基本概念 1.1 多道程序设计 允许多个程序同时进入内存运行,目的是为了提高CPU系统效率 1.2 并发环境与并发程序 并发环境: 一段时间间隔内,单处理器上

Linux 操作系统学习笔记

一,unix 1.unix 特点 伸缩性强,开放性好, 2.基本原则 所有对象,硬件都是文件 配置数据以文本形式保存 短小的单目的程序构成 多个程序合作完成复杂任务 3.gnu 基本原则是共享,建立自由开放的unix系统 1984年 richard stallman 发起 基本体系是micro kernel 4.gpl Copyleft 原作者所有权 5.linux起源 Linustorvalds, 自由的类unix操作系统, 遵循gnu和gpl 6.linux 可以实现unix功能 遵循开源许

计算机操作系统学习笔记_4_进程管理 --处理机调度

h3.western { font-family: "Liberation Sans",sans-serif; }h3.cjk { font-family: "微软雅黑"; }h3.ctl { font-family: "AR PL UMing CN"; }h1 { margin-bottom: 0.21cm; }h1.western { font-family: "Liberation Sans",sans-serif; f

计算机操作系统学习笔记_2_进程管理 --进程与线程(上)

h3.western { font-family: "Liberation Sans",sans-serif; }h3.cjk { font-family: "微软雅黑"; }h3.ctl { font-family: "AR PL UMing CN"; }h2.western { font-family: "Liberation Sans",sans-serif; font-size: 16pt; }h2.cjk { fon