首页 >> szping11>> 思考快与慢资源CSDN下载
img
数据库诊断案例性能优化实践(一线Oracle DBA工作...

CruiseYoung提供的带有详细书签的电子书籍目录 http://blog.csdn.net/fksec/article/details/7888251 Oracle DBA手记:数据库诊断案例与性能优化实践(一线Oracle DBA工作思考的心得,盖国强亲自策划) 基本信息 作者: 《Oracle DBA手记》编委会    出版社:电子工业出版社 ISBN:9787121101397 上架时间:2010-1-19 出版日期:2010 年1月 开本:16开 页码:407 版次:1-1 编辑推荐    Oracle ACE总监、Oracle畅销书作者盖国强亲自策划    汇集五位一线Oracle DBA工作思考的心得    从不同视角展示数据库诊断与优化的思路 内容简介    本书由多位工作在数据库维护一线的工程师合著而成,包含了精心挑选的数据库诊断案例与性能优化实践经验,内容涉及oracle典型错误的分析和诊断,各种sql优化方法(包括调整索引,处理表碎片,优化分页查询,改善执行计划等),以及优化系统性能的经验。    作者不仅强调案例的实用性和可操作性,更着重再现解决问题的过程和思路并总结经验教训,希望将多年积累的工作方法,以及对dba的职业发展的感悟展现出来,供广大oracle dba借鉴参考。 作译者    盖国强 网名Eygle Oracle ACE总监,恩墨科技创始人,ITPUB论坛超级版主,远程DBA服务的倡导者和实践者,致力于以技术服务客户。著有《深入解析Oracle》、《循序渐进Oracle》、《深入浅出Oracle》等书;从2010年开始,致力于《Oracle DBA手记》的撰写与编辑工作,并与张乐奕共同创立了ACOUG用户组,在国内推进公益自由的Oracle技术交流活动。    熊军(网名老熊),ITPUB论坛Oracle专题深入讨论版版主,现从事Oracle第三方技术支持工作,擅长Oracle数据库故障诊断处理和性能优化。个人网站:http://www.laoxiong.net.    杨廷琨(网名Yangtingkun),现任海虹医药电子商务有限公司首席DBA, ITPUB论坛Oracle数据库管理版版主。2004年曾参与编写《Oracle数据库性能优化》一书,2007年被Oracle公司授予Oracle ACE称号,喜欢研究Oracle相关的技术问题,他的技术博客上积累了1500多篇Oracle相关的原创技术文章。个人技术博客:http://yangtingkun.itpub.net.    段林仲(网名zergduan),ITPUB论坛Oracle专题深入讨论版版主,现任职于北京某合资手机制造公司生产系统DBA,对Oracle数据库技术有浓厚的兴趣。喜欢与志同道合的朋友交流技术心得。    邹德平(网名半瓶/banping),Oracle 10g OCP,现在某大型上市公司担任Oracle DBA,乐于在实践中不断努力,学习成长。个人网站:http://www.banping.com . 目录 封面 -16 封底 412 扉页 -15 版权 -14 推荐序:2.0时代的DBA专家圈子 -13 代序:分享知识 收货快乐 -11 目录 -5 第1篇 DBA工作手记 1 Eygle的DBA工作手记(作者:盖国强) 3 DBA 2.0的时代 4 DBA日常工作职责——我对DBA的7点建议 10 DBA最重要的素质有哪些 11 DBA职业生涯之误删除篇 12 DBA警世录——有些习惯DBA需要养成 13 RAC环境下故障处理一则 14 SQL_TRACE跟踪与诊断 16 临时表空间组导致递归SQL高度解析案例 19 使用闪回查询恢复误删除的数据 21 使用ErrorStack进行错误跟踪及诊断 24 断电故障导致ASM DiskGroup故障及恢复案例 26 共享池的改进与ORA-04031的变化 27 共享内存无法正常释放的处理 34 LOG_BUFFER设置与变迁 35 LOGMNR简单而强大的工具 36 从数据字典中获得更多知识 38 Yangtingkun的DBA工作手记(作者:杨廷琨) 45 利用SQL解释一个魔术 46 ORA-600(17069)错误的解决过程 47 V$SQL视图显示结果异常的诊断 51 存储过程ORA-4068之错误解析 54 一次网络连接错误的诊断 57 浅谈JOB和DATABASE LINK的一个特点 58 一次ORA-01041错误诊断 60 一个ORA-604错误的分析 61 ORA-7445(kdodpm)错误 62 函数索引产生隐藏列 65 用SQL解决一道有趣的题 67 老熊的DBA手记(作者:熊军) 71 TNS_ADMIN和OEM引起的血案 72 木马防护软件引起的DB Link故障 74 坏块与数据恢复 76 IP地址冲突引发的ORA-12541错误 82 ORA-8103错误的处理 83 半瓶的DBA工作手记(作者:邹德平) 87 SQL执行计划改变导致查询变慢 88 大数据量操作导致Data Guard延迟 91 Oracle重做日志切换频繁导致的性能问题 92 一次ORA-01438错误的解决办法 93 使用LogMiner查找日志信息 95 通过合并碎片来优化Oralce的存储结构 97 RAC环境下GES报警情况的处理 99 第2篇 诊断案例篇 101 ASM案例分析与诊断(作者:杨廷琨) 103 ASM实例连接之ORA-1012错误分析 104 ASM空间扩展故障解决 111 ASM创建表空间之ORA-569错误解决 115 一次监听故障的诊断与处理(作者:熊军) 121 问题诊断分析 122 监听的工作原理 123 问题的解决 127 总结 129 ORA系列错误与诊断(作者:杨廷琨) 131 解决ORA-600(16164)错误的过程 132 一次ORA-942错误的跟踪 139 删除表空间出现ORA-22868错误 150 一次ORA-1461错误处理过程 160 ORA-01200错误裸设备恢复(作者:盖国强) 169 问题的发现 170 问题的分析 171 控制文件与数据文件头 174 物理设备确认 176 解决方案的确定 176 恢复执行 177 使用rman管理裸设备文件 181 Oracle放弃对于裸设备的支持 184 Oracle数据库版本的含义 185 Oracle数据库无响应故障的处理(作者:熊军) 189 无响应故障现象分析 190 无响应故障成因分析 190 无响应故障处理流程 191 怎样避免数据库出现无响应故障 193 一个实际的案例处理过程 193 案例的总结分析 198 总结 201 RAC环境诊断案例一则(作者:杨廷琨) 203 第3篇 SQL调优篇 213 合理利用索引解决性能问题(作者:熊军) 215 案例一:利用复合索引解决性能问题 216 B Tree索引的结构及特点 218 Oracle如何扫描索引 223 案例二:巧用索引降序扫描解决性能问题 224 通过索引访问数据时的成本计算 225 案例三:降低clustering factor,解决性能问题 227 总结 228 SQL优化与调整实践(作者:杨廷琨) 229 HINT如何跨越视图生效 230 SQL优化引出的问题 236 SQL语句中常量的处理 244 一次更新操作的优化 247 索引访问与数据读取(作者:盖国强) 253 高DB Time使用之SQL发现 254 执行计划获取及索引优化 255 INDEX FAST FULL SCAN与排序 256 降序INDEX FULL SCAN消除排序的优化 260 INDEX FAST FULL SCAN的数据访问 262 INDEX FULL SCAN的数据访问 268 键值顺序与索引访问的性能 269 使用基于函数的索引提升查询性能 274 SQL优化之Everything is possible(作者:杨廷琨) 281 UNION效率比UNION ALL效率高 282 恒等查询条件对查询的影响 284 两层GROUP BY的效率反而比一层GROUP BY高 287 增加DISTINCT后查询效率反而提高 290 增加索引改变执行计划 292 第4篇 性能优化篇 295 执行计划与统计信息案例 297 CBO使用CHECK的一个Bug 298 查询正常作为条件报错的问题 303 JOB调用DBMS_STATS包出错的诊断 308 长时间LATCH FREE等待——记一次系统异常的诊断过程 310 一次ORA-4030问题诊断及解决 317 Oracle数据库性能与统计信息(作者:熊军) 329 一个真实的案例 330 统计信息的重要性 334 统计信息的相关知识 335 面临的问题与挑战 337 关于统计信息的常见误区 339 总结 341 聚簇因子、柱状图与执行计划(作者:段林仲) 343 CLUSTERING_FACTOR对于执行计划的影响 344 CURSOR_SHARING与SQL的执行计划 348 Histograms对于执行计划的影响 352 表碎片及分页查询优化(作者:盖国强) 363 性能分析的起点 364 AWR与OSWatcher诊断案例 367 Statspack之案例分析 370 数据表之统计数据分析 371 碎片的消除与性能优化 374 Cache与Nocache对于性能的影响 379 ERP系统中Cache表案例分析 380 对于分页查询的优化 384 DBMS_PROFILER包用于诊断与优化 387 总结 390 一次排序的调整与优化(作者:段林仲) 391 Statspack之问题发现 392 SQL排序之问题定位 393 SQL优化与问题解决 394 优化后性能比较 400 参考文献 403 索引 405 编辑手记 409 设计手记:属于DBA的记事簿 411 序言   推荐序   2.0时代的DBA专家圈子   看了Eygle发来的《Oracle DBA手记》的手稿,感到这本书的写作思路很有些新意。随着数据库技术自动化程度的提高,DBA工作价值开始扩展到更大的层面,因此共享一些探路者的心得变得尤为可贵。暴露其思维过程,是Web2.0时代技术圈子的一大特点。在以前的某些时代,有些工程师们仅仅把技术作为赚钱的手艺,很多操作技巧都是密不外传的,总怕“教会了徒弟,饿死了师父”。本书的编排是对此种落后思维的一种否定。具体表现为三大特色:      编辑与遴选:搜索时代,我们更需要向导   在搜索无处不在的年代,某种知识入门的难度已经不在于知识的获取,而在于知识的筛选。这个时候,一个过来人的理解往往是最好参考。如果说科学家们的乐趣来自对造物主的发现,那么顶级工程师们的乐趣则来自于理解另外一些人的聪明智慧。归根到底,整个软件行业都建立在基于数学基础的人类思维的设计,从根子上说,软件技术本身都是人聪明智慧的结晶。另一方面,由于数据库技术应用环境的复杂性,经验也是不可缺少的。对于一个入门DBA 来说,有两样东西对进阶比较关键:一是理解设计者是怎么想的,二是理解使用者环境的复杂性。两者都需要经验的积累,需要勤奋。      集体创作:小团体的自组织带来多维度的启发   在技术学习上,每个人的根器不同,因此,启发的方式也不同。几位DBA高手把讲自己日常的体会筛选成集,一方面基于术业专攻的原则,另一方面对读者的启发带来了维度的增加。可能某一读者恰好喜欢杨廷琨的理解方式和文字表达,那他可以一段时间内多花时间去看他那1500多篇博客的积累。可能另一些人因为欣赏Eygle的技术能力与为人方式,因此虚拟地拜师学艺,可以少走弯路。师傅领入门,修行在个人,武侠小说里拜师学艺的传奇故事,现在可以在网络上更大范围地复制开来,只是读者现在有了更多选择。此书的集体创作带来更多维度,有助程度不同的读者增加理解。      分享心得:我是怎么想的   古代的知识分子,书房是不让别人进的,怕的是被别人看去了心思。Web 2.0的精神是反其道而行之。如果技术知识的发展速度远远超过个人的学习能力,形成一种共享文化,对于少走弯路是极其必要的。而其中的关键,就是暴露思维过程,告诉别人自己在特定问题上的想法。这些想法慢慢会汇聚成一种思维方法,这些思维方法,是比具体某个版本的操作技巧更为长远的。本书第一编的各位作者的手记,恰是体现了这么一种精神。      本书发行之时,恰逢Oracle 11gR2 发布,新版本的设计已经开始面向数据中心,增加了更多的技术特性,DBA们的眼光,也不再局限于数据库本身,而应该放眼更大的数据中心,建设这个更大的主题。因此,更希望本书对于夯实DBA们的日常工作大有裨益,可以有机会抬头看看更长远的方向。      刘 松   甲骨文大中华区产品战略总监   2009年10月       .     代序   代 序   分享知识 收获快乐   2009年8月,我们终于完成了这本书的组稿、编辑工作,可以将它呈现给大家,在这里我想和大家分享一下为什么会有这本书,以及这本书的来龙去脉。   一、选题及出发   在2008年底,我修订了《深入浅出Oracle》一书,并更名为《深入解析Oracle》,那本书的副标题是“DBA入门、进阶与诊断案例”。多年来,很多读者提出建议:可否尽量扩展诊断案例的部分,甚至独立出版一本案例集。而要写作这样一本书,仅仅靠我个人的知识和力量,显然十分有限,所以我想到了联合更多的作者,共同来完成这样一本书。   我首先游说老杨(杨廷琨),他几年来在自己的博客上积累了1500多篇高质量的原创技术文章,而经由他自己整理提炼成文,显然会精彩纷呈。感谢老杨,他愉快地答应了我的合著要求,并全力开始了自己的写作。   在网络上,我经常能够看到大量精彩的诊断案例与故障处理过程,又常常遗憾这些文字被互联网的海量信息所淹没,于是我进一步萌生了对这些文字进行“编辑”的想法,想通过自己的阅读、学习和选择,让更多的作者能将自己的经验分享出来,想想一本全部来自第一线DBA的经验集合,将会是多么宝贵的财富啊!   于是我通过自己的网站和邮件发出约稿邀请,得到了很多朋友的积极响应,最后汇集了本书的五位作者,而我希望这只是一个合作的开始,期待以后会有更多的作者将自己的经验总结分享出来。   这本书以诊断案例和性能优化实践为主,自然也就涉及到了不同版本、不同环境的数据库,也许有些数据库版本会逐渐退出历史舞台(现在Oracle Database 11gR2也已经发布),但是经验、思路和方法会具有长久的参考价值,如果这本书中的某一章节或某一部分内容对您有所帮助、有所借鉴,那我们就会无比欣喜,这本书也就有了其存在的意义。   真正全力开始这本书的写作与组稿,是在2009年3月结束一个客户为期8天的定制培训之后,客户的DBA们强烈建议我组织一本DBA工作实践的书,不一定要多深奥,但要可以给想要或者刚刚进入这个行业的DBA们一些参考,让他们了解真实的DBA工作。这些建议和我之前的一些想法不谋而合,于是我加快了推进这本书的写作。   感谢这些朋友的建议推动了我,在这次培训中的另外一个收获就是,结识了本书的作者之一。   二、本书的内容   综合所有的稿件,我将这本书分成四篇,分别为:   1. DBA工作手记   这一篇收录了四章的内容,分别为盖国强、杨廷琨、熊军,以及邹德平的DBA工作手记。本部分概要记录了日常工作中一些经验和技巧,也包含一些对于DBA工作的探讨。记录日常、记录工作、记录思考,是这一篇的主题。   2. 诊断案例篇   这一篇包含了六章内容,详解地记录了DBA工作中遇到的一些案例及思考、处理过程。其中杨廷琨撰写了第5、7、10章,熊军撰写了第6、9章,盖国强撰写了第8章。   3. SQL调优篇   这一部分包含了同SQL优化相关的实践,包含大量关于使用索引进行SQL优化的实际案例。其中熊军撰写了第11章,杨廷琨撰写了第12、14章,盖国强撰写了第13章。   4. 性能优化篇   包含了一些CBO模式下的优化案例,以及存储与分页查询的优化过程。Oracle的CBO是数据库中最为复杂的部分,通过一些实际的案例分析,我们希望能够为大家展现解决问题的思路和规律。在这一部分,杨廷琨撰写了第15章,熊军撰写了第16章,段林仲撰写了第17、19章,盖国强撰写了第18章。   显然,这是我们主观的划分,各篇内容也不可能包括某方面知识的全部细节,我们只是记录了工作涉及的一些案例和实践,如果其中能与读者的工作有某种契合,那就是我们工作的巨大收获。   三、作者们的话   在本书完稿之际,我邀请几位作者,各自写下一段话,作为本书的导引:   积累与分享——老杨如是说   8年的DBA经验告诉我,学习Oracle并不是一件轻松的事情。对Oracle有一定了解的人都会有同样的感受,那就是Oracle数据库包含的内容博大精深。对于一个初学者而言要学习的东西实在太多了,比如数据库的基本概念、数据库的管理操作、SQL语法、PL/SQL语句、数据库备份恢复、性能优化,等等。但即使有了这些还是不够。要成为一名合格的DBA,对Oracle的一些高级特性也应有一定的了解,比如DATA GUARD、RAC、STREAM,等等。对于Oracle的选件功能也多少应该知道一些,比如全文检索、XDB、VAULT,等等。   上面列举的还仅仅只是Oracle数据库方面的知识。如果你是开发DBA,那么对于开发相关的知识、对于程序设计和数据库结构设计,甚至对中间件技术等都要有所涉猎。如果你是管理DBA,那么还必须具备操作系统方面的知识,另外对存储的划分、网络的规划等知识也要掌握。   要学习的东西确实很多,但这还远远不够。上面所说的还只是知识范围,作为一名DBA还要不断学习各种新知识和新特性。要知道Oracle每一两年就会推出一个修订版,每三到五年会推出一个换代大版本。对于DBA来说不仅要学习大量的新知识,而且目前所掌握的有些知识和经验在以后的版本中也可能不再适用。对于DBA来说,学习应该是一个不间断的过程。如果你停滞不前就意味着你OUT了。事实上,就在我写下这篇文章的同时,Oracle数据库最新的11.2版已经可以下载了,对于DBA来说,新的学习目标又出现了。   即使你拥有了以上这些知识也并不意味着你已经成为一名高级DBA。对于高级DBA来说,知识只是必须具备条件的一部分,积累大量的分析、解决问题的经验则是另一部分。而这部分是无法通过阅读文档获取的。事实上如果靠个人解决自己碰到的问题来积累经验是需要相当长的积累时间的。何况一般初学者很难在平时工作中得到独立解决问题的机会,有时即使出现了这种机会,但由于知识积累的不足也会导致在遇到问题时无从下手。   知识确实很重要,掌握的知识越多,对细节的了解越清晰,解决问题时就越能得心应手、游刃有余。不过有时解决问题的思路更重要,前面提到了Oracle数据库包含了太多的内容,而当你吸取了这些知识后遇到问题时,如何利用这些知识来分析问题、解决问题或避免问题的出现,这就不是知识本身能解决的了。   一个困扰初学者很长时间的问题,一名高级DBA可能几分钟就可以搞定,因为很可能这个问题高级DBA在此前已经碰到过了。即使同样是从来没有遇到的新问题,当高级DBA已经解决完问题时,初学者可能还没有找到解决问题的思路,而这就是经验上的差距。   简单地说,知识告诉你一个特性是什么,以及如何使用这个特性,它的优缺点是什么;而经验告诉你这个特性何时该用,何时不该使用,在何种情况下使用能够发挥特性的优点,在何种情况下会导致这个特性的缺点。知识与经验二者同样重要,当这两个方面都积累到一定的程度时,你自然也就会成为一名高级DBA了。   说了这么多,恐怕要把初学者吓坏了,成为DBA要掌握那么多的知识,还要积累这么多的经验,听上去似乎是mission impossible。但是要知道罗马也不是一天建成的,初学者到高级DBA之路虽然漫长,只要坚定信心坚持下去,这条路也并不困难。Eygle的名言放在这里再合适不过了:   兴趣+勤奋+坚持+方法≈成功。   虽然获取大量的知识是一个漫长的过程,好在信息时代有足够方便的途径来获取知识。Oracle的官方文档、各种官方、非官方的网站、论坛以及个人Blog都是获取知识的来源,除此之外,还有很多已经出版的书籍来帮助大家解决知识获取的问题。   经验的获取则要困难一些。无论是文档、网站,还是目前出版的大部分书籍都是偏重知识的传授部分,偏重经验介绍的寥寥无几,而经验的共享确实也要比传授知识更困难一些。   正是出于以上这个原因,才促成了这样一本书的写作与出版。当Eygle联系我打算出这样一本书时,我马上就答应了。将DBA解决问题的案例集合成一本书,在国内应该是比较少见的。我们的本意是分享自己解决问题的经验,期望能给初学者带来一定的启发,起到抛砖引玉的作用。希望这种尝试能够获得预期的效果,同时希望有更多的人参与到经验分享的过程中来,也希望这种方式确实可以帮助初学者更快完成经验的积累。   感谢Eygle,是他的努力促成了这本书。本书的所有作者都是Eygle联系并约稿的,除此以外,稿件的审校和整理以及出版事宜基本上也是他一个人完成的,没有他辛苦的劳动就没有这本书。最后要感谢我的妻子,她的工作和IT不沾边,对Oracle技术更是毫无了解,但她在我写作过程中帮我审校了所有拼写上和语法上的错误并通顺了文章的语句。如果这本书中我的文章还算通顺,主要归功于我的妻子。   兴趣+勤奋,理论+实践——老熊的学习经验谈   可以用8个字来概括我的学习经验,那就是“兴趣+勤奋,理论+实践”。这里我将这几年学习Oracle的心得写出来,希望对打算学习Oracle或者准备深入学习Oracle但暂时又不得其门而入的朋友们有所帮助。   兴趣:其实我并不是科班出身,甚至在参加工作的前两年,所从事的工作还与IT没有任何关系。只是我对计算机技术有着非常浓厚的兴趣,到后来兴趣主要集中在Oracle数据库上,这样最终才转行到了Oracle数据库上。这几年来我不停地学习Oracle,兴趣是最重要的原因。当然,兴趣不是天生就有的,而是在接触事物的过程中,慢慢培养出来的。有了兴趣,就会主动去了解、掌握这个事物。也就是说,只要我们有了兴趣,就会非常主动地去学习Oracle数据库。如果不主动,很难学好一门技术。因此,培养对Oracle数据库的兴趣,是学好Oracle数据库的关键。   勤奋:很显然这个世界上天才很少,作为一个普通人来说,如果要精通一门技术,做好一件事情,勤奋与坚持是必不可少的。对于学习Oracle数据库来说,如能数年如一日地坚持下来,那么深入掌握Oracle也不是什么难事。对于我个人来说,坚持学习Oracle,每天并不需要花很多的时间,只需要每天掌握1个小小的知识点,会1个简单的操作就可以了。   理论:我个人学习Oracle数据库时,喜欢从理论或者说是基础知识入手,尽量先掌握系统的概念、架构、原理等。掌握了理论基础,才能举一反三,才能融会贯通。如果没有理论基础,那么在大多数时候只能凭经验来进行Oracle数据库的操作。如果要学好Oracle,扎实的基础理论知识是必不可少的。我从学习Oracle以来,很多官方文档,如《Oracle Database Concepts》、《Oracle Database Performance Tuning Guide》、《Oracle Database Administrator’s Guide》等都看了个遍,有的文档甚至看了数遍。不过官方文档过于枯燥,缺乏必要的示例和案例,非官方出版的Oracle书籍就是很好的补充材料。   实践:如果只有理论,而没有操作实践,那就很容易陷入“眼高手低”的境地。学习Oracle时,需要掌握各项功能的使用,具体到SQL的使用,各个性能视图的使用,数据字典的使用等。如果没有这些,在进行操作时,会有无从下手的感觉。比如在进行紧急性能问题处理时,还要去参考很多的资料,那显然是临时抱佛脚。读再多的书,看再多的文档,如果没有进行过实际操作,那永远不会有多大的提高。不管是开发、性能优化、故障处理还是日常维护等,最终都要落地到实践操作上。理论与实践是相辅相成的,理论需要实践来巩固和验证,实践操作需要理论来指导。   基本的操作熟练后,需要进一步提高,也就是在做对事情的基础上,再考虑把事情做得更好。比如针对某一项操作需要,怎样才能最大限度地减少对业务的影响,最大限度地提高效率等。   除了前面提到的“兴趣加勤奋,理论加实践”,学习过程中也应该注意学习方法,这样才能达到事半功倍的效果。在学习和操作Oracle数据库的过程中,把学到的东西整理下来,形成文字,比如写BLOG等。写的过程就是梳理知识和思考的一个过程,这种方法对我个人来说作用非常大。   当然经常上论坛参与讨论或者阅读别人博客上的文章,也是提高Oracle水平,积累案例经验,拓宽视野的好方法。   从兴趣到爱好——段林仲的话   学习Oracle的最大动力是兴趣,只要有兴趣,你会发现一切都是那么让人兴奋!也许你会在知识积累的过程中感到枯燥,请一定要坚持,终有一天你会豁然开朗,发现你所学到的东西是那么的有意思。   不要认为DBA的工作很程序化,很乏味;不要放过你身边的点点滴滴,就像Eygle说的:“如果你每天搞定一个数据库中的Top SQL,坚持一个月你会发现你很有收获。”Oracle就是这么神奇的东西,它提供给你方法,提供给你工具,去挖掘、去探索那些神奇的未知的东西。在挖掘、探索的过程中你会发现无限的乐趣,也许有一天你会发现DBA不单单是一种工作,更可以当作一种爱好。   春雨润物,潜移默化——邹德平   虽然使用Oracle已经多年,却越发觉得自己只是才推开Oracle大门的一角,离登堂入室还有很远的距离。但正是对Oracle技术的兴趣,使我能坚持下来并有勇气和大家分享自己的一点经验。   我相信兴趣是最好的老师,只有兴趣才能让一个人去坚持,甚至着迷。当一个人在一件事情上花了很多心思的时候,他的知识就会在不知不觉中提高,如春雨润物,悄然无声。   正如书名“Oracle DBA手记”所言,集的是一线DBA的工作经验总结和心得提炼,也许书中没有体现Oracle的知识框架,但是这些案例的处理过程正是对Oracle各种知识的实际应用,而这种实际生产环境中发生的真实案例对读者来说可能具有更大的价值。   在谈及Oracle数据库学习经验时,多位作者都不约而同地提到了兴趣与勤奋的重要,其实这也是本书所以能够最后完成的重要契机,如果大家都能够在自己感兴趣的领域深耕下去,坚持不懈地积累、总结,那么多年以后一定能够获得让人惊讶的成绩。如果每位DBA朋友都能够总结经验、分享所得,那么数据库的学习就不是一件难事、苦事。   我们非常期待这种合作、分享的模式能够继续下去,有更多的朋友不断参与进来,将自己的所学、所知分享出来,独乐乐不如众乐乐,愿我们都能体味到分享的快乐,更希望这本书只是一个起点!   最后,感谢几位作者们的辛勤劳动与经验分享。由于编者的经验有限,书中难免存在错漏之处,敬请读者朋友们批评指正。    谢谢大家!   Eygle   2009年9月

img
网络常见问题故障1000例

可能该书会有很多人用得上,而本下载频道里已有的下载地址有误且文件不可用,故而再次上传,以供有需要者下载!(共21.53M,分三个下载文件)内容简介 本书从大量的E-mail和BBS中精心筛选了1000个问题,针对网络设计、组建和管理的具体故障,重点介绍了解决问题的策略和排除故障的思路,彻底摒弃了枯燥的理论和简单的操作,着力突出了实用性和可操作性,力求使读者读一反三,触类旁通,从而培养独立思考和处理问题的能力。本书集实用性、思想性、可读性为一体,是一本适合广大计算机编程爱好者的优秀读物。本书适合网络维护工程师、网络工程技术人员、信息系统管理人员,以及所有已经或正准备从事网络管理的网络爱好者。目录 第1章 网络连接常见问题与故障1.1 网络搭建(网络拓扑、网络连接)1.1.1 网络规划1. 物理隔离要建两个网络2. 划分子网隔离子网3. IP与MAC绑定难题4. 实现多VLAN的Internet连接共享5. 延伸至750m6. 延伸至1500m7. 4层楼的拓扑结构8. 网络拓扑问题9. 超过254台计算机怎样在同一个网段10. 教育网多用户共同上网问题11. 限制部门计算机间的访问12. 不能相互访问却要连接共享1.1.2 设备互连13. 10Base-T共享网络连接14. 共享网络传输速率低 15. 不对称交换设备的连接16. 对称交换网络的连接17. 利用三层交换机提高传输效率18. GigaStack菊花链堆迭19. GigaStack星形堆迭20. Catalyst 3750的StackWise堆迭21. Uplink端口使用直通线级联22. Uplink口与相邻端口不能同时使用23. Uplink端口级联24. 普通端口的级联25. 计算机与集线设备的连接26. 什么时候该用直通线?什么时候该用交叉线?27. 重新连接后居然不通28. 局域网连接比Modem还慢29. 家庭网络组网方案30. 更换100Mbps交换机后无法连接至Internet31. 局域网之间使用Modem连接32. 交换机端口能否全部连接计算机1.1.3 连接状态33. 交换机端口指示灯熄灭34. Cisco交换机指示灯35. 判断交换机指示灯状态36. 网卡灯亮却不能上网37. Link灯不停地闪动38. COL指示灯长亮或不断闪烁 39. Collision指示灯闪个不停40. 集线器上与路由器LAN端口连接的灯不亮41. 网卡指示灯及其判断1.2 双机直连42. 被拨叫的主机没有反应43. 远程拨号速率和哪些因素有关44. 利用Modem实现双机直连45. USB连线无法相互访问46. IEEE 1394接口实现双机直连47. Windows XP的串口双机直连48. 相互无法Ping通49. 两台计算机无法实现直连50. 提示“网络电缆没有插好”51. 两台计算机通过3块网卡上网52. 两根网线连接3台计算机及 宽带53. 双机直连LED灯不亮54. 能单独上网却Ping不通对方55. 双机直连不能Ping通1.3 网络链路(综合布线、跳线)56. 140m的距离可否使用超5类屏蔽线57. RJ-45头针脚排列顺序58. 水晶头应压住外层绝缘皮59. 建筑间布线应当选择单模光纤60. 架空式布线应当注意的问题61. 埋入式布线应当注意的问题62. 能否改变双绞线线序63. 实现OA的远程办公64. 避免双绞线遭遇雷击65. 用粗缆还是光缆66. 双绞线的100m极限67. 突破100m极限68. 剪短网线后速度更慢69. 计算机出现短暂的反应迟缓70. Ping服务器时通时不通71. 网速特别慢72. 局域网速度非常慢1.4 网络设备(交换机、路由器、集线器、网卡)1.4.1 设备使用73. UPS的使用和保养74. 电池的使用和保养75. 路由器的选用76. 路由器拨号故障只发生在机关77. 网络经常瘫痪78. 交换机越用越快79. 5口的交换机只能用4口80. 更换交换机后下载速率猛增81. 连接到Hub后死机82. 连接Hub后速率大幅下降83. 用户多时网络速度变慢84. 安装网卡后无法重新启动85. 网卡频繁丢失86. 网卡驱动无法完全卸载87. 无法删除未知设备88. 网卡接口损坏89. 更换网卡后系统不认90. USB网卡无法正确安装91. 网卡MAC地址为FF-FF-FF-FF-FF-FF92. 提示“已有网络适配器使用此地址”93. 经常提示“你的网线没有连接好”94. 只能使用系统自带的驱动95. 更换PCI接口导致故障96. 内置网卡不能上网97. Windows XP上不能同时使用两块网卡98. 网卡工作不正常99. 网卡丢包频繁掉线100. 重复安装的网卡101. 只能发送数据包,而不能接收数据包102. 网卡无法正常安装103. Windows XP下TP-Link网卡安装故障104. 安装网卡找不到RTL8139. SYS文件105. 连接WOL线导致计算机黑屏106. 无法正确安装DFE-530TX网卡107. 无法正确安装PCMCIA网卡108. 移动位置就Ping不通109. 安装网卡后无法正常关机110. 安装网卡并连接后导致网速变慢111. 同一台计算机在家里无法正常使用1.4.2 设备设置112. 主机到本地路由器的以太口不通

img
网络常见问题故障1000例(中)

本书第二部分内容简介 本书从大量的E-mail和BBS中精心筛选了1000个问题,针对网络设计、组建和管理的具体故障,重点介绍了解决问题的策略和排除故障的思路,彻底摒弃了枯燥的理论和简单的操作,着力突出了实用性和可操作性,力求使读者读一反三,触类旁通,从而培养独立思考和处理问题的能力。本书集实用性、思想性、可读性为一体,是一本适合广大计算机编程爱好者的优秀读物。本书适合网络维护工程师、网络工程技术人员、信息系统管理人员,以及所有已经或正准备从事网络管理的网络爱好者。目录 第1章 网络连接常见问题与故障1.1 网络搭建(网络拓扑、网络连接)1.1.1 网络规划1. 物理隔离要建两个网络2. 划分子网隔离子网3. IP与MAC绑定难题4. 实现多VLAN的Internet连接共享5. 延伸至750m6. 延伸至1500m7. 4层楼的拓扑结构8. 网络拓扑问题9. 超过254台计算机怎样在同一个网段10. 教育网多用户共同上网问题11. 限制部门计算机间的访问12. 不能相互访问却要连接共享1.1.2 设备互连13. 10Base-T共享网络连接14. 共享网络传输速率低 15. 不对称交换设备的连接16. 对称交换网络的连接17. 利用三层交换机提高传输效率18. GigaStack菊花链堆迭19. GigaStack星形堆迭20. Catalyst 3750的StackWise堆迭21. Uplink端口使用直通线级联22. Uplink口与相邻端口不能同时使用23. Uplink端口级联24. 普通端口的级联25. 计算机与集线设备的连接26. 什么时候该用直通线?什么时候该用交叉线?27. 重新连接后居然不通28. 局域网连接比Modem还慢29. 家庭网络组网方案30. 更换100Mbps交换机后无法连接至Internet31. 局域网之间使用Modem连接32. 交换机端口能否全部连接计算机1.1.3 连接状态33. 交换机端口指示灯熄灭34. Cisco交换机指示灯35. 判断交换机指示灯状态36. 网卡灯亮却不能上网37. Link灯不停地闪动38. COL指示灯长亮或不断闪烁 39. Collision指示灯闪个不停40. 集线器上与路由器LAN端口连接的灯不亮41. 网卡指示灯及其判断1.2 双机直连42. 被拨叫的主机没有反应43. 远程拨号速率和哪些因素有关44. 利用Modem实现双机直连45. USB连线无法相互访问46. IEEE 1394接口实现双机直连47. Windows XP的串口双机直连48. 相互无法Ping通49. 两台计算机无法实现直连50. 提示“网络电缆没有插好”51. 两台计算机通过3块网卡上网52. 两根网线连接3台计算机及 宽带53. 双机直连LED灯不亮54. 能单独上网却Ping不通对方55. 双机直连不能Ping通1.3 网络链路(综合布线、跳线)56. 140m的距离可否使用超5类屏蔽线57. RJ-45头针脚排列顺序58. 水晶头应压住外层绝缘皮59. 建筑间布线应当选择单模光纤60. 架空式布线应当注意的问题61. 埋入式布线应当注意的问题62. 能否改变双绞线线序63. 实现OA的远程办公64. 避免双绞线遭遇雷击65. 用粗缆还是光缆66. 双绞线的100m极限67. 突破100m极限68. 剪短网线后速度更慢69. 计算机出现短暂的反应迟缓70. Ping服务器时通时不通71. 网速特别慢72. 局域网速度非常慢1.4 网络设备(交换机、路由器、集线器、网卡)1.4.1 设备使用73. UPS的使用和保养74. 电池的使用和保养75. 路由器的选用76. 路由器拨号故障只发生在机关77. 网络经常瘫痪78. 交换机越用越快79. 5口的交换机只能用4口80. 更换交换机后下载速率猛增81. 连接到Hub后死机82. 连接Hub后速率大幅下降83. 用户多时网络速度变慢84. 安装网卡后无法重新启动85. 网卡频繁丢失86. 网卡驱动无法完全卸载87. 无法删除未知设备88. 网卡接口损坏89. 更换网卡后系统不认90. USB网卡无法正确安装91. 网卡MAC地址为FF-FF-FF-FF-FF-FF92. 提示“已有网络适配器使用此地址”93. 经常提示“你的网线没有连接好”94. 只能使用系统自带的驱动95. 更换PCI接口导致故障96. 内置网卡不能上网97. Windows XP上不能同时使用两块网卡98. 网卡工作不正常99. 网卡丢包频繁掉线100. 重复安装的网卡101. 只能发送数据包,而不能接收数据包102. 网卡无法正常安装103. Windows XP下TP-Link网卡安装故障104. 安装网卡找不到RTL8139. SYS文件105. 连接WOL线导致计算机黑屏106. 无法正确安装DFE-530TX网卡107. 无法正确安装PCMCIA网卡108. 移动位置就Ping不通109. 安装网卡后无法正常关机110. 安装网卡并连接后导致网速变慢111. 同一台计算机在家里无法正常使用1.4.2 设备设置112. 主机到本地路由器的以太口不通

img
网络常见问题故障1000例(下)

本书第三部分(共三部分)内容简介 本书从大量的E-mail和BBS中精心筛选了1000个问题,针对网络设计、组建和管理的具体故障,重点介绍了解决问题的策略和排除故障的思路,彻底摒弃了枯燥的理论和简单的操作,着力突出了实用性和可操作性,力求使读者读一反三,触类旁通,从而培养独立思考和处理问题的能力。本书集实用性、思想性、可读性为一体,是一本适合广大计算机编程爱好者的优秀读物。本书适合网络维护工程师、网络工程技术人员、信息系统管理人员,以及所有已经或正准备从事网络管理的网络爱好者。目录 第1章 网络连接常见问题与故障1.1 网络搭建(网络拓扑、网络连接)1.1.1 网络规划1. 物理隔离要建两个网络2. 划分子网隔离子网3. IP与MAC绑定难题4. 实现多VLAN的Internet连接共享5. 延伸至750m6. 延伸至1500m7. 4层楼的拓扑结构8. 网络拓扑问题9. 超过254台计算机怎样在同一个网段10. 教育网多用户共同上网问题11. 限制部门计算机间的访问12. 不能相互访问却要连接共享1.1.2 设备互连13. 10Base-T共享网络连接14. 共享网络传输速率低 15. 不对称交换设备的连接16. 对称交换网络的连接17. 利用三层交换机提高传输效率18. GigaStack菊花链堆迭19. GigaStack星形堆迭20. Catalyst 3750的StackWise堆迭21. Uplink端口使用直通线级联22. Uplink口与相邻端口不能同时使用23. Uplink端口级联24. 普通端口的级联25. 计算机与集线设备的连接26. 什么时候该用直通线?什么时候该用交叉线?27. 重新连接后居然不通28. 局域网连接比Modem还慢29. 家庭网络组网方案30. 更换100Mbps交换机后无法连接至Internet31. 局域网之间使用Modem连接32. 交换机端口能否全部连接计算机1.1.3 连接状态33. 交换机端口指示灯熄灭34. Cisco交换机指示灯35. 判断交换机指示灯状态36. 网卡灯亮却不能上网37. Link灯不停地闪动38. COL指示灯长亮或不断闪烁 39. Collision指示灯闪个不停40. 集线器上与路由器LAN端口连接的灯不亮41. 网卡指示灯及其判断1.2 双机直连42. 被拨叫的主机没有反应43. 远程拨号速率和哪些因素有关44. 利用Modem实现双机直连45. USB连线无法相互访问46. IEEE 1394接口实现双机直连47. Windows XP的串口双机直连48. 相互无法Ping通49. 两台计算机无法实现直连50. 提示“网络电缆没有插好”51. 两台计算机通过3块网卡上网52. 两根网线连接3台计算机及 宽带53. 双机直连LED灯不亮54. 能单独上网却Ping不通对方55. 双机直连不能Ping通1.3 网络链路(综合布线、跳线)56. 140m的距离可否使用超5类屏蔽线57. RJ-45头针脚排列顺序58. 水晶头应压住外层绝缘皮59. 建筑间布线应当选择单模光纤60. 架空式布线应当注意的问题61. 埋入式布线应当注意的问题62. 能否改变双绞线线序63. 实现OA的远程办公64. 避免双绞线遭遇雷击65. 用粗缆还是光缆66. 双绞线的100m极限67. 突破100m极限68. 剪短网线后速度更慢69. 计算机出现短暂的反应迟缓70. Ping服务器时通时不通71. 网速特别慢72. 局域网速度非常慢1.4 网络设备(交换机、路由器、集线器、网卡)1.4.1 设备使用73. UPS的使用和保养74. 电池的使用和保养75. 路由器的选用76. 路由器拨号故障只发生在机关77. 网络经常瘫痪78. 交换机越用越快79. 5口的交换机只能用4口80. 更换交换机后下载速率猛增81. 连接到Hub后死机82. 连接Hub后速率大幅下降83. 用户多时网络速度变慢84. 安装网卡后无法重新启动85. 网卡频繁丢失86. 网卡驱动无法完全卸载87. 无法删除未知设备88. 网卡接口损坏89. 更换网卡后系统不认90. USB网卡无法正确安装91. 网卡MAC地址为FF-FF-FF-FF-FF-FF92. 提示“已有网络适配器使用此地址”93. 经常提示“你的网线没有连接好”94. 只能使用系统自带的驱动95. 更换PCI接口导致故障96. 内置网卡不能上网97. Windows XP上不能同时使用两块网卡98. 网卡工作不正常99. 网卡丢包频繁掉线100. 重复安装的网卡101. 只能发送数据包,而不能接收数据包102. 网卡无法正常安装103. Windows XP下TP-Link网卡安装故障104. 安装网卡找不到RTL8139. SYS文件105. 连接WOL线导致计算机黑屏106. 无法正确安装DFE-530TX网卡107. 无法正确安装PCMCIA网卡108. 移动位置就Ping不通109. 安装网卡后无法正常关机110. 安装网卡并连接后导致网速变慢111. 同一台计算机在家里无法正常使用1.4.2 设备设置112. 主机到本地路由器的以太口不通

img
华为编程开发规范案例

软件编程规范培训实例与练习 软件编程规范培训实例与练习  问题分类 1 逻辑类问题(A类)-指设计、编码中出现的计算正确性和一致性、程序逻辑控制等方面出现的问题,在系统中起关键作用,将导致软件死机、功能正常实现等严重问题; 接口类问题(B类)-指设计、编码中出现的函数和环境、其他函数、全局/局部变量或数据变量之间的数据/控制传输不匹配的问题,在系统中起重要作用,将导致模块间配合失效等严重问题; 维护类问题(C类)-指设计、编码中出现的对软件系统的维护方便程度造成影响的问题,在系统中不起关键作用,但对系统后期维护造成不便或导致维护费用上升; 可测试性问题(D类)-指设计、编码中因考虑不周而导致后期系统可测试性差的问题。  处罚办法 问题发生率: P=D/S D=DA+0.5DB+0.25DC 其中: P -问题发生率 D -1个季度内错误总数 DA -1个季度内A类错误总数 DB -1个季度内B类错误总数 DC -1个季度内C类错误总数 S -1个季度内收到问题报告单总数 1)当D≥3时,如果P≥3%,将进行警告处理,并予以公告; 2)当D≥5时,如果P≥5%,将进行罚款处理,并予以公告。 目 录 一、逻辑类代码问题 第5页 1、变量/指针在使用前就必须初始化 第5页 【案例1.1.1】 第5页 2、防止指针/数组操作越界 第5页 【案例1.2.1】 第5页 【案例1.2.2】 第6页 【案例1.2.3】 第7页 【案例1.2.4】 第8页 3、避免指针的非法引用 第9页 【案例1.3.1】 第9页 4、变量类型定义错误 第10页 【案例1.4.1】 第10页 5、正确使用逻辑与&&、屏蔽&操作符 第17页 【案例1.5.1】 第17页 6、注意数据类型的匹配 第18页 【案例1.6.1】 第18页 【案例1.6.2】 第18页 7、用于控制条件转移的表达式及取值范围是否书写正确 第20页 【案例1.7.1】 第20页 【案例1.7.2】 第21页 【案例1.7.3】 第22页 8、条件分支处理是否有遗漏 第24页 【案例1.8.1】 第24页 9、引用已释放的资源 第26页 【案例1.9.1】 第26页 10、分配资源是否已正确释放 第28页 【案例1.10.1】 第28页 【案例1.10.2】 第29页 【案例1.10.3】 第30页 【案例1.10.4】 第32页 【案例1.10.5】 第33页 【案例1.10.6】 第35页 【案例1.10.7】 第38页 11、防止资源的重复释放 第39页 【案例1.11.1】 第39页 12、公共资源的互斥性和竞用性 第40页 【案例1.12.1】 第40页 【案例1.12.2】 第40页 二、接口类代码问题 第43页 1、对函数参数进行有效性检查 第43页 【案例2.1.1】 第43页 【案例2.1.2】 第43页 【案例2.1.3】 第44页 【案例2.1.4】 第46页 【案例2.1.5】 第47页 【案例2.1.6】 第48页 2、注意多出口函数的处理 第49页 【案例2.2.1】 第49页 三、维护类代码问题 第51页 1、 统一枚举类型的使用 第51页 【案例3.1.1】 第51页 2、 注释量至少占代码总量的20% 第51页 【案例3.2.1】对XXX产品BAM某版本部分代码注释量的统计 第51页 四、产品兼容性问题 第52页 1、系统配置、命令方式 第52页 【案例4.1.1】 第52页 【案例4.1.2】 第53页 2、设备对接 第54页 【案例4.2.1】 第54页 3、其他 第55页 【案例4.3.1】 第55页 五、版本控制问题 第58页 1、新老代码中同一全局变量不一致 第58页 【案例5.1.1】 第58页 六、可测试性代码问题 第59页 1、调试信息/打印信息的正确性 第59页 【案例6.1.1】 第59页 一、逻辑类代码问题 1、变量/指针在使用前就必须初始化 【案例1.1.1】 C语言中最大的特色就是指针。指针的使用具有很强的技巧性和灵活性,但同时也带来了很大的危险性。在XXX的代码中有如下一端对指针的灵活使用: ... ... _UC *puc_card_config_tab; ... ... Get_Config_Table( AMP_CPM_CARD_CONFIG_TABLE, &ul_card_config_num, &puc_card_config_tab, use_which_data_area ); ... ... b_middle_data_ok = generate_trans_middle_data_from_original_data( puc_card_config_tab, Ul_card_config_num) .... ... 其中红色部分巧妙的利用指向指针的指针为指针puc_card_config_tab赋值,而在兰色部分使用该指针。但在Get_Config_Table函数中有可能失败返回而不给该指针赋值。因此,以后使用的可能是一个非法指针。 指针的使用是非常灵活的,同时也存在危险性,必须小心使用。指针使用的危险性举世共知。在新的编程思想中,指针基本上被禁止使用(JAVA中就是这样),至少也是被限制使用。而在我们交换机的程序中大量使用指针,并且有增无减。 2、防止指针/数组操作越界 【案例1.2.1】 在香港项目测试中,发现ISDN话机拨新业务号码时,若一位一位的拨至18位,不会有问题。但若先拨完号码再成组发送,会导致MPU死机。 处理过程: 查错过程很简单,按呼叫处理的过程检查代码,发现某一处的判断有误,本应为小于18的判断,写成了小于等于18。 结 论: 代码编写有误。 思考与启示: 1、极限测试必须注意,测试前应对某项设计的极限做好充分测试规划。 2、测试极限时还要注意多种业务接入点,本例为ISDN。对于交换机来说,任何一种业务都要分别在模拟话机、ISDN话机、V5话机、多种形式的话务台上做测试。对于中继的业务,则要充分考虑各种信令:TUP、ISUP、PRA、NO1、V5等等。 【案例1.2.2】 对某交换类进行计费测试,字冠011对应1号路由、1号子路由,有4个中继群11,12,13,14(都属于1#模块),前后两个群分别构成自环。其中11,13群向为出中继,12,14群向为入中继,对这四个群分别进行计费设置,对出入中继都计费。电话60640001拨打01160010001两次,使四个群都有机会被计费,取话单后浏览话单发现对11群计费计次表话单出中继群号不正确,其它群的计次表中出中继群号正常。 处理过程: 与开发人员在测试组环境多次重复以上步骤,发现11群的计次表话单有时正常,有时其出中继群号就为一个随机值,发生异常的频率比较高。为什么其它群的话单正常,唯独11群不正常呢?11群是四个群中最小的群,其中继计次表位于缓冲区的首位,打完电话后查询内存发现出中继群号在内存中是正确的,取完话单后再查就不正确了。 结 论: 话单池的一个备份指针Pool_head_1和中继计次表的头指针重合,影响到第一个中继计次表的计费。 思考与启示: 随机值的背后往往隐藏着指针问题,两块内存缓冲区的交界处比较容易出现问题,在编程时是应该注意的地方。 【案例1.2.3】 【正 文】 在接入网产品A测试中,在内存数据库正常的情况下的各种数据库方面的操作都是正常的。为了进行数据库异常测试,于是将数据库内容人为地破坏了。发现在对数据库进行比较操作时,出现程序跑死了现象。 经过跟踪调试发现问题出现在如下一段代码中: 1 for(i=0; idbf_count; i++) 2 { 3 pDBFat = (_NM_DBFAT_STRUC *)(NVDB_BASE + DBFAT_OFFSET + i*DBFAT_LEN); 4 if(fat_check(pDBFat) != 0) 5 { 6 pSysHead->system_flag = 0; 7 head_sum(); 8 continue; 9 } 10 if(strlen(dbf->dbf_name) != 0 && strncmp(dbf->dbf_name, pDBFat->dbf_name, strlen(dbf->dbf_name)) == 0) 11 { 12 dbf_ptr1 = (_UC *)pDBFat->dbf_head; 13 filesize = pDBFat->dbf_fsize; 14 break; 15 } 16 } 在测试时发现程序死在循环之中,得到的错误记录是"Bus Error"(总线出错),由此可以说明出现了内存操作异常。 经过跟踪变量值发现循环变量i的阀值pSysHead->dbf_count的数值为0xFFFFFFFF,该值是从被破坏的内存数据库中获取的,正常情况下该值小于127。而pDBFat是数据库的起始地址,如果pSysHead->dbf_count值异常过大,将导致pDBFat值超过最大内存地址值,随后进行的内存操作将导致内存操作越界错误,因而在测试过程中数据库破坏后就出现了主机死机的现象。 上面的问题解决起来很容易,只需在第一行代码中增加一个判断条件即可,如下:  for(i=0; idbf_coun && i < MAX_DB_NUM; i++) // MAX_DB_NUM=127 这样就保证了循环变量i的值在正常范围内,从而避免了对指针pDBFat进行内存越界的操作。   从上面的测试过程中,我们可以看到:如此严重的问题,仅仅是一个简单的错误引起的。实际上,系统的不稳定往往是由这些看似很简单的小错误导致的。这个问题给我们教训的是:在直接对内存地址进行操作时,一定要保证其值的合法性,否则容易引起内存操作越界,给系统的稳定性带来潜在的威胁。 【案例1.2.4】 近日在CDB并行测试中发现一个问题:我们需要的小区负荷话统结果总是为零,开始还以为小区负荷太小,于是加大短消息下发数量,但还为零,于是在程序中加入测试代码,把收到的数据在BAM上打印出来, 结果打印出来的数据正常,不可能为零,仔细查看相关代码,问题只可能在指针移位上有问题,果然在函数中发现一处比较隐蔽的错误。 /* 功能:一个BM模块内所有小区CDB侧广播消息忙闲情况 */ /*************************************************************/ void Cell_CBCH_Load_Static(struct MsgCB FAR *pMsg) { 。。。 memcpy((_UC *)&tmp_msg,pMsg,sizeof(tmp_msg)); pMsg=pMsg+sizeof(tmp_msg);//sizeof(tmp_msg)=10;本意是想移动10个字节,可是实际上指针移动了10*sizeof(struct MsgCB)个字节; CellNum=tmp_msg.usCellNum; 。。。 } 1 所以结构指针传入函数后,如要进行指针移动操作,最好先将其转化为_UC型再说。总之指针操作要小心为上。 3、避免指针的非法引用 【案例1.3.1】 【正 文】 在一次测试中,并没有记得做了什么操作,发现HONET系统的主机复位了,之后,系统又工作正常了。由于没有打开后台的跟踪窗口,当时查了半天没有眉目。过了半天,现象又出现了,而且这次是主机在反复复位,系统根本无法正常工作了。 我凭记忆,判断应该是与当时正在测试的DSL板的端口配置有关。于是将板上所有端口配置为普通2B+D端口,重新加载在主机数据,现象消失。于是初步定位为主机在DSL端口处理过程中有重大错误。 我在新的数据上努力恢复原出问题的现象,却一直没有重现,于是恢复原数据,加载后立即重现。并注意到,当DSL端口激活时,主机复位。仔细比较两种数据的差别,发现出现主机复位问题的数据中DSL板配置了MNT/MLT端口,但是没有做DSL端口之间的半永久数据。 于是在程序中不断加打印语句,通过后台的DBWIN调试程序跟踪,最后终于定位为:每当执行到portdsl.c的DeviceDslMsgProc()函数中处理U口透传的 if ( SPC_STATE_OK == pSpcCB->bySpcState ) 语句时,主机复位。但是该语句似乎并无不妥。 再分析整个函数,pSpcCB在函数前部分已经被赋值, pSpcCB = SpcCB + (PortTable+index)->spcNo; 但由于得到 index 后,没有任何判断,导致若MNT/MLT端口没有做半永久,端口激活后,执行此部分函数,(PortTable+index)->spcNo 有可能为NULL_WORD,于是,运算后,pSpcCB 可能为非法值。此时主机在取进行判断,就不知会导致什么后果了。 其实,改起来很简单,只要在这两句前增加一个判断就行了。于是,修改代码为: if ( (PortTable+index)->spcNo != NULL_WORD) { pSpcCB = SpcCB + (PortTable+index)->spcNo; if ( SPC_STATE_OK == pSpcCB->bySpcState ) {。。。} } 修改后,问题不再重现。 经过分析可以发现,编译环境是有很大的容许空间的,若主机没有做充分的保护,很可能会有极严重的随即故障出现。所以编程时一定要考虑各种可能情况;而测试中遇到此类死机问题,则要耐心的定位到具体是执行哪句代码时出现的,再进行分析。因为问题很隐蔽,直接分析海一样的代码是很难发现的。 4、变量类型定义错误 【案例1.4.1】 【正 文】 在FRI板上建几条FRPVC,其DLCI类型分别为:10Bit/2bytes、10bit/3bytes、16bit/3bytes、17bit/4bytes、23bit/4bytes。相应的DLCI值为:16、234、991、126975、1234567,然后保存,重起MUX,观察PVC的恢复情况,结果DLCI值为16、234和991的PVC正确恢复,而DLCI=126975的PVC恢复的数据错误为61439,而DLCI=1234567的PVC完全没有恢复。 对于17/4类型,DLCI=126975的PVC在恢复时变成61439,根据这条线索,查找原因,发现126975-61439=65535,转化二进制就是10000000000000000,也就是说在数据恢复或保存时把原数据的第一个1给忽略了。此时第一个想法是:在程序处理中,把无符号长整型变量当作短整型变量处理了,为了证实这个判断,针对17bit/4bytes类型又重新设计测试用例:(1) 先建PVC,DLCI=65535,然后保存,重起MUX,观察PVC的恢复情况,发现PVC能够正确恢复; (2)再建PVC,DLCI=65536,然后保存,重起MUX,观察PVC的恢复情况,此时PVC不能正确恢复。 至此基本可以断定原因就是出在这里。带着这个目的查看原代码,发现在以下代码中有问题: int _GetFrDlci( DWORD* dwDlci, char* str, DWORD dwDlciType, DWORD dwPortType, DWORD dwSlotID, DWORD dwPortID) { DWORD tempDlci; char szArg[80]; 1 char szLine[80]; ID LowPVCEP; DWORD dwDlciVal[5][2] = { {16,1007}, {16,1007}, {1024,64511}, {2048,129023}, {131072,4194303} } ; ... } typedef struct tagFrPppIntIWF { ... WORD wHdlcPort; WORD wHdlcDlci; WORD wPeerHdlcDlci; WORD wPeerOldAtmPort; ... } SFrPppIntIWFData; DWORD SaveFrNetIntIWFData ( DWORD *pdwWritePoint ) { BYTE bSlotID, bPeerSlotID; DWORD dwCCID, dwPeerCCID; WORD wHdlcPort, wAtmPort, wIci, wPeerIci, wPeerHdlcPort ; WORD wCount; ... } DWORD SaveFrNetExtIWFData ( DWORD *pdwWritePoint ) { BYTE bSlotID; DWORD dwCCID, dwPeerCCID; WORD wHdlcPort, wAtmPort, wIci ; WORD wCount; ... unSevData.FrNetExtIWF[wCount].bSlotID = bSlotID; unSevData.FrNetExtIWF[wCount].wHdlcPort = wHdlcPort; unSevData.FrNetExtIWF[wCount].wHdlcDlci = gFrPVCEP[bSlotID ][ gFrPVCC[bSlotID][dwCCID].dwLoPVCEP ].dwDLCI; unSevData.FrNetExtIWF[wCount].wOldAtmPort = wAtmPort; unSevData.FrNetExtIWF[wCount].wAtmDlci = gFrPVCEP[ bSlotID ][ gFrPVCC[bSlotID][dwCCID].dwHiPVCEP ].dwDLCI; unSevData.FrNetExtIWF[wCount].dwMapMode = gFrPVCC[bSlotID][dwCCID].dwMapMode;      ... } DWORD RestoreFrNetExtIWFData ( WORD wSlotID, BYTE *pReadPoint ) { WORD wCount, wTotalNetIWF; BYTE bSlotID, bHdlcDlciType, bAtmDlciType; WORD wOldAtmPort, wAtmDlci, wHdlcPort, wHdlcDlci; DWORD dwMapMode, dwCIR, dwBe; DWORD dwCCID, dwResult, dwAtmPort; wTotalNetIWF = g_MuxData.SevDataSize.wFrNetExtIWFNum; ... } DWORD RestoreFrHdlcIntIWFData ( WORD wSlotID, BYTE *pReadPoint ) { WORD wCount, wTotalHdlcIWF; DWORD dwCCID, dwPeerCCID, dwAtmPort, dwPeerAtmPort; DWORD dwResult; BYTE bSlotID, bPeerSlotID; WORD wHdlcPort, wOldAtmPort, wCIR; WORD wPeerHdlcPort, wPeerOldAtmPort; ... } 其中涉及DLCI值的变量都为WORD(即无符号短整型)类型,在程序的处理时,出现WORD和DWORD(无符号长整型)类型在一句中同时存在的情况,至此可以判断问题出在这里。由于DLCI值在不同类型时的取值范围不同,前三种类型的取值范围为16~991,第四种取值范围为2048~126975,第五种取值范围为131072~4194303,所以当采用前三种DLCI类型时,采用WORD类型最大值为65535,已经完全够用了;而对于第四种类型时,其取值在超过65535时,获取DLCI值的函数_GetFrDlci()采用DWORD类型,而负责保存和恢复的两个函数SaveFrNetExtIWFData()和RestoreFrNetExtIWFData(),都把DLCI的值当作WORD类型进行处理,因此导致DLCI取值越界,于是程序把原本为长整型的DLCI强制转换成整型,从而导致DLCI值在恢复时,比原数据小65536。而在程序运行过程中,这些数据保存在DRAM中,程序运行直接从DRAM中获取数据,程序不会出错;当FRI板复位或插拔后,需要从FLASH中读取数据,此时恢复函数的错误就表现出来。 另一个问题是为什么23/4类型的DLCI数据不能恢复?这是由于对于23/4类型的PVC,其DLCI的取值范围为:131072~4194303,而程序强制转换并恢复的数据最大只能是65535,所以这条PVC不能恢复。 至此,DLCI数据恢复出错的原因完全找到,解决的方法是将DLCI的类型改为DWORD类型。从这个案例可以看出,在程序开发中一个很低级的错误,将在实际工作中造成很严重的后果。 【案例1.4.2】 【正 文】 在FRI板上建几条FRPVC,其DLCI类型分别为:10Bit/2bytes、10bit/3bytes、16bit/3bytes、17bit/4bytes、23bit/4bytes。相应的DLCI值为:16、234、991、126975、1234567,然后保存,重起MUX,观察PVC的恢复情况,结果DLCI值为16、234和991的PVC正确恢复,而DLCI=126975的PVC恢复的数据错误为61439,而DLCI=1234567的PVC完全没有恢复。 对于17/4类型,DLCI=126975的PVC在恢复时变成61439,根据这条线索,查找原因,发现126975-61439=65535,转化二进制就是10000000000000000,也就是说在数据恢复或保存时把原数据的第一个1给忽略了。此时第一个想法是:在程序处理中,把无符号长整型变量当作短整型变量处理了,为了证实这个判断,针对17bit/4bytes类型又重新设计测试用例:(1) 先建PVC,DLCI=65535,然后保存,重起MUX,观察PVC的恢复情况,发现PVC能够正确恢复; (2)再建PVC,DLCI=65536,然后保存,重起MUX,观察PVC的恢复情况,此时PVC不能正确恢复。 至此基本可以断定原因就是出在这里。带着这个目的查看原代码,发现在以下代码中有问题: int _GetFrDlci( DWORD* dwDlci, char* str, DWORD dwDlciType, DWORD dwPortType, DWORD dwSlotID, DWORD dwPortID) { DWORD tempDlci; char szArg[80]; char szLine[80]; ID LowPVCEP; DWORD dwDlciVal[5][2] = { {16,1007}, {16,1007}, {1024,64511}, {2048,129023}, {131072,4194303} } ; ... } typedef struct tagFrPppIntIWF { ... WORD wHdlcPort; WORD wHdlcDlci; WORD wPeerHdlcDlci; WORD wPeerOldAtmPort; ... } SFrPppIntIWFData; DWORD SaveFrNetIntIWFData ( DWORD *pdwWritePoint ) { BYTE bSlotID, bPeerSlotID; DWORD dwCCID, dwPeerCCID; WORD wHdlcPort, wAtmPort, wIci, wPeerIci, wPeerHdlcPort ; WORD wCount; ... } DWORD SaveFrNetExtIWFData ( DWORD *pdwWritePoint ) { BYTE bSlotID; DWORD dwCCID, dwPeerCCID; WORD wHdlcPort, wAtmPort, wIci ; WORD wCount; ... unSevData.FrNetExtIWF[wCount].bSlotID = bSlotID; unSevData.FrNetExtIWF[wCount].wHdlcPort = wHdlcPort; unSevData.FrNetExtIWF[wCount].wHdlcDlci = gFrPVCEP[bSlotID ][ gFrPVCC[bSlotID][dwCCID].dwLoPVCEP ].dwDLCI; unSevData.FrNetExtIWF[wCount].wOldAtmPort = wAtmPort; unSevData.FrNetExtIWF[wCount].wAtmDlci = gFrPVCEP[ bSlotID ][ gFrPVCC[bSlotID][dwCCID].dwHiPVCEP ].dwDLCI; unSevData.FrNetExtIWF[wCount].dwMapMode = gFrPVCC[bSlotID][dwCCID].dwMapMode;      ... } DWORD RestoreFrNetExtIWFData ( WORD wSlotID, BYTE *pReadPoint ) { WORD wCount, wTotalNetIWF; BYTE bSlotID, bHdlcDlciType, bAtmDlciType; WORD wOldAtmPort, wAtmDlci, wHdlcPort, wHdlcDlci; DWORD dwMapMode, dwCIR, dwBe; DWORD dwCCID, dwResult, dwAtmPort; wTotalNetIWF = g_MuxData.SevDataSize.wFrNetExtIWFNum; ... } DWORD RestoreFrHdlcIntIWFData ( WORD wSlotID, BYTE *pReadPoint ) { WORD wCount, wTotalHdlcIWF; DWORD dwCCID, dwPeerCCID, dwAtmPort, dwPeerAtmPort; DWORD dwResult; BYTE bSlotID, bPeerSlotID; WORD wHdlcPort, wOldAtmPort, wCIR; WORD wPeerHdlcPort, wPeerOldAtmPort; ... } 其中涉及DLCI值的变量都为WORD(即无符号短整型)类型,在程序的处理时,出现WORD和DWORD(无符号长整型)类型在一句中同时存在的情况,至此可以判断问题出在这里。由于DLCI值在不同类型时的取值范围不同,前三种类型的取值范围为16~991,第四种取值范围为2048~126975,第五种取值范围为131072~4194303,所以当采用前三种DLCI类型时,采用WORD类型最大值为65535,已经完全够用了;而对于第四种类型时,其取值在超过65535时,获取DLCI值的函数_GetFrDlci()采用DWORD类型,而负责保存和恢复的两个函数SaveFrNetExtIWFData()和RestoreFrNetExtIWFData(),都把DLCI的值当作WORD类型进行处理,因此导致DLCI取值越界,于是程序把原本为长整型的DLCI强制转换成整型,从而导致DLCI值在恢复时,比原数据小65536。而在程序运行过程中,这些数据保存在DRAM中,程序运行直接从DRAM中获取数据,程序不会出错;当FRI板复位或插拔后,需要从FLASH中读取数据,此时恢复函数的错误就表现出来。 另一个问题是为什么23/4类型的DLCI数据不能恢复?这是由于对于23/4类型的PVC,其DLCI的取值范围为:131072~4194303,而程序强制转换并恢复的数据最大只能是65535,所以这条PVC不能恢复。 至此,DLCI数据恢复出错的原因完全找到,解决的方法是将DLCI的类型改为DWORD类型。从这个案例可以看出,在程序开发中一个很低级的错误,将在实际工作中造成很严重的后果。 5、正确使用逻辑与&&、屏蔽&操作符 【案例1.5.1】 【案例描述】:由于C语言中位与比求模效率高,因而系统设计时,对于模128的地方都改为与127,系统定义的宏为#define MOD128 127和#define W_MOD 127(定义的宏的名字易引起误解),但实际程序中还是采取求模,从而引起发送窗口欲重发的和实际重发的不一致,最终导致链路复位此类严重问题,曾在定位此问题时花了不少时间。 【处理过程】:处理过程如下: #define MOD128 127 //队列长128,当队头到128时,上其返回。 #define W_MOD 127 //发送窗口队列,意义同上。 在函数L2_TO_L1()中,有如下语句: linkstate_ptr->SendWin.head = (head + 1) % W_MOD ; 这里当head=126时,SendWin.head = 0,这将造成发送窗口指针和队列窗口指针错位,造成链路复位; 另外,在重发函数void INVOKE_RETRANSMISSION(_US logic_link,_US n_r)中,有如下语句: retran_num = (LinkState[logic_link].Vs + MOD128 - (_UC)n_r) % MOD128 ; w_head = (LinkState[logic_link].SendWin.head + W_MOD - retran_num) % W_MOD ; 第一个语句求欲重发的消息包个数,第二个语句求重发的起始位置,当Vs小于n_r时,将造成实际重发数小于欲重发数,同时造成实际起始重发位置和欲重发起始位置错开,从而引起链路复位。上面三个语句应该做如下改动: linkstate_ptr->SendWin.head = (head + 1) & W_MOD ; retran_num = (LinkState[logic_link].Vs + MOD128 + 1 - (_UC)n_r) & MOD128 ; w_head = (LinkState[logic_link].SendWin.head + W_MOD + 1 - retran_num) & W_MOD ; 【结 论】:由于链路通信对系统效率要求很高,算法采用效率最高的,但位与(&)和求模(%)这小小的区别,造成的竟是链路复位这种严重的错误。 【思考与启示】:对这类问题,大家在阅读代码或代码审查时一定要注意,仔细一点往往能发现问题,但在测试中来定位这种问题,花费的时间往往更长。 6、注意数据类型的匹配 【案例1.6.1】 【案例描述】 下面通过测试中的一个例子来说明这个问题:命令DSP N7C是用来显示NO7电路状态的,其参数设备类型DID支持TUP和ISUP,参数信道号BSN支持多值输入(最多支持32路查询),正常情况下该命令没有问题。但试了非正常情况下,问题就出来了。 1、首先试BSN参数越界情况,即参数BSN超过32路查询,选了几个数据段,问题就出来了。对于0&&300和0&&256,该命令返回结果不一致,对前者认为参数越界,对后者返回执行成功。 2、对于参数DID,选定一种设备类型(TUP或ISUP),让参数BSN所包含的32路电路跨越TUP和ISUP,两次结果是不一致的。 【处理过程】 反馈到开发人员那里,第一个问题是BAM的问题,第二个问题是SM的问题。 【结 论】 1、为数据超出范围溢出造成,int值赋值给BYTE,造成数据丢失。 2、问题的产生是因为查询的第一个信道是TUP电路,但是却按ISUP电路查询。ISUP的维护处理函数判断第一个信道不是ISUP信道,认为整个的PCM不是ISUP类型的PCM,返回全部的电路状态为未安装。消息处理不合理。TUP也会产生如此错误。 【思考与启示】 我们的MML命令并不是无懈可击的,许多表面上的小问题,往往隐藏着代码的缺陷和错误。 【案例1.6.2】 【正 文】 当我们使用PC-LINT检查代码时,会发现大量的数据类型不匹配的告警,大部分情况下,这种代码上存在的问题并不会引起程序功能实现上的错误,但有些情况下,也许会产生严重的问题: 一、不同数据类型变量之间赋值引起的问题,实际上,该类问题也可以分为几种情况: 1、直接赋值,比如,把一个WORD型变量赋给一个INT型变量,如果WORD型变量大于32767,INT型变量得到的就是一个负值了。 【例一】一次测试过程中发现,SDH送的告警在BAM调试窗口打印出红色提示:File(XXX),Line(XXX):Invalid alarm id ,from: 7, AlarmId: 65463 经过检查数据发现,并没有ID为65463的告警,分析上报的数据帧,发现上报的告警ID为B7,原来代码中有一处强制类型转换: sdhAlmStru.AlarmId = (WORD)RecvBuffer[iTmpLen + 5]; char型强制转换成WORD型。B7就变成了FFB7,十进制就是65463。由于char是有符号型,B7的第8位为1,所以转换后为FFB7,而不是代码作者希望的00B7,如果第8位是0,或该变量是BYTE型,转换就不会有问题了。 2、函数形参和实参不一致,实际上和第一种情况本质上是一样的,只是表现的形式不太一样,这种情况也是代码中经常出现的问题,下面例子是测试中曾经发现的一个小问题: 【例二】在file01中的INT DebugMsgProc(char byMsg0, char byMsg1)函数,两个形参都是char型,而实际传入的参数都是BYTE型,结果函数中的如下语句: PrintfE(PID_RED," %d ticks time out!",byMsg1); 在byMsg1大于127时,输出错误的结果。 二、不同数据类型之间的比较操作 在循环终止条件的判断中,不同类型变量的比较操作是容易造成死循环错误的地方,同时也是开发人员容易忽视的地方,值得测试人员多加留意。下面两个例子是该类错误的两种典型情况: 【例三】file02文件中某函数中如下代码,可能造成死循环: ...... int i; WORD *pCheck =(WORD*)p; WORD wCheckSum=*pCheck; pCheck++; for(i=1;i= g_wASL32StartPSN ) && ( ( ( bsn - g_wASL32StartPSN ) % 32 ) == 15 || ( ( bsn - g_wASL32StartPSN ) % 32 == 16 ) ) ) return TRUE; if ( ( bsn % 16 ) == 7 || ( bsn % 16 ) == 8 ) return TRUE; return FALSE; 作者的本意是如果是32路用户板(蓝色字体判断),就看端口号是否是第15和16路,如果是,就是反极性端口,返回TRUE,否则就不是,应该返回FALSE。但代码表达的意思是:如果是32路用户板并且端口号是15或16就返回真值,否则还要执行下边语句。 当端口在32路用户板上,但端口号不是15或16时,不同的32路端口的起始地址g_wASL32StartPSN,会导致不同的非15、16端口被误认为是反极性端口。举个例子,当g_wASL32StartPSN的值为3000时,端口号为3000(第一块板上的第0个端口)就被认为是反极性端口,这与作者的意图完全相悖。 可以将代码修改如下: if ( ( bsn >= g_wASL32StartPSN ) { if ( ( ( bsn - g_wASL32StartPSN ) % 32 ) == 15 || ( ( bsn - g_wASL32StartPSN ) % 32 == 16 ) ) ) return TRUE; } else if ( ( bsn % 16 ) == 7 || ( bsn % 16 ) == 8 ) return TRUE; return FALSE; 通过这个例子,我觉得在代码审查时应该留意在判断条件较多的情况下,每个输入是否都能正确输出,在单元测试、集成测试、系统测试时要针对边界值设计相应的测试用例。 判断条件较多时开发人员也应该适当分开写,既使代码更易读,又不容易出错。 8、条件分支处理是否有遗漏 【案例1.8.1】 【现 象】 在接入网主机程序的代码审查中,发现dbquery.c的DBQ_Init_ANType函数中如下代码段缺少应有的条件分支,在数据异常的情况下,会产生较严重的问题。 【处理过程】 该错误比较隐蔽,现在说明如下: Max2B1QStatTime 最大统计时间 Max2B1QStatPortNum最大统计端口数 MAX_2B1Q_STAT_PSN 最大统计内存分配数量 其中:Max2B1QStatTime(最大统计时间)和Max2B1QStatPortNum (最大统计 端口数)的乘积不能大于MAX_2B1Q_STAT_PSN 程序如下: //查询数据库,获得Max2B1QStatTime的值 directQueryCond.tupleNo = 10; error_code = DB_Query( RID_OTHERS_PARA_INFO, 1, (LPDBCondition)&directQueryCond, (BYTE FAR *)&tempstruct0 ); //查询数据库成功 if( error_code == DB_SUCCESS ) { //tempstruct0.data是数据库中为Max2B1QStatTime配置的值 if ( tempstruct0.data > MAX_2B1Q_STAT_PSN ) Max2B1QStatTime = MAX_2B1Q_STAT_PSN; else if ( tempstruct0.data != 0 ) Max2B1QStatTime = tempstruct0.data; } //查询数据库,获得Max2B1QStatPortNum的值 directQueryCond.tupleNo = 11; error_code = DB_Query( RID_OTHERS_PARA_INFO, 1, (LPDBCondition)&directQueryCond, (BYTE FAR *)&tempstruct0 ); //查询数据库成功 if( error_code == DB_SUCCESS ) { //tempstruct0.data为数据库中为Max2B1QStatPortNum配置的值,如果其缺省值和Max2B1QStatTime乘积值大于MAX_2B1Q_STAT_PSN的话: if ( (tempstruct0.data * Max2B1QStatTime) > MAX_2B1Q_STAT_PSN ) Max2B1QStatPortNum = MAX_2B1Q_STAT_PSN / Max2B1QStatTime; //如果在合理范围内且不为0的话: else if ( tempstruct0.data != 0 ) Max2B1QStatPortNum = tempstruct0.data; } 此处if-else if 分支没有判断 值为0的情况,即数据库为Max2B1QStatPortNum配置的值为0: tempstruct0.data == 0,则Max2B1QStatPortNum就为缺省值32。 【结 论】 由于内存限制,Max2B1QStatTime(最大统计时间)和Max2B1QStatPortNum(最大统计端口数)的乘积不能大于MAX_2B1Q_STAT_PSN, 如果从数据库中得到Max2B1QStatTime为MAX_2B1Q_STAT_PSN,而数据库中最大统计端口数恰好为0,由于上述代码没有对tempstruct0.data == 0的情况进行判断,Max2B1QStatPortNum为缺省值32,这样Max2B1QStatTime和Max2B1QStatPortNum乘积已经是32倍MAX_2B1Q_STAT_PSN了,远远超过了设计内存的限制。 造成这种错误的原因是判断语句对条件判断不完整。 【思考与启示】 在代码审查时,应该十分注意条件判断的的完备性。好多问题就是因为条件判断不完全造成的。 9、引用已释放的资源 【案例1.9.1】 【正 文】 在计费测试的过程中,用呼叫器进行大话务量呼叫测试。30路话路通过TUP自环呼叫另外30路话路,计费数据的设定是这样的:通过计费情况索引对主叫计费,得到详细话单。首先保证计费数据设定的正确性,打了几次自环电话后,查看话单正常,则开始呼叫。 呼叫几万次后停止呼叫,取话单进行观察。发现这30路每次呼叫总会出现一张告警话单,其余话单正常,该告警话单相对于话路来说是随机出现的。 通知开发人员后,首先我们再次对计费数据进行了确认。某个用户在某次呼叫产生了告警话单,其上一次和下一次呼叫的计费情况都正常,两次呼叫之间的时间间隔只有几秒钟,排除了人为修改数据的可能。开发人员认为是CCB的问题,后来一查果然如此。 当中继选线发生了同抢需要重新选线时,CCB的reset_CCB_for_reseatch_called_location()就会把有关的呼叫信息清掉,造成计费情况分析失败,产生计费费用为0的告警话单。 更正reset_CCB_for_reseatch_called_location()中清除被叫信息的代码,重选中继时不清除被叫用户这部分属性。 思考与启示: 1、在计费测试过程中,对话单的观察很重要,不应该放过任何一个细小的疑点; 2、计费测试仅仅打几次电话往往达不到效果,越接近用户实际使用的情况越可能发现问题。 【案例1.9.2】 【案例描述】 在进行128模块V5用户CENTREX新业务测试时,偶然遇到一个怪现象:对群内一个V5ST用户只开放MCT权限,在进行恶意呼叫追查时,有一次报恶意呼叫追查成功音只报了一半,当正要报出恶意呼叫的号码时,业务中断重新回到通话态,随即重新追查一次,报“已申请其它新业务,本次申请不成功”。恶意呼叫追查与任何新业务都不会冲突,而且此用户也只有恶意呼叫追查有权,可以肯定此时程序出问题了。为了重现,再次挂机,重新呼叫,应用此新业务,但这个现象一直没有出现。大约反复操作20遍,又出现了一次这样的情况,显然程序中可能存在某种问题。 【处理过程】 出现这个问题后,及时与开发人员A取得了联系,并一起试图重现这个问题,通过许多次的反复操作,又出现了一次这种情况。确认问题后,A表现出高度的责任心,马上驾调试环境,反复调测,终于在当天就逮住了狐狸尾巴: 1、当用户接听恶意呼叫者的电话, 并启动恶意呼叫追查业务后, 在V5_CR_VOICETONE状态下, 只要听MCT音的用户用脉冲方式拨任意一个数字, 则立即停止送MCT音, 而将用户切换回与恶意呼叫者的通话. 但是程序中没有对拨号类型作判断, 导致用户若用音频拨号也会作同样的处理。 2、除了取消此次MCT服务, 将用户切换回与恶意呼叫者的通话外, 如果不释放MCT_HANDLE, 由于每个模块只有一个这样的资源, 则下一次使用MCT业务的用户不能成功, 因为会在申请MCT_HANDLE时失败, V5模块和ST模块在这个地方处理都有问题, 没有将MCT_HANDLE释放掉, 对于V5用户会听新业务失败音, 对于ST用户会听音乐。 当不停的拨测V5用户的MCT业务时, 有时在听音时, 可能由于网板有杂音等原因(或用户碰了话机的按键), 导致DTR收到一位号, 则会立即停止此次MCT服务, 用户会听到MCT送音突然中断, 然后恢复了与恶意呼叫者的通话. 而下次再用MCT时, 由于上面所述的原因, 会听到新业务失败音, 此次失败后, 无论MCT_HANDLE分配成功与否, 该用户的MCT标志都被置为1, 所以在用户挂机时, 会将该模块唯一的MCT_HANDLE资源释放掉. 则以后该功能又可以正常实现。 在追查这个问题时,开发人员A又发现了一个可能导致死机的严重问题:在用户启动MCT服务, 正在听报追查号码的MCT音时, 若恶意用户此时挂机, CCB的处理中, 只针对ST用户送DISCONNECT, 而对V5ST用户送的是RELEASE消息, 这导致V5X收到此消息后, 将该V5ST用户的cr2清除掉, V5_USER_TALBE[ ]. cr2变为0xFFFF, 这样在V5_CR_VOICETONE超时后, 程序中会检查cr2的状态是否为HOLD, 当取cr2的内容时, 由于cr2已被清除, 会发生指针越界的GP错误。 【结 论】 通过调测发现、定位并解决问题。 【思考与启示】 我们平常一些熟视无睹的业务或按正常流程操作没有问题的业务,不能保证它就一定没有问题,要善于抓住一丝一毫的异常现象。对于很难重现的问题千万不要轻易放过,我们网上设备所出的问题很多都是一些在实验室难以出现或很难重现的一些问题,一些显而易见的问题一般都可消灭在实验室,难就难在消灭一些隐藏很深的问题。说老实话,我们的产品还有许多问题 ,需要我们扎扎实实锲而不舍的工作。 10、分配资源是否已正确释放 【案例1.10.1】 【正 文】 在对接入网A产品的网管软件测试中,发现了一个WINDSOWS资源损耗的的问题:当网管软件运行几天后,WINDOWS总会出现“资源不够”的告警提示。如果网管软件不关掉再重新启动的话,就会出现WINDOWS资源完全耗尽的现象,最终网管系统反应很慢,无法正常工作。 从现象上可以判断出,网管软件存在隐蔽的内存泄露或资源不释放的问题,并且这种资源耗尽是一个缓慢的过程。如何定位这个问题呢? 定位这种问题可以利用WINDOWS中的一个系统资源监视工具。打开Windows的“附件/系统工具/资源状况”,这是一个系统资源、用户资源、和GDI资源的实时监视工具。 工具有了,那么如何发现导致不断消耗资源的特定操作呢? 首先和开发人员共同探讨,列出几个最可能消耗资源的操作和一些操作组合,这样就缩小了监视范围,避免没有范围的碰运气,否则如大海捞针。 监视前,首先重新启动WINDOWS,最好不运行其他的程序,打开“系统状况”这个监视工具,然后运行网管软件,记下此时的资源状况数据。 然后针对一个可疑的操作,快速大量地重复进行。这种重复性的操作可以利用QArun测试工具执行,QArun可以记录操作者的一次操作步骤,然后按照设定的次数重复操作。操作后,观察此时的资源状况,并记下此时的数据,与操作前的数据比较,如果操作前后的数据数据没有变化或变化很小,可排除此项操作,否则就可断定此项操作会引起资源耗尽。 对其它可疑的操作和操作组合重复以上过程。 通过以上的步骤,终于找出引起资源耗尽的罪魁祸首。分析相应部分的代码,发现引起资源耗尽原因有:内存泄露,画笔和画刷资源用完后未释放等。 【案例1.10.2】 【正 文】 某产品后台软件版本,是用C++写的,程序员在写代码时,经常在构造函数中申请一块内存,而不释放,在程序其他代码中也经常只管申请,不管释放。 例如: void WarnSvr::SaveWarnData() { ...... for(int m=0;mCsn=Buffer[EVENT_ALARM][m].Csn; item->Position=m +(RecordsInHistoryFile-RecordsInBuffer[EVENT_ALARM]); //If a warn with a certain Csn is not in EventFilterIndex //it is not necessary to be added to HistoryFilterIndex int item_total=EventFilterIndex.GetItemsInContainer(); BOOL find_flag=false; for(int k=0;kCsn==item->Csn) { find_flag=true; break; } if(find_flag) { HistoryFilterIndex.Add(item); if(HistoryFilterIndex.IsFull()) ClearIndexEntry(); } //建议在此处加上: // else // delete item; }。 有的程序员认为,后台运行的环境有大量内存,几个字节的浪费不会造成死机等重大事故。然而,长时间累计起来,必然会造成资源紧张而出现故障。 实际上,这种思想是造成我们产品不稳定的原因之一。我们的主机在网上能运行几个月几年,大家对内存的分配释放较敏感,而我们的后台产品往往只能正常运行几天。这个地方不注意也是原因之一吧。 【案例1.10.3】 【正 文】 在进行代码审查过程中,造成内存泄漏的代码比较多。下面举几种常见的内存泄漏错误,供测试人员在代码审查中参考: 1. 函数有多个出口时,没有在每个出口处对动态申请的内存进行释放。一般在异常处理时容易出现这种错误。下面的代码段就是这样的例子: ..... pRecord = new char[pTable->GetRecordLength()]; assert(pRecord != NULL); if (pTable->GoTop(FALSE) != DBIERR_NONE) return; // 如果从这里返回,pRecord将得不到释放 ..... pTable->Close(); delete[] pRecord; } 2. 给指针赋值时,没有检查指针是否为空,如果指针不为空,那么指针原来指向的内存将丢失。请看如下代码段: .... struct FileInfo * pdbffile = new struct FileInfo; pdbffile->pfileinfo = new struct ffblk; pdbffile->srcname = srcRootPath; pdbffile->desname = desRootPath; pdbffile->prev = NULL; pfile = pdbffile; //赋值之前没有检查一下pfile是否为空,如果不为空,会造成pfile指向的内存丢失。 dbf_start_needed = FALSE; dbf_Finish = FALSE; flag_begined = TRUE; if(FALSE == Copy(TRUE)) { dbf_start_needed = TRUE; WarnMsgOut("Error occurs while copying files in directory,trying again."); } } 3. 连续二次内存动态分配,在第二次分配失败时,忘记释放第一次已经申请到的内存。 .... pMsgDB_DEV = (PDBDevMsg)GetBuff( sizeof( DBDevMsg ), __LINE__); if( pMsgDB_DEV == NULL ) return; pMsgDBApp_To_Logic = (LPDBSelfMsg)GetBuff( sizeof(DBSelfMsg), __LINE__ ); if( pMsgDBApp_To_Logic == NULL ) return;//此处返回造成pMsgDB_DEV指向的内存丢失 .... 4.代码中缺少应有的条件分支处理,导致程序未执行任何操作而退出时,也可能没有释放应释放的内存,这种情况一般是缺少应有的else分支,或switch语句的default分支没有应有的处理。 static void OncePowerCmdHandle( struct HT_Appmsg * msg ) { ... ... pPower_test_answer =(struct _oncepower_test_answer *)GetBuff(sizeof(struct _oncepower_test_answer),__LINE__); if( pPower_test_answer == NULL_PTR ) return; ... ... if (TSS_State[testpsn].state == TEST_DEV_BUSY || TSS_State[testpsn].state == TEST_DEV_ERROR ) {... } else if (TSS_State[testpsn].state == TEST_DEV_IDLE ) {... } // 缺少 else 分支,可能造成 pPower_test_answer 得不到释放 } 造成内存泄漏的情况很多,以上是几种典型的情况。 虽然内存泄露一般出现在异常情况下,毕竟给系统造成很大的隐患,使系统的健壮性降低。测试人员在作代码审查时,对上述几种情况要尤其注意。 【案例1.10.4】 【正 文】在进行SAR的PDU包发收的测试过程中要同时考虑几个边界值,即发送包大小范围[0-Nmax],SAR的PDU包接收的最大值Kmax,MBUF块的大小M.在实测中,将SAR的PDU包接收的最大值设为2000(Kmax=2000B), MBUF的块长设为512(M = 512B),则发送包大小的正确分支的取值为下限0,上限Nmax=2000,然后在0与2000之间随机取若干值,再考虑MBUF的块长,还可增加M倍数的若干选值及其附近值.以上是测试的一般思路,但由于很偶然的机会选择包长2000,及Kmax=2000B,才发现问题.原因如下: MBUF块长512,但块中实际存放数据的只有500(MBUF头上有2个长字,尾部有1个长字共12B只用于块控制),而发送的包长正好是500的整数倍4,由于是整数倍,所以SAR(BT8230)从FREE链上摘成5个MBUF(原因从略),而SAR驱动只知道有4个MBUF,这样到上层用户时,只释放4个MBUF,从而漏掉1个MBUF,经过很短一段时间后,内存即被耗尽.(此问题非常严重,因为在实际运用中,是500的整数倍的PDU包的概率较小,但一旦出现就会发生一次内存泄漏,这样经过若干天或若干月的运行后会使系统崩溃) 以前未发现此问题的原因是因为原来使用的缓冲块长为2048,减去12B的控制信息,实际存放数据的长度为2036.由于只考虑了2048这个值,忽略了2036,所以在选取上下限中的若干值时,选取包的长度是2036的倍数的概率就非常小,因而未发现该问题. 由于测试中一般很难将取值范围中的所有值覆盖全,所以在选取上下限中的若干取值时要格外仔细,考虑的方面尽可能全,因为很有可能其中某些值就是测试边界值.凡是涉及的数字尽量选取,象该例中正确分支的测试边界为0,2000,512及其整数倍,500 及其整数倍,12 及其整数倍等值,它们是必测的边界值,而非可测可不测的随机选取的所谓若干选值. 【案例1.10.5】 【正 文】 ABIS.CPP中的函数rel_ABIS_CCB_conn( )中,在进行消息链表Msg_Queue[ces]的拆链操作时,对于相应的CCB只进行了一次拆链操作,即只拆除了一个节点,如果出现该CCB对应的消息节点不止一个的情况就会出现大量节点不能释放的问题。 if( Msg_Queue[ces].msghead != NULL_PTR )//message buffer notempty { //get first message record pMsgRecord = Msg_Queue[ces].msghead; //release buffer-messages concerning with ccb_no for( index = 0; index < MSGBUFFERNUM; index++ ) { //这里要对pMsgRecord的值进行判断 if( (pMsgRecord != NULL_PTR) && pMsgRecord->CCB_no == ccb_no ) { //free the message buffer if( pMsgRecord == Msg_Queue[ces].msghead )//head Msg_Queue[ces].msghead = pMsgRecord->pnext; else if( pMsgRecord == Msg_Queue[ces].msgtail )//tail { Msg_Queue[ces].msgtail = pPrevMsgRecord; Msg_Queue[ces].msgtail->pnext = NULL_PTR; } else//not head and tail { pPrevMsgRecord->pnext = pMsgRecord->pnext; } //put buffer back to buffer pool if( Msg_Buffer.empty_num == 0 ) { Msg_Buffer.linkhead = Msg_Buffer.linktail = pMsgRecord; pMsgRecord->pnext = NULL_PTR;//这里将 pMsgRecord->pnext置为空 Msg_Buffer.empty_num++; } else { Msg_Buffer.linktail->pnext = pMsgRecord; pMsgRecord->pnext = NULL_PTR;//这里将 pMsgRecord->pnext置为空 Msg_Buffer.linktail = pMsgRecord; Msg_Buffer.empty_num++; } } else if( pMsgRecord == NULL_PTR ) break;//end of if //get next message record pPrevMsgRecord = pMsgRecord; pMsgRecord = pMsgRecord->pnext;//这时pMsgRecord为 NULL_PTR将跳出for循环语句 }//end of for }//end of if 这里在拆除一个节点后导致pMsgRecord为NULL_PTR,再进行判断时将会跳出循环,这样将不能保证所有与同一个CCB有关的节点均被拆除,这时如果与同一个CCB对应的消息节点不止一个则这些消息节点均无法释放,造成可用的节点数不断减少,直接影响系统的建链过程,给系统的稳定带来隐患。 后与开发人员联系,根据这段算法编写小程序验证了该问题,并提出了相应的解决方案,消除了该隐患。 【案例1.10.6】 【正 文】 1、建立一个呼叫,并保持通话。在AM控存监控操作界面中观察通话建立在哪一块FBI板上。 2、将有通话的FBI板拔出,观察通话情况,此时话音中断,但信令仍然保持。观察AM控存监控操作界面和E3M板2K网界面,发现AM侧因为检测到光纤已断,将通话在CTN、E3M板上占用的时隙置为空闲,即在AM控存监控操作界面和E3M板2K网界面观察不到时隙占用情况。 3、分别在30秒、1分钟、3分钟时将拔出的FBI板插回原槽位,发现每次插回FBI板后话音立即恢复。 4、观察BAM上的打印消息,发现打印的各模块占用CTN板大HW上DM时隙的空闲个数比较混乱。打印消息如下图所示: 其中: 1) 由于模块1、2、3、4各占用CTN板上两条大HW,每个DM时隙个数为256(即由两条大HW的两个DM组成,由于与OPT相联的大HW上有两个保留时隙,因此此DM上空闲时隙个数为:254。 2) 由于E3M板只与一条大HW相联,故每个DM上空闲的时隙个数为:128。 本现象对应2个问题:idle_count打印混乱,BM释放故障光路的时隙和对应的CCB、无线信道等资源。 1、idle_count打印混乱是由于函数restore_one_hw中的一些处理不当造成的,以前被当作B型机的历史遗留问题没有重视; 2、B2模块有2条光路,如果断掉其中一条,模块状态不会改变,原B型机程序对此不作任何处理,但应该增加这个功能,以免光路故障导致资源吊死。 解决方法: 问题一: 将函数restore_one_hw中原代码作如下改动: mod_dm[mod][i].tail.tsn = idle_dm_head + 125; ( idle_dm_head == 384 ) ? mod_dm[mod][i].idle_count += TS_PER_DM - 1: mod_dm[mod][i].idle_count += TS_PER_DM - 1; 改为: if ( idle_dm_head != 384 ) { mod_dm[mod][i].tail.tsn = idle_dm_head + 127; mod_dm[mod][i].idle_count += TS_PER_DM; } else { mod_dm[mod][i].tail.tsn = idle_dm_head + 126; mod_dm[mod][i].idle_count += TS_PER_DM - 1; } 问题二分析如下: 目前的模块状态是由IPATH调用DBMS模块的边检查实现的,只要存在一条可用的光路,即认为相邻模块为正常,对于具体的OPT板上的时隙状态的维护没有与呼叫控制的接口。具体的OPT板状态功能的检测是由IPATH完成的,在BM侧没有专门维护OPT和MC2板的模块,将转交OS组处理。 总结: 在拔出FBC板后,通话话音被中断,AM/CM侧已将与被拔出的FBC 板相关的资源全部置为不可用,此时BM侧主机程序也应该与AM/CM侧一致,释放掉所占用的资源,并将原通话的信令连接断开。这可能是由于不同模块的开发人员缺少相互间了解而造成的,即AM/CM侧与BM侧开发人员交流不够。作为测试人员对类似两个或多个模块相关的部分应该充分进行测试,不要想当然,往往是看起来不可能出问题的地方也容易测出问题。 【案例1.10.7】 在进行有关排队指示的系统测试中,先闭塞掉基站的所有业务信道TCH,进行呼叫,再直接挂机或超时释放,发现TC存在中继资源吊死的问题。 由于此问题重现,后经定位分析,发现是ccb超时后收到AIR发来的clear cmd,进入 rel_one_bm_res( )时,由于ccb所登记的CIC还放在pre_occupied_res,并没有放入occuped_res,而rel_one_bm_res()只对存入occuped_res的CIC进行判断,并向AIE发UNBOOKCIC,而没有对存入pre_occupied_res的CIC进行判断,并UNBOOK掉,导致TC的中继资源吊死。应在超时函数或释放函数中对pre_occupied_res的CIC进行处理。 在此过程中,CIC资源还存放在老CCB的pre_occupied_res中,在超时函数或释放函数中均未对pre_occupied_res中的CIC进行处理(即向AIE UNBOOK),导致TC中继资源吊死。 在超时函数RR_time_out()中timer_name为TN_WAIT_ASS_READY时,和释放函数rel_one_bam_res()中增加对CCB的pre_occupied_res中的CIC的判断和释放处理。 在使用资源同时,就要周密地考虑好资源的释放问题,只有这样,才能使我们的系统不断地稳定下来。 资源的释放对于我们的交换机来说是至关重要的,一点点的疏忽都可能最终使我们的交换机因为无资源使用而死掉,要知道,“千里长堤,毁于蚁穴”。 11、防止资源的重复释放 【案例1.11.1】 【正 文】 当进行大话务量呼叫时,在统计代码中出现AIE收到UNBOOK CIC消息时,发现自身电路状态为空闲,出现一个断言。这说明AIE电路电路被误释放了。 这个问题出现的原因有以下几种: 1. RR可能发错了电路号,导致AIE状态错误。 2. AIE可能发起资源核查,失败后将本控制表项释放了。 3. RR可能发起了重复释放操作,导致AIE的某个表项连续收到两个UNBOOK消息。 分析完了可能的情况,就要一一分析定位。 在可能原因一发生的情况下,RR发来的UNBOOK消息所带的AIR连接号和模块号会错误,导致我们会出现断言。而在测试数据结果文件中,没有出现这个断言,因此可能原因一不成立。 在可能原因二发生的情况下,AIE收到资源核查失败消息的数目应该不是零。但是实际情况下统计结果中收到资源核查失败消息的个数为零,说明情况二也不成立。 由上分析,这个问题只可能是由于RR重复释放造成的。但是为何会发生重复释放,这需要进行进一步分析。 从呼叫的正常流程来看,是不会产生重复释放的,因此我们怀疑该问题与异常流程有关。从统计代码中查找异常流程,发现该次统计中BSC内切换流程多次出现问题,具体原因是由于切换过程中在目标小区申请不到信道,产生切换失败造成的。因此集中研究这个流程,发现存在问题如下: 当原小区向目标小区发送内部切换请求消息时,带来了AIR和AIE的各项信息,而目标小区收到这些信息后就将之保存在自身的占用资源中。如果目标侧申请信道失败,就会向源侧发内部切换拒绝消息,而后产生本地释放。由于在释放前目标侧RR没有将占用资源中的AIR和AIE信息清除,因此导致重复释放时对AIR和AIE发起了释放操作。由于AIR释放时有保护机制,所以不会产生问题,而AIE没有保护机制,新CCB就将AIE电路释放掉了。而后当老CCB在通话结束后发起释放时,就产生了重复释放。 从上面分析可以看出,这个问题是由于RR释放流程的错误造成的,因此,我们要对此加以修改,在新CCB释放前将AIR和AIE信息从预占资源中清除。 RR的释放是一个非常复杂的过程,如何正确的整理资源,确保资源的合理释放,这是摆在我们面前的一个艰巨的问题,我们要仔细分析各种可能发生的情况,正确释放各种资源,即不会吊死资源,也不会产生重复释放。 12、公共资源的互斥性和竞用性 【案例1.12.1】 【正 文】 试验环境:CPX8216 CPCI 机架、vxWorks操作系统、Tornado1.0.1调试环境 测试用例:测试板间通信性能。从接口板A向接口板B循环发送消息,通过超级终端观察消息的收发情况。 测试结果:每发送一定数量的消息帧后,会出现发送地址出错现象。 原因分析:接收板回送缓冲区指针给发送板,是采用memcpy单字节拷贝的方式。若发送速度快于接收速度,两板竞用发送板系统总线访问缓冲区指针所在的共享内存,导致数据访问冲突。memcpy过程被打断,即出现发送板读发送地址出错现象。 采用四字节拷贝函数bcopyLongs传送发送缓冲区指针,问题解决。 共享内存的访问设计,除了考虑互斥外,还有总线竞用问题。 【案例1.12.2】 【正 文】 问题描述: 在进行主BCCH载频互助新功能开发的并行联调测试的过程中,发现了以下的问题:在数管台设置“TRX倒换是否允许”为“是”,进行设定整表后,关闭基站其中配有4个TRX的小区的主BCCH所在的TRX电源,发现对应小区重新初始化并成功,也就是载频互助成功。这个时候从后台对该小区所在的站点进行4级复位,同时重新打开之前关闭的该小区的原配主BCCH所在TRX的电源,发现对应小区初始化失败。 问题定位: 在问题定位开始,先是查看了载频互助相关代码在站点初始化流程中的处理。BTSM程序初始化过程中,先是判断这一次初始化之前是否发生过载频互助,若发生过,再判断原配主BCCH(即数据库中实际配置的主BCCH所在的TRX)是否已经恢复(即能正常建立TEI,能正常设置该TRX对应的RC属性,总之能正常开工)。若载频互助发生过,且原配主BCCH所在的TRX(CoTRXGroupForBts[BtsNo].MainTRX)已经恢复,即把之前进行互助的TRX (CoTRXGroupForBts[BtsNo].AidTRX)的数据和原配的主BCCH所在TRX的数据交换回来,并重新进行初始化。表面上看原理应该没有什么逻辑错误,怎么会出现初始化不成功呢? 我们对程序中的每一个可能导致该问题的变量加打印调试程序,然后重现该问题,终于在打印出来的信息中发现在载频互助发生后其互助的主BCCH所在的TRX与实际数据配置主BCCH所在的TRX为同一TRX,这有问题,因为载频互助的实质就是实际数据配置主BCCH所在的TRX不能正常开工而借用其他TRX作为主BCCH。于是我们根据此线索查询了所有BTSM的程序,没有发现问题的根源。于是我们查了最近合进版本的相关模块的程序,终于找出了问题的根源所在。 在载频互助程序中以全局变量ptrBTS_CONFIG_MAP[BtsNo].TRX_no_BCCH_in表示当前实际运行的主BCCH所在的TRX号,是随时变化的;以CoTRXGroupForBts[BtsNo].MainTRX表示原配的主BCCH所在的TRX号,是固定的。两者在系统开工的系统开工的接口函数FetchOneSiteConfig( )中赋了相同的值:该函数的409行有赋值语句CoTRXGroupForBts[BTS_no_temp].MainTRX = ptrBTS_CONFIG_MAP[BTS_no_temp].TRX_no_BCCH_in。以前函数FetchOneSiteConfig()只是在系统开工时才调用过一次,故CoTRXGroupForBts[BTS_no_temp].MainTRX 在系统开工以后是不变的,但是在DBMI同步开发的整改中,作了如下处理:在每一次数据动态设定后,先判断站点下有没有发生过载频互助,若发生过则试图先把目前进行互助的TRX的数据与实际数据配置成主BCCH的TRX的数据倒换回来,然后进行站点初始化。问题就出现在这,在DBMI中认为DB中原配的主BCCH的TRX是ptrBTS_CONFIG_MAP[BTS_no_temp].TRX_no_BCCH_in,而且每次进行站点初始化时都调用函数FetchOneSiteConfig(),这样将导致CoTRXGroupForBts[BTS_no_te

img
音视频开发进阶指南:基于Android与iOS平台的实践

音视频开发进阶指南:基于Android与iOS平台的实践

img
从0到1 开启未来

《从0到1》写的清晰、理性又实际。不只是企业家,每个不满于现状、对世界前景有想法的人都应该读一读这本书。

img
思考的快与慢

经济学大师以在饮水机旁闲谈的方式与我们讨论思考的快与慢,直觉与理性,以及决策中人的重要性,增强了人们对自己的了解。

img
Java从小白到大牛精简版 高清完整PDF版

本书是一本 Java 语言学习教程,读者群是零基础小白,通过本书的学习能够成为 Java大牛。主要内容包括: Java 语法基础、数据类型、运算符、控制语句、数组、字符串、面向对象基础、继承与多态、抽

img
思考,快与慢(超清晰pdf)

在本书中,卡尼曼会带领我们体验一次思维的终极之旅。他认为,我们的大脑有快与慢两种作决定的方式。常用的无意识的“系统1”依赖情感、记忆和经验迅速作出判断,它见闻广博,使我们能够迅速对眼前的情况作出反应。

img
编程匠艺.part1.rar

如果你可以编写出合格的代码,但是想更进一步、创作出组织良好而且易于理解的代码,并希望成为一名真正的编程专家或提高现有的职业技能,那么《编程匠艺——编写卓越的代码》都会为你给出答案。本书的内容遍及编程的各个要素,如代码风格、变量命名、错误处理和安全性等。此外,本书还对一些更广泛的编程问题进行了探讨,如有效的团队合作、开发过程和文档编写,等等。本书各章的末尾均提供一些思考问题,这些问题回顾了各章中的一些关键概念,可以促使你像专家一样思考,从而使本书成为那些渴望作为团队的一分子,职业并高效地编程的新手们的一本绝佳的参考书。 [编辑本段]作者简介   Pete Goodliffe是一位软件开发专家,他在软件“食物链”上从未驻足不前。他在各种各样的项目中使用过许多种语言。他还在教授和指导程序员方面有着丰富的经验,并且常年为ACCU的C Vu杂志(www.accu.org)撰写栏目“编程的职业化”。Pete痴迷于编写出色的、没有错误的代码,这使得他有更多的时间与自己的孩子共度美好时光. [编辑本段]推 荐 序   ——打造开发者的核心竞争力   信息产业几乎是开放程度最高的产业,其发展之快,让身处其中的我们既感到兴奋和激动,也时常困惑和惶恐。信息技术的变化太快、太激烈,一切都在迅速革新当中,作为技术人员,我们今天熟悉并且赖以安身立命的东西,很快就会变成故纸堆里的古董。人生总是需要不断积累才能越走越高的,如果找不到一项可以积累提升、而且被市场所承认的核心竞争力,事业的大厦是建不起来的。于是一个严峻的问题就摆在每一个技术人员的面前——什么才是我们的核心竞争力?   二十年前,人们认为软件开发者的核心竞争力应该体现在他的聪明才智和扎实的基本功上,要精通算法、硬件、编译、计算机体系结构,要有天马行空般的想像力和创造力,能够单枪匹马单打独斗。至于什么团队协作、编码规范、单元测试、最佳实践,都是对天才的不必要的羁绊。   大约到了20世纪90年代中期,风水变了。随着软件的规模越来越大,以及当代操作系统、面向对象技术、软件开发工具、程序库和框架的普及,“平台”和“工具”变得越来越重要了。大多数时候,开发者不需要自己从头实现所有功能,他所依赖的平台已经准备好了现成的组件供使用,有的时候甚至应用程序中最繁琐、最复杂的部分已经由框架或程序库实现好了。因此,人们普遍认为,一个开发者的核心竞争力很大程度上表现为对平台与工具的理解和掌握,并依托这种理解创造性地构筑产品,或者更确切地说,一种创造性地组装产品的能力。你还在为刚刚琢磨出来的一个高效算法而得意吗?旁边那位借助程序库里提供的组件,已经快速地完成了整个功能模块;你发明了一个新的视频格式以及对应的codec?很了不起!不过另一个初出茅庐、连快速傅里叶变换算法都写不出来的团队已经基于sourceforge上的开源组件做出产品来,并且播得满互联网都是了。当然,黑客们仍然有着不可取代的价值,总是能找到自己的位置;但是对于那些强调团队作战、快速行动的企业来说,一个熟练掌握MFC、Delphi、J2EE、ASP.NET或者LAMP的开发者可能更实用,能更有效地为企业带来价值。因此,这样的程序员便一时成为企业的宠儿,众人眼中的高手。   然而不到十年下来,问题又出现了。流行的平台和工具如走马灯般你方唱罢我登场:昨天还在为领悟了MFC、Delphi而沾沾自喜,今天就发现应用主流已经是Web了;刚刚啃完艰深的EJB2,抬眼一看却发现它已经被Spring的拥趸们批倒批臭了;上个月还是冲在敏捷Java领域的改革派,这个月就被一群嘴上无毛的RoR粉丝给划到改革的对立面去了;ASP.NET还没学踏实呢,微软又准备好ASP.NET MVC、ASP.NET AJAX、Silverlight等等一大堆新玩意让你啃了。这样下去,什么时候是个头?把自己的核心竞争力建立在这些转瞬即逝的昙花上,难道不是把有限的生命投入到无限的瞎折腾之中吗?难道只有钻到一间舒舒服服的大公司里,到了三十多岁就寻求所谓的“转型”,顺着一条十分确凿的“职场路线”攀或是混,最后在公司没有倒闭或者自己没有被“战略裁员”的幸运之下头顶玻璃天花板光荣退休,才是中国程序员的归宿?什么才是程序员可以长期积累,不断提高,不但足以安身立命,而且能够实现梦想、成就事业的核心竞争力呢? 回答好这个问题,对于今天的开发者来说,可能比掌握和精通某项具体技术意义重大得多。   在我看来,当代程序员的核心竞争力至少应该体现在这么几点上:有扎实的基本功,活跃的想像力与创造力,快速的学习能力,具备行业和领域知识,以及专业的软件工艺能力。而在这其中,专业软件技能是最基本、也是最重要的一项。   什么是专业软件技能呢?就是正确地开发软件的能力,更具体地说,是通过一系列有组织的、有原则、流程化、可检验、可重复的实践行为,协作式开发高质量程序的能力。对于一个程序员来说,这是你的看家老本,对于一个软件团队来说,这是你们的立足之基。算法不会,可以查资料慢慢掌握;不理解行业,可以边做边学,逐渐深入;缺乏创新,可以站在巨人肩膀上耐心摸索;甚至基本功不足,也可以自我弥补,可是如果没有做软件的专业态度和实践技能,没有制作合格软件的工艺水平,连一段高质量的程序都写不出来,试问你还剩下什么?   经过近三十年的时间,人们最终认识到,在规模化团队协作的情况下,决定软件产品质量的不再是个人的聪明才智,也不是靠什么神仙技术,而是团队的工艺实践。是否在一开始就形成了开发计划?是否对这个计划进行了必要的确认、维护和跟踪?必要的规范文档是否撰写了?是否形成了合理的架构?是否恰当地选择了开发工具和编程语言?是否建构了适于团队渐进协作的良好的工具和工作平台?是否一开始就形成了有力的缺陷核查、控制和跟踪策略并始终严格地执行?是否制定了连续一致的编码标准,并且通过诸如代码走查等加以保证?是否有完整的测试制度?是否具有明确的性能优化和软件安全性保障过程?是否在整个生命周期贯彻了严格的版本管理、配置管理、发布管理和软件维护退役管理措施?这些实实在在的问题,是需要耐心与细心地用具体实践细节来回答的。当一个团队对于这些问题都给出了明确而一致的回答并且用行动来执行的时候,他们就是一个专业的、具有核心竞争力的团队。而当一个个体开发者能够对这些问题具备正确的观念,并且通过施加自己的影响力促进团队向正确的方向前进的时候,他就是一个具有核心竞争力的开发者。一个具有核心竞争力的团队和开发者,是可以不断进步的,是具备把握机遇的能力的;一旦时机合适,他们就完全有可能实现更大的目标。   十多年以前国内外软件界对工艺的问题并不重视。大部分人要么执迷于技术本身,指望某一天一个面向某某的技术能够一劳永逸的解决软件开发中的所有问题,要么就是把问题大而化之为“软件工程”,企图以指令性的方式,在宏观的层面上用管理取代工艺。在这两个方向上,程序员要么被视为可以充分放纵的孤胆英雄,要么被视为伟大编程技术最终出现之前不得不存在的过渡品,或者管理指令的机械的执行体,“人”的维度消失了。这种对于人和工艺细节的忽视也体现在技术著作方面。软件工程、面向对象、编程技巧和产品手册之类的著作汗牛充栋,而认真谈到软件工艺的书屈指可数。   直到20世纪90年代中期,随着一些软件产品的规模越来越大,微软率先认识到工艺问题的重要性,于是出版了诸如《代码大全》、《编写清晰的代码》等一系列探讨这一问题的著作。直到20世纪90年代末期,当整个工业界从面向对象和软件工程的幻影泡沫中走出来之后,才开始认真全面地审视软件工艺的问题,而且通过敏捷运动、把软件工艺的重要性和基本实践提到了一个令人瞩目的位置上。事实上,敏捷运动可以认为是软件工艺的复兴运动。此外,随着《代码大全2》、《软件工艺》、《代码阅读》、《程序员修炼之道》等经典作品的出版,在技术图书领域也陆续出现了一批专门探讨软件工艺的著作。这本《编程匠艺》也是这个领域中的一本佳作。   本书是一部全面讨论软件构造工艺实践的著作,从软件开发的计划到架构设计,从编码风格规范到软件缺陷的检测与管理,从程序员工具箱的配备到团队协作精神的塑造,这本书都给予了翔实、风趣而具有启发性的讨论。这些讨论,既有原则性、理论性一面,也有技术性的具体建议,对于团队领导者、高级开发者和每一个希望快速进步的程序员具有明确的指导意义。如果读者认同软件工艺的重要性,那么可以说这本书是帮助读者建构自己核心竞争力的一本难得的作品。特别值得一提的是,这本书中文版的翻译流畅自然,在很多地方都体现出译者的认真态度和翻译功力。对于一本翻译自英文的技术著作来说,这无疑是一个大大的加分。   当然,一本书的覆盖面和功效毕竟是有限的,核心竞争力的确立和建构归根到底是一个艰苦实践的过程,不同性格的人也一定有着不同的目标和方式。但是我相信,对于有心人来说,只要我们不断地探索和实践,都会获得自己的核心竞争力,做一个有准备的人,争取和等待机会的垂青,最终实现自己的人生目标。   读此书有感而发,借题发挥,是为评论。 [编辑本段]译 者 序   作为从事软件开发的程序员,你肯定遇到过这样的情况:自认为完美的代码,在项目快要结束的时候,却总是会发现还有好多内容需要修改。更有甚者,由于人员的变动,那些他们遗留下来的“老代码”,作为时间留给程序员与项目组的最大遗产,却可能会成为项目组的灾难。   除了受制于人类自身的缺陷之外,还有由于组织而带来的问题,如客户需求不断变更、必须在有限的时间和预算之内完成项目,来自内部所谓“项目管理”的种种压力,等等。天哪,这些问题我们绝大部分人都赶上了。   列宁曾在监狱中写下了《怎么办?》,指导了俄国的十月革命。而在软件业,从一代宗师Frederick P. Brooks的《人月神话》开始,就在找“怎么办”这个“银弹”了。然而,“狼来了”在多次被喊出来后,已经很少有人相信了。我们必须承认,这些都是根本层面的问题,目前还不能得到解决。但是,本书的作者Pete Goodliffe认为,至少我们可以采取一些方式,减少一些开发上的痛苦。因为,除了开发,人生还有许多更为美好的事物在等着我们。我们这次也可以高喊“银弹来了”。没有最好,只有更好,谁知道这次不是真的呢?   著名国画大师齐白石在年轻的时候,曾经做过木匠。据说有一次他和师傅去给地主干活,在路上迎面走来另外一对木匠师徒。齐先生的师傅说,赶紧给别人让路。师徒俩站在路边,老师恭敬地目送那两人渐渐走远。齐白石不解,问师傅:同是木匠,你我师徒为什么要给他们让路。老师傅回头说:为什么?别人是做细活的,我们是做粗活的。   Pete Goodliffe在业界的年头快要超过好多人的年龄了,此君曾经涉猎多个领域、不同的编程语言以及多种架构,并且曾经在采用不相同流程的公司里从事开发。在本书中,他把多年压箱底的一些观念想法和技巧告诉了大家,这些都是时间与智慧的结合,相信无论是开发人员、项目经理甚至测试人员,都可以从中发现阿里巴巴开启金库的钥匙。   那么本书有什么特色呢?对于想了解内容的普通读者来说,本书至少有以下特点:   1.贴近实际 《编程匠艺——编写卓越的代码》是本书的书名,但也是作者的用心所在。人生有三个境界,最后一个就是“看山是山,看水是水”。这是废话吗?当然不是,作者对此给出了最好的解答。作为程序员,我们最喜欢争论不同工具、平台、方法之间的优劣。而作者却通过多年经验,力图告诉我们应该如何提高质量,并成为一名优秀的程序员。这些方法就像点石成金的手指,它们是方法论,而不是针对具体的工具或者平台的说教。我们现在所缺的,恰恰是这些能使自己更进一阶的手段,而不是那些特殊的技术细节。   2.内容丰富翔实 很少有一本书能涵盖如此多的领域,并且还如此扎实。作为一名程序员,我们可能永远无法达到完美。而需要处于一种持续不断地提高的状态,总会有更多的东西需要学习。那么下一步应该做什么呢?这里就有答案。   3.可作为“秘要心法” 本书不仅适合入门者,也适合需要提高的开发人员,以及那些想管理好所谓代码猴子的项目经理们。与《项目经理案头手册》一样,这本书也将成为每人的案头手册或者枕边书,可以作为应急或者提升的手段。如果以后碰到了问题,可以随时参阅相关的章节。   4.心态决定一切 这句话对吗?有了良好心态,不一定行,如果没有,肯定不行。我们常常羡慕于老外以四五十岁的年纪仍然能继续从事编程,为什么我们不行呢?可能不同的读者都会找到属于自己的答案!Pete Goodliffe具有宽阔的视野,扎实的基础,广泛的爱好,带有一种程序员应该具有的高雅和恬淡。这正是我们这个浮躁的时代中积极探索的一代程序员所不具备的。   最后禁不住要抱怨一下,作者Pete Goodliffe以他丰富的阅历和爱好,给译者带来了不小的麻烦,比如出于它对于音乐的爱好,所有章节的标题都来自英国的歌曲名称。为了理解上的直观,我们在翻译的过程中采取的是“信达雅”中的“雅”,以保证国内读者能很快切入主题。本书每章开始和行文的过程中,作者都引用了历史上或者现在社会中一些名人的名言,这给翻译增加了不少的难度,但是由于贴切精辟,这些名言也可称之为点睛之笔。尤为值得高兴的是,此君对我中华文化竟然也有一定的造诣,孔夫子和老子的哲理名言竟然多次出现,而且能够贴切地表达出这些圣人的思想对软件开发有哪些启示,这非常不简单,难为了作者,也着实难为了译者。从外国作者的笔下,让我们着实体会到了自己国家的文化源远流长。这从一个侧面也体现出东海西海,千圣一心。   此书给了我们一个快速成功进阶的好范例。我觉得它更像一个程序员的入门或者修行心法。从此入门,我们可以少走很多弯路。同时,我们也要争取像佛经中“般若波罗密”所讲的那样:大智慧到彼岸,最后连佛法也像渡河的筏子一样,成佛后立即丢弃。我更希望的是,看过此书的读者们,最后能够拍案而起,大声说:我可以了。 [编辑本段]图书目录   第I篇 代码表面第一部分   第1章 善于防守——健壮代码的防御性编程技巧 3   1.1 向优秀的代码前进 4   1.2 设想:最坏的选择 4   1.3 什么是防御性编程 6   1.4 又大又坏的世界 8   1.5 防御性编程技巧 8   1.5.1 使用好的编码风格和合理的设计 9   1.5.2 不要仓促地编写代码 9   1.5.3 不要相信任何人 10   1.5.4 编码的目标是清晰,而不是简洁 10   1.5.5 不要让任何人做他们不该做的修补工作 11   1.5.6 编译时打开所有警告开关 11   1.5.7 使用静态分析工具 12   1.5.8 使用安全的数据结构 12   1.5.9 检查所有的返回值 13   1.5.10 审慎地处理内存(和其他宝贵的资源) 13   1.5.11 在声明位置初始化所有变量 14   1.5.12 尽可能推迟一些声明变量 14   1.5.13 使用标准语言工具 14   1.5.14 使用好的诊断信息日志工具 15   1.5.15 审慎地进行强制转换 15   1.5.16 细则 15   1.6 约束 16   1.6.1 约束的内容 17   1.6.2 移除约束 18   1.7 总结 20   1.8 另请参见 20   1.9 思考 21   1.9.1 深入思考 21   1.9.2 结合自己 22   第2章 精心布局——源代码的版面和样式 23   2.1 什么是关键 24   2.2 了解你的读者 25   2.3 什么是好的样式 26   2.4 使用括号 26   2.4.1 K&R括号风格 27   2.4.2 悬挂式的括号风格 27   2.4.3 缩进的括号风格 29   2.4.4 其他的括号风格 29   2.5 主宰一切的风格 30   2.6 内部风格(以及在哪里使用它们) 31   2.7 设立标准 33   2.8 正义的战争 35   2.9 总结 35   2.10 另请参见 37   2.11 思考 37   2.11.1 深入思考 37   2.11.2 结合自己 38   第3章 名正言顺——为有意义的事物起有意义的名称 39   3.1 为什么我们应该恰当地命名呢 41   3.2 我们对什么进行命名 41   3.3 名字游戏 42   3.3.1 描述性 42   3.3.2 技术上正确 42   3.3.3 符合语言习惯 43   3.3.4 恰当 43   3.4 具体细节 44   3.4.1 命名变量 44   3.4.2 命名函数 45   3.4.3 命名类型 46   3.4.4 命名名字空间 47   3.4.5 命名宏 48   3.4.6 命名文件 48   3.5 玫瑰不叫玫瑰 49   3.5.1 保持前后一致 50   3.5.2 利用上下文 51   3.5.3 使用对你有利的名称 51   3.6 总结 52   3.7 另请参见 53   3.8 思考 53   3.8.1 深入思考 54   3.8.2 结合自己 55   第4章 不言自明——编写“自文档化”代码的技巧 57   4.1 自文档化的代码 59   4.2 编写自文档化代码的技术 61   4.2.1 使用好的样式编写简单的代码 61   4.2.2 选择有意义的名称 62   4.2.3 分解为原子函数 62   4.2.4 选择描述性的类型 63   4.2.5 命名常量 63   4.2.6 强调重要的代码 64   4.2.7 分组相关信息 64   4.2.8 提供文件头 64   4.2.9 恰当地处理错误 65   4.2.10 编写有意义的注释 65   4.3 实用的自文档化方法 66   4.3.1 文学性编程 66   4.3.2 文档化工具 67   4.4 总结 69   4.5 另请参见 70   4.6 思考 71   4.6.1 深入思考 71   4.6.2 结合自己 72   第5章 随篇注释——如何编写代码注释 73   5.1 什么是代码注释 74   5.2 注释看上去是什么样的 75   5.3 多少注释是恰当的 75   5.4 注释中应该有些什么 76   5.4.1 解释为什么,而不是怎么样 76   5.4.2 不要描述代码 76   5.4.3 不要取代代码 76   5.4.4 确保注释有用 77   5.4.5 避免分心 78   5.5 实践 79   5.6 从审美的角度看注释 80   5.6.1 一致性 80   5.6.2 清晰的块注释 80   5.6.3 缩进的注释 81   5.6.4 行尾注释 81   5.6.5 帮助你阅读代码 81   5.6.6 选择一种维护成本较低的风格 82   5.6.7 分隔板 82   5.6.8 标志 83   5.6.9 文件头注释 83   5.7 使用注释 84   5.7.1 帮助你编写例行程序 84   5.7.2 错误修正通告 85   5.7.3 注释过时 85   5.7.4 维护和空洞无物的注释 86   5.8 总结 86   5.9 另请参见 87   5.10 思考 87   5.10.1 深入思考 88   5.10.2 结合自己 88   第6章 人非圣贤——处理不可避免的情况——代码中的错误情形 89   6.1 从何而来 90   6.2 错误报告机制 91   6.2.1 不报告 91   6.2.2 返回值 92   6.2.3 错误状态变量 93   6.2.4 异常 93   6.2.5 信号 95   6.3 检测错误 95   6.4 处理错误 96   6.4.1 何时处理错误 97   6.4.2 可能的反应 98   6.4.3 代码示例 100   6.5 使地狱浮现 104   6.6 管理错误 105   6.7 总结 106   6.8 另请参见 107   6.9 思考 107   6.9.1 深入思考 107   6.9.2 结合自己 108   第II篇 代码的神秘生命   第7章 欲善其事,先利其器——使用工具构建软件 111   7.1 什么是软件工具 112   7.2 为什么要在意工具 114   7.3 使工具发挥作用 115   7.3.1 了解它能做些什么 115   7.3.2 学习如何驾驭它 116   7.3.3 了解它适合什么任务 116   7.3.4 检查它是否可用 116   7.3.5 找到了解更多信息的途径 117   7.3.6 查明新版本何时出现 117   7.4 哪个工具 117   7.4.1 源代码编辑工具 118   7.4.2 代码构建工具 120   7.4.3 调试和调查工具 123   7.4.4 语言支持工具 124   7.4.5 其他工具 125   7.5 总结 126   7.6 另请参见 127   7.7 思考 128   7.7.1 深入思考 128   7.7.2 结合自己 128   第8章 测试时代——测试代码的魔术 129   8.1 反思现实 131   8.2 谁、是什么、何时以及为什么 132   8.2.1 我们为什么要测试 132   8.2.2 谁来进行测试 133   8.2.3 测试的内容有些什么 133   8.2.4 何时进行测试 134   8.3 测试并不难…… 135   8.4 测试的类型 138   8.5 选择单元测试用例 142   8.6 为测试而设计 144   8.7 看!不要用手! 144   8.8 面对故障该怎么办 145   8.9 你能管理它吗 146   8.9.1 缺陷跟踪系统 147   8.9.2 bug审查 148   8.10 总结 149   8.11 另请参见 150   8.12 思考 150   8.12.1 深入思考 150   8.12.2 结合自己 151   第9章 寻找缺陷——调试:当事情进展得不顺利时该怎么办 153   9.1 生活的真相 154   9.2 bug的种类 155   9.2.1 从远处看 155   9.2.2 从近处看 156   9.2.3 从更近处看 158   9.3 消灭害虫 160   9.3.1 地下之路 161   9.3.2 地上之路 161   9.4 搜寻bug 162   9.4.1 编译时错误 162   9.4.2 运行时错误 164   9.5 如何修正缺陷 167   9.6 预防 169   9.7 除蜂剂、驱虫剂、捕蝇纸 169   9.7.1 调试器 169   9.7.2 内存访问校验器 170   9.7.3 系统调用跟踪 170   9.7.4 内核转储 170   9.7.5 日志 170   9.8 总结 171   9.9 另请参见 172   9.10 思考 173   9.10.1 深入思考 173   9.10.2 结合自己 173   第10章 代码构建——将源代码转换为可执行代码的过程 175   10.1 语言障碍 176   10.1.1 解释型语言 177   10.1.2 编译型语言 178   10.1.3 字节编译型语言 179   10.2 小题大做 179   10.3 构建软件版本 181   10.4 怎样才算是一个优秀的构建系统 184   10.4.1 简洁 184   10.4.2 一致 184   10.4.3 可重复和可靠 185   10.4.4 原子性 186   10.4.5 能够应付错误 187   10.5 技术细节 187   10.5.1 目标的选择 187   10.5.2 内务处理 189   10.5.3 依赖关系 189   10.5.4 自动构建 190   10.5.5 构建配置 191   10.5.6 递归地使用make 192   10.6 请发布我吧 192   10.7 构建大师是全能的吗 194   10.8 总结 195   10.9 另请参见 195   10.10 思考 196   10.10.1 深入思考 196   10.10.2 结合自己 196   第11章 追求速度——优化程序和编写高效的代码 199   11.1 优化是什么 200   11.2 是什么使代码不尽如人意 201   11.3 为什么不进行优化呢 202   备选方案 204   11.4 为什么要进行优化 205   11.5 优化的具体细节 206   11.5.1 证明你需要进行优化 206   11.5.2 找出运行得最慢的代码 207   11.5.3 测试代码 208   11.5.4 优化代码 209   11.5.5 优化之后 209   11.6 优化的技术 210   11.6.1 设计更改 210   11.6.2 代码更改 213   11.7 编写高效的代码 217   11.8 总结 219   11.9 另请参见 219   11.10 思考 220   11.10.1 深入思考 220   11.10.2 结合自己 221   第12章 不安全感综合症——编写安全的程序 223   12.1 危险 224   12.2 敌人 226   12.3 借口,都是借口 228   12.4 感到很脆弱 229   12.4.1 不安全的设计和体系结构 229   12.4.2 缓冲溢出 229   12.4.3 嵌入的查询字符串 230   12.4.4 竞争状况 231   12.4.5 整数溢出 231   12.5 防范措施 232   12.5.1 系统安装技术 233   12.5.2 软件设计技术 234   12.5.3 代码实现技术 235   12.5.4 规程技术 236   12.6 总结 236   12.7 另请参见 237   12.8 思考 238   12.8.1 深入思考 238   12.8.2 结合自己 238   第III篇 代码的形成过程   第13章 崇尚设计——如何创作出优秀的软件设计 241   13.1 边设计边编程 242   13.2 我们要设计什么 243   13.3 为什么这么忙乱 244   13.4 良好的软件设计 245   13.4.1 简洁 246   13.4.2 优雅 247   13.4.3 模块化 247   13.4.4 良好的接口 248   13.4.5 可扩展性 251   13.4.6 避免重复 251   13.4.7 可移植性 252   13.4.8 符合语言习惯 252   13.4.9 良好地文档化 253   13.5 如何设计代码 253   13.5.1 设计方法和过程 254   13.5.2 设计工具 255   13.6 总结 257   13.7 另请参见 258   13.8 思考 258   13.8.1 深入思考 258   13.8.2 结合自己 259   第14章 软件体系结构——奠定软件设计的基础 261   14.1 什么是软件体系结构 262   14.1.1 软件蓝图 262   14.1.2 视图 263   14.1.3 在何时和何处进行体系结构设计 264   14.1.4 用体系结构来做什么 265   14.1.5 关于组件和连接 266   14.2 什么是良好的体系结构 268   14.3 体系结构风格 269   14.3.1 没有体系结构 269   14.3.2 分层的体系结构 270   14.3.3 管道和过滤器体系结构 271   14.3.4 客户端/服务器体系结构 271   14.3.5 基于组件的体系结构 273   14.3.6 框架 274   14.4 总结 275   14.5 另请参见 276   14.6 思考 276   14.6.1 深入思考 276   14.6.2 结合自己 277   第15章 改良与革命——代码是如何成长的 279   15.1 软件腐烂 281   15.2 警告信号 282   15.3 代码是如何成长的 284   15.4 相信不可能之事 286   15.5 对此我们可以做些什么 287   15.5.1 编写新代码 287   15.5.2 维护现有代码 288   15.6 总结 290   15.7 另请参见 290   15.8 思考 291   15.8.1 深入思考 292   15.8.2 结合自己 292   第IV篇 “一群”程序员第一部分   第16章 代码猴子——培养正确的编程态度和方法 295   16.1 各种各样的猴子 296   16.1.1 卖力工作的程序员 297   16.1.2 代码猴子 298   16.1.3 权威 299   16.1.4 半权威 300   16.1.5 傲慢的天才 300   16.1.6 牛仔 302   16.1.7 规划者 302   16.1.8 老前辈 303   16.1.9 狂热者 304   16.1.10 单线条程序员 305   16.1.11 拖沓者 306   16.1.12 勉强的团队领导 306   16.1.13 你 307   16.2 理想的程序员 308   16.3 那么该怎么办 308   16.4 最愚蠢的人 309   16.5 总结 310   16.6 另请参见 310   16.7 行为表格 311   16.8 思考 312   16.8.1 深入思考 312   16.8.2 结合自己 312   第17章 团结就是力量——团队合作与个人程序员 315   17.1 我们的团队——概览 316   17.2 团队组织 318   17.2.1 管理方法 318   17.2.2 责任划分 318   17.2.3 组织和代码结构 320   17.3 团队合作工具 320   17.4 团队疾病 322   17.4.1 巴别塔 322   17.4.2 独裁制 324   17.4.3 民主制 325   17.4.4 卫星站 327   17.4.5 大峡谷 329   17.4.6 流沙 330   17.4.7 旅鼠 332   17.5 良好团队合作的个人技巧和特点 333   17.5.1 沟通 333   17.5.2 谦虚 334   17.5.3 处理冲突 334   17.5.4 学习和适应能力 335   17.5.5 了解你的不足之处 336   17.6 团队合作原则 336   17.6.1 集体代码所有制 336   17.6.2 尊重别人的代码 337   17.6.3 编码准则 337   17.6.4 定义成功 337   17.6.5 定义责任 338   17.6.6 避免倦怠 338   17.7 团队的生命周期 339   17.7.1 团队的创建 339   17.7.2 团队的成长 341   17.7.3 团队合作 342   17.7.4 团队结束 343   17.8 总结 345   17.9 另请参见 346   17.10 行为表格 347   17.11 思考 348   17.11.1 深入思考 348   17.11.2 结合自己 348   第18章 安全措施——源代码控制与自我控制 349   18.1 我们的责任 350   18.2 源代码控制 351   18.2.1 修订控制 352   18.2.2 访问控制 353   18.2.3 处理代码库 354   18.2.4 在代码树上创建分支 354   18.2.5 源代码控制简史 356   18.3 配置管理 356   18.4 备份 358   18.5 发布源代码 359   18.6 应该将源代码放在哪里 360   18.7 总结 361   18.8 另请参见 362   18.9 思考 363   18.9.1 深入思考 363   18.9.2 结合自己 363   第V篇 开发过程的组成部分第一部分   第19章 注意细节——编写软件规范 367   19.1 规范到底是什么 368   19.2 规范的类型 369   19.2.1 需求规范 371   19.2.2 功能规范 373   19.2.3 系统体系结构规范 373   19.2.4 用户界面规范 374   19.2.5 设计规范 374   19.2.6 测试规范 375   19.3 规范应当包含哪些内容 376   19.4 规范编写过程 379   19.5 我们为什么会不编写规范 381   19.6 总结 383   19.7 另请参见 383   19.8 思考 384   19.8.1 深入思考 384   19.8.2 结合自己 384   第20章 代码审查——执行代码审查 385   20.1 什么是代码审查 386   20.2 何时进行审查 387   20.2.1 是否要进行审查 388   20.2.2 审查哪些代码 389   20.3 执行代码审查 389   20.3.1 代码审查会议 390   20.3.2 集成审查 392   20.4 审查你的态度 393   20.4.1 作者的态度 393   20.4.2 审查人员的态度 394   20.5 完美的代码 395   20.6 代码审查之外 396   20.7 总结 397   20.8 另请参见 397   20.9 清单 398   20.10 思考 399   20.10.1 深入思考 399   20.10.2 结合自己 399   第21章 时间估计——软件时间范围估计的魔术 401   21.1 在黑暗中摸索 402   21.2 为什么估计这么困难? 403   21.3 压力之下 405   21.4 实用的估计方法 406   21.5 计划游戏 409   21.6 坚持! 412   21.7 总结 415   21.8 另请参见 415   21.9 思考 416   21.9.1 深入思考 416   21.9.2 结合自己 416   第VI篇 从高处鸟瞰   第22章 程序秘方——代码开发的方法和过程 419   22.1 编程风格 420   22.1.1 结构化编程 421   22.1.2 面向对象的程序设计 422   22.1.3 函数式编程 423   22.1.4 逻辑编程 424   22.2 烹饪方法:做什么与怎样做 424   22.3 开发过程 425   22.3.1 混乱 426   22.3.2 瀑布模型 427   22.2.3 SSADM和PRINCE 429   22.3.4 V模型 430   22.3.5 原型设计 430   22.3.6 迭代和增量开发 432   22.3.7 螺旋模型 432   22.3.8 敏捷的方法 433   22.3.9 其他开发过程 434   22.4 已经够了! 435   22.5 选择一种过程 436   22.6 总结 437   22.7 另请参见 438   22.8 思考 438   22.8.1 深入思考 438   22.8.2 结合自己 439   第23章 编程领域大观——不同的编程分支 441   23.1 应用程序编程 442   23.1.1 塑装软件 443   23.1.2 定制应用程序 444   23.2 游戏编程 445   23.3 系统编程 446   23.4 嵌入式编程 447   23.5 分布式编程 450   23.6 网络应用程序编程 451   23.7 企业编程 453   23.8 数字编程 454   23.9 那又怎样 455   23.10 总结 456   23.11 另请参见 456   23.12 思考 457   23.12.1 深入思考 457   23.12.2 结合自己 457   第24章 下一步呢——结果好就一切都好 459   但下一步该做什么呢? 460   答案和讨论 463   参考书目 559   索引 564 [编辑本段]图书章节   第7章 欲善其事,先利其器——使用工具构建软件   任何胆敢使用超乎自己力量的装置,都会身陷危险。   ——J.R.R.托尔金(J.R.R. Tolkien)   要想成为一位多产的艺人,你需要有一套顺手的工具。水暖工工具箱里的东西可以帮助他完成任何任务,要不然你就不会在下次家里的水龙头漏水时去叨唠他了。   只是拥有这些工具还不够,它们的质量也很重要。差劲的工具会让人对优秀的工匠感到失望。无论你的水暖工有多能干,如果压缩阀不好,也会到处都是水。   当然,是你对这些工具的使用使你成为一名杰出的工匠。工具本身什么也做不成。在电动工具出现之前,木匠们就已经能做出精美的家具了。工具相对而言是基础的,使用工具的技能才是创造精美物品的关键。   编程也是同样的道理。要把工作做好,你需要得到一套适当工具的支持;这应该是一套让你充满信心的工具,你知道如何使用它们,对你所遇到的工作也非常适用。要创造出非凡的代码,不仅需要有技艺精湛的编程高手,还要有好用的工具和灵活运用这些工具的能力。   这是一个重要的问题。你使用工具的方式可以看出你是否能成为一名真正多产的程序员。在极端的情况下,这些工具可以提供决定你的项目成功与否的简化操作。软件工厂那不懈的前进步伐,要求你紧紧抓住任何可以帮助你编写更好的代码,以及更快和更可靠地编写代码的工具。   其他章节会包含一些涉及某种特定工具的内容。本章我们将把软件工具作为一个整体来讨论。编程是一项没有工具就无法进行的工作。我们日复一日地使用着工具,使用编译器就像使用开罐器一样自然,没有经过太多的思考。如果它运转正常,就没有任何问题,但是当它发生了故障(或者你需要开启一个奇形怪状的罐头)时,不管开罐器有多高档,你都会被卡住。一个简单便宜但是能用的开罐器要好过一个外表华丽构造复杂但是不能用的装置。

img
ZFiver_Version_09-1220-1827

ZFiver Version 09-1220-1827 此版改进了内核引擎并修正了启动程序时的“网络错误”提示。 ------------------ ZFiver QQ五子棋机器人 功能简介: 1. 针对QQ游戏平台五子棋:能够代替人进行思考和落子,替你战胜对手。同时支持无禁手和禁手规则。 2. 具有四个不同的“大脑”:就是四个不同的引擎,各具特色,可以应对不同的对手! 3. 设置难度:机器人下棋当然也需要进行“思考”,和人一样,思考得越久,下的水平越高。所以你要设置合适的难度,如果碰到水平并不是特别优秀的玩家设置较低的难度就可以,这样落子的速度较快,反之如果碰到难得一见的高手,就应设置较高的难度,虽然想的时间会变长一些,但战胜的把握更大。 4. 获胜预先提示:当机器人觉得你即将获胜的时候,它会进行提示! 5. 走子延时:机器人有时会下得很快,最快可以在对手落子后的0.5秒内出击。但如果总是这么快容易让对手发现不是真人在下棋,所以适当延长走棋时间。 6. 人机模式:你可以把机器人当作你的一个小军师,让他帮你下一步,有时候合作也很有意思哦。 7. 收拾残局(仅“推荐”引擎有此功能):即在游戏已经开始的情况下继续替你战斗,这是一个非常实用的功能。另外有一个特殊用法:如果你同意悔棋,这个时候棋盘上的棋子就改变了,可以先中止机器人操作然后再点“自动模式”。 8.随开:就是执黑子时随机任意开局,不选它时,执黑开局的第一子都是走中心。 9.挂机:在无人值守的情况下自动对战。试想,如果你将房间设置改为只同“分差不超过100的玩家”进行游戏,找一个热闹的房间和抢手的桌子,也许几个小时后你就升了好几段(当然前提是这个过程中你没有被蓝钻踢掉)! -------------------- 注意事项: 1. 因为本机器人使用了部分图形识别的方法,所以在使用机器人的期间,请切记,游戏过程中,游戏窗口不要被其他的窗口遮挡,此外在游戏过程中也尽量不要移动游戏窗口,也最好不要进行其他的操作,否则有可能导致机器人出错。 2. 一般情况下游戏窗口和机器人的打开顺序没有特别讲究,可以先开游戏也可以先开机器人。 3. 自动模式:只需要在游戏窗口已经打开的状态下,直接点击机器人上的“自动模式”按钮,即可开始自动对战。 4. 如果你想游戏时同时玩QQ等聊天软件,强烈建议你使用“虚拟桌面”软件,把本软件和游戏在同一虚拟桌面上启动,这样就不会互相干扰,也就避免了软件冲突,减少了软件出错的机会。 ----------------------- 关于四个引擎水平的特点: 1. 推荐:反应速度较快,棋力较强,具有最多的功能(支持收拾残局以及获胜提示)。 推荐设置:5~30 2. 敏锐:当设置思考时间为5到10时左右时具有最强的棋力,当对手出狠招的时候会进行提示(比如对手必胜开局),同时支持获胜提示。 推荐设置:5~10 3. 强力:在四者中具有最强的棋力,但反应较慢而且不支持其他功能,一般不使用,推荐只在遇到难得一见的对手时使用。 推荐设置:5~20 4. 禁手:支持有禁手规则。 推荐设置:5~20 ------------------------- 获取试用激活码: 每个人都可以享受 1 天的免费试用。 ------------------------- 获得正式激活码: 注册ZFiver费用为10元/月、 注册后在有效期内可以免费升级。 交费方式:1、支付宝(淘宝网) 2、财富通(腾讯QQ) 3、网银转账 4、中国移动手机充值卡 联系QQ:48377000 邮箱:48377000@qq.com ZFiver最新消息:http://48377000.qzone.qq.com/ ---------------------------

img
SQL Server 2000一键10秒极速安装.part1

由于我只能上传20M的附件,而这个安装包有83M,所以被分成了5个文件,每个1分,共5分。你会每次至少省下15分钟的安装SQL的时间 5个文件可以在这个网页中找到:http://dhf104109.download.csdn.net/ 我是做用友T系列管理软件的,之前被SQL严重困扰, 由于客户的电脑环境不同,经常出现无法安装的情况, 甚至因为安装演示版时无法正常安装SQL而白白丢失过客户。 几经思考研究,终于制成了这个一键安装版, 安装过程只需要十秒左右(视电脑而定), 与同事们分享,百试百灵,现在已经成功安装了一百次以上,没有不成功的。 原理:其实只是提取了SQL server 2000安装后写入硬盘的文件及注册表信息等,所以只有83M, 比原来的安装包几百M小了非常多,但大家不要怀疑,这些绝对是完整的所有文件了。 将提取的文件制成了这个后缀为exe的自解压文件, 安装过程其实只是把这些文件还原到你的电脑,所以速度非常快。 使用方法: 1、下载后,解压,双击“SQL Server 2000一键10秒极速安装.exe”进行安装。 2、等待15秒左右,重启电脑。由于有些电脑比较慢,建议等待一分钟再重启最保险。 必要说明: 1、由于SQL运行环境要求计算机名不可以带有标点符号,所以安装时顺便把计算机名改成了“SQLSERVER” 2、考虑到数据安全,SQL安装目录改为了D:\Program Files\Microsoft SQL Server(原来是在C盘中)

img
SQL Server 2000一键10秒极速安装.part2

由于我只能上传20M的附件,而这个安装包有83M,所以被分成了5个文件,每个1分,共5分。你会每次至少省下15分钟的安装SQL的时间 分。 5个文件可以在这个网页中找到:http://dhf104109.download.csdn.net/ 我是做用友T系列管理软件的,之前被SQL严重困扰, 由于客户的电脑环境不同,经常出现无法安装的情况, 甚至因为安装演示版时无法正常安装SQL而白白丢失过客户。 几经思考研究,终于制成了这个一键安装版, 安装过程只需要十秒左右(视电脑而定), 与同事们分享,百试百灵,现在已经成功安装了一百次以上,没有不成功的。 原理:其实只是提取了SQL server 2000安装后写入硬盘的文件及注册表信息等,所以只有83M, 比原来的安装包几百M小了非常多,但大家不要怀疑,这些绝对是完整的所有文件了。 将提取的文件制成了这个后缀为exe的自解压文件, 安装过程其实只是把这些文件还原到你的电脑,所以速度非常快。 使用方法: 1、下载后,解压,双击“SQL Server 2000一键10秒极速安装.exe”进行安装。 2、等待15秒左右,重启电脑。由于有些电脑比较慢,建议等待一分钟再重启最保险。 必要说明: 1、由于SQL运行环境要求计算机名不可以带有标点符号,所以安装时顺便把计算机名改成了“SQLSERVER” 2、考虑到数据安全,SQL安装目录改为了D:\Program Files\Microsoft SQL Server(原来是在C盘中)

img
SQL Server 2000一键10秒极速安装.part3

由于我只能上传20M的附件,而这个安装包有83M,所以被分成了5个文件,每个1分,共5分。你会每次至少省下15分钟的安装SQL的时间 分。 5个文件可以在这个网页中找到:http://dhf104109.download.csdn.net/ 我是做用友T系列管理软件的,之前被SQL严重困扰, 由于客户的电脑环境不同,经常出现无法安装的情况, 甚至因为安装演示版时无法正常安装SQL而白白丢失过客户。 几经思考研究,终于制成了这个一键安装版, 安装过程只需要十秒左右(视电脑而定), 与同事们分享,百试百灵,现在已经成功安装了一百次以上,没有不成功的。 原理:其实只是提取了SQL server 2000安装后写入硬盘的文件及注册表信息等,所以只有83M, 比原来的安装包几百M小了非常多,但大家不要怀疑,这些绝对是完整的所有文件了。 将提取的文件制成了这个后缀为exe的自解压文件, 安装过程其实只是把这些文件还原到你的电脑,所以速度非常快。 使用方法: 1、下载后,解压,双击“SQL Server 2000一键10秒极速安装.exe”进行安装。 2、等待15秒左右,重启电脑。由于有些电脑比较慢,建议等待一分钟再重启最保险。 必要说明: 1、由于SQL运行环境要求计算机名不可以带有标点符号,所以安装时顺便把计算机名改成了“SQLSERVER” 2、考虑到数据安全,SQL安装目录改为了D:\Program Files\Microsoft SQL Server(原来是在C盘中)

img
思考快与慢

《纽约时报》2011年度十大好书 新书上市,连续20多周蝉联亚马逊、《纽约时报》畅销书排行榜前20名,上市至今超过7个月,横扫全球各大畅销书排行榜,稳居亚马逊总榜前50名 《经济学人》、《华尔街日报》

img
思考,快与慢 中文版.pdf

学会思考,从我做起, 学会思考,从我做起, 学会思考,从我做起

img
ZFiver QQ五子棋全自动工具

ZFiver 使用说明 这是一款QQ游戏平台的五子棋对战机器人,能替人进行思考和落子,支持无人值守挂机,并极少被人战胜! --------------------------------------------- ZFiver 功能简介: 1. 针对QQ游戏平台五子棋:能够代替人进行思考和落子,替你战胜对手。同时支持无禁手和禁手规则。 2. 具有四个不同的“大脑”:就是四个不同的“ZFiver引擎”,各具特色,可以应对不同的对手! 3. 设置难度:机器人下棋当然也需要进行“思考”,和人一样,思考得越久,下的水平越高。所以你要设置合适的难度,如果碰到水平并不是特别优秀的玩家设置较低的难度就可以,这样落子的速度较快,反之如果碰到难得一见的高手,就应设置较高的难度,虽然想的时间会变长一些,但战胜的把握更大。 4. 获胜预先提示:当机器人觉得你即将获胜的时候,它会进行提示! 5. 走子延时:机器人有时会下得很快,最快可以在对手落子后的0.5秒内出击。但如果总是这么快容易让对手发现不是真人在下棋,所以增加了“走子延时”的功能,适当延长落子时间。 6. 人机模式:你可以把机器人当作你的一个小军师,让他帮你下一步。如果要让机器人下棋,又要聊QQ或做别的事情,只能用此模式!建议记住快捷方式F6,按一次帮你走一步。 7. 收拾残局(仅推荐引擎有此功能):即在游戏已经开始的情况下继续替你战斗,这是一个非常实用的功能。另外有一个特殊用法:如果你同意悔棋,这个时候棋盘上的棋子就改变了,可以先中止机器人操作然后再点“自动模式”。 8. 挂机:在无人值守的情况下自动对战。试想,如果你将房间设置改为只同“分差不超过100的玩家”进行游戏,找一个热闹的房间和抢手的桌子,也许几个小时后你就升了好几段(当然前提是这个过程中你没有被蓝钻踢掉)! --------------------------------------------- 注意事项: 1. 因为本机器人使用了部分图形识别的方法,所以在使用机器人的期间,请切记,游戏过程中,游戏窗口不要被其他的窗口遮挡,此外在游戏过程中也尽量不要移动游戏窗口,也最好不要进行其他的操作,否则有可能导致机器人出错。 2. 一般情况下游戏窗口和机器人的打开顺序没有特别讲究,可以先开游戏也可以先开机器人。 3. 自动模式:只需要在游戏窗口已经打开的状态下,直接点击机器人上的“自动模式”按钮,即可开始自动对战。 ---------------------------------------------关于四个ZFiver引擎各自的特点(重要): 1. 推荐:反应速度较快,棋力较强,具有最多的功能(支持收拾残局以及获胜提示)。 推荐设置:5~30 2. 敏锐:当设置思考时间为5到10时左右时具有最强的棋力,当对手出狠招的时候会进行提示(比如对手必胜开局),同时支持获胜提示。 推荐设置:5~10 3. 强力:在四个引擎中具有最强的棋力,但反应较慢而且不支持其他功能,一般不使用,推荐只在遇到难得一见的对手时使用。 推荐设置:5~20 4. 禁手:支持有禁手规则。 推荐设置:5~20 --------------------------------------------- 获取试用激活码: 每个人都可以享受 1 天的免费试用。第一次打开机器人会出现激活界面,您将获得自己的机器码,请把您的机器码发送到 48377000@qq.com ,一般情况下我会在24小时之内把激活码回复给您。每人每机限领一次,激活码可以使用 1 天,只能在被绑定的机器上使用,之后使用需要进行购买。 如果您觉得好用请将它推荐给您的好友,您每推荐1位好友来试用本机器人将额外获取1天的激活码,最少3天起,推荐越多试用越多,最多可达30天!请确保您的好友在发送机器码的时候注明您为推荐人!格式如下: 我的机器码为: XXXXX-XXXXX-XXXXX-XXXXX-XXXXX 推荐人: xxx@xxx.com (即您的Email地址) ---------------------------------------------注册获得正式激活码: 注册ZFiver费用为10元/月、50元/年,100元/5年。 注册后在有效期内可以免费升级。 交费方式:1、支付宝(淘宝网) 2、财富通(腾讯QQ) 3、网银转账 4、中国移动手机充值卡 联系QQ:48377000 邮箱:48377000@qq.com ZFiver最新消息:http://48377000.qzone.qq.com/ 2009.12.7

img
SQL Server 2000一键10秒极速安装.part4

由于我只能上传20M的附件,而这个安装包有83M,所以被分成了5个文件,每个1分,共5分。你会每次至少省下15分钟的安装SQL的时间 分。 5个文件可以在这个网页中找到:http://dhf104109.download.csdn.net/ 我是做用友T系列管理软件的,之前被SQL严重困扰, 由于客户的电脑环境不同,经常出现无法安装的情况, 甚至因为安装演示版时无法正常安装SQL而白白丢失过客户。 几经思考研究,终于制成了这个一键安装版, 安装过程只需要十秒左右(视电脑而定), 与同事们分享,百试百灵,现在已经成功安装了一百次以上,没有不成功的。 原理:其实只是提取了SQL server 2000安装后写入硬盘的文件及注册表信息等,所以只有83M, 比原来的安装包几百M小了非常多,但大家不要怀疑,这些绝对是完整的所有文件了。 将提取的文件制成了这个后缀为exe的自解压文件, 安装过程其实只是把这些文件还原到你的电脑,所以速度非常快。 使用方法: 1、下载后,解压,双击“SQL Server 2000一键10秒极速安装.exe”进行安装。 2、等待15秒左右,重启电脑。由于有些电脑比较慢,建议等待一分钟再重启最保险。 必要说明: 1、由于SQL运行环境要求计算机名不可以带有标点符号,所以安装时顺便把计算机名改成了“SQLSERVER” 2、考虑到数据安全,SQL安装目录改为了D:\Program Files\Microsoft SQL Server(原来是在C盘中) 极速 SQL 一键安装 绿色

img
SQL Server 2000一键10秒极速安装.part5

由于我只能上传20M的附件,而这个安装包有83M,所以被分成了5个文件,每个1分,共5分。你会每次至少省下15分钟的安装SQL的时间。 分。 5个文件可以在这个网页中找到:http://dhf104109.download.csdn.net/ 我是做用友T系列管理软件的,之前被SQL严重困扰, 由于客户的电脑环境不同,经常出现无法安装的情况, 甚至因为安装演示版时无法正常安装SQL而白白丢失过客户。 几经思考研究,终于制成了这个一键安装版, 安装过程只需要十秒左右(视电脑而定), 与同事们分享,百试百灵,现在已经成功安装了一百次以上,没有不成功的。 原理:其实只是提取了SQL server 2000安装后写入硬盘的文件及注册表信息等,所以只有83M, 比原来的安装包几百M小了非常多,但大家不要怀疑,这些绝对是完整的所有文件了。 将提取的文件制成了这个后缀为exe的自解压文件, 安装过程其实只是把这些文件还原到你的电脑,所以速度非常快。 使用方法: 1、下载后,解压,双击“SQL Server 2000一键10秒极速安装.exe”进行安装。 2、等待15秒左右,重启电脑。由于有些电脑比较慢,建议等待一分钟再重启最保险。 必要说明: 1、由于SQL运行环境要求计算机名不可以带有标点符号,所以安装时顺便把计算机名改成了“SQLSERVER” 2、考虑到数据安全,SQL安装目录改为了D:\Program Files\Microsoft SQL Server(原来是在C盘中) 极速 SQL 一键安装 绿色

img
扫不完的雷--java小游戏

用Java写的小游戏,相比传统的扫雷加入了很多新的系统,最特别的是“恢复系统”,即挖开的格子过一会儿会还原,且有一定概率变成雷,这才成了“扫不完的雷”。以下是游戏规则及说明: 这个游戏的理念是“快速思考,眼疾手快”。 一、你只有三分钟时间来扫(当然技术好也可以加时间),当你点下中央的开始按钮,右边的计时器就开始倒计时,所以你必须快速地判断。 二、配合这种快速理念,游戏对你的误判是允许的,每当你点到一颗雷,右边的秒表(用秒表就是为了提醒大家,这是个快速扫雷的游戏)就会减少一个。(当然也有办法让秒表补回来) 三、时间到了或三个秒表都没了,游戏就结束了,所以这个游戏的主旨不是输赢,而是分数。右侧会记录你的得分,同时还有个道具值(第四条会解释),获取细则如下: 1.点开空白格得1分 2.点开数字格就得数字所示的分,同时得道具值1点。 四、为了让你扫得更快,游戏里提供了6种道具: 1.铲子(你没看错,这把铲子是从《植物大战僵尸》里抢来的),使用时消耗20点道具值,可以用它把雷挖掉,既用它点开雷不会减秒表,并获得30分,同时剩余时间会加9秒; 但若用它挖开的不是雷,那就和不用道具点开一样,只是会浪费20点道具值,所以在确定有雷的格子上用吧。(铲子就好比以前用右键插的红旗,但在这个游戏里更适用) 2.四种方向的箭头,水平的消耗40点道具值,垂直的消耗30点道具值,用它的时候会从你点下的地方开始朝对应方向沿直线烧出一道火焰,碰到已点开的格子或边界就停下来,被烧开的格子会焦掉,当然雷也会被烧坏(既不减秒表),不是雷的格子照样得分、得道具值。 ***注,这些箭头除了用来烧格子,还可以有奖励事件(考验你的手速了):火焰的前进速度是随机的,你要是觉得它很慢时就试着把它拦下来(既在它烧到前把它将要烧到的格子点开);成功的话剩余时间也会增加,然后剩余秒表不足三个就会补一个(这是唯一补秒表的方式)并加18秒,若秒表足三个就会加100点道具值、200分和36秒;但点到雷的话,若在火焰还差1格或两格时点的,那么你太强了,雷会被点坏的,否则还是会减秒表。 所以大家训练好自己的手速和眼力吧,当然更保险的做法是用铲子去拦,但可千万别点歪到别的道上去了,另外还有个考验RP的设定:点歪了却点到一个空白格,它连带出的格子把火焰挡下的话也算成功。 3.爆弹,消耗20点道具值,以你点下的格子为中心烧开周围的25格,但没有拦截的奖励设定。 五、为了方便大家操作,6个道具都设了快捷键,默认是:铲子 e;爆弹 q;↑ w;→ d;← a;↓ w。 你也可以根据自己的喜好在游戏菜单中的游戏设置里修改。 六、道具有冷却时间,除了铲子为0秒外,别的都是4秒。 七、你相信借这些道具就能在有限时间内扫开所有雷么? 下面这个设定才正是我给这个游戏取名为“扫不完的雷”的原因:除数字外,所有被点开、挖开、或是烧开的格子都会在8秒后恢复,同时有1/8的概率变成雷(原来是雷的一定不会变成雷)。否则你若真摊把雷扫完了,去哪里再赚分呢? 这个系统的引入对传统的扫雷理念作了新的定义,已挖开的数字可能会因为周围生成了雷而变化,到时候的游戏画面绝不是传统扫雷里会出现的。所以推荐大家在挖出一大块空格子时,就在这里多多摸索吧,不要到别处多点了,以免恢复后情况变化了不好判断。 (顺便提示一个小技巧,恢复后,若有数字的格子没变化就说明刚才它周围点开的空格子恢复了仍是空格子。还有很多小技巧就等大家自己去体会了) 八、游戏结束时会有排名的记录界面,记录下前三名的成绩,当然若本次的成绩比第三名还底,它就只作显示了。 九、以下内容是为了增加趣味性,与游戏无关 1.开始游戏后,右边有个女人,平时是不动的,当你用铲子挖到雷时,她会扭一会儿,以示祝贺。若觉得碍眼的话,可以在视图菜单中关掉。 2.被雷炸到会震噢。 3.从游戏菜单中退出会与点右上角的“X”不同,有时会“抽筋”。 4.打开类似游戏设置的面板,时间会暂停,但别想这样延长思考时间噢。

img
深入理解Android:卷I--详细书签版

CruiseYoung提供的带有详细书签的电子书籍目录 http://blog.csdn.net/fksec/article/details/7888251 深入理解Android:卷I(51CTO网站“2011年度最受读者喜爱的原创IT技术图书”) 基本信息 作者: 邓凡平 出版社:机械工业出版社 ISBN:9787111357629 上架时间:2011-9-13 出版日期:2011 年9月 开本:16开 页码:488 版次:1-1 编辑推荐   结合实际应用开发需求,以情景分析的方式有针对性地对Android的源代码进行了十分详尽的剖析,深刻揭示Android系统的工作原理    机锋网、51CTO、开源中国社区等专业技术网站一致鼎力推荐 内容简介   《深入理解android:卷1》是一本以情景方式对android的源代码进行深入分析的书。内容广泛,以对framework层的分析为主,兼顾native层和application层;分析深入,每一部分源代 码的分析都力求透彻;针对性强,注重实际应用开发需求,书中所涵盖的知识点都是android应用开发者和系统开发者需要重点掌握的。    全书共10章,第1章介绍了阅读本书所需要做的准备工作,主要包括对android系统架构和源码阅读方法的介绍;第2章通过对android系统中的mediascanner进行分析,详细讲解了 android中十分重要的jni技术;第3章分析了init进程,揭示了通过解析init.rc来启动zygote以及属性服务的工作原理;第4章分析了zygote、systemserver等进程的工作机制,同时还讨论了 android的启动速度、虚拟机heapsize的大小调整、watchdog工作原理等问题;第5章讲解了android系统中常用的类,包括sp、wp、refbase、thread等类,同步类,以及java中的handler类和 looper类,掌握这些类的知识后方能在后续的代码分析中做到游刃有余;第6章以mediaserver为切入点,对android中极为重要的binder进行了较为全面的分析,深刻揭示了其本质。第7章对 audio系统进行了深入的分析,尤其是audiotrack、audioflinger和audiopolicyservice等的工作原理。第8章深入讲解了surface系统的实现原理,分析了surface与activity之间以及surface 与surfaceflinger之间的关系、surfaceflinger的工作原理、surface系统中的帧数据传输以及layerbuffer的工作流程。第9章对vold和rild的原理和机制进行了深入的分析,同时还探讨了 phone设计优化的问题;第10章分析了多媒体系统中mediascanner的工作原理。    本书适合有一定基础的android应用开发工程师和系统工程师阅读。通过对本书的学习,大家将能更深刻地理解android系统,从而自如应对实际开发中遇到的难题。 作译者   邓凡平,资深Android开发工程师,热衷于Android源代码的研究,对Android的架构设计和实现原理有非常深刻的认识和理解,应用开发经验也十分丰富。目前就职于国内一家领先的 Android企业,负责Framework的开发和维护。乐于分享,活跃于CSDN等专业社区,撰写的Android Framework源码的系列文章深受读者欢迎。此外,他对Linux内核、C/C++/Python相关的技术 ,以及高性能网络服务器和多核并行开发等也有一定的研究。 目录 封面 -17 封底 489 扉页 -16 版权 -15 推荐序 -14 前言 -12 致谢 -9 目录 -7 第1章 阅读前的准备工作 1 1.1 系统架构 2 1.1.1 Android系统架构 2 1.1.2 本书的架构 3 1.2 搭建开发环境 4 1.2.1 下载源码 4 1.2.2 编译源码 6 1.3 工具介绍 8 1.3.1 Source Insight介绍 8 1.3.3 Busybox的使用 11 1.4 本章小结 12 第2章 深入理解JNI 13 2.1 JNI概述 14 2.2 学习JNI的实例:MediaScanner 15 2.3 Java层的MediaScanner分析 16 2.3.1 加载JNI库 16 2.3.2 Java的native函数和总结 17 2.4 JNI层MediaScanner的分析 17 2.4.1 注册JNI函数 18 2.4.2 数据类型转换 22 2.4.3 JNIEnv介绍 24 2.4.4 通过JNIEnv操作jobject 25 2.4.5 jstring介绍 27 2.4.6 JNI类型签名介绍 28 2.4.7 垃圾回收 29 2.4.8 JNI中的异常处理 32 2.5 本章小结 32 第3章 深入理解init 33 3.1 概述 34 3.2 init分析 34 3.2.1 解析配置文件 38 3.2.2 解析service 42 3.2.3 init控制service 48 3.2.4 属性服务 52 3.3 本章小结 60 第4章 深入理解zygote 61 4.1 概述 62 4.2 zygote分析 62 4.2.1 AppRuntime分析 63 4.2.2 Welcome to Java World 68 4.2.3 关于zygote的总结 74 4.3 SystemServer分析 74 4.3.1 SystemServer的诞生 74 4.3.2 SystemServer的重要使命 77 4.3.3 关于SystemServer的总结 83 4.4 zygote的分裂 84 4.4.1 ActivityManagerService发送请求 84 4.4.2 有求必应之响应请求 86 4.4.3 关于zygote分裂的总结 88 4.5 拓展思考 88 4.5.1 虚拟机heapsize的限制 88 4.5.2 开机速度优化 89 4.5.3 Watchdog分析 90 4.6 本章小结 93 第5章 深入理解常见类 95 5.1 概述 96 5.2 以“三板斧”揭秘RefBase、 sp和WP 96 5.2.1 第一板斧——初识影子对象 96 5.2.2 第二板斧——由弱生强 103 5.2.3 第三板斧——破解生死魔咒 106 5.2.4 轻量级的引用计数控制类LightRefBase 108 5.2.5 题外话——三板斧的来历 109 5.3 Thread类及常用同步类分析 109 5.3.1 一个变量引发的思考 109 5.3.2 常用同步类 114 5.4 Looper和Handler类分析 121 5.4.1 Looper类分析 122 5.4.2 Handler分析 124 5.4.3 Looper和Handler的同步关系 127 5.4.4 HandlerThread介绍 129 5.5 本章小结 129 第6章 深入理解Binder 130 6.1 概述 131 6.2 庖丁解MediaServer 132 6.2.1 MediaServer的入口函数 132 6.2.2 独一无二的ProcessState 133 6.2.3 时空穿越魔术——defaultServiceManager 134 6.2.4 注册MediaPlayerService 142 6.2.5 秋风扫落叶——StartThread Pool和join Thread Pool分析 149 6.2.6 你彻底明白了吗 152 6.3 服务总管ServiceManager 152 6.3.1 ServiceManager的原理 152 6.3.2 服务的注册 155 6.3.3 ServiceManager存在的意义 158 6.4 MediaPlayerService和它的Client 158 6.4.1 查询ServiceManager 158 6.4.2 子承父业 159 6.5 拓展思考 162 6.5.1 Binder和线程的关系 162 6.5.2 有人情味的讣告 163 6.5.3 匿名Service 165 6.6 学以致用 166 6.6.1 纯Native的Service 166 6.6.2 扶得起的“阿斗”(aidl) 169 6.7 本章小结 172 第7章 深入理解Audio系统 173 7.1 概述 174 7.2 AudioTrack的破解 174 7.2.1 用例介绍 174 7.2.2 AudioTrack (Java空间)分析 179 7.2.3 AudioTrack (Native空间)分析 188 7.2.4 关于AudioTrack的总结 200 7.3 AudioFlinger的破解 200 7.3.1 AudioFlinger的诞生 200 7.3.2 通过流程分析AudioFlinger 204 7.3.3 audio track cblk t分析 230 7.3.4 关于AudioFlinger的总结 234 7.4 AudioPolicyService的破解 234 7.4.1 AudioPolicyService的创建 235 7.4.2 重回AudioTrack 245 7.4.3 声音路由切换实例分析 251 7.4.4 关于AudioPolicy的总结 262 7.5 拓展思考 262 7.5.1 DuplicatingThread破解 262 7.5.2 题外话 270 7.6 本章小结 272 第8章 深入理解Surface系统 273 8.1 概述 275 8.2 一个Activity的显示 275 8.2.1 Activity的创建 275 8.2.2 Activity的UI绘制 294 8.2.3 关于Activity的总结 296 8.3 初识Surface 297 8.3.1 和Surface有关的流程总结 297 8.3.2 Surface之乾坤大挪移 298 8.3.3 乾坤大挪移的JNI层分析 303 8.3.4 Surface和画图 307 8.3.5 初识Surface小结 309 8.4 深入分析Surface 310 8.4.1 与Surface相关的基础知识介绍 310 8.4.2 SurfaceComposerClient分析 315 8.4.3 SurfaceControl分析 320 8.4.4 writeToParcel和Surface对象的创建 331 8.4.5 lockCanvas和unlockCanvasAndPost分析 335 8.4.6 GraphicBuffer介绍 344 8.4.7 深入分析Surface的总结 353 8.5 SurfaceFlinger分析 353 8.5.1 SurfaceFlinger的诞生 354 8.5.2 SF工作线程分析 359 8.5.3 Transaction分析 368 8.5.4 关于SurfaceFlinger的总结 376 8.6 拓展思考 377 8.6.1 Surface系统的CB对象分析 377 8.6.2 ViewRoot的你问我答 384 8.6.3 LayerBuffer分析 385 8.7 本章小结 394 第9章 深入理解Vold和Rild 395 9.1 概述 396 9.2 Vold的原理与机制分析 396 9.2.1 Netlink和Uevent介绍 397 9.2.2 初识Vold 399 9.2.3 NetlinkManager模块分析 400 9.2.4 VolumeManager模块分析 408 9.2.5 CommandListener模块分析 414 9.2.6 Vold实例分析 417 9.2.7 关于Vold的总结 428 9.3 Rild的原理与机制分析 428 9.3.1 初识Rild 430 9.3.2 RIL_startEventLoop分析 432 9.3.3 RIL Init分析 437 9.3.4 RIL_ register分析 444 9.3.5 关于Rild main函数的总结 447 9.3.6 Rild实例分析 447 9.3.7 关于Rild的总结 459 9.4 拓展思考 459 9.4.1 嵌入式系统的存储知识介绍 459 9.4.2 Rild和Phone的改进探讨 462 9.5 本章小结 463 第10章 深入理解MediaScanner 464 10.1 概述 465 10.2 android.process.media分析 465 10.2.1 MSR模块分析 466 10.2.2 MSS模块分析 467 10.2.3 android.process.media媒体扫描工作的流程总结 471 10.3 MediaScanner分析 472 10.3.1 Java层分析 472 10.3.2 JNI层分析 476 10.3.3 PVMediaScanner分析 479 10.3.4 关于MediaScanner的总结 485 10.4 拓展思考 486 10.4.1 MediaScannerConnection介绍 486 10.4.2 我问你答 487 10.5 本章小结 488 前言   虽然前言位于书的最前面,但往往是最后才完成的。至今,本书的撰写工作算是基本完成了,在书稿付梓之前,心中却有些许忐忑和不安,因为拙著可能会存在Bug。为此,我先为书中可 能存在的Bug将给大家带来的麻烦致以真诚的歉意。另外,如果大家发现本书存在纰漏或有必要进一步探讨的地方,请发邮件给我(fanping.deng@gmail.com),我会尽快回复。非常乐意与大 家交流。      本书主要内容   全书一共10章,其中一些重要章节中还设置了“拓展思考”部分。这10章的主要内容是:   第1章介绍了阅读本书所需要做的一些准备工作,包括对Android整个系统架构的认识,以及Android开发环境和源码阅读环境的搭建等。注意,本书分析的源码是Android2.2。   第2章通过Android源码中的一处实例深入地介绍了JNI技术。   第3章围绕init进程,介绍了如何解析init.rc以启动Zygote和属性服务(property service)的工作原理。   第4章剖析了zygote和system_server进程的工作原理。本章的拓展思考部分讨论了Andorid的启动速度、虚拟机heapsize的大小调整问题以及“看门狗”的工作原理。   第5章讲解了Android源码中常用的类,如sp、wp、RefBase、Thread类、同步类、Java中的Handler类以及Looper类。这些类都是Android中最常用和最基本的,只有掌握这些类的知识,才 能在分析后续的代码时游刃有余。   第6章以MediaServer为切入点,对Binder进行了较为全面的分析。本章拓展思考部分讨论了与Binder有关的三个问题,它们分别是Binder和线程的关系、死亡通知以及匿名Service。笔者 希望,通过本章的学习,大家能更深入地认识Binder的本质。   第7章阐述了Audio系统中的三位重要成员AudioTrack、AudioFlinger和AudioPolicyService的工作原理。本章拓展思考部分分析了AudioFlinger中DuplicatingThread的工作原理,并且和 读者一道探讨了单元测试、ALSA、Desktop check等问题。通过对本章的学习,相信读者会对Audio系统有更深的理解。   第8章以Surface系统为主,分析了Activity和Surface的关系、Surface和SurfaceFlinger的关系以及SurfaceFlinger的工作原理。本章的拓展思考部分分析了Surface系统中数据传输控制 对象的工作原理、有关ViewRoot的一些疑问,最后讲解了LayerBuffer的工作流程。这是全书中难度较大的一章,建议大家反复阅读和思考,这样才能进一步深入理解Surface系统。   第9章分析了Vold和Rild,其中Vold负责Android平台中外部存储设备的管理,而Rild负责与射频通信有关的工作。本章的拓展思考部分介绍了嵌入式系统中与存储有关的知识,还探讨了 Rild和Phone设计优化方面的问题。   第10章分析了多媒体系统中MediaScanner的工作原理。在本章的拓展思考部分,笔者提出了几个问题,旨在激发读者深入思考和学习Android的欲望。      本书特色   笔者认为,本书最大的特点在于,较全面、系统、深入地讲解了Android系统中的几大重要组成部分的工作原理,旨在通过直接剖析源代码的方式,引领读者一步步深入于诸如Binder、 Zygote、Audio、Surface、Vold、Rild等模块的内部,去理解它们是如何实现的,以及如何工作的。笔者根据研究Android代码的心得,在本书中尝试性地采用了精简流程、逐个击破的方法进 行讲解,希望这样做能帮助读者更快、更准确地把握各模块的工作流程及其本质。本书大部分章节中都专门撰写了“拓展思路”的内容,希望这部分内容能激发读者对Android代码进行深入研 究的热情。      本书面向的读者   (1)Android应用开发工程师 .  对于Android应用开发工程师而言,本书中关于Binder,以及sp、wp、Handler和Looper等常用类的分析或许能帮助你迅速适应Android平台上的开发工作。   (2)Android系统开发工程师   Android系统开发工程师常常需要深入理解系统的运转过程,而本书所涉及的内容可能正是他们在工作和学习中最想了解的。那些对具体模块(如Audio系统和Surface系统)感兴趣的读者 也可以直接阅读相关章节的内容。   这里有必要提醒一下,要阅读此书,应具有C++的基本知识,因为本书的大部分内容都集中在了Native层。      如何阅读本书   本书是在分析Android源码的基础上展开的,而源码文件所在的路径一般都很长,例如,文件AndroidRuntime.cpp的真实路径就是framework/base/core/jni/AndroidRuntime.cpp。为了书 写方便起见,我们在各章节开头把该章所涉及的源码路径全部都列出来了,而在具体分析源码时,则只列出该源码的文件名。   下面就是一个示例:   [--]AndroidRuntime.cpp]   //这里是源码分析和一些注释。   如有一些需要特别说明的地方,则会用下面的格式表示:   [--]AndroidRuntime.cpp::特别说明]   特别说明可帮助读者找到源码中的对应位置。   另外,本书在描述类之间的关系以及在函数调用流程上使用了UML的静态类图以及序列图。UML是一个强大的工具,但它的建模规范过于烦琐,为更简单清晰地描述事情的本质,本书并未 完全遵循UML的建模规范。   本书所使用的UML图都比较简单,读者大可不必花费时间专门学习UML。   本书的编写顺序,其实应该是6、5、4、7、8、9、10、2、3、1章,但出于逻辑连贯性的考虑,还是建议读者按本书的顺序阅读。其中,第2、5、6章分别讲述了JNI、Android常用类以及 Binder系统,这些都是基础知识,我们有必要完全掌握。其他部分的内容都是针对单个模块的,例如Zygote、Audio、Surface、MediaScanner等,读者可各取所需,分别对其进行研究。      致谢   首先要感谢杨福川编辑。本书最初的内容来自我的博客,但博客里的文章都没有图,格式也较混乱。是杨编辑最先鼓励我将这些博文整理修改成册,所以我对杨福川编辑的眼光佩服得五 体投地。在他的同事杨绣国和白宇的帮助下,我最终才将博客中那些杂乱的文章撰成了今天这本图文并茂、格式工整的书籍。   其次要感谢我的妻子。为写成此书,我几乎将周末所有的时间都花在了工作中,而长时间在生活上对妻子不闻不问。对丈夫呆若木鸡式的冷淡,妻子却给予了最大的宽容。另外,我的岳 父母和我的父母亲都给予了我最无私的帮助,他们都是平凡而伟大的父母亲。还有我和妻子的亲戚们,他们的宽厚和善良时刻感动着我。   在IT职业的道路上,非常感念前东家中科大洋公司的领导和同事们,他们是邓伟先生、刘运红先生、王宁先生等。当初,如果没有他们宽容的接纳和细心的指导,现在我不可能成为一名 合格的程序员。   非常感谢我现在供职的单位中科创达公司。在这里工作,我常有这样一种感慨:不是所有人都能自己开公司创业的,而又有多少人能够有机会和一个优秀的创业公司一起成长、一起发展 呢?创达开明的领导、睿智而富有激情的工作伙伴正是孕育本书的沃土。公司领导赵鸿飞先生、吴安华女士等人更是给予了我最大的肯定和鼓励。   这里要特别提及的是,我的大学同窗,即为本书作序的邓必山先生。如果没有他的推荐,凭自己那份简陋、单薄的简历,是根本无法与Android亲密接触的。另外,他还曾在技术和个人发 展上给予过我很多的指导,对此,我将永志不忘!   谢谢那些共享Android知识的网友们!没有大家前期点滴的奉献,或许我至今还在琢磨着某段代码呢。   最后应感谢的是肯花费时间和精力阅读本书的读者,你们的意见和建议将会是我获得的巨大的精神财富!      邓凡平   2011年6月于北京    序言   近两年来,IT行业的最热点聚焦到了移动互联网上。PC时代,WINTEL联盟成就了英特尔和微软各自的霸业。移动互联网时代,谁将上演新的传奇?新生的Android当年仅用短短一年多的时 间就跻身全球智能操作系统的三甲行列。在北美市场,如今Android已经超过iOS和黑莓系统成为老大!Android势不可挡,ARM+Android组合的前景一片光明,越来越多的从业者加入了Android 行列!   与带给人们良好用户体验的iOS不一样的是,Android是一个开放的系统,其所有代码都是开源的。因此,对于开发者而言,不仅可以做到知其然,更可以做到知其所以然!   然而,要想知道其所以然,并不是一件简单的事情。回想当初,我开始接触Android的时候,除了Android源码外,其他资料甚少。Android是基于Linux的完整操作系统,其代码量让人望 而生畏。可以想象,在没有指导的情况下一头扎进操作系统庞大的代码中是一件让人多么痛苦的事情。时间过得很快,Android生态链已经得到了充分的发展。现在市场上的Android资料已经 开始泛滥,书籍已经数不胜数。然而,绝大部分书籍只限于讲解Android应用的开发(拜Android应用API所赐),没有深入到系统级的探讨,极少的所谓提供Android深入指导的资料也只是浅 尝辄止。如果想深入了解Android系统,只有华山一条路:自己看Android源代码!   正是因为如此,当初凡平告诉我他要系统地整理其深入钻研Android源代码的心得时,我表示了强烈的赞同。这是一件极少有人做过的事情,这件事情将给已经或即将跨入Android世界的 同仁们极大的帮助!这本书里,作者以代码框架为主线,用循序渐进的方式将框架中的关键点一一剖开,从而给读者一个架构清楚、细节完善的立体展现。另外,凡平还会用他的幽默给正在 啃枯燥代码的您带来不少笑意和轻松。毫无疑问,如果您想深入了解Android系统,这本书就是您进入Android神秘世界的钥匙。   如果您看准了移动互联网的前景,想深入理解Android,那就让这本书指导您前进吧!      邓必山   2011年6月于北京    媒体评   作者是Thundersoft多媒体组的牛人,技术精深,乐于分享,对Android系统有真正的理解。《深入理解Android:卷1》内容给力,语言生动,全书没有一句废话,各章中的“拓展思考” 尤为精彩,体现了作者对Android实现原理的深入理解和批判性思考。为什么Android的短信群发很慢?为什么拔出SD卡时有的程序会退出?读者都能从本书中找到诸如此类的各种实际问题的 答案。更重要的是,读者能够对Android的整个体系有一个全新的理解。如果你通读了这本书,请一定投一份简历给我们。——Thundersoft(中科创达软件科技(北京)有限公司)      对于Android开发工程师而言,本书不可多得,分析透彻深入,针对性极强。Android系统本身极为庞大,如果要对整个系统进行面面俱到且细致入微地分析,恐怕不是一两本书能完成的 。本书从开发者的实际需求出发,有针对性地对Android系统中的重要知识点和功能模块的源代码实现进行了剖析,这样既能帮助开发者解决实际问题,又能使分析深入透彻,而不是停留于表 面。强烈推荐!——机锋网(http://www.gfan.com/)      这本书非常实用,绝不是枯燥的源代码分析,是深入理解Android工作机制和实现原理的一本好书。为什么说它实用呢?因为它的最终目的并不是停留着源代码分析上,而是要帮助开发者 解决实际问题,于是所有知识点的分析和讲解都是从开发者的实际需求出发的。与一般的源代码分析的书相比较而言,本书在语言上多了几分幽默,更加生动易懂。更重要的是,本书的分析 十分深入,探讨了Android相关功能模块的本质。——51CTO移动开发频道(http://mobile.51cto.com/)      随着Android 系统越来越流行,Android应用的需求也在不断变化,对于开发者而言,深入理解Android系统原理显得越来越重要。目前市面上Android 开发相关的图书已经很多,但真正 能够系统、深入地讲解Android系统原理的书还乏善可陈。这本书的出版恰逢其时,该书同时兼备深度和广度,以循序渐进的方式,优雅的语言,深入分析到各个模块的源码与原理。另外,它 启发性的讲解方式,更有助于读者的学习和思考。——开源中国社区(http://www.oschina.net/)   

img
内存管理内存管理内存管理

内存管理内幕 dragonimp's blog coder.developer.[designer].ArchitecturE.manager.^_^... posts - 29, comments - 121, trackbacks - 27 My Links Home Contact Login News !!! Article Categories ..OT .Mixed Asp.net Cognos Cryptology Database DotText Jabber MatLab NetWork OpenGL Programming Tuxedo Unix/C/C++ Web/Scripts Archives February, 2006 (2) January, 2006 (3) December, 2005 (2) November, 2005 (2) October, 2005 (2) May, 2005 (1) April, 2005 (7) March, 2005 (9) May, 2004 (1) Post Categories collection (rss) Daily Report (rss) NEWS (rss) Projects (rss) say you say me (rss) Image Galleries Application Galleries Funny OpenGL ReverseProxy My Sites blogs.impx.net Finance HomePage Weblogs AKUN's bLog Gin scottdensmore scottelkin.com scottwater's Blogs Sonu's WebLog 内存管理内幕 本文将对 Linux? 程序员可以使用的内存管理技术进行概述,虽然关注的重点是 C 语言,但同样也适用于其他语言。文中将为您提供如何管理内存的细节,然后将进一步展示如何手工管理内存,如何使用引用计数或者内存池来半手工地管理内存,以及如何使用垃圾收集自动管理内存。 为什么必须管理内存 内存管理是计算机编程最为基本的领域之一。在很多脚本语言中,您不必担心内存是如何管理的,这并不能使得内存管理的重要性有一点点降低。对实际编程来说,理解您的内存管理器的能力与局限性至关重要。在大部分系统语言中,比如 C 和 C++,您必须进行内存管理。本文将介绍手工的、半手工的以及自动的内存管理实践的基本概念。 追溯到在 Apple II 上进行汇编语言编程的时代,那时内存管理还不是个大问题。您实际上在运行整个系统。系统有多少内存,您就有多少内存。您甚至不必费心思去弄明白它有多少内存,因为每一台机器的内存数量都相同。所以,如果内存需要非常固定,那么您只需要选择一个内存范围并使用它即可。 不过,即使是在这样一个简单的计算机中,您也会有问题,尤其是当您不知道程序的每个部分将需要多少内存时。如果您的空间有限,而内存需求是变化的,那么您需要一些方法来满足这些需求: 确定您是否有足够的内存来处理数据。 从可用的内存中获取一部分内存。 向可用内存池(pool)中返回部分内存,以使其可以由程序的其他部分或者其他程序使用。 实现这些需求的程序库称为分配程序(allocators),因为它们负责分配和回收内存。程序的动态性越强,内存管理就越重要,您的内存分配程序的选择也就更重要。让我们来了解可用于内存管理的不同方法,它们的好处与不足,以及它们最适用的情形。 C 风格的内存分配程序 C 编程语言提供了两个函数来满足我们的三个需求: malloc:该函数分配给定的字节数,并返回一个指向它们的指针。如果没有足够的可用内存,那么它返回一个空指针。 free:该函数获得指向由 malloc 分配的内存片段的指针,并将其释放,以便以后的程序或操作系统使用(实际上,一些 malloc 实现只能将内存归还给程序,而无法将内存归还给操作系统)。 物理内存和虚拟内存 要理解内存在程序中是如何分配的,首先需要理解如何将内存从操作系统分配给程序。计算机上的每一个进程都认为自己可以访问所有的物理内存。显然,由于同时在运行多个程序,所以每个进程不可能拥有全部内存。实际上,这些进程使用的是虚拟内存。 只是作为一个例子,让我们假定您的程序正在访问地址为 629 的内存。不过,虚拟内存系统不需要将其存储在位置为 629 的 RAM 中。实际上,它甚至可以不在 RAM 中 —— 如果物理 RAM 已经满了,它甚至可能已经被转移到硬盘上!由于这类地址不必反映内存所在的物理位置,所以它们被称为虚拟内存。操作系统维持着一个虚拟地址到物理地址的转换的表,以便计算机硬件可以正确地响应地址请求。并且,如果地址在硬盘上而不是在 RAM 中,那么操作系统将暂时停止您的进程,将其他内存转存到硬盘中,从硬盘上加载被请求的内存,然后再重新启动您的进程。这样,每个进程都获得了自己可以使用的地址空间,可以访问比您物理上安装的内存更多的内存。 在 32-位 x86 系统上,每一个进程可以访问 4 GB 内存。现在,大部分人的系统上并没有 4 GB 内存,即使您将 swap 也算上,每个进程所使用的内存也肯定少于 4 GB。因此,当加载一个进程时,它会得到一个取决于某个称为系统中断点(system break)的特定地址的初始内存分配。该地址之后是未被映射的内存 —— 用于在 RAM 或者硬盘中没有分配相应物理位置的内存。因此,如果一个进程运行超出了它初始分配的内存,那么它必须请求操作系统“映射进来(map in)”更多的内存。(映射是一个表示一一对应关系的数学术语 —— 当内存的虚拟地址有一个对应的物理地址来存储内存内容时,该内存将被映射。) 基于 UNIX 的系统有两个可映射到附加内存中的基本系统调用: brk:brk() 是一个非常简单的系统调用。还记得系统中断点吗?该位置是进程映射的内存边界。brk() 只是简单地将这个位置向前或者向后移动,就可以向进程添加内存或者从进程取走内存。 mmap:mmap(),或者说是“内存映像”,类似于 brk(),但是更为灵活。首先,它可以映射任何位置的内存,而不单单只局限于进程。其次,它不仅可以将虚拟地址映射到物理的 RAM 或者 swap,它还可以将它们映射到文件和文件位置,这样,读写内存将对文件中的数据进行读写。不过,在这里,我们只关心 mmap 向进程添加被映射的内存的能力。munmap() 所做的事情与 mmap() 相反。 如您所见,brk() 或者 mmap() 都可以用来向我们的进程添加额外的虚拟内存。在我们的例子中将使用 brk(),因为它更简单,更通用。 实现一个简单的分配程序 如果您曾经编写过很多 C 程序,那么您可能曾多次使用过 malloc() 和 free()。不过,您可能没有用一些时间去思考它们在您的操作系统中是如何实现的。本节将向您展示 malloc 和 free 的一个最简化实现的代码,来帮助说明管理内存时都涉及到了哪些事情。 要试着运行这些示例,需要先复制本代码清单,并将其粘贴到一个名为 malloc.c 的文件中。接下来,我将一次一个部分地对该清单进行解释。 在大部分操作系统中,内存分配由以下两个简单的函数来处理: void *malloc(long numbytes):该函数负责分配 numbytes 大小的内存,并返回指向第一个字节的指针。 void free(void *firstbyte):如果给定一个由先前的 malloc 返回的指针,那么该函数会将分配的空间归还给进程的“空闲空间”。 malloc_init 将是初始化内存分配程序的函数。它要完成以下三件事:将分配程序标识为已经初始化,找到系统中最后一个有效内存地址,然后建立起指向我们管理的内存的指针。这三个变量都是全局变量: 清单 1. 我们的简单分配程序的全局变量 int has_initialized = 0; void *managed_memory_start; void *last_valid_address; 如前所述,被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者当前中断点。在很多 UNIX? 系统中,为了指出当前系统中断点,必须使用 sbrk(0) 函数。sbrk 根据参数中给出的字节数移动当前系统中断点,然后返回新的系统中断点。使用参数 0 只是返回当前中断点。这里是我们的 malloc 初始化代码,它将找到当前中断点并初始化我们的变量: 清单 2. 分配程序初始化函数 /* Include the sbrk function */ #include void malloc_init() { /* grab the last valid address from the OS */ last_valid_address = sbrk(0); /* we don't have any memory to manage yet, so *just set the beginning to be last_valid_address */ managed_memory_start = last_valid_address; /* Okay, we're initialized and ready to go */ has_initialized = 1; } 现在,为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。在对内存块进行了 free 调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用 malloc 时,我们要能够定位未被使用的内存块。因此,malloc 返回的每块内存的起始处首先要有这个结构: 清单 3. 内存控制块结构定义 struct mem_control_block { int is_available; int size; }; 现在,您可能会认为当程序调用 malloc 时这会引发问题 —— 它们如何知道这个结构?答案是它们不必知道;在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。这使得返回的指针指向没有用于任何其他用途的内存。那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。然后,当通过 free() 将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。 在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退 sizeof(struct mem_control_block) 个字节,并将其标记为可用的。这里是对应的代码: 清单 4. 解除分配函数 void free(void *firstbyte) { struct mem_control_block *mcb; /* Backup from the given pointer to find the * mem_control_block */ mcb = firstbyte - sizeof(struct mem_control_block); /* Mark the block as being available */ mcb->is_available = 1; /* That's It! We're done. */ return; } 如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。分配内存稍微困难一些。以下是该算法的略述: 清单 5. 主分配程序的伪代码 1. If our allocator has not been initialized, initialize it. 2. Add sizeof(struct mem_control_block) to the size requested. 3. start at managed_memory_start. 4. Are we at last_valid address? 5. If we are: A. We didn't find any existing space that was large enough -- ask the operating system for more and return that. 6. Otherwise: A. Is the current space available (check is_available from the mem_control_block)? B. If it is: i) Is it large enough (check "size" from the mem_control_block)? ii) If so: a. Mark it as unavailable b. Move past mem_control_block and return the pointer iii) Otherwise: a. Move forward "size" bytes b. Go back go step 4 C. Otherwise: i) Move forward "size" bytes 我们主要使用连接的指针遍历内存来寻找开放的内存块。这里是代码: 清单 6. 主分配程序 void *malloc(long numbytes) { /* Holds where we are looking in memory */ void *current_location; /* This is the same as current_location, but cast to a * memory_control_block */ struct mem_control_block *current_location_mcb; /* This is the memory location we will return. It will * be set to 0 until we find something suitable */ void *memory_location; /* Initialize if we haven't already done so */ if(! has_initialized) { malloc_init(); } /* The memory we search for has to include the memory * control block, but the users of malloc don't need * to know this, so we'll just add it in for them. */ numbytes = numbytes + sizeof(struct mem_control_block); /* Set memory_location to 0 until we find a suitable * location */ memory_location = 0; /* Begin searching at the start of managed memory */ current_location = managed_memory_start; /* Keep going until we have searched all allocated space */ while(current_location != last_valid_address) { /* current_location and current_location_mcb point * to the same address. However, current_location_mcb * is of the correct type, so we can use it as a struct. * current_location is a void pointer so we can use it * to calculate addresses. */ current_location_mcb = (struct mem_control_block *)current_location; if(current_location_mcb->is_available) { if(current_location_mcb->size >= numbytes) { /* Woohoo! We've found an open, * appropriately-size location. */ /* It is no longer available */ current_location_mcb->is_available = 0; /* We own it */ memory_location = current_location; /* Leave the loop */ break; } } /* If we made it here, it's because the Current memory * block not suitable; move to the next one */ current_location = current_location + current_location_mcb->size; } /* If we still don't have a valid location, we'll * have to ask the operating system for more memory */ if(! memory_location) { /* Move the program break numbytes further */ sbrk(numbytes); /* The new memory will be where the last valid * address left off */ memory_location = last_valid_address; /* We'll move the last valid address forward * numbytes */ last_valid_address = last_valid_address + numbytes; /* We need to initialize the mem_control_block */ current_location_mcb = memory_location; current_location_mcb->is_available = 0; current_location_mcb->size = numbytes; } /* Now, no matter what (well, except for error conditions), * memory_location has the address of the memory, including * the mem_control_block */ /* Move the pointer past the mem_control_block */ memory_location = memory_location + sizeof(struct mem_control_block); /* Return the pointer */ return memory_location; } 这就是我们的内存管理器。现在,我们只需要构建它,并在程序中使用它即可。 运行下面的命令来构建 malloc 兼容的分配程序(实际上,我们忽略了 realloc() 等一些函数,不过,malloc() 和 free() 才是最主要的函数): 清单 7. 编译分配程序 gcc -shared -fpic malloc.c -o malloc.so 该程序将生成一个名为 malloc.so 的文件,它是一个包含有我们的代码的共享库。 在 UNIX 系统中,现在您可以用您的分配程序来取代系统的 malloc(),做法如下: 清单 8. 替换您的标准的 malloc LD_PRELOAD=/path/to/malloc.so export LD_PRELOAD LD_PRELOAD 环境变量使动态链接器在加载任何可执行程序之前,先加载给定的共享库的符号。它还为特定库中的符号赋予优先权。因此,从现在起,该会话中的任何应用程序都将使用我们的 malloc(),而不是只有系统的应用程序能够使用。有一些应用程序不使用 malloc(),不过它们是例外。其他使用 realloc() 等其他内存管理函数的应用程序,或者错误地假定 malloc() 内部行为的那些应用程序,很可能会崩溃。ash shell 似乎可以使用我们的新 malloc() 很好地工作。 如果您想确保 malloc() 正在被使用,那么您应该通过向函数的入口点添加 write() 调用来进行测试。 我们的内存管理器在很多方面都还存在欠缺,但它可以有效地展示内存管理需要做什么事情。它的某些缺点包括: 由于它对系统中断点(一个全局变量)进行操作,所以它不能与其他分配程序或者 mmap 一起使用。 当分配内存时,在最坏的情形下,它将不得不遍历全部进程内存;其中可能包括位于硬盘上的很多内存,这意味着操作系统将不得不花时间去向硬盘移入数据和从硬盘中移出数据。 没有很好的内存不足处理方案(malloc 只假定内存分配是成功的)。 它没有实现很多其他的内存函数,比如 realloc()。 由于 sbrk() 可能会交回比我们请求的更多的内存,所以在堆(heap)的末端会遗漏一些内存。 虽然 is_available 标记只包含一位信息,但它要使用完整的 4-字节 的字。 分配程序不是线程安全的。 分配程序不能将空闲空间拼合为更大的内存块。 分配程序的过于简单的匹配算法会导致产生很多潜在的内存碎片。 我确信还有很多其他问题。这就是为什么它只是一个例子! 其他 malloc 实现 malloc() 的实现有很多,这些实现各有优点与缺点。在设计一个分配程序时,要面临许多需要折衷的选择,其中包括: 分配的速度。 回收的速度。 有线程的环境的行为。 内存将要被用光时的行为。 局部缓存。 簿记(Bookkeeping)内存开销。 虚拟内存环境中的行为。 小的或者大的对象。 实时保证。 每一个实现都有其自身的优缺点集合。在我们的简单的分配程序中,分配非常慢,而回收非常快。另外,由于它在使用虚拟内存系统方面较差,所以它最适于处理大的对象。 还有其他许多分配程序可以使用。其中包括: Doug Lea Malloc:Doug Lea Malloc 实际上是完整的一组分配程序,其中包括 Doug Lea 的原始分配程序,GNU libc 分配程序和 ptmalloc。 Doug Lea 的分配程序有着与我们的版本非常类似的基本结构,但是它加入了索引,这使得搜索速度更快,并且可以将多个没有被使用的块组合为一个大的块。它还支持缓存,以便更快地再次使用最近释放的内存。ptmalloc 是 Doug Lea Malloc 的一个扩展版本,支持多线程。在本文后面的参考资料部分中,有一篇描述 Doug Lea 的 Malloc 实现的文章。 BSD Malloc:BSD Malloc 是随 4.2 BSD 发行的实现,包含在 FreeBSD 之中,这个分配程序可以从预先确实大小的对象构成的池中分配对象。它有一些用于对象大小的 size 类,这些对象的大小为 2 的若干次幂减去某一常数。所以,如果您请求给定大小的一个对象,它就简单地分配一个与之匹配的 size 类。这样就提供了一个快速的实现,但是可能会浪费内存。在参考资料部分中,有一篇描述该实现的文章。 Hoard:编写 Hoard 的目标是使内存分配在多线程环境中进行得非常快。因此,它的构造以锁的使用为中心,从而使所有进程不必等待分配内存。它可以显著地加快那些进行很多分配和回收的多线程进程的速度。在参考资料部分中,有一篇描述该实现的文章。 众多可用的分配程序中最有名的就是上述这些分配程序。如果您的程序有特别的分配需求,那么您可能更愿意编写一个定制的能匹配您的程序内存分配方式的分配程序。不过,如果不熟悉分配程序的设计,那么定制分配程序通常会带来比它们解决的问题更多的问题。要获得关于该主题的适当的介绍,请参阅 Donald Knuth 撰写的 The Art of Computer Programming Volume 1: Fundamental Algorithms 中的第 2.5 节“Dynamic Storage Allocation”(请参阅参考资料中的链接)。它有点过时,因为它没有考虑虚拟内存环境,不过大部分算法都是基于前面给出的函数。 在 C++ 中,通过重载 operator new(),您可以以每个类或者每个模板为单位实现自己的分配程序。在 Andrei Alexandrescu 撰写的 Modern C++ Design 的第 4 章(“Small Object Allocation”)中,描述了一个小对象分配程序(请参阅参考资料中的链接)。 基于 malloc() 的内存管理的缺点 不只是我们的内存管理器有缺点,基于 malloc() 的内存管理器仍然也有很多缺点,不管您使用的是哪个分配程序。对于那些需要保持长期存储的程序使用 malloc() 来管理内存可能会非常令人失望。如果您有大量的不固定的内存引用,经常难以知道它们何时被释放。生存期局限于当前函数的内存非常容易管理,但是对于生存期超出该范围的内存来说,管理内存则困难得多。而且,关于内存管理是由进行调用的程序还是由被调用的函数来负责这一问题,很多 API 都不是很明确。 因为管理内存的问题,很多程序倾向于使用它们自己的内存管理规则。C++ 的异常处理使得这项任务更成问题。有时好像致力于管理内存分配和清理的代码比实际完成计算任务的代码还要多!因此,我们将研究内存管理的其他选择。 半自动内存管理策略 引用计数 引用计数是一种半自动(semi-automated)的内存管理技术,这表示它需要一些编程支持,但是它不需要您确切知道某一对象何时不再被使用。引用计数机制为您完成内存管理任务。 在引用计数中,所有共享的数据结构都有一个域来包含当前活动“引用”结构的次数。当向一个程序传递一个指向某个数据结构指针时,该程序会将引用计数增加 1。实质上,您是在告诉数据结构,它正在被存储在多少个位置上。然后,当您的进程完成对它的使用后,该程序就会将引用计数减少 1。结束这个动作之后,它还会检查计数是否已经减到零。如果是,那么它将释放内存。 这样做的好处是,您不必追踪程序中某个给定的数据结构可能会遵循的每一条路径。每次对其局部的引用,都将导致计数的适当增加或减少。这样可以防止在使用数据结构时释放该结构。不过,当您使用某个采用引用计数的数据结构时,您必须记得运行引用计数函数。另外,内置函数和第三方的库不会知道或者可以使用您的引用计数机制。引用计数也难以处理发生循环引用的数据结构。 要实现引用计数,您只需要两个函数 —— 一个增加引用计数,一个减少引用计数并当计数减少到零时释放内存。 一个示例引用计数函数集可能看起来如下所示: 清单 9. 基本的引用计数函数 /* Structure Definitions*/ /* Base structure that holds a refcount */ struct refcountedstruct { int refcount; } /* All refcounted structures must mirror struct * refcountedstruct for their first variables */ /* Refcount maintenance functions */ /* Increase reference count */ void REF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount++; } /* Decrease reference count */ void UNREF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount--; /* Free the structure if there are no more users */ if(rstruct->refcount == 0) { free(rstruct); } } REF 和 UNREF 可能会更复杂,这取决于您想要做的事情。例如,您可能想要为多线程程序增加锁,那么您可能想扩展 refcountedstruct,使它同样包含一个指向某个在释放内存之前要调用的函数的指针(类似于面向对象语言中的析构函数 —— 如果您的结构中包含这些指针,那么这是必需的)。 当使用 REF 和 UNREF 时,您需要遵守这些指针的分配规则: UNREF 分配前左端指针(left-hand-side pointer)指向的值。 REF 分配后左端指针(left-hand-side pointer)指向的值。 在传递使用引用计数的结构的函数中,函数需要遵循以下这些规则: 在函数的起始处 REF 每一个指针。 在函数的结束处 UNREF 第一个指针。 以下是一个使用引用计数的生动的代码示例: 清单 10. 使用引用计数的示例 /* EXAMPLES OF USAGE */ /* Data type to be refcounted */ struct mydata { int refcount; /* same as refcountedstruct */ int datafield1; /* Fields specific to this struct */ int datafield2; /* other declarations would go here as appropriate */ }; /* Use the functions in code */ void dosomething(struct mydata *data) { REF(data); /* Process data */ /* when we are through */ UNREF(data); } struct mydata *globalvar1; /* Note that in this one, we don't decrease the * refcount since we are maintaining the reference * past the end of the function call through the * global variable */ void storesomething(struct mydata *data) { REF(data); /* passed as a parameter */ globalvar1 = data; REF(data); /* ref because of Assignment */ UNREF(data); /* Function finished */ } 由于引用计数是如此简单,大部分程序员都自已去实现它,而不是使用库。不过,它们依赖于 malloc 和 free 等低层的分配程序来实际地分配和释放它们的内存。 在 Perl 等高级语言中,进行内存管理时使用引用计数非常广泛。在这些语言中,引用计数由语言自动地处理,所以您根本不必担心它,除非要编写扩展模块。由于所有内容都必须进行引用计数,所以这会对速度产生一些影响,但它极大地提高了编程的安全性和方便性。以下是引用计数的益处: 实现简单。 易于使用。 由于引用是数据结构的一部分,所以它有一个好的缓存位置。 不过,它也有其不足之处: 要求您永远不要忘记调用引用计数函数。 无法释放作为循环数据结构的一部分的结构。 减缓几乎每一个指针的分配。 尽管所使用的对象采用了引用计数,但是当使用异常处理(比如 try 或 setjmp()/longjmp())时,您必须采取其他方法。 需要额外的内存来处理引用。 引用计数占用了结构中的第一个位置,在大部分机器中最快可以访问到的就是这个位置。 在多线程环境中更慢也更难以使用。 C++ 可以通过使用智能指针(smart pointers)来容忍程序员所犯的一些错误,智能指针可以为您处理引用计数等指针处理细节。不过,如果不得不使用任何先前的不能处理智能指针的代码(比如对 C 库的联接),实际上,使用它们的后果通实比不使用它们更为困难和复杂。因此,它通常只是有益于纯 C++ 项目。如果您想使用智能指针,那么您实在应该去阅读 Alexandrescu 撰写的 Modern C++ Design 一书中的“Smart Pointers”那一章。 内存池 内存池是另一种半自动内存管理方法。内存池帮助某些程序进行自动内存管理,这些程序会经历一些特定的阶段,而且每个阶段中都有分配给进程的特定阶段的内存。例如,很多网络服务器进程都会分配很多针对每个连接的内存 —— 内存的最大生存期限为当前连接的存在期。Apache 使用了池式内存(pooled memory),将其连接拆分为各个阶段,每个阶段都有自己的内存池。在结束每个阶段时,会一次释放所有内存。 在池式内存管理中,每次内存分配都会指定内存池,从中分配内存。每个内存池都有不同的生存期限。在 Apache 中,有一个持续时间为服务器存在期的内存池,还有一个持续时间为连接的存在期的内存池,以及一个持续时间为请求的存在期的池,另外还有其他一些内存池。因此,如果我的一系列函数不会生成比连接持续时间更长的数据,那么我就可以完全从连接池中分配内存,并知道在连接结束时,这些内存会被自动释放。另外,有一些实现允许注册清除函数(cleanup functions),在清除内存池之前,恰好可以调用它,来完成在内存被清理前需要完成的其他所有任务(类似于面向对象中的析构函数)。 要在自己的程序中使用池,您既可以使用 GNU libc 的 obstack 实现,也可以使用 Apache 的 Apache Portable Runtime。GNU obstack 的好处在于,基于 GNU 的 Linux 发行版本中默认会包括它们。Apache Portable Runtime 的好处在于它有很多其他工具,可以处理编写多平台服务器软件所有方面的事情。要深入了解 GNU obstack 和 Apache 的池式内存实现,请参阅参考资料部分中指向这些实现的文档的链接。 下面的假想代码列表展示了如何使用 obstack: 清单 11. obstack 的示例代码 #include #include /* Example code listing for using obstacks */ /* Used for obstack macros (xmalloc is a malloc function that exits if memory is exhausted */ #define obstack_chunk_alloc xmalloc #define obstack_chunk_free free /* Pools */ /* Only permanent allocations should go in this pool */ struct obstack *global_pool; /* This pool is for per-connection data */ struct obstack *connection_pool; /* This pool is for per-request data */ struct obstack *request_pool; void allocation_failed() { exit(1); } int main() { /* Initialize Pools */ global_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(global_pool); connection_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(connection_pool); request_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(request_pool); /* Set the error handling function */ obstack_alloc_failed_handler = &allocation_failed; /* Server main loop */ while(1) { wait_for_connection(); /* We are in a connection */ while(more_requests_available()) { /* Handle request */ handle_request(); /* Free all of the memory allocated * in the request pool */ obstack_free(request_pool, NULL); } /* We're finished with the connection, time * to free that pool */ obstack_free(connection_pool, NULL); } } int handle_request() { /* Be sure that all object allocations are allocated * from the request pool */ int bytes_i_need = 400; void *data1 = obstack_alloc(request_pool, bytes_i_need); /* Do stuff to process the request */ /* return */ return 0; } 基本上,在操作的每一个主要阶段结束之后,这个阶段的 obstack 会被释放。不过,要注意的是,如果一个过程需要分配持续时间比当前阶段更长的内存,那么它也可以使用更长期限的 obstack,比如连接或者全局内存。传递给 obstack_free() 的 NULL 指出它应该释放 obstack 的全部内容。可以用其他的值,但是它们通常不怎么实用。 使用池式内存分配的益处如下所示: 应用程序可以简单地管理内存。 内存分配和回收更快,因为每次都是在一个池中完成的。分配可以在 O(1) 时间内完成,释放内存池所需时间也差不多(实际上是 O(n) 时间,不过在大部分情况下会除以一个大的因数,使其变成 O(1))。 可以预先分配错误处理池(Error-handling pools),以便程序在常规内存被耗尽时仍可以恢复。 有非常易于使用的标准实现。 池式内存的缺点是: 内存池只适用于操作可以分阶段的程序。 内存池通常不能与第三方库很好地合作。 如果程序的结构发生变化,则不得不修改内存池,这可能会导致内存管理系统的重新设计。 您必须记住需要从哪个池进行分配。另外,如果在这里出错,就很难捕获该内存池。 垃圾收集 垃圾收集(Garbage collection)是全自动地检测并移除不再使用的数据对象。垃圾收集器通常会在当可用内存减少到少于一个具体的阈值时运行。通常,它们以程序所知的可用的一组“基本”数据 —— 栈数据、全局变量、寄存器 —— 作为出发点。然后它们尝试去追踪通过这些数据连接到每一块数据。收集器找到的都是有用的数据;它没有找到的就是垃圾,可以被销毁并重新使用这些无用的数据。为了有效地管理内存,很多类型的垃圾收集器都需要知道数据结构内部指针的规划,所以,为了正确运行垃圾收集器,它们必须是语言本身的一部分。 收集器的类型 复制(copying): 这些收集器将内存存储器分为两部分,只允许数据驻留在其中一部分上。它们定时地从“基本”的元素开始将数据从一部分复制到另一部分。内存新近被占用的部分现在成为活动的,另一部分上的所有内容都认为是垃圾。另外,当进行这项复制操作时,所有指针都必须被更新为指向每个内存条目的新位置。因此,为使用这种垃圾收集方法,垃圾收集器必须与编程语言集成在一起。 标记并清理(Mark and sweep):每一块数据都被加上一个标签。不定期的,所有标签都被设置为 0,收集器从“基本”的元素开始遍历数据。当它遇到内存时,就将标签标记为 1。最后没有被标记为 1 的所有内容都认为是垃圾,以后分配内存时会重新使用它们。 增量的(Incremental):增量垃圾收集器不需要遍历全部数据对象。因为在收集期间的突然等待,也因为与访问所有当前数据相关的缓存问题(所有内容都不得不被页入(page-in)),遍历所有内存会引发问题。增量收集器避免了这些问题。 保守的(Conservative):保守的垃圾收集器在管理内存时不需要知道与数据结构相关的任何信息。它们只查看所有数据类型,并假定它们可以全部都是指针。所以,如果一个字节序列可以是一个指向一块被分配的内存的指针,那么收集器就将其标记为正在被引用。有时没有被引用的内存会被收集,这样会引发问题,例如,如果一个整数域中包含一个值,该值是已分配内存的地址。不过,这种情况极少发生,而且它只会浪费少量内存。保守的收集器的优势是,它们可以与任何编程语言相集成。 Hans Boehm 的保守垃圾收集器是可用的最流行的垃圾收集器之一,因为它是免费的,而且既是保守的又是增量的,可以使用 --enable-redirect-malloc 选项来构建它,并且可以将它用作系统分配程序的简易替代者(drop-in replacement)(用 malloc/free 代替它自己的 API)。实际上,如果这样做,您就可以使用与我们在示例分配程序中所使用的相同的 LD_PRELOAD 技巧,在系统上的几乎任何程序中启用垃圾收集。如果您怀疑某个程序正在泄漏内存,那么您可以使用这个垃圾收集器来控制进程。在早期,当 Mozilla 严重地泄漏内存时,很多人在其中使用了这项技术。这种垃圾收集器既可以在 Windows? 下运行,也可以在 UNIX 下运行。 垃圾收集的一些优点: 您永远不必担心内存的双重释放或者对象的生命周期。 使用某些收集器,您可以使用与常规分配相同的 API。 其缺点包括: 使用大部分收集器时,您都无法干涉何时释放内存。 在多数情况下,垃圾收集比其他形式的内存管理更慢。 垃圾收集错误引发的缺陷难于调试。 如果您忘记将不再使用的指针设置为 null,那么仍然会有内存泄漏。 结束语 一切都需要折衷:性能、易用、易于实现、支持线程的能力等,这里只列出了其中的一些。为了满足项目的要求,有很多内存管理模式可以供您使用。每种模式都有大量的实现,各有其优缺点。对很多项目来说,使用编程环境默认的技术就足够了,不过,当您的项目有特殊的需要时,了解可用的选择将会有帮助。下表对比了本文中涉及的内存管理策略。 表 1. 内存分配策略的对比 策略分配速度回收速度局部缓存易用性通用性实时可用SMP 线程友好 定制分配程序 取决于实现 取决于实现 取决于实现 很难 无 取决于实现 取决于实现 简单分配程序内存使用少时较快很快差 容易 高 否 否 GNU malloc中 快 中 容易 高 否 中 Hoard 中 中 中 容易 高 否 是 引用计数 N/A N/A 非常好 中 中 是(取决于 malloc 实现) 取决于实现 池 中 非常快 极好 中 中 是(取决于 malloc 实现) 取决于实现 垃圾收集 中(进行收集时慢) 中 差 中 中 否 几乎不 增量垃圾收集 中 中 中 中 中 否 几乎不 增量保守垃圾收集 中 中 中 容易 高 否 几乎不 ii) Go back to step 4 posted on Saturday, November 26, 2005 5:35 PM Feedback No comments posted yet. Post Comment TitlePlease enter a title NamePlease enter your name Url Comment Please enter a comment Remember Me? Powered by: Copyright © dragonimp

img
The C++ Programming Language

The C++ Programming Language 我觉得《The C++ Programming Language》是C++入门者的最佳读物。 理由如下: 1,入门书籍的选择一定要慎之又慎,不然会大大影响初学者对C++的认识。好比在一块木板上钉一颗钉子,如果开头几锤子敲下去就敲歪了,那么以后要想纠正就非常困难了。很多人最后放弃学习C++,就是因为他们被一些不好的书所误导,以至于他们还没有来得及领略C++的恢宏与壮美就鸣金收兵了。这种情况非常令人可惜。有鉴于此,只要有人问我初学C++应该选那本书,我就会毫不犹豫地推荐由C++创始人Bjarne Stroustrup执笔的鸿篇巨制:《The C++ Programming Language》!之所以向大家推荐这本对于初学者而言可能略显艰深的书籍,是因为:选择C++作为学习对象的人一般都是下定决心专业从事软件研发的技术人员。一般而言,他们对计算机领域的各种基础知识是具备一定功底的。如果初学者并不想在程序设计领域深入地走下去,那么,我不会推荐他学习C++,而会推荐他去学习其他语言。 2,不像市面上常见的一些强调学习过程短平快的编程书籍,学习这本书不要贪图速成。因为C++本来就不是一门可以速成的语言。所谓的速成无非是仅仅学会C++的一丁点皮毛。《The C++ Programming Language》这本书可以长时期放在你的枕边伴你入眠,它将伴随着你度过编程生涯的每一个成长阶段。因此买这本书的投资是绝对划算的。表面上看,学习这样的经典著作可能会很慢,会很吃力,但是我相信这才是真正的捷径,因为它不会让你走弯路,不会让你在学习过程中一次又一次地纠正自己在阅读其他低质量C++书籍过程中所慢慢形成的错误观念,所以说:似远实近! 3,OO观念的初步建立是学习C++的前提,必须时刻用OO观念去理解C++的设计决策。OO观念的核心有三条:封装,继承,多态。网上关于OO基本观念的介绍也是多如牛毛。初学者不必急于一下子完全理解它们,因为OO观念的深刻理解只能从长时期的思考和实践过程中获得。这本书的最大特点就是在引入C++各项语法特征的过程中特别注重交待清楚它们的来龙去脉,尤具价值的是,书中每一章后面都给出了极其实用的编程建议(Advice),这些编程建议是国内那些C++作者不可能写得出来的。我这么说绝对不是崇洋媚外。如果说中国的IT技术水平落后国外20年的话,那么中国的IT教育水平至少要落后国外30年。中国目前在IT技术写作领域没有哪一个称得上是大师级别的原创技术作家,大多是靠剪刀加浆糊吃饭的。档次低一点的,直接抄袭和拼凑国外名家的作品;档次高一点的,熟读一下技术标准和规范,然后再翻译成中文,就变成著作了。 4,读完国内作者写的C++书籍之后,你很难获得一种C++究竟强大在何处的认识,好像你只不过是在熟悉一种普通编程语言的语法细节罢了。很多人发出这样的疑问:绝大多数C++的语法概念其他语言也有,比如函数的概念,结构的概念,子程序的概念,类的概念等等,那么究竟是什么样的的特性使得C++成为了目前最具有影响力的编程语言之一呢?这是那些国内C++书籍所不能告诉你的。国内C++书籍大量的着墨点基本上全部落在各种编程语言都具有的这些共性上面,基本上没有看见过有哪本书专门介绍过C++所独具的“特性”。为什么这些作者不讲呢?那是因为C++独具的“特性”通常隐藏于C++的整体设计之中,如果不是像Bjarne Stroustrup那样的骨灰级C++大师,一般的中国作者绝对不可能具备俯瞰C++设计全局的能力。 5,各种编程语言的语法共性基本上人人都可以在很短时间内熟练掌握。关于C++的入门语法介绍,网上免费资料暴多,初学者完全可以一边跟随着这些入门资料的指导,一边在电脑上敲敲打打,从而快速度过学习一门语言的语法熟悉阶段。一旦初学者在短时间内熟悉了C++的基本语法,那么这些国内作者编写的C++书籍基本上就可以束之高阁了。因为这些书籍大多没有什么思想价值,作为技术手册参考一下勉强可以,但如果用来深入掌握C++就只能说是缘木求鱼了。况且这些书通常并不便宜,一般也要30-50元人民币。同学们一般资金有限,所谓好钢要用在刀刃上,与其买一本利用价值不高的书,还不如加一点点钱去购买真正经典的书籍。所以我绝对不会推荐初学者去购买国内作者写的C++书籍。 6,不是说中国在IT领域没有高手,就我所知,中国的技术高手还是有不少的,但是愿意把自己的宝贵经验和心得体会用笔写成书的高手我就极少看到过了。首先,这些经年累月积累下来的知识财富对于一个吃技术饭的人来说实在太宝贵,写成书公开对作者而言实在太吃亏。希望大家承认并接受这个事实。知识本来就具有财产的属性,不然“知识产权”一词从何而来?其次,写书很花时间,基本上是一件燃烧自己照亮别人的雷锋行为。当然,这里的写书指的是作者毫无保留地把自己的精华经验写出来而不是为了敛财而瞎编。最后,目前在中国,通过奉献自己的技术经验来获得合理的收益和报酬还不现实,中国还不具备产生专职技术作家的土壤,原因是多方面的,大家都明白。 7,为什么国外有那么多高手愿意写书呢?原因有多种。一、国外有良好的知识产权保护制度,技术高手可以通过写书来获得不低于当一个资深工程师的收入。二、国外的技术积累实在太雄厚,有些东西在中国可能是技术秘密,但是对于国外作家而言,算不上是技术秘密,因此可以写成书公开。三、对某些作家而言,他公布自己的经验越多,对自己越有利。比如Bjarne Stroustrup,很显然,对他来说,全世界使用C++的程序员越多,他的地位就越不可动摇。所以,Bjarne Stroustrup完全可以把自己关于C++的一切技术思考和经验毫无保留地告诉给全世界的C++程序员。微软和SUN免费公布自己的技术规范也是同样道理。所以在国外,作者,读者,出版商共同构成了一个良性循环的知识生态系统。毫无疑问,这样的结果必定是多赢。但是,请牢记,真正有商业价值的技术你绝对不可能通过买书学习来获得,比如,你绝对不可能在市面上买到Adobe公司关于字体引擎的算法讲解书籍。要是你恰恰需要这些技术怎么办?回答是:购买!买不起或者买不到就只有脱下外套挽起袖子自己来搞了。没有技术的封锁就没有技术的进步,这又是一个需要用辩证法来解释的问题。 8,我相信,只有通过精读C++创始人撰写的这本书,你才可以最准确地理解和把握C++区别于其他语言的精髓。Bjarne Stroustrup不仅仅介绍了C++的语法,更重要的是全面阐释了C++各种基本设施的设计动机以及在应用过程中应当注意避免那些问题。这本书不仅仅告诉你How,更重要的是他会告诉你Why!况且,世界上没有哪个人能比Bjarne Stroustrup更加了解C++!世界上也没有哪个人能比Bjarne Stroustrup把C++阐释得更加清楚!这就是我为什么强烈推荐此书的最根本理由!我觉得学习C++的过程中最好时刻审问自己:C++中的某些规范为什么要这么制定?比如:为什么要搞运算符重载?为什么要保留广受诟病的多重继承机制?等等。思考这些问题就是在揣摩C++标准委员会的设计意图,而这些设计意图实际上深刻地反映了工业界所面临的各种现实问题。 9,是否懂得C语言并不是学习C++的前提。不仅仅是因为C++本身兼容C语言,更重要的是C++所倡导的程序设计观念是和C语言完全不同的。C语言在某种意义上是对CPU模型的抽象。尽管C++完全保留了C语言的机器模型,但是C++更加注重对现实世界进行抽象。为此,C++创始人Bjarne Stroustrup专门写过一篇著名的文章:《Learning Standard C++ as a New Language》(把标准C++作为一种全新的语言来学习)

img
蚂蚁课堂(每特学院)第二期视频和资料

资料课时内容: 0001-蚂蚁课堂(每特学院)-2期-多线程快速入门 第01节、线程与进程的区别 第02节、为什么要用到多线程 第03节、多线程应用场景 第04节、使用继承方式创建线程 第05节、使用Runnable接口方式创建线程 第06节、使用匿名内部类方式创建线程 第07节、多线程常用api 第08节、守护线程与非守护线程 第09节、多线程几种状态 第10节、join方法介绍 第11节、t1、t2、t3执行顺序面试题讲解 第12节、使用多线程分批处理信息 资料+源码.rar 0002-蚂蚁课堂(每特学院)-2期-多线程线程安全 第01节、什么是线程安全问题 第02节、使用同步代码块解决线程安全问题 第03节、证明同步函数使用this锁 第04节、静态同步代码块 第05节、多线程死锁 第06节、Java内存模型 第07节、Volatile可见性 第08节、AtomicInteger原子类 资料+源码.rar 0003-蚂蚁课堂(每特学院)-2期-多线程之间通讯 第00节、课前疑问 第01节、多线程之间通讯 第02节、wait()、notify()区别 第03节、jdk1.5Lock锁 第04节、怎么停止线程 第05节、ThreadLock原理剖析 第06节、总结 资料+源码.rar 0004-蚂蚁课堂(每特学院)-2期-java并发包&并发队列 第01节、Vector与ArrayList区别和HasTable与HasMap线程安全源码分析 第02节、ConcurrentHashMap原理分析 第03节、CountDownLatch用法 第04节、CyclicBarrier用法 第05节、Semaphore信号量 第06节、并发队列ConcurrentLinkedDeque、BlockingQueue阻塞队列用法 第07节、使用并发阻塞队列实现生产者与消费者 资料+源码.rar 0005-蚂蚁课堂(每特学院)-2期-线程池原理剖析&锁的深度化 第01节、线程池概述 第02节、创建线程池四种方式 第03节、线程池原理分析 第04节、线程数合理配置 第05节、悲观锁与乐观锁区别 第06节、重入锁 第07节、读写锁 第08节、CAS无锁机制 第09节、自旋锁 资料+源码.rar 0006-蚂蚁课堂(每特学院)-2期-数据交换格式&反射机制&SpringIOC;原理分析 第01节、什么是数据交换格式 第02节、什么是json 第03节、使用fastjson解析json 第04节、使用json转换成对象 第05节、自定义json字符串 第06节、XML解析 第07节、Java的反射机制 第08节、使用java的反射机制访问私有属性 第09节、使用java的反射机制+Dom4j实现SpringIOC原理 资料+源码.rar 0007-蚂蚁课堂(每特学院)-2期-自定义注解与Java设计模式 第01节、注解概述 第02节、自定义注解 第03节、使用注解实现ORM框架映射 第04节、设计模式概述 第05节、什么是单例 第06节、饿韩式写法 第07节、工厂设计模式 第08节、代理模式概述 第09节、静态代理 第10节、JDK动态代理 第11节、CGLIB动态代理 资料+源码.rar 0008-蚂蚁课堂(每特学院)-2期-Socket网络通讯基础 第01节、网络通讯概述 第02节、TCP与UDP协议区别 第03节、UDP发送客户端与服务器 第04节、TCP协议三次握手 第05节、TCP协议发送客户端与服务器端 第06节、使用线程池支持多个线程同时访问 资源+源码.rar 0009-蚂蚁课堂(每特学院)-2期-NIO编程基础 第01节、IO与NIO区别 第02节、Buffer的数据存取 第03节、make与rest用法 第04节、直接缓冲区与非缓冲区区别 第05节、实际操作直接缓冲区与非缓冲区比较 第06节、分散读取聚集写入 第07节、编码格式 第08节、总结 资料+源码.rar 0010-蚂蚁课堂(每特学院)-2期-NIO高级编程与Netty入门 第01节、阻塞IO与非阻塞IO区别 第02节、NIO客户端与服务器端 第03节、Netty框架介绍 第04节、Netty服务器端 第05节、Netty客户端 第06节、总结 资料+源码.rar 0011-蚂蚁课堂(每特学院)-2期-Netty高级 第01节、长连接与短连接区别 第02节、粘包与拆包 第03节、序列化 资料+源码.rar 0012-蚂蚁课堂(每特学院)-2期-JVM参数调优配置 第01节、Java内存结构概述 第02节、新生代与老年代 第03节、堆内存参数配置 第04节、配置新生代与老年代调优参数 第05节、堆溢出解决办法 第06节、栈溢出解决办法 资料+源码.rar 0013-蚂蚁课堂(每特学院)-2期-垃圾回收机制算法分析 第01节、什么是java垃圾回收机制 第02节、内存溢出与内存泄露的区别 第03节、引用计数法 第04节、复制算法 第05节、标记清除与标记压缩算法 第06节、分代算法 第07节、垃圾收集器与jmeter压力测试工具用法 第08节、tomcat参数调优测试-串行回收 第09节、tomcat参数调优测试-并行回收 资料+源码.rar 0014-蚂蚁课堂(每特学院)-2期-Maven项目管理工具 第01节、Maven概述 第02节、Maven约定 第03节、Maven安装与配置 第04节、Maven常用命令 第05节、maven依赖 第06节、Maven创建分模块工程 第06节、Maven打包原理 资料+源码.rar 0015-蚂蚁课堂(每特学院)-2期-DNS解析过程&Servlet;源码分析&外网映射工具&COOKIE;与Session实现原理 第01节、javase与javaee区别 第02节、web服务器 第03节、DNS解析过程 第04节、将本地服务器映射到外网 第05节、Servlert生命周期 第06节、Servlet源码分析 第07节、Servlet是否线程安全 第08节、COOKIE底层实现原理 第09节、session底层实现原理 资料+源码.rar 0016-蚂蚁课堂(每特学院)-2期-Http协议与表单防止重复提交&跨域五种实战解决方案 第01节、Http协议概述&同步与异步区别 第02节、Http请求头分析 第03节、使用时间戳解决浏览器缓存问题 第04节、Http反盗链技术概念 第05节、Http防盗链技术代码实现 第06节、重定向实现原理 资料+源码.rar 0017-蚂蚁课堂(每特学院)-2期-表单重复提交&防止模拟请求&跨域解决方案&XSS;攻击 第00节、接口如何防止被模拟 第01节、Http与Https区别 第02节、使用HttpClient发送Http请求 第03节、使用PostMen发送Http请求 第04节、长连接与短连接区别 第05节、什么是跨域访问 第06节、使用jsonp解决跨域问题 第07节、使用HttpClient转发请求解决跨域问题 第08节、什么是表单重复提交 第09节、使用Token解决表单重复问题 资料+源码.rar 0018-蚂蚁课堂(每特学院)-2期-Linux开发001 第01节、XSS攻击流程与钓鱼网站概述 第02节、使用转义解决XSS攻击 第03节、Web安全基础总结 第04节、linux概述 第05节、Linux虚拟机安装 第06节、Linux常用命令 资料+源码.rar 0019-蚂蚁课堂(每特学院)-2期-Linux开发002 第01节、vi编辑器 第02节、使用rpm安装MySQL 第03节、静态IP设置 第04节、jdk和tomcat环境安装 第05节、克隆虚拟机 资料+源码.rar 0020-蚂蚁课堂(每特学院)-2期-MySQL优化001 第01节、MySQ概述 第02节、MySQL优化方案 第03节、数据库三大范式 第04节、分库分表 第05节、水平分割取摸算法案例 第06节、如何定位慢查询 资料+源码.rar 0021-蚂蚁课堂(每特学院)-2期-MySQL优化002 第01节、课程规划 第02节、索引概述 第03节、索引底层实现原理 第04节、普通索引&唯一索引 第05节、SQL语句优化 第06节、MySQL存储引擎区别 第07节、Myisam注意事项 资料+源码.rar 0022-蚂蚁课堂(每特学院)-2期-MySQL读写分离&Mybatis;基础知识 第01节、Mysql高可用概念 第02节、MySQL主从复制原理 第03节、MySQL主从复制配置 第04节、MySQL读写分离概述 第05节、MySQLMyCat读写分离 第06节、MyBatis概述 第07节、MyBatisSQL注入 第08节、Mybatis#与$区别 第09节、Generator使用 资料+源码.rar 0023-蚂蚁课堂(每特学院)-2期-SpringIOC与AOP 第01节、Spring概述 第02节、SpringIOC概述 第03节、Spring环境搭建 第04节、Spring依赖注入 第05节、Spring注解方式使用 第06节、代理设计模式概述 第07节、SpringAOP概述 第08节、SpringAop注解方式 第09节、SpringAopXML方式 资料+源码.rar 0024-蚂蚁课堂(每特学院)-2期-Spring事物与传播行 第01节、事物概述 第02节、Spring事物环境搭建 第03节、编程事物(手动事物) 第04节、事物底层原理分析 第05节、事物传播行为 第06节、声明事物(XML与注解方式) 资料+源码.rar 0025-蚂蚁课堂(每特学院)-2期-SpringIOC源码分析 第01节、分析源码思路 第02节、查看源码关联 第03节、BeanFactory分析 第04节、SpringIOC核心代码分析 第05节、Spring获取Bean对象 资料+源码.rar 0026-蚂蚁课堂(每特学院)-2期-SpringBean生命周期与Aop、SpringMVC执行流程源码分析 第01节、Bean生命周期分析 第02节、Spring AOP源码分析 第03节、Spring MVC源码分析 第04节、Spring MVC源码断点分析 资料+源码.rar 0027-蚂蚁课堂(每特学院)-2期-SpringBoot基础 第00节、Springboot整合使用Springjpa 第01节、Springboot整合使用SpringJpa 第01节、SpringBoot概述 第02节、创建第一个SpringBoot项目 第03节、SpringBoot提供接口 第04节、SpringBoot第二种启动方式 第05节、SpringBoot访问静态资源 第06节、SpringBoot全局捕获异常 第07节、SpringBoot整合Freemarker 第08节、SpringBoot整合JSP视图 第09节、springboot整合使用JdbcTemplate 资料+源码.rar 0028-蚂蚁课堂(每特学院)-2期-SpringBoot高级 小敏同学面试题解析 第00节、SpringBoot读取配置文件 第01节、@SpringBootApplication用法 第02节、SpringBoot修改端口号 第02节、SpringBoot多数据源拆分思路 第03节、SpringBootYML文件 第03节、SpringBoot整合多数据源实现 第04节、SpringBoot事物 第04节、SpringBoot打包发布 第05节、SpringBoot分布式事物AutoCAD 第06节、SpringBoot整合日志 第07节、SpringBootAOP统一处理Web请求 第08节、SpringBoot实现定时任务 第09节、SpringBoot异步调用 第11节、SpringBoot区分不同环境 资料+源码.rar 0029-蚂蚁课堂(每特学院)-2期-Redis基础 第01节、什么是Java内置缓存 第02节、Java操作EhCache 第03节、什么是nosql数据库 第04节、NoSQ介绍 第05节、windows安装redis 第06节、Linux环境安装Redis 第07节、Redis五种基本数据类型 第08节、jedis操作Redis数据库 资料+源码.rar 0030-蚂蚁课堂(每特学院)-2期-Redis高级 知识点回顾 第01节、Spring封装Redis 第02节、Redis主从复制与哨兵机制 第03节、Redis主从复制实现 第04节、Redis哨兵机制实现 第05节、Redis持久化RDB存储 第06节、Redis持久化之AOF 第07节、Redis事物机制 第08节、Redis哨兵机制 资料+源码.rar 0031-蚂蚁课堂(每特学院)-2期-Nginx基础 第01节、nginx基础与安全体系架构 第02节、windows安装nginx 第03节、服务器集群产生的问题 第04节、使用Nginx搭建集群 第05节、负载均衡策略 第06节、服务器宕机容错机制 第07节、使用Nginx搭建企业API接口网关 资料+源码.rar 0032-蚂蚁课堂(每特学院)-2期-Nginx高可用 第01节、分布式与集群区别 第02节、Linux环境下安装nginx 第03节、Keepalived高可用介绍 第04节、使用nginx+Keepalived服务器高可用 第05节、Session共享解决方案 第06节、高并发解决方案总结 资料+源码.rar 0033-蚂蚁课堂(每特学院)-2期-ActivityMQ基础 第01节、同步请求概念介绍 第02节、为什么要使用消息中间件 第03节、消息中间件通讯方式 第04节、消息中间使用场景 第05节、windows环境安装ActiveMQ 第06节、ActiveMQ发布订阅 第06节、ActiveMQ消费者 第06节、ActiveMQ生产者 资料+源码.rar 0034-蚂蚁课堂(每特学院)-2期-ActivityMQ高级 第01节、课前回顾 第02节、ActivityMQ设置持久化机制 第03节、JMS可靠消息 第04节、SpringBoot整合ActivityMQ 第05节、使用ActivityMQ注意事项 第06节、解决消息中间件幂等 第07节、消费者集群 资料+源码.rar 0035-蚂蚁课堂(每特学院)-2期-RocketMQ基础 第01节、rocketmq概述 第02节、rocketmq原理 第03节、rocketmq环境搭建 第04节、rocketmq集群环境搭建 第05节、rocketmqApi介绍 第06节、rocketmq重试机制与幂等解决 资料+源码.rar 0037-蚂蚁课堂(每特学院)-2期-春节总结.zip 0038-蚂蚁课堂(每特学院)-2期-分布式JOB任务调度平台 第01节、任务调度概述 第02节、使用Quartz实现定时调度 第03节、分布JOB如何解决幂等性 第04节、XXL-JOB环境概述 第05节、分布式任务调度平台执行原理 第06节、任务调度平台执行器运行 第07节、任务调度平台路由策略 资料+源码.rar 0039-蚂蚁课堂(每特学院)-2期-SpringCloud微服务基础 第01节、网站架构演变过程 第02节、微服务架构概述 第03节、SpringCloud概述 第04节、服务注册与服务发现 第05节、搭建euraka注册中心 第06节、发布服务会员提供者 第07节、消费会员服务 第08节、SpringCloud调用服务原理 资料+源码.rar 0040-蚂蚁课堂(每特学院)-2期-SpringCloud微服务高级 第01节、SpringCloud服务负载均衡实现原理 第02节、使用ribbon搭建服务负载均衡 第03节、什么是接口网关 第04节、使用Zuul搭建服务接口网关 第05节、使用Zuul网关拦截参数 第06节、分布式配置中心概述 第07节、搭建分布式配置中心 课前复习 资料+源码.rar 0041-蚂蚁课堂(每特学院)-2期-SpringCloud架构知识-服务熔断、服务降级、限流策略 第01节、fegin客户端调用工具 第02节、服务雪崩效应产生原因 第03节、模拟服务雪崩效果产生 第04节、雪崩效应解决办法 第05节、使用hystrix实现服务降级 第06节、使用hystrix解决服务雪崩原因 第07节、SpringCloud面试题 课前复习 资料+源码.rar 0042-蚂蚁课堂(每特学院)-2期-Zookeeper基础 第01节、Zookeeper概述 第02节、Zookeeper应用场景 第03节、Windows环境下搭建Zookeeper 第04节、Java语言操作Zookeeper 第05节、创建Zookeeper临时节点 第06节、Watcher事件通知 第07节、Zookeeper基础知识 资料+源码.rar 0043-蚂蚁课堂(每特学院)-2期-Zookeeper实现分布式锁 第01节、使用Zookeeper实现分布式锁概述 第02节、解决生产订单号线程安全问题 第03节、实现分布式锁解决方案 第04节、Zookeeper概述 第05节、使用Zookeeper实现分布式锁 资料+源码.rar 0044-蚂蚁课堂(每特学院)-2期-Zookeeper实现负载均衡与选举策略 第01节、使用Zookeeper实现负载均衡原理 第02节、使用Zookeeper实现分布式锁回顾 第03节、搭建负载均衡项目环境 第04节、代码实现负载均衡策略 第05节、实现负载均衡轮训算法 第06节、思考题使用Zookeeper实现选举策略 资料+源码.rar 0045-蚂蚁课堂(每特学院)-2期-Dubbo基础 第01节、网站系统演变过程 第02节、分布式开发需要使用到技术 第03节、微服务架构 第04节、Dubbo架构原理 第05节、Dubbo能够解决问题 第06节、Dubbo创建项目架构模式 第07节、发布会员服务-使用Dubbo提供服务 第08节、订单消费服务-使用Dubbo消费服务 资料+源码.rar 0046-蚂蚁课堂(每特学院)-2期-Dubbo高级 第01节、Dubbo-Admin平台 第02节、Dubbo实现负载均衡、容错机制 第03节、Dubbx使用 第04节、Dubbo相关面试题 资料+源码.rar 0047-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之环境搭建与代码重构 第00节、封装LogAspectServiceApi 第01节、技术知识点回顾 第02节、系统架构流程 第03节、项目使用技术知识点 第04节、项目结构创建方式 第05节、搭建SpringCloudEurekaServer注册中心 第06节、创建会员API工程发布服务 第07节、ResponseBase封装 第08节、BaseApiService封装 第09节、lomBok插件安装 资料+源码.rar 0048-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之会员系统-会员服务平台搭建 第01节、数据库垂直与水平拆分区别区别 第02节、整合数据库访问层 第03节、提供会员注册接口 第04节、会员注册整体流程 第05节、搭建消息服务平台 第06节、会员服务发送消息到消息服务平台 资料+源码.rar 0049-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之会员系统-会员登录与注册 第01节、项目概述 第02节、注册163邮箱账号 第03节、消息服务平台-发送邮件 第04节、移动App登录 第05节、使用令牌方式进行登录 第06节、使用token查询用户信息 资料+源码.rar 0050-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之会员系统-H5端与第三方授权联合登录 第01节、分布式系统Web控制概述 第02节、创建Web项目 第03节、Web项目整合视图 第04节、使用fegin调用会员服务实现注册 第05节、使用fegin调用会员服务实现登录 第06节、使用fegin调用会员服务查询用户信息 第07节、QQ授权登录流程 第08节、oauth2.0协议概述 第09节、会员服务提供联合登录接口 第10节、关联账号原理 第11节、生成QQ授权登录链接 第12节、生成授权码链接 第13节、授权登录关联账号 资料+源码.rar 0051-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之微信公众号系统-初始微信公众号 第01节、微信公众号项目演示 第02节、混合开发技术流程 第03节、微信公众号平台 第04节、微信消息推送原理 第05节、外网映射工具 第06节、微信服务器通知验证 第07节、接受微信服务推送消息 第08节、微信推送消息幂等 第09节、调用智能机器人接口 资料+源码.rar 0052-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之微信公众号系统-使用框架开发微信公众号 上节课疑问 第01节、微信框架介绍 第02节、项目中使用微信框架 第03节、创建微信模板接口 第04节、项目使用模板接口 第05节、HTTP协议参数转义 资料+源码.rar 0053-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之支付系统-支付宝平台源码分析 第01节、支付项目概述 第02节、聚合支付概念 第03节、支付架构整个流程 第04节、支付宝沙箱环境 第05节、安全加密方式 第06节、支付宝Demo环境运行 第07节、支付宝流程源码分析 资料+源码.rar 0054-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之支付系统-实现分布式事物解决方案 第01节、支付流程回顾 第02节、项目整合支付接口流程 第03节、创建支付token接口 第04节、实现查询支付token 第05节、PC-Web调用支付接口完成整体流程 资料+源码.rar 0055-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之支付系统-支付回调核心代码 第01节、支付宝回调接口 第02节、同步回调与异步回调 第03节、同步回调业务逻辑 第04节、Web接口调用同步服务层代码 第05节、使用from表单隐藏同步回调参数 资料+源码.rar 0056-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之支付系统-支付宝异步回调 第01节、异步回调概述 第02节、创建订单服务 第03节、完成订单服务 第04节、异步回调幂等性 第05节、异步回调分布式事物问题 第06节、支付宝异步联调测试 第07节、用户对一笔订单重复支付 资料+源码.rar 0057-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之支付系统-分布式事物解决方案 第01节、分布式事物解决方案概述 第02节、分布式事物产生的原因 第03节、CPA与Base理论 第04节、2PC-两段提交协议 第05节、使用MQ解决分布式事物思想 资料+源码.rar 0058-蚂蚁课堂(每特学院)-2期-互联网微服务电商项目之项目总结 - 使用LCN框架解决分布式事物 第01节、LCN框架介绍 第02节、LCN框架原理 第03节、LCN框架执行流程 第04节、启动tx-manager 第06节、演示分布式事物场景 第07节、使用LCN框架解决分布式事物 资料+源码.rar 0059-蚂蚁课堂(每特教育)-2期-Java就业指导 第一天 第01节、传统公司与互联网公司区别 第02节、简历书写介绍 第03节、书写一套高级工程师简历 第04节、简历中项目介绍 第05节、投简历方式 第06节、外包与本部区别 第07节、外包公司选择 资料+源码.rar 0060-蚂蚁课堂(每特教育)-2期-Java就业指导 第二天 第01节、外包公司盈利模式分析 第02节、一线城市就业薪资 第03节、面试官问,你最大的收获是什么 第04节、面试问,你们项目中最大的难题,你是如何解决的? 第05节、让面试官跟着你的思路走 资料+源码.rar

img
c#多线程抓取网页内容

在《爬虫/蜘蛛程序的制作(C#语言)》一文中,已经介绍了爬虫程序实现的基本方法,可以说,已经实现了爬虫的功能。只是它存在一个效率问题,下载速度可能很慢。这是两方面的原因造成的: 1. 分析和下载不能同步进行。在《爬虫/蜘蛛程序的制作(C#语言)》中已经介绍了爬虫程序的两个步骤:分析和下载。在单线程的程序中,两者是无法同时进行的。也就是说,分析时会造成网络空闲,分析的时间越长,下载的效率越低。反之也是一样,下载时无法同时进行分析,只有停下下载后才能进行下一步的分析。问题浮出水面,我想大家都会想到:把分析和下载用不同的线程进行,问题不就解决了吗? 2. 只是单线程下载。相信大家都有用过网际快车等下载资源的经历,它里面是可以设置线程数的(近年版本默认是10,曾经默认是5)。它会将文件分成与线程数相同的部分,然后每个线程下载自己的那一部分,这样下载效率就有可能提高。相信大家都有加多线程数,提升下载效率的经历。但细心的用户会发现,在带宽一定的情况下,并不是线程越多,速度越快,而是在某一点达到峰值。爬虫作为特殊的下载工具,不具备多线程的能力何以有效率可谈?爬虫在信息时代的目的,难道不是快速获取信息吗?所以,爬虫需要有多线程(可控数量)同时下载网页。 好了,认识、分析完问题,就是解决问题了: 多线程在C#中并不难实现。它有一个命名空间:System.Threading,提供了多线程的支持。 要开启一个新线程,需要以下的初始化: ThreadStart startDownload = new ThreadStart( DownLoad ); //线程起始设置:即每个线程都执行DownLoad(),注意:DownLoad()必须为不带有参数的方法 Thread downloadThread = new Thread( startDownload ); //实例化要开启的新类 downloadThread.Start();//开启线程 由于线程起始时启动的方法不能带有参数,这就为多线程共享资源添加了麻烦。不过我们可以用类级变量(当然也可以使用其它方法,笔者认为此方法最简单易用)来解决这个问题。知道开启多线程下载的方法后,大家可能会产生几个疑问: 1. 如何控制线程的数量? 2. 如何防止多线程下载同一网页? 3. 如何判断线程结束? 4. 如何控制线程结束? 下面就这几个问题提出解决方法: 1. 线程数量我们可以通过for循环来实现,就如同当年初学编程的打点程序一样。 比如已知用户指定了n(它是一个int型变量)个线程吧,可以用如下方法开启五个线程 Thread[] downloadThread;//声名下载线程,这是C#的优势,即数组初始化时,不需要指定其长度,可以在使用时才指定。这个声名应为类级,这样也就为其它方法控件它们提供了可能 ThreadStart startDownload = new ThreadStart( DownLoad );//线程起始设置:即每个线程都执行DownLoad() downloadThread = new Thread[ n ];//为线程申请资源,确定线程总数 for( int i = 0; i < n; i++ )//开启指定数量的线程数 { downloadThread[i] = new Thread( startDownload );//指定线程起始设置 downloadThread[i].Start();//逐个开启线程 } 好了,实现控制开启线程数是不是很简单啊? 2. 下面出现的一个问题:所有的线程都调用DonwLoad()方法,这样如何避免它们同时下载同一个网页呢? 这个问题也好解决,只要建立一下Url地址表,表中的每个地址只允许被一个线程申请即可。具体实现: 可以利用数据库,建立一个表,表中有四列,其中一列专门用于存储Url地址,另外两列分别存放地址对应的线程以及该地址被申请的次数,最后一列存放下载的内容。(当然,对应线程一列不是必要的)。当有线程申请后,将对应线程一列设定为当前线程编号,并将是否申请过一列设置为申请一次,这样,别的线程就无法申请该页。如果下载成功,则将内容存入内容列。如果不成功,内容列仍为空,作为是否再次下载的依据之一,如果反复不成功,则进程将于达到重试次数(对应该地址被申请的次数,用户可设)后,申请下一个Url地址。主要的代码如下(以VFP为例): <建立表> CREATE TABLE (ctablename) ( curl M , ctext M , ldowned I , threadNum I ) &&建立一个表ctablename.dbf,含有地址、文本内容、已经尝试下载次数、线程标志(初值为-1,线程标志是从0开始的整数)四个字段 <提取Url地址> cfullname = (ctablename) + '.dbf'&&为表添加扩展名 USE (cfullname) GO TOP LOCATE FOR (EMPTY( ALLTRIM( ctext ) ) AND ldowned < 2 AND ( threadNum = thisNum OR threadNum = - 1) ) &&查找尚未下载成功且应下载的属于本线程权限的Url地址,thisNum是当前线程的编号,可以通过参数传递得到 gotUrl = curl recNum = RECNO() IF recNum <= RECCOUNT() THEN &&如果在列表中找到这样的Url地址 UPDATE (cfullname) SET ldowned = ( ldowned + 1 ) , threadNum = thisNum WHERE RECNO() = recNum &&更新表,将此记录更新为已申请,即下载次数加1,线程标志列设为本线程的编号。 <下载内容> cfulltablename = (ctablename) + '.dbf' USE (cfulltablename) SET EXACT ON LOCATE FOR curl = (csiteurl) &&csiteurl是参数,为下载到的内容所对应的Url地址 recNumNow = RECNO()&&得到含有此地址的记录号 UPDATE (cfulltablename) SET ctext = (ccontent) WHERE RECNO() = recNumNow &&插入对应地址的对应内容 <插入新地址> ctablename = (ctablename) + '.dbf' USE (ctablename) GO TOP SET EXACT ON LOCATE FOR curl = (cnewurl) &&查找有无此地址 IF RECNO() > RECCOUNT() THEN &&如果尚无此地址 SET CARRY OFF INSERT INTO (ctablename) ( curl , ctext , ldowned , threadNum ) VALUES ( (cnewurl) , "" , 0 , -1 ) &&将主页地址添加到列表 好了,这样就解决了多线程中,线程冲突。当然,去重问题也可以在C#语言内解决,只根建立一个临时文件(文本就可以),保存所有的Url地址,差对它们设置相应的属性即可,但查找效率可能不及数据库快。 3. 线程结束是很难判断的,因为它总是在查找新的链接。用者认为可以假设:线程重复N次以后还是没有能申请到新的Url地址,那么可以认为它已经下载完了所有链接。主要代码如下: string url = ""; int times = 0; while ( url == "" )//如果没有找到符合条件的记录,则不断地寻找符合条件的记录 { url = getUrl.GetAUrl( …… );//调用GetAUrl方法,试图得到一个url值 if ( url == "" )//如果没有找到 { times ++;//尝试次数自增 continue; //进行下一次尝试 } if ( times > N ) //如果已经尝试够了次数,则退出进程 { downloadThread[i].Abort; //退出进程 } else//如果没有尝试够次数 { Times = 0; //尝试次数归零处理 } //进行下一步针对得到的Url的处理 } 4. 这个问题相对简单,因为在问题一中已经建议,将线程声名为类级数组,这样就很易于控制。只要用一个for循环即可结束。代码如下: for( int i = 0; i < n; i++ )//关闭指定数量n的线程数 { downloadThread[i].Abort();//逐个关闭线程 } 好了,一个蜘蛛程序就这样完成了,在C#面前,它的实现原来如此简单。 这里笔者还想提醒读者:笔者只是提供了一个思路及一个可以实现的解决方案,但它并不是最佳的,即使这个方案本身,也有好多可以改进的地方,留给读者思考。 最后说明一下我所使用的环境: winXP sp2 Pro VFP 9.0 Visual Studio 2003 .net中文企业版 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/peter1_jiang/archive/2007/10/23/1839137.aspx

img
操作系统(内存管理)

文将对 Linux™ 程序员可以使用的内存管理技术进行概述,虽然关注的重点是 C 语言,但同样也适用于其他语言。文中将为您提供如何管理内存的细节,然后将进一步展示如何手工管理内存,如何使用引用计数或者内存池来半手工地管理内存,以及如何使用垃圾收集自动管理内存。 为什么必须管理内存 内存管理是计算机编程最为基本的领域之一。在很多脚本语言中,您不必担心内存是如何管理的,这并不能使得内存管理的重要性有一点点降低。对实际编程来说,理解您的内存管理器的能力与局限性至关重要。在大部分系统语言中,比如 C 和 C++,您必须进行内存管理。本文将介绍手工的、半手工的以及自动的内存管理实践的基本概念。 追溯到在 Apple II 上进行汇编语言编程的时代,那时内存管理还不是个大问题。您实际上在运行整个系统。系统有多少内存,您就有多少内存。您甚至不必费心思去弄明白它有多少内存,因为每一台机器的内存数量都相同。所以,如果内存需要非常固定,那么您只需要选择一个内存范围并使用它即可。 不过,即使是在这样一个简单的计算机中,您也会有问题,尤其是当您不知道程序的每个部分将需要多少内存时。如果您的空间有限,而内存需求是变化的,那么您需要一些方法来满足这些需求: 确定您是否有足够的内存来处理数据。 从可用的内存中获取一部分内存。 向可用内存池(pool)中返回部分内存,以使其可以由程序的其他部分或者其他程序使用。 实现这些需求的程序库称为 分配程序(allocators),因为它们负责分配和回收内存。程序的动态性越强,内存管理就越重要,您的内存分配程序的选择也就更重要。让我们来了解可用于内存管理的不同方法,它们的好处与不足,以及它们最适用的情形。 回页首 C 风格的内存分配程序 C 编程语言提供了两个函数来满足我们的三个需求: malloc:该函数分配给定的字节数,并返回一个指向它们的指针。如果没有足够的可用内存,那么它返回一个空指针。 free:该函数获得指向由 malloc 分配的内存片段的指针,并将其释放,以便以后的程序或操作系统使用(实际上,一些 malloc 实现只能将内存归还给程序,而无法将内存归还给操作系统)。 物理内存和虚拟内存 要理解内存在程序中是如何分配的,首先需要理解如何将内存从操作系统分配给程序。计算机上的每一个进程都认为自己可以访问所有的物理内存。显然,由于同时在运行多个程序,所以每个进程不可能拥有全部内存。实际上,这些进程使用的是 虚拟内存。 只是作为一个例子,让我们假定您的程序正在访问地址为 629 的内存。不过,虚拟内存系统不需要将其存储在位置为 629 的 RAM 中。实际上,它甚至可以不在 RAM 中 —— 如果物理 RAM 已经满了,它甚至可能已经被转移到硬盘上!由于这类地址不必反映内存所在的物理位置,所以它们被称为虚拟内存。操作系统维持着一个虚拟地址到物理地址的转换的表,以便计算机硬件可以正确地响应地址请求。并且,如果地址在硬盘上而不是在 RAM 中,那么操作系统将暂时停止您的进程,将其他内存转存到硬盘中,从硬盘上加载被请求的内存,然后再重新启动您的进程。这样,每个进程都获得了自己可以使用的地址空间,可以访问比您物理上安装的内存更多的内存。 在 32-位 x86 系统上,每一个进程可以访问 4 GB 内存。现在,大部分人的系统上并没有 4 GB 内存,即使您将 swap 也算上, 每个进程所使用的内存也肯定少于 4 GB。因此,当加载一个进程时,它会得到一个取决于某个称为 系统中断点(system break)的特定地址的初始内存分配。该地址之后是未被映射的内存 —— 用于在 RAM 或者硬盘中没有分配相应物理位置的内存。因此,如果一个进程运行超出了它初始分配的内存,那么它必须请求操作系统“映射进来(map in)”更多的内存。(映射是一个表示一一对应关系的数学术语 —— 当内存的虚拟地址有一个对应的物理地址来存储内存内容时,该内存将被映射。) 基于 UNIX 的系统有两个可映射到附加内存中的基本系统调用: brk: brk() 是一个非常简单的系统调用。还记得系统中断点吗?该位置是进程映射的内存边界。 brk() 只是简单地将这个位置向前或者向后移动,就可以向进程添加内存或者从进程取走内存。 mmap: mmap(),或者说是“内存映像”,类似于 brk(),但是更为灵活。首先,它可以映射任何位置的内存,而不单单只局限于进程。其次,它不仅可以将虚拟地址映射到物理的 RAM 或者 swap,它还可以将它们映射到文件和文件位置,这样,读写内存将对文件中的数据进行读写。不过,在这里,我们只关心 mmap 向进程添加被映射的内存的能力。 munmap() 所做的事情与 mmap() 相反。 如您所见, brk() 或者 mmap() 都可以用来向我们的进程添加额外的虚拟内存。在我们的例子中将使用 brk(),因为它更简单,更通用。 实现一个简单的分配程序 如果您曾经编写过很多 C 程序,那么您可能曾多次使用过 malloc() 和 free()。不过,您可能没有用一些时间去思考它们在您的操作系统中是如何实现的。本节将向您展示 malloc 和 free 的一个最简化实现的代码,来帮助说明管理内存时都涉及到了哪些事情。 要试着运行这些示例,需要先 复制本代码清单,并将其粘贴到一个名为 malloc.c 的文件中。接下来,我将一次一个部分地对该清单进行解释。 在大部分操作系统中,内存分配由以下两个简单的函数来处理: void *malloc(long numbytes):该函数负责分配 numbytes 大小的内存,并返回指向第一个字节的指针。 void free(void *firstbyte):如果给定一个由先前的 malloc 返回的指针,那么该函数会将分配的空间归还给进程的“空闲空间”。 malloc_init 将是初始化内存分配程序的函数。它要完成以下三件事:将分配程序标识为已经初始化,找到系统中最后一个有效内存地址,然后建立起指向我们管理的内存的指针。这三个变量都是全局变量: 清单 1. 我们的简单分配程序的全局变量 int has_initialized = 0; void *managed_memory_start; void *last_valid_address; 如前所述,被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者 当前中断点。在很多 UNIX® 系统中,为了指出当前系统中断点,必须使用 sbrk(0) 函数。 sbrk 根据参数中给出的字节数移动当前系统中断点,然后返回新的系统中断点。使用参数 0 只是返回当前中断点。这里是我们的 malloc 初始化代码,它将找到当前中断点并初始化我们的变量: 清单 2. 分配程序初始化函数 /* Include the sbrk function */ #include void malloc_init() { /* grab the last valid address from the OS */ last_valid_address = sbrk(0); /* we don't have any memory to manage yet, so *just set the beginning to be last_valid_address */ managed_memory_start = last_valid_address; /* Okay, we're initialized and ready to go */ has_initialized = 1; } 现在,为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。在对内存块进行了 free 调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用 malloc 时,我们要能够定位未被使用的内存块。因此, malloc 返回的每块内存的起始处首先要有这个结构: 清单 3. 内存控制块结构定义 struct mem_control_block { int is_available; int size; }; 现在,您可能会认为当程序调用 malloc 时这会引发问题 —— 它们如何知道这个结构?答案是它们不必知道;在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。这使得返回的指针指向没有用于任何其他用途的内存。那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。然后,当通过 free() 将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。 在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退 sizeof(struct mem_control_block) 个字节,并将其标记为可用的。这里是对应的代码: 清单 4. 解除分配函数 void free(void *firstbyte) { struct mem_control_block *mcb; /* Backup from the given pointer to find the * mem_control_block */ mcb = firstbyte - sizeof(struct mem_control_block); /* Mark the block as being available */ mcb->is_available = 1; /* That's It! We're done. */ return; } 如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。分配内存稍微困难一些。以下是该算法的略述: 清单 5. 主分配程序的伪代码 1. If our allocator has not been initialized, initialize it. 2. Add sizeof(struct mem_control_block) to the size requested. 3. start at managed_memory_start. 4. Are we at last_valid address? 5. If we are: A. We didn't find any existing space that was large enough -- ask the operating system for more and return that. 6. Otherwise: A. Is the current space available (check is_available from the mem_control_block)? B. If it is: i) Is it large enough (check "size" from the mem_control_block)? ii) If so: a. Mark it as unavailable b. Move past mem_control_block and return the pointer iii) Otherwise: a. Move forward "size" bytes b. Go back go step 4 C. Otherwise: i) Move forward "size" bytes ii) Go back to step 4 我们主要使用连接的指针遍历内存来寻找开放的内存块。这里是代码: 清单 6. 主分配程序 void *malloc(long numbytes) { /* Holds where we are looking in memory */ void *current_location; /* This is the same as current_location, but cast to a * memory_control_block */ struct mem_control_block *current_location_mcb; /* This is the memory location we will return. It will * be set to 0 until we find something suitable */ void *memory_location; /* Initialize if we haven't already done so */ if(! has_initialized) { malloc_init(); } /* The memory we search for has to include the memory * control block, but the users of malloc don't need * to know this, so we'll just add it in for them. */ numbytes = numbytes + sizeof(struct mem_control_block); /* Set memory_location to 0 until we find a suitable * location */ memory_location = 0; /* Begin searching at the start of managed memory */ current_location = managed_memory_start; /* Keep going until we have searched all allocated space */ while(current_location != last_valid_address) { /* current_location and current_location_mcb point * to the same address. However, current_location_mcb * is of the correct type, so we can use it as a struct. * current_location is a void pointer so we can use it * to calculate addresses. */ current_location_mcb = (struct mem_control_block *)current_location; if(current_location_mcb->is_available) { if(current_location_mcb->size >= numbytes) { /* Woohoo! We've found an open, * appropriately-size location. */ /* It is no longer available */ current_location_mcb->is_available = 0; /* We own it */ memory_location = current_location; /* Leave the loop */ break; } } /* If we made it here, it's because the Current memory * block not suitable; move to the next one */ current_location = current_location + current_location_mcb->size; } /* If we still don't have a valid location, we'll * have to ask the operating system for more memory */ if(! memory_location) { /* Move the program break numbytes further */ sbrk(numbytes); /* The new memory will be where the last valid * address left off */ memory_location = last_valid_address; /* We'll move the last valid address forward * numbytes */ last_valid_address = last_valid_address + numbytes; /* We need to initialize the mem_control_block */ current_location_mcb = memory_location; current_location_mcb->is_available = 0; current_location_mcb->size = numbytes; } /* Now, no matter what (well, except for error conditions), * memory_location has the address of the memory, including * the mem_control_block */ /* Move the pointer past the mem_control_block */ memory_location = memory_location + sizeof(struct mem_control_block); /* Return the pointer */ return memory_location; } 这就是我们的内存管理器。现在,我们只需要构建它,并在程序中使用它即可。 运行下面的命令来构建 malloc 兼容的分配程序(实际上,我们忽略了 realloc() 等一些函数,不过, malloc() 和 free() 才是最主要的函数): 清单 7. 编译分配程序 gcc -shared -fpic malloc.c -o malloc.so 该程序将生成一个名为 malloc.so 的文件,它是一个包含有我们的代码的共享库。 在 UNIX 系统中,现在您可以用您的分配程序来取代系统的 malloc(),做法如下: 清单 8. 替换您的标准的 malloc LD_PRELOAD=/path/to/malloc.so export LD_PRELOAD LD_PRELOAD 环境变量使动态链接器在加载任何可执行程序之前,先加载给定的共享库的符号。它还为特定库中的符号赋予优先权。因此,从现在起,该会话中的任何应用程序都将使用我们的 malloc(),而不是只有系统的应用程序能够使用。有一些应用程序不使用 malloc(),不过它们是例外。其他使用 realloc() 等其他内存管理函数的应用程序,或者错误地假定 malloc() 内部行为的那些应用程序,很可能会崩溃。ash shell 似乎可以使用我们的新 malloc() 很好地工作。 如果您想确保 malloc() 正在被使用,那么您应该通过向函数的入口点添加 write() 调用来进行测试。 我们的内存管理器在很多方面都还存在欠缺,但它可以有效地展示内存管理需要做什么事情。它的某些缺点包括: 由于它对系统中断点(一个全局变量)进行操作,所以它不能与其他分配程序或者 mmap 一起使用。 当分配内存时,在最坏的情形下,它将不得不遍历 全部进程内存;其中可能包括位于硬盘上的很多内存,这意味着操作系统将不得不花时间去向硬盘移入数据和从硬盘中移出数据。 没有很好的内存不足处理方案( malloc 只假定内存分配是成功的)。 它没有实现很多其他的内存函数,比如 realloc()。 由于 sbrk() 可能会交回比我们请求的更多的内存,所以在堆(heap)的末端会遗漏一些内存。 虽然 is_available 标记只包含一位信息,但它要使用完整的 4-字节 的字。 分配程序不是线程安全的。 分配程序不能将空闲空间拼合为更大的内存块。 分配程序的过于简单的匹配算法会导致产生很多潜在的内存碎片。 我确信还有很多其他问题。这就是为什么它只是一个例子! 其他 malloc 实现 malloc() 的实现有很多,这些实现各有优点与缺点。在设计一个分配程序时,要面临许多需要折衷的选择,其中包括: 分配的速度。 回收的速度。 有线程的环境的行为。 内存将要被用光时的行为。 局部缓存。 簿记(Bookkeeping)内存开销。 虚拟内存环境中的行为。 小的或者大的对象。 实时保证。 每一个实现都有其自身的优缺点集合。在我们的简单的分配程序中,分配非常慢,而回收非常快。另外,由于它在使用虚拟内存系统方面较差,所以它最适于处理大的对象。 还有其他许多分配程序可以使用。其中包括: Doug Lea Malloc:Doug Lea Malloc 实际上是完整的一组分配程序,其中包括 Doug Lea 的原始分配程序,GNU libc 分配程序和 ptmalloc。 Doug Lea 的分配程序有着与我们的版本非常类似的基本结构,但是它加入了索引,这使得搜索速度更快,并且可以将多个没有被使用的块组合为一个大的块。它还支持缓存,以便更快地再次使用最近释放的内存。 ptmalloc 是 Doug Lea Malloc 的一个扩展版本,支持多线程。在本文后面的 参考资料部分中,有一篇描述 Doug Lea 的 Malloc 实现的文章。 BSD Malloc:BSD Malloc 是随 4.2 BSD 发行的实现,包含在 FreeBSD 之中,这个分配程序可以从预先确实大小的对象构成的池中分配对象。它有一些用于对象大小的 size 类,这些对象的大小为 2 的若干次幂减去某一常数。所以,如果您请求给定大小的一个对象,它就简单地分配一个与之匹配的 size 类。这样就提供了一个快速的实现,但是可能会浪费内存。在 参考资料部分中,有一篇描述该实现的文章。 Hoard:编写 Hoard 的目标是使内存分配在多线程环境中进行得非常快。因此,它的构造以锁的使用为中心,从而使所有进程不必等待分配内存。它可以显著地加快那些进行很多分配和回收的多线程进程的速度。在 参考资料部分中,有一篇描述该实现的文章。 众多可用的分配程序中最有名的就是上述这些分配程序。如果您的程序有特别的分配需求,那么您可能更愿意编写一个定制的能匹配您的程序内存分配方式的分配程序。不过,如果不熟悉分配程序的设计,那么定制分配程序通常会带来比它们解决的问题更多的问题。要获得关于该主题的适当的介绍,请参阅 Donald Knuth 撰写的 The Art of Computer Programming Volume 1: Fundamental Algorithms 中的第 2.5 节“Dynamic Storage Allocation”(请参阅 参考资料中的链接)。它有点过时,因为它没有考虑虚拟内存环境,不过大部分算法都是基于前面给出的函数。 在 C++ 中,通过重载 operator new(),您可以以每个类或者每个模板为单位实现自己的分配程序。在 Andrei Alexandrescu 撰写的 Modern C++ Design 的第 4 章(“Small Object Allocation”)中,描述了一个小对象分配程序(请参阅 参考资料中的链接)。 基于 malloc() 的内存管理的缺点 不只是我们的内存管理器有缺点,基于 malloc() 的内存管理器仍然也有很多缺点,不管您使用的是哪个分配程序。对于那些需要保持长期存储的程序使用 malloc() 来管理内存可能会非常令人失望。如果您有大量的不固定的内存引用,经常难以知道它们何时被释放。生存期局限于当前函数的内存非常容易管理,但是对于生存期超出该范围的内存来说,管理内存则困难得多。而且,关于内存管理是由进行调用的程序还是由被调用的函数来负责这一问题,很多 API 都不是很明确。 因为管理内存的问题,很多程序倾向于使用它们自己的内存管理规则。C++ 的异常处理使得这项任务更成问题。有时好像致力于管理内存分配和清理的代码比实际完成计算任务的代码还要多!因此,我们将研究内存管理的其他选择。 回页首 半自动内存管理策略 引用计数 引用计数是一种 半自动(semi-automated)的内存管理技术,这表示它需要一些编程支持,但是它不需要您确切知道某一对象何时不再被使用。引用计数机制为您完成内存管理任务。 在引用计数中,所有共享的数据结构都有一个域来包含当前活动“引用”结构的次数。当向一个程序传递一个指向某个数据结构指针时,该程序会将引用计数增加 1。实质上,您是在告诉数据结构,它正在被存储在多少个位置上。然后,当您的进程完成对它的使用后,该程序就会将引用计数减少 1。结束这个动作之后,它还会检查计数是否已经减到零。如果是,那么它将释放内存。 这样做的好处是,您不必追踪程序中某个给定的数据结构可能会遵循的每一条路径。每次对其局部的引用,都将导致计数的适当增加或减少。这样可以防止在使用数据结构时释放该结构。不过,当您使用某个采用引用计数的数据结构时,您必须记得运行引用计数函数。另外,内置函数和第三方的库不会知道或者可以使用您的引用计数机制。引用计数也难以处理发生循环引用的数据结构。 要实现引用计数,您只需要两个函数 —— 一个增加引用计数,一个减少引用计数并当计数减少到零时释放内存。 一个示例引用计数函数集可能看起来如下所示: 清单 9. 基本的引用计数函数 /* Structure Definitions*/ /* Base structure that holds a refcount */ struct refcountedstruct { int refcount; } /* All refcounted structures must mirror struct * refcountedstruct for their first variables */ /* Refcount maintenance functions */ /* Increase reference count */ void REF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount++; } /* Decrease reference count */ void UNREF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount--; /* Free the structure if there are no more users */ if(rstruct->refcount == 0) { free(rstruct); } } REF 和 UNREF 可能会更复杂,这取决于您想要做的事情。例如,您可能想要为多线程程序增加锁,那么您可能想扩展 refcountedstruct,使它同样包含一个指向某个在释放内存之前要调用的函数的指针(类似于面向对象语言中的析构函数 —— 如果您的结构中包含这些指针,那么这是 必需的)。 当使用 REF 和 UNREF 时,您需要遵守这些指针的分配规则: UNREF 分配前左端指针(left-hand-side pointer)指向的值。 REF 分配后左端指针(left-hand-side pointer)指向的值。 在传递使用引用计数的结构的函数中,函数需要遵循以下这些规则: 在函数的起始处 REF 每一个指针。 在函数的结束处 UNREF 第一个指针。 以下是一个使用引用计数的生动的代码示例: 清单 10. 使用引用计数的示例 /* EXAMPLES OF USAGE */ /* Data type to be refcounted */ struct mydata { int refcount; /* same as refcountedstruct */ int datafield1; /* Fields specific to this struct */ int datafield2; /* other declarations would go here as appropriate */ }; /* Use the functions in code */ void dosomething(struct mydata *data) { REF(data); /* Process data */ /* when we are through */ UNREF(data); } struct mydata *globalvar1; /* Note that in this one, we don't decrease the * refcount since we are maintaining the reference * past the end of the function call through the * global variable */ void storesomething(struct mydata *data) { REF(data); /* passed as a parameter */ globalvar1 = data; REF(data); /* ref because of Assignment */ UNREF(data); /* Function finished */ } 由于引用计数是如此简单,大部分程序员都自已去实现它,而不是使用库。不过,它们依赖于 malloc 和 free 等低层的分配程序来实际地分配和释放它们的内存。 在 Perl 等高级语言中,进行内存管理时使用引用计数非常广泛。在这些语言中,引用计数由语言自动地处理,所以您根本不必担心它,除非要编写扩展模块。由于所有内容都必须进行引用计数,所以这会对速度产生一些影响,但它极大地提高了编程的安全性和方便性。以下是引用计数的益处: 实现简单。 易于使用。 由于引用是数据结构的一部分,所以它有一个好的缓存位置。 不过,它也有其不足之处: 要求您永远不要忘记调用引用计数函数。 无法释放作为循环数据结构的一部分的结构。 减缓几乎每一个指针的分配。 尽管所使用的对象采用了引用计数,但是当使用异常处理(比如 try 或 setjmp()/ longjmp())时,您必须采取其他方法。 需要额外的内存来处理引用。 引用计数占用了结构中的第一个位置,在大部分机器中最快可以访问到的就是这个位置。 在多线程环境中更慢也更难以使用。 C++ 可以通过使用 智能指针(smart pointers)来容忍程序员所犯的一些错误,智能指针可以为您处理引用计数等指针处理细节。不过,如果不得不使用任何先前的不能处理智能指针的代码(比如对 C 库的联接),实际上,使用它们的后果通实比不使用它们更为困难和复杂。因此,它通常只是有益于纯 C++ 项目。如果您想使用智能指针,那么您实在应该去阅读 Alexandrescu 撰写的 Modern C++ Design 一书中的“Smart Pointers”那一章。 内存池 内存池是另一种半自动内存管理方法。内存池帮助某些程序进行自动内存管理,这些程序会经历一些特定的阶段,而且每个阶段中都有分配给进程的特定阶段的内存。例如,很多网络服务器进程都会分配很多针对每个连接的内存 —— 内存的最大生存期限为当前连接的存在期。Apache 使用了池式内存(pooled memory),将其连接拆分为各个阶段,每个阶段都有自己的内存池。在结束每个阶段时,会一次释放所有内存。 在池式内存管理中,每次内存分配都会指定内存池,从中分配内存。每个内存池都有不同的生存期限。在 Apache 中,有一个持续时间为服务器存在期的内存池,还有一个持续时间为连接的存在期的内存池,以及一个持续时间为请求的存在期的池,另外还有其他一些内存池。因此,如果我的一系列函数不会生成比连接持续时间更长的数据,那么我就可以完全从连接池中分配内存,并知道在连接结束时,这些内存会被自动释放。另外,有一些实现允许注册 清除函数(cleanup functions),在清除内存池之前,恰好可以调用它,来完成在内存被清理前需要完成的其他所有任务(类似于面向对象中的析构函数)。 要在自己的程序中使用池,您既可以使用 GNU libc 的 obstack 实现,也可以使用 Apache 的 Apache Portable Runtime。GNU obstack 的好处在于,基于 GNU 的 Linux 发行版本中默认会包括它们。Apache Portable Runtime 的好处在于它有很多其他工具,可以处理编写多平台服务器软件所有方面的事情。要深入了解 GNU obstack 和 Apache 的池式内存实现,请参阅 参考资料部分中指向这些实现的文档的链接。 下面的假想代码列表展示了如何使用 obstack: 清单 11. obstack 的示例代码 #include #include /* Example code listing for using obstacks */ /* Used for obstack macros (xmalloc is a malloc function that exits if memory is exhausted */ #define obstack_chunk_alloc xmalloc #define obstack_chunk_free free /* Pools */ /* Only permanent allocations should go in this pool */ struct obstack *global_pool; /* This pool is for per-connection data */ struct obstack *connection_pool; /* This pool is for per-request data */ struct obstack *request_pool; void allocation_failed() { exit(1); } int main() { /* Initialize Pools */ global_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(global_pool); connection_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(connection_pool); request_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(request_pool); /* Set the error handling function */ obstack_alloc_failed_handler = &allocation_failed; /* Server main loop */ while(1) { wait_for_connection(); /* We are in a connection */ while(more_requests_available()) { /* Handle request */ handle_request(); /* Free all of the memory allocated * in the request pool */ obstack_free(request_pool, NULL); } /* We're finished with the connection, time * to free that pool */ obstack_free(connection_pool, NULL); } } int handle_request() { /* Be sure that all object allocations are allocated * from the request pool */ int bytes_i_need = 400; void *data1 = obstack_alloc(request_pool, bytes_i_need); /* Do stuff to process the request */ /* return */ return 0; } 基本上,在操作的每一个主要阶段结束之后,这个阶段的 obstack 会被释放。不过,要注意的是,如果一个过程需要分配持续时间比当前阶段更长的内存,那么它也可以使用更长期限的 obstack,比如连接或者全局内存。传递给 obstack_free() 的 NULL 指出它应该释放 obstack 的全部内容。可以用其他的值,但是它们通常不怎么实用。 使用池式内存分配的益处如下所示: 应用程序可以简单地管理内存。 内存分配和回收更快,因为每次都是在一个池中完成的。分配可以在 O(1) 时间内完成,释放内存池所需时间也差不多(实际上是 O(n) 时间,不过在大部分情况下会除以一个大的因数,使其变成 O(1))。 可以预先分配错误处理池(Error-handling pools),以便程序在常规内存被耗尽时仍可以恢复。 有非常易于使用的标准实现。 池式内存的缺点是: 内存池只适用于操作可以分阶段的程序。 内存池通常不能与第三方库很好地合作。 如果程序的结构发生变化,则不得不修改内存池,这可能会导致内存管理系统的重新设计。 您必须记住需要从哪个池进行分配。另外,如果在这里出错,就很难捕获该内存池。 回页首 垃圾收集 垃圾收集(Garbage collection)是全自动地检测并移除不再使用的数据对象。垃圾收集器通常会在当可用内存减少到少于一个具体的阈值时运行。通常,它们以程序所知的可用的一组“基本”数据 —— 栈数据、全局变量、寄存器 —— 作为出发点。然后它们尝试去追踪通过这些数据连接到每一块数据。收集器找到的都是有用的数据;它没有找到的就是垃圾,可以被销毁并重新使用这些无用的数据。为了有效地管理内存,很多类型的垃圾收集器都需要知道数据结构内部指针的规划,所以,为了正确运行垃圾收集器,它们必须是语言本身的一部分。 收集器的类型 复制(copying): 这些收集器将内存存储器分为两部分,只允许数据驻留在其中一部分上。它们定时地从“基本”的元素开始将数据从一部分复制到另一部分。内存新近被占用的部分现在成为活动的,另一部分上的所有内容都认为是垃圾。另外,当进行这项复制操作时,所有指针都必须被更新为指向每个内存条目的新位置。因此,为使用这种垃圾收集方法,垃圾收集器必须与编程语言集成在一起。 标记并清理(Mark and sweep):每一块数据都被加上一个标签。不定期的,所有标签都被设置为 0,收集器从“基本”的元素开始遍历数据。当它遇到内存时,就将标签标记为 1。最后没有被标记为 1 的所有内容都认为是垃圾,以后分配内存时会重新使用它们。 增量的(Incremental):增量垃圾收集器不需要遍历全部数据对象。因为在收集期间的突然等待,也因为与访问所有当前数据相关的缓存问题(所有内容都不得不被页入(page-in)),遍历所有内存会引发问题。增量收集器避免了这些问题。 保守的(Conservative):保守的垃圾收集器在管理内存时不需要知道与数据结构相关的任何信息。它们只查看所有数据类型,并假定它们 可以全部都是指针。所以,如果一个字节序列可以是一个指向一块被分配的内存的指针,那么收集器就将其标记为正在被引用。有时没有被引用的内存会被收集,这样会引发问题,例如,如果一个整数域中包含一个值,该值是已分配内存的地址。不过,这种情况极少发生,而且它只会浪费少量内存。保守的收集器的优势是,它们可以与任何编程语言相集成。 Hans Boehm 的保守垃圾收集器是可用的最流行的垃圾收集器之一,因为它是免费的,而且既是保守的又是增量的,可以使用 --enable-redirect-malloc 选项来构建它,并且可以将它用作系统分配程序的简易替代者(drop-in replacement)(用 malloc/ free 代替它自己的 API)。实际上,如果这样做,您就可以使用与我们在示例分配程序中所使用的相同的 LD_PRELOAD 技巧,在系统上的几乎任何程序中启用垃圾收集。如果您怀疑某个程序正在泄漏内存,那么您可以使用这个垃圾收集器来控制进程。在早期,当 Mozilla 严重地泄漏内存时,很多人在其中使用了这项技术。这种垃圾收集器既可以在 Windows® 下运行,也可以在 UNIX 下运行。 垃圾收集的一些优点: 您永远不必担心内存的双重释放或者对象的生命周期。 使用某些收集器,您可以使用与常规分配相同的 API。 其缺点包括: 使用大部分收集器时,您都无法干涉何时释放内存。 在多数情况下,垃圾收集比其他形式的内存管理更慢。 垃圾收集错误引发的缺陷难于调试。 如果您忘记将不再使用的指针设置为 null,那么仍然会有内存泄漏。 回页首 结束语 一切都需要折衷:性能、易用、易于实现、支持线程的能力等,这里只列出了其中的一些。为了满足项目的要求,有很多内存管理模式可以供您使用。每种模式都有大量的实现,各有其优缺点。对很多项目来说,使用编程环境默认的技术就足够了,不过,当您的项目有特殊的需要时,了解可用的选择将会有帮助。下表对比了本文中涉及的内存管理策略。 表 1. 内存分配策略的对比 策略 分配速度 回收速度 局部缓存 易用性 通用性 实时可用 SMP 线程友好 定制分配程序 取决于实现 取决于实现 取决于实现 很难 无 取决于实现 取决于实现 简单分配程序 内存使用少时较快 很快 差 容易 高 否 否 GNU malloc 中 快 中 容易 高 否 中 Hoard 中 中 中 容易 高 否 是 引用计数 N/A N/A 非常好 中 中 是(取决于 malloc 实现) 取决于实现 池 中 非常快 极好 中 中 是(取决于 malloc 实现) 取决于实现 垃圾收集 中(进行收集时慢) 中 差 中 中 否 几乎不 增量垃圾收集 中 中 中 中 中 否 几乎不 增量保守垃圾收集 中 中 中 容易 高 否 几乎不 参考资料 您可以参阅本文在 developerWorks 全球站点上的 英文原文。 Web 上的文档 GNU C Library 手册的 obstacks 部分 提供了 obstacks 编程接口。 Apache Portable Runtime 文档 描述了它们的池式分配程序的接口。 基本的分配程序 Doug Lea 的 Malloc 是最流行的内存分配程序之一。 BSD Malloc 用于大部分基于 BSD 的系统中。 ptmalloc 起源于 Doug Lea 的 malloc,用于 GLIBC 之中。 Hoard 是一个为多线程应用程序优化的 malloc 实现。 GNU Memory-Mapped Malloc(GDB 的组成部分) 是一个基于 mmap() 的 malloc 实现。 池式分配程序 GNU Obstacks(GNU Libc 的组成部分)是安装最多的池式分配程序,因为在每一个基于 glibc 的系统中都有它。 Apache 的池式分配程序(Apache Portable Runtime 中) 是应用最为广泛的池式分配程序。 Squid 有其自己的池式分配程序。 NetBSD 也有其自己的池式分配程序。 talloc 是一个池式分配程序,是 Samba 的组成部分。 智能指针和定制分配程序 Loki C++ Library 有很多为 C++ 实现的通用模式,包括智能指针和一个定制的小对象分配程序。 垃圾收集器 Hahns Boehm Conservative Garbage Collector 是最流行的开源垃圾收集器,它可以用于常规的 C/C++ 程序。 关于现代操作系统中的虚拟内存的文章 Marshall Kirk McKusick 和 Michael J. Karels 合著的 A New Virtual Memory Implementation for Berkeley UNIX 讨论了 BSD 的 VM 系统。 Mel Gorman's Linux VM Documentation 讨论了 Linux VM 系统。 关于 malloc 的文章 Poul-Henning Kamp 撰写的 Malloc in Modern Virtual Memory Environments 讨论的是 malloc 以及它如何与 BSD 虚拟内存交互。 Berger、McKinley、Blumofe 和 Wilson 合著的 Hoard -- a Scalable Memory Allocator for Multithreaded Environments 讨论了 Hoard 分配程序的实现。 Marshall Kirk McKusick 和 Michael J. Karels 合著的 Design of a General Purpose Memory Allocator for the 4.3BSD UNIX Kernel 讨论了内核级的分配程序。 Doug Lea 撰写的 A Memory Allocator 给出了一个关于设计和实现分配程序的概述,其中包括设计选择与折衷。 Emery D. Berger 撰写的 Memory Management for High-Performance Applications 讨论的是定制内存管理以及它如何影响高性能应用程序。 关于定制分配程序的文章 Doug Lea 撰写的 Some Storage Management Techniques for Container Classes 描述的是为 C++ 类编写定制分配程序。 Berger、Zorn 和 McKinley 合著的 Composing High-Performance Memory Allocators 讨论了如何编写定制分配程序来加快具体工作的速度。 Berger、Zorn 和 McKinley 合著的 Reconsidering Custom Memory Allocation 再次提及了定制分配的主题,看是否真正值得为其费心。 关于垃圾收集的文章 Paul R. Wilson 撰写的 Uniprocessor Garbage Collection Techniques 给出了垃圾收集的一个基本概述。 Benjamin Zorn 撰写的 The Measured Cost of Garbage Collection 给出了关于垃圾收集和性能的硬数据(hard data)。 Hans-Juergen Boehm 撰写的 Memory Allocation Myths and Half-Truths 给出了关于垃圾收集的神话(myths)。 Hans-Juergen Boehm 撰写的 Space Efficient Conservative Garbage Collection 是一篇描述他的用于 C/C++ 的垃圾收集器的文章。 Web 上的通用参考资料 内存管理参考 中有很多关于内存管理参考资料和技术文章的链接。 关于内存管理和内存层级的 OOPS Group Papers 是非常好的一组关于此主题的技术文章。 C++ 中的内存管理讨论的是为 C++ 编写定制的分配程序。 Programming Alternatives: Memory Management 讨论了程序员进行内存管理时的一些选择。 垃圾收集 FAQ 讨论了关于垃圾收集您需要了解的所有内容。 Richard Jones 的 Garbage Collection Bibliography 有指向任何您想要的关于垃圾收集的文章的链接。 书籍 Michael Daconta 撰写的 C++ Pointers and Dynamic Memory Management 介绍了关于内存管理的很多技术。 Frantisek Franek 撰写的 Memory as a Programming Concept in C and C++ 讨论了有效使用内存的技术与工具,并给出了在计算机编程中应当引起注意的内存相关错误的角色。 Richard Jones 和 Rafael Lins 合著的 Garbage Collection: Algorithms for Automatic Dynamic Memory Management 描述了当前使用的最常见的垃圾收集算法。 在 Donald Knuth 撰写的 The Art of Computer Programming 第 1 卷 Fundamental Algorithms 的第 2.5 节“Dynamic Storage Allocation”中,描述了实现基本的分配程序的一些技术。 在 Donald Knuth 撰写的 The Art of Computer Programming 第 1 卷 Fundamental Algorithms 的第 2.3.5 节“Lists and Garbage Collection”中,讨论了用于列表的垃圾收集算法。 Andrei Alexandrescu 撰写的 Modern C++ Design 第 4 章“Small Object Allocation”描述了一个比 C++ 标准分配程序效率高得多的一个高速小对象分配程序。 Andrei Alexandrescu 撰写的 Modern C++ Design 第 7 章“Smart Pointers”描述了在 C++ 中智能指针的实现。 Jonathan 撰写的 Programming from the Ground Up 第 8 章“Intermediate Memory Topics”中有本文使用的简单分配程序的一个汇编语言版本。 来自 developerWorks 自我管理数据缓冲区内存 (developerWorks,2004 年 1 月)略述了一个用于管理内存的自管理的抽象数据缓存器的伪 C (pseudo-C)实现。 A framework for the user defined malloc replacement feature (developerWorks,2002 年 2 月)展示了如何利用 AIX 中的一个工具,使用自己设计的内存子系统取代原有的内存子系统。 掌握 Linux 调试技术 (developerWorks,2002 年 8 月)描述了可以使用调试方法的 4 种不同情形:段错误、内存溢出、内存泄漏和挂起。 在 处理 Java 程序中的内存漏洞 (developerWorks,2001 年 2 月)中,了解导致 Java 内存泄漏的原因,以及何时需要考虑它们。 在 developerWorks Linux 专区中,可以找到更多为 Linux 开发人员准备的参考资料。 从 developerWorks 的 Speed-start your Linux app 专区中,可以下载运行于 Linux 之上的 IBM 中间件产品的免费测试版本,其中包括 WebSphere® Studio Application Developer、WebSphere Application Server、DB2® Universal Database、Tivoli® Access Manager 和 Tivoli Directory Server,查找 how-to 文章和技术支持。 通过参与 developerWorks blogs 加入到 developerWorks 社区。 可以在 Developer Bookstore Linux 专栏中定购 打折出售的 Linux 书籍。 关于作者 Jonathan Bartlett 是 Programming from the Ground Up 一书的作者,这本书介绍的是 Linux 汇编语言编程。Jonathan Bartlett 是 New Media Worx 的总开发师,负责为客户开发 Web、视频、kiosk 和桌面应用程序。您可以通过 johnnyb@eskimo.com 与 Jonathan 联系。

  • 全部
  • 安全技术
  • 存储
  • 操作系统
  • 服务器应用
  • 行业
  • 课程资源
  • 开发技术
  • 考试认证
  • 数据库
  • 网络技术
  • 信息化
  • 移动开发
  • 云计算
  • 大数据
  • 跨平台
  • 音视频
  • 游戏开发
  • 人工智能
  • 区块链
  • Actionscript
  • Delphi
  • Javascript
  • Python
  • Web开发
  • 数据库诊断案例
  • 性能优化实践一线OracleDBA工作
  • 网络常见问题
  • 故障1000例
  • 网络常见问题
  • 故障1000例中
  • 网络常见问题
  • 故障1000例下
  • 华为编程开发规范
  • 案例
  • 音视频开发进阶指南:基于Android与iOS平台的实践
  • 从0到1开启未来
  • 思考的快与慢
  • Java从小白到大牛精简版高清完整PDF版
  • 思考,快与慢(超清晰pdf)
  • 编程匠艺part1rar
  • ZFiverVersion0912201827
  • SQLServer2000一键10秒极速安装part1
  • SQLServer2000一键10秒极速安装part2
  • SQLServer2000一键10秒极速安装part3
  • 思考快与慢
  • 思考,快与慢中文版pdf
  • ZFiverQQ五子棋全自动工具
  • SQLServer2000一键10秒极速安装part4
  • SQLServer2000一键10秒极速安装part5
  • 扫不完的雷java小游戏
  • 深入理解Android:卷I详细书签版
  • 内存管理内存管理内存管理
  • TheCProgrammingLanguage
  • 蚂蚁课堂每特学院第二期视频和资料
  • c多线程抓取网页内容
  • 操作系统内存管理
  • PPT下载2019第二届以太坊技术及应用大会
  • PPT下载2018中国大数据技术大会(BDTC)
  • jdk1764位
  • Navicat
  • zhangyueDirectX0xc000007b
  • PLSQLDeveloper解压版64位可用
  • javaAPI17中文版chm
  • zhangyueDirectX0xc000007b修复
  • apktool反编译资源apk
  • Navicat破解注册机
  • googlechrome浏览器
  • CSDN下载频道资源及相关规则调整公告V1110
  • 下载频道用户反馈专区
  • 下载频道积分规则调整V171018