跟着Code走,详解Symbian清理栈

【清理栈是干什么的?】

程序一般提供两种错误处理机制,通过返回值判断和异常处理。通过返回值判断是程序正常执行流程中,对错误的处理方式;而异常处理是程序执行过程出现异常时,处理错误的方式。清理栈是Symbian下的异常处理机制,结合TRAP/Leave,保证程序出现异常时,已经申请的资源得以释放。

编程者如果认为某个函数的执行过程可能发生异常(调用到的代码可能调用User::Leave,或者自己编写的代码可能调用User::Leave),需要对这个函数进行TRAP,同时保证这个函数中申请的资源都被正确的放入清理栈。当程序发生异常时(调用User::Leave),清理栈中的资源在离开TRAP范围之前,会得以释放。

其实好的编程习惯是,任何时候你都应该把申请的资源正确的放入清理栈,即使你的代码中没有处理TRAP/Leave。因为上层的函数可能处理了TRAP/Leave,如果资源都被正确的放入了清理栈,上层函数处理TRAP/Leave后继续执行,可以保证没有资源泄漏。如果你申请的资源没有放入清理栈,上层函数处理TRAP/Leave后继续执行,就已经有了资源泄漏了。

Symbian有这样的编程规范,把可能Leave的函数以L或LC结尾命名,但是这并不是强制的。你不能假设没有以L或LC结尾命名的函数,就不会Leave。

 

【如何使用清理栈?】

清理栈的使用本身很简单,但是为了保持本文的完整性,我们在这里还是给出样例。

首先,新建的线程默认是没有清理栈的,如果你需要使用清理栈,需要建立清理栈,如下。CTrapCleanup::New函数中会自动把线程的当前清理栈设为最近New的清理栈,需要被管理的资源都被放在最近创建的清理栈中。如果你在下一层的函数中再次调用CTrapCleanup::New创建清理栈,线程的当前清理栈会被设为新建的清理栈,新建的清理栈中会保存旧的清理栈的指针,新建的清理栈析构时,会把线程清理栈恢复为旧的清理栈。

GLDEF_C TInt E32Main()
{
    // Create cleanup stack
    __UHEAP_MARK;
    CTrapCleanup* cleanup = CTrapCleanup::New();

    TRAPD(mainError, DoStartL());

    delete cleanup;
    __UHEAP_MARKEND;
    return KErrNone;
}

下面是使用清理栈的代码,新建的CActiveScheduler被放入清理栈,然后调用了可能Leave的MainL函数。如果MainL中发生了Leave,则err不会为KErrNone,然后调用User::Leave(err)。发生Leave后,最后一句CleanupStack::PopAndDestroy(scheduler)就不会被调用到了,程序会进行调用栈的回滚,直到找到上一级的TRAP,在回滚的过程中,scheduler会被清理栈释放。如果MainL没有发生Leave,最后一句CleanupStack::PopAndDestroy(scheduler);会被调用到,scheduler会被从清理栈中弹出并且释放。

    CActiveScheduler* scheduler = new (ELeave) CActiveScheduler();
    CleanupStack::PushL(scheduler);
    CActiveScheduler::Install(scheduler);

    TRAPD(err, MainL());
    if (err != KErrNone)
    {
        User::Leave(err);
    } 
    CleanupStack::PopAndDestroy(scheduler);

程序发生Leave后,如果在调用栈回滚过程中,一直都没有找到TRAP,最后线程会crash。

 

【清理栈是怎样运作的?】

Kernal Package中的文件kernel/eka/euser/cbase/ub_cln.cpp包含了清理栈用户态的实现代码。下图是Symbian清理栈用户态相关实现类的结构。用户一般通过CleanupStack提供的静态函数进行操作,但是真正功能主要由线程的TTrapHandler* iTrapHandler;提供。每个线程都有一个当前TrapHandler指针,保存在DThread成员变量TTrapHandler* iTrapHandler;(或者用户态的线程私有数据,取决于编译时的宏定义)。

 5014f6724417a996d6656_2

