本博客用于鄙人的每周总结,如有错误不妥之处,还望各位前辈指导。谢谢!
一、互斥锁是什么东西?
互斥锁是一种uCos2对资源保护的措施,比如任务需要访问某些资源的,但是这些资源不能同时被几个任务访问,这个时候就需要互斥锁这类的技术。互斥锁保护的资源要求,同时访问该资源的任务只能有一个,如果其他任务需要访问,则需要等待。那么程序如何实现互斥锁了?这里我列出了,使用互斥锁所需要解决的问题。
- 怎么创建一个互斥锁?
2、怎么查询当前资源是否可以使用?
3、如果当前任务访问被互斥锁保护的资源时,发现该资源正在被其他任务所占用,那么程序如何处理。、
4、如何长时间等待不到资源,任务该怎么办,就这样一直被挂起呢?
5、当任务访问资源结束,怎么释放互斥锁,以及让等待的任务得到互斥锁呢?
6、如果我不想保护该资源了,怎么删除一个互斥锁。
在此之前,我需要强调几点,首先我们得明白任务和TCB的关系。
二、如何创建一个互斥锁
互斥锁是和事件ECB控制块是绑定在一起的,要创建一个互斥锁,就需要创建了一个事件ECB。UCos2在初始化的时候,就创建了一个空白的ECB数组,就是为了后来的直接使用。那么我们应该访问这个数组,怎么知道数组中哪个位置是没有被使用的呢?这里uCos2使用了一个指针OSEventFreeList,专门用于标记数组中未被使用的的位置。通过这个指针我们就可以初始化自己的ECB事件控制块了。这里介绍一下ECB的结构体数据类型。
typedef struct os_event {
INT8U OSEventType; //事件的类型
void *OSEventPtr; //主要用来存放消息邮箱或消息队列的指针
INT16U OSEventCnt; //信号量的计数器
#if OS_LOWEST_PRIO <= 63
INT8U OSEventGrp; //等待任务组
INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; //等待任务表
#else
INT16U OSEventGrp; //事件等待表,类似于任务就绪表
INT16U OSEventTbl[OS_EVENT_TBL_SIZE];
#endif
#if OS_EVENT_NAME_SIZE > 1
INT8U OSEventName[OS_EVENT_NAME_SIZE];
#endif
} OS_EVENT;
那么我们创建一个互斥锁的时候,就需要对这些变量进行赋值。具体函数代码如下:
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; //更新指针,指向下一个空闲的ECB
OS_EXIT_CRITICAL();
pevent->OSEventType = OS_EVENT_TYPE_MUTEX; //事件类型为互斥锁
pevent->OSEventCnt = (INT16U)((INT16U)prio << 8) | OS_MUTEX_AVAILABLE; //设置互斥锁可用
pevent->OSEventPtr = (void *)0; //这个在后来的操作中用用处
#if OS_EVENT_NAME_SIZE > 1
pevent->OSEventName[0] = '?'; //设置名称
pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
OS_EventWaitListInit(pevent); //对事件等待列表的初始化
这样我们就创建了一个互斥锁,我们对结构体的一些变量做更近一步的解释。其中比较难理解的,和后面在防止优先级翻转的问题上必不可少的一个成员OSEventCnt,这个成员为16位的数据,高低八位的数据代表不同的含义。
成员变量 | 功能 | 备注 |
OSEventCnt | 高八位:在后面的优先级翻转问题中,会详细解释。 |
|
低八位:用于表示当前互斥锁的状态,当数据为0xFF时,表示该互斥锁可以访问;如果为其他值则代表现在占用该互斥锁的任务的优先级,也就是当任务占用互斥锁的时候,会把本身的优先级存储在低八位。 |
|
三、如何去查询一个互斥锁是否可用
这里涉及到两个函数,两个函数都是去访问互斥锁的资源,但是两个函数的作用是不一样。其中BOOLEAN OSMutexAccept (OS_EVENT *pevent, INT8U *perr)函数是一个非堵塞的函数,当访问的互斥锁不能够使用的时候,会直接返回。不会做其他处理,如果访问的互斥锁可以直接使用,则进行一些必要的操作。判断的方式就是我们上面介绍的OSEventCnt低八位是否为0xFF。另一个函数是void OSMutexPend (OS_EVENT *pevent, INT16U timeout, INT8U *perr),也是访问互斥锁的函数,但是该函数是堵塞的。就是在互斥锁不能够访问的时候,会将该任务挂起,等待互斥锁的释放。这里就涉及到很多的问题:
- 怎么把任务挂起。
- 任务怎么知道自己能够访问互斥锁了,
- 如果长时间等不到互斥锁,有什么机制来防止这种情况。
如何挂起任务,大致需要两个步骤,一个是将任务在事件的等待列表中对应的位置置位,二来就是把自己在就绪表中的状态设置为非就绪态即可。为了防止长时间等不到互斥锁,函数的输入参数中的timeout用于设置等待互斥锁的时间,如果在等待的这段时间中,互斥锁都无法释放,那么任务就会停止等待该互斥锁,转为就绪态。那么任务怎么知道自己等待的互斥锁可以访问的呢?占用互斥锁的任务,在访问结束之后,会释放互斥锁,具体的操作和上面挂起一个互斥锁的操作是相反的,一是要在事件等待的列表中,把优先级最高的任务在列表中清零,然后把该任务在就绪表中的设置为就绪态,就可以参数OS的任务调度。等到自己的时间片,任务就可以执行了,访问等待的互斥锁所保护的资源了。