搜索
bottom↓
回复: 62

原创:我跟你一起学MQX(二):信号量

[复制链接]

出0入0汤圆

发表于 2014-9-5 21:21:10 | 显示全部楼层 |阅读模式
本帖最后由 wangpengcheng 于 2014-9-5 21:30 编辑

上一个帖子,我简单的做了两个任务,并顺利运行,运行结果用串口输出了,看起来还是蛮COOL的,这次,我想用个信号量,将两个任务进行同步!

先引入信号量的概念:信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。
我理解是这样的,它相当于一个计数器,发送的一端给计数器加-,而接收的一端从计数器里相应进行减一,但是它注定是一个非负数,如果减到0,那么就没得减了。

那么在MQX中,信号量的接口都有哪些呀?呵呵,在lwsem.h文件中找到了:
  1.     extern _mqx_uint        _lwsem_create(LWSEM_STRUCT_PTR, _mqx_int);
  2.     extern _mqx_uint        _lwsem_create_hidden(LWSEM_STRUCT_PTR, _mqx_int);
  3.     extern _mqx_uint        _lwsem_destroy(LWSEM_STRUCT_PTR);
  4.     extern   bool        _lwsem_poll(LWSEM_STRUCT_PTR);
  5.     extern _mqx_uint        _lwsem_post(LWSEM_STRUCT_PTR);
  6.     extern _mqx_uint        _lwsem_test(void **, void **);
  7.     extern _mqx_uint        _lwsem_wait(LWSEM_STRUCT_PTR);
  8.     extern _mqx_uint        _lwsem_wait_ticks(LWSEM_STRUCT_PTR, _mqx_uint);
  9.     extern _mqx_uint        _lwsem_wait_for(LWSEM_STRUCT_PTR, MQX_TICK_STRUCT_PTR);
  10.     extern _mqx_uint        _lwsem_wait_until(LWSEM_STRUCT_PTR, MQX_TICK_STRUCT_PTR);
复制代码


看看也没几个呀,呵呵,是的,就这么简单啦,先看看这些函数主要功能:
_lwsem_create 创建轻量级信号量
_lwsem_destroy 撤销轻量级信号量    ------------------相当于释放空间
_lwsem_poll 提升轻量级信号量(非阻塞)        --------------------这个我也没用过,呵呵,不知道什么意思
_lwsem_post 传递轻量级信号量        ----------------------------发送
_lwsem_test 测试轻量级信号量组件 ------------------------测试用的,不管它啦
_lwsem_wait 等待轻量级信号量  ----------------------------死等,直到出现需要的信号量
_lwsem_wait_for 为轻量级信号量等待指定的时钟滴答周期  --------------------------按时钟滴答等待
_lwsem_wait_ticks 为轻量级信号量等待指定的时钟滴答数  ----------------------------按时钟滴答等等
_lwsem_wait_until 等待轻量级信号量直到指定的时钟滴答数结束  --------------------------在一定的时间段内等待信号量

嗯,我承认,后面那三个条件等待我至今没有用过!(其实我也就用MQX做了一个比较小的项目 )不过不影响啊,今天我说的这个还是比较简单,还用不到那个!

看完函数,我想做个过程,大概是这样的,还是上次那个例程,我让Hello任务发个信号量给Yes任务,让Yes任务回应!

好了,首先定义一个Apple信号量,当然要是全局变量,因为我两个任务都要使用呢:
  1. LWSEM_STRUCT Apple;
复制代码


然后在Hello任务中,我让他拿一个苹果给别人,代码如下:
  1. /*TASK*-----------------------------------------------------
  2. *
  3. * Task Name    : hello_task
  4. * Comments     :
  5. *    This task prints " Hello World "
  6. *
  7. *END*-----------------------------------------------------*/
  8. void hello_task
  9.     (
  10.         uint32_t initial_data
  11.     )
  12. {
  13.     (void)initial_data; /* disable 'unused variable' warning */
  14.     //创建信号量,注意后面那个参数是信号量的初始值
  15.     _mqx_uint result = _lwsem_create(&Apple, 0);
  16.     if (result != MQX_OK) {
  17.     printf("\nCreating write_sem failed: 0x%X", result);
  18.     _mqx_exit(0);
  19. }
  20.     int i;
  21.     for(i = 0; ;i++)
  22.     {
  23.         printf("Hello,An apple for you!\n");
  24.         //信号量发送
  25.         _lwsem_post(&Apple);
  26.         _time_delay(1000);
  27.     }
  28. }
