基本概念
- 临界资源:每次只允许一个任务进行访问(读/写)的资源
- 临界区:每次只允许一个任务执行的代码片段
- 任务同步:任务之间具有相对执行顺序
- 任务互斥:多个任务在同一时刻都需要访问临界资源,但临界资源只允许一个任务访问
整体调用流程
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
| void SysCallHandler(uint type, uint cmd, uint param1, uint param2) { switch(type) { case 0: TaskCallHandler(cmd, param1, param2); break; case 1: MutexCallHandler(cmd, param1, param2); break; case 2: KeyCallHandler(cmd, param1, param2); break; case 3: SysInfoCallHandler(cmd, param1, param2); break; default: break; } }
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" \ "movl $" #cmd ", %%ebx \n" \ "movl %0, %%ecx \n" \ "movl %1, %%edx \n" \ "int $0x80 \n" \ : \ : "r"(param1), "r"(param2) \ : "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; } 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
|
static List gMList = {0};
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); } return ret; }
static void SysEnterCritical(Mutex* mutex, uint* wait) { if( mutex && IsMutexValid(mutex) ) { switch(mutex->type) { 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: 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 { 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; }
|
互斥锁优化设计
- 同一个任务可多次获取同一个互斥锁,即调用多次EnterCritical()不会阻塞
- 只有获取锁的任务,才能释放锁,避免其他任务恶意销毁锁
- 无法销毁正在被使用的锁
任务通过自身标识堆锁进行标记mutex->lock=(uint)gCTaskAddr;
释放锁时,通过锁标记与任务自身标识判断合法性IsEqual(mutex->lock,gCTaskAddr)
只有未被标识的锁能够被销毁IsEqual(mutex->lock, 0)==>true