Chinaunix首页 | 论坛 | 博客
  • 博客访问: 180281
  • 博文数量: 37
  • 博客积分: 1367
  • 博客等级: 中尉
  • 技术积分: 465
  • 用 户 组: 普通用户
  • 注册时间: 2007-06-06 17:41
文章分类

全部博文(37)

文章存档

2015年(1)

2012年(17)

2011年(10)

2010年(1)

2009年(8)

我的朋友

分类: C/C++

2009-07-08 13:00:39

Symbian清除栈的深入分析

从表面看起来清除栈的概念还是很容易理解的,使用起来也是比较方便。市面上关于Symbian的大部分书籍都对Symbian清除栈的使用有详细的介绍。可是,到现在,很少有资料详细涉及Symbian清除栈工作原理。我们接触最多的资料一般是《Symbian OS Explained Effective C++ Programming for Smartphones》这本书,但是这本书似乎也是给人一种戛然而止的感觉,使我们只能局限于Symbian应用程序的开发,却不能深入了解 Symbian操作系统本身。于是,我想利用业余时间和更多的人一起探讨一下Symbian清除栈的工作原理。当然,也不仅限于此,后续我还会写更多的文章和大家一起探讨Symbian的核心工作原理,甚至一起对Symbian的内核进行分析。我希望能够吸引更多的人参与Symbian的学习和研究过程,来共同壮大Symbian开发阵营。

下面我们来探讨Symbian清除栈的工作原理,这是我写的第一篇关于Symbian操作系统工作原理的文章,所以还是有一些表述上的问题,希望大家多多给出意见。另外,也希望读者有一定的Symbian和C++基础,否则还是会影响阅读的。

清除栈实际上是一种半自动的内存回收机制,Symbian为了达到这个目的,做了很多工作,甚至付出了不少代价。与之相关的有清除栈本身的框架、TRAP宏及Leave机制。本文先从Symbian清除栈框架介绍,如果阅读过《Symbian OS Explained Effective C++ Programming for Smartphones》这本书的话,可以直接从本文图1以下的位置开始阅读。

1.1.1.   清除栈的框架:

清除栈保存了在发生异常退出时会被销毁的对象的指针,而这些对象是由TRAP宏来标定为不同的异常退出等级,即TRAP宏是可以嵌套的,每一级嵌套的TRAP宏之内如果发生异常退出,则只有该TRAP宏内推入清除栈的对象才会被销毁,下面的代码说明了这个问题:

CServer2* NewServerL(const TDesC& aServerName)

