活动对象框架探秘(上篇)

 

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()做一个更详细的介绍,当然,这些都是我个人的理解,纰漏在所难免,欢迎大家批评指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值