打印
[ARM入门]

微型时间片轮转操作系统:LineOS

[复制链接]
3627|24
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
nicholasldf|  楼主 | 2015-12-19 11:11 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 nicholasldf 于 2015-12-19 14:49 编辑

微型时间片轮转操作系统,其实只能叫做时间片轮转调度器,就是不断轮流切换任务的功能,只有LineOS.h 和 LineOS.c两个文件
核心代码就几个函数,大概2007年写的,昨晚给部门做uCOS-III培训,又翻了出来,,希望对于嵌入式操作系统初学者理解OS的概念有一点点帮助。

使用:
可以用在实际编程当中,,把功能分为多个任务模块,分而治之处理。

扩展:
可以稍微改造,可以让任务主动放弃CPU,以便CPU利用率最大化,并加入idle空闲任务。
可以针对STM32稍作移植,也很容易。


//========================================LineOS核心开始=========================================================
//-------------------------------------------------头文件------------------------------------------------------------
//这个头文件就是定义了任务结构体,还有两个宏定义,在切换任务时,pushall()用于保存CPU上下文到当前任务,popall()用于恢复下一个任务的CPU上下文

//任务结构体
typedef struct
{
        unsigned char* task_stk_top;//堆栈顶部
        unsigned char* task_stk_bottom;//堆栈底部
        unsigned char  task_stk_size;//堆栈大小
}OS_TASK_TCB;

//宏定义,将所有的CPU寄存器,压入到当前任务堆栈
#define pushall() \
        __asm__ __volatile__ ("push r1"   "\n\t");\
        __asm__ __volatile__ ("push r0"   "\n\t");\
        __asm__ __volatile__ ("in  r0, 0x3f"   "\n\t");\
        __asm__ __volatile__ ("push r0"   "\n\t");\
        __asm__ __volatile__ ("eor r1, r1"   "\n\t");\
        __asm__ __volatile__ ("push r2"   "\n\t");\
        __asm__ __volatile__ ("push r3"   "\n\t");\
        __asm__ __volatile__ ("push r4"   "\n\t");\
        __asm__ __volatile__ ("push r5"   "\n\t");\
        __asm__ __volatile__ ("push r6"   "\n\t");\
        __asm__ __volatile__ ("push r7"   "\n\t");\
        __asm__ __volatile__ ("push r8"   "\n\t");\
        __asm__ __volatile__ ("push r9"   "\n\t");\
        __asm__ __volatile__ ("push r10"   "\n\t");\
        __asm__ __volatile__ ("push r11"   "\n\t");\
        __asm__ __volatile__ ("push r12"   "\n\t");\
        __asm__ __volatile__ ("push r13"   "\n\t");\
        __asm__ __volatile__ ("push r14"   "\n\t");\
        __asm__ __volatile__ ("push r15"   "\n\t");\
        __asm__ __volatile__ ("push r16"   "\n\t");\
        __asm__ __volatile__ ("push r17"   "\n\t");\
        __asm__ __volatile__ ("push r18"   "\n\t");\
        __asm__ __volatile__ ("push r19"   "\n\t");\
        __asm__ __volatile__ ("push r20"   "\n\t");\
        __asm__ __volatile__ ("push r21"   "\n\t");\
        __asm__ __volatile__ ("push r22"   "\n\t");\
        __asm__ __volatile__ ("push r23"   "\n\t");\
        __asm__ __volatile__ ("push r24"   "\n\t");\
        __asm__ __volatile__ ("push r25"   "\n\t");\
        __asm__ __volatile__ ("push r26"   "\n\t");\
        __asm__ __volatile__ ("push r27"   "\n\t");\
        __asm__ __volatile__ ("push r28"   "\n\t");\
        __asm__ __volatile__ ("push r29"   "\n\t");\
        __asm__ __volatile__ ("push r30"   "\n\t");\
        __asm__ __volatile__ ("push r31"   "\n\t");\