(以上图片摘自博客 http://blog.sina.com.cn/zixieqiangwei

我们先来看看CTrapCleanup::New的实现,代码如下(文件kernel/eka/euser/cbase/ub_cln.cpp中)。这个函数中除了新建CTrapCleanup外,还新建了CCleanup::New,并且把CCleanup保存到了iHandler的成员变量中,最后把新建的iHandler保存到了县城私有数据,同时在成员变量iOldHandler中保存了旧的handler。CCleanup负责保存需要释放的资源指针,并在必要的时候释放。需要把资源指针放入CCleanup时,可以首先从线程私有数据中拿到TrapHandler,接着从其成员得到CCleanup的指针,就可以调用函数了。CCleanup初始分配了KCleanupInitialSlots=8个资源项的数组,一般来说足够了,除非你的调用嵌套非常深。需要注意的是,CCleanup并不是直接保存资源指针,而是以TCleanupStackItem的形式保存,TCleanupStackItem中包括了资源释放的函数指针,这样可以以任意方式释放资源。

EXPORT_C CTrapCleanup *CTrapCleanup::New()
    {

    CTrapCleanup *pT=new CTrapCleanup;
    if (pT!=NULL)
        {
        CCleanup *pC=CCleanup::New();
        if (pC!=NULL)
            {
            pT->iHandler.iCleanup=pC;
            pT->iOldHandler=User::SetTrapHandler(&pT->iHandler);
            }
        else
            {
            delete pT;
            pT=NULL;
            }
        }
    return(pT);
    }

接着我们来看看发生Leave时的动作,代码在文件kernel/eka/euser/us_trp.cpp中。 实现代码分了使用用户态线程私有数据保存TrapHandler,和使用内核线程对象成员变量保存TrapHandler的两种情况,为了分析简单,我们只考虑使用内核线程对象成员变量保存的情况,代码简化如下。与Exec::LeaveStart对应的内核处理函数是ExecHandler::LeaveStart,在文件kernel/eka/kernel/scodeseg.cpp中,并没有复杂的操作,仅仅是设置线程正在Leave的标志位,然后返回TrapHandler。接着调用TrapHandler::Leave,这是关键过程,对应实现代码在文件kernel/eka/euser/cbase/ub_cln.cpp的TCleanupTrapHandler::Leave函数中,直接调用iCleanup->PopAndDestroyAll();释放了所有资源(这里的iCleanup就是前面CTrapCleanup::New时创建的CCleanup对象)。释放了资源后,下面的代码接着throw了XLeaveException,并且呆了aReason,然后会被TRAP中的catch抓住。

EXPORT_C void User::Leave(TInt aReason)
    {
    TTrapHandler* pH = Exec::LeaveStart();

    if (pH)
        pH->Leave(aReason);    // causes things on the cleanup stack to be cleaned up
    throw XLeaveException(aReason);
    }

 

【是否所有的异常都可以被TRAP?】

答案:否。

Symbian的TRAP/Leave是基于C++ try/catch实现的。查看TRAP的源码(如下),我们可以发现,TRAP宏定义中只是catch了XLeaveException,对于其他的exception调用了User::Invariant。User::Invariant最终调用了Panic,最终会调用TheCurrentThread->Die(EExitPanic,aReason,aCategory);,如果线程是主线程,程序就退出了。

#define 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只能catch到Leave产生的exception,不能catch其他exception。

 

【Panic是什么?】

Panic也是一种异常,它是系统代码认为已经发生了无法挽回的错误,必须让线程、进程甚至系统crash。它与TRAP/Leave基本上毫无关系,Panic的代码执行路径与TRAP不同,无法被TRAP到。

 

【还有其他异常么?】

答案:有。

例如,我们都知道,任何系统都可能发生除0错误,这也是异常。这些异常也是TRAP无法catch的。对于这一类的异常,每个线程都有exception handler,保存在成员变量DThread的成员变量TExceptionHandler iExceptionHandler;中。发生此类异常后,系统会判断当前线程能否处理,如果不能处理则会crash线程。线程默认的exception handler是NULL,你可以写自己的exception handler,然后设置到线程数据中去。可以被线程的exception handler处理的异常如下:

enum TExcType
    {
    EExcGeneral=0, ///    EExcIntegerDivideByZero=1, ///     EExcSingleStep=2, ///     EExcBreakPoint=3, ///    EExcIntegerOverflow=4, ///     EExcBoundsCheck=5, ///     EExcInvalidOpCode=6, ///     EExcDoubleFault=7, ///    EExcStackFault=8, ///     EExcAccessViolation=9, ///     EExcPrivInstruction=10, ///     EExcAlignment=11, ///     EExcPageFault=12, ///     EExcFloatDenormal=13, ///     EExcFloatDivideByZero=14, ///     EExcFloatInexactResult=15, ///     EExcFloatInvalidOperation=16, ///     EExcFloatOverflow=17, ///     EExcFloatStackCheck=18, ///     EExcFloatUnderflow=19, ///     EExcAbort=20, ///     EExcKill=21, ///     EExcUserInterrupt=22, ///     EExcDataAbort=23, ///     EExcCodeAbort=24, ///     EExcMaxNumber=25, ///     EExcInvalidVector=26, ///     };

 

Symbian的TRAP/Leave机制是利用try/catch实现的简易的异常处理机制,基本的逻辑就是保存资源指针,异常的时候释放,并且产生可以被抓住的XLeaveException。为什么不直接使用try/catch?或许是因为早期时候Symbian并不支持C++ try/catch,毕竟Symbian出来的时候C++还没有标准化呢。关于Symbian下try/catch的实现,那又是一部分新的内容了,如果你有兴趣详解,希望能够分享。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值