嵌入式操作系统uCOS2复习指南

更新时间:2024-03-13 22:25:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

复习: 第一章:

实时操作系统、操作系统基本功能、任务、多任务、任务状态及相互关系、任务切换、可重入和不可重入;可剥夺和不可剥夺内核;同步与通信:同步、互斥、临界区、事件、信号量、互斥信号量、消息邮箱、消息队列;中断、时钟、内存管理。

第二章: 任务管理:

任务控制块TCB数据结构及各数据项意义 任务控制块实体

任务控制块空闲链表、就绪链表 优先级指针表 任务堆栈

任务就绪表及就绪组及相关代码

图2.16:任务状态转换图,要弄清楚任务各状态及转换条件

程序2.6,2.7,2.8和2.9,获取就绪任务中的最高优先级,能给出OsRdyGrp和OsRdyTbl后,依据程序,算出最高优先级;并且说明处理时间是恒定的

程序2.10、2.11、2.14、2.15、2.17、2.27、2.28、2.29、2.30、2.34分析

第三章 中断和时间管理 中断处理流程,图3.1

时钟中断服务,程序3.2,OSTIMETICK(程序2.27) 任务延迟函数OSTIMEDLY作用及代码分析(程序3.4)

第4章

ECB数据结构

事件等待组、等待表作用,与就绪组合就绪表有何联系和不同 事件控制块空闲链表及ECB初始化函数(程序4.3) 事件等待函数(程序4.5) 将等待事件就绪(程序4.8)

信号管理:OSSEMCREAT、OSSEMDEL、OSSEMPEND、OSSEMPOST 4.3.9:信号量应用举例

互斥信号管理:OSMutexCreat、OSMutexDEL、OSMutexPEND、OSMUtexPOST 优先级反转

解决优先级反转采用何种策略 4.4.8:互斥信号量应用举例

第5章

5.1 消息邮箱:OSMBOXCREAT、DEL、PEND、POST 5.1.8 例子

5.2消息队列:Os_QInit,OsQCreat;POST;PEND 消息队列数据结构:图5.8到5.11 5.2.8 例子

第6章内存管理 内存控制块数据结构 MCB链表

Os_MemInit();OsMemCreat();OsMemGet();OsMemPut()

设内存区有6个块构成,依次画出4个图:内存块创建后、分配一个块后、再分配两个块后、释放第一次分配的块后的结构图。 实例 实验内容

μC/OS-II实时操作系统

1. 嵌入式实时操作系统μC/OS-II内核分析 1.1 μC/OS-II简介

μC/OS-II是一个源码公开、可移植、可固化、可裁剪、占先式、支持多任务的实时操作系统,最初是由Jean J.Labrosse先生撰写的,前身是μC/OS。应用面覆盖了诸多领域,如照相机、医疗器械、音响设备、发动机控制、高速公路电话系统、自动提款机等。

μC/OS-II 是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C语言编写的。CPU 硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种其它的CPU 上。用户只要有标准的ANSI 的C交叉编译器,有汇编器、连接器等软件工具,就可以将μC/OS-II嵌人到开发的产品中。μC/OS-II 具有执行效率高、占用空间小、实时性能优良和可扩展性强等特点, 最小内核可编译至 2KB 。μC/OS-II 已经移植到了几乎所有知名的CPU 上。

1.2 μC/OS-II内核结构

在多任务系统中,内核负责管理各个任务,或者说为每一个任务分配CPU时间及其相关的资源,并且负责任务之间的通信。内核提供的基本服务是任务切

换。使用实时内核可以大大简化应用系统的设计,因为实时内核允许将应用分成若干个任务,由实时内核来管理它们。内核提供必不可少的系统服务。

μC/OS-II它仅仅包含了任务调度,任务管理,时间管理,内存管理和任务间的通信和同步等基本功能。没有提供输入输出管理,文件系统,网络等额外的服务。但由于uC/OS-II良好的可扩展性和源码开放,这些非必须的功能完全可以由用户自己根据需要分别实现。uC/OS-II目标是实现一个基于优先级调度的抢占式的实时内核,并在这个内核之上提供最基本的系统服务,如信号量,邮箱,消息队列,内存管理,中断管理等。