//宏定义,将下一个任务堆栈里面的所有上下文,弹出到CPU寄存器
#define popall() \
        __asm__ __volatile__ ("pop r31"   "\n\t");\
        __asm__ __volatile__ ("pop r30"   "\n\t");\
        __asm__ __volatile__ ("pop r29"   "\n\t");\
        __asm__ __volatile__ ("pop r28"   "\n\t");\
        __asm__ __volatile__ ("pop r27"   "\n\t");\
        __asm__ __volatile__ ("pop r26"   "\n\t");\
        __asm__ __volatile__ ("pop r25"   "\n\t");\
        __asm__ __volatile__ ("pop r24"   "\n\t");\
        __asm__ __volatile__ ("pop r23"   "\n\t");\
        __asm__ __volatile__ ("pop r22"   "\n\t");\
        __asm__ __volatile__ ("pop r21"   "\n\t");\
        __asm__ __volatile__ ("pop r20"   "\n\t");\
        __asm__ __volatile__ ("pop r19"   "\n\t");\
        __asm__ __volatile__ ("pop r18"   "\n\t");\
        __asm__ __volatile__ ("pop r17"   "\n\t");\
        __asm__ __volatile__ ("pop r16"   "\n\t");\
        __asm__ __volatile__ ("pop r15"   "\n\t");\
        __asm__ __volatile__ ("pop r14"   "\n\t");\
        __asm__ __volatile__ ("pop r13"   "\n\t");\
        __asm__ __volatile__ ("pop r12"   "\n\t");\
        __asm__ __volatile__ ("pop r11"   "\n\t");\
        __asm__ __volatile__ ("pop r10"   "\n\t");\
        __asm__ __volatile__ ("pop r9"   "\n\t");\
        __asm__ __volatile__ ("pop r8"   "\n\t");\
        __asm__ __volatile__ ("pop r7"   "\n\t");\
        __asm__ __volatile__ ("pop r6"   "\n\t");\
        __asm__ __volatile__ ("pop r5"   "\n\t");\
        __asm__ __volatile__ ("pop r4"   "\n\t");\
        __asm__ __volatile__ ("pop r3"   "\n\t");\
        __asm__ __volatile__ ("pop r2"   "\n\t");\
        __asm__ __volatile__ ("pop r0"   "\n\t");\
        __asm__ __volatile__ ("out 0x3f, r0"   "\n\t");\
        __asm__ __volatile__ ("pop r0"   "\n\t");\
        __asm__ __volatile__ ("pop r1"   "\n\t");\
        __asm__ __volatile__ ("RETI"   "\n\t");\



//-------------------------------------------------源文件------------------------------------------------------------
//头文件
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "LineOS.h"

//volatile unsigned int play =0;
volatile unsigned char next_task = 0;//指示下一个任务的变量
volatile unsigned char count = 0;
//堆栈使用情况检测函数
void task_stack_chek(unsigned char task_id,unsigned char*stkused);

/*任务堆栈初始化函数,在创建任务时调用,参数task为任务入口地址,task_id为任务ID编码*/
unsigned char* stackinit( void (*task)( void ),   unsigned char task_id )
{
    unsigned char* stk;
    unsigned int temp;         

        temp = (unsigned int)task;//任务入口地址

        stk = &( task_stack[task_id][99] );//指向栽顶
        *stk-- = (unsigned char)(temp&0xff);//任务入口地址入栽
        *stk-- = (unsigned char)(temp>>8);
        //32个寄存器和CPU寄存器入栽
        *stk-- = (unsigned char)0x00;        /* R0  = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R1  = 0x00 */
        *stk-- = (unsigned char)0x80;/*SREG寄存器入栽,使能全局中断有效*/
        *stk-- = (unsigned char)0x80;        /* R2  = 0x00 */        
        *stk-- = (unsigned char)0x00;        /* R3  = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R4  = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R5  = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R6  = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R7  = 0x00 */        
        *stk-- = (unsigned char)0x00;        /* R8  = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R9  = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R10 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R11 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R12 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R13 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R14 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R15 = 0x00 */        
        *stk-- = (unsigned char)0x00;        /* R16 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R17 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R18 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R19 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R20 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R21 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R22 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R23 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R24 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R25 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R26 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R27 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R28 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R29 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R30 = 0x00 */
        *stk-- = (unsigned char)0x00;        /* R31 = 0x00 */
        return ( (unsigned char*)stk );                //返回堆栽栽顶指针
}