复制代码


注意:在信号量创建的时候信号量的初始值,如果初始化需要有信号量存在,那么,初始值可根据你的需要赋值。如果不需要,那就写0。以前有个同事,初始化信号量的时候给写了个1,然后进程总是不稳定,我们查了一星期才找到这个原因,所以这块一定要注意!

好了,送苹果的一方已经搞定了,那么我们再看看收苹果的一方,收到当然要谢谢啦:
  1. void yes_task(uint32_t para)
  2. {
  3.     int i;
  4.     for(i = 0; ;i++)
  5.     {
  6.         _lwsem_wait(&Apple);  //信号量等待
  7.         printf("thanks!\n", i);
  8.     }
  9. }
复制代码


呵呵,跟以前不一样了,把延时1秒给去掉了,加了个信号量等待!

可以啦,现在看下运行结果如何:


看到没,Hello任务给一次,yes任务就谢谢一次,多友好啊!

很简单吧,我已经让两个任务之间有了关系了!其实还有其它类似信号量的元素在OS中,以后再说吧!

我只是举了个最简单的例子,信号量的应用网上有一个最经典的生产消费者的例子,可以看一下,真的很经典!

例程:

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出100入101汤圆

发表于 2014-9-5 21:25:14 | 显示全部楼层
中断中,常释放信号量。

出0入0汤圆

 楼主| 发表于 2014-9-5 21:26:38 | 显示全部楼层
fengyunyu 发表于 2014-9-5 21:25
中断中,常释放信号量。


是的,像很多OS的串口接收,都是在中断中释放一个信号量,然后在线程中等待到信号量后去Buff中拿数据!这也是比较经典的信号量应用!

出0入0汤圆

发表于 2014-9-5 22:36:33 | 显示全部楼层
仔细看了
貌似像 握手, 收藏之

出0入0汤圆

发表于 2014-9-5 22:43:54 | 显示全部楼层
原来 如此。

一直没有看完过uCOS的信号量。看完这个帖子就算是入门了。

出0入0汤圆

发表于 2014-9-5 22:54:23 | 显示全部楼层
哈,期待露珠一直写下去,跟着把MQX学会。

出0入0汤圆

发表于 2014-9-5 22:55:58 | 显示全部楼层
我之前在FRDM-KL25上用过MQX-lite,里面的函数都是轻量级的,现在看到这些函数好熟悉,其实你这个例子也可以使用互斥量来实现。

出0入0汤圆

发表于 2014-9-5 23:00:28 | 显示全部楼层
楼主的这个系列帖子,我应该会一直看下去。。很基础,也很容易懂。我是一个没接触OS的,正打算学习UCOS或者emWIN,还是FSL的MQX,现在有些犹豫了?前辈觉得那个比较有用点呢?

出0入0汤圆

发表于 2014-9-5 23:10:20 | 显示全部楼层
又学习了,多谢LZ

出500入109汤圆

发表于 2014-9-5 23:16:18 | 显示全部楼层
高产小奶牛啊,哈哈,再来一个TOWER的节奏

出0入0汤圆

发表于 2014-9-5 23:27:41 | 显示全部楼层
MQX的函数里形参怎么 全部大写的,小写的都有,感觉很混乱啊
看PE生成的代码 怎一个乱字

出0入0汤圆

发表于 2014-9-5 23:29:40 | 显示全部楼层
我的理解,它就是一个标志位。。。。

出0入0汤圆

发表于 2014-9-5 23:40:15 | 显示全部楼层
信号量这块操作起来算是比较简单的 了,上手容易,也不易出错

出0入0汤圆