{

// Install the active scheduler;

    CActiveScheduler* scheduler = new(ELeave) CActiveScheduler;

    CleanupStack::PushL(scheduler);

    CActiveScheduler::Install(scheduler);

   

    CSmallServServer* server = CSmallServServer::NewLC();

    User::LeaveIfError(User::RenameThread(aServerName));

    RProcess::Rendezvous(KErrNone);

    TRAPD(r, server->StartL(aServerName);

if (r != KErrNone)

       {

       //…

       }

    CleanupStack::Pop(2, scheduler);

    return static_cast(server);

    }

   

TInt ThreadStart()

    {

    __UHEAP_MARK;

    TInt err = KErrNone;

   

    // set cleanup stack manually

    CTrapCleanup* cleanup = CTrapCleanup::New();

    CServer2* server = NULL;

    if (cleanup)

       {

       TRAP(err, server = NewServerL(KServer));

       }

    else

       {

       err = KErrNoMemory;

       }

if (err == KErrNone)

       {

       // start the active Scheduler

       CActiveScheduler::Start();

       User::InfoPrint(_L("after"));

       }

    delete server;

    delete CActiveScheduler::Current();

    delete cleanup;

   

    __UHEAP_MARKEND;

    return err;

    }  

在NewServerL函数中有TRAP宏存在,如果该宏的中StartL函数发生异常退出,则NewServerL已经推入清除栈的 scheduler 和server并不受到任何影响,但如果NewServerL函数中User::LeaveIfError产生异常退出,则已经推入清除栈的 scheduler和server将由清除栈自动析构。

那么清除栈到底如何实现这些功能呢?我们先来看看清除栈的创建过程,在一般的GUI应用程序中,我们通常不关心清除栈的创建,可以直接使用,这是因 为 GUI的框架已经为我们创建好了。但有时候我们需要手动创建清除栈,比如创建一个新的非GUI进程,并且这个进程中的主线程必须用到清除栈。因 此,Symbian提供了CTrapCleanup类用于清除栈的初始化。

我们继续从上面代码中来看看清除栈是如何初始化的,可以看出上面这段代码是一个单独的进程启动过程,其中有这样一段代码。

CTrapCleanup* cleanup = CTrapCleanup::New();

delete cleanup;

经典的清除栈框架对这段代码的描述是这样,当创建CTrapCleanup类对象的时候,CTrapCleanup::New()中发生以下事件:

1.线程当前的异常处理程序被保存起来。

2.在CTrapCleanup对象中创建一个名为iHandler的TCleanTrapHandler类对象(它持有一个包含实际清除栈实现代码的CCleanup对象)。

3.调用User;;SetTrapHandler(),将TCleanupTrapHanlder对象作为线程中新的异常处理程序。

1 清除栈结构

当调用CleanupStack::PushL()或CleanupStack::Pop()时,这些静态函数会调用 User::TrapHandler()来获取已经安装TCleanupTrapHandler对象,从而可以访问CCleanup对象(见图 1)。正如前面所说,CCleanup对象是实现清除栈代码的核心类,它真正的负责CleanupStack类中静态函数的实现。

现在我们来看CCleanup及其他一些辅助类如何实现清除栈的功能,以CleanupStack类中的三个推入函数为例来逐渐揭开清除栈的真相,这三个函数在Symbian应用开发过程中是再熟悉不过的了,分别是:

IMPORT_C static void PushL(TCleanupItem anItem);

IMPORT_C static void PushL(TAny* aPtr);

IMPORT_C static void PushL(CBase* aPtr);

这三个函数将分别调用CCleanup对象中的以下三个函数:

EXPORT_C void CCleanup::PushL(TCleanupItem anItem)

{

    …// check consistency

    iNext->Set(anItem);

    iNext++;

    if (iNext + 1 >= iTop)

       {

       …// reallocate memory to cleanup stack

       }

    }

EXPORT_C void CCleanup::PushL(TAny* aPtr)

{

    PushL(TCleanupItem(User::Free, aPtr);

    }

EXPORT_C void CCleanup::PushL(CBase* anObject)

{

    PushL(TCleanupItem(TCleanupOperation(doDelete), anObject);

    }

可以看到在CCleanup对象中也有类似的三个PushL重载函数,我们先来看看CCleanup::PushL(TCleanupItem anItem)函数,因为CCleanup::PushL(TAny* aPtr)和CCleanup::PushL(CBase* anObject)在内部实际上也是在调用它。

首先,该函数内部的iNext和iTop是用来控制清除栈指针的,这和数据结构中栈的处理是一样的,我们来看看e32base.h中iNext和iTop的定义:

//Pointer to the top of the cleanup stack.

TCleanupStackItem* iTop;

   //Pointer to the next availaible slot in the cleanup stack.

   TCleanupStackItem* iNext;

请注意注释部分,iNext原来就是指向清除栈的下一个空闲槽,该空闲槽的类定义是TCleanupStackItem,且该类对象的集合组成清除 栈。那么CCleanup::PushL(TCleanupItem anItem)函数就是在清除栈中将所传过来的对象放入空闲槽(Set(anItem))并且“推入”清除栈(iNext++),即让iNext指向清除 栈下一个空闲槽。

现在我们已经知道在Push过程中清除栈是如何被控制的,但是用户所传过来的对象是如何保存的呢,这就需要TCleanupItem类,该类是 CCleanup::PushL(TCleanupItem anItem) 函数的参数类型,并且被放入清除栈的空闲槽(Set(anItem)),我们继续从e32base.h寻找TCleanupItem类的定义,很幸运,发 现它是这样定义的:

typedef void (*TCleanupOperation)(TAny*);

class TCleanupItem

    {

public:

    inline TCleanupItem(TCleanupOperation anOperation);

    inline TCleanupItem(TCleanupOperation anOperation,TAny* aPtr);

private:

    TCleanupOperation iOperation;

    TAny* iPtr;

    friend class TCleanupStackItem;

    };

原来TCleanupItem将用户要推入清除栈的对象及其销毁处理的方法分别放入iPtr和iOperation函数指针中,在发生异常退出或是CleanupStack::PopAndDestroy就可以将所指向的对象“就地处理”了(后面还会详细介绍)。

说到这里,对清除栈的Push机制,我们就有了一个比较完整的了解,再来看CCleanup::PushL(TAny* aPtr)就比较容易理解了。该函数将User::Free作为对象销毁的处理方法和对象的指针一起放入TCleanupItem对象并推入清除栈的空闲 槽,在发生异常退出或是CleanupStack::PopAndDestroy时就可以调用User::Free来释放TAny*指向对象的内存空间 了。

同理,CCleanup::PushL(CBase* anObject)函数也是这样处理的,该函数将doDelete函数指针放入TCleanupItem对象并推入清除栈的空闲槽,在发生异常退出或是 CleanupStack::PopAndDestroy时就可以调用doDelete来释放CBase*指向对象的内存空间了。那么doDelete又 是如何处理的呢?有些读者可能已经猜出十之八九。不过,为了表示清楚,还是列出来:

LOCAL_C void doDelete(CBase* aPtr)

    {

    delete aPtr;

    }

原来就是这样简单,因为CBase的析构函数是虚函数,从CBase继承下来的C类,只要简单的delete就可以!

我们再看看CleanupStack::PopAndDestroy()或异常退出时是如何删除清除栈对象的,iNext会调用成员函数Cleanup(),该函数会执行下面一行代码:

(*iOperation)(iPtr);

其中iOperation的定义为

typedef void (*TCleanupOperation)(TAny*);

TCleanupOperation iOperation;

可以看出,iOperation指向的函数被调用,比如说,对于CBase继承下来的C类,就会调用刚讲过的doDelete,对于TAny*就会调用User::Free()来直接释放内存,以此类推。

1.1.2.   TRAP宏

这是Symbian SDK中关于TRAP宏的定义:

TRAP(_r,_s){                                                 \

    TInt& __rref = _r;                                     \

    __rref = 0;                                             \

    { TRAP_INSTRUMENTATION_START; }                         \

    try {                                                    \

        __WIN32SEHTRAP                                      \

         TTrapHandler* ____t = User::MarkCleanupStack();     \

        _s;                                                 \

        User::UnMarkCleanupStack(____t);                    \

        { TRAP_INSTRUMENTATION_NOLEAVE; }                   \

        __WIN32SEHUNTRAP                                    \

        }                                                   \

    catch (XLeaveException& l)                              \

        {                                                   \

        __rref = l.GetReason();                             \

        { TRAP_INSTRUMENTATION_LEAVE(__rref); }             \

        }                                                   \

    catch (...)                                             \

        {                                                    \

        User::Invariant();                                  \

        }                                                   \

    { TRAP_INSTRUMENTATION_END; }                           \

}

这段代码看起来似乎并不重要,但就是这段看似不起眼的代码决定了Symbian整个清除栈及其异常退出的框架。

先来看Symbian是如何实现TRAP嵌套与清除栈挂钩的:

TTrapHandler* ____t = User::MarkCleanupStack();     \

_s;                                                 \

User::UnMarkCleanupStack(____t);                    \

这里User::MarkCleanupStack()实际上还是求助于CCleanup对象的代码实现,它调用CCleanup::NextLevel()函数。

EXPORT_C void CCleanup::NextLevel()

{

    iNext->MarkLevel();

    iNext++;

    }

看起来似乎有点让人费解,怎么又在操作iNext?其实,MarkLevel()函数会将iNext指向的下一个清除栈空闲槽 TCleanupStackItem对象设定为一个标记,该标记将不再会放入任何用户推入对象,仅仅作为标记,然后iNext指向再下一个清除栈空闲槽, 于是新的用户推入对象只会放入该标记之上,如图2所示:

2 TRAP嵌套

同样,User::UnMarkCleanupStack()实际上也是求助于CCleanup对象的代码实现,它调用 CCleanup::PreviousLevel()函数,这个函数就是将iNext减一,位置回退一位并将标志所占空闲槽改为可以推入对象的空闲槽,如 下所示:

EXPORT_C void CCleanup::PreviousLevel()

{

    --iNext;

if (!iNext->IsLevelMarker())

       {

       Panic(ENotEmpty);

       }

    }

这里有人就要问了,为什么iNext需要判断是否为标记?而且如果不是,竟然会将整个程序Panic!那是什么原因造成这么严重的错误?其实,这就 是防止 Symbian开发新手经常犯的错误,即只Push对象进入清除栈却没有及时的将其Pop出清除栈。因此,TRAP宏执行到这里时就会检查清除栈该标记以 上部分是否还有未清除的对象,如果有,就说明有对象忘Pop了,于是整个程序便Panic。需要提醒的是,实际清除栈的代码远比上面描述的要复杂,其中还 有许多问题需要考虑,在此就不一一描述了。

我们再来看Symbian的异常退出(Leave),异常退出机制也是清除栈实现的重要组成部分,经常使用的异常退出函数如下:

IMPORT_C static void Leave(TInt aReason);

IMPORT_C static void LeaveNoMemory();

IMPORT_C static TInt LeaveIfError(TInt aReason);

IMPORT_C static TAny* LeaveIfNull(TAny* aPtr);

这些函数都实现了如下代码:

TTrapHandler* t = GetTrapHandler();

if (t)

t->Leave(aReason);

throw XLeaveException(aReason);

TTrapHandler对象的Leave函数最终又是求助于CCleanup对象的代码,CCleanup则是这样实现的,即清除该TRAP宏中所有已经推入的用户对象:

while(!(--iNext)->IsLevelMarker())

    {

    iNext->Cleanup();

}

可以看到,如果iNext指向的不是标记,则循环进行对象的清除工作。

到此为止,似乎清除栈的相关内容我们已经介绍完了,但还有一些问题需要解释,我们看到在异常退出的那些函数里,有这样一行代码:

throw XLeaveException(aReason);

而在TRAP宏里有这样的catch模块:

catch (XLeaveException& l)                               \

        {                                                   \

        __rref = l.GetReason();                             \

        { TRAP_INSTRUMENTATION_LEAVE(__rref); }             \

        }    

原来Symbian在实现TRAP和异常退出时,还是使用的标准C++异常机制。那么就有人要问了,为什么不让Symbian应用开发人员使用呢? 这可能 有历史遗留方面的原因,但有一个理由很明显,如果让Symbian应用开发人员使用标准C++异常机制,一旦发生标准C++异常,谁来清除清除栈内的对象 呢?显然,不能让开发人员来维护这件事情,而使用TRAP宏就可以在发生异常时强制清除所需要清除的对象。所以,Symbian为清除栈真是费尽了心机 啊。

此文转自:http://hi.baidu.com/c_linuxsymbian/blog/item/922e30160328951e972b433f.html

阅读(1141) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~