/*任务堆栈检查函数,也就是task_id指定任务的堆栈使用了多少,有没有溢出*/
void task_stack_chek(unsigned char task_id,   unsigned char*stkused)
{
      unsigned char unused=0;
      unsigned char *p;

      p = task_tcb[task_id].task_stk_bottom;

      while( 0 == *p++ ){
           unused ++;
      }

    *stkused = task_tcb[task_id].task_stk_size - unused;
}


/*任务堆栈清零函数,创建任务时,清零task_id指定任务的堆栈,以便做堆栈使用量检查*/
void task_stack_cLR(unsigned char task_id)
{  
       unsigned char *temp = task_tcb[task_id].task_stk_bottom;

       for(unsigned char i=0; i<task_tcb[task_id].task_stk_size; i++){
            temp = 0;
       }   
}

/*任务创建函数,初始化任务结构体,清零并初始化堆栈,保存堆栈指针,task是任务入口函数地址,task_id 是任务ID编码*/
void taskcreat( void (*task)( void ), unsigned char task_id )
{
        unsigned char *temp;

        task_tcb[task_id].task_stk_size=100;
        task_tcb[task_id].task_stk_bottom = &( task_stack[ task_id ][0] );
        task_stack_cLR(task_id);
        temp = stackinit( task, task_id );//初始化任务堆栽
        task_tcb[task_id].task_stk_top = temp;//保存栽顶指针        
}

/*操作系统启动函数,运行第一个任务*/
void OS_Start( void )
{      
        //定时器0初始化
        //TCCR0 = 0X01;
        //TIMSK = 0X01;
        //TCNT0 = 0x00;
        //sei();

        //CPU堆栈指针指向第一个任务的堆栈指针,准备运行第一个任务
        SP = task_tcb[0].task_stk_top + 33;
        //用中断返回指令将入口地址弹出到PC指针,运行第一个任务
        asm("RETI");
}

/*操作系统定时器节拍中断,按照时间片轮转,切换任务,保存CPU上下文到当前任务堆栈,从下一个任务堆栈恢复下一个任务的CPU上下文*/
//属性设置:让中断程序不保存任何寄存器,以便通过pushall、popall保存和恢复上下文
void SIG_OVERFLOW0( void ) __attribute__ ( ( signal, naked ) );
//中断处理函数
SIGNAL( SIG_OVERFLOW0 )
{   
        pushall( );//保存当前任务的所有寄存器
        count++;
        if(count>100)
        {
                //注:SP是CPU的堆栈寄存器
                count = 0;               
                task_tcb[ next_task % 8 ].task_stk_top =SP;//保存当前任务的栈顶SP指针到当前任务的控制块TCB
                next_task++;
                SP = task_tcb[ next_task % 8 ].task_stk_top;//指向下一个任务的栈顶SP指针
        }
        popall( );//恢复下一个任务的所有寄存器
}
//========================================LineOS核心结束=========================================================




//========================================下面是与OS核心无关的示例代码=========================================================
//========================================应用例子开始=========================================================
//LED灯端口定义
#define LED_PORT PORTA
#define LED_DDR  DDRA
#define LED_PIN  PINA

#define delay  33//时间延迟设定
#define delay_time  _delay_ms(10);//时间

volatile OS_TASK_TCB task_tcb[8];//任务结构体数组
unsigned char  task_stack[8][100];//任务堆栈数组