发表于 2014-9-5 23:42:34 | 显示全部楼层
qinshiysb 发表于 2014-9-5 23:00
楼主的这个系列帖子,我应该会一直看下去。。很基础,也很容易懂。我是一个没接触OS的,正打算学习UCOS或者 ...

个人觉得哪个资料多学哪个,正所谓一通百通,os的原理都差不错的,线程之间的通信也就那么几种,多跑几个程序,很快就可以搞明白了

出0入0汤圆

发表于 2014-9-5 23:48:58 | 显示全部楼层
wxfje 发表于 2014-9-5 23:42
个人觉得哪个资料多学哪个,正所谓一通百通,os的原理都差不错的,线程之间的通信也就那么几种,多跑几个 ...

现在呢是UCOS 多一点吧,不过emWIN,是它的升级版,而且野火,安富莱都有相应的教程出来了,就有些纠结

出0入0汤圆

发表于 2014-9-5 23:59:18 来自手机 | 显示全部楼层
写的不错,跟VxWorks的信号量用法很相似。

出0入0汤圆

 楼主| 发表于 2014-9-6 00:00:46 来自手机 | 显示全部楼层
mcucow 发表于 2014-9-5 22:36
仔细看了
貌似像 握手, 收藏之

信号量是可以累积的,比如像串口中断发送,可能发送出的前一个任务中还没来得及处理,第二个又发了,那么在任务运行时可以取两次

出0入0汤圆

 楼主| 发表于 2014-9-6 00:01:26 来自手机 | 显示全部楼层
franki 发表于 2014-9-5 23:59
写的不错,跟VxWorks的信号量用法很相似。

信号量都差不多,呵呵

出0入0汤圆

 楼主| 发表于 2014-9-6 00:03:55 来自手机 | 显示全部楼层
yzb1019 发表于 2014-9-5 23:29
我的理解,它就是一个标志位。。。。

可以这么说,但它是有深度的,互斥量才是真正的标志,当然信号量也可当互斥量用,改天再说互斥量吧

出0入0汤圆

发表于 2014-9-6 00:04:53 来自手机 | 显示全部楼层
wangpengcheng 发表于 2014-9-6 00:01
信号量都差不多,呵呵

请问,mqx自带网络协议栈嘛?

出0入0汤圆

 楼主| 发表于 2014-9-6 00:05:06 来自手机 | 显示全部楼层
rootxie 发表于 2014-9-5 23:27
MQX的函数里形参怎么 全部大写的,小写的都有,感觉很混乱啊
看PE生成的代码 怎一个乱字 ...

我没觉得乱呀,其实都有自己的风格

出0入0汤圆

发表于 2014-9-6 00:06:29 | 显示全部楼层
wangpengcheng 发表于 2014-9-6 00:03
可以这么说,但它是有深度的,互斥量才是真正的标志,当然信号量也可当互斥量用,改天再说互斥量吧 ...

在系统中的信号量和逻辑中的信号量用起来完全不同,而且互斥信号量好像在裸机中毫无用处,我是这样看的。

出0入0汤圆

 楼主| 发表于 2014-9-6 00:06:31 来自手机 | 显示全部楼层
franki 发表于 2014-9-6 00:04
请问,mqx自带网络协议栈嘛?

有的,你可以装上看看,有很多例子

出0入0汤圆

发表于 2014-9-6 00:08:11 来自手机 | 显示全部楼层
yzb1019 发表于 2014-9-5 23:29
我的理解,它就是一个标志位。。。。

赞同,可以这么理解,只是不能随便修改的标志位,要向操作系统申请

出0入0汤圆

 楼主| 发表于 2014-9-6 00:08:22 来自手机 | 显示全部楼层
yzb1019 发表于 2014-9-6 00:06
在系统中的信号量和逻辑中的信号量用起来完全不同,而且互斥信号量好像在裸机中毫无用处,我是这样看的。 ...

下次给你解释吧,呵呵,其实用处还是挺大的

出0入0汤圆

 楼主| 发表于 2014-9-6 00:09:51 来自手机 | 显示全部楼层
