转:一步一步教你使用uCOS-II

转:⼀步⼀步教你使⽤uCOS-II
第⼀篇 UCOS介绍
第⼀篇 UCOS介绍
这个⼤家都知道。呵呵。考虑到咱们学习的完整性还是在这⾥唠叨⼀下。让⼤家再熟悉⼀下。⾼⼿们忍耐⼀下吧! uC/OS II(Micro Control Operation System Two)是⼀个可以基于ROM运⾏的、可裁减的、抢占式、实时多任务内核,具有⾼度可移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统(RTOS)。为了提供最好的移植性能,uC/OS II最⼤程度上使⽤ANSI C语⾔进⾏开发,并且已经移植到近40多种处理器体系上,涵盖了从8位到64位各种CPU(包括DSP)。 
uC/OS II可以简单的视为⼀个多任务调度器,在这个任务调度器之上完善并添加了和多任务操作系统相关的系统服务,如信号量、邮箱等。其主要特点有公开源代码,代码结构清晰、明了,注释详尽,组织有条理,可移植性好,可裁剪,可固化。内核属于抢占式,最多可以管理60个任务。
透水石
µC/OS-II 的前⾝是µC/OS,最早出⾃于1992 年美国嵌⼊式系统专家Jean J.Labrosse 在《嵌⼊式系统编程》杂志的5 ⽉和6 ⽉刊上刊登的⽂章连载,并把µC/OS 的源码发布在该杂志的B B S 上。 
µC/OS 和µC/OS-II 是专门为计算机的嵌⼊式应⽤设计的,绝⼤部分代码是⽤C语⾔编写的。CPU 硬件相
关部分是⽤汇编语⾔编写的、总量约200⾏的汇编语⾔部分被压缩到最低限度,为的是便于移植到任何⼀种其它的CPU 上。⽤户只要有标准的ANSI 的C交叉编译器,有汇编器、连接器等软件⼯具,就可以将µC/OS-II嵌⼈到开发的产品中。µC/OS-II 具有执⾏效率⾼、占⽤空间⼩、实时性能优良和可扩展性强等特点,最⼩内核可编译⾄ 2KB 。µC/OS-II 已经移植到了⼏乎所有知名的CPU 上。 
严格地说uC/OS-II只是⼀个实时操作系统内核,它仅仅包含了任务调度,任务管理,时间管理,内存管理和任务间的通信和同步等基本功能。没有提供输⼊输出管理,⽂件系统,⽹络等额外的服务。但由于uC/OS-II良好的可扩展性和源码开放,这些⾮必须的功能完全可以由⽤户⾃⼰根据需要分别实现。
uC/OS-II⽬标是实现⼀个基于优先级调度的抢占式的实时内核,并在这个内核之上提供最基本的系统服务,如信号量,邮箱,消息队列,内存管理,中断管理等。
uC/OS-II以源代码的形式发布,但并不意味着它是开源软件。你可以将其⽤于教学和私下研究(peaceful research);但是如果你将其⽤于商业⽤途,那么你必须通过Micrium获得商⽤许可。
虽然uCOS-II在商业上使⽤时需要的得到授权并且费⽤也是⼀笔不⼩的数字,但是他的开源毕竟带领我们⾛⼊了内核的世界。在此我代表嵌⼊式⼯程师向Mr Jean J.Labrosse 致谢。
任务管理
uC/OS-II 中最多可以⽀持64 个任务,分别对应优先级0~63,其中0 为最⾼优先级。63为最低级,系统保留了4个最⾼优先级的任务和4个最低优先级的任务,所有⽤户可以使⽤的任务数有56个。 
uC/OS-II提供了任务管理的各种函数调⽤,包括创建任务,删除任务,改变任务的优先级,任务挂起和恢复等。 
系统初始化时会⾃动产⽣两个任务:⼀个是空闲任务,它的优先级最低,该任务仅给⼀个整形变量做累加运算;另⼀个是系统任务,它的优先级为次低,该任务负责统计当前cpu的利⽤率。
在系统初始化完毕后启动任务时必须创建⼀份⽤户任务,也就是说必须有⼀个应⽤程序(⽤户任务,使⽤应⽤程序对于我们经常使⽤Windows⽤户容易接受⼀些。呵呵),否则系统会崩溃。当然还有⼀些其他的要求,咱们后续再说,下⾯简要概述⼀下任务管理相关的函数1:建⽴任务OSTaskCreat()/OSTaskCreatExt()
如果想让UCOS管理⽤户的任务,必须先建⽴任务。可以通过将任务的地址和其他参数传递到以下两个函数之⼀来建⽴任务。当调⽤OSTaskCreat()时,需要四个参数:
OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTU prio)
Task:是指向任务代码的指针,pdata:是任务开始执⾏是,传递给任务的参数的指针,ptos:是分配
给任务的堆栈的栈顶指针,prio是分配给任务的优先级。
也可以⽤OSTaskCreatExt(),不过该函数需要9个参数,前四个参数与OSTaskCreat()⼀样,例如:
INT8U OSTaskCreateExt(void(*task)(void *pd),void *pdata,OS_STK *ptos, INT8U prio, INT16U  id, OS_STK  *pbos, OS_STK  *pbos,
OS_STK  *pbos, INT16U  opt)
汽水取样id参数为要建⽴的任务创建⼀个特殊的标识符。pbos是指向任务的堆栈栈底的指针,⽤于堆栈的检验。stk  _size⽤于指定堆栈成员数⽬的容量。pext是指向⽤户附加的数据域的指针,⽤来扩展任务的OS_TCB。opt⽤于设定OSTaskCreateExt()的选项,指定是否允许堆栈检验,是否将堆栈清零,任务是否要进⾏浮点操作等等。
2:任务堆栈OS_STK()
每个任务都有⾃⼰的堆栈,堆栈必须申明为OS_STK类型,并且由连续的内存空间组成。可以静态分配堆栈空间,也可以动态分配堆栈空间。
间。
3:堆栈检验OSTaskStkChk()
有时确定任务实际需要的堆栈空间的⼤⼩是很有必要的,因为这样就可以避免为任务分配过多的堆栈空间,从⽽减少应⽤程序代码所需的RAM空间。
4:删除任务OSTaskDel()
有时需要删除任务,删除任务,是说任务返回并处于休眠态,并不是说任务的代码被删除了,只是任务的代码不再被UCOS调⽤。删除任务前应保证所删任务并⾮空闲任务。
5:请求删除任务OSTaskDelReq()
有时,任务会占⽤⼀些内存缓冲或信号量⼀类的资源。这时,假如另⼀个任务试图删除该任务,这些被占⽤的资源就会因为没有被释放⽽丢失。在这种情况下,需想办法拥有这些资源的任务在使⽤完资源后先释放资源,再删除⾃⼰。
6:改变任务的优先级OSTaskChangePrio()
在建⽴任务时,会分配给任务⼀个优先级。在程序运⾏期间,可以通过调⽤该函数改变任务的优先级。也就是说,UCOS允许动态的改变任务的优先级。
7:挂起任务OSTaskSuspend()
任务挂起是⼀个附加功能,也就是说,如果任务在被挂起的同时也在等待延迟时间到,那么,需要对任务做取消挂起的操作,并且等待延迟时间到,任务才能转让就绪状态。任务可以挂起⾃⼰或者其他任务。
8:恢复任务OSTaskResume()
挂起的任务只有通过该函数才能被恢复。
9:获得任务的信息OSTaskQuery()
通过调⽤该函数,来获得⾃⾝或其他应⽤任务的信息
时间管理
uC/OS-II的时间管理是通过定时中断来实现的,该定时中断⼀般为10毫秒或100毫秒发⽣⼀次(这个时间⽚段是OS的作者推荐的,⼤家可以参考邵贝贝翻译的《嵌⼊式实时操作系统ucos-II》这本书),时间频率取决于⽤户对硬件系统的定时器编程来实现。中断发⽣的时间间隔是固定不变的,该中断也成为⼀个时钟节拍。这⾥隐含的意思就是你选择的芯⽚如果想使⽤UCOS系统,前提条件⼀定要有⼀
个Timer。  uC/OS-II要求⽤户在定时中断的服务程序中,调⽤系统提供的与时钟节拍相关的系统函数,例如中断级的任务切换函数,系统时间函数。uCOS时间管理的相关函数
1:任务延迟函数OSTimeDly()
Ucos提供⼀个可以被任务调⽤⽽将任务延时⼀段特定时间的功能函数,即OSTimeDly().任务调⽤OSTimeDly()后,⼀旦规定的时间期满或者有其他的任务通过调⽤OSTimeDlyResume()取消了延时,他就会进⼊就绪状态。只有当该任务在所有就绪态任务中具有最⾼的优先级,它才会⽴即运⾏。
2:按时,分,秒延时函数OSRimeDLyHMSM()
与OSTimeDly()⼀样,调⽤OSRimeDlyHMSM()函数也会是UCOS进⾏⼀次任务调度,并且执⾏下⼀个优先级最⾼的就绪任务。当OSTimeDlyHMSM()后,⼀旦规定的时间期满,或者有OSTimeDlyResume(),它就会马上处于就绪态。同样,只有当该任务在所有就绪态任务中具有最⾼的优先级,他才开始运⾏。
3:恢复延时的任务OSTimeDlyResume()
延时的任务可以不等待延时的期满,⽽是通过其他任务取消延时⽽使⾃⼰处于就绪态,可以通过该函数来实现,实际
上,OSTimeDlyResume()也可以唤醒正在等待的事件。
4:系统时间OSTimeGet()和OSTimeSet()
内存管理
在ANSI C中是使⽤malloc和free两个函数来动态分配和释放内存。例如在Linux系统中就是这样。但在嵌⼊式实时系统中,多次这样的操作会导致内存碎⽚,因为嵌⼊式系统尤其是uCOS是实地址模式,这种模式在分配任务堆栈时需要整块连续的空间,否则任务⽆法正确运⾏。且由于内存管理算法的原因,malloc和free的执⾏时间也是不确定。这点是实时内核最⼤的⽭盾。
基于以上的原因uC/OS-II中把连续的⼤块内存按分区管理。每个分区中包含整数个⼤⼩相同的内存块,但不同分区之间的内存快⼤⼩可以不钢碗
同。⽤户需要动态分配内存时,系统选择⼀个适当的分区,按块来分配内存。释放内存时将该块放回它以前所属的分区,这样能有效解决碎⽚问题,同时执⾏时间也是固定的。
同时uCOS-II根据以上的处理封装了适合于⾃⼰的动态内存分配函数OSMemGet()和OSMemPut(),但是使⽤这两个函数动态分配内存前需要先创建内存空间,也就是第⼆段咱们介绍的内存分块。呵呵,不罗嗦了,具体的关于内存管理的函数如下:
内存控制块的数据结构
Typedef
struct
{void  *osmemaddr    ;指向内存分区起始地址的指针。
Void  *osmemfreelist  ;指向下⼀个空余内存控制块或者下⼀个空余内存块的指针,
Int32u  osmemblksize  ;内存分区中内存块的⼤⼩,是建⽴内存分区时定义的。
Int32u osmemnblks    ;内存分区中总的内存块数量,也是建⽴该内存分区时定义的。
Int32u osmemnblks    ;内存分区中总的内存块数量,也是建⽴该内存分区时定义的。
Int32u  osmemnfree    ;内存分区块中当前获得的空余块数量。
}os_mem;
1;建⽴⼀个内存分区,OSMemCreate()
2:分配⼀个内存块,OSMemGet()
应⽤程序通过调⽤该函数,从已经建⽴的内存分区中申请⼀个内存块。该函数唯⼀的参数是指向特定内存分区的指针。
3:释放⼀个内存块,OSMemPut()
当应⽤程序不再使⽤⼀个内存块时,必须及时的把它释放,并放回到相应的内存分区中,这个操作就是通过调⽤该函数实现的。
4:查询⼀个内存分区的状态,OSQMemQuery()。
任务间通信与同步
对⼀个多任务的操作系统来说,任务间的通信和同步是必不可少的。uC/OS-II中提供了4种同步对象,分别是信号量,邮箱,消息队列和事件。所有这些同步对象都有创建,等待,发送,查询的接⼝⽤于实现进程间的通信和同步。
对于这4种同步对象将在后⾯⼀⼀讨论。
任务调度
免烧砖机模具uC/OS-II 采⽤的是可剥夺型实时多任务内核。可剥夺型的实时内核在任何时候都运⾏就绪了的最⾼优先级的任务。
uC/os-II的任务调度是完全基于任务优先级的抢占式调度,也就是最⾼优先级的任务⼀旦处于就绪状态,则⽴即抢占正在运⾏的低优先级任务的处理器资源。为了简化系统设计,uC/OS-II规定所有任务的优先级不同,因为任务的优先级也同时唯⼀标志了该任务本⾝。
UCOS的任务调度在⼀下情况下发⽣:
1)⾼优先级的任务因为需要某种临界资源,主动请求挂起,让出处理器,此时将调度就绪状态的低优先级任务获得执⾏,这种调度也称为任务级的上下⽂切换。
2)⾼优先级的任务因为时钟节拍到来,在时钟中断的处理程序中,内核发现⾼优先级任务获得了执⾏条件(如休眠的时钟到时),则在中断态直接切换到⾼优先级任务执⾏。这种调度也称为中断级的上下⽂切换。
这两种调度⽅式在uC/OS-II的执⾏过程中⾮常普遍,⼀般来说前者发⽣在系统服务中,后者发⽣在时钟中断的服务程序中。
调度⼯作的内容可以分为两部分:最⾼优先级任务的寻和任务切换。其最⾼优先级任务的寻是通
过建⽴就绪任务表来实现的。u C / O S 中的每⼀个任务都有独⽴的堆栈空间,并有⼀个称为任务控制块TCB(Task Control Block)的数据结构,其中第⼀个成员变量就是保存的任务堆栈指针。任务调度模块⾸先⽤变量OSTCBHighRdy 记录当前最⾼级就绪任务的TCB 地址,然后调⽤OS_TASK_SW()函数来进⾏任务切换。
第⼆章搭建UCOS-II 2.52版的调试平台
在这⼀章中我们主要讨论UCOSII的源码调试环境,为了给⼤家⼀个共同的学习平台,我搜集整理了⼀写资料,就是以X86为平台,使⽤BC31(这个堪称⾻灰级的编译器)来调试UCOSII源码。当然你也可以⽤BC45或更⾼版本的编译器,具体⽅法⼤同⼩异,我在此就不再啰嗦。
本章节的主要内容包括四点:
1、下载并安装BC31编译器
2、下载并安装UCOS-II2.52版本源代码
3、使⽤BC31编译UCOS-II源码金银花绿原酸
4、让OS的第⼀个任务RUN起来
接下来会在每个帖⼦中讨论⼀点。耐⼼等待哦!
下载并安装BC31编译器
我在这⾥提供给⼤家这个⾻灰级的编译器BC31.需要的可以下载。见附件(⾻灰级编译器BC31)由于这个软件的⽐较⼤,分成两个压缩包。下班了,先到这⾥,回家再传附件!灯箱广告制作
包。下班了,先到这⾥,回家再传附件!
让⾃⼰的第⼀个任务Run起来
前⾯已经给⼤家介绍了如何在PC机上调试UCOS,⽅法和需要的软件都介绍给⼤家了,相信有兴趣的朋友已经安装调试了,下⾯咱们就让⾃⼰的第⼀个任务在PC上Run起来。
OK,下⾯我就分步介绍建⽴⾃⼰的第⼀个任务
第⼀步:CopyC:\SOFTWARE\uCOS-II⽬录下的EX1_x86L⽂件夹。作为我们的⼯程模板
第⼆步:修改⼯程模板的名字为:HelloEEWorld
第三部:按照咱们前⾯的《使⽤ BC31 ⼯具编译 UCOS‐II 的源码过程》修改配置⽂件;
第四步:修改Test.c⽂件,建⽴⾃⼰的第⼀个任务
具体的内容我就不再帖⼦上写了。⼤家可以参考附件HelloEEWorld.rar⾥⾯的Test.c⽂件。然后编译
关于UCOS任务的理解
UCOS的运⾏是基于任务运⾏的,为了能够好的使⽤UCOS我们先要对UCOS的任务的概念做⼀个理解
在学习UCOS任务前我们先对我们以前使⽤的模式做⼀个回顾--前后台模式。
这种系统可称为前后台系统或超循环系统(Super-Loops)。应⽤程序是⼀个⽆限的循环,循环中调⽤相应的函数完成相应的操作,这部分可以看成后台⾏为(background)。中断服务程序处理异步事件,这部分可以看成前台⾏ foreground。后台也可以叫做任务级。前台也叫中断级。时间相关性很强的关键操作(Critical operation)⼀定是靠中断服务来保证的。因为中断服务提供的信息⼀直要等到后台程序⾛到该处理这个信息这⼀步时才能得到处理,这种系统在处理信息的及时性上,⽐实际可以做到的要差。这个指标称作任务级响应时间。最坏情况下的任务级响应时间取决于整个循环的执⾏时间。因为循环的执⾏时间不是常数,程序经过某⼀特定部分的准确时间也是不能确定的。进⽽,如果程序修改了,循环的时序也会受到影响。
这种系统是在我们上学时和做⼩项⽬时经常⽤到,很多⼯程师称这种⽅式为“裸奔”。哈哈!我⼤学毕业后的钱三年写的项⽬都是在裸奔。UCOS-II是基于任务运⾏的。⼀个任务,也称作⼀个线程,是⼀个简单的程序,该程序可以认为 CPU 完全只属该程序⾃⼰。实时应⽤程序的设计过程,包括如何把问题分割成多个任务,每个任务都是整个应⽤的某⼀部分,每个任务被赋予⼀定的优先级,有它⾃⼰的⼀套 CPU 寄存器和⾃⼰的栈空间(如下图所⽰)。
可以这么理解,UCOS-II的每⼀个任务都有⼀个CPU,任务在运⾏时占⽤CPU的全部资源,同时拥有⾃⼰的⼀套寄存器,当任务执⾏完毕后(时间⽚到),他把⾃⼰的CPU寄存器所有内容保存到⾃⼰的堆栈中,同时把CPU让给别的任务,那么得到CPU使⽤权的任务把⾃⼰的CPU寄存器从⾃⼰的堆栈中放到真正的CPU寄存器中开始运⾏,就这样周⽽复始。
⼤家⼀定不要把任务的运⾏当成是函数的调⽤,这完全是两回事。这个我们到后⾯的任务调度时在细说。每个任务都是⼀个⽆限的循环。每个任务都处在以下 5种状态之⼀的状态下,这5种状态是休眠态,就绪态、运⾏态、挂起态(等待某⼀事件发⽣)和被中断态(参见下图)
休眠态相当于该任务驻留在内存中,但并不被多任务内核所调度。就绪意味着该任务已经准备好,可以运⾏了,但由于该任务的优先级⽐正在运⾏的任务的优先级低,还暂时不能运⾏。运⾏态的任务是指该任务掌握了 CPU 的控制权,正在运⾏中。挂起状态也可以叫做等待事件态WAITING,指该任务
在等待,等待某⼀事件的发⽣,(例如等待某外设的 I/O 操作,等待某共享资源由暂不能使⽤变成能使⽤状态,等待定时脉冲的到来或等待超时信号的到来以结束⽬前的等待,等等)。最后,发⽣中断时,CPU提供相应的中断服务,原来正在运⾏的任务暂不能运⾏,就进⼊了被中断状态。如下图表⽰µC/OS-Ⅱ中⼀些函数提供的服务,这些函数使任务从⼀种状态变到另⼀种状态。
简单的我们可以把每⼀次任务的切换当成⼀次中断,这个中断不同于我们在使⽤前后台模式时的中断,那个中断是硬件中断,中断时需要保存的CPU寄存器是由硬件实现的,⽽在UCOS中的任务切换是软中断,CPU保存了必要的寄存器后在切换时系统会在保存任务使⽤的寄存器。
补充知识-可剥夺型内核和不可剥夺型内核
不可剥夺型内核
不可剥夺型内核要求每个任务⾃我放弃CPU 的所有权。不可剥夺型调度法也称作合作型多任务,各个任务彼此合作共享⼀个 CPU。异步事件还是由中断服务来处理。中断服务可以使⼀个⾼优先级的任务由挂起状态变为就绪状态。但中断服务以后控制权还是回到原来被中断了的那个任务,直到该任务主动放弃 CPU 的使⽤权时,那个⾼优先级的任务才能获得 CPU的使⽤权。
不可剥夺型内核允许每个任务运⾏,直到该任务⾃愿放弃 CPU的控制权。中断可以打⼊运⾏着的任务。
中断服务完成以后将 CPU 控制权还给被中断了的任务。任务级响应时间要⼤⼤好于前后系统,但仍是不可知的,商业软件⼏乎没有不可剥夺型内核。
不可剥夺型内核的⼯作过程见下图:
可剥夺型内核
当系统响应时间很重要时,要使⽤可剥夺型内核。因此,µC/OS-Ⅱ以及绝⼤多数商业上销售的实时内核都是可剥夺型内核。最⾼优先级的任务⼀旦就绪,总能得到CPU 的控制权。当⼀个运⾏着的任务使⼀个⽐它优先级⾼的任务进⼊了就绪态,当前任务的CPU使⽤权就被剥夺了,或者说被挂起了,那个⾼优先级的任务⽴刻得到了 CPU的控制权。如果是中断服务⼦程序使⼀个⾼优先级的任务进⼊就绪态,中断完成时,中断了的任务被挂起,优先级⾼的那个任务开始运⾏。使⽤可剥夺型内核,最⾼优先级的任务什么时候可以执⾏,可以得到 CPU的控制权是可知的。使⽤可剥夺型内核使得任务级响应时间得以最优化。
可剥夺型内核的⼯作过程是这样的:
UCOS-II 任务调度
任务调度是内核的主要职责之⼀,就是要决定该轮到哪个任务运⾏了。多数实时内核是基于优先级调
度法的,UCOS也不例外。每个任务根据其重要程度的不同被赋予⼀定的优先级。基于优先级的调度法指,CPU总是让处在就绪态的优先级最⾼的任务先运⾏。然⽽,究竟何时让⾼优先级任务掌握CPU 的使⽤权,有两种不同的情况,这要看⽤的是什么类型的内核,是不可剥夺型的还是可剥夺型内核。
上⼀次咱们已经介绍了可剥夺型内核和不可剥夺型内核的⼯作过程了。在此不再赘述!
当多任务内核决定运⾏另外的任务时,它保存正在运⾏任务的当前状态,即CPU寄存器中的全部内容。这些内容保存在任务的当前状况保存区,也就是任务⾃⼰的栈区之中,上⼀次讨论的内容中有这个图⽰。⼊栈⼯作完成以后,就是把下⼀个将要运⾏的任务的当前状况从该任务的栈中重新装⼊ CPU 的寄存器,并开始下⼀个任务的运⾏。这个过程叫做任务切换。任务切换过程增加了应⽤程序的额外负荷。CPU的内部寄存器越多,额外负荷就越重。做任务切换所需要的时间取决于CPU有多少寄存器要⼊栈。实时内核的性能不应该以每秒钟能做多少次任务切换来评价。⽽是要看OS总的关中断时间。总的关中断时间越短说明这个内核的实时性越好。这个问题在前⾯⼀个坛友的问题中我做了详细的描述,有兴趣的朋友可以在UCOS这个版块这个帖⼦。
任务调度的算法有很多种。⼀种是基于优先级的。⼀种是基于时间⽚的。这两种算法在邵贝贝教授翻译的《UCOS-II内核详解》这本书中有详细解释。我就不再重复。如果坛⼦⾥有朋友对此有什么不明⽩。可以在这⾥留⾔。咱们再讨论。
UCOS-II的⽂件结构
前⾯我们对UCOS的基础知识做了了解,其中有些地⽅由于邵贝贝翻译的树上讲解的很少我就没有班门弄斧,⼤家可以结合那本书来看。有问题或不明⽩的在这⾥讨论,欢迎⼤家剔除问题。
这次我们主要了解UCOS-II的⽂件结构。等对UCOS⽂件结构了解以后,我们就逐⼀的去讲解其各章的重点和难点,达到在短时间内学会使

本文发布于:2024-09-23 05:29:42,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/3/154942.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:任务   中断   内核   内存   系统   时间   函数
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议