/* 主函数main,创建了8个任务,并启动操作系统 */
int main( void )
{  
        //LED灯控制端口设置
        LED_PORT=0Xff;
        LED_DDR =0Xff;   

        //lcd_init();

        taskcreat( task1, 0 );
        taskcreat( task2, 1 );
        taskcreat( task3, 2 );
        taskcreat( task4, 3 );
        taskcreat( task5, 4 );
        taskcreat( task6, 5 );
        taskcreat( task7, 6 );
        taskcreat( task8, 7 );

        OS_Start( );//开始运行任务

        while( 1 );   
}


/*第一个任务,初始化LCD,定时器,定义了一个大数组,分配于堆栈,因此其堆栈使用深度大一些。在LCD显示,自身堆栈使用情况,以及从‘A’到‘Z’*/
void task1( void )
{   
        unsigned char temp, play ='A';
        unsigned char stkused =0;
        unsigned char arry[30] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30};

        //LCD1602初始化
        lcd_init();

        //定时器初始化
        TCCR0 = 0X05;
            TIMSK = 0X01;

        //LCD1602清屏
        lcd_write_comm(0x01);

        //定时器计数寄存器清零,并使能全局CPU中断
        TCNT0 = 0x00;
        sei();

        while( 1 ) {
                //任务1自身堆栈使用情况检查        
                task_stack_chek( next_task%8, &stkused );

                //在LCD指定位置,显示大写字母'A'到'Z'的变化
                if( play >'Z' ){
                    play ='A';
                }
                lcd_posion( 0, 0 );
                lcd_write_data( play++ );

                //在LCD指定位置显示自己的堆栈使用量
                lcd_write_str(1,0,":");                 
                temp = (stkused/10)%10 + 48;
                lcd_write_data( temp );               
                temp = stkused%10 + 48;
                lcd_write_data( temp );

                //翻转8个LED灯,达到闪烁效果
                for( temp =0; temp <30; temp ++){
                   LED_PORT ^= 1 << ( arry[temp] % 8 );
                   _delay_ms(66);
                   _delay_ms(66);
                }

                //延时
                for( unsigned char i=0; i<delay; i++)
                    delay_time;
        }
}

/*第二个任务,在LCD显示,自身堆栈使用情况,以及从‘a’到‘z’*/
void task2(void)
{
        unsigned char temp, play ='a';
        unsigned char stkused=0;        

        while(1){
                //任务2自身堆栈使用情况检查
                task_stack_chek(next_task%8,&stkused);

                //在LCD指定位置,显示小写字母'a'到'z'的变化
                if( play >'z' ){
                    play ='a';
                }
                lcd_posion( 4, 0 );
                lcd_write_data( play++ );

                //在LCD指定位置显示自己的堆栈使用量
                lcd_write_str(5,0,":");        
                temp = (stkused/10)%10 + 48;
                lcd_write_data( temp );               
                temp = stkused%10 + 48;
                lcd_write_data( temp );               

                //延时
                for( unsigned char i=0; i<delay; i++)
                    delay_time;
    }
}

/*第三个任务,在LCD显示,自身堆栈使用情况*/
void task3(void)
{
        unsigned char temp ;
        unsigned char stkused=0;

        while(1){
                //任务3自身堆栈使用情况检查
                task_stack_chek(next_task%8,&stkused);

                //在LCD指定位置显示自己的堆栈使用量
                lcd_write_str(8,0,"C:");               
                temp = (stkused/10)%10 + 48;
                lcd_write_data( temp );               
                temp = stkused%10 + 48;
                lcd_write_data( temp );

                for( unsigned char i=0; i<delay; i++)
                    delay_time;
    }
}

/*第四个任务,在LCD显示,自身堆栈使用情况*/
void task4(void)
{
        unsigned char temp ;
        unsigned char stkused=0;        

        while(1){
                //任务4自身堆栈使用情况检查
                task_stack_chek(next_task%8,&stkused);

                //在LCD指定位置显示自己的堆栈使用量
                lcd_write_str(12,0,"D:");               
                temp = (stkused/10)%10 + 48;
                lcd_write_data( temp );               
                temp = stkused%10 + 48;
                lcd_write_data( temp );

                for( unsigned char i=0; i<delay; i++)
                    delay_time;
    }
}

