Download presentation
Presentation is loading. Please wait.
1
ucOS-II(RTOS)的基本概念
2
前后台系统(Foreground/Background System)
应用程序是一个无限循环,其中调用相应的中断函数ISR完成相应的操作,这部分为后台行为(background)。中断服务程序ISR处理异步事件,这部分为前台行为(foreground)。后台也叫任务级,前台也叫中断级。
3
代码的临界段(临界区 ) 指处理时不可分割的代码。 一旦这部分代码开始执行,则不允许任何中断打入。
为确保临界段代码的执行,在进入临界段之前要关中断,而临界段代码执行完以后要立即开中断。
4
资源 任何为任务所占用的实体都可称为资源。 如输入输出设备中的打印机、键盘、显示器,或者是一个变量,一个结构或一个数组等。
5
共享资源 可以被一个以上任务使用的资源叫做共享资源。
为了防止数据被破坏,每个任务在与共享资源打交道时,必须独占该资源。称之为互斥(mutual exclusion)。
6
多任务 利用CPU(中央处理单元)在许多任务之间转换、调度来实现多任务的运行。 像一个具有许多后台任务的前后台系统。
7
任务 也称为一个线程,是一个简单的程序,它可以认为CPU完全只属于自己。
8
任务的状态 每个无限循环的任务都处于以下5种状态之一:休眠态,就绪态、运行态、挂起态(等待某一事件发生)和被中断态。
9
任务切换(Context Switch or Task Switch)
又称为上下文切换,或CPU寄存器内容切换。 当多任务内核决定运行另外的任务时,它将正在运行任务的当前状态(Context)保存到任务的堆栈区中(Task’s Context Storage area)。 入栈以后,再将另外要运行任务的状态从它的堆栈区中提取出来,装入CPU寄存器,从而开始该任务的运行。 任务切换增加了应用程序的额外负荷。
10
内核(Kernel) 多任务系统中,内核提供的基本服务是任务切换,它将应用分成若干个任务来管理,为每个任务分配CPU时间,并且负责任务之间的通讯。 内核本身也增加了应用程序的额外负荷,代码空间增加ROM的用量,内核本身的数据结构增加了RAM的用量。而每个任务有自己的堆栈空间,占用了很多内存。另外,内核本身对CPU的时间占用为2%~5%。 通过提供必不可少的系统服务,如信号量管理、邮箱、消息队列、延时等,实时内核使CPU的利用变得更加有效。
11
调度(Scheduler) 另外一个词dispatcher,也是调度的意思。
内核的主要职责之一,基于每个任务的不同优先级,决定该轮到哪个任务运行。 基于优先级的调度法有两种不同的情况,根据内核是不可剥夺型的还是可剥夺型来区分。
12
不可剥夺型内核(Non-Preemptive Kernel)
要求每个任务自我放弃CPU的所有权,是合作型多任务,各个任务彼此合作共享一个CPU。 允许使用不可重入函数。 几乎不需要使用信号量来保护共享数据。运行的任务占有CPU,不必担心被别的任务抢占。 任务级响应时间要大大好于前后台系统,但仍是不可知的,商业软件几乎都没有用不可剥夺型内核。
14
可剥夺型内核(Preemptive Kernel)
当系统响应时间很重要时,就要使用可剥夺型内核。最高优先级的任务一旦就绪,总能得到CPU的控制权。 使用可剥夺型内核时,应用程序不应直接使用不可重入型函数,并且要利用互斥型信号量来满足互斥条件。 如果调用不可重入型函数,低优先级任务的CPU使用权被高优先级任务剥夺,不可重入型函数中的数据有可能被破坏。
16
可重入性(Reentrancy) 可重入型函数可以被一个以上的任务调用,而不必担心数据的破坏。
可重入型函数任何时候都可以被中断,一段时间以后又可以运行,而相应数据不会丢失。 可重入型函数可以只使用局部变量,即变量保存在CPU寄存器中或堆栈中。如果使用全局变量,则要对全局变量予以保护。
17
可重入型函数的例子 void strcpy(char *dest, char *src)
{ while (*dest++ = *src++) { ; } *dest = NUL; } 函数Strcpy()是字符串复制。因为参数是存在堆栈中的,所以函数Strcpy()可以被多个任务调用,而不必担心各个任务调用函数期间会互相破坏对方的指针。
18
不可重入型函数的例子 int Temp; void swap(int *x, int *y)
{Temp = *x; *x = *y; *y = Temp;} Swap() 函数使它的两个形式变量的值互换。用下列方法之一可使Swap()函数具有可重入性: 1)把Temp定义为局部变量 2)调用Swap()函数之前关中断,之后再开中断 3)用信号量禁止该函数在使用过程中被再次调用
19
不可重入型函数的例子
20
时间片轮番调度法 当两个或两个以上任务有同样优先级,内核允许一个任务运行事先确定的一段时间,叫做时间额度(quantum,也叫时间片调度),然后切换给另一个任务。在满足以下条件时,内核把CPU控制权交给下一个任务就绪态的任务: 1)当前任务已无事可做 2)当前任务在时间片还没结束时已经完成了。 目前,μC/OS-Ⅱ不支持时间片轮番调度法,因为应用程序中各任务的优先级必须互不相同。
21
任务优先级 静态优先级:应用程序执行过程中诸任务优先级不变。
动态优先级:应用程序执行过程中,任务的优先级是可变的,实时内核应当避免出现优先级反转问题。 优先级反转:高优先级任务在使用共享资源之前,首先必须得到该资源的信号量(Semaphore)。而低优先级任务始终占用着该资源的信号量,高优先级任务只能进入挂起状态,直到低优先级任务释放那个共享资源的信号量。这样就导致高优先级的任务实际上降为低优先级的任务。可以通过动态优先级加以解决。
22
优先级反转
23
优先级继承 为了纠正优先级反转,在任务3使用共享资源的时候,提升任务3的优先级。任务完成时予以恢复。任务3的优先级必须升至最高,高于允许使用该资源的任何任务。 为防止发生优先级反转,内核应该能自动变换任务的优先级,这叫做优先级继承。ucOS-II不支持优先级继承,其它一些商业内核具有该功能。
24
优先级继承
25
任务优先级分配 实时系统大多综合了软实时和硬实时两种需求。软实时系统要求任务执行得尽量快,并不要求在某一特定时间内完成。硬实时系统要求任务不但执行无误,还要准时完成。 RMS(Rate Monotonic Scheduling)基于任务执行频繁度的优先级分配法 ,用于分配任务优先级。 RMS定理认为最高执行频率的任务具有最高的优先级。
26
互斥条件 实现任务间通信最简单办法是使用共享数据结构。特别是当所有任务都在一个单一地址空间下,能使用全局变量、指针、缓冲区、链表、循环缓冲区的时候,使用共享数据结构通信就更容易。 虽然共享数据区的办法简化了任务间的信息交换,但是必须保证每个任务在处理共享数据时的排它性,以避免竞争和数据的破坏。 与共享资源打交道时,要满足互斥条件,方法有: 1)关中断 2)使用测试并置位指令 3)禁止做任务切换 4)利用信号量
27
1)关中断和开中断 处理共享数据时要保证互斥,最简便快捷的办法是关中断和开中断。 void Function (void)
{ OS_ENTER_CRITICAL(); /在这里处理共享数据/ OS_EXIT_CRITICAL(); }
28
2)测试并置位 如果不使用实时内核,当两个任务共享一个资源时,一定要约定好:先测试某一全局变量,如果该变量是0,允许该任务与共享资源打交道。为防止另一任务也要使用该资源,前者只要简单地将全局变量置为1,这通常称作测试并置位(Test-And-Set),即TAS操作。 TAS操作可能是微处理器的单独一条不会被中断的指令,或者是在程序中关中断做TAS操作再开中断。
29
利用测试并置位处理共享资源 Disable interrupts; 关中断
if (‘Access Variable’ is 0) { 如果“资源不可用”标志为0 Set variable to 1; 置“资源不可用”标志为1 Reenable interrupts; 重开中断 Access the resource; 处理该资源 Set the ‘Access Variable’ back to 0; 清“资源不可使用”标志为0 Reenable interrupts; 重新开中断} else { 否则 Reenable interrupts; 开中断 /* You don’t have access to the resource, try back later; */ /* 资源不可使用,以后再试; */}
30
3)先禁止然后允许任务切换 如果任务不与中断服务子程序共享变量或数据结构,可以使用禁止、然后允许任务切换。
此时虽然任务切换是禁止了,但中断还是开着的。如果这时中断来了,中断服务子程序会在这一临界区内立即执行。 中断服务子程序结束时,尽管有优先级高的任务已经进入就绪态,内核还是返回到原来被中断了的任务。直到执行完给任务切换开锁函数OSSchedUnlock (),内核再看有没有优先级更高的任务被中断服务子程序激活而进入就绪态,如果有,则做任务切换。
31
“禁止任务切换”与内核的任务调度与协调功能相悖,可以采取以下方法:先给任务切换上锁,然后开锁实现数据共享。
void Function (void) { OSSchedLock(); /任务切换上锁 . /* You can access shared data in here (interrupts are recognized) */. /*在这里处理共享数据(中断是开着的)*/ OSSchedUnlock(); /任务切换开锁 }
32
4)信号量(Semaphores) 信号量是60年代中期Edgser Dijkstra 发明的。
信号量实际上是一种授权、一种约定机制,在多任务内核中普遍使用,信号量用于: 1)控制共享资源的使用权(满足互斥条件) 2)标志某事件的发生 3)使两个任务的行为同步
33
4)信号量(Semaphores) 信号量像是一把授权钥匙,某个任务要运行下去,得先拿到这把授权钥匙。如果信号量已被别的任务占用,该任务只得被挂起,直到信号量被当前使用者释放。 一般对信号量只能实施三种操作:初始化(INITIALIZE)或建立(CREATE);等信号(WAIT)或挂起(PEND);给信号(SIGNAL)或发信号(POST)。初始化时要给信号量赋初值,等待信号量的任务表(Waiting list)应清空。
34
4)信号量(Semaphores) 任务以发信号操作(SIGNAL)释放信号量。
如果没有任务在等待信号量,信号量的值加1。如果有任务在等待该信号量,那么就会有一个任务进入就绪态,信号量的值不加1。于是授权钥匙给了等待信号量的诸任务中的一个任务。 至于给了哪个任务,要看内核是如何调度的。收到信号量的任务可能是以下两者之一: 1)等待信号量任务中优先级最高的(μC/OS-Ⅱ支持) 2)最早开始等待信号量的那个任务,即按先进先出的原则(First In First Out ,FIFO)
35
通过获得信号量处理共享数据 使用信号量之前,一定要初始化。作为互斥条件,信号量初始化为1。 OS_EVENT *SharedDataSem;
void Function (void) { INT8U err; OSSemPend(SharedDataSem, 0, &err);/与同一共享数据打交道的任务调用等待信号量函数OSSemPend() ./* You can access shared data in here (interrupts are recognized) */. /*共享数据的处理在此进行(中断是打开的)*/ OSSemPost(SharedDataSem);/任务处理完共享数据以后再调用释放信号量函数OSSemPost() }
36
用信号量得到打印机的使用权
37
隐含的信号量 通常情况,每个任务都知道有个信号表示资源可不可以使用。要想使用该资源,要先得到这个信号。
然而有些情况下,最好把信号量藏起来,各个任务在同某一资源打交道时,并不知道实际上是在申请得到一个信号量。
38
隐含的信号量 例如,多任务共享一个RS-232C外设接口,各任务要发送命令给接口另一端的设备并接收该设备的回应。
39
隐含的信号量程序 设信号量初值为1,表示允许使用。初始化是在通讯口驱动程序的初始化部分完成的。
INT8U CommSendCmd(char *cmd, char *response, INT16U timeout) { /Cmd指向送出的ASCII码字符串命令;Response指向外设回应的字符串;timeout指设定的时间间隔。 Acquire port's semaphore; /第一个调用CommSendCmd()函数的任务申请并得到了信号量 Send command to device; /向外设发送命令 Wait for response (with timeout);/等待外设响应 if (timed out) { Release semaphore; /释放信号量 return (error code); /外设超时不响应,则返回超时错误信息。 } else { Release semaphore; /释放信号量 return (no error); } }
40
计数式信号量 用于某资源可以同时为几个任务所用。例如,用信号量管理缓冲区阵列(buffer pool) 。
例如缓冲区阵列中共有10个缓冲区,任务通过调用申请缓冲区函数BufReq()向缓冲区管理方申请得到缓冲区使用权。当缓冲区使用权不再需要时,通过调用释放缓冲区函数BufRel()将缓冲区还给管理方。
41
用信号量管理缓冲区阵列(buffer pool)
42
用信号量管理缓冲区程序 BUF *BufReq(void) { BUF *ptr; Acquire a semaphore;
Disable interrupts; ptr = BufFreeList; BufFreeList = ptr->BufNext; Enable interrupts; return (ptr); }
43
用信号量管理缓冲区程序 void BufRel(BUF *ptr) { Disable interrupts;
ptr->BufNext = BufFreeList; BufFreeList = ptr; Enable interrupts; Release semaphore; }
44
用信号量管理缓冲区阵列 缓冲区阵列管理方可以满足前十个申请缓冲区的任务,就好像有10把钥匙可以发给诸任务。当所有的钥匙都用完了,申请缓冲区的任务被挂起,直到信号量重新变为有效。 缓冲区管理程序在处理链表指针时,为满足互斥条件,是关掉中断的。任务使用完某一缓冲区,通过调用缓冲区释放函数BufRel()将缓冲区还给系统。 系统先将该释放的缓冲区指针插入到空闲缓冲区链表中(Linked list),然后再给信号量加1或释放该信号量。这一过程隐含在缓冲区管理程序BufReq()和BufRel()之中,调用这两个函数的任务不用管函数内部的详细过程。
45
信号量的用法总结 请求和释放信号量的过程是要花时间的,对于简单的共享变量可以通过关中断、开中断来处理,以提高效率。
对于复杂的变量,如浮点数的处理,如果相应微处理器又没有硬件的浮点协处理器,这种情况下就有必要使用信号量了。
46
死锁(或抱死) (Deadlock (or Deadly Embrace))
指两个任务无限期地互相等待对方控制着的资源。 假设任务T1正独享资源R1,任务T2在独享资源R2,而此时T1又要独享R2,T2也要独享R1,于是哪个任务都没法继续执行了,因此发生了死锁。 最简单的防止发生死锁的方法是让每个任务都: 1)先得到全部需要的资源再做下一步的工作 2)用同样的顺序去申请多个资源 3)释放资源时使用相反的顺序 内核大多允许用户在申请信号量时定义等待超时,并返回系统错误的代码,以此化解死锁。
47
同步 可以利用信号量使某任务与中断服务同步(或与另一个任务同步,这两个任务间没有数据交换)。
信号量用来指示某一事件的发生(不再是一把用来保证互斥条件的授权钥匙)。
48
单向同步 用来实现同步机制的信号量初始化成0,信号量用于这种类型同步的时候称为单向同步(unilateral rendezvous)。
49
双向同步 两个任务可以用两个信号量同步它们的行为。这叫做双向同步(bilateral rendezvous)。双向同步同单向同步类似,只是两个任务要相互同步。 在任务与中断服务之间不能使用双向同步,因为在中断服务中不可能等一个信号量。
50
两个任务双向同步程序 Task1(){ for (;;) { Perform operation; Signal task #2; (1)
Wait for signal from task #2; (2) Continue operation; } } Task2(){ for (;;) { Perform operation; Signal task #1; (3) Wait for signal from task #1; (4) }
51
事件标志(Event Flags) 当某任务要与多个事件同步时,要使用事件标志。 任务与任何事件之一发生同步,称为独立型同步(逻辑或关系)。
任务与若干事件都发生了同步,称之为关联型同步(逻辑与关系)。
52
独立型及关联型同步
53
多个事件的组合信号 典型地,8个、16个或32个事件可以组合在一起,取决于用的哪种内核。每个事件占一位(bit),以32位的情况为多。
任务或中断服务可以给某一位置位或复位,当任务所需的事件都发生了(在事件位置位时做判断),该任务继续执行。 内核如果支持事件标志,则提供事件标志置位、事件标志清零和等待事件标志等服务。
54
多个事件的组合信号
55
任务间的通信(Intertask Communication)
在任务之间或中断服务与任务间的信息传递称为任务间的通信。 任务间的通信有两个途径:通过全局变量或发消息给另一个任务。 用全局变量时,必须保证每个任务或中断服务程序独享该变量。中断服务中保证独享的唯一办法是关中断。如果两个任务共享某变量,各任务实现独享该变量的办法可以是关中断再开中断,或使用信号量(如前面提到的那样)。 任务只能通过全局变量与中断服务程序通讯,而任务并不知道什么时候全局变量被中断服务程序修改了,除非中断程序以信号量方式向任务发信号或者是该任务以查询方式不断周期性地查询变量的值。要避免这种情况,可以考虑使用邮箱或消息队列。
56
消息邮箱(Message Mail boxes)
典型的消息邮箱也称作交换消息。 一个任务或一个中断服务程序通过内核服务,用一个指针型变量,把一则消息(指针所指向的内容)放到邮箱里去。同样,一个或多个任务可以通过内核服务接收这则消息。
57
消息邮箱(Message Mail boxes)
每个邮箱有相应的正在等待消息的任务列表,要得到消息的任务会因为邮箱是空的而被挂起,且被记录到等待消息的任务表中,直到收到消息。 内核允许用户定义等待超时,等待消息的时间超过了,仍然没有收到该消息,任务会进入就绪态,并返回出错信息。 消息放入邮箱后,或者是把消息传给等待消息的任务表中优先级最高的那个任务(基于优先级),或者是将消息传给最先开始等待消息的任务(基于先进先出)。
58
消息邮箱(Message Mail boxes)
内核一般提供以下邮箱服务: 1)邮箱内消息内容的初始化,邮箱里最初可以有,也可以没有消息 2)将消息放入邮箱(POST) 3)等待有消息进入邮箱(PEND) 4)如果邮箱内有消息,就接受这则消息。如果邮箱里没有消息,则用返回代码表示调用结果,是收到了消息还是没有收到消息。 消息邮箱也可以当作只取两个值的信号量来用。邮箱里有消息,表示资源可以使用,而空邮箱表示资源已被其它任务占用。
59
消息邮箱(Message Mail boxes)
把消息放入邮箱。 用一个I字表示邮箱,旁边的小砂漏表示超时计时器,计时器旁边的数字表示定时器设定值,即任务最长可以等多少个时钟节拍(Clock Ticks) 。
60
消息队列(Message Queue) 消息队列实际上是邮箱阵列,用于给任务发消息。
通过内核提供的服务,任务或中断服务子程序可以将一条消息(该消息的指针)放入消息队列。同样,一个或多个任务可以通过内核服务从消息队列中得到消息。 通常,任务先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。而μC/OS-Ⅱ也允许使用后进先出方式(LIFO)。
61
消息队列(Message Queue) 类似于邮箱的使用
两个大写的I表示消息队列,“10”表示消息队列最多可以放10条消息,沙漏旁边的0表示任务没有定义超时,将永远等下去,直至消息的到来。
62
消息队列(Message Queue) 内核提供的消息队列服务如下: 1)消息队列初始化。队列初始化时总是清为空。
2)放一则消息到队列中去(Post) 3)等待一则消息的到来(Pend) 4)如果队列中有消息则任务可以得到消息,但如果此时队列为空,内核并不将该任务挂起(Accept)。如果有消息,则将消息从队列中取走;没有消息则用特别的返回代码通知调用者,队列中没有消息。
63
中断 中断是一种硬件机制,用于通知CPU有个异步事件发生了,微处理器一般允许中断嵌套。
中断一旦被识别,CPU保存部分(或全部)现场(Context),即部分或全部寄存器的值,跳转到中断服务子程序(ISR)。 中断服务子程序做事件处理,处理完成后,程序回到: 1)在前后台系统中,程序回到后台程序 2)对不可剥夺型内核而言,程序回到被中断了的任务 3)对可剥夺型内核而言,让进入就绪态的优先级最高的任务开始运行
64
中断延迟 实时内核最重要的指标就是关中断的时间。所有实时系统在进入临界区代码段之前都要关中断,执行完临界代码之后再开中断。关中断的时间越长,中断延迟就越长。 中断延迟 = 关中断的最长时间 + 开始执行中断服务子程序的第一条指令的时间
65
中断响应 中断响应定义为从中断发生到开始执行用户的中断服务子程序代码来处理这个中断的时间。
中断响应时间包括开始处理这个中断前的全部开销。例如执行用户代码之前要保护现场,将CPU的各寄存器推入堆栈,这段时间将被记作中断响应时间。
66
中断响应 前后台系统的中断响应时间 = 中断延迟 + 保存CPU内部寄存器的时间
中断响应是系统在最坏情况下的响应中断的时间
67
中断恢复时间(Interrupt Recovery)
前后台系统的中断恢复时间 = 恢复CPU内部寄存器值的时间 + 执行中断返回指令的时间 不可剥夺型内核的中断恢复时间 = 恢复CPU内部寄存器值的时间 + 执行中断返回指令的时间 可剥夺型内核的中断恢复时间 = 判定是否有优先级更高的任务进入了就绪态的时间 + 恢复那个优先级更高任务的CPU内部寄存器的时间 + 执行中断返回指令的时间
68
中断延迟、响应和恢复 包括前后台系统、不可剥夺性内核、可剥夺性内核相应的中断延迟、响应和恢复过程。
对于可剥夺型实时内核,中断返回函数将决定是返回到被中断的任务,还是让那个优先级最高任务运行。
69
中断处理时间 虽然中断服务的处理时间应该尽可能的短,但是对处理时间并没有绝对的限制。
70
非屏蔽中断(NMI) 留做紧急处理用,用于时间要求最苛刻的中断服务,如断电时保存重要的信息。
中断延迟时间 = 指令执行时间中最长的那个时间 + 开始做非屏蔽中断服务的时间 中断响应时间 = 中断延迟时间 + 保存CPU寄存器花的时间 中断恢复时间 = 恢复CPU寄存器的时间 + 执行中断返回指令的时间。
71
非屏蔽中断(NMI) 在非屏蔽中断的中断服务子程序中,不能使用内核提供的服务,因为非屏蔽中断是关不掉的,所以不能在非屏蔽中断处理中处理临界区代码。 可以向非屏蔽中断传送参数或从非屏蔽中断获取参数。参数的传递必须使用全局变量,全局变量的位数必须是一次完成读或写,即不应该是两个分离的字节,要两次读或写才能完成。
72
时钟节拍(Clock Tick) 时钟节拍是特定的周期性中断。中断之间的时间间隔取决于不同的应用,一般在10mS到200mS之间。
时钟节拍的中断使得内核可以将任务延时若干个整数时钟节拍,以及当任务等待事件发生时,提供等待超时的依据。时钟节拍越快,系统的额外开销就越大。
73
对存储器的需求 8位CPU最小内核的总代码量 = 应用程序代码 + 内核代码
不支持单独中断用栈的内核的RAM总需求 = 应用程序的RAM需求 + (任务栈需求 + 最大中断嵌套栈需求) * 任务数 支持单独中断用栈的内核的RAM总需求 = 应用程序的RAM需求 + 内核数据区的RAM需求 + 各任务栈需求之总和 + 最多中断嵌套之栈需求 多任务系统比前后台系统需要更多的代码空间(ROM)和数据空间(RAM)。额外的代码空间取决于内核的大小,而RAM的用量取决于系统中的任务数。
74
使用实时内核的优缺点 使得实时应用程序的设计和扩展变得容易,不需要大的改动就可以增加新的功能。通过有效的服务,如信号量、邮箱、队列、延时、超时等,RTOS使得资源得到更好的利用。 使用实时内核会增加价格成本,如软件维护成本。 如果应用项目对额外的需求可以承受,应该考虑使用实时内核。这些额外的需求包括:内核的价格,额外的ROM/RAM开销,2%~4%的CPU额外负荷。
Similar presentations