【NUCLEO-L552ZE测评】+基于FREERTOS的移植
<div class='showpostmsg'><p>最近载玩L552这块板子,突然想用FREERTOS,想要自己移植,但是去网上看下基本上都是基于M0与M3的移植,针对M33内核移植的帖子很少,但是就自己写一个帖子,并向大家介绍一下自己移植过程同时也对使用其他的使用者来说也是一个很好的介绍,希望对大家有所帮助。</p><p>简单向大家介绍一下FREERTOS:</p>
<p>FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数<a href="https://baike.baidu.com/item/%E5%AE%9E%E6%97%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F" target="_blank">实时操作系统</a>能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行。</p>
<p><strong>功能特点:</strong></p>
<p>用户可配置内核功能</p>
<p>多平台的支持</p>
<p>提供一个高层次的信任代码的完整性</p>
<p>目标代码小,简单易用</p>
<p>遵循MISRA-C标准的编程规范</p>
<p>强大的执行跟踪功能</p>
<p>堆栈溢出检测</p>
<p>没有限制的任务数量</p>
<p>没有限制的任务优先级</p>
<p>多个任务可以分配相同的优先权</p>
<p>队列,二进制信号量,计数信号灯和递归通信和同步的任务</p>
<p>优先级继承</p>
<p>免费开源的源代码</p>
<p><strong>原理与实现:</strong></p>
<p>任务调度机制是<a href="https://baike.baidu.com/item/%E5%B5%8C%E5%85%A5%E5%BC%8F%E5%AE%9E%E6%97%B6%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F" target="_blank">嵌入式实时操作系统</a>的一个重要概念,也是其核心技术。对于可剥夺型内核,优先级高的任务一旦就绪就能剥夺优先级较低任务的CPU使用权,提高了系统的实时响应能力。不同于μC/OS-II,FreeRTOS对系统任务的数量没有限制,既支持优先级<a href="https://baike.baidu.com/item/%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95" target="_blank">调度算法</a>也支持轮换调度算法,因此FreeRTOS采用双向<a href="https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8" target="_blank">链表</a>而不是采用查任务就绪表的方法来进行任务调度。系统定义的链表和链表节点数据结构如下所示:</p>
<p>//定义链表结构</p>
<p>typedef struct xLIST{</p>
<p>unsigned portSHORPT usNumberOfItems; //usNumberOfItems为链表的长度,为0表示链表为空</p>
<p>volatile xListItem * pxHead; //pxHead为链表的头指针</p>
<p>volatile xListItem * pxIndex; //pxIndex指向链表当前结点的<a href="https://baike.baidu.com/item/%E6%8C%87%E9%92%88" target="_blank">指针</a></p>
<p>volatile xListItem xListEnd; //xListEnd为<a href="https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8" target="_blank">链表</a>尾结点</p>
<p>}xList;</p>
<p>//定义链表结点的结构</p>
<p>struct xLIST_ITEM {</p>
<p>port Tick type; //port Tick Type为时针节拍数据类型,</p>
<p>xItem Value; //xItem Value的值用于实现时间管理,可根据需要选择为16位或32位</p>
<p>volatile struct xLIST_ITEM * pxNext; //指向链表的前一个结点</p>
<p>void * pvOwner; //指向此<a href="https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8" target="_blank">链表</a>结点所在的任务控制块</p>
<p>void * pvContainer; //指向此链表结点所在的链表</p>
<p>};</p>
<p>FreeRTOS中每个任务对应于一个任务控制块(TCB),其定义如下所示:</p>
<p>typedef struct tskTaskControlBlock {</p>
<p>portSTACK_TYPE * pxTopOfStack; //指向任务堆栈结束处</p>
<p>portSTACK_TYPE * pxStack; //指向任务堆栈起始处</p>
<p>unsigned portSHORT usStackDepth; //定义堆栈深度</p>
<p>signed portCHAR pcTaskName; //任务名称</p>
<p>unsigned portCHAR ucPriority; //任务优先级</p>
<p>xListItem xGenericListItem; //用于把TCB插入就绪<a href="https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8" target="_blank">链表</a>或等待链表</p>
<p>xListItem xEventListItem; //用于把TCB插入事件链表(如<a href="https://baike.baidu.com/item/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97" target="_blank">消息队列</a>)</p>
<p>unsigned portCHAR ucTCBNumber; //用于记录功能</p>
<p>}tskTCB;</p>
<p>FreeRTOS定义就绪任务链表<a href="https://baike.baidu.com/item/%E6%95%B0%E7%BB%84" target="_blank">数组</a>为xList pxReady—TasksLists。其中portMAX_PRIORITIES为系统定义的最大优先级。若想使优先级为n的任务进入就绪态,需要把此任务对应的TCB中的结点xGenericListltem插入到链表pxReadyTasksLiStS中,还要把xGenericListItem中的pvContainer指向pxReadyTasksLists方可实现。</p>
<p>当进行任务调度时,<a href="https://baike.baidu.com/item/%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95" target="_blank">调度算法</a>首先实现优先级调度。系统按照优先级从高到低的顺序从就绪任务链表数组中寻找usNumberOfItems第一个不为0的优先级,此优先级即为当前最高就绪优先级,据此实现优先级调度。若此优先级下只有一个就绪任务,则此就绪任务进入运行态;若此优先级下有多个就绪任务,则需采用轮换调度算法实现多任务轮流执行。</p>
<p>若在优先级n下执行轮换调度算法,系统先通过执行</p>
<p>(pxReadyTasksLists)→pxIndex=(pxReadyTasks-Lists)→pxlndex→pxNext语句得到当前结点所指向的下一个结点,再通过此结点的pvOwner<a href="https://baike.baidu.com/item/%E6%8C%87%E9%92%88" target="_blank">指针</a>得到对应的任务控制块,最后使此任务控制块对应的任务进入运行态。由此可见,在FreeRTOS中,相同优先级任务之间的切换时间为一个时钟节拍周期。</p>
<p>以图l为例,设系统的最大任务数为pottMAX_PRIORITIES,在某一时刻进行任务调度时,得到pxReadyTasksLists.usNumberOfItems=O(i=2...portMAX_PRIORITIES)以及pxReadyTasksLists。usNumberOfItems=3。由此内核可知当前最高就绪优先级为l,且此优先级下已有三个任务已进入就绪态.由于最高就绪优先级下有多个就绪任务,系统需执行轮换调度</p>
<p>算法实现任务切换;通过<a href="https://baike.baidu.com/item/%E6%8C%87%E9%92%88" target="_blank">指针</a>pxlndex可知任务l为当前任务,而任务l的pxNext结点指向任务2,因此系统把pxIndex指向任务2并执行任务2来实现任务调度。当下一个时钟节拍到来时,若最高就绪优先级仍为1,由图l可见,系统会把pxIndex指向任务3并执行任务3。</p>
<p>为了加快任务调度的速度,FreeRTOS通过变量ucTopReadyPriotity跟踪当前就绪的最高优先级。当把一个任务加入就绪链表时,如果此任务的优先级高于ucTopReadyPriority,则把这个任务的优先级赋予ucTopReadyPriority。这样当进行优先级调度时,<a href="https://baike.baidu.com/item/%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95" target="_blank">调度算法</a>不是从portMAX_PRIORITIES而是从ucTopReady-Priority开始搜索。这就加快了搜索的速度,同时缩短了<a href="https://baike.baidu.com/item/%E5%86%85%E6%A0%B8" target="_blank">内核</a>关断时间。</p>
<p>2.2任务管理的实现 实现多个任务的有效管理是操作系统的主要功能。FreeRTOS下可实现创建任务、删除任务、挂起任务、恢复任务、设定任务优先级、获得任务相关信息等功能。下面主要讨论FreeRTOS下任务创建和任务删除的实现。当调用sTaskCreate()函数创建一个新的任务时,FreeRTOS首先为新任务分配所需的内存。若<a href="https://baike.baidu.com/item/%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D" target="_blank">内存分配</a>成功,则初始化任务控制块的任务名称、<a href="https://baike.baidu.com/item/%E5%A0%86%E6%A0%88" target="_blank">堆栈</a>深度和任务优先级,然后根据堆栈的增长方向初始化任务控制块的堆栈。接着,FreeRTOS把当前创建的任务加入到就绪任务链表。若当前此任务的优先级为最高,则把此优先级赋值给变量ucTopReadyPriorlty(其作用见2.1节)。若任务调度程序已经运行且当前创建的任务优先级为最高,则进行任务切换.</p>
<p>不同于μC/OS—II,FreeRTOS下任务删除分两步进行。当用户调用<a href="https://baike.baidu.com/item/vTaskDelete" target="_blank">vTaskDelete</a>()函数后,执行任务删除的第一步:FreeRTOS先把要删除的任务从就绪任务<a href="https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8" target="_blank">链表</a>和事件等待链表中删除,然后把此任务添加到任务删除链表,若删除的任务是当前运行任务,系统就执行任务调度函数,至此完成任务删除的第一步。当系统空闲任务即prvldleTask()函数运行时,若发现任务删除链表中有等待删除的任务,则进行任务删除的第二步,即释放该任务占用的内存空间,并把该任务从任务删除链表中删除,这样才彻底删除了这个任务。值得注意的是,在FreeRTOS中,当系统被配置为不可剥夺<a href="https://baike.baidu.com/item/%E5%86%85%E6%A0%B8" target="_blank">内核</a>时,空闲任务还有实现各个任务切换的功能。</p>
<p>通过比较μC/OS-II和FreeRTOS的具体代码发现,采用两步删除的策略有利于减少内核关断时间,减少任务删除函数的执行时间,尤其是当删除多个任务的时候。</p>
<p>2.3时间管理的实现 FreeRTOS提供的典型时间管理函数是vTaskDelay(),调用此函数可以实现将任务延时一段特定时间的功能。在FreeRT0S中,若一个任务要延时xTicksToDelay个时钟节拍,系统内核会把当前系统已运行的时钟节拍总数(定义为xTickCount,32位长度)加上xTicksToDelay得到任务下次唤醒时的时钟节拍数xTimeToWake。然后,<a href="https://baike.baidu.com/item/%E5%86%85%E6%A0%B8" target="_blank">内核</a>把此任务的任务控制块从就绪<a href="https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8" target="_blank">链表</a>中删除,把xTim</p>
<p>eToWake作为结点值赋予任务的xItemValue,再根据xTimeToWake的值把任务控制块按照顺序插入不同的链表。若xTimeToWake>xTickCount,即计算中没有出现溢出,内核把任务控制块插入到pxDelayedTaskList链表;若xTimeToWake 每发生一个时钟节拍,内核就会把当前的xTick-Count加1。若xTickCount的结果为0,即发生溢出,<a href="https://baike.baidu.com/item/%E5%86%85%E6%A0%B8" target="_blank">内核</a>会把pxOverflowDelayedTaskList作为当前<a href="https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8" target="_blank">链表</a>;否则,内核把pxDelaycdTaskList作为当前链表。内核依次比较xTickCotlrtt和链表各个结点的xTimcToWake。若xTick-Count等于或大于xTimeToWake,说明延时时间已到,应该把任务从等待链表中删除,加入就绪链表。</p>
<p>由此可见,不同于μC/OS—II,FreeRTOS采用“加”的方式实现时间管理。其优点是时间节拍函数的执行时间与任务数量基本无关,而μC/OS—II的OSTimcTick()的执行时间正比于应用程序中建立的任务数。因此当任务较多时,FreeRTOS采用的时间管理方式能有效加快时钟节拍中断程序的执行速度。</p>
<p>2.4内存分配策略 每当任务、队列和信号量创建的时候,FreeRTOS要求分配一定的RAM。虽然采用malloc()和free()函数可以实现申请和释放内存的功能,但这两个函数存在以下缺点:并不是在所有的<a href="https://baike.baidu.com/item/%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%B3%BB%E7%BB%9F" target="_blank">嵌入式系统</a>中都可用,要占用不定的程序空间,可重入性欠缺以及执行时间具有不可确定性。为此,除了可采用malloc()和free()函数外,FreeRTOS还提供了另外两种<a href="https://baike.baidu.com/item/%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D" target="_blank">内存分配</a>的策略,用户可以根据实际需要选择不同的内存分配策略。</p>
<p>第1种方法是,按照需求内存的大小简单地把一大块内存分割为若干小块,每个小块的大小对应于所需求内存的大小。这样做的好处是比较简单,执行时间可严格确定,适用于任务和队列全部创建完毕后再进行<a href="https://baike.baidu.com/item/%E5%86%85%E6%A0%B8" target="_blank">内核</a>调度的系统;这样做的缺点是,由于内存不能有效释放,系统运行时应用程序并不能实现删除任务或队列。</p>
<p>第2种方法是,采用<a href="https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8" target="_blank">链表</a>分配内存,可实现动态的创建、删除任务或队列。系统根据空闲内存块的大小按从小到大的顺序组织空闲内存链表。当应用程序申请一块内存时,系统根据申请内存的大小按顺序搜索空闲内存链表,找到满足申请内存要求的最小空闲内存块。为了提高内存的使用效率,在空闲内存块比申请内存大的情况下,系统会把此空闲内存块一分为二。一块用于满足申请内存的要求,一块作为新的空闲内存块插入到链表中。</p>
<p>下面以图2为例介绍方法2的实现。假定用于动态分配的RAM共有8KB,系统首先初始化空闲内存块链表,把8KB RAM全部作为一个空闲内存块。当应用程序分别申请1KB和2KB内存后,空闲内存块的大小变为5KB3。2KB的内存使用完毕后,系统需要把2KB插入到现有的空闲内存块链表。由于2 KB<5KB,所以把这2 KB插入5KB的内存块之前。若应用程序又需要申请3 KB的内存,而在空闲内存块链表中能满足申请内存要求的最小空闲内存块为5KB,因此把5KB内存拆分为2部分,3KB部分用于满足申请内存的需要,2KB部分作为新的空闲内存块插入链表。随后1KB的内存使用完毕需要释放,系统会按顺序把1KB内存插入到空闲内存链表中。</p>
<p>方法2的优点是,能根据任务需要高效率地使用内存,尤其是当不同的任务需要不同大小的内存的时候。方法二的缺点是,不能把应用程序释放的内存和原有的空闲内存混合为一体,因此,若应用程序频繁申请与释放“随机”大小的内存,就可能造成大量的<a href="https://baike.baidu.com/item/%E5%86%85%E5%AD%98%E7%A2%8E%E7%89%87" target="_blank">内存碎片</a>。这就要求应用程序申请与释放内存的大小为“有限个”固定的值(如图2中申请与释放内存的大小固定为l KB、2 KB或3 KB)。方法2的另一个缺点是,程序执行时间具有一定的不确定性。</p>
<p>μC/OS—II提供的内存管理机制是把连续的大块内存按分区来管理,每个分区中包含整数个大小相同的内存块。由于每个分区的大小相同,即使频繁地申请和释放内存也不会产生内存碎片问题,但其缺点是内存的利用率相对不高。当申请和释放的内存大小均为一个固定值时(如均为2 KB),FreeRTOS的方法2<a href="https://baike.baidu.com/item/%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D" target="_blank">内存分配</a>策略就可以实现类似μC/OS—Ⅱ的内存管理效果。</p>
<p>2.5FreeRTOS的移植 FreeRTOS操作系统可以被方便地移植到不同处理器上工作,现已提供了ARM、MSP430、AVR、PIC、C8051F等多款处理器的移植。FreeRTOS在不同处理器上的移植类似于μC/0S一II,故本文不再详述FreeRTOS的移植。此外,TCP/IP协议栈μIP已被移植到FreeRTOS上,具体代码可见FreeRTOS网站。</p>
<p>2.6FreeRTOS的不足 相对于常见的μC/OS—II操作系统,FreeRTOS操作系统既有优点也存在不足。其不足之处,一方面体现在系统的服务功能上,如FreeRTOS只提供了<a href="https://baike.baidu.com/item/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97" target="_blank">消息队列</a>和<a href="https://baike.baidu.com/item/%E4%BF%A1%E5%8F%B7%E9%87%8F" target="_blank">信号量</a>的实现,无法以后进先出的顺序向消息队列发送消息;另一方面,FreeRTOS只是一个<a href="https://baike.baidu.com/item/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%86%85%E6%A0%B8" target="_blank">操作系统内核</a>,需外扩第三方的GUI(<a href="https://baike.baidu.com/item/%E5%9B%BE%E5%BD%A2%E7%94%A8%E6%88%B7%E7%95%8C%E9%9D%A2" target="_blank">图形用户界面</a>)、TCP/IP协议栈、FS(文件系统)等才能实现一个较复杂的系统,不像μC/OS-II可以和μC/GUI、μC/FS、μC/TCP-IP等无缝结合。</p>
<p><strong>移植过程:</strong></p>
<p>从FREERTOS的官网下载最新的内核源码,下载的网址如下:https://sourceforge.net/projects/freertos/</p>
<p><strong>文件夹分析:</strong></p>
<p>FreeRTOS包含Demo例程和内核源码(比较重要,我们就需要提取该目录下的大部分文件)。<br />
<strong>Source</strong>文件夹里面包含的是FreeRTOS内核的源代码,我们移植FreeRTOS的时候就需要这部分源代码;<br />
<strong>Demo</strong> 文件夹里面包含了FreeRTOS官方为各个单片机移植好的工程代码,FreeRTOS为了推广自己,会给各种半导体厂商的评估板写好完整的工程程序,这些程序就放在Demo这个目录下,这部分Demo非常有参考价值。</p>
<p><strong>Source文件夹:</strong></p>
<p>这里我们再重点分析下FreeRTOS/ Source文件夹下的文件,①和③包含的是FreeRTOS的通用的头文件和C文件,这两部分的文件试用于各种编译器和处理器,是通用的。需要移植的头文件和C文件放在②portblle这个文件夹。</p>
<p>portblle文件夹,是与编译器相关的文件夹,在不同的编译器中使用不同的支持文件。①中的KEIL就是我们就是我们使用的编译器,其实KEIL里面的内容跟RVDS里面的内容一样,所以我们只需要③RVDS文件夹里面的内容即可,里面包含了各种处理器相关的文件夹,从文件夹的名字我们就非常熟悉了,我们学习的STM32有M0、M3、M4等各种系列,FreeRTOS是一个软件,单片机是一个硬件,FreeRTOS要想运行在一个单片机上面,它们就必须关联在一起。MemMang文件夹下存放的是跟内存管理相关的源文件。</p>
<p>我们是使用STM32cubeL5xx的GPIO反转的例程为模板添加FREERTOS</p>
<p></p>
<p>在GPIO_IOToggle文件夹下面创建一个文件夹FREERTOS,并将FREERTOS官网下载的文件中的SOURCE文件夹copy到FREERTOS文件夹中,或者是cube所带的软件库中也是带的有的freertos的文件的STM32Cube_FW_L5_V1.3.0\Middlewares\Third_Party\FreeRTOS中,本人使用的就是自带的FREERTOS的文件,</p>
<p>将以下文件添加到代码工程中。</p>
<p>并添加FreeRTOSConfig.h到include文件夹下面,否则会报错,一般的配置都是在此文件夹下面配置的。</p>
<p>再添加port.c的时候由于RVDS文件夹中没有M33内核的文件,所以我是从GCC文件夹下面的ARM_M33_NTZ问价夹下面添加的。并添加相应的路径。</p>
<p>并将PendSV_Handler,SysTick_Handler,SVC_Handler几个函数屏蔽否则会报错。</p>
<p>最后编译,发现没错,OK到此我们的移植已经完成。</p>
<p>下面创建一个闪灯的任务。</p>
<p>1.创建句柄</p>
<p>osThreadId_t LEDThreadHandle;<br />
const osThreadAttr_t LEDThread_attributes = {<br />
.name = "LEDThread",<br />
.priority = (osPriority_t) osPriorityNormal,<br />
.stack_size = 512<br />
};</p>
<p>2.函数初始化。</p>
<p>void BSP_LED_Init()<br />
{<br />
GPIO_InitTypeDef GPIO_Init;</p>
<p><br />
__HAL_RCC_GPIOB_CLK_ENABLE();</p>
<p><br />
/* configure the GPIO_LED pin */<br />
GPIO_Init.Pin = GPIO_PIN_7;<br />
GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP;<br />
GPIO_Init.Pull = GPIO_PULLUP;<br />
GPIO_Init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;<br />
HAL_GPIO_Init(GPIOB, &GPIO_Init);</p>
<p> HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);</p>
<p>}</p>
<p>3.回调函数:</p>
<p>void BSP_LED_Toggle(void)<br />
{<br />
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7);<br />
}<br />
/* USER CODE BEGIN Header_ToggleLEDsThread */<br />
/**<br />
* @brief Function implementing the LEDThread thread.<br />
* @param argument: Not used <br />
* @retval None<br />
*/<br />
/* USER CODE END Header_ToggleLEDsThread */<br />
void ToggleLEDsThread(void *argument)<br />
{<br />
/* USER CODE BEGIN 5 */<br />
(void) argument;<br />
/* Infinite loop */<br />
for(;;)<br />
{<br />
/* Toggle LED2 each 400ms */<br />
BSP_LED_Toggle();</p>
<p> osDelay(400);<br />
}<br />
/* USER CODE END 5 */<br />
}</p>
<p>写main函数:</p>
<p>int main(void)<br />
{<br />
HAL_Init();<br />
SystemClock_Config();<br />
BSP_LED_Init();<br />
osKernelInitialize();<br />
LEDThreadHandle = osThreadNew(ToggleLEDsThread, NULL, &LEDThread_attributes);<br />
osKernelStart();<br />
while (1)<br />
{<br />
/* USER CODE END WHILE */</p>
<p> /* USER CODE BEGIN 3 */<br />
}<br />
/* USER CODE END 3 */<br />
}</p>
<p>到此我们的移植已经完成,希望对大家有所帮助。</p>
<p> </p>
<p> </p>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>
页:
[1]