《软件调试》导读之操作系统篇
在今天的计算机架构中,操作系统是整个系统的统治者,它指挥着系统中的软硬件。如果拿人类社会来类比,那么操作系统好比是国家机器,应用软件是公民,操作系统的各个执行体是国家机构。从这个角度来看,操作系统与应用软件之间是统治与被统治的关系。
对于最终用户来说,他们需要的是应用软件,大多用户说不清什么是操作系统,只知道没有它不行。用户之所以肯掏出钱买操作系统是因为有了它才能跑自己需要的应用软件。从这个角度来说,应用软件是操作系统从用户那里拿到钱的资本,应用软件是前台唱戏的主角,操作系统是藏在后面的支持者。
概而言之,应用软件与操作系统之间既有统治关系,又有共生关系,应用软件对操作系统来说可谓是“船可载舟,亦能覆舟”。理解了这层意义之后,对于一个操作系统来说,它存活的关键就是要能运行尽可能多的应用软件,而且这些软件最好是用户离不开的,最好是在其它操作系统上无法运行的,最好是不断更新的,最好是高质量的,而且最好这样的软件会层出不穷、源源不断的涌出来.......
或者说,对一个操作系统来说,光把自己的代码弄好还远远不够,还要有能力跑缤纷多彩的应用软件,有魄力为应用软件搭建一个宽广伟岸的运行平台,有魅力吸引软件开发商(ISV)前仆后继的为其开发应用软件。深刻理解这一点不容易,做到这一点就更不容易了。
要有高质量的应用软件不容易,为了做到这一点,操作系统需要提供基础设施来支持。有点像很多好的公司都为员工准备健身房一样,操作系统也要给应用软件建设好磨练筋骨的地方,让它们身强体壮,身手敏捷,能适应各种复杂甚至恶劣的运行环境。
当然对于不守纪律的人,也要有纪律来惩罚,吊起来上刑(图中右侧那个)。对于生病了人,应该有医生帮助它治疗。
让ISV愿意在一个系统上做软件开发不是件容易的事,吸引一个ISV可能很简单,但是吸引成千上万的ISV就不容易了。软件开发不是请客吃饭,一个软件开发团队的日常开销不是小数目。还活着ISV大多懂得不能在螺蛳壳里做道场的道理。就像选择开发区做投资要考察它的基础设施一样,要在一个系统上做开发,也应该衡量下这个系统的基础设施怎么样。对于开发区来说,交通、水、电、煤气、网络等等都是头等重要的基础设施。而对一个操作系统来说,API、开发工具和调试设施可谓是直接影响软件开发效率的关键基础设施。
《软件调试》将操作系统的调试设施分为如下几类:
支持调试器的系统设施:因为调试器是软件调试的核心工具,所以支持调试器是操作系统支持调试的首要任务。在DOS这样的单任务系统中,调试器可以直接使用硬件中的调试设施,因此基本不需要操作系统的支持,但是在一个多任务的操作系统中,调试器可能也同时运行很多个,这就要求操作系统必须来统一管理和协调调试资源。另外,多任务环境下的诸多保护机制使得让操作系统来实现必要的“调试器功能”是最合适的,比如收集和分发调试事件。从运行模式角度来看,多任务环境下的软件有的运行在搞特权的内核模式下,有的运行在低特权的用户模式下。调试这两种不同模式下的软件有着很多不同,因此通常使用不同的调试器,即内核态调试器(第18章)和用户态调试器(第9章和第10章)。
异常处理:处理异常是软件开发中一个老生常谈的话题。也是很多程序员觉得难以理解和棘手的问题。操作系统能不能减轻程序员这方面的负担呢?如果能又是如何做的呢?在这个问题上,不同操作系统的做法有挺大的不同。举例来说,同样是C++语言规范中的try{}catch()结构,在某些系统下就可以捕捉到CPU产生的异常(有时称异步异常),而在某些系统上就不能捕捉。《软件调试》的第11章详细介绍了Windows操作系统中管理和分发异常的方法。如果应用软件自己没有处理异常又怎么样呢?第12章介绍了未处理异常的处置方法。很多人对Windows下异常处理机制的一个困惑是搞不清楚所谓的第一轮(First Chance)和第二轮(Last Chance)。看过上面两章后,可以把这个问题彻底搞清楚。
错误通知机制:指实时的向用户通报错误信息(第13章),通过对话框、声音、闪动窗口等。
错误报告机制:软件是要给客户用的,而且发布到客户手里的软件也会出问题。从BUG的成本曲线(《软件调试》图1-9,P23)来看,解决发布后的BUG的成本是最高的。如何降低这个成本呢?普遍认可的一种方法就是让位于客户那里的软件为自己产生一份“生病”报告,整理,然后借助互联网发送出来(第14章)。
错误记录机制:指永久记录软件的运行过程,特别是遇到异常和错误时的情况(第15章)。
事件追踪机制:错误记录机制通常不适合频繁的输出,如果频繁输出那么不仅会明显影响系统的性能,而且可能导致记录文件很大,难以检索有用的信息。而事件追踪机制就是针对这一需求而设计的,因为是使用专门的内存缓冲区,而且具有动态开启机制,所以它能够承受频繁的信息输出,而且开销不大(第16章)。
验证机制:根据BUG的成本曲线,越早发现问题越好,但是做到早发现问题并不容易。一种方法就是一个更严格的标准进行测试,让被测试软件在更苛刻的条件下运行,故意为其设置障碍来考验它。如何实施这些考验呢?操作系统的验证机制就是满足这一需要的(第19章)。
硬件错误管理机制:有些严重的崩溃和挂住是与硬件有关的,但是却找不到进一步的信息来定位到根源,以便排除或者在以后的产品中改进。PCIe总线标准中制定了报告错误的通用机制,CPU中也有机器检查设施(第6章),但是这些硬件设施是需要软件,特别是系统软件的配合才能发挥作用的(第17章)。
打印调试信息:打印调试信息(Output debug info或者Print)是一种简单易用的辅助调试方法。这种方法的不足就是效率比较低,不仅运行效率低,而且增加和减少需要重新编译,时间成本很高(10.7节)。
崩溃和转储机制:转储是最古老的调试方法之一,简单说就是把内存数据“拍张照片”保存下来。在Jack Dennis为《软件调试》写的《历史回眸》短文中就提到了这种方法,当年是先使用一个工具程序将内存中的数据显示到CRT上,然后用照相机拍下CRT上的内容,最后再使用胶卷阅读器来阅读冲洗出来的胶片。这可真是给内存“拍照”(12.9节和13.3节)。
为了让读者对以上调试设施有一个全面的理解,《软件调试》使用了三分之一的篇幅进行介绍,分12章,总页数达376页之多。这样的篇幅也使这一篇成为全书六篇中最长的一篇。为什么花这么大篇幅呢?
1)第9、10、18章分别介绍的是用户态和内核态调试模型,是理解调试器的基础,因此理当不惜笔墨。这三章分别是34页、46页和52页,加起来为132页,占这一篇的三分之一。
2)第11章和12章介绍异常分发和未处理异常,这些内容不仅与调试器有着密切关系,而且是本书的“异常”主题的核心内容。这两章一共有86页。
3)其它7章肩负着介绍上面提到的其它辅助调试设施的责任,一共用了158页,平均每章22页。介绍这些内容一则是对调试有全面的理解,在软件工程中使用这些设施,另外也可以帮助大家更好的理解操作系统,提高综合调试能力。
另一个问题是以什么方式来介绍这些调试设施。是以一个具体的操作系统为例详细介绍,还是以抽象理论为主,偶尔举例。《软件调试》采用的是前一种方法,而且选择的是Windows。为什么选择Windows呢?主要原因是它的广泛性。为什么没有选择LINUX呢?主要原因是它在调试方面还有很多足。很多资深的LINUX也不讳言LINUX在调试方面的缺欠,直至今天,LINUX下最普遍使用的调试方法依然是PRINT。事实上,选择LINUX来写,会好写很多,毕竟有现成的源代码可以读。
尽管书中多次明显提到选择Windows只是将其作为一个操作系统实例来介绍操作系统对软件调试的支持,但是还有一些人无法理解。不过,在买了书的读者中,还是理解的人多,其中也有一些专业做LINUX的工程师或者讲师。
上面介绍了这一篇的写作目的、主要内容和结构布局,下面再推荐一下阅读的顺序。对于初级读者,建议先泛读第9、10两章之外的所有章节,然后仔细读第11章和12章,再后则读第13章中的《硬错误和蓝屏》。对于有一定调试基础的中级读者,可以根据自己的兴趣仔细读感兴趣的章节。对于高级读者,可以先读第9、10和18章,然后浏览其它章节,遇到感兴趣的仔细阅读。
[12月31晨略作修改,增加插图]