其实以我们目前实现的系统,是可以实现控制不同任务中的代码执行顺序的,用互斥锁就可以达到这种效果
直接看下面的示例代码:
void TaskA(void)
{
gMutexRice = MutexCreat();
SetCursorPos(0, 8);
print("cook rice:");
MutexLock(gMutexRice);
while (1)
{
static U32 cnt = 0;
if(cnt < 20)
{
SetCursorPos(12, 8);
print("%d\n", cnt++);
Delay(999999);
}
else
break;
}
MutexUnLock(gMutexRice);
}
void TaskB(void)
{
gMutexDish = MutexCreat();
SetCursorPos(0, 10);
print("cook dish:");
MutexLock(gMutexDish);
while (1)
{
static U32 cnt = 0;
if(cnt < 30)
{
SetCursorPos(12, 10);
print("%d\n", cnt++);
Delay(999999);
}
else
break;
}
MutexUnLock(gMutexDish);
}
void TaskC(void)
{
MutexLock(gMutexRice);
MutexLock(gMutexDish);
SetCursorPos(0, 12);
print("have dinner\n");
MutexUnLock(gMutexRice);
MutexUnLock(gMutexDish);
}
完整代码见:app.c
运行效果,可以看到,任务 C 在等待任务 A 和任务 B 都结束后才打印出 "have dinner"
从上面的实验代码可以看出,使用互斥锁貌似也可以实现事件,然而,这么做合适吗?
使用互斥锁也能实现我们想要的等待效果,但是,上面的任务中,我们想保护的临界资源是什么?貌似并没有需要保护的临界资源。所以使用互斥锁实现等待显然是不合理的,那么接下来我们就专门实现事件机制吧
既然使用互斥锁能实现等待的效果,那么我们完全可以参见互斥锁的做法来实现等待机制嘛
创建 “event.c” 和 “event.h” 文件,用于实现等待机制
先实现与用户层到内核层的系统调用
内核 | 用户 | 功能 |
---|---|---|
SYS_EventCreat | EventCreat | 创建事件 |
SYS_WaitEvent | WaitEvent | 事件 |
SYS_SetEvent | SetEvent | 设置事件 |
SYS_ClearEvent | ClearEvent | 清除事件 |
SYS_EventDestory | EventDestory | 销毁事件 |
关于系统调用的实现前面已经讲过好几遍了,这里就不再详细描述了,具体详见:再论系统调用
内核层现在并没有实现具体功能,仅打印函数名替代,用以检测系统的调用实现。具体实现见:event.c、event.h、syscall.c、u_syscall.c、u_syscall.h
在 “app.c” 中测试一下等待相关函数系统调用是否成功,具体见:app.c
EVENT* gEvent = NULL;
void TaskA(void)
{
gEvent = EventCreat();
SetEvent(gEvent);
WaitEvent(gEvent);
ClearEvent(gEvent);
EventDestory(gEvent);
while (1);
}
测试结果如下:
// 初始化事件
void EventInit(void)
{
ListInit(&EVENT_LIST);
}
// 创建事件
EVENT* SYS_EventCreat(void)
{
EVENT* event = (EVENT *)Malloc(sizeof(EVENT));
if(NULL == event)
return NULL;
event->event = 0; // 事件未生产状态
ListAddHead(&EVENT_LIST, (LIST_NODE *)event); // 头插
QueueInit(&event->wait); // 初始化事件中的等待队列
return event;
}
// 检查事件是否合法有效
static BOOL CheckEvent(EVENT* event)
{
LIST_NODE* pListNode = NULL;
EVENT* nodeTmp = NULL;
LIST_FOR_EACH(&EVENT_LIST, pListNode)
{
nodeTmp = (EVENT *)LIST_NODE(pListNode, EVENT, node);
if(event == nodeTmp)
return 1;
}
return 0;
}
// 销毁事件
E_RET SYS_EventDestory(EVENT* event)
{
// 检查参数合法性
if(NULL == event || !CheckEvent(event))
return E_ERR;
// 删除链表节点并释放内存
ListDelNode(&event->node);
Free(event);
return E_OK;
}
E_RET SYS_WaitEvent(EVENT* event)
{
// 检查参数合法性
if(NULL == event || !CheckEvent(event))
return E_ERR;
// 将当前任务节点从任务就绪队列中转移到等待时间的等待队列中
if(0 == event->event)
EventSuspend(event);
return E_OK;
}
E_RET SYS_SetEvent(EVENT* event)
{
// 检查参数合法性
if(NULL == event || !CheckEvent(event))
return E_ERR;
// 将事件的状态设置为 1,表明事件发生
event->event = 1;
// 将事件 event 中的等待队列中的任务转移到任务就绪队列中
EventResume(event);
return E_OK;
}
// 将当前任务节点从任务就绪队列中转移到事件的等待队列中
E_RET EventSuspend(EVENT* event)
{
QUEUE_NODE* nodeTmp = NULL;
// 把当前任务节点从就绪任务队列中取出,当前任务节点在队列尾,取出后添加到事件 event 的 wait 等待队列中
nodeTmp = QueueTailRemove(&TASK_READY_QUEUE);
if(NULL == nodeTmp)
return E_ERR;
QueueAdd(&event->wait, nodeTmp);
// 从就绪任务队列中取出一个任务节点并执行该任务,再将该任务节点重新添加到就绪任务队列中
nodeTmp = QueueRemove(&TASK_READY_QUEUE);
if(NULL == nodeTmp)
return E_ERR;
current_task = (volatile TASK *)QUEUE_NODE(nodeTmp, TASK, node);
QueueAdd(&TASK_READY_QUEUE, nodeTmp);
TSS* tss = (TSS*)(*(U32*)TSS_ENTRY_ADDR); // 找到 TSS
tss->esp0 = (U32)(¤t_task->reg) + sizeof(current_task->reg); // TSS.esp0 指向任务上下文数据结构 reg 的末尾
current_reg = (U32)(¤t_task->reg); // current_reg 指向任务上下文数据结构 reg 的起始位置
SWITCH_TO(current_task);
return E_OK;
}
// 将事件 event 中的等待队列中的任务转移到任务就绪队列中
E_RET EventResume(EVENT* event)
{
QUEUE_NODE* nodeTmp = NULL;
// 从事件的等待队列中取出所有任务,并将其添加到就绪队列中
while(event->wait.length)
{
nodeTmp = QueueTailRemove(&event->wait);
if(NULL == nodeTmp)
return E_ERR;
QueueHeadAdd(&TASK_READY_QUEUE, nodeTmp);
}
return E_OK;
}
E_RET SYS_ClearEvent(EVENT* event)
{
// 检查参数合法性
if(NULL == event || !CheckEvent(event))
return E_ERR;
// 将事件的状态设置为 0,表明事件已被处理
event->event = 0;
return E_OK;
}
S32 main(void)
{
...
EventInit(); // 初始化事件
...
}
EVENT* gEventRice = NULL;
EVENT* gEventDish = NULL;
void TaskA(void)
{
gEventRice = EventCreat();
SetCursorPos(0, 8);
print("cook rice:");
while (1)
{
static U32 cnt = 0;
if(cnt < 20)
{
SetCursorPos(12, 8);
print("%d\n", cnt++);
Delay(999999);
}
else
{
break;
}
}
SetEvent(gEventRice);
}
void TaskB(void)
{
gEventDish = EventCreat();
SetCursorPos(0, 10);
print("cook dish:");
while (1)
{
static U32 cnt = 0;
if(cnt < 30)
{
SetCursorPos(12, 10);
print("%d\n", cnt++);
Delay(999999);
}
else
{
break;
}
}
SetEvent(gEventDish);
}
void TaskC(void)
{
WaitEvent(gEventRice);
WaitEvent(gEventDish);
SetCursorPos(0, 12);
print("have dinner\n");
}
再来一个实验测试一下我们实现的事件机制
实验目标:任务 A 每打印计数累加 20 次,任务 B 打印计数加 1
完整代码见:app.c
实验核心代码如下,当任务 A 调用 SetEvent(),这时候任务 B 就会被从等待队列中重新放入就绪队列调度执行,但其会被一直调度执行,想要任务 B 只执行一遍,可以调用 ClearEvent() 函数清掉标志,当任务 B 再次循环执行到 WaitEvent() 函数后就会被挂起,从而达到只执行一次的效果
EVENT* gEvent = NULL;
void TaskA(void)
{
static U32 cnt = 0;
gEvent = EventCreat();
while (1)
{
SetCursorPos(0, 10);
print("TASK A: %d\n", cnt++);
if(cnt%20 == 0)
SetEvent(gEvent);
Delay(999999);
if(cnt > 200)
break;
}
EventDestory(gEvent);
}
void TaskB(void)
{
static U32 cnt = 0;
while (1)
{
WaitEvent(gEvent);
ClearEvent(gEvent);
SetCursorPos(0, 12);
print("TASK B: %d\n", cnt++);
Delay(999999);
}
}
最终成果展示:
现在,我们的系统中已经实现了互斥锁以及任务之间等待机制,那么自然就引出了多任务设计中的经典死锁问题,直接看下面的代码
单独看 TaskA 任务,程序首先获取 gMutexA 互斥锁,再获取 gMutexB 互斥锁,然后释放 gMutexB 互斥锁,最后释放 gMutexA 互斥锁,没啥毛病,但是,如果此时还有一个 TaskB 任务并行执行,那么问题就来了
假设程序在执行 TaskA 任务的 ① 语句,TaskA 获取到 gMutexA 互斥锁,然后可能调度到 TaskB 的 ② 语句处执行,TaskB 获取到 gMutexB 互斥锁,再然后程序可能又调度回 TaskA ③ 处执行,此时由于 gMutexB 被 TaskB 加锁占用,那么 TaskA 就会阻塞挂起,再接着程序可能被调度到 ④ 处执行,由于 gMutexA 被 TasKA 加锁占用,那么 TaskB 也会被阻塞挂起,最终 TaskA 和 TaskB 这两个任务都被挂起了,没有人可以释放 gMutexA 和 gMutexB 互斥锁,于是 TaskA 和 TaskB 永远都不可能被唤醒参与调度执行,对于这种现象,我们也称之为死锁
再来看一个经典死锁示例
TaskA 任务由于在等待 gEventB 事件,可能导致 TaskA 任务被阻塞挂起,此时 TaskB 任务又在等待 gEventA 事件,那么 TaskB 任务也有可能被阻塞挂起,TaskA 和 TaskB 任务都被阻塞挂起,于是 TaskA 无法设置 gEventA,那么就可能无法唤醒 TaskB,而在 TaskB 中又无法设置 gEventB,那么 TaskA 也同样无法被唤醒。这样子也可能造成死锁
总结一下死锁现象:任务因执行所需的资源无法获得而进入无限阻塞的状态
对于多任务程序来说,死锁是非常可怕的,因为死锁的现象不一定能复现,调试起来非常困难,所以我么在设计多任务程序的时候,一定要注意避免死锁的发生
对此,我么可以整理一下死锁的发生条件
怎样避免死锁呢?这个就只能靠程序员小心、精心设计程序了
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。