接下来从几方面对μC/OS-II的内核作出介绍: ①临界区的处理机制

所谓临界区,即多个任务共享的资源,在某一时刻只允许一个任务访问。 μC/OS-II在处理临界区时,代码需要关中断,处理完毕后再开中断,以避免同时有其他任务或中断服务进入临界区代码。μC/OS-II提供了两个宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()来实现对临界区的排他性操作。

②任务定义及状态

μC/OS-II的任务是一个无限的循环,一个任务可以有返回类型,有形式参数 变量,但是任务是绝不会返回的。当任务完成以后,任务可以自我“删除”,即μC/OS-II不理会这个任务了,这个任务的代码也不会再运行。μC/OS-II可以管理多达64个任务,其中空任务(IDLE)和统计任务(STATISTICS)被系统占用。任务在建立时必须被赋予不同的优先级,优先级的数值越小,则任务的优先级越高。μC/OS-II总是运行进入就绪状态的优先级最高的任务。图2.1给出了μC/OS-II控制下的任务状态转换过程。在任一时刻,任务的状态一定是这五种状态之一。

③任务控制块OS_TCB

μC/OS-II中是采用任务控制块的方式对任务进行管理的。任务控制块在任务被建立时赋值,它是一个数据结构,当任务的CPU使用权被剥夺时,μC/OS-II 用它来保存该任务的状态。而当任务重新得到CPU使用权时,任务控制块能确保重庆大学硕士学位论文实时嵌入式操作系统μC/OS-Ⅱ内核分析

④就绪表

每一个任务的就绪态标志都放入就绪表中,就绪表中有两个变量OSRdyGrp和OSRdyTbl[]。在OSRedyGrp中,任务按优先级分组,8个任务为一组。OSRedyGrp中的每一位表示8组任务中每一组中是否有进入就绪态的任务。任务进入就绪态时,就绪表OSRdyTbl[]中的相应元素的相应位也置位。μC/OS-II就是利用就绪任务表对任务进行优先级的调度。如果任务被删除,则该任务在表中相应优先级的位置要清零。

⑤任务调度

μC/OS-II总是运行就绪态任务中优先级最高的那一个,任务调度由函数OSSched()来完成。任务如果在中断服务子程序中调用OSSched(),此时中断嵌套层数OSIntNesting>0,或者由于用户至少调用了一次给任务调度上锁的函数OSSchedLock(),使OSLockNesting>0,则调度不允许。如果不是在中断服务子程序中调用OSSched(),并且任务调度是允许的,即没有上锁,则任务调度函数查找就绪任务表,将找出那个进入就绪态且优先级最高的任务。一旦找到那个优先级最高的任务,OSSched()检验这个优先级最高的任务是不是当前正在运行的任务,以此来避免不必要的任务调度。重庆大学硕士学位论文实时嵌入式操作系统μC/OS-Ⅱ内核分析

⑥中断处理

μC/OS-II中,中断服务子程序将全部CPU寄存器推入当前任务栈。μC/OS-II允许中断嵌套,由中断嵌套层数计数器OSIntNesting跟踪嵌套层数。处理完中断服务后,μC/OS-II调用函数OSIntExit()通知内核,到了返回任务级代码的时候了,于是OSIntExit()将OSIntNesting减一。当OSIntNesting为零时,μC/OS-II要判定有没有优先级较高的任务进入了就绪态,若有,μC/OS-II就返回到那个高优先级的任务,如果调度被禁止了(OSIntNesting>0),μC/OS-II将被返回到被中断了的任务。

⑦时钟节拍

μC/OS-II需要用户提供周期性信号元,用于实现时间延时和确认超时。时钟节拍频率可在μC/OS-II的配置文件中配置,时钟节拍率越高,系统的额外负荷就越重,时钟节拍源可以是专门的硬件定时器。应用程序必须在多任务系统启动以后再启动时钟节拍源计时,也就是在调用OSStart()之后。

1.3 任务管理

μC/OS-II可以管理多达64个任务,并从中保留了四个最高优先级和四个最低优先级的任务供自己使用,所以用户可以使用的只有56个任务。任务的优先级越高,反映优先级的值则越低。其中0 为最高优先级,63为最低级。uC/OS-II提供了任务管理的各种函数调用,包括创建任务,删除任务,改变任务的优先级,任务挂起和恢复等。

