Coastline版权所有,转载请注明出处。
做Symbian的人都会用AO来处理异步,但是对于CActiveScheduler、CActive、CActiveSchedulerWait等一整套机制,估计不是每个人都了解的。坦白地说从2007年8月接触Symbian至今,我也只是掌握了最基础的开发技巧,至于Symbian Internals,知之甚少。之前写过一篇《利用CActiveSchedulerWait实现同步》,也只是在应用层面上的讲解,知其然而不知其所以然,显然是很危险的,不明白其工作原理,出了问题就很被动。一直以来都想要把活动对象框架整明白,无奈下不了这个决心研究。最近论坛很多人讨论,一咬牙凑个热闹,参考了官方材料和Vincent牛人以及其他无数Symbian前辈的资料,花了一天时间仔细地学习了一番。现在有些明朗了,不过仍旧有些细节不甚明了,估计要仔仔细细看过《Symbian OS Internals》才行了吧。
先把今天的心得记录下来,我这个人记忆力不行,我怕明天就全忘光了:P
先看看CActiveScheduler的结构:
class CActiveScheduler : public CBase
{
…
private:
TLoop* iStack;
TPriQue iActiveQ; //TPriQue是TDblQueBase双向链表模板类的派生类,加入了优先级支持。所以用Pri前缀
TAny* iSpare;
}
以下代码摘自某官方PDF文档,应该是类似于伪代码,至少和3rd中CActiveScheduler的声明不太能对上号,但这些伪代码已经足够让我们一窥个中究竟了。
//CActiveScheduler::Install的实现
EXPORT_C void CActiveScheduler::Install(CActiveScheduler *aManager)
{
if (aManager!=NULL)
__ASSERT_ALWAYS(Exec::ActiveScheduler()==NULL,Panic(EReqManagerAlreadyExists));
//Exec是Symbian Calls软中断,NewLC上有位高人做过详细讲解,那篇文章值得好好研究
Exec::SetActiveScheduler(aManager);
}
EXPORT_C void CActiveScheduler::Start()
{
CActiveScheduler* pS=Exec::ActiveScheduler();
__ASSERT_ALWAYS(pS!=NULL, Panic(EReqManagerDoesNotExist));
pS->OnStarting(); //OnStarting()做了什么?小弟愚笨,猜测不到
TRAPD(error, pS->DoStart()); // shocking, it’s true, but BAFL actually leaves from it’s
// function overriding CActiveScheduler::Error, so we need to TRAP it to ensure that
// OnStopping gets called
// 显然,DoStart()就是重头戏。正如上面英文注释所说,DoStart()显然会Leave,因为它会调用AO的RunL()啊,
// 但是为什么它不叫DoStartL()呢,费解ing
pS->OnStopping();
if (error != KErrNone)
User::Leave(error);
}
//我们的主角:DoStart()的实现
void CActiveScheduler::DoStart()
{
TDblQueIter q(iActiveQ); //构造一个CActive的双向链表枚举器,用来遍历加入到活动对象调度器中的每一个AO
TInt level=iLevel++; //iLevel是活动对象调度器的嵌套级数。这边注意一下,看class CActiveScheduler的声明,并没有 iLevel这个成员,相应的,有一个TLoop* iStack;这个struct TLoop是个什么东西无从得知,不过应该是iLevel的改良版,作用依然是记录当前调度器的嵌套层数。用户为了实现同步,显式调用CActiveScheduler::Start()后,启动了嵌套的规划器,iLevel就会自加一。一个CActiveScheduler::Stop()只会令与它配对的CActiveScheduler::Start()返回,所以可以实现同步。这个过程有些绕,需要仔细体会。
while (iLevel>level)
{
WaitForAnyRequest();
//重点关注一下WaitForAnyRequest();这个成员函数应该是间接调用User::WaitForAnyRequest(),根据Vincent的说法:
/* User::WaitForAnyRequest到底做了什么呢?原来对于一个线程,在user mode下是RThread,同时在kernel里面有一个DThread和它对应。DThread有一个RequestSemaphore的成员。
异步请求别的RThread,也就是一个server来做某件事,当server做完以后,它就会来修改这个semaphore,把semaphore加一。
当RThread(application)调用 User::WaitForAnyRequest后就会把semaphore的值减一。当semaphore的值为-1时线程就suspend. */
//从Vincent的话中可以了解到:WaitForAnyRequest就是等待异步操作完成。显然,在某个时间,执行到WaitForAnyRequest()时,semaphore很有可能是一个大于0的正整数N,这说明目前有N个异步操作完成了,这些异步操作可能是文件操作,也可能是网络、按键、Timer、UI等。WaitForAnyRequest判断semaphore是否是-1,是的话说明没有任何事件需要处理,但是总不能退出程序吧,也不能跑一个死循环白白浪费CPU吧,所以显然应该把线程挂起,直到semaphore被Kernel改动后(即有异步操作完成了),线程才被唤醒。然后继续执行下面的代码。
q.SetToFirst(); //这里面包含了按照优先级排序的过程
FOREVER //原文就是这样写的,估计就是while(ETrue)吧
{
CActive *pR=q++; //一个一个AO进行遍历,直到找到第一个异步请求已完成的优先级最高的AO为止。
__ASSERT_ALWAYS(pR!=NULL,Panic(EReqStrayEvent)); //如果某个AO是空指针,则发生迷失信号Panic
if (pR->IsActive() && pR->iStatus!=KRequestPending) //这就是AO提交异步请求时一定要调用SetActive()的缘故,pR->iStatus!=KRequestPending这如何理解?原来某个server(其他线程,甚至其他进程)在处理你的异步请求时,会通过Kernel将你的AO的iStatus设成KRequestPending,等它完成了异步操作,就再次通过Kernel将iStatus设成一个非KRequestPending的TInt值,这个TInt值代表了异步操作的结果。比如KErrNone、KErrNotFound啊等等。这个结果码可以通过iStatus.Int()来获取。所以说我们在某个AO的RunL中千万不要得意洋洋地以为RunL被执行到就意味着异步操作一定成功了,千万要记得先用iStatus.Int()==KErrNone判断一把。
{
pR->iActive=EFalse; //激活状态复位
TRAPD(r,pR->RunL()); //代码控制权回到了用户手中,我们写那么一大堆代码,原来都是通过某个RunL被调用的,悲哀啊…
if (r!=KErrNone) //用户代码发生了Leave,就调用RunError
{
r = pR->RunError(r);
if (r!=KErrNone) //假如在RunError中没有返回KErrNone的话,调用Error()。Error()一旦被调用,就会引发一个Panic,程序就退出了,所以说如果想继续让程序运行下去,在RunError中应该返回KErrNone。
Error(r);
}
break; //一个loop只处理一个AO。原因很简单,因为处理完一个AO,就得把semaphore减一。而且刚刚执行的RunL中,很可能又递交了一个优先级超高的AO的异步请求,所以需要一次新的loop来进行AO的重新排序。当然,这样也带来一个弊端,要是某个低优先级的AO完成了异步操作,但是由于排在它前面的高优先级的AO的RunL中不断发起新的异步请求,等RunL跑完后新的异步请求也完成了,那么这时候理论上那个低优先级的AO的RunL将永远没有机会被执行。(如果事实真的是这样的话,那这样的代码就类似于死循环了,很可怕啊,嗯,我需要验证一下)
}
}//FOREVER
}//while
}
//停止CActiveScheduler
EXPORT_C void CActiveScheduler::Stop()
//
// Stop despatching request completions.
//
{
CActiveScheduler *pS=Exec::ActiveScheduler();
__ASSERT_ALWAYS(pS!=NULL,Panic(EReqManagerDoesNotExist));
pS->iLevel–; //关键代码就这一行。iLevel减1,那么DoStart()中的while (iLevel>level)就不满足条件了,因此DoStart()终于可以返回了,不容易啊,如果调用CActiveScheduler::Start()是最外层,那么显然就退出了应用程序。如果是嵌套的CActiveScheduler::Start()调用,则返回到CActiveScheduler::Start()的调用处——但那儿依然是某个RunL的间接调用。所以说嵌套的CActiveScheduler::Start()和CActiveScheduler::Stop()可以实现同步。由于嵌套CActiveScheduler::Start()和CActiveScheduler::Stop()会把代码弄乱,至少从代码上不能一目了然地区分某个CActiveScheduler::Stop()对应的是哪一层的CActiveScheduler::Start(),所以CActiveSchedulerWait出现了。当然,你想在CActiveSchedulerWait中继续来一个同步,估计得使用CActiveSchedulerWaitWait了。。。O(∩_∩)O哈哈~开玩笑。
__ASSERT_ALWAYS(pS->iLevel>=0,Panic(EReqTooManyStops));
}
EXPORT_C CActiveScheduler *CActiveScheduler::Current()
//
// Return the currently installed active scheduler.
//
{
return(Exec::ActiveScheduler());
}
在《活动对象框架探秘(下篇)》中将会对同步的实现,以及User::WaitForRequest()做一个更详细的介绍,当然,这些都是我个人的理解,纰漏在所难免,欢迎大家批评指正。