|
日志内容
| 基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
电脑圈圈 发表于 2006-2-24 20:31:00 |
圈圈操作系统下载: 51_00_os.rar
*************基于51内核的圈圈操作系统****************
说明:这是圈圈在学习嵌入式操作系统后在51上写的一个简单的操作系统,提供创建任务,删除任务,任务调度,系统延时,任务挂起,发送消息,等待消息,CPU使用率统计,系统时间,串口发送、接收数据及串口资源管理等功能。由于时间匆忙,部分注释可能在调试时忘记更改,如有发现不能理解的注释,请给圈圈留言: blog.asp?name=computer00
可使用keil自带的软件仿真,也可以使用硬件来运行,使用硬件时,上位机推荐使用windows自带的超级终端,关于超级终端的使用,可参看: more.asp?name=iC921&id=8878
由于task_switch.c文件中使用了在线汇编,所以task_switch.c文件的设置必须改成下面的样子编译才可以通过。


|
| 阅读全文 | 回复(122) | 引用通告 | 编辑
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2009-4-9 15:41:00 |
在View菜单下面的Options里面,修改一下串口的背景颜色,一般使用白色的背景颜色。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
hnrian(游客)发表评论于2009-4-9 11:15:00 |
00,我 在用软件调试的时候,怎么串口 是黑的呀。。什么也看不见 ?
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2009-3-28 9:16:00 |
这个操作系统没有管理外部中断,所以你的外部中断要自己管理。就是写一个外部中断服务函数。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
caiping15(游客)发表评论于2009-3-28 3:15:00 |
请问斑竹这个操作系统如何处理外部中断?
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2008-7-6 12:33:00 |
关于堆栈深度,不光只要保存切换任务上下文,在每个任务中,还可能需要调用函数,这也是需要堆栈的,所以要保留一些堆栈留给具体的任务使用,多大就要看具体的任务调用函数多少了。 关于10ms时间,使用的是定时器2的自动重载模式,所以可以保证精度。只要不停止定时器就行了,跟中断是否关闭无关的。 高优先级的任务先运行,但是如果它的运行并不需要全部的CPU时间,所以其它任务在一个时钟节拍内还是有运行的机会的。高优先级的任务虽然先获得CPU运行时间,但是在它处理完毕后,就会进入挂起状态,就会主动放弃CPU的使用,然后就会切换到低优先级的任务。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
KOfr3U(游客)发表评论于2008-7-4 19:34:00 |
因为每次进入中断服务程序时都关断了中断,即时间定时也被关了,这样的10ms误差也挺大的是吗?而且在task4,task3,task2,task1里都有延时一个时钟节拍的情况,任务被挂起了,当task4在释放打印时延时了一个时钟节拍,进入了任务切换,执行任务3,又被延时一个时钟节拍,被挂起,这时再次进入定时中断服务程序时,任务4的延时时钟节拍到了,任务被唤醒,按照优先级别的切换,是不是任务应该切换到任务4(优先级为0)执行呢?如果是的话,下个时钟节拍来的时候,任务3也是应该被唤醒了,又被切换到任务3,这样,任务2,任务1不是不被执行了吗?请问这个跟任务延时的时钟节拍个数有关系的吧? 我全速运行你的UCOS,从串口窗能看到5个任务都被执行了,但是按照我上面的分析好象执行不到任务1,2的,请指点下,谢谢!!
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
KOfr3U(游客)发表评论于2008-7-4 17:32:00 |
圈圈,我理解了,谢谢! 当进入中断时,PC指针即马上压入堆栈,占2个字节,退出中断时也是最后一个弹出PC指针的,我这里有2个疑问想问, 1、堆栈的深度我想不用0X1B个字节,2(任务入口地址)+2(PC指针)+3(编译器自动保存字节)+8(R0~R7)+2(DPTR) = 17个字节即可呢,请指教。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2008-6-25 21:29:00 |
你要先把每个任务的堆栈结构搞清楚。在创建任务时,就已经把一个任务的入口地址保存在堆栈中了。至于第一次进中断时的PC值,被扔掉了。idle任务的堆栈结构也是一样的,堆栈中保存着idle任务的入口地址。当第一次中断发生后,其它刚创建的任务都处于挂起状态,最后查询的结果就是idle任务可以运行,中断返回时,刚好使用了栈中的idle入口地址做为返回地址。因此就回到dile任务了。自己仔细再想想吧。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
访客KOfr3U(游客)发表评论于2008-6-25 18:24:00 |
不好意思,我还是没有想明白是如何跳转到TASK_IDLE上的. temp=0x01; for(i=0;i<MAX_TASK;i++) //查找优先级最高的任务 { if(OS_Task_List&temp) //如果此任务存在 { if(OS_pcb[i].Delay!=0) //如果任务需要延时 { OS_pcb[i].Delay--; //延时时间减1 if(OS_pcb[i].Delay==0) //如果延时时间到 { OS_pcb[i].Suspend=0; //任务解挂起 } } if(OS_pcb[i].Suspend==0) //查找未挂起任务 { if(OS_pcb[i].Priority<OS_pcb[OS_Current_ID].Priority) //如果优先级高,则切换之 { OS_Current_ID=i; } } } temp<<=1; //调整temp的值,以检查下一个任务 } SP=OS_pcb[OS_Current_ID].Task_SP; //任务堆栈指针切换 这个时候的OS_Current_ID = 0,此时的堆栈指针就指向了task_idle的堆栈,请问这里的堆栈的26个字节里有包含PC的指针吗?(不好意思,之前都只知道会返回到被中断的地方,没有去想过改变PC的),如果没有保存的话,PC指针这是不是应该还是在main函数的while(1)里吗? 请圈圈再给指示下,谢谢!
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2008-6-24 11:30:00 |
是的,刚进去时的确是最后一个任务的栈,调整后就指到那里了。 但是刚创建的任务,都是处于延时状态的,所以都不会被执行。 最后就只好执行idle了。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
访客KOfr3U(游客)发表评论于2008-6-24 9:48:00 |
if(!OS_Running) //如果是第一次进入,则调整好堆栈指针位置,使其不返回主函数 { SP-=2; //将堆栈指针下移2,因为从主函数中断时压栈的两个返回地址是无用的 for(i=1;i<Num_PUSH_bytes+1;i++) //同时将刚被压栈的那几个往下平移2个字节 { ((unsigned char idata *)(SP-Num_PUSH_bytes))[i]=((unsigned char idata *)(SP-Num_PUSH_bytes+2))[i]; } OS_Running=1; //开始运行 OS_Run_Time=0; //运行总时间清0 } OS_Run_Time++; //每一个时钟节拍,运行总时间加1 OS_pcb[OS_Current_ID].Task_SP=SP; //保存当前任务堆栈指针 OS_Current_ID=0; //设置为空闲任务的ID号,优先级最低 应该就是这一段是你所说的保存了任务task_idel 我不明白怎么保存的?它不是把堆栈往前移了2个字节吗,况且这是最顶的堆栈也是属于task4的任务啊,能否花些时间再帮我解释下,非常感谢
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
访客KOfr3U(游客)发表评论于2008-6-24 8:24:00 |
谢谢圈圈,有效率!我先看看,还会碰到很多问题的,到时再来请教,谢谢!
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2008-6-24 1:16:00 |
第一次中断是做了特殊处理的,它把栈指针换了,换成了task idle的栈指针……结果返回时,就返回到idle了。从此后,程序就再也不会返回到main那里去了,没事干时就去idle。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
访客KOfr3U(游客)发表评论于2008-6-23 18:45:00 |
00有个问题想请教啊,期待回复: 1、当程序执行到OS_Start()的while(1)后,等待定时器的中断发生,然后这时的PC指针应该是指向while(1);进行任务优先级的切换后,返回不是还应该返回到while(1) 吗,它是怎么执行到task_idle函数里的啊,我还没有想过来,麻烦你解答下,谢谢!
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
duo2182.asp发表评论于2008-4-30 8:23:00 |
原来是0区对应的地址。找到了。因为没用过汇编编程,没有注意到。谢谢楼主,我继续研究00os
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2008-4-29 20:16:00 |
这个你仔细看看书吧...估计任何一本51的书都会有讲这个问题... 51有4组工作寄存器,分别放在哪了……
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
访客8cs1Vj(游客)发表评论于2008-4-29 16:24:00 |
请问,__asm PUSH 0 表示把寄存器R0入栈是吧。这个0是在那里定义为R0的。我找不到
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-12-9 0:27:00 |
还是你的编译器有问题,换个好点的试试。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
eliucheng发表评论于2007-12-8 18:12:00 |
Build target 'Target 1' compiling main.c... compiling OS_core.c... OS_CORE.C(183): warning C289: '=': converting non-pointer to pointer compiling task_switch.C... assembling task_switch.src... compiling UART.c... linking... BL51 BANKED LINKER/LOCATER V6.00 - SN: K1CNC-Y0ZNPL COPYRIGHT KEIL ELEKTRONIK GmbH 1987 - 2005 "main.obj", "OS_core.obj", "task_switch.obj", "UART.obj" TO "51_00_OS" RAMSIZE(256) CODE( 0X0000-0X1FFF ) *** FATAL ERROR L210: I/O ERROR ON INPUT FILE: EXCEPTION 0021H: PATH OR FILE NOT FOUND FILE: task_switch.obj Target not created 好象还有点区别//// 没有出现 那个 (170%)
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
lp(游客)发表评论于2007-12-5 21:46:00 |
这么快就回答了,十分感谢!我会继续努力,认真研究它的。用来学习操作系统不错! 再次感谢LZ!
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-12-5 0:15:00 |
看这个提示: RESTRICTED VERSION WITH 0800H BYTE CODE SIZE LIMIT; USED: 0E2DH BYTE (177%) 它说你这个keil是2K字节代码限制版的(因为你未注册),实际我的程序已经超过2K了(177%),所以无法编译。 你需要去注册一下,或者去网上找个完全破解版的KEIL C51来用。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
lp(游客)发表评论于2007-12-4 22:29:00 |
我下载了你的操作系统之后,用KEIL打开编译!(已经按照你图片里说的设置过了)但是出现了下面的错误: ------------------------------------------------------------------------------------------ Build target 'Target 1' compiling main.c... compiling OS_core.c... OS_CORE.C(183): warning C289: '=': converting non-pointer to pointer compiling task_switch.C... assembling task_switch.src... compiling UART.c... linking... BL51 BANKED LINKER/LOCATER V6.05 - SN: Eval Version COPYRIGHT KEIL ELEKTRONIK GmbH 1987 - 2007 "main.obj", "OS_core.obj", "task_switch.obj", "UART.obj" TO "51_00_OS" RAMSIZE(256) CODE( 0X0000-0X1FFF ) ****************************************************************************** * RESTRICTED VERSION WITH 0800H BYTE CODE SIZE LIMIT; USED: 0E2DH BYTE (177%) * ****************************************************************************** Program Size: data=217.0 xdata=0 code=4165 LINK/LOCATE RUN COMPLETE. 0 WARNING(S), 0 ERROR(S) *** FATAL ERROR L250: CODE SIZE LIMIT IN RESTRICTED VERSION EXCEEDED MODULE: C:\PROGRAM FILES\KEIL\C51\LIB\C51S.LIB (-----) LIMIT: 0800H BYTES Target not created ----------------------------------------------------------------------------------------- 是编译器的问题吗?还是有什么别的问题? 我是初学者,问题很弱智,望谅解!
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-12-1 14:10:00 |
这个没办法解决,因为keil编译器的问题,代码不可重入。 你实在要调用,可以使用临界段访问,即在一个时刻只允许一个任务访问该函数。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
xpstudio2008(游客)发表评论于2007-12-1 13:20:00 |
不好意思,上个帖子中也没表述清楚。 写成两个函数就能解决。可是写成两个函数就有点浪费ROM了,呵呵。 如何能解决这个多个任务调用同一个函数的问题呢。不仅仅是个延时函数,还可以是,比如说BCD转十进制的函数。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-11-30 21:29:00 |
应该是delay函数的问题。你分别改成两个不一样的delay函数再试试。delay函数使用的局部变量地址是固定的,你这样两个函数都调用,可能就出错了。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
xpstudio2008(游客)发表评论于2007-11-30 19:33:00 |
OO你好,我深入学习了这个OS。先想做两个任务的切换,如下。 void delay(uchar i)// reentrant { uint j; while(i--) { j=0xffff; while(j--); }; } void Task_0()reentrant { while(1) { delay(4); Task_0_Tick++; } } void Task_1()reentrant { while(1) { delay(2); Task_1_Tick++; } } 这两个任务都去调用delay(),在任务调度里我改成时间片轮换法(如下),但是这两个Tick计数的值都是一样的。看了生成的汇编也没看懂。请O_O指教啊。 //切换任务,这是我改的任务调度。 OS_CurrID++; if(OS_CurrID==2) OS_CurrID=0; SP=Task_SP[OS_CurrID];
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-11-27 0:36:00 |
平时我们写的程序,互相调用时,编译器会分析出调用关系,从而可以分配到不同的RAM。但是这里,编译器分析不出调用关系,所以就会跟其它的函数发生RAM地址冲突问题。keil的函数默认是不可重入的,也是这个原因,如果要可重入的函数,需要加上关键字reentrant声明。 加上#ifdef这些只是为了防止,有备无患,以后使用头文件时就不用顾忌那么多了,岂不是很好?这个做法基本上已经是公认的了,不管你引用了多少次。难保以后移植到其它地方可能又会多次引用?所以还是加上好。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
fox(游客)发表评论于2007-11-26 22:25:00 |
你好!首先,很感谢你的OS,它让我学到了很多。。。 在这里有一个问题,就是您的头文件里都有类似这种句子: #ifndef __OS_CORE_H__ #define __OS_CORE_H__ ... #endif 我在网上查了一下,说是为了防止头文件被重复声明。。。 但是每个C文件中只是分别声明了一次,并没有在同一个C文件中声明两次, 这样是不是就不会出现重复声明的问题了?? (我把它去掉后编译,并没有出现问题)
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
XX(游客)发表评论于2007-11-26 22:19:00 |
00,你好!首先很感谢您的解答。。。 但是我还是有点疑问。。。 既然按照你说的不同函数的局部变量都要定义为static; 原因是不是因为:KEIL的所有函数的局部变量共用某一块RAM??? 那么即使不用RTOS的话,平时编写的程序的函数不也要都定义为static,才会防止冲突嘛?
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-11-25 19:32:00 |
是的,自己建立的任务的局部变量,也都要定义为static。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
XX(游客)发表评论于2007-11-24 18:13:00 |
static unsigned long int i; //使用static申明局部变量,避免变量被分配到相同的地址 请问,如果不使用static定义局部变量的话,是不是可能出现不同任务的局部变量地址上出现冲突?? 还有,是不是自己建立的任务的局部变量,也都要定义为static啊???
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-10-3 21:30:00 |
当然有保存PC了。进入中断时,硬件会自动将PC压入堆栈。我只要换了堆栈,那么下面的返回时,自然就回到另外的任务去了。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
zhenkyss(游客)发表评论于2007-10-3 19:14:00 |
如果你没保存PC的话,怎么返回到你中断了的程序呢~ UCOS那本书举例任务切换的时候是保存PC的~~
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-10-3 15:39:00 |
我在这里并没有保存PC,只是保存工作寄存器而已,虽然已经运行到这里了,但是工作寄存器还是没变,是原来任务的工作寄存器。 任务在初始化时就将堆栈设置好了。每创建一个任务,就会初始化堆栈,如果该任务被激活,那么就会将任务的堆栈内容弹出。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
zhenkyss(游客)发表评论于2007-10-3 12:47:00 |
void Timer2ISR(void) interrupt 5 using 1 { unsigned char i; unsigned char temp; EA=0; //关中断 __asm PUSH DPL //保护当前任务寄存器,其中ACC,PSW,B寄存器编译器自动压栈 __asm PUSH DPH __asm PUSH 0 __asm PUSH 1 __asm PUSH 2 __asm PUSH 3 __asm PUSH 4 __asm PUSH 5 __asm PUSH 6 __asm PUSH 7 TF2=0; //清中断标志TF2 圈圈,任务切换这里我有疑问哦! 上面保存寄存器的值为什么不是保存程序运行到 unsigned char temp; EA=0; //关中断 这两句时的寄存器值,而是当前任务的寄存器值呢?程序都运行到这里了,PC值难道不变,还是指向任务发生中断前的那一句代码? 还有哦!如果高优先级任务之前没有运行过,那任务栈里面就没数据了,那怎么能通过恢复寄存器的值做任务调度呢?
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-10-1 23:31:00 |
这是我自己写的程序,我当然知道一个时钟节拍是10ms了,我的定时器设置的就是10ms中断一次。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
zhenkyss(游客)发表评论于2007-9-30 15:47:00 |
圈圈 系统时钟不准哦. 还有为什么你知道一个时钟节拍是10MS啊(太长了吧!!)
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
foxlw(游客)发表评论于2007-8-27 17:12:00 |
今年的大赛清单里面有小车,还有光电传感器和角度传感器 不知道您有没有做过这方面的设计 可以指点一下吗?
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
computer00发表评论于2007-8-26 18:28:00 |
1. 尽量先使用内部RAM,在内部RAM不够的情况下,才去使用XRAM,因为访问XRAM的速度慢。 2. 恰恰相反,定义为static类型,并不能解决重入问题。可重入函数要求变量要分配在栈空间中,这样才能在再一次调用时不会产生问题。加上static之后,变量的地址就固定了,在生命期上,它跟全局变量一样了,只是在作用域上,是局部变量而已。这样的函数是不能重入的,所以,在这个操作系统中,不能同时创建同一个任务,否则就可能会出现运行结果不正确。因为两个函数的变量地址相同。在keil中,加reentrant就可以声明为可重入函数,可以用来给中断调用,递归调用等。然而,keil并不将这些函数的变量放在系统栈中,而是自己管理了一个软件栈。具体的细节,也许可以在keil的帮助手册里找到。
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
| Re:基于51的圈圈操作系统(51_00_OS)出来拉…… |
|
foxlw(游客)发表评论于2007-8-26 17:01:00 |
再次感谢您的解答。。。 1。是不是我把变量都定义为XDATA,这样既利用了我那个STC单片机的外部RAM,又可以节省更多的内部RAM给任务作堆栈呢??是不是这样利用比较合理??? 2。就是关于把局部变量都定义为static类型,这样就可以避免函数的重入问题了。。。 我看到网上有许多解决函数重入的问题,有的说是在函数后面加上reentrant 但是我还是觉得您的这个方法最简单,使用。。能给我稍微解释一下原理吗? 还有,这样做会带来缺点吗??
|
| 个人主页 | 引用 | 返回 | 删除 | 回复
|
|
用户信息
载入中...
|