任务管理中最重要的数据结构是任务控制块OS_TCB typedef struct os_tcb{

OS_STK*OSTCBStkPtr;/*当前栈顶指针*/ #if OS_TASK_CREATE_EXT_EN

Void*OSTCBExtPtr;/*指向用户定义数据的指针*/ OS_STK*OSTCBStkBottom;/*栈底指针*/ INT32U OSTCBStksize;/*堆栈大小*/ INT16U OSTCBOpt;/*任务选项*/ INT16U OSTCBId;/*任务ID(0..65535)*/ #endif

struct os_tcb*OSTCBNext;/*指向TCB链中下一TCB块的指针*/ strtuct os_tcb*OSTCBPrev;/*指向TCB链中上一TCB块的指针*/ #if(OS_Q_EN&&(OSJ_MAX_QS>=2))||OS_MBOX_EN||OS_SEM_EN OS_EVENT*OSTCBEventPtr;/*事件控制块指针*/ #endif

#if(OS_Q_EN&&(OSJ_MAX_QS>=2))||OS_MBOX_EN Void*OSTCBMsg;

/*从OSMboxPost()或OSQPost()收到的消息*/ #endif

INT16U OSTCBDly;/*延迟时间*/ INT8U OSTCBStat;/*任务状态*/ INT8U OSTCBPrio;/*任务优先级(0..63)*/

INT8U OSTCBX;/*根据任务优先级指出就绪组(0..7)*/

个操作可以通过调用0SMemCreate()函数来完成。如下程序说明了如何建立一个含有100个内存块、每个内存块32字节的内存分区。

OS_MEM*CommTxBuf; INT8U CommTxPart[100][32]; void main(void) {INT8U err; OSInit();

CommTxBuf=OSMemCreate(CommTxPart,100,32,&err); OSStart();}

OSMemCreate()函数共有4个参数:内存分区的起始地址、分区内的内存块总块数、每个内存块的字节数和一个指向错误信息代码的指针。如果OSMemCreate()操作失败,它将返回一个NULL指针。否则,它返回一个指向内存控制块的指针。对内存管理的其它操作,象OSMemGet(),OSMemPut(),OSMemQuery()等,都要通过该指针进行。每个内存分区必须含有至少两个内存块,每个内存块至少为一个指针的大小,因为同一分区中的所有空闲内存块是由指针串联起来的。接着,OSMemCreate()从系统中的空闲内存控制块中取得一个内存控制块,该内存控制块包含相应内存分区的运行信息。在上述条件均得到满足时,所要建立的内存分区内的所有内存块被链接成一个单向链表。然后,在对应内存控制块中填写相应信息,返回指向该内存块的指针。

分配一个内存块:应用程序可以调用OSMemGet()函数从己经建立的内存分区中申请一个内存块。该函数的唯一参数是指向特定内存分区的指针,该指针在建立内存分区时,由OSMemCreate()函数返回。显然,应用程序必须知道内存块的大小,并且在使用时不能超过该容量。

释放一个内存块:当用户应用程序不再使用一个内存块时,必须及时地把它释放并放回到相应的内存分区中。这由OSMemPut()函数完成。

它的第一个参数pmem是指向内存控制块的指针,也即内存块属于的内存分区。OSMemPut()首先检查内存分区是否已满。如满,说明系统在分配和释放内存时出现了错误。如未满,要释放的内存块被插到该分区的空闲内存块链表中。最后,将分区中空闲内存块总数加1。

程序清单OSMemPut()

INT8U OSMemPut(OS_MEM*pmem,void*pblk) {OS_ENTER_CRITICAL();

if(pmem->OSMemNFree>=pmem->OSMemNBlks){ OS_EXIT_CRITICAL(); return(OS_MEM_FULL);}

*(void**)pblk=pmem->OSMemFreeList; pmem->OSMemFreeList=pblk; pmem->OSMemNFree++; OS_EXIT_CRITICAL(); return(OS_NO_ERR);}

1.5时间管理

uC/OS-II的时间管理是通过定时中断来实现的,该定时中断一般为10毫秒或100毫秒发生一次,时间频率取决于用户对硬件系统的定时器编程来实现。中断发生的时间间隔是固定不变的,该中断也成为一个时钟节拍。

任务延时函数:OSTimeDly()的功能是:申请该服务的任务可以延时一段时间,时间的长短用时钟节拍的数目来确定。调用该函数会使μC/OS-II进行一次任务调度,并执行下一个优先级最高的就绪态任务。任务调用OSTimeDly()后,一旦规定的时间期满或者有其它的任务通过调用OSTimeDlyResume()取消了延时,它就马上进入就绪状态。但只有当该任务在所有就绪任务中具有最高的优先级时,它才会立即运行。

按时分秒延时函数:OSTimeDly(),用户可以使用定义全局常数

OS_TICKS_PER_SEC将时间转换成时钟段。增加了OSTimeDlyHMSM()函数后,用户就可以按小时(H)、分(M)、秒(S)和毫秒(m)来定义时间了。与OSTimeDly()一样,调用OSTimeDlyHMSM()函数也会使μC/OS-II进行一次任务调度,并且执行下一个优先级最高的就绪态任务。任务调用OSTimeDlyHMSM()后,一旦规定的时间期满或有其它的任务调用OSTimeDlyResume()取消了延时,它就会马上处于就绪态。同样,只有当该任务在所有就绪态任务中具有最高的优先级时,它才会立即运行。

结束延时:通过调用OSTimeDlyResume()和指定要恢复的任务的优先级来结束延时正处于延时期的任务。实际上,OSTimeDlyResume()也可以唤醒正在等待事件的任务。在这种情况下,等待事件发生的任务会考虑是否终止等待事件。

系统时间:无论时钟节拍何时发生,μC/OS-II都会将一个32位的计数器加1。这个计数器在用户调用OSStart()初始化多任务和4 294 967 295个节拍执行完一遍的时候从0开始计数。在时钟节拍的频率等于100Hz的时候,这个32位的计数器每隔497天就重新开始计数。用户可以通过调用OSTimeGet()来获得该计数器的当前值。也可以通过调用OSTimeSet()来改变该计数器的值。

1.6 任务间通信与同步

对一个多任务的操作系统来说,任务间的通信和同步是必不可少的。uC/OS-II

中提供了4中同步对象,分别是信号量,邮箱,消息队列和事件。所有这些同步对象都有创建,等待,发送,查询的接口用于实现进程间的通信和同步。 1.6.1事件控制块ECB(Event Control Blocks)

一个任务或者中断服务子程序可以通过事件控制块ECB来向另外的任务发信号。这里,所有的信号都被看成是事件。一个任务还可以等待另一个任务或中断服务子程序给它发送信号。这里要注意的是,只有任务可以等待事件发生,中断服务子程序是不能这样做的。对于处于等待状态的任务,还可以给它指定一个最长等待时间,以此来防止因为等待的事件没有发生而无限期地等下去。多个任务可以同时等待同一个事件的发生。在这种情况下,当该事件发生后,所有等待该事件的任务中,优先级最高的任务得到了该事件并进入就绪状态,准备执行。上述的事件,可以是信号量、邮箱或者消息队列等。 ECB数据结构 typedef struct{

void*OSEventPtr;/*指向消息或者消息队列的指针*/

INT8U OSEventTbl[OS_EVENT_TBL_SIZE];/*等待事件任务列表*/ INT16U OSEventCnt;/*计数器(当事件是信号量时)*/ INT8U OSEventType;/*时间类型*/ INT8U OSEventGrp;/*等待任务所在的组*/ }OS_EVENT;

事件控制块也用和任务控制块一样的方法通过查找优先级来定位任务。对于事件控制块进行的一些通用操作包括:初始化一个事件控制块OSEventWaitListInit()使一个任务进入就绪态OSEventTaskRdy()使一个任务进入等待该事件的状态OSEventWait()因为等待超时而使一个任务进入就绪态OSEventTO() 1.6.2信号量

μC/OS-II中的信号量由两部分组成:一个是信号量的计数值,它是一个16位 的无符号整数(0到65 535之间);另一个是等待该信号量的任务组成的等待任务表。要在配置文件中将OS_SEMeeEN开关量常数置成1,这样μC/OS-II才能支持信号量。在使用一个信号量之前,要调用OSSemCreate()函数建立该信号量,即对信号量的初始计数值赋值,该初始值为0到65 535之间的一个数。如果信号量是用来表示一个或者多个事件的发生,那么该信号量的初始值应设为0。如果信号量是用于对共享资源的访问,那么该信号量的初始值应设为1(例如,把它当作二值信号量使用)。如果该信号量是用来表示允许任务访问n个相同的资源,那么该初始值应该是n,并把该信号量作为一个可计数的信号量使用。μC/OS-II提供了5个对信号量进行操作的函数:OSSemCreate(),OSSempend(),OSSemPost(),OSSemAccept()和OSSemQuery()函数。对于二值信号量,该值就是1;旗帜符号信号量用于表示某事件的发生。这时数字N代表事件已经发生的次数。从图中可以看出OSSemPost()函数可以由任务或中断服务子程序调用,而OSSemPend()和OSSemQuery()函数只能被任务程序调用。建立一个信号量OSSemCreate(INT16U cnt):

首先,OSSemCreate()从空闲事件控制块链表中得到一个事件控制块,将该控制块的事件类型设置成信号量OS_EVENT_TYPE_SEM,并使链表的指针指向下一个空闲的事件控制块。其它的信号量操作函数通过检查该域来保证所操作的事件类型的正确。例如,这可以防止调用OSSemPost()函数对一个用作邮箱的任务控制块进行操作。接着,用信号量的初始值对事件控制块进行初始化,并对事件控制块的等待任务列表进行初始化。因为信号量正在被初始化,所以这时没有任何任务等待该信号量。最后,OSSemCreate()返回给调用函数一个指向事件控制块的指针。以后对信号量的所有操作都是通过该指针完成的。如果系统中没有可用的事件控制块,OSSemCreate()将返回一个NULL指针。值得注意的是,在μ

C/OS-II中,信号量一旦建立就不能删除了,因此也就不可能将一个己分配的事件控制块再放回到空闲ECB链表中。如果有任务正在等待某个信号量,或者某任务的运行依赖于某信号量的出现时,删除该任务是很危险的。 1.6.3邮箱

邮箱是μC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送一个指针型的变量。该指针指向一个包含了特定“消息”的数据结构。为了在μC/OS-II中使用邮箱,必须将OS_CFG.H中的OS_MBOX_EN常数置为1。使用邮箱之前,必须先通过调用OSMboxCreate()函数建立该邮箱,并且指定指针的初始值。μC/OS-II提供了5种对邮箱的操作:OSMboxCreate(),OSMboxPend(),OSMboxPost(),OSMboxAccept()和OSMboxQuery()函数。这些操作和信号量的操作基本相似,只是对应ECB和TCB中不同的域。下图描述了任务、中断服务子程序和邮箱之间的关系,这里用符号“I”表示邮箱。邮箱含的内容是一个指向消息的指针。一个邮箱只能含一个这样的指针,或一个指向NULL的指针。任务或者中断服务子程序可以调用函数OSMboxPost(),但只有任务可以调用OSMboxPend()和OSMboxQuery(). 1.6.4消息队列

消息队列是μC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量。为了使用μC/OS-II的消息队列功能,需要在配置头文件文件中,将OS_Q_EN常数设置为1,并且通过常数OS_MAX_QS来决定μC/OS-II支持的最多消息队列数。在使用一个消息队列之前,先通过调用OSQCreate()建立该消息队列,并定义消息队列中的单元数(消息数)。μC/OS-II提供了7个对消息队列进行操作的函数:OSQCreate(),OSQPend(),OSQPost(),OSQPostFront(),OSQAccept(),OSQFlush()和OSQQuery()函数。队列看作时多个邮箱组成的数组,只是它们共用一个等待任务列表。每个指针所指向的数据结构由具体的应用程序决定的。N代表了消息队列中的总单元数。当调用OSQPend()或者OSQAccept()之前,调用N次OSQPost()或者OSQPostFront()就会把消息队列填满。一个任务或者中断服务子程序可以调用OSQPost(),OSQPostFront(),OSQFlush()或者OSQAccept()函数。

1.7任务调度

uC/OS-II 采用的是可剥夺型实时多任务内核。可剥夺型的实时内核在任何时

候都运行就绪了的最高优先级的任务。uC/os-II的任务调度是完全基于任务优先级的抢占式调度,也就是最高优先级的任务一旦处于就绪状态,则立即抢占正在运行的低优先级任务的处理器资源。为了简化系统设计,uC/OS-II规定所有任务的优先级不同,因为任务的优先级也同时唯一标志了该任务本身。 任务调度将在以下情况下发生:

1) 高优先级的任务因为需要某种临界资源,主动请求挂起,让出处理器,此时将调度就绪状态的低优先级任务获得执行,这种调度也称为任务级的上下文切换。

