深入理解uCOS任务调度机制:中断、临界区、信号量和互斥锁详解

中断服务程序

与裸机程序不同,进入中断函数时需要先通知uCOS即将进入中断服务例程,允许其跟踪中断嵌套,当中断结束后,需要通知uCOS已经结束中断服务程序,将重新启动任务调度
示例如下:

其中OSIntEnter()即表示进入中断
OSIntExit()既表示退出中断

共享资源保护

典型的共享资源包括变量(数据或全局),数据结构体,I/O设备寄存器等,共享资源的可靠访问,必须保证任务对数据的独享权,否则可能因为任务的竞争破坏数据。例如任务1在访问OLED时,可能刚发送开始信号,就被另一个需要使用OLED的任务打断,该任务又重新发了一遍开始信号,就将导致OLED的信号接收变得混乱,或者是某一任务需要进行喂狗,但在喂狗时被其余任务打断,导致喂狗无法完成,就可能导致系统复位。
常见的共享资源保护有如下方法:
1.关中断,开中断
2.调度器上锁
3.使用信号量
4.使用互斥锁
将于下文详细介绍各种方法及其各自的适用范围

临界区

代码的临界段称为临界区,指的是处理时不可以被分割的代码,一旦这部分代码开始执行,则不允许任何中断打断,大多数系统为确保临界区代码不被打断,进入临界区时需要关中断,一旦临界区代码完成,需要立即开中断,否则导致中断延迟的后果是比较严重的。常见案例如喂狗,FLASH的写入,获取当前时钟节拍数等。
uCOS中存在大量的临界区代码,分为以下情况:
情况1:中断处理程序和任务都会访问临界区的代码,需要用关中断的方式加以保护,一旦用了关中断的方式,临界区的代码必须快速完成,否则会导致中断延迟影响严重,例如串口丢包等。
情况2:仅有任务访问临界断代码,可以通过给调度器上锁的方式来保护,一旦使用调度器上锁,内核会禁止用户进行阻塞调用,如果某些用户强行进行阻塞调用,很可能导致内核崩溃
这里插入图片描述
其中:
OS_CRITICAL_ENTER()表示进入临界区;
OS_CRITICAL_EXIT()表示退出临界区
选择进入临界区究竟是关中断还是调度器上锁,需要找到os_cfg.h中的宏定义
OS_CFG_ISR_POST_DEFERRED_EN
当该宏定义为0时:屏蔽除NMI和fault外的所有中断
当该宏定义为1时:对调度器上锁。注意:在调度器上锁期间严禁调用各种可能引起任务调度的函数,比如睡眠延迟,阻塞等待信号量/事件标志组/多个内核对象等,否则可能导致内核崩溃

信号量

信号量(semaphore)
信号量常用于任务的同步,通过该信号,就能够控制某个任务的执行,这个信号量具有计数值,因此,可以称为计数信号量。计数信号量可以用于资源管理,允许多个任务获取信号量访问共享资源,但是会限制任务的最大数目。访问的任务数达到了支持的最大数目时,就会阻塞其他试图获取该信号量的任务,直到有任务释放了该信号量。这就是计数型信号量的运作机制,虽然计数信号量允许多个任务访问同个资源,但是也有限定,比如某个资源限定只有3个信号量访问,那么当第四个任务访问时,会因为获取不到信号量而进入阻塞,等到有任务释放掉信号量后才能开始运作,其具体的运行机制如下图:


