资源管理
典型的共享资源有:变量(静态或者全局变量),数据结构体,RAM中的表格和I/O设备中的寄存器等。
共享内存能够虽然能让任务之间的信息交换变的简单,但如果任务对数据不具有独享权,则可能导致任务间的竞争以及数据的破坏。
最常用的独占共享资源和创建临界区的方法有:
- 关中断
- 禁止任务调度
- 使用信号量
- 使用互斥信号量
资源共享方法 | 何使选用 |
---|---|
关、开中断 | 当访问共享资源的速度很快(读取或写入极少量时)以至于访问共享资源所花费的时间小于系统的中断关闭时间。 由于使用该方法会影响到中断延迟,不建议使用 |
调度器上锁 | 当访问共享资源的时间比系统中断关闭时间长,却比系统给调度器上锁的时间短时。 使用调度器上锁,会使当前任务为最高优先级任务。这有悖于系统的初衷(剥夺型)。但该方法优于关中断,因为不会影响中断的延迟 |
信号量 | 当所有的任务可以无限期等待共享资源的访问时。 使用信号量可能导致优先级反转,其优点在于比互斥信号量快一些 |
互斥信号量 | 访问共享资源的首选方法,当某些任务对共享资源的访问有时间要求时。 μC/OS-III的互斥信号量具有一套内建的优先级继承的机制 |
1 关中断、开中断
void yourFunction(void)
{
CPU_SR_ALLOC(); /*分配存储空间来存储当前cpu的中断状态*/
/*进入临界区*/
CPU_CRITICAL_ENTER(); /*将CPU的中断标志位存储在CPU_SR_ALLOC()分配的存储空间中,并屏蔽所有的可屏蔽中断*/
Access the resource; /*原子操作不被打破*/
CPU_CRITICAL_EXIT(); /*恢复局部变量中存储的CPU中断标志位*/
/*退出临界区*/
}
关中断和开中断是一个任务和一个中断服务程序共享变量或数据结构的唯一方法
2 给调度器上锁/开锁
void YourFunction(void)
{
OS_ERR err;
OSSchedLock(&err); /*调度器上锁,可以嵌套250层*/
Access the resource; /*非原子操作,可以被中断*/
OSSchedUnlock(&err); /*调度器开锁,开锁次数和上锁次数相同则启动调度器*/
}
通过该方法,两个甚至两个以上的任务可以共享数据而避免出现竞争。需要注意,**如果调度器已经上锁,但并没有关中断,那么一旦中断发生,即使在临界区内,中断服务程序也将被立即执行,但即使由此而使得某些具有更高优先级的任务就绪了,在中断服务程序结束后,内核仍将返回至被中断的任务。**在调度器上锁的情况下,中断服务程序总是会返回至被打断的任务中去,此时内核的行为与那些不可剥夺型内核十分相似。在调度器上锁时,μC/OS-III禁止用户进行阻塞型调用。
3 信号量
信号量像是一种上锁机制,代码必须获得对应的钥匙才能继续执行,一旦获得了钥匙,也就意味着该任务具有进入被锁部分代码的权限,一旦执行至被锁代码段,则任务需一直等待,直到对应被锁部分代码的钥匙被再次释放才能继续执行。在共享资源时,只有任务才能使用信号量,中断服务程序则不能使用。
信号量通常分为两种:二进制信号量与计数型信号量。二进制信号量只能取0或1,计数型信号量则可以取0~255或者0~65535或者0~4294967295,这取决于信号量规约机制使用的位数,在μC/OS-III中信号量的最大值由OS_SEM_CTR设定。
函数 | 作用 |
---|---|
OSSemCreate() | 创建一个信号量 |
OSSemDel() | 删除一个信号量 |
OSSemPend() | 等待一个信号量 |
OSSemPost() | 释放或发出一个信号量 |
OSSemPendAbort() | 取消等待信号量 |
OSSemSet() | 强制设置一个信号量的值 |
3.1 信号量结构
struct os_sem {
OS_OBJ_TYPE Type; /*被定义为SEMA*/
CPU_CHAR *NamePtr; /*信号的名字(ASCII字符串)*/
OS_PEND_LIST PendList; /*该信号量的任务挂起表*/
OS_SEM *DbgPrevPtr;
OS_SEM *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
OS_SEM_CTR Ctr; /*信号量的当前值*/
CPU_TS TS; /*时间戳*/
CPU_INT08U SemID; /*信号量唯一ID号*/
};
3.2 二进制信号量
**二进制信号量只运行多个任务中的一个任务对资源由访问权限。**要想获取资源的任务必须执行等待(挂起)操作,如果该资源对应的信号量有效(值大于0), 则任务将获得钥匙获取资源。如果信号量的值为0,则等待信号量的任务被置入等待信号量任务列表。
OS_SEM MySem;
void main(void)
{
OS_ERR err;
...
OSInit(&err);
...
OSSemCreate(&MySem, "My Semaphore", 1, &err); /*创建一个初始值值为1的信号量,信号量的别名为"My Semaphore"*/
/*check err*/
...
/*创建Task*/
OSStart(&err);
}
void Task(void* p_arg)
{
OS_ERR err;
CPU_TS ts; /*信号量被释放时的时间戳*/
while(DEF_ON)
{
...
/*当信号量的值大于0,则任务可以获取资源,同时,任务对信号量的值-1*/
OSSemPend(&MySem, 0, OS_OPT_PEND_BLOCKING, &ts, &err); /*阻塞一直等待信号量(=0任务等待,>0任务获取资源)*/
switch(err)
{
case OS_ERR_NONE:
Access shared Resource;
OSSemPost(&MySem, OS_OPT_POST_1, &err); /*资源访问后,释放信号量(执行信号量的值+1)*/
/*check err*/
break;
case OS_ERR_PEND_ABORT:
/*未获取到钥匙,该任务被挂起,CPU执行其他任务*/
break;
case OS_ERR_OBJ_DEL:
/*信号量已被删除*/
break;
default:
/*其他错误*/
}
...
}
}
3.3 计数型信号量
计数型信号量用于某资源可以为几个任务所用时。
3.4 使用信号量的注意事项
访问共享资源时引入信号量并不会增加系统的中断延迟,如果中断服务程序或者当前任务在访问共享资源的过程中,使得一个具有更高优先级的任务进入就绪态,那么该任务将会被立刻执行。
3.5 任务优先级反转
任务H的优先级(高)>任务M的优先级(中)>任务L的优先级(低)
(1)任务M与任务H开始处于挂起状态,任务L运行
(2)任务L获取信号量(二进制信号量)
(3)任务L开始访问共享资源
(4)任务H就绪获得CPU的使用权
(5)任务H开始运行
(6)任务H想获得任务L所获得的信号量,但由于未被释放,任务H被挂起等待
(7)任务L获得CPU的使用权,继续对共享资源的访问
(8)任务M就绪获得CPU的使用权
(9)任务M开始运行
(10)任务M执行完毕,任务L就绪获得CPU使用权
(11)任务L继续访问共享资源
(12)任务L执行完成释放信号量,系统知道任务H一直在等待该信号量,执行任务调度
(13)任务H获得CPU的使用权并获得信号量,任务H执行
以上情况,任务H的优先级实际被降到任务L的水平。因为任务H一直要等待直到任务L释放占有的共享资源。由于任务M剥夺了任务L的CPU使用权,使得任务H的等待时间延长,这被叫做无界优先级反转。之所以叫做“ 无界 ”是由于中等优先级任务的运行延长了任务L访问资源的时间以及任务H的等待时间,如果期间有多个中等优先级对CPU使用权进行剥夺,则任务H的等待时间将会一直延长下去。
4 互斥型信号量(MUTEX)
互斥型信号量是一种特殊的二进制信号量,可以通过它解决无界优先级反转的问题。
任务H的优先级(高)>任务M的优先级(中)>任务L的优先级(低)
(1)任务M与任务H开始处于挂起状态,任务L运行
(2)任务L获取信号量(二进制信号量)
(3)任务L开始访问共享资源
(4)任务H就绪获得CPU的使用权
(5)任务H开始运行
(6)任务H想获得任务L所获得的信号量,但由于未被释放,任务H被挂起等待,此时μC/OS-III将任务L的优先级提升到与任务H相同的水平
(7)任务L继续访问共享资源
(8)当任务M就绪,发现自己的优先级低于此时任务L的优先级,任务M仍留在就绪表中
(8’)任务L对共享资源访问结束,释放互斥信号量,同时,μC/OS-III将任务L的优先级还原,系统发现任务H正在等待互斥信号量,执行任务调度
(9)任务H获得互斥信号量,开始对共享资源访问
(10)任务H访问资源结束,释放互斥信号量
(11)任务H继续执行
(12)任务H执行结束,启动任务调度,任务M获得CPU使用权
μC/OS-III支持完全优先级继承,一旦一个具有高优先级的任务H想要访问共享资源,但共享资源正在被低优先级任务访问,则占有该资源的任务的优先级被提升至与任务H相同,以减少中等优先级增加任务H的等待时间,避免优先级反转。
与信号量相同,只有任务才能使用互斥信号量,中断服务程序不可以使用。
函数 | 作用 |
---|---|
OSMutexCreate() | 创建一个互斥信号量 |
OSMutexDel() | 删除一个互斥信号量 |
OSMutexPend() | 等待一个互斥信号量 |
OSMutexPendAbort() | 取消等待互斥信号量 |
OSMutexPost() | 释放一个互斥信号量 |
4.1 互斥信号量结构
struct os_mutex { /* Mutual Exclusion Semaphore */
/* ------------------ GENERIC MEMBERS ------------------ */
OS_OBJ_TYPE Type; /* 互斥信号量类型,被设置为4个ASCII字符'M','U','T','E'*/
CPU_CHAR *NamePtr; /* 自定义互斥信号量名称(ASCII字符串)*/
OS_PEND_LIST PendList; /* 互斥信号量等待挂起表*/
/* ------------------ SPECIFIC MEMBERS ------------------ */
OS_TCB *OwnerTCBPtr; /*表示当前占有互斥信号量任务的任务控制块*/
OS_PRIO OwnerOriginalPrio; /*优先级未被提升之前的任务优先级,避免无界优先级反转问题*/
OS_NESTING_CTR OwnerNestingCtr; /* Mutex is available when the counter is 0 */
CPU_TS TS; /*互斥信号量被最近一次释放的时间戳*/
};
5 死锁
死锁(deadlock)也称为抱死(deadly embrace),指两个任务无限制地互相等待对方控制着地资源
void Task1(void* p_arg)
{
...
while(DEF_ON)
{
wait for event to occur; (1)
Aquire M1; (2)
Access R1; (3)
...
Interrupt occur! (4)
...
Aquire M2; (8)
Access R2;
}
}
void Task2(void* p_arg)
{
...
while(DEF_ON)
{
Wait for event to occur; (5)
Aquire M2; (6)
Access R2; (7)
...
Aquire M1; (8)
Access R1;
}
}
Task2优先级高于Task1优先级
(1)任务2未就绪,且其等待的事件发生,任务1运行
(2)任务1继续运行,任务1请求互斥信号量M1
(3)任务1请求互斥信号量成功,并访问共享资源R1
(4)此时,一种中断发生,中断服务程序使任务2就绪,任务2获得CPU使用权
(5)任务2开始运行,并且任务2等待的事件发生,任务2继续运行
(6)任务2请求互斥信号量M2
(7)任务2请求成功,访问共享资源R2
(8)任务2继续运行,请求互斥信号量M1,由于M1正在被任务1使用,任务2被挂起等待,调度器执行调度
(9)任务1获得CPU使用权,并从被中断处继续执行,此时,任务1请求互斥信号量M2,由于M2正在被任务2使用,任务1被挂起等待,调度器执行调度
(10)此时两个任务便进入死锁状态,谁也无法继续运行,因为谁也无法获取对方占用的资源
5.1 避免死锁的方法
- 先得到全部需要的资源,再进行下一步工作
- 用相同的顺序申请多个资源
- 再调度请求信号量的函数时设定超时时间(并未完全解决,只是暂时解决死锁问题)
5.1.1 先得到全部需要的资源,再进行下一步工作
void Task1(void* p_arg)
{
...
while(DEF_ON)
{
wait for event to occur;
Aquire M1;
Aquire M2;
Access R1;
Access R2;
}
}
void Task2(void* p_arg)
{
...
while(DEF_ON)
{
Wait for event to occur;
Aquire M1;
Aquire M2;
Access R1;
Access R2;
}
}
5.1.2 用相同的顺序申请多个资源
void Task1(void* p_arg)
{
...
while(DEF_ON)
{
wait for event to occur;
Aquire M1;
Access R1;
Aquire M2;
Access R2;
}
}
void Task2(void* p_arg)
{
...
while(DEF_ON)
{
Wait for event to occur;
Aquire M1;
Access R1;
Aquire M2;
Access R2;
}
}