2) 高优先级的任务因为时钟节拍到来,在时钟中断的处理程序中,内核发现高优先级任务获得了执行条件(如休眠的时钟到时),则在中断态直接切换到高优先级任务执行。这种调度也称为中断级的上下文切换。

这两种调度方式在uC/OS-II的执行过程中非常普遍,一般来说前者发生在系统服务中,后者发生在时钟中断的服务程序中。

调度工作的内容可以分为两部分:最高优先级任务的寻找和任务切换。其最高优先级任务的寻找是通过建立就绪任务表来实现的。u C / O S 中的每一个任务都有独立的堆栈空间,并有一个称为任务控制块TCB(Task Control Block)的数据结构,其中第一个成员变量就是保存的任务堆栈指针。任务调度模块首先用变量OSTCBHighRdy 记录当前最高级就绪任务的TCB 地址,然后调用OS_TASK_SW()函数来进行任务切换。

2. μC/OS-II的组成部分

μC/OS-II可以大致分成核心、任务处理、时间处理、任务同步与通信,CPU的移植等5个部分。 1) 核心部分(OSCore.c)

是操作系统的处理核心,包括操作系统初始化、操作系统运行、中断进出的前导、时钟节拍、任务调度、事件处理等多部分。能够维持系统基本工作的部分都在这里。