/*第五个任务,在LCD显示,自身堆栈使用情况*/
void task5(void)
{
        unsigned char temp ;
        unsigned char stkused=0;

        while(1){
                //任务5自身堆栈使用情况检查
                task_stack_chek(next_task%8,&stkused);

                //在LCD指定位置显示自己的堆栈使用量
                lcd_write_str(0,1,"E:");               
                temp = (stkused/10)%10 + 48;
                lcd_write_data( temp );               
                temp = stkused%10 + 48;
                lcd_write_data( temp );

                for( unsigned char i=0; i<delay; i++)
                    delay_time;
    }
}

/*第六个任务,在LCD显示,自身堆栈使用情况*/
void task6(void)
{
        unsigned char temp ;
        unsigned char stkused=0;

        while(1){
                //任务6自身堆栈使用情况检查
                task_stack_chek(next_task%8,&stkused);

                //在LCD指定位置显示自己的堆栈使用量
                lcd_write_str(4,1,"F:");               
                temp = (stkused/10)%10 + 48;
                lcd_write_data( temp );               
                temp = stkused%10 + 48;
                lcd_write_data( temp );                 

                for( unsigned char i=0; i<delay; i++)
                    delay_time;
    }
}

/*第七个任务,在LCD显示,自身堆栈使用情况*/
void task7(void)
{
        unsigned char temp ;
        unsigned char stkused=0;

        while(1){
                //任务7自身堆栈使用情况检查
                task_stack_chek(next_task%8,&stkused);

                //在LCD指定位置显示自己的堆栈使用量
                lcd_write_str(8,1,"G:");               
                temp = (stkused/10)%10 + 48;
                lcd_write_data( temp );               
                temp = stkused%10 + 48;
                lcd_write_data( temp );

                for( unsigned char i=0; i<delay; i++)
                    delay_time;
    }
}

/*第八个任务,在LCD显示,自身堆栈使用情况*/
void task8(void)
{
        unsigned char temp ;
        unsigned char stkused=0;

        while(1){
                //任务8自身堆栈使用情况检查
                task_stack_chek(next_task%8,&stkused);

                //在LCD指定位置显示自己的堆栈使用量
                lcd_write_str(12,1,"H:");               
                temp = (stkused/10)%10 + 48;
                lcd_write_data( temp );               
                temp = stkused%10 + 48;
                lcd_write_data( temp );

                for( unsigned char i=0; i<delay; i++)
                    delay_time;
    }
}


//========================================LCD1602驱动=========================================================
这部分驱动就是普通的LCD1602驱动代码,,没什么特别的地方
//=========================================LCD1602驱动结束============================================================

评分
参与人数 1威望 +1 收起 理由
cov0xt + 1 很给力!

相关帖子

沙发
冰零分子| | 2015-12-19 11:39 | 只看该作者
谢谢楼主分享

使用特权

评论回复
板凳
阿南| | 2015-12-19 13:56 | 只看该作者
楼主能详细讲解下吗?不然别个一看一堆代码,头就

使用特权

评论回复
地板
Simon21ic| | 2015-12-19 14:09 | 只看该作者
非常简单的多任务核心,时间一到就切换,我也拿来做多任务的培训可否?

使用特权

评论回复
5
nicholasldf|  楼主 | 2015-12-19 14:29 | 只看该作者
Simon21ic 发表于 2015-12-19 14:09
非常简单的多任务核心,时间一到就切换,我也拿来做多任务的培训可否?

可以,知识随便分享:D

使用特权

评论回复
6
Simon21ic| | 2015-12-19 14:31 | 只看该作者
阿南 发表于 2015-12-19 13:56
楼主能详细讲解下吗?不然别个一看一堆代码,头就

