#define END_EXCEPT_PROTECT() \
} \
{ \
asm volatile("3:\n":"memory"); \
_except_unregister_handler(u); \
}
第一个宏用于注册异常处理程序,首先取得异常处理程序的入口地址(第4行:asm volatile("jmp 1f\n");),标号1定义在第二个宏中,其后就是异常处理程序,第二个宏的第5行(asm volatile("1:\t call 2b\n");)调用第一个宏里的标号2,然后再弹出堆栈第一个双字就得到了异常处理程序入口地址,将异常处理程序入口和当前堆栈指针入栈作为参数调用__except_register_handler()函数注册,注意__except_register_handler函数一定要指定asmlinkage前缀以指明参数用堆栈传递,不然编译器肯定会用寄存器传递参数。注册完之后,就进入到危险代码区了,然后在危险代码结束的时候,就来到第二个宏的第一句:asm volatile("jmp 3f\n");跳转到收尾部分,取消当前注册的异常处理程序。
这里需要注意的三个宏之间多余出来的那两组花括号:这两组花括号绝对不能省略,它们会把危险代码和异常处理程序标记为函数中相对独立的部分,以免编译器在优化时将这两部分的代码和其他部分优化成一个整体――直接后果就是堆栈混乱。
4 注意事项
写到这里,Lnux下的SEH异常处理实现基本上完成了。但是,还有一个相当重要的地方需要注意。那就是C++的类及其他类似需要堆栈展开的应用。这类代码在编译时会修改堆栈指针,也会引起堆栈混乱,所以不能与注册异常处理程序的代码在同一个函数体内(可以将其放到子函数里)。在Windows下如果代码中有类似情况,编译器会报告Cannot use __try in functions that require object unwinding错误。但在本文讨论的范围明显不可能包括修改编译器,因此,只能让使用者在编写代码时注意不要避免这种情况。
5 结语
开放是Linux操作系统最大的优点,针对Linux的Bug或是在某种应用上的不足,大家都可以提出自己的解决方案,然后最优的方案会被整合到下一个新版本的内核中。而在研究操作系统的过程中,得到好处的绝对不仅仅是操作系统本身。对于研究者,可以提高自己的知识水平;对于应用者,可以得到更适合的平台。在这样一个双赢的局面下,将会有更多更好的功能被增加到Linux操作系统中,让用户和开发者们使用起来更方便,更完善。
参考文献
[1] Jeffrey Richter. Windows 95 Windows NT 3.5高级编程技术[M].第一版. 北京:清华大学出版社1996. 杨季文等.80X86汇编语言程序设计教程[M]. 北京:清华大学出版社1999.
|