2) 任务处理部分(OSTask.c)

任务处理部分中的内容都是与任务的操作密切相关的。包括任务的建立、删除、挂起、恢复等等。因为μC/OS-II是以任务为基本单位调度的,所以这部分内容也相当重要。 3) 时钟部分(OSTime.c)

μC/OS-II中的最小时钟单位是timetick(时钟节拍)。任务延时等操作是在这里完成的。

4) 任务同步和通信部分

为事件处理部分,包括信号量、邮箱、邮箱队列、事件标志等部分;主要用于任务间的互相联系和对临界资源的访问。 5) 与CPU的接口部分

是指μC/OS-II针对所使用的CPU的移植部分。由于μC/OS-II是一个通用性的操作系统,所以对于关键问题上的实现,还是需要根据具体CPU的具体内容和要求作相应的移植。这部分内容由于牵涉到SP等系统指针,所以通常用汇编语言编写。主要包括中断级任务切换的底层实现、任务级任务切换的底层实现、时钟节拍的产生和处理、中断的相关处理部分等内容。

3 内核移植与测试

我们所要选择的处理器还必须满足如下条件才能保证μC/OS-II移植后能够正常运行。

1.处理器的C编译器能产生可重入代码。 2.用C语言就可以打开和关闭中断。 3.处理器支持中断,并且能产生定时中断。

4.处理器支持能够容纳一定量数据(可能是几千字节)的硬件堆栈。

5.处理器有将堆栈指针和其它CPU寄存器读出和存储到堆栈或内存中的指令。

代码移植结束,下一步所需要做的就是测试。就是让内核自己测试自己。这 样做是非常有好处的:首先,可以避免使原本就复杂的事情更加复杂;其次,如果出现问题,可以知道问题出在内核代码上而不是应用程序。将在此测试μC/OS-II提供的一些功能,除了空闲任务(idle task)和统计任务(statistic task)外,还有其它一些任务。每秒钟任务切换次数、CPU利用百分率、当前的任务总数、各个任务

的执行时间、各个任务在所有任务中占用的比例、目前日期和时间。

本文来源:https://www.bwwdw.com/article/ryq8.html

Top