Presentation is loading. Please wait.

Presentation is loading. Please wait.

UCOS -II的使用 撰写:李湧 2006-06-29.

Similar presentations


Presentation on theme: "UCOS -II的使用 撰写:李湧 2006-06-29."— Presentation transcript:

1 UCOS -II的使用 撰写:李湧 2006-06-29

2 提纲 1 2 3 4 5 6 7 uCOS-II操作系统的特点 uCOS -II操作系统内核结构 uCOS -II操作系统任务管理

3 µC/OS-II的特点 µC/OS-II的特点 UC/OS是一个非常小巧的实时操作系统,称为“微内核OS”;
整个代码分为内核层以及移植层,这样使得它的植性很方便。 采用抢占式调度策略,保证任务的实时性。 能够管理多达64个任务。 提供了邮箱、消息队列、信号量、内存管理、时间管理等系统服务。

4 临界区(Critical Sections)
C/OS-II的内核结构 临界区(Critical Sections) 为了实现资源共享,一个操作系统必须提供临界区的功能。 μC/OS-Ⅱ为了处理临界区代码需要关中断,处理完毕后再开中断。这使得μC/OS-Ⅱ能够避免同时有其它任务或中断服务进入临界区代码。 μC/OS-Ⅱ定义两个宏(macros)来开关中断。分别是:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。 这两个宏的定义取决于所用的微处理器,每种微处理器都有自己的OS_CPU.H文件。 调度器上锁和开锁(Locking and UnLocking the Scheduler) l        μC/OS-Ⅱ是怎样处理临界段代码的; l        什么是任务,怎样把用户的任务交给μC/OS-Ⅱ; l        任务是怎样调度的; l        应用程序CPU的利用率是多少,μC/OS-Ⅱ是怎样知道的; l        怎样写中断服务子程序; l        什么是时钟节拍,μC/OS-Ⅱ是怎样处理时钟节拍的; l        μC/OS-Ⅱ是怎样初始化的,以及 怎样启动多任务;

