基本概念

  • 临界资源:每次只允许一个任务进行访问(读/写)的资源
  • 临界区:每次只允许一个任务执行的代码片段
  • 任务同步:任务之间具有相对执行顺序
  • 任务互斥:多个任务在同一时刻都需要访问临界资源,但临界资源只允许一个任务访问

整体调用流程

Mutex操作可能导致任务状态转换,因此功能必须在内核实现。并且进入内核态后所有用户态任务都会暂停执行。这个时候内核可以直观地决定任务是否能够进入临界区。

因此我们需要暴露出系统调用接口给用户使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//0x80号中断会触发调用此中断处理函数
void SysCallHandler(uint type, uint cmd, uint param1, uint param2) // __cdecl__
{ //根据type中断功能号调用不同的功能处理,其中type为1时触发Mutex的中断处理函数
switch(type)
{
case 0:
TaskCallHandler(cmd, param1, param2);
break;
case 1://cmd子功能号
MutexCallHandler(cmd, param1, param2);
break;
case 2:
KeyCallHandler(cmd, param1, param2);
break;
case 3:
SysInfoCallHandler(cmd, param1, param2);
break;
default:
break;
}
}

//cmd子功能号
void MutexCallHandler(uint cmd, uint param1, uint param2)
{
if( cmd == 0 )
{
uint* pRet = (uint*)param1;

*pRet = (uint)SysCreateMutex(param2);
}
else if( cmd == 1 )
{
SysEnterCritical((Mutex*)param1, (uint*)param2);
}
else if( cmd == 2 )
{
SysExitCritical((Mutex*)param1);
}
else
{
SysDestroyMutex((Mutex*)param1, (uint*)param2);
}
}

uint type, uint cmd, uint param1, uint param2这些参数如何传入?
肯定是不能使用栈的,因为用户态和内核态栈不一样。需要借助寄存器传入参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#define SysCall(type, cmd, param1, param2)    
asm volatile( \
"movl $" #type ", %%eax \n" \ //$1 是表示 立即数1
"movl $" #cmd ", %%ebx \n" \
"movl %0, %%ecx \n" \ //%0 0号变量
"movl %1, %%edx \n" \
"int $0x80 \n" \
: \
: "r"(param1), "r"(param2) \ //对应的传入ecx,edx的变量
: "eax", "ebx", "ecx", "edx" \
)

uint CreateMutex(uint type)
{
volatile uint ret = 0;

SysCall(1, 0, &ret, type);

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

互斥锁内核态底层功能函数

1
2
3
4
5
6
7
8
9
typedef struct 
{
ListNode head; //链表
Queue wait; //因为被互斥锁的等待队列
uint type; //
uint lock; //0表示空闲 1表示占用 ==> 优化设计 mutex->lock=(uint)gTaskAddr
//lock关键成员,任务进入临界区要设置为占用状态,出临界区要设置为空闲状态,
//如果任务想进入临界区但是lock被占用了,任务只能够进入等待队列
} Mutex;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// typedef struct _ListNode {
// struct _ListNode* next;
// struct _ListNode* prev;
// } ListNode;

// typedef ListNode List;

static List gMList = {0}; //全局互斥锁链表,只存储互斥锁id 即 互斥锁的地址

//创建一把锁,返回锁变量的地址
static Mutex* SysCreateMutex(uint type)
{
Mutex* ret = Malloc(sizeof(Mutex));

if( ret )
{
Queue_Init(&ret->wait);

ret->lock = 0;
ret->type = type;

List_Add(&gMList, (ListNode*)ret);
}
//互斥锁的地址作为互斥锁id返回
return ret;
}

static void SysEnterCritical(Mutex* mutex, uint* wait)
{
if( mutex && IsMutexValid(mutex) )
{
switch(mutex->type)
{ //占用状态,任务只能进入等待状态
//空闲状态,任务占用临界资源并且将lock设置为已经被占用
case Normal:
SysNormalEnter(mutex, wait);
break;

case Strict:
SysStrictEnter(mutex, wait);
break;
default:
break;
}
}
}

void SysExitCritical(Mutex* mutex)
{
if( mutex && IsMutexValid(mutex) )
{
switch(mutex->type)
{
case Normal:
//lock标记设置为0 表示空闲状态 并且要通知等待队列的任务进入就绪队列
SysNormalExit(mutex);
break;
case Strict:
SysStrictExit(mutex);
break;
default:
break;
}
}
}

static void SysDestroyMutex(Mutex* mutex, uint* result)
{
if( mutex )
{
ListNode* pos = NULL;

*result = 0;
//链表遍历,地址是否相同
List_ForEach(&gMList, pos)
{
if( IsEqual(pos, mutex) )
{
if( IsEqual(mutex->lock, 0) )
{
List_DelNode(pos);

Free(pos);

*result = 1;
}

break;
}
}
}
}

无法获取互斥锁是导致任务进入等待状态的原因之一

互斥锁提供给上游应用的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
uint CreateMutex(uint type)
{
volatile uint ret = 0;

SysCall(1, 0, &ret, type);

return ret;
}

void EnterCritical(uint mutex)
{
volatile uint wait = 0;
do
{ //80号中断 使用功能1(互斥锁功能)的子功能1(进入互斥区)
SysCall(1, 1, mutex, &wait);
} //必须要增加while循环,避免多个进程恢复执行状态时全都直接进入临界区
while( wait );//每次都需要竞争,只有一个进程能跳出while进入临界区
}

void ExitCritical(uint mutex)
{
SysCall(1, 2, mutex, 0);
}

uint DestroyMutex(uint mutex)
{
uint ret = 0;

SysCall(1, 3, mutex, &ret);

return ret;
}

互斥锁优化设计

  1. 同一个任务可多次获取同一个互斥锁,即调用多次EnterCritical()不会阻塞
  2. 只有获取锁的任务,才能释放锁,避免其他任务恶意销毁锁
  3. 无法销毁正在被使用的锁

任务通过自身标识堆锁进行标记mutex->lock=(uint)gCTaskAddr;

释放锁时,通过锁标记与任务自身标识判断合法性IsEqual(mutex->lock,gCTaskAddr)

只有未被标识的锁能够被销毁IsEqual(mutex->lock, 0)==>true