PV原语:
1965年,荷兰学者Dijkstra提出了利用信号量机制解决进程同步问题,信号量正式成为有效的进程同步工具,现在信号量机制被广泛的用于单处理机和多处理机系统以及计算机网络中。
信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用临界区的进程数。
Dijkstra同时提出了对信号量操作的PV原语。
P原语操作的动作是:
  (1)S减1;
  (2)若S减1后仍大于或等于零,则进程继续执行;
  (3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度。
V原语操作的动作是:
  (1)S加1;
  (2)若相加结果大于零,则进程继续执行;
  (3)若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度。
PV操作对于每一个进程来说,都只能进行一次,而且必须成对使用。在PV原语执行期间不允许有中断的发生。
信号量的P、V操作,P表示申请一个资源,每次P操作使信号量减1,V是释放一个资源,每次V操作使信号量加1。信号量表示的是当前可用的资源个数,当信号量为负时,申请资源的进程(任务)就只能等待了。所以,信号量是负的多少,就表明有多少个进程(任务)申请了资源但无资源可用,只能处于等待状态。
除了访问共享资源外,亦可中断/任务控制某任务的执行,称之为“单向同步”。
创建信号量:
首先需要定义信号量对象

然后创建信号量

其中,第一个参数填充信号量的地址,第二个参数是信号量的名称,可以由用户自定义
第三个参数是信号量的初值,第四个参数是错误码
阻塞等待信号量:


其中第二个参数填写OS_OPT_POST_1时,表示释放信号量给最高优先级且就绪的任务
填写OS_OPT_POST_ALL,表示释放信号量给所有等待信号量的任务
信号量应用举例:
1.任务同步:

当任务1执行到释放信号量时,任务2才能够被执行,起到了任务1控制任务2的效果
保护共享资源:

当任务1进行串口打印前,进行P操作,从而独占对串口的使用,后进行V操作,任务2等待到信号量,进行P操作,从而独占对串口的操作
达到共享资源的目的
优先级反转:
使用信号量可能导致任务的优先级反转,这将破坏任务完成的预期顺序,以下是一个具体例子

(1)任务H和任务M处于挂起状态,等待某一任务的发生,任务L处于运行状态
(2) 某一时刻任务L需要访问共享资源,任务L需要先获得对应的信号量
(3)任务L获得了信号量并开始访问共享资源
(4)任务H等待的事情发生,由于其优先级较高,任务H开始运行
(5)任务H也要访问该共享资源,但发现任务量被L占有
(7)任务L占有CPU使用权,再次开始运行
(8)任务M等待的事情发生,由于其优先级较高,任务M开始运行直到执行完毕
(9)任务L继续运行,直到访问共享资源完毕后释放信号量
(10)任务H获得信号量,继续运行直到结束
在这种情况下,由于任务H需要一直等待任务L释放信号量,所以任务H的优先级实际上降到了与任务L同级的水平,更糟糕的是,由于任务M剥夺了CPU的使用权,使得任务H的情况更加恶化,直接导致了优先级反转的问题
解决方案:
为了避免优先级反转这个问题,UCOSIII支持一种特殊的二进制信号量:互斥信号量,即互斥锁,用它可以解决优先级反转问题。
目前解决优先级反转有许多种方法。其中普遍使用的有2种方法:一种被称作优先级继承(priority inheritance);另一种被称作优先级天花板(priority ceilings)。
A. 优先级继承(priority inheritance) :优先级继承是指将低优先级任务的优先级提升到等待它所占有的资源的最高优先级任务的优先级。当高优先级任务由于等待资源而被阻塞时,此时资源的拥有者的优先级将会临时自动被提升,以使该任务不被其他任务所打断,从而能尽快的使用完共享资源并释放,再恢复该任务原来的优先级别。
B. 优先级天花板(priority ceilings): 优先级天花板是指将申请某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级。(这个优先级称为该资源的优先级天花板) 。
A和B的区别:优先级继承,只有当占有资源的低优先级的任务被阻塞时,才会提高占有资源任务的优先级;而优先级天花板,不论是否发生阻塞,都提升

使用信号量的注意事项:
程序中可以使用任意多的信号量访问各种资源。比如一个信号量来访问一个共享的显示设备,另一个则访问一个共享的打印机;再用一个信号量来共享一些数据结构,另一个信号量来保护缓冲池等。不过一般建议信号量来访问I/O设备,而不用它来访问存储单元。
信号量常被用过了头,用信号量来处理简单的共享变量更是小题大做。请求和释放信号量的过程相当耗费时间的。尽管关中断可能会带来一些间接成本,甚至导致那些并不访问共享资源的高优先级任务也无法在第一时间被执行,但用户仍可以通过关中断、开中断来处理简单的共享变量从而提高整体工作效率。
例如两个任务共享一个32位的整数变量,一个任务给这个任务变量加1,另一个任务给这个变量清0。如果要求不管哪种操作,CPU都能在极短时间内完成,显然就不会使用信号量来满足互斥访问的条件了。每个任务只需要在操作这个任务之前关中断,之后再开中断就可以了。然而,如果这个变量是浮点数,而相应的微处理器又没有硬件的浮点协处理器,则浮点运算的时间相当长,如此一来,关中断时间长了便会影响到中断延迟,这种情况下就有必要使用信号量了。

互斥锁

互斥锁,亦称:互斥信号量。
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个任务(线程)访问该对象(任务之间访问到相同的函数、相同的全局变量)。某个任务得到互斥锁后,就可以访问共享资源,其他任务等待该任务释放互斥锁才能进行访问。
何时可以用普通信号量替代互斥锁?如果没有任务对共享资源访问有截止的时间,那么普通信号量可以替代互斥锁;反之则必须使用互斥锁。因为前者会造成无界优先级反转,后者却不会.
函数接口
1.创建互斥锁
void OSMutexCreate(OS_MUTEX *p_mutex//互斥锁的对象
OS_CHAR *name//互斥锁的名字
OS_ERR *p_err)//错误码
创建互斥锁前,需要先定义互斥锁变量
2.等待互斥锁:若等待互斥锁成功,则锁定共享资源

参数:
● p_mutex,互斥锁对象
● timeout,超时时间,默认写0,一直等待
● opt,设置当前等待互斥锁的阻塞方式,默认写OS_OPT_PEND_BLOCKING,阻塞等待。如果互斥锁此时被另外一个任务占用,且指定的阻塞类型为OS_OPT_PEND_NON_BLOCKING,则OSMutexPend就会直接返回而不再等待互斥锁被释放。
● p_ts,用于记录等待互斥锁花了多长时间,默认写NULL,不记录。
● p_err,返回错误码,没有错误的就返回OS_ERR_NONE

说明:
如果占有互斥锁是一个较低优先级多任务,那么UCOSIII就会临时提升它的优先级,使得其等于此时想要获取互斥锁的任务优先级
3.释放互斥锁

互斥锁应用:保护共享资源(主要是针对硬件设备,一把锁保护一个硬件)

物联沃分享整理
物联沃-IOTWORD物联网 » 深入理解uCOS任务调度机制:中断、临界区、信号量和互斥锁详解

发表评论