这个应该只是纯任务切换,定时到了就切换下一个任务
没有什么调度算法,没有IPC等等,用来培训多任务还是不错的

使用特权

评论回复
7
nicholasldf|  楼主 | 2015-12-19 14:55 | 只看该作者
LineOS多任务时间片轮转调度器源码

LineOS源码.zip

3.86 KB

lineOS多任务时间片调度器源码

使用特权

评论回复
8
nicholasldf|  楼主 | 2015-12-19 14:56 | 只看该作者
Simon21ic 发表于 2015-12-19 14:31
这个应该只是纯任务切换,定时到了就切换下一个任务
没有什么调度算法,没有IPC等等,用来培训多任务还是 ...

没错,是这样的。。。。就是说明了OS如何切换任务而已。
想把它改到STM32,,加上主动放弃CPU调度API,,加上IDLE空闲任务,,挺好玩,,呵呵

使用特权

评论回复
9
huangqi412| | 2015-12-19 17:40 | 只看该作者
AVR都老的动不了了, 现在是ARM时代,挪在STM32比较方便。

使用特权

评论回复
10
lvyunhua| | 2015-12-19 20:28 | 只看该作者
多谢楼主分享!

使用特权

评论回复
11
yklstudent| | 2015-12-19 21:31 | 只看该作者
AVR都快被人收购了

使用特权

评论回复
12
cov0xt| | 2015-12-20 08:19 | 只看该作者
好东西,收藏了,慢慢研究。

使用特权

评论回复
13
undersky| | 2015-12-20 10:10 | 只看该作者
mark一下:lol

使用特权

评论回复
14
jlhgold| | 2015-12-20 10:33 | 只看该作者
我对OS一直有个疑问 串口怎么办 串口不能打印了一半就调度到另一个去吧

使用特权

评论回复
15
huangqi412| | 2015-12-20 10:44 | 只看该作者
jlhgold 发表于 2015-12-20 10:33
我对OS一直有个疑问 串口怎么办 串口不能打印了一半就调度到另一个去吧 ...

资源上锁   申请释放

使用特权

评论回复
16
hg897823521| | 2015-12-20 10:57 | 只看该作者
感谢楼主分享

使用特权

评论回复
17
Simon21ic| | 2015-12-20 14:17 | 只看该作者
这套应该还不能算操作系统,任务调度还都是编译时决定的。操作系统里的任务调度,一般是运行时决定的

使用特权

评论回复
18
nicholasldf|  楼主 | 2015-12-21 08:46 | 只看该作者
Simon21ic 发表于 2015-12-20 14:17
这套应该还不能算操作系统,任务调度还都是编译时决定的。操作系统里的任务调度,一般是运行时决定的 ...

以操作系统之大名吸引眼球,,简单演示任务切换之原理;P

使用特权

评论回复
19
nicholasldf|  楼主 | 2015-12-21 10:40 | 只看该作者
jlhgold 发表于 2015-12-20 10:33
我对OS一直有个疑问 串口怎么办 串口不能打印了一半就调度到另一个去吧 ...

会有这个情况的
比如自己实现的UartPrintf函数,,采用轮训方式发送,,可能打印了一半就被切换了。
这时可以使用发送中断的方式,,在中断逐个字节发送
也可以启用DMA,是最好的方式了,,设置缓冲区和发送长度,,硬件自动帮你发送
也可以把UartPrintf当做临界段保护起来,,也就是关闭中断,,这种方式会影响实时性,CPU效率也低

使用特权

评论回复
20
jlhgold| | 2015-12-22 21:53 | 只看该作者
nicholasldf 发表于 2015-12-21 10:40
会有这个情况的
比如自己实现的UartPrintf函数,,采用轮训方式发送,,可能打印了一半就被切换了。
这时 ...

中断方式可行 DMA就算了 不是所有MCU都有的  呵呵

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

61

主题

261

帖子

10

粉丝