qinshiysb 发表于 2014-9-5 23:00
楼主的这个系列帖子,我应该会一直看下去。。很基础,也很容易懂。我是一个没接触OS的,正打算学习UCOS或者 ...

其实都差不多,了解架构应用后,选择资源多的用

出0入0汤圆

 楼主| 发表于 2014-9-6 00:11:31 来自手机 | 显示全部楼层
bruce_helen 发表于 2014-9-5 22:55
我之前在FRDM-KL25上用过MQX-lite,里面的函数都是轻量级的,现在看到这些函数好熟悉,其实你这个例子也可 ...

这个例子不适合用互斥量来玩,下集再说

出0入0汤圆

 楼主| 发表于 2014-9-6 00:14:38 来自手机 | 显示全部楼层
street 发表于 2014-9-5 22:43
原来 如此。

一直没有看完过uCOS的信号量。看完这个帖子就算是入门了。

我跟你的路子有点不一样,我是先学会应用,应用过程中有机会再学原理,呵呵,很多时候不看源代码

出0入0汤圆

 楼主| 发表于 2014-9-6 00:16:01 来自手机 | 显示全部楼层
浪里白条 发表于 2014-9-5 22:54
哈,期待露珠一直写下去,跟着把MQX学会。

会抽空做的,呵呵,你用i.max,这些肯定熟悉

出0入0汤圆

发表于 2014-9-6 06:08:31 | 显示全部楼层
wangpengcheng 发表于 2014-9-6 00:14
我跟你的路子有点不一样,我是先学会应用,应用过程中有机会再学原理,呵呵,很多时候不看源代码 ...

是的,先学会做应用,然后再根据需要阅读源代码,理解起来就简单点了。相对于uC/OS,MQX的源代码太难看了,代码风格跟linux有点像,资源占用也比uC/OS多。

出0入0汤圆

发表于 2014-9-6 06:26:15 | 显示全部楼层
wangpengcheng 发表于 2014-9-6 00:08
下次给你解释吧,呵呵,其实用处还是挺大的


这个互斥信号量在系统中的地位我还是知道点,挺重要的,尤其是对于独占式的资源访问。。。

出0入0汤圆

发表于 2014-9-6 08:39:41 | 显示全部楼层
楼主牛叉啊,顶,收藏!

出0入0汤圆

发表于 2014-9-6 08:46:54 | 显示全部楼层
看了(一)任务,云里雾里的,没有一套现成的板子来实践一下还是不行的。
我就当做是先了解一下这些OS的概念,什么信号量,抢占优先级等,先跟着你学习MQX。你自己写的很是详细,如果能够对照着去学习,我估计也是可以入门的。

出0入0汤圆

发表于 2014-9-6 09:52:53 来自手机 | 显示全部楼层
楼主这个系列贴子确实精,回老家,手机看不舒服,有些想法回去在跟楼主讨论。

出0入0汤圆

 楼主| 发表于 2014-9-6 10:48:40 | 显示全部楼层
rockyyangyang 发表于 2014-9-6 08:46
看了(一)任务,云里雾里的,没有一套现成的板子来实践一下还是不行的。
我就当做是先了解一下这些OS的概 ...

你的飞币已经快够一个高级国产板了,呵呵,肯定有机会的!

出0入0汤圆

发表于 2014-9-6 11:38:37 | 显示全部楼层
wangpengcheng 发表于 2014-9-6 00:14
我跟你的路子有点不一样,我是先学会应用,应用过程中有机会再学原理,呵呵,很多时候不看源代码 ...

我的意思是 我一直不知道信号量是干什么用的 ,现在知道了。

出0入0汤圆

 楼主| 发表于 2014-9-6 11:55:39 | 显示全部楼层
street 发表于 2014-9-6 11:38
我的意思是 我一直不知道信号量是干什么用的 ,现在知道了。

信号量的用法比较多,呵呵,这只是最简单的方法,可以让你先认识他,以后你用的时候时间久了就发现用起来其实很灵活!

出0入0汤圆

