问题
在当前实现下,阻塞的多个任务重新进入执行态,会发生什么?
这些任务不会去争夺互斥锁,而是继续向下执行。
当前实现的深入分析
解决方案设计
任务从阻塞中 (因获取互斥锁失败而阻塞) 恢复执行时
- 必须再次尝试获取锁
内核需要返回获取锁的结果 (成功 or 失败)
- 成功:任务继续执行,进入临界区
- 失败:任务等待机会,再次尝试获取锁
解决方案实现步骤
1. 在系统调用 EnterCritical() 中,向内核传入标记变量 wait 的地址
2. 内核根据目标 Mutex 的状态设置标记变量的 wait 的值
3. 任务从系统调用返回后,判断标记变量 wait 的值
- True - 再次尝试获取锁
- False - 无需再次尝试,继续向下执行
问题
如果有任务比较聪明,在进入临界区之前,先主动调用 ExitCritical(),会发生什么?
下面的任务执行后会发生什么?
在 TaskA 进入临界区后,TaskB 获得了互斥锁,也进入了临界区,TaskB 并没有阻塞。
下面任务的行为合法吗?
互斥锁的优化设计
同一个任务中可多次获取同一个互斥锁
- 同一个任务中多次调用 EnterCritical() 不会阻塞
只有获取锁的任务,才能释放锁
- 强行释放锁的 (异常行为) 任务将被内核直接杀死
无法销毁处于占用状态的互斥锁
- 强行调用 DestroyMutex() 销毁占用状态的锁,将无效返回
互斥锁的优化实现方案
任务通过自身标识对锁进行标记
- mutex->lock = (uint)gCTaskAddr;
释放锁时,通过锁标记与任务自身标识判断合法性
- IsEqual(mutex->lock, gCTaskAddr)
只有未标识的锁能被销毁
- IsEqual(mutex->lock, 0) => true
互斥锁的优化实现
syscall.c
#include "syscall.h"
#define SysCall(type, cmd, param1, param2) asm volatile( \
"movl $" #type ", %%eax \n" \
"movl $" #cmd ", %%ebx \n" \
"movl %0, %%ecx \n" \
"movl %1, %%edx \n" \
"int $0x80 \n" \
: \
: "r"(param1), "r"(param2) \
: "eax", "ebx", "ecx", "edx" \
)
void Exit()
{
SysCall(0, 0, 0, 0);
}
uint CreateMutex()
{
volatile uint ret = 0;
SysCall(1, 0, &ret, 0);
return ret;
}
void EnterCritical(uint mutex)
{
volatile uint wait = 0;
do
{
SysCall(1, 1, mutex, &wait);
}while(wait);
}
void ExitCritical(uint mutex)
{
SysCall(1, 2, mutex, 0);
}
uint DestroyMutex(uint mutex)
{
uint ret = 0;
SysCall(1, 3, mutex, &ret);
return ret;
}
mutex.c
#include "mutex.h"
#include "memory.h"
#include "task.h"
extern volatile Task* gCTaskAddr;
static List gMList = {0};
void MutexModInit()
{
List_Init(&gMList);
}
void MutexCallHandler(uint cmd, uint param1, uint param2)
{
if(cmd == 0)
{
uint* pRet = (uint*)param1;
*pRet = (uint)SysCreateMutex();
}
else if(cmd == 1)
{
SysEnterCritical((Mutex*)param1, (uint*)param2);
}
else if(cmd == 2)
{
SysExitCritical((Mutex*)param1);
}
else
{
SysDestroyMutex((Mutex*)param1, (uint*)param2);
}
}
static uint IsMutexValid(Mutex* mutex)
{
uint ret = 0;
if(mutex)
{
ListNode* pos = NULL;
List_ForEach(&gMList, pos)
{
if(IsEqual(pos, mutex))
{
ret = 1;
break;
}
}
}
return ret;
}
Mutex* SysCreateMutex()
{
Mutex* ret = (Mutex*)Malloc(sizeof(Mutex));
if(ret)
{
List_Add(&gMList, (ListNode*)ret);
ret->lock = 0;
}
return ret;
}
void SysDestroyMutex(Mutex* mutex, uint* result)
{
if(mutex)
{
ListNode* pos = NULL;
List_ForEach(&gMList, pos)
{
if(IsEqual(pos, mutex))
{
if(IsEqual(mutex->lock, 0))
{
List_DelNode(pos);
Free(pos);
*result = 1;
}
else
{
*result = 0;
}
break;
}
}
}
}
void SysEnterCritical(Mutex* mutex, uint* wait)
{
if(IsMutexValid(mutex))
{
if(mutex->lock)
{
if(IsEqual(mutex->lock, gCTaskAddr))
{
*wait = 0;
}
else
{
MtxSchedule(WAIT);
*wait = 1;
}
}
else
{
mutex->lock = (uint)gCTaskAddr;
*wait = 0;
}
}
}
void SysExitCritical(Mutex* mutex)
{
if(IsMutexValid(mutex))
{
if(IsEqual(mutex->lock, gCTaskAddr))
{
mutex->lock = 0;
MtxSchedule(NOTIFY);
}
else
{
KillTask();
}
}
}
首先我们修改了 EnterCritical 函数,内核会根据互斥锁是否已经占用而修改 wait 的值,为1的话,说明当前已经有任务进入临界区,等互斥锁被释放,它需要循环去争夺互斥锁。
我们解决了同一个任务重复想去占用互斥锁的情形,不会阻塞。
如果没有占用互斥锁的任务想要释放锁,则内核会将这个任务销毁。
只有在互斥锁被释放时,才能销毁互斥锁。
小结
互斥锁是一种特殊的内核变量,用于保护临界资源
多个任务在互斥锁的协调下,能够有序互斥的执行
互斥锁的具体实现需要在内核中完成
互斥锁的使用需要遵循预定的规则