5 任务(task) 一个任务通常是一个无限循环: void mytask(void *pdata) { do init;
C/OS-II的内核结构 任务(task) 一个任务通常是一个无限循环: void mytask(void *pdata) { do init; while (1) { do something; waiting; }

6 void YourTask (void *pdata)
C/OS-II的内核结构 任务(task) uCos中的任务可以自我删除     void YourTask (void *pdata)     {        /* 用户代码 */        OSTaskDel(OS_PRIO_SELF);     }

7 任务状态 (task States) C/OS-II的任务管理 等待或挂起 收到消息 等待消息 挂起 休眠 就绪 运行 中断服务 中断
删除任务 中断 中断结束 创建任务 任务调度 任务被占先 等待消息 挂起 收到消息 挂起时间到

8 任务(task) μC/OS-Ⅱ可以管理多达64个任务。
C/OS-II的内核结构 任务(task) μC/OS-Ⅱ可以管理多达64个任务。 保留了优先级为0、1、2、3、OS_LOWEST_PRIO-3、OS_LOWEST_PRI0-2,OS_LOWEST_PRI0-1以及OS_LOWEST_PRI0这8个任务以被将来使用。 用户可以有多达56个应用任务。必须给每个任务赋以不同的优先级。优先级号越低,任务的优先级越高。

9 μC/OS中,中断服务子程序要尽可能短。 用户中断服务子程序框架: 保存全部CPU寄存器;
C/OS-II的内核结构 μC/OS中的中断处理 μC/OS中,中断服务子程序要尽可能短。 用户中断服务子程序框架: 保存全部CPU寄存器; 调用OSIntEnter或OSIntNesting直接加1; 执行用户代码做中断服务; 调用OSIntExit(); 恢复所有CPU寄存器; 执行中断返回指令;

10 OSIntEnter() void OSIntEnter (void) { OS_ENTER_CRITICAL();
C/OS-II的内核结构 OSIntEnter() void OSIntEnter (void) { OS_ENTER_CRITICAL(); OSIntNesting++; OS_EXIT_CRITICAL(); }

11 OSIntExit C/OS-II的内核结构 OS_ENTER_CRITICAL();
if ((--OSIntNesting | OSLockNesting) == 0) { OSIntExitY = OSUnMapTbl[OSRdyGrp]; OSPrioHighRdy = (INT8U)((OSIntExitY << 3) + OSUnMapTbl[OSRdyTbl[OSIntExitY]]); if (OSPrioHighRdy != OSPrioCur) { OSTCBHighRdy=OSTCBPrioTbl[OSPrioHighRdy]; OSCtxSwCtr++; OSIntCtxSw(); } } OS_EXIT_CRITICAL();

12 中断与时钟节拍 C/OS-II的内核结构
当发生中断时,首先应保护现场,将CPU寄存器入栈,再处理中断函数,然后恢复现场,将CPU寄存器出栈,最后执行中断返回。 uC/OS中提供了OSIntEnter()和OSIntExit()告诉内核进入了中断状态。 时钟节拍是一种特殊的中断,操作系统的脉搏。对任务列表进行扫描,判断是否有延时任务应该处于准备就绪状态,最后进行上下文切换。

13 C/OS-II的内核结构 时钟节拍 μC/OS需要用户提供周期性信号源,用于实现时间延时和确认超时。节拍率应在说10到100Hz。时钟节拍率越高,系统的额外负荷就越重。 时钟节拍的实际频率取决于用户应用程序的精度。时钟节拍源可以是专门的硬件定时器,也可以是来自50/60Hz交流电源的信号。 用户必须在多任务系统启动以后再开启时钟节拍器,也就是在调用OSStart()之后。

14 OSTickISR void OSTickISR(void) { 保存处理器寄存器的值;
C/OS-II的内核结构 OSTickISR void OSTickISR(void) { 保存处理器寄存器的值; 调用OSIntEnter()或是将OSIntNesting加1; 调用OSTimeTick();   调用OSIntExit(); 恢复处理器寄存器的值; 执行中断返回指令; }

15 μC/OS-Ⅱ初始化 在调用μC/OS-Ⅱ的任何其它服务之前,μC/OS-Ⅱ要求用户首先调用系统初始化函数OSInit()。
C/OS-II的内核结构 μC/OS-Ⅱ初始化 在调用μC/OS-Ⅱ的任何其它服务之前,μC/OS-Ⅱ要求用户首先调用系统初始化函数OSInit()。 OSInit()建立空闲任务idle task,这个任务总是处于就绪态的。空闲任务OSTaskIdle()的优先级总是设成最低,即OS_LOWEST_PRIO。 μC/OS-Ⅱ还初始化了4个空数据结构缓冲区。

16 …… μC/OS-Ⅱ的启动 多任务的启动是用户通过调用OSStart()实现的。然而,启动μC/OS-Ⅱ之前,用户至少要建立一个应用任务。
C/OS-II的内核结构 μC/OS-Ⅱ的启动 多任务的启动是用户通过调用OSStart()实现的。然而,启动μC/OS-Ⅱ之前,用户至少要建立一个应用任务。 OSInit(); /* 初始化uC/OS-II*/ …… 调用OSTaskCreate()或OSTaskCreateExt(); OSStart(); /*开始多任务调度!永不返回 */

17 任务控制块 OS_TCB是一个数据结构,保存该任务的相关参数,包括任务堆栈指针,状态,优先级,任务表位置,任务链表指针等。
C/OS-II的任务管理 任务控制块(TCB) 任务控制块 OS_TCB是一个数据结构,保存该任务的相关参数,包括任务堆栈指针,状态,优先级,任务表位置,任务链表指针等。 一旦任务建立了,任务控制块OS_TCBs将被赋值。 所有的任务控制块分为两条链表,空闲链表和使用链表。

18 任务控制块结构的主要成员 C/OS-II的任务管理 OS_STK *OSTCBStkPtr; /*当前任务栈顶的指针*/
struct os_tcb *OSTCBNext; /*任务控制块的双重链接指针*/ struct os_tcb *OSTCBPrev; /*任务控制块的双重链接指针*/ OS_EVENT *OSTCBEventPtr; /*事件控制块的指针*/ void *OSTCBMsg; /*消息的指针*/ INT16U OSTCBDly; /*任务延时*/ INT8U OSTCBStat; /*任务的状态字*/ INT8U OSTCBPrio; /*任务优先级*/ INT8U OSTCBX; /*用于加速进入就绪态的过程*/ INT8U OSTCBY; /*用于加速进入就绪态的过程*/ INT8U OSTCBBitX; /*用于加速进入就绪态的过程*/ INT8U OSTCBBitY;  /*用于加速进入就绪态的过程*/

19 想让µC/OS-Ⅱ管理用户的任务,用户必须要先建立任务。 用户可以通过传递任务地址和其它参数到以下两个函数之一来建立任务:
C/OS-II的任务管理 任务创建 想让µC/OS-Ⅱ管理用户的任务,用户必须要先建立任务。 用户可以通过传递任务地址和其它参数到以下两个函数之一来建立任务: OSTaskCreate() OSTaskCreateExt()。 任务不能由中断服务程序(ISR)来建立。

20 任务调度(Task Scheduling)
C/OS-II的任务管理 任务调度(Task Scheduling) C/OS是抢占式实时多任务内核,优先级最高的任务一旦准备就绪,则拥有CPU的所有权开始投入运行。 C/OS中不支持时间片轮转法,每个任务的优先级要求不一样且是唯一的,所以任务调度的工作就是:查找准备就绪的最高优先级的任务并进行上下文切换。

21 任务调度(Task Scheduling)
C/OS-II的任务管理 任务调度(Task Scheduling) μC/OS-Ⅱ总是运行进入就绪态任务中优先级最高的那一个。确定哪个任务优先级最高,下面该哪个任务运行了的工作是由调度器(Scheduler)完成的。 任务级的调度是由函数OSSched()完成的。中断级的调度是由另一个函数OSIntExt()完成的,这个函数将在以后描述。

22 任务调度器(Scheduler) C/OS-II的任务管理 void OSSched (void) { INT8U y;
  OS_ENTER_CRITICAL(); if ((OSLockNesting | OSIntNesting) == 0) { y = OSUnMapTbl[OSRdyGrp]; OSPrioHighRdy = (INT8U)((y << 3) OSUnMapTbl[OSRdyTbl[y]]); if (OSPrioHighRdy != OSPrioCur) { OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; OSCtxSwCtr++; OS_TASK_SW(); } OS_EXIT_CRITICAL();

23 C/OS-II的任务管理 任务切换(Task switching) 将被挂起的任务寄存器入栈 将最高优先级任务的寄存器出栈

24 任务级的任务切换OS_TASK_SW()
C/OS-II的任务管理 任务级的任务切换OS_TASK_SW() 通过系统调用指令完成 保护当前任务的现场 恢复新任务的现场 执行中断返回指令 开始执行新的任务

25 任务切换OS_TASK_SW()的代码 C/OS-II的任务管理 Void OSCtxSw(void) {
将R1,R2,R3及R4推入当前堆栈; OSTCBCurOSTCBStkPtr = SP; OSTCBCur = OSTCBHighRdy; SP = OSTCBHighRdy OSTCBSTKPtr; 将R4,R3,R2及R1从新堆栈中弹出; 执行中断返回指令; }

26 内存管理 在ANSI C中可以用malloc()和free()两个函数动态地分配内存和释放内存。在嵌入式实时操作系统中,容易产生内存碎片。
C/OS-II的内存管理 内存管理 在ANSI C中可以用malloc()和free()两个函数动态地分配内存和释放内存。在嵌入式实时操作系统中,容易产生内存碎片。 µC/OS-II中,操作系统把连续的大块内存按分区来管理。每个分区中包含有整数个大小相同的内存块。 在一个系统中可以有多个内存分区。这样,用户的应用程序就可以从不同的内存分区中得到不同大小的内存块。但是,特定的内存块在释放时必须重新放回它以前所属于的内存分区。

27 C/OS-II的内存管理 内存分区示意图

28 C/OS-II的内存管理 内存控制块 为了便于内存的管理,在µC/OS-II中使用内存控制块(memory control blocks)的数据结构来跟踪每一个内存分区,系统中的每个内存分区都有它自己的内存控制块。 typedef struct { void *OSMemAddr; /*分区起始地址*/ void *OSMemFreeList; /*下一个空闲内存块*/ INT32U OSMemBlkSize; /*内存块的大小*/ INT32U OSMemNBlks; /*内存块数量*/ INT32U OSMemNFree; /*空闲内存块数量 */ } OS_MEM;

29 C/OS-II的内存管理 内存管理初始化 如果要在µC/OS-II中使用内存管理,需要在OS_CFG.H文件中将开关量OS_MEM_EN设置为1。这样µC/OS-II 在启动时就会对内存管理器进行初始化 (OSMemInit() )。

30 建立一个内存分区,OSMemCreate()
C/OS-II的内存管理 建立一个内存分区,OSMemCreate() 在使用一个内存分区之前,必须使用OSMemCreate()先建立该内存分区。该函数共有4个参数:内存分区的起始地址、分区内的内存块总块数、每个内存块的字节数和一个指向错误信息代码的指针。 每个内存分区必须含有至少两个内存块,每个内存块至少为一个指针的大小 。 OS_MEM *CommTxBuf; INT8U CommTxPart[100][32]; CommTxBuf = OSMemCreate(CommTxPart, 100, 32, &err);

31 C/OS-II的内存管理 分配一个内存块,OSMemGet() 调用OSMemGet()函数从已经建立的内存分区中申请一个内存块。该函数的唯一参数是指向特定内存分区的指针,该指针在建立内存分区时,由OSMemCreate()函数返回。 注意的是,用户可以在中断服务子程序中调用OSMemGet(),因为在暂时没有内存块可用的情况下,OSMemGet()不会等待,而是马上返回NULL指针。

32 C/OS-II的内存管理 释放一个内存块,OSMemPut() 应用程序不再使用一个内存块时,必须及时地把它释放并放回到相应的内存分区中。这个操作由OSMemPut()函数完成。 必须注意的是,OSMemPut()并不知道一个内存块是属于哪个内存分区的。 释放内存块时必须将它释放到正确的分区。

33 C/OS-II的时间管理 时间管理 µC/OS-Ⅱ(其它内核也一样)要求用户提供定时中断来实现延时与超时控制等功能。这个定时中断叫做时钟节拍,它应该每秒发生10至100次。时钟节拍的频率越高,系统的负荷就越重。 与时钟管理相关的系统服务有: OSTimeDLY(INT16U ticks ) OSTimeDLYHMSM(INT8U hours, INT8U minutes, INT8U seconds, INT16U milli ) OSTimeDlyResmue(INT8U prio ) OStimeGet(void) OSTimeSet(INT32U ticks ) 不可以延时另一个任务 如果一个任务处理结果中,要求另一个任务延迟一段时间,怎么实现?

34 任务间交互方法 数据共享 任务间通讯 C/OS中,采用多种方法保护任务之间的共享数据和提供任务之间的通信。
C/OS-II的任务间通讯 任务间交互方法 C/OS中,采用多种方法保护任务之间的共享数据和提供任务之间的通信。 数据共享 提供OS_ENTER_CRITICAL和OS_EXIT_CRITICAL来对临界资源进行保护 OSSchedLock( )禁止调度保护任务级的共享资源。 任务间通讯 提供了经典操作系统任务间通信方法:信号量、邮箱、消息队列,标志。

35 事件控制块ECB C/OS-II的任务间通讯
所有的通信信号都被看成是事件(event), 一个称为事件控制块(ECB, Event Control Block)的数据结构来表征每一个具体事件,ECB的结构如下: typedef struct { void *OSEventPtr; /*指向消息或消息队列的指针*/ INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /*等待任务列表*/ INT16U OSEventCnt; /*计数器(事件是信号量时)*/ INT8U OSEventType; /*事件类型:信号量、邮箱等*/ INT8U OSEventGrp; /*等待任务组*/ } OS_EVENT; 与TCB类似的结构,使用两个链表,空闲链表与使用链表

36 信号量 semaphore C/OS-II的任务间通讯
信号量在多任务系统中用于:控制共享资源的使用权、标志事件的发生、使两个任务的行为同步。 µC/OS-II提供了5种对信号量的操作: 建立一个信号量, OS_EVENT *OSSemCreate (INT16U cnt) 等待一个信号量, void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) 无等待地请求一个信号量, INT16U OSSemAccept (OS_EVENT *pevent) 释放一个信号量, INT8U OSSemPost (OS_EVENT *pevent) 查询一个信号量的当前状态, INT8U OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *pdata)

37 互斥信号量 mutual exclusion semaphore
C/OS-II的任务间通讯 互斥信号量 mutual exclusion semaphore 信号量在多任务系统中用于:控制共享资源的使用权、标志事件的发生、使两个任务的行为同步。 µC/OS-II提供了6种对互斥信号量的操作: OS_EVENT *OSMutexCreate(INT8U prio, INT8U *err); void OSMutexPend(OS_EVENT *pevent, INT16U timeout, INT8U *err); INT8U OSMutexAccept(OS_EVENT *pevent, INT8U *err); OS_EVENT *OSMutexDel(OS_EVENT *pevent, INT8U opt, INT8U *err); INT8U OSMutexPost(OS_EVENT *pevent); INT8U OSMutexQuery(OS_EVENT *pevent, OS_MUTEX_DATA *pdata);

38 邮箱 mailbox C/OS-II的任务间通讯
使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。该指针指向一个包含了特定“消息”的数据结构 一个mailbox中只能有一个消息 µC/OS-II提供了5种对邮箱的操作: OS_EVENT *OSMboxCreate (void *msg) void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) void *OSMboxAccept (OS_EVENT *pevent) INT8U OSMboxPost (OS_EVENT *pevent, void *msg) INT8U OSMboxQuery (OS_EVENT *pevent, OS_MBOX_DATA *pdata)

39 消息队列 message queue C/OS-II的任务间通讯 使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量。
一个message queue中可以包含多个message,FIFO原则 µC/OS-II提供了7种对消息队列的操作: OS_EVENT *OSQCreate (void **start, INT16U size) void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) INT8U OSQPost (OS_EVENT *pevent, void *msg) INT8U OSQPostFront (OS_EVENT *pevent, void *msg) //LIFO void *OSQAccept (OS_EVENT *pevent) INT8U OSQFlush (OS_EVENT *pevent) INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata)

40 标志 flag C/OS-II的任务间通讯 用来标志系统中任何‘事情’的发生,限于二值逻辑 一个标志用一位表示,
8个标志占用一个字节,称为一个标志组 µC/OS-II提供了6种对事件标志的操作: OS_FLAG_GRP *OSFlagCreate(OS_FLAGS flags, INT8U *err); OS_FLAGS OSFlagPend(OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U wait_type, INT16U timeout, INT8U *err); OS_FLAGS OSFlagAccept(OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U wait_type, INT8U *err); OS_FLAGS OSFlagPost(OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U operation, INT8U *err); OS_FLAG_GRP *OSFlagDel(OS_FLAG_GRP *pgrp, INT8U opt, INT8U *err); OS_FLAGS OSFlagQuery(OS_FLAG_GRP *pgrp, INT8U *err);

41 总结 不存在一个内核任务/实体,内核的管理是通过调用系统函数来实现的。
C/OS-II的任务间通讯 总结 不存在一个内核任务/实体,内核的管理是通过调用系统函数来实现的。 每个任务有自己的堆栈空间。内核对任务的占先式调度不会干扰每个任务的总的运行结果。 如果一个任务处理结果中,要求另一个任务延迟一段时间,怎么实现?

42 C/OS-II的移植 COS-II在ARM7上的移植 所谓移植,是指使一个实时操作系统能够在某个微处理器平台上运行。COS-II的主要代码都是由标准的C语言写成的,移植方便。

43 移植COS-II满足的条件 处理器的C编译器能产生可重入代码 在程序中可以打开或者关闭中断
处理器支持中断,并且能产生定时中断(通常在10—1000Hz之间) 处理器支承能够容纳一定量数据的硬件堆栈 处理器有将堆栈指针和其他CPU寄存器存储和读出到堆栈(或者内存)的指令

44 COS-II的数据类型 C/OS-II的移植
由于C语言的数据类型的长度是与编译器类型相关的,UC/OS考虑到在各个平台的可移植 性,没有使用C语言的数据类型,而是定义了自己的数据类型。(定义在OS_CPU.H): 类型代码 类型 宽度 BOOLEAN 布尔型 8 INT8U 8位无符号整数 8 INT8S 8位有符号整数 8 INT16U 16位无符号整数 16 INT16S 16为有符号整数 16 INT32U 32位无符号整数 32 INT32S 32位有符号整数 32 FP32 单精度浮点数 32 FP64 双精度浮点数 64

45 打开/关闭中断 在COS-II中,可以通过: OS_ENTER_CRITICAL () OS_EXIT_CRITICAL()
宏来控制系统关闭或者打开中断。这需要处理器的支持。 在ARM7TDMI的处理器上,可以设置相应的寄存器来关闭或者打开系统的所有中断。

46 C/OS-II的移植 处理器支持中断并且能产生定时中断 COS-II是通过处理器产生的定时器的中断来实现多任务之间的调度的。ARM7TDMI的处理器上可以产生定时器中断。 本系统工作在60MHz的主频下,定时器的中断的频率为100Hz。也就是系统的响应时间为10ms。

47 C/OS-II的移植 处理器支持硬件堆栈 COS-II进行任务调度的时候,会把当前任务的CPU寄存器存放到此任务的堆栈中,然后,再从另一个任务的堆栈中恢复原来的工作寄存器,继续运行另一个任务。所以,寄存器的入栈和出栈是COS-II多任务调度的基础。 ARM7处理器中有专门的指令处理堆栈,可以灵活的使用堆栈。

48 设置OS_CPU.H中与处理器和编译器相关的代码 用C语言编写六个操作系统相关的函数(OS_CPU_C.C)
C/OS-II的移植 例:C/OS-II在S3C44B0X上的移植 设置OS_CPU.H中与处理器和编译器相关的代码 用C语言编写六个操作系统相关的函数(OS_CPU_C.C) 用汇编语言编写四个与处理器相关的函数(OS_CPU.ASM)

49 设置与处理器和编译器相关的代码 OS_CPU.H中定义了与编译器相关的数据类型。比如:INT8U、INT8S等。
C/OS-II的移植 设置与处理器和编译器相关的代码 OS_CPU.H中定义了与编译器相关的数据类型。比如:INT8U、INT8S等。 与 ARM处理器相关的代码,使用 OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL() 宏开启/关闭中断 设施堆栈的增长方向 :堆栈由高地址向低地址增长

50 C/OS-II的移植 用C语言编写六个操作系统相关的函数 void *OSTaskStkInit (void (*task)(void *pd),void *pdata, void *ptos, INT16U opt) void OSTaskCreateHook (OS_TCB *ptcb) void OSTaskDelHook (OS_TCB *ptcb) void OSTaskSwHook (void) void OSTaskStatHook (void) void OSTimeTickHook (void) 后5个函数为钩子函数,可以不加代码

51 用汇编语言编写四个与处理器相关的函数 OSStartHighRdy() OSCtxSw() OSIntCtxSw() OSTickISR()
C/OS-II的移植 用汇编语言编写四个与处理器相关的函数 OSStartHighRdy() OSCtxSw() OSIntCtxSw() OSTickISR()

52 移植要点 定义函数OS_ENTER_CRITICAL和OS_ENTER_CRITICAL。 定义函数OS_TASK_SW执行任务切换。
C/OS-II的移植 移植要点 定义函数OS_ENTER_CRITICAL和OS_ENTER_CRITICAL。 定义函数OS_TASK_SW执行任务切换。 定义函数OSCtxSw实现用户级上下文切换,用纯汇编实现。 定义函数OSIntCtxSw实现中断级任务切换,用纯汇编实现。 定义函数OSTickISR。 定义OSTaskStkInit来初始化任务的堆栈。

53 Success Together


Download ppt "UCOS -II的使用 撰写:李湧 2006-06-29."

Similar presentations


Ads by Google