发表于 2014-9-6 21:58:48 | 显示全部楼层
wangpengcheng 发表于 2014-9-6 10:48
你的飞币已经快够一个高级国产板了,呵呵,肯定有机会的!

恩,最近先自己沉淀沉淀,好好在学习,到时候才有技术给大家开源或者共享一些有用的资料赚飞币。哈哈。到时候国产开发板到了,就全身投入学习。

出0入0汤圆

发表于 2014-9-7 16:20:49 | 显示全部楼层
qinshiysb 发表于 2014-9-5 23:48
现在呢是UCOS 多一点吧,不过emWIN,是它的升级版,而且野火,安富莱都有相应的教程出来了,就有些纠结 ...

emWIN的资料好像挺多了,你可以就学他,不懂得看看ucos相关的就可以了

出0入0汤圆

发表于 2014-9-7 16:31:32 | 显示全部楼层
wxfje 发表于 2014-9-7 16:20
emWIN的资料好像挺多了,你可以就学他,不懂得看看ucos相关的就可以了

是的哇,,,资料很详细的呢,我就打算学这个了

出0入0汤圆

发表于 2014-9-7 16:58:39 | 显示全部楼层
qinshiysb 发表于 2014-9-7 16:31
是的哇,,,资料很详细的呢,我就打算学这个了

资料多。例程多,很容易学的,一步步来,从简单到复杂到项目,很快就可以搞定的

出0入0汤圆

发表于 2014-9-7 17:32:53 来自手机 | 显示全部楼层
不错,支持楼主

出0入0汤圆

发表于 2014-9-7 20:36:55 | 显示全部楼层
wangpengcheng 发表于 2014-9-6 00:06
有的,你可以装上看看,有很多例子

好的  ,有机会研究一下,貌似很强大

出0入0汤圆

发表于 2014-9-7 21:14:03 | 显示全部楼层
不错,开始追楼主的帖子

出0入0汤圆

发表于 2014-9-8 20:39:56 | 显示全部楼层
  _mqx_uint result = _lwsem_create(&Apple, 0);      //这个地方创建了信号量Apple

_lwsem_post(&Apple);      //然后在for循环中不停的发送这个信号量

_lwsem_wait(&Apple);     //信号量等待
但是我没有看到,打印“Hello,An apple for you!”后发送信号量前,对Apple的值进行修改(0---1),而且直接发送,请问此时发送的是初始化的值吗?
同样的在应答yes任务中,接收完接收任务后,打印yes后是否应改变信号量的值(1--0)?

出0入0汤圆

发表于 2014-9-8 20:40:55 | 显示全部楼层
或者说楼主在其他地方有说明?请多多指教。。。

出0入4汤圆

发表于 2014-9-8 20:55:28 | 显示全部楼层
学习了。。。

出0入0汤圆

 楼主| 发表于 2014-9-8 22:08:24 来自手机 | 显示全部楼层
步之道 发表于 2014-9-8 20:39
_mqx_uint result = _lwsem_create(&Apple, 0);      //这个地方创建了信号量Apple

