??xml version="1.0" encoding="utf-8" standalone="yes"?>一本色道久久综合,色综合久久最新中文字幕,久久久久亚洲AV无码专区网站 http://www.shnenglu.com/feixuwu/archive/2011/05/14/146395.htmlfeixuwu feixuwu Sat, 14 May 2011 13:16:00 GMT http://www.shnenglu.com/feixuwu/archive/2011/05/14/146395.html http://www.shnenglu.com/feixuwu/comments/146395.html http://www.shnenglu.com/feixuwu/archive/2011/05/14/146395.html#Feedback 0 http://www.shnenglu.com/feixuwu/comments/commentRss/146395.html http://www.shnenglu.com/feixuwu/services/trackbacks/146395.html 问题 最q游戏开始技术封了(jin)Q不q刚刚上U?个小ӞServer挂?jin),挂在框架代码里,一个不可能挂的地方?br />从CallStack看,是在获取数据时发送请求包的时候挂的,׃框架部分是其他部门的同事开发的Q所以查问题的时候就拉上他们?jin)?br />大家折腾?天,没有实质性的q展Q服务器q是基本上每3个小时宕Zơ。由于上层逻辑大部分都在我那,所以压力比较大Q宕机的直接原因是hashtable的一个桶的指针异常, q个hashtable是框架代码的一个内部成员,按道理我们是无从破坏的,只有可能是多U程环境下P代器损坏D的?br />但是框架代码在这个地方确实无懈可击,所以真正的原因应该q是上层代码破坏?jin)堆内存Q很可能是一个memcpy界D的。这毕竟是个猜想Q如何找到证据呢Q这是个问题?br />把所有代码里的memcpy览?jin)一遍,没有发现明显问题?br />
猜测 一般游戏中比较Ҏ(gu)出现但是不好查的问题很多时候都是脚本(luaQ导致的Q我们的脚本部分是一个同事几q前写的Q在几个产品中都使用q,按道理没q么脆弱Q不q老大q是和最初开发这个模块的部门沟通了(jin)下, q真发现问题?jin),赶紧拿?jin)新的版本更新上去。经q一天的观察Q服务器没有宕机?jin),OKQ问题碰巧解决了(jin),背了(jin)q么久的黑锅Q终于放下来?jin)?br />
PageHeap 假如没有y解决?jin)这个问题,正常的思\该如何解册个问题呢Q这个时候我怀念windows?jin),在windows下有PageHeap来解册cd界的问题。基本思\是每次分配内存的时候,都将内存的结放在页的边~,紧接着q块内存分配一块不能写的内存,q样Q一旦写界Q就?x)写异常Q导致宕机。linux下没有现成的工具Q但是linux提供?jin)mmap功能Q我们可以自己实现这样一个功能,当然Q这一切都不用自己动手?jin),tcmalloc已经包含?br />q个功能?jin),不过在文里基本没有介绍Q我也是在阅读tcmalloc代码时看到的Q这个功能默认是关闭的,打开q个开关需要改写代码:(x) q个代码在debugallocation.cc里:(x) DEFINE_bool(malloc_page_fence, EnvToBool("TCMALLOC_PAGE_FENCE", false), "Enables putting of memory allocations at page boundaries " "with a guard page following the allocation (to catch buffer " "overruns right when they happen)."); 把falseҎ(gu)true可以了(jin)?br />惌在项目里加入PageHeap功能Q只需要链接的时候加?-ltcmalloc_debug卛_。把它加入项目中Q试着q行下,直接挂了(jin)Q?br />仔细一看,原来是项目中很多成员变量没有初始化导致的Qtcmalloc_debug?x)自动将new 和malloc出来的内存初始化为指定|q样Q一旦变量没有初始化Q很Ҏ(gu)暴露了(jin)?br />修改完这个问题后Q编译,再运行,q是挂,q个是mprotect的时候挂的,错误是内存不够,q怎么可能呢,其实是达C(jin)资源限制?jin)?br />echo 128000 > /proc/sys/vm/max_map_count 把map数量限制加大,再运行,OK?jin)?br /> 但是游戏Server启动后,发现一个问题,CPU长期处于100%Q导致登陆一个玩安很困难,gdb中断后,info threadQ发现大部分的操作都在mmap和mprotect,最开?br />怀疑我的linux版本有问题,Dq?个AP慢,写了(jin)试E序试了(jin)下,发现其实API不慢Q估计是频繁调用D的?br />所以得换种思\优化下才可以Q其实大部分情况下,我们free的时候,无需页面munmap掉,可以先cacheq来Q下ơ分配的时候,如果有,直接拿来用就可以?jin)?br />最单的cache法是定义一个void* s_pageCache[50000]数组Q页面数相同的内存组成一个链表,挂在一个数l项下,q个很像STL的小内存处理Q我们可以将mmap出来的内存的 前面几个字节(一个指针大?用于索引下一个freePage。当然这个过E需要加锁,不能用pthread的锁Q因Z们会(x)调用malloc{内存分配函敎ͼ(j)Q必ȝspinlockQ从linux源码里直接抄一个过来即可?br />static void* s_pagePool[MAX_PAGE_ALLOC]={0}; malloc的时候,先从pagePool里面获取: // 先从pagePool?br /> void* pFreePage = NULL; spin_lock(&s_pageHeapLock); assert(nPageNum < MAX_PAGE_ALLOC); if(s_pagePool[nPageNum]) { pFreePage = s_pagePool[nPageNum]; void* pNextFreePage = *((void**)pFreePage); s_pagePool[nPageNum] = pNextFreePage; } spin_unlock(&s_pageHeapLock); free内存的时候,直接攑ֈpagePoll? spin_lock(&s_pageHeapLock); assert(nPageNum < MAX_PAGE_ALLOC); void* pNextFree = s_pagePool[nPageNum]; *(void**)pAddress = pNextFree; s_pagePool[nPageNum] = pAddress; spin_unlock(&s_pageHeapLock); ~译、运?OK?jin),CPUq速降下来?jin),I的时候不?%,而且也能辑ֈ(g)写溢出的问题?br /> ]]> core和CallStack http://www.shnenglu.com/feixuwu/archive/2011/04/10/143871.htmlfeixuwu feixuwu Sun, 10 Apr 2011 06:47:00 GMT http://www.shnenglu.com/feixuwu/archive/2011/04/10/143871.html http://www.shnenglu.com/feixuwu/comments/143871.html http://www.shnenglu.com/feixuwu/archive/2011/04/10/143871.html#Feedback 0 http://www.shnenglu.com/feixuwu/comments/commentRss/143871.html http://www.shnenglu.com/feixuwu/services/trackbacks/143871.html 最q项目开始集中测试了(jin)Q服务器E序l常crashQ由于服务器一般情况下都是关闭?jin)core的,所以好几次都只能通过杂ؕ的日志来定位问题?br>当然Q我们可以通过ulimit来打开core开养I不过q可能带来新的问题:(x)我们的服务器E序每个core文g大概?G多,试期间如果频繁crash,没有注意?qing)时清理Q一不小?j)就会(x)把盘写满Q?br>而且core文g毕竟是和q程E序相关的,有时候找相应版本也是个麻?ch)事?br> 能否在程序crash的时候,callStack以及(qing)参数和局部变量都记录到日志里Q?br>q个技术其实在游戏客户端已l用?jin)很多年了(jin),一般游戏客L(fng)crash后,都会(x)弹出一个是否发送错误的选择框,其实是发送的CallStack的日志和MiniDUmp文g?br>要想记录CallStack必然涉?qing)到Stack的遍历,linux下的Stack遍历使用很简单,单的backtrace可以搞定,man backtrace有现成的例子, q比windows下复杂的头疼的StackWalk好用的多?br> 解决?jin)Stack遍历问题后,q剩下一个问题:(x)如何在程序crash的时候得到通知执行我们自己的dump代码Q?br>在Windwos下有SEH异常来实现这个功能,而linux下可以通过使用信号在进Ecrash的时候执行自q处理代码?br> 好了(jin)Q开始写个简单代码测试下: 首先讄几个主要crash信号的处理函?br>signal(SIGSEGV, &DumpHelper::OnCrash); signal(SIGABRT, &DumpHelper::OnCrash); signal(SIGFPE, &DumpHelper::OnCrash); 在OnCrash里我们用前面提到的backtracepd函数Q来记录堆栈: void* szStackFrame[100]; int nFrameCount = backtrace(szStackFrame, 100); char** strFrameInfo = backtrace_symbols(szStackFrame, nFrameCount); char szDumpFileName[1024] = {0}; snprintf(szDumpFileName, sizeof(szDumpFileName), "dump_%u.log", (unsigned int)time(NULL) ); FILE* pFile = fopen(szDumpFileName, "wb"); if(!pFile) return; for(int i = 0; i < nFrameCount; i++) { fprintf(pFile, "%s\n", strFrameInfo[i]); } fclose(pFile); free(strFrameInfo); 接着Q设|几个嵌套调用的函数Q?br>void fun() { //assert(0); int* p = NULL; *p =3; }
void fun1() { fun(); }
void fun2() { fun1(); }
void fun3() { fun2(); } 最后,我们在main函数里执行fun3,注意~译的时候带?rdynamic 选项?span class=Apple-style-span style="WORD-SPACING: 0px; FONT: medium Simsun; TEXT-TRANSFORM: none; COLOR: rgb(0,0,0); TEXT-INDENT: 0px; WHITE-SPACE: normal; LETTER-SPACING: normal; BORDER-COLLAPSE: separate; orphans: 2; widows: 2; webkit-border-horizontal-spacing: 0px; webkit-border-vertical-spacing: 0px; webkit-text-decorations-in-effect: none; webkit-text-size-adjust: auto; webkit-text-stroke-width: 0px">
q行下,果然可以打印基本的堆栈,不过马上Q发C(jin)新的问题Q这个堆栈信息也太简陋了(jin)Q只有调用函数的名字Q其余的参数、局部变量完全没有, q个和gdb能看到的callStack差距也太大了(jin)?br>解决q个问题最单的办法是用gdb来打印堆?在这里,gdb和其他程序有区别Q如果你试图通过 echo "bt"|gdb -p XXX>a.txt来获得堆栈,那将?x)非常失望?br>Ҏ(gu)不v作用Qgoogle?jin)下Q基本没什么解军_法?br>不过gdb 可以从文件读入指令,例如 gdb XXX<cmddataQ这l了(jin)我们Z(x)Q?br>system("echo \"bt full|gcore\">testcmd"); char dbx[160]={0}; sprintf(dbx, "gdb -p %d ./main<testcmd >gdbdump_%d.log", getpid(), getpid() ); system(dbx); 试q行Q发现可以打印详l的堆栈Q不q,要求机器上有gdb. 上面的命令还dump?jin)一个core文gQ不q这个core文g的堆栈信息是错误的,我不知道Z么。。。?br> 多线E环境下使用上述办法Q只能输Z个线E的堆栈Q需要先获取U程数目Q然后逐个U程打印堆栈?br> 最后,Z(jin)避免影响正常的coredump,要在OnCrash的处理函数里信L(fng)处理函数讄为默认?br>如果我一定要有core呢,setrlimit吧,Lcore限制卛_?/span>
]]>GCC目~译速度优化 http://www.shnenglu.com/feixuwu/archive/2011/03/19/142210.htmlfeixuwu feixuwu Sat, 19 Mar 2011 08:39:00 GMT http://www.shnenglu.com/feixuwu/archive/2011/03/19/142210.html http://www.shnenglu.com/feixuwu/comments/142210.html http://www.shnenglu.com/feixuwu/archive/2011/03/19/142210.html#Feedback 5 http://www.shnenglu.com/feixuwu/comments/commentRss/142210.html http://www.shnenglu.com/feixuwu/services/trackbacks/142210.html 执行文gQ基本不考虑~译成动态库Q所有代码的头文件依赖也是一团糟Q随着目的增大,~译速度来慢Q到后来~译一个项?q程同时~译都需?0来分钟?br> 其实分析下可以发玎ͼ主要的编译速度损耗在头文件上Q尤其是模板相关的头文g。VC有一个预~译头文件技术,常用的公共头文件放在一P预先~译成pch文gQ这?br>可以加快~译速度。gcc到底有没有类似技术呢Q打开gcc的手册搜索了(jin)precompiledQ发现还真有相关介绍Q用方法也很简单?br> 主要是以下步骤:(x) 1、在目下徏立一?stdafx.h的文Ӟ包含?jin)大部分公共头文件。在每个cpp最开始都#include "stdafx.h"。cpp文g包含?jin)这个预~译头文件后Q就可以原来和 stdafx .h 里头文g重复的内容删除了(jin)Q尤其是模板相关的头文gQ另外,非PCH的头文g里尽量少包含其他头文件?nbsp; 2、修改makefile文g, 加入OBJ?gch的依?用一个简单的目做示例,一看就明白
TARGET=TimerTest PCH=stdafx.h.gch PCH_H=stdafx.h OBJ=stdafx.o TimerManager.o TimerTest.o %.o:%.cpp g++ -Wall -c -g $^ -o $@ $(TARGET):$(OBJ) g++ -g $^ -o $@ pch.d:stdafx.cpp g++ -g -MM stdafx.cpp |sed 's/stdafx.o/stdafx.h.gch/'>$@ -include pch.d $(OBJ):$(PCH) $(PCH): g++ $(PCH_H) clean: rm -f $(OBJ) $(PCH)
完成以上内容后,make clean,再重新编译,初步估计只需?分钟Q! 整整优化?-5倍?br>
]]> 定时器的实现 http://www.shnenglu.com/feixuwu/archive/2011/03/13/141744.htmlfeixuwu feixuwu Sun, 13 Mar 2011 14:06:00 GMT http://www.shnenglu.com/feixuwu/archive/2011/03/13/141744.html http://www.shnenglu.com/feixuwu/comments/141744.html http://www.shnenglu.com/feixuwu/archive/2011/03/13/141744.html#Feedback 0 http://www.shnenglu.com/feixuwu/comments/commentRss/141744.html http://www.shnenglu.com/feixuwu/services/trackbacks/141744.html 没有定时器,所有需要定时的dQ都只能dcMOnUpdate的函敎ͼ在主循环的时候执行。定旉求少的时候,看不出明昄问题Q但是一旦这U需求多?jin),其是很多内部对象有定时需求的时候, q个问题比较明显了(jin)Q写好了(jin)OnUpdate后,q要建立一条从d@环MainLoop到自wOnUpdate的调用链?br> 事g其实是一个广播和订阅的关p,Delegate是实现q样一套机制的利器Q目前Delegate的实C要有2U,一U是CodeProject上的一个FastDelegate实现Q另外一个比较典型的实现是boost?br>实现?jin),无论采取哪种实现?gu)Q实现难度都不算太大?br> Server当前框架对定时器无Q何支持,只有一个DoMainLoop的函数可以派生来q行自己的定旉辑?br> 我原来都是用的ACE装的组Ӟ用了(jin)一D|间也没发现明N题,不过ACE的定时器不太适合在这个新目用,主要原因有如下几点:(x) 1、ACE库太大了(jin)Q不想仅仅ؓ(f)?jin)定时器引入一个这么庞大的库?br> 2、ACE的定时器需要额外启动一个定时器U程Q定时Q务是在定时器U程跑的Q而我们的目逻辑其实是在单个U程q行的,如果直接采用ACE定时器,?x)给逻辑带来额外的复杂度。由于整个逻辑U程的框架是公共模块Q手头也没有代码Q所以将定时器线E的d发送到主逻辑U程q行也是不可行的?br> 3、ACE的定时器有很多种QTIMER_QUEUE、TIMER_WHELL、TIMER_HEAP{,个h感觉q些定时器的插入、取消操作都比较耗时Q加以改装放CU程run的带价将?x)很大?br> 其实linux内核有一个比较高性能的定时器Q代码在kernel/Timer.c里, 2.6内核的定时器代码更是z?br>linux的定时Q务都是以jiffie
为单位的Qlinux所有定时Q务分?个阶梯, struct tvec { struct list_head vec[TVN_SIZE]; }; struct tvec_root { struct list_head vec[TVR_SIZE]; }; struct tvec_base { spinlock_t lock; struct timer_list *running_timer; unsigned long timer_jiffies; struct tvec_root tv1; struct tvec tv2; struct tvec tv3; struct tvec tv4; struct tvec tv5; } ____cacheline_aligned;
对一个新的定时Q务,处理Ҏ(gu)如下: static void internal_add_timer(struct tvec_base *base, struct timer_list *timer) { unsigned long expires = timer->expires; unsigned long idx = expires - base->timer_jiffies; struct list_head *vec; if (idx < TVR_SIZE) { int i = expires & TVR_MASK; vec = base->tv1.vec + i; } else if (idx < 1 << (TVR_BITS + TVN_BITS)) { int i = (expires >> TVR_BITS) & TVN_MASK; vec = base->tv2.vec + i; } else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) { int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK; vec = base->tv3.vec + i; } else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) { int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK; vec = base->tv4.vec + i; } else if ((signed long) idx < 0) { /* * Can happen if you add a timer with expires == jiffies, * or you set a timer to go off in the past */ vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK); } else { int i; /* If the timeout is larger than 0xffffffff on 64-bit * architectures then we use the maximum timeout: */ if (idx > 0xffffffffUL) { idx = 0xffffffffUL; expires = idx + base->timer_jiffies; } i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK; vec = base->tv5.vec + i; } /* * Timers are FIFO: */ list_add_tail(&timer->entry, vec); } 从上可以看到Linux对定时器的处理:(x)对即在TVR_SIZE
个jiffies内到辄定时dQ将它挂到第一ltv1
下,具体是挂到expires & TVR_MASK
对应的列表上厅R?br>同一个jiffies到达的定时器是挂在同一个链表的?br>同理Q挂到第二个l的?到期旉于 1 << (TVR_BITS + TVN_BITS) jiffies的?br>挂到W三个组的是 到期旉于1 << (TVR_BITS + 2 * TVN_BITS)
jiffies的?br>挂到W四个组的是 到期旉于 1 << (TVR_BITS + 3 * TVN_BITS)
jiffies的?br>过1 << (TVR_BITS + 3 * TVN_BITS) 的挂到第五组?br>q样Q所有到期的d都会(x)在第一l。Q何时刻都可以直接通过当前jiffies&TVR_SIZE
来找到需要运行的定时器Q务列表,定时器的插入效率是O(1)?br> 下面是定时器的运行代码:(x) static int cascade(struct tvec_base *base, struct tvec *tv, int index) { /* cascade all the timers from tv up one level */ struct timer_list *timer, *tmp; struct list_head tv_list; list_replace_init(tv->vec + index, &tv_list); /* * We are removing _all_ timers from the list, so we * don't have to detach them individually. */ list_for_each_entry_safe(timer, tmp, &tv_list, entry) { BUG_ON(tbase_get_base(timer->base) != base); internal_add_timer(base, timer); } return index; } #define INDEX(N) ((base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK) /** * __run_timers - run all expired timers (if any) on this CPU. * @base: the timer vector to be processed. * * This function cascades all vectors and executes all expired timer * vectors. */ static inline void __run_timers(struct tvec_base *base) { struct timer_list *timer; spin_lock_irq(&base->lock); while (time_after_eq(jiffies, base->timer_jiffies)) { struct list_head work_list; struct list_head *head = &work_list; int index = base->timer_jiffies & TVR_MASK; /* * Cascade timers: */ if (!index && (!cascade(base, &base->tv2, INDEX(0))) && (!cascade(base, &base->tv3, INDEX(1))) && !cascade(base, &base->tv4, INDEX(2))) cascade(base, &base->tv5, INDEX(3)); ++base->timer_jiffies; list_replace_init(base->tv1.vec + index, &work_list); while (!list_empty(head)) { void (*fn)(unsigned long); unsigned long data; timer = list_first_entry(head, struct timer_list,entry); fn = timer->function; data = timer->data; timer_stats_account_timer(timer); set_running_timer(base, timer); detach_timer(timer, 1); spin_unlock_irq(&base->lock); { int preempt_count = preempt_count(); fn(data); if (preempt_count != preempt_count()) { printk(KERN_ERR "huh, entered %p " "with preempt_count %08x, exited" " with %08x?\n", fn, preempt_count, preempt_count()); BUG(); } } spin_lock_irq(&base->lock); } } set_running_timer(base, NULL); spin_unlock_irq(&base->lock); } 当第一l运行完一轮后Q需要将tv2的一l新的定时Q务加到第一l。这好比时钟的指针Q秒针运行一圈后Q分针步q一|后箋的调整都是类伹{?
cascade
是负责下一l的定时dd到前面的d阶梯。只有当W一轮的定时d全部q行完毕后,才会(x)需要从W二轮调入新的Q务,只有W二U别的Q务都调入完毕后,才需要从W三轮的定时d调入新的dQ?br> if (!index && (!cascade(base, &base->tv2, INDEX(0))) && (!cascade(base, &base->tv3, INDEX(1))) && !cascade(base, &base->tv4, INDEX(2))) cascade(base, &base->tv5, INDEX(3));
q就是负责调整的代码Q相当的z?br>参照上述代码实现一个定时器后,加入4000个定时Q务:(x) for(int i = 1; i < 4000; i++) { g_TimerHandle[i] = g_timerManager.setTimer(&tmpSink1, i, i*10, "ss"); }
?0毫秒?000*10毫秒,q行后,试下性能Q?br>函数?nbsp; 执行ơ数 最时?nbsp; q_旉 最大时?br>TimerManager::runTimer 2170566 10 10 3046 可以看到Q除?jin)个别时间是因?f)U程切换D数据比较大外,q_每次q行runTimer的时间是10微秒?br>q个旉q包括每个定时器的执行消耗,效率q是不错的?br> ]]> Ogre初体?/title> http://www.shnenglu.com/feixuwu/archive/2010/09/25/127669.htmlfeixuwu feixuwu Sat, 25 Sep 2010 13:44:00 GMT http://www.shnenglu.com/feixuwu/archive/2010/09/25/127669.html http://www.shnenglu.com/feixuwu/comments/127669.html http://www.shnenglu.com/feixuwu/archive/2010/09/25/127669.html#Feedback 1 http://www.shnenglu.com/feixuwu/comments/commentRss/127669.html http://www.shnenglu.com/feixuwu/services/trackbacks/127669.html 做完?jin)这些之后,本想为我的PSP山寨一个新剑侠情缘。不料后来连l加?jin)好几天班,加?jin)几天班之后,Z懒了(jin)Q山寨游戏的事情也就无疾而终?jin)?br>前面写过几篇逆向工程的文章,前几天翻出来看了(jin)下,感觉像是另一个h写的天书Q我自己看自q文章且如此Q别人就更不用说?jin),其实对大部分言Q关?j)的只是逆向的成果。对新剑侠情~的资源和相x染感兴趣的朋友可以单独Email我? 开始阅读Ogre代码正是在这百无聊赖的状态下开始的QOgre推出来很多年?jin),貌?5q就听说朋友说vq这个项目,不过我一向是专注服务端开发,对客L(fng)开发经验不是很多,?D领域完全是的新手了(jin)Q所以一直也没仔l研I。这几天拿v原来下蝲的一个版本,单读?jin)下代码?br>Ogre的结构还是很清晰的,和手册上说的一P主要是那几个对象,Demo大部分也很简单,代码量不多,看v来很振奋人心(j)?br>但是Ҏ(gu)q样的新手来_(d)首先想了(jin)解的当然是渲染流E?Ogre的渲染流E确实会(x)?D新手不适应Q它是从RenderTarget开始的Q一个RenderTarget可以有几个ViewPortQ每个ViewPort都有一个独立的摄像机,q可以实现同屏幕多个渲染?br>通过ViewPort对象的update调用 mCamera->_renderScene(this, mShowOverlays);
来执行场景渲染,而场景渲染里Q最重要的要_findVisibleObjects?jin)?br>q个函数可见的物体d到渲染队列里Q这个函数非常的l,里面q用C(jin)Vistor,_不好Ҏ(gu)被绕晕,好在我挺住了(jin)Q熬q来?jin)?br>熟?zhn)了(jin)大致的渲染程后,我觉得该写点东西来实战?jin)?br>3D教程的开始一般会(x)教大家画三角形,所以我也想用OgreM三角形玩玩, 一开始,我也想从像那些Demo一样从ExampleApplicationl承Q不q我发现q样启动太慢?jin),而且我不需要加载那么多的材质, 所以自己手动Configure?jin),代码如? Ogre::LogManager* pLogManager = new Ogre::LogManager; Ogre::Log* pLog = pLogManager->createLog("ogreLearn1.log"); pLog->setDebugOutputEnabled(true); Ogre::Root* pRootObject = new Ogre::Root; pRootObject->loadPlugin("RenderSystem_Direct3D9_d.dll"); pRootObject->loadPlugin("Plugin_OctreeSceneManager_d.dll"); Ogre::RenderSystem* pRenderSystem = pRootObject->getRenderSystemByName("Direct3D9 Rendering Subsystem"); pRenderSystem->setConfigOption("Full Screen", "False"); pRootObject->setRenderSystem(pRenderSystem); Ogre::RenderWindow* pRenderWindow = pRootObject->initialise(true); ~译试?jin)下Q可以正常运行,不过发现屏幕是花的,我还没有创徏场景呢,l箋d摄像机和ViewPort以及(qing)场景 // 创徏场景和摄像机以及(qing)ViewPort Ogre::SceneManager* pSceneManager = pRootObject->createSceneManager(Ogre::ST_GENERIC, "OgreLearn1"); Ogre::Camera* pCamera = pSceneManager->createCamera("MainCamara"); pCamera->setPosition(0.0, 0.0, -20.0); pCamera->lookAt(0, 0, 0); pCamera->setNearClipDistance(2); Ogre::Viewport* pViewPort = pRenderWindow->addViewport(pCamera); pViewPort->setBackgroundColour(Ogre::ColourValue(0, 0, 0, 1.0f) ); pCamera->setAspectRatio(pViewPort->getActualWidth()/pViewPort->getActualHeight() );
最后加上pRootObject->startRendering();
~译q行Q一切正常,屏幕颜色也变成了(jin)惌的黑Ԍ恩,下一步该d三角形了(jin)Q我不太喜欢用OgreManualObjectQ一堆的J琐操作。这里用自定义的Mesh来绘?角Ş?br>pSceneManager->setAmbientLight(Ogre::ColourValue(0.2, 0.2, 0.2) ); Ogre::MeshPtr pMeshData = Ogre::MeshManager::getSingleton().createManual("Learn", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); Ogre::SubMesh* pSubMesh = pMeshData->createSubMesh(); pSubMesh->useSharedVertices = false; pSubMesh->vertexData = new Ogre::VertexData; pSubMesh->vertexData->vertexStart = 0; pSubMesh->vertexData->vertexCount = 3;
先设|了(jin)环境?其实没啥用,我后面会(x)止)Q然后创Z(jin)一个自定义的Mesh, 紧接着的是创徏一个SubMeshQ要知道Ogre中最的|格是SubMesh,创徏好SubMesh后,要填充网格结构了(jin)Q?br>创徏?jin)一个VertexData,讄点数目?Q也是一个三角ŞQ,下面该定义顶Ҏ(gu)式了(jin)Q?br>Ogre::VertexDeclaration* pDecle = pSubMesh->vertexData->vertexDeclaration; size_t sOffset = 0; pDecle->addElement(0, sOffset, Ogre::VET_FLOAT3, Ogre::VES_POSITION); sOffset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3); pDecle->addElement(0, sOffset, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); sOffset += Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR);
上述代码定义?jin)顶?gu)式,只有基本的坐标和颜色?br>下一步将是申h存,填充点l构?br>Ogre::HardwareVertexBufferSharedPtr vBuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(sOffset, 3, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY); float* pReal = static_cast<float*>(vBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD)); Ogre::RGBA* pColor = NULL; *pReal++ = -2.0f; *pReal++ = 0.0f; *pReal++ = 0.0f; pColor = (Ogre::RGBA*)pReal; pRenderSystem->convertColourValue(Ogre::ColourValue(1.0f, 0.0, 0, 0.0f), pColor); pReal = (float*)(pColor+1); *pReal++ = 0.0f; *pReal++ = 2.0f; *pReal++ = 0.0f; pColor = (Ogre::RGBA*)pReal; pRenderSystem->convertColourValue(Ogre::ColourValue(0.0f, 0, 1.0, 1.0f), pColor); pReal = (float*)(pColor+1); *pReal++ = 2.0f; *pReal++ = 0.0f; *pReal++ = 0.0f; pColor = (Ogre::RGBA*)pReal; pRenderSystem->convertColourValue(Ogre::ColourValue(1.0f, 0, 0, 1.0f), pColor); pReal = (float*)(pColor+1); vBuf->unlock(); pSubMesh->vertexData->vertexBufferBinding->setBinding(0, vBuf); pMeshData->load(); pMeshData->_setBounds(Ogre::AxisAlignedBox(-2, 0, -1, 2, 2, 1) ); 填充点后,讄|格包围盒,q样一个自定义的网格就创徏好了(jin)Q接下来要创Z个用该|格的实体了(jin) Ogre::Entity* pEntity = pSceneManager->createEntity("TestEntity", "Learn"); pEntity->setMaterialName("BaseWhiteNoLighting"); pSceneManager->getRootSceneNode()->createChildSceneNode()->attachObject(pEntity); pEntity->getParentNode()->setPosition(3, 0, 0); pEntity->getParentNode()->rotate(Ogre::Quaternion(1.0f, 1.0f, 0, 1.0f) );
好了(jin)Q这样实体也创徏好了(jin)Q接下来执行渲染吧:(x) pRootObject->startRendering();
遇到的问?/h2>
上述代码是运行正常的Q但是一开始,我执行的l果是看不到M东西Q跟t了(jin)下,发现实体每次都被摄像剪了(jin)Q才发觉自定义Mesh要自p|包围盒子, 讄可包围盒子?br> 讄?jin)包围盒后,数据已经q入?jin)D3D的渲染管道,但是q是没看C角ŞQ仔l观察,原来摄像机对着的是三角形的背面。。?br>调整摄像机后Q终于能看到一个三角Ş?jin),不过是白色的。。?br>从这个症状看Q应该是没有关闭光照D的,但是我明明主动调用RenderSystem关闭光照?jin)啊Q仔l跟t了(jin)下原来是材质在捣乱, 默认的材质是开启了(jin)光照的,所以在渲染前的SceneManager::_setPass
的时候,开启了(jin)光照?br>q好办,d讄?jin)关闭光照的材?BaseWhiteNoLighting" 后,l于看到?jin)彩色三角Ş了(jin)?br> ]]> select ?epoll http://www.shnenglu.com/feixuwu/archive/2010/07/10/119995.htmlfeixuwu feixuwu Sat, 10 Jul 2010 10:40:00 GMT http://www.shnenglu.com/feixuwu/archive/2010/07/10/119995.html http://www.shnenglu.com/feixuwu/comments/119995.html http://www.shnenglu.com/feixuwu/archive/2010/07/10/119995.html#Feedback 1 http://www.shnenglu.com/feixuwu/comments/commentRss/119995.html http://www.shnenglu.com/feixuwu/services/trackbacks/119995.html 今天闲来无事Q翻看了(jin)下内总码,l合内核代码和大家分享下我的观点?br>
一、连接数
我本Z曄在项目中用过select和epoll,对于selectQ感触最q是linux下select最大数目限?windows 下似乎没有限?Q每个进E的select最多能处理FD_SETSIZE个FD(文g句柄)Q?br>如果要处理超q?024个句柄,只能采用多进E了(jin)?br>常见的用slect的多q程模型是这L(fng)Q?一个进E专门acceptQ成功后fd通过unix socket传递给子进E处理,父进E可以根据子q程负蝲分派。曾l用q?个父q程+4个子q程 承蝲?jin)超q?000个的负蝲?br>q种模型在我们当时的业务q行的非常好。epoll在连接数斚w没有限制Q当然可能需要用戯用API重现讄q程的资源限制?br>
二、IO差别
1、select的实?/h2>
q段可以l合linux内核代码描述?jin),我用的?.6.28Q其?.6的代码应该差不多吧?br>先看看select: selectpȝ调用的代码在fs/Select.c下, asmlinkage long sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp) { struct timespec end_time, *to = NULL; struct timeval tv; int ret; if (tvp) { if (copy_from_user(&tv, tvp, sizeof(tv))) return -EFAULT; to = &end_time; if (poll_select_set_timeout(to, tv.tv_sec + (tv.tv_usec / USEC_PER_SEC), (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC)) return -EINVAL; } ret = core_sys_select(n, inp, outp, exp, to); ret = poll_select_copy_remaining(&end_time, tvp, 1, ret); return ret; }
前面是从用户控g拯各个fd_set到内核空_(d)接下来的具体工作在core_sys_select中, core_sys_select->do_select,真正的核?j)内容在do_select里:(x) int do_select(int n, fd_set_bits *fds, struct timespec *end_time) { ktime_t expire, *to = NULL; struct poll_wqueues table; poll_table *wait; int retval, i, timed_out = 0; unsigned long slack = 0; rcu_read_lock(); retval = max_select_fd(n, fds); rcu_read_unlock(); if (retval < 0) return retval; n = retval; poll_initwait(&table); wait = &table.pt; if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { wait = NULL; timed_out = 1; } if (end_time && !timed_out) slack = estimate_accuracy(end_time); retval = 0; for (;;) { unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp; set_current_state(TASK_INTERRUPTIBLE); inp = fds->in; outp = fds->out; exp = fds->ex; rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex; for (i = 0; i < n; ++rinp, ++routp, ++rexp) { unsigned long in, out, ex, all_bits, bit = 1, mask, j; unsigned long res_in = 0, res_out = 0, res_ex = 0; const struct file_operations *f_op = NULL; struct file *file = NULL; in = *inp++; out = *outp++; ex = *exp++; all_bits = in | out | ex; if (all_bits == 0) { i += __NFDBITS; continue; } for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) { int fput_needed; if (i >= n) break; if (!(bit & all_bits)) continue; file = fget_light(i, &fput_needed); if (file) { f_op = file->f_op; mask = DEFAULT_POLLMASK; if (f_op && f_op->poll) mask = (*f_op->poll)(file, retval ? NULL : wait); fput_light(file, fput_needed); if ((mask & POLLIN_SET) && (in & bit)) { res_in |= bit; retval++; } if ((mask & POLLOUT_SET) && (out & bit)) { res_out |= bit; retval++; } if ((mask & POLLEX_SET) && (ex & bit)) { res_ex |= bit; retval++; } } } if (res_in) *rinp = res_in; if (res_out) *routp = res_out; if (res_ex) *rexp = res_ex; cond_resched(); } wait = NULL; if (retval || timed_out || signal_pending(current)) break; if (table.error) { retval = table.error; break; } /* * If this is the first loop and we have a timeout * given, then we convert to ktime_t and set the to * pointer to the expiry value. */ if (end_time && !to) { expire = timespec_to_ktime(*end_time); to = &expire; } if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS)) timed_out = 1; } __set_current_state(TASK_RUNNING); poll_freewait(&table); return retval; }
上面的代码很多,其实真正关键的代码是q一? mask = (*f_op->poll)(file, retval ? NULL : wait);
q个是调用文件系l的 poll函数Q不同的文gpȝpoll函数自然不同Q由于我们这里关注的是tcpq接Q而socketfs的注册在 net/Socket.c里?br>register_filesystem(&sock_fs_type);
socket文gpȝ的函C是在net/Socket.c里:(x) static const struct file_operations socket_file_ops = { .owner = THIS_MODULE, .llseek = no_llseek, .aio_read = sock_aio_read, .aio_write = sock_aio_write, .poll = sock_poll, .unlocked_ioctl = sock_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = compat_sock_ioctl, #endif .mmap = sock_mmap, .open = sock_no_open, /* special open code to disallow open via /proc */ .release = sock_close, .fasync = sock_fasync, .sendpage = sock_sendpage, .splice_write = generic_splice_sendpage, .splice_read = sock_splice_read, }; 从sock_poll跟随下去Q?br>最后可以到 net/ipv4/tcp.c?br>unsigned int tcp_poll(struct file *file, struct socket *sock, poll_table *wait)
q个是最l的查询函数Q?br>也就是说select 的核?j)功能是调用tcp文gpȝ的poll函数Q不停的查询Q如果没有想要的数据Q主动执行一ơ调度(防止一直占用cpuQ,直到有一个连接有惌的消息ؓ(f)止?br>从这里可以看出select的执行方式基本就是不同的调用poll,直到有需要的消息为止Q如果select 处理的socket很多Q这其实Ҏ(gu)个机器的性能也是一个消耗?br>
2、epoll的实?/h2>
epoll的实C码在 fs/EventPoll.c下, ׃epoll涉及(qing)到几个系l调用,q里不逐个分析?jin),仅仅分析几个关键点?br>W一个关键点?br>static int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd)
q是在我们调用sys_epoll_ctl d一个被理socket的时候调用的函数Q关键的几行如下Q?br>epq.epi = epi; init_poll_funcptr(&epq.pt, ep_ptable_queue_proc); /* * Attach the item to the poll hooks and get current event bits. * We can safely use the file* here because its usage count has * been increased by the caller of this function. Note that after * this operation completes, the poll callback can start hitting * the new item. */ revents = tfile->f_op->poll(tfile, &epq.pt);
q里也是调用文gpȝ的poll函数Q不q这ơ初始化?jin)一个结构,q个l构?x)带有一个poll函数的callback函数Qep_ptable_queue_procQ?br>在调用poll函数的时候,?x)执行这个callbackQ这个callback的功能就是将当前q程d?socket的等待进E上?br>static void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt) { struct epitem *epi = ep_item_from_epqueue(pt); struct eppoll_entry *pwq; if (epi->nwait >= 0 && (pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL))) { init_waitqueue_func_entry(&pwq->wait, ep_poll_callback); pwq->whead = whead; pwq->base = epi; add_wait_queue(whead, &pwq->wait); list_add_tail(&pwq->llink, &epi->pwqlist); epi->nwait++; } else { /* We have to signal that an error occurred */ epi->nwait = -1; } }
注意到参?whead
实际上是 sk->sleepQ其实就是将当前q程d到sk的等待队列里Q当该socket收到数据或者其他事件触发时Q会(x)调用 sock_def_readable
或者sock_def_write_space
通知函数来唤醒等待进E,q?个函数都是在socket创徏的时候填充在skl构里的?br>从前面的分析来看Qepoll实是比select聪明的多、轻杄多,不用再苦哈哈的去轮询?jin)?br> ]]> 推荐一个跨q_内存分配?/title> http://www.shnenglu.com/feixuwu/archive/2010/07/10/119980.htmlfeixuwu feixuwu Sat, 10 Jul 2010 09:32:00 GMT http://www.shnenglu.com/feixuwu/archive/2010/07/10/119980.html http://www.shnenglu.com/feixuwu/comments/119980.html http://www.shnenglu.com/feixuwu/archive/2010/07/10/119980.html#Feedback 11 http://www.shnenglu.com/feixuwu/comments/commentRss/119980.html http://www.shnenglu.com/feixuwu/services/trackbacks/119980.html http://code.google.com/p/google-perftools/Q,据说google的很多品都用到?jin)这个内存分配库Q而且l他试Q我们的游戏客户端集成了(jin)q个最新内存分配器后,FPS提高?jin)将q?0帧左叻Iq可是个?jin)不L(fng)提升Q要知道3Dl的兄弟忙了(jin)几周也没见这么大的性能提升?br> 如果我们自己本n用的crt提供的内存分配器Q这个提升也不得什么。问题是我们内部pȝ是有一个小内存理器的Q一般来说小内存分配的算法都大同异Q现成的实现也很多,比如linux内核的slab、SGI STL的分配器、ogre自带的内存分配器Q我们自q内存分配器也和前面列丄实现差不多。让我们来看看这个项目有什么特别的吧?br>
一、用方?/h1>
打开主页Q由于公司网l禁止SVN从外部更斎ͼ所以只能下载了(jin)打包的源代码。解压后Q看到有个doc目录Q进去,打开使用文Q发C用方法极为简单:(x) To use TCMalloc, just link TCMalloc into your application via the
"-ltcmalloc" linker flag.再看法Q也没什么特别的Q还是和slab以及(qing)SGI STL分配器类似的法?br>unix环境居然只要链接q个tcmalloc库就可以?jin)!Q太方便?jin),不过我手头没有linux环境Q文上也没提到windows环境怎么使用Q?br>打开源代码包Q有个vs2003解决Ҏ(gu)Q打开Q随便挑选一个测试项目,查看目属性,发现仅仅?点不同:(x) 1、链接器命o(h)行里多了(jin) "..\..\release\libtcmalloc_minimal.lib"Q就是链接的时候依赖了(jin)q个内存优化库?br>2、链接器->输入->强制W号引用 多了(jin) __tcmalloc?br>q样可以正的使用tcmalloc库了(jin)Q测试了(jin)下,试目q行OK!
二、如何替换CRT的malloc
从前面的描述可知Q项目强制引用了(jin)__tcmallocQ?搜烦(ch)?jin)测试代码,没发现用到_tcmalloc相关的函数和变量,q个选项应该是ؓ(f)?jin)防止dll被优化掉(因ؓ(f)代码里没有什么地方用到这个dll的符??br>初看hQ链接这个库后,不会(x)影响M现有代码:我们没有引用q个Lib库的头文Ӟ也没有用过q个dll的导出函数。那么这个dll是怎么优化应用E序性能的呢Q?br>实际调试Q果然发现问题了(jin)Q看看如下代?br> void* pData = malloc(100); 00401085 6A 64 push 64h 00401087 FF 15 A4 20 40 00 call dword ptr [__imp__malloc (4020A4h)] 跟踪 call mallocq句Qstepq去Q发现是 78134D09 E9 D2 37 ED 97 jmp `anonymous namespace'::LibcInfoWithPatchFunctions<8>::Perftools_malloc (100084E0h) 果然Q从q里开始,p转到l(f)ibtcmalloc提供的Perftools_malloc?jin)?br>原来是通过API挂钩来实现无~替换系l自带的malloc{crt函数的,而且q是通过大家公认的不推荐的改写函数入口指令来实现的,一般只有在游戏外挂和金p怹cȝ软g才会(x)用到q样的挂钩技术, 而且金山词霸l常需要更新补丁解决不同系l兼定w题?br>
三、性能差别原因
如前面所qͼtcmalloc实用了(jin)很hacker的办法来实现无缝的替换系l自带的内存分配函数Q本人在使用q类技术通常是用来干坏事的。。。)(j)Q但是这也不以解释Z么它的效率比我们自己的好那么多?br>回到tcmalloc 的手册,tcmalloc除了(jin)使用常规的小内存理外,对多U程环境做了(jin)Ҏ(gu)处理Q这和我原来见到的内存分配器大有不同Q一般的内存分配器作者都?x)偷懒,把多U程问题扔给使用者,大多是加 个bool型的模板参数来表C是否是多线E环境,q美其名?可定Ӟ末了(jin)q得吹嘘下模板的优越性?br>tcmalloc是怎么做的呢? {案是每U程一个ThreadCacheQ大部分操作pȝ都会(x)支持thread local storage 是传说中的TLS,q样可以实现每U程一个分配器?jin)?br>q样Q不同线E分配都是在各自的threadCache里分配的。我们的目的分配器׃是多U程环境的,所以不三七二十一Q全都加锁了(jin)Q性能自然׃?jin)?br> 仅仅是如此,q是不以将tcmalloc和ptmalloc2分个高下Q后者也是每个线E都有threadCache的?br>关于q个问题Qdoc里有一D说明,原文贴出来:(x) ptmalloc2 also reduces lock contention by using per-thread arenas but
there is a big problem with ptmalloc2's use of per-thread arenas. In
ptmalloc2 memory can never move from one arena to another. This can
lead to huge amounts of wasted space. 大意是这L(fng)Qptmalloc2 也是通过tls来降低线E锁Q但是ptmalloc2各个U程的内存是独立的,也就是说Q第一个线E申L(fng)内存Q释攄时候还是必L到第一个线E池中(不可UdQ,q样可能D大量内存?gu)费?br>
四、代码细?/h1>
1、无~替换malloc{crt和系l分配函数?/h2>
前面提到tcmalloc?x)无~的替换掉原有dll中的mallocQ这意味着使用tcmalloc的项目必L MDQ多U程dllQ或者MDdQ多U程dll调试Q。tcmalloc的dll定义?jin)一?br>static TCMallocGuard module_enter_exit_hook; 的静态变量,q个变量?x)在dll加蝲的时候先于DllMainq行Q在q个cȝ构造函敎ͼ?x)运行PatchWindowsFunctions来挂钩所有dll?malloc、free、new{分配函敎ͼq样pC(jin)替换功能Q除此之外, Z(jin)保证pȝ兼容性,挂钩API的时候还实现?jin)智能分析指令,否则写入W一条Jmp指o(h)的时候可能会(x)破环后箋指o(h)的完整性?br>
2、LibcInfoWithPatchFunctions 和ThreadCache?/h2>
LibcInfoWithPatchFunctions模板cd含tcmalloc实现的优化后的malloc{一pd函数。LibcInfoWithPatchFunctions的模板参数在我看来没什么用处,tcmalloc默认可以挂钩 最?0个带有malloc导出函数的库(我想肯定是够用了(jin))。ThreadCache在每个线E都?x)有一个TLS对象Q?br>__thread ThreadCache* ThreadCache::threadlocal_heap_?br>
3、可能的问题
设想下这样一个情景:(x)假如有一个dll 在tcmalloc之前加蝲Qƈ且在分配?jin)内存(使用crt提供的mallocQ,那么在加载tcmalloc后,tcmalloc?x)替换所有的free函数Q然后,在某个时刻, 在前面的那个dll代码中释放该内存Q这岂不是很危险。实际测试发现没有Q何问题,关键在这里:(x) span = Static::pageheap()->GetDescriptor(p); if (!span) { // span can be NULL because the pointer passed in is invalid // (not something returned by malloc or friends), or because the // pointer was allocated with some other allocator besides // tcmalloc. The latter can happen if tcmalloc is linked in via // a dynamic library, but is not listed last on the link line. // In that case, libraries after it on the link line will // allocate with libc malloc, but free with tcmalloc's free. (*invalid_free_fn)(ptr); // Decide how to handle the bad free request return; }
tcmalloc?x)通过span识别q个内存是否自己分配的,如果不是Qtcmalloc?x)调用该dll原始对应函数(q个很重?释放。这样就解决?jin)这个棘手的问题?br>
五、其?/h1>
其实tcmalloc使用的每个技术点我从前都用过Q但是我从来没想q用API挂钩来实现这样一个有的内存优化库(即惌Q也是一闪而过否定了(jin)Q?br>从tcmalloc得到灉|Q结合常用的外挂技术,可以很轻杄开发一个独立工P(x)q个工具可以挂蝲到指定进E进行内存优化,在我看来Q这可能可以作ؓ(f)一个外挂辅助工h优化那些 内存优化做的很差D帧速很低的国游戏?br> ]]>
þĻƵ
|
þþþþþþþþ |
˾þô߽鶹 |
þþþþúݺݶ |
պŷþ |
˾þô߽Ʒ |
þþƷƷ |
þþþùһëƬ |
þɫۺ |
˾þۺ |
Ʒþþþþ |
Ʒþþþ |
þþƷԴվ |
97Ʒþ찴Ħ |
þۺϾɫۺϾ99 |
þþƷ99þ㽶ɫ |
Ļһþվ |
þþþ99ƷƬţţӰ |
þþƷAV |
ۺϾƷþ |
97Ʒ91þþþþ |
ƷþþӰԺӰƬ |
Ʒһþ |
þþƷwwwˬ |
þҹҹݺ2022 |
þ99ۺϾƷ |
˾Ʒþþþ7777 |
þþþù |
Ʒһþ |
þһ |
ŷ˾þۺ |
鶹Ʒþþһ |
Ʒþþþsm |
þøƬ |
þܳ |
ŷƷ˾þԻӰƬ |
ݺݾþۺ |
þ99Ʒþþþþò |
9999Ʒŷþþþþ
|
þþþavר |
鶹þþ9ԴƬ |