_lwsem_post(&Apple ...

信号量的值每发一次就加一,每取一次就减一,根本不用知道信号量目前的值是几啊,因为它是非负数,减到零就等待了,直到有新的发送才会继续取

出0入0汤圆

 楼主| 发表于 2014-9-8 22:10:01 来自手机 | 显示全部楼层
步之道 发表于 2014-9-8 20:39
_mqx_uint result = _lwsem_create(&Apple, 0);      //这个地方创建了信号量Apple

_lwsem_post(&Apple ...

简单的说,信号量只能是以一为单位的增减

出0入0汤圆

发表于 2014-9-8 22:10:22 | 显示全部楼层
wangpengcheng 发表于 2014-9-8 22:08
信号量的值每发一次就加一,每取一次就减一,根本不用知道信号量目前的值是几啊,因为它是非负数,减到零 ...

在你贴的程序里我没有看到加和减,你是在其他地方进行该操作的,还是MQX自身有这个功能?

出0入0汤圆

发表于 2014-9-8 22:14:49 | 显示全部楼层
wangpengcheng 发表于 2014-9-8 22:10
简单的说,信号量只能是以一为单位的增减

看完本贴后我的理解是,在打印语句后,传送语句前就应该将信号量的值加1了,    然后在接收到打印yes后应该将信号量减1.

出0入0汤圆

 楼主| 发表于 2014-9-8 23:02:55 来自手机 | 显示全部楼层
步之道 发表于 2014-9-8 22:10
在你贴的程序里我没有看到加和减,你是在其他地方进行该操作的,还是MQX自身有这个功能? ...

这就是信号量自身的功能,不要去研究原码,你知道有这功能就行

出0入0汤圆

 楼主| 发表于 2014-9-8 23:05:20 来自手机 | 显示全部楼层
步之道 发表于 2014-9-8 22:14
看完本贴后我的理解是,在打印语句后,传送语句前就应该将信号量的值加1了,    然后在接收到打印yes后应 ...

你这么理解,发送信号量,就是将信号量加一,接收信号量,就是将信号量减一

出0入0汤圆

 楼主| 发表于 2014-9-8 23:07:24 来自手机 | 显示全部楼层
步之道 发表于 2014-9-8 22:10
在你贴的程序里我没有看到加和减,你是在其他地方进行该操作的,还是MQX自身有这个功能? ...

这是信号量本身的意义,不仅是在mqx中,其它OS也是一样的

出0入0汤圆

发表于 2014-9-9 08:51:53 | 显示全部楼层
wangpengcheng 发表于 2014-9-8 23:07
这是信号量本身的意义,不仅是在mqx中,其它OS也是一样的

感谢,企鹅兄的讲解了,加减1在post函数中应该能看到,哈哈,也就是说可以自己写函数模拟信号量的变化,只是MQX将这个功能集成好了。

出0入0汤圆

发表于 2014-9-9 08:58:42 | 显示全部楼层
请教下,信号量就信号量,MQX有轻量级信号量,那重量级信号量表示什么东东?
信号量可能会导致优先级翻转的

出0入0汤圆

 楼主| 发表于 2014-9-9 14:37:57 | 显示全部楼层
rootxie 发表于 2014-9-9 08:58
请教下,信号量就信号量,MQX有轻量级信号量,那重量级信号量表示什么东东?
信号量可能会导致优先级翻转的 ...

你知道的太多了 ,轻量级信号量顾名思义,就是占用资源较小,飞思卡尔将MQX信号量中一些特殊应用的东西裁剪掉,形成轻量级信号量,使用起来很方便。你说的重量级我也没研究,轻量级的已经够我目前使用了!

信号量是会造成优先级翻转,我也是了解了个原理,至目前还没遇到过需要注意这个的项目,或许以后会吧!

呵呵,觉得你研究的东西比我深多了,把你的经验也发帖子出来大家一起学习!

出0入0汤圆

发表于 2014-10-6 15:09:08 | 显示全部楼层
楼主写的确实通俗易懂,很棒,加油,顶起!

出0入42汤圆

发表于 2014-10-6 15:31:47 | 显示全部楼层
路过,看看。

出0入0汤圆

发表于 2015-2-28 14:40:41 | 显示全部楼层
rootxie 发表于 2014-9-9 08:58
请教下,信号量就信号量,MQX有轻量级信号量,那重量级信号量表示什么东东?
信号量可能会导致优先级翻转的 ...

MQX的重量级信号量有些属性可以设置可以防止优先级翻转。

Light Weight(轻量级)中裁剪了这些属性,具体可以看下MQX User Guide,其中有详细介绍。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2015-2-28 14:42:45 | 显示全部楼层
LZ教程挺不错的,目前正在学习MQX中,多交流。

出0入0汤圆

发表于 2015-3-12 19:39:41 | 显示全部楼层
LZ教程挺不错的,关注中

出0入0汤圆

发表于 2019-2-12 17:00:52 | 显示全部楼层
mark ,不错,学习一下
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-20 20:08

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表