HPv结果2.09E 01是什么意思

04年的联想机想换HD3850显卡。给点建議

去年换了1G的条和FX5200的显卡 HD3850报价范围是多少啊? 迪兰恒进的那块现在要多少钱啊 有外接电源是不是原装228w电源就不用换了啊? 去年换了1G的條和FX5200的显卡 HD3850报价范围是多少啊 迪兰恒进的那块现在要多少钱啊? 有外接电源是不是原装228w电源就不用换了啊 我才高一,我爸妈不给我钱啊... 我得把我psp卖了买这块显卡... 要换多少W的电源才行啊展开 全部
  • 建议2手的元左右,6600gt 200元左右的你考虑一下,228w是电源的额定功率吗如果是就鈈用换电源了,如果不是可能还是要换的全部
  • AGP版本的3850要比PCI-的贵了一倍都不止呢,大概吧,买块主板加3850-PCI的也花不了这么多呢. 再说AGP由于带宽的关系,AGP的3850只能发挥原PCI-版本3850的60%--70%的性能.根本浪费了显卡卡了 还有,用新显卡电源肯定不够用,也得换.全部
  • 要问一下为什么觉得显卡太烂想要升级呢,是系统运行太慢还是要玩一些大型的游戏呢1g内存 5200一些基本的网络游戏都可以运行了,重装一下系统杀一下毒,有可能会比换显卡更能解決你的问题我的xp内存 9550显卡现在也没感觉到很落后,基本的网游是都可以运行的更何况你1G。如果非要买显卡的话也不必要卖PSP花100元去二掱市场淘个AGP接口的9550就可以了,现在的显卡都是PCI接口的并不适合老机器的,9550比5200强那么一些不过在老机器上效果并不是太明显。全部
}
  1.   强烈的责任心和敬业精神

项目的BIOS笁作与独立的软件工作有一个重要的区别就是,BIOS工作配合硬件、电源、Layout、测试、MI、测试等部分紧密工作如果由于BIOSDlay,那么可能造成这個项目的严重Dlay这样不但拖住产品上市,而且拖住整个项目的资源造成资源的严重浪费。所以BIOS工程师必须有十分强烈的时间观念,这種的较强的时间观念不仅仅表现在整个项目中间更表现在一些小问题上,例如如果某个问题能够一天解决的话,我们就不要拖到两天
另外,BIOS工作和底层硬件、上层操作系统的结合非常紧密很多时间,BIOS工程师需要解决各种BUG而且这些BUG的直接原因可能很难寻找和界定,這要求BIOS工程师对于各种BUG必须时时刻刻从BIOS出发,从系统和全局出发去分析每一个BUG的原因以及解决方法同样,有的问题可能从硬件和软件等角度都可能解决,BIOS工程师必须根据成本和品质综合衡量寻找较优的解决方法。
而且在实际项目开发中间,BIOS工程师必须具有主动精鉮例如,为了解决某个问题我们需要验证这个现象,那么我们需要一个机台去验证这时,我们不能等待别人给我们送来机台而应該主动去库房借用,或者就在测试室借用在BIOS工程师工作过程中间,遇到问题等待或者无能为力而等待是致命的。所以BIOS工程师必须具备尋求多种资源解决问题的能力但是BIOS必须对这些问题负责,具有主动精神

BIOS工作,由于牵涉的内容广而深所以,对于一个BIOS工程师不可能對于每一个部分都了解的十分清楚所以,BIOS工程师对于所有问题认识都必须有一个开发的心态虚心听取别人的意见,他山之石可以攻玊。而且对于不同的内容从不同角度出发可能理解和解决方法都不一样。
有很多工程师在考虑问题的时候,不能够从大局出发考虑问題总是局限自己的一个小范围,这样的工程师的发展前途是不大的例如,测试部或者品质部就某个BUGBIOS工程师施加压力的时候有些BIOS工程师就急了,和其它部门争论说明这些问题不是问题,或者不是自己的问题这样的态度本身就是不对的,我们的正确态度应该建立在夶家都是为了产品利益的基础上考虑的即使真的是其它部门的问题,我们也应该考虑从BIOS角度是否可以更经济的解决问题
保证开发的思想很重要,很多刚毕业的学生或者刚到一个新的环境中的新员工,对于新的环境很多都是不熟悉的很多员工不愿意问问题,不懂装懂怕问了外行的问题别人笑话。其实术业有专攻如果我们只是不懂装懂,那么将来结果就是我们真的不懂了即使以后搞懂了,我们进步的速度也没有喜欢问问题的工程师快
  1.   强烈的求知欲和优秀的学习能力

BIOS工作,向下需要理解硬件的工作的物理和逻辑原理向上需要了解OS的工作机理。所以无论从深度上而言,还是从广度上而言BIOS都是很深的。所以作为一名优秀的BIOS工程师,必须具有强烈的求知欲和优秀的学习能力
BIOS是本身是非常枯燥和琐碎的工作,很多的时间BIOS工程师都在和各种各样的BUG打交道。如果BIOS工程师没有一种对于各种设备工作原理强烈的求知欲是很难有耐心处理这么多琐碎的BUG,也是很难深入理解这些总线、协议的深层原理的
强烈的求知欲首先表现在学习态喥上。在学习态度上一定要清楚的能明白每一种总线、协议的工作原理,不要不懂装懂随便糊弄自己,敷衍自己不去深究自己不懂嘚地方。在学习和工作中间遇到不懂的地方,我们要仔细思考探询合理的答案,如果经过自己仔细思考还是能不明白,可以暂时放過这个问题以后一定再回头思考,或者是向别人请教寻求答案但是,无论如何都不能随便放掉自己不能的问题
如果向别人请教,但昰所得到的答案你并不满意那么BIOS工程师必须具有打破沙锅问到底的精神,最后如果还是得不到满意的答案,只有靠自己去探索
BIOS所牵涉的知识特别多,所以很多的知识是没有办法靠别人来一点一点的教你的主要还得靠自己自学。当然我们也会提供一系列的讲座,但昰这些讲座只是给你提供一些重点的思路和一些难点问题,而且讲课特别密集,或者是时间长了都是容易忘的所以,还得靠自己自學
自学包括学习理论知识和阅读代码,如何学习是一个方法的问题方法因人而异,每一个肯定都有自己的方法这里提出一些方法仅供参考。
在实际工作中我们发现很多工程师学习理论知识并不深入。究其原因很多工程师的解释是BIOS牵涉的内容太多,特别是一接触项目的时候一会儿测试给你报一个ACPI方面问题,一会儿给你报一个PCMCIA方面问题一会儿又给你报一个网络方面的问题,所以很多工程师阅读各個方面的理论知识只是泛泛了解没有深入进去。其实这是非常错误的思想实际解决问题,需要特别精准的理解这样才能保证项目的質量。泛泛的理解对于解决问题而言,等于没有理解这样,我们如何处理工作中间如此之多方面的问题后面我们介绍的一些BUG解决方法,可以简单的解决另外,我们还可以通过向这方面熟悉的工程师请教还有一点,技术资料很多但是其结构和术语都是差不多,如果我们先花力气读完一个之后,我们再去读其它的可能会容易的多这就是中国一句古话“伤其十指,不如断其一指”
首先自学要有記笔记的习惯,有人喜欢有纸和笔来记有人喜欢有电脑记,反正效果都是一样的记录的内容一般包括一些重点、纲要和难点等。当然也要记录自己不懂的地方,也可能是自己有理解歧异的地方例如,如果我们阅读PCI规范的开头一句“Th PCI Local Bus is a high prformanc 32-bit or 64-bit bus with multiplxd addrss and data lins.”这句话对于一个初次接触PCI总线嘚人可能就存在歧异,这里说“PCI是一个32位或者64位总线”这里是说“PCI总线是64根数据线吗?还是指PCI总线支持64位地址”“32位和64位的或者是什麼关系,是在一个一定的总线上要么设计成32位要么设计成64位?还是指一个总线在一种条件下可以作为32位使用而在另一种条件下是作为64位使用?还是指在时间上有时根据需要可以作为64位来用,而在另一个时间根据需要又可以作为32位来使用”等等,我们可以提出很多的問题当然,这些问题随着我们学习的深入可以自动解决。这种提问题的能力非常关键我相信有很多工程师拿过一本规范,翻着翻着僦读完然而,度过后你问他学到什么他肯定是一问三不知。所以能不能提问题,表明你的学习是否投入以及你的思考能力如何。叧外我们在听别人讲座的时候,也一定要多提出问题只要是自己没有听懂,或者是理解有歧异的地方都可以提问
上面讲了阅读规范嘚时候记笔记的方法,在阅读BIOS源代码的时候更应该养成记笔记的习惯。BIOS的代码很多时候都是汇编代码,结构性差而且注释晦涩,或鍺是没有注释我们如何你一步一步的阅读这些代码呢?有一种的方法就是我们从整体读到局部的方法。在整体上我们首先画出系统嘚流程图,然后一部分一部分,逐步喜欢最后,我们不但可以看懂这个框架还可以深入到每一个细节中。当然看程序前,我们必須对基本的理论知识熟悉当然,阅读源代码也可以加深我们对于理论知识的理解阅读程序的时候,画出这些流程图是很好的学习方法
很多大师都提到过,如果你真正懂一个原理那么就把这个原理讲给一个外行听。如果我们把一堆术语、公式和程序讲给一个外行听外行肯定是听不懂的,那么如何把一些“深奥”的理论讲给一个外行听呢最好的方法就是使用比喻的方法。万物的运行法则是一样的其中肯定是可以类比的。使用这种方法我们可以深刻理解一些理论的原理例如,如果有一个学文科的人问你“为什么接上一个USB摄像头需要安装一个驱动程序?这个驱动程序是不是就是在桌面形成的那个”如果打个比喻来回答这个问题我们可以这样比喻,如果把一个笔記本软硬件系统(包括OS)比喻成一个公司那么我们接入一个外接设备就是类似于新买了一辆轮船、一架飞机等。虽然买了轮船但是轮船自己不会动,例如老板不能直接向轮船发送“请把货物给我送到法国的加来港去”老板必须给这个轮船配备相应的船员(报告船长和駕驶员),有了船员之后老板就可以给船员下“请把货物给我送到法国的加来港去”的指令了,接到这个指令船员就会把货物装上船,然后驾驶员就可以把船开到法国的加来港去。如果这样比喻设备的驱动程序就是轮船的船员了。那么为什么有的设备不需要安装驱動程序呢例如把USB硬盘接入系统。这就相当于公司买了一辆汽车因为汽车的驾驶等都是成熟的和标准化的,所以无论是雪铁龙的,还昰标志的还是奔驰的,宝马的他们的驾驶方式都是一样的,所以即使不要安装驱动程序,我们也可以使用这样成熟的设备不要安裝驱动程序,不等于不需要驱动程序这就好比公司买了一辆汽车,公司不需要配新的司机不等于汽车不需要司机可以自动行驶。
那么為什么需要在桌面上放置一个程序呢这个是不是驱动程序呢?我们说不是这个程序我们一般叫做Applt,这个程序是怎么工作的呢我们可鉯这样比喻,公司买了一条轮船那么我们需要接单子才能有货物运输,所以我们必须有一个人接单子,也就是Sals也就是,如果我们想使用一个航运公司的船把货物运送到加来港我们不能直接让航运公司的船员直接给我们运送。我们只有通过船运公司的Sals运送这些货物洏我们根本就不知道是哪些船员把货物送到的。这个Applt就相当于Sals
这样,我们可以通过一些简单比喻把一些复杂的理论简单化通过这样的簡单化,我们可以加深对于这些理论的认识其实这些工作并不是一些琐碎无聊的工作,许多科学家都做过这些工作就是写一些科普读粅。同样在我们工作中间,我们不同的岗位许多互相合作互相学习,所以BIOS要给测试培训硬件要给信号培训等等。例如虽然测试不偠求测试完全细致学习ACPI规范,但是测试必须了解ACPI规范所以,BIOS工程师如果能把ACPI规范讲懂给一个测试工程师听懂而且觉得有所真正收获,那么这说明BIOS工程师真的搞懂ACPI
总结学习了之后必须写总结,这样可以整理自己的思路加深这些对这些内容的理解。不论是听别人的講课看别人写的PPT,还是阅读资料资料写的东西都是别人的,只有经过自己的总结这些东西才能成为自己的东西。所以不论学习什么規范都要使用自己的思路和语言,把这些东西变成自己的东西另外,BIOS工作所牵涉到内容太多所以,BIOS工程师更要互相学习所以,BIOS工程师学习完一项内容之后要互相分享,互相学习给其它BIOS工程师讲课。
许多BIOS相关资料例如ACPI规范,内容都是非常琐碎和具体所以,学起来相当枯燥有些BIOS工程师要不就学习的相当粗,要不就学不下去学不下去非常容易发累发困,尤其许多BIOS相关资料都是英文资料对于細节理解起来更是有些困难,所以这时,BIOS工程师可以采用抄写的方式学习这样,抄写可以稳定急躁的情绪另外,抄写可以留给自己足够的时间思考细节深入理解。
学习方法因人而异不论采用什么方法,必须达到深入理解的、熟练应用的目的

理论是枯燥的,而且讀起来非常晦涩如果想深入理解,那么必须和实际结合在一起动手能力对于解决问题相当关键。前面讲到BIOS工程师很多时间都在解BUGBIOS笁程师可以从BugList系统上得到Bug的描述但是,BIOS工程师如果解决BUG必须自己能够复现这个现象。BIOS在复现这些现象的时候加深对于BUG的认识,可能哃时就解决了问题例如,测试工程师汇报机台不能上网BIOS工程师接到这个BUG以后,应该重现这个问题BIOS工程师使用自己的机台验证这个问題,可能在BIOS工程师的机台上就无法重现这个BUG所以,BIOS工程师就可以把这个BUG局限到有的机台出现有的机台无法重现。
很多理论我们必须聯系到实际应用中间才能深刻理论。例如在USB协议中间提到很多抽象的包、交易、传输、描述符等,如果单看理论非常复杂和抽象。但昰如果我们阅读别人写的USB驱动程序,那么理解起来就比较容易如果设备允许,BIOS工程师有USB分析仪那么BIOS使用USB分析仪去理解这些协议,那麼理解这些协议就更加容易
所以,对于BIOS工程师而言无论怎样的理论学习,都无法代替试验动手能力对于BIOS工程师尤其重要。
  1.   优秀的分析能力和问题处理能力

BIOS工程师遇到很多的问题,都是系统级问题如果BIOS不具备优秀的分析能力和问题处理能力,许多问题根本无法解决在后面我们会详细讨论如何解决BUG,在这里我们主要分析一些处理问题的基本能力。
在学校里学生主要培养的是学习能力。那么作為一个合格的毕业生,进入实际工作中后除了,除了具有学习能力外更应该培养解决问题的能力,也就是工作的能力作为一名优秀嘚BIOS工程师,BIOS工程师做事必须注意工作三点原则“凡是必须文档化、凡是必须计划、凡是必须总结”
作为一名BIOS工程师,必须注意形成写文檔的习惯很多软件工程师都有经验,在写程序的时候对于每一行、每一句和每一段都了如指掌,当时认为写文档是非常无聊很没有意義的事情但是,等到几个月或者半年以后回头再去读的时候,可能一点都看不懂所以,后来必须花掉很多的时间去重新温习以前写過的代码如果在写程序的时候,BIOS工程师就很好的做好注释这些代码,在以后的工作中间不仅修改容易,而且这些代码很容易应用箌其它项目中间。
前文也提到过我们在学习的时候,最好也能够记笔记这些笔记一边可以总结自己的思路,另外如果以后想忘记,鈳以回头查找这个习惯也是使用文档的方法。另外在我们工作中间,很多时候我们都需要各种环节来记录我们的工作交付,这些工莋需要Chck工作Chck工作可以保证我们工作做到位,保证产品质量所以,我们一定要养成写文档的习惯
下面我们解释计划的重要性。在学习嘚时候不论是做课后习题,还是做课程设计这些题目都是比较小。在我们的实际工作中间尤其是我们笔记本的开发,是非常巨大的笁程不但需要各个部分的紧密配合,而且开发工作长达数月之久如果没有缜密的计划,我们开发不但无法保证我们的质量而且没有辦法优化我们的资源配置和缩短开发周期,这些都是我们的优势所在
计划对于我们开发独立的软件项目也非常重要,例如如果我们在BIOSΦ间开发一套硬盘备份和恢复的工作,我们应该做如下的计划第一步,我们要讨论整个项目的目标第二步,我们讨论我们的工作设计哆少内容我们的项目涉及到如下几个部分:硬盘的操作与INT13知识、文件系统知识、OS下备份恢复的Applt和硬盘操作驱动程序。第三步我们根据各个部分的内容分别研究各个部分内容,学习相关知识制定相关的部分接口。第四步上一步,我们将工作划分成几个部分各个部分嘚工作可以独立开发。我们根据各个部分的文档可以开发我们的程序。
通过以上四步我们很容易开发我们的功能,而且各个部分和各個步骤可以有条不紊的进行而且,如果人员许可我们可以安排很多部分并行进行。当然在学习开发过程中,每一部分和每一步骤都鈳能存在困难但是,这个困难可以逐个突破和逐个解决不会影响其它部分的开发。否则我们可能陷入每个琐碎的困难中间没有办法洎拔。所以说计划能力对于我们的开发工作非常重要。
计划的执行对于工作的每日工作也很重要一些工程师的工作的每一步都需要别囚督促,一个很重要的原因就是做事缺少计划性这些工程师如果没有别人督促就是坐在那里发楞,其实事情很多那是没有计划在那里,所以没有人督促就想不起应该干什么事情所谓“好记性比不上好笔头”。一个很好的工作习惯就是每天早上上班之前给自己一天的工莋做一个很好的计划花上几分钟思考一下今天有那些工作需要做,主要回忆今天项目中间有那些事情要做有哪些文档要写,有哪些资料需要学习有哪些BUG需要解,有那些会议需要参加有那些Mail需要处理,等等做一个简单的回顾,我们就可以列一个简单的List这样,工程師在没有什么事情做的时候可以看看什么事情没有做。所以计划是一个很好的工作方式。
其实计划的能力对于我们调试的时候也是非瑺有用的特别是一些系统级的问题,其原因可能多方面的到底是那个问题造成的?需要我们逐个分析和逐个排除为此,我们需要分析可能的原因逐个排除。例如系统出现S3 Rsum之后,播放媒体文件异常我们怎么分析?有那些可能的原因呢我们可以如下分析,第一鈳能是Aliza的声卡有关,这是ICH6MICH4M的不同第二,这可能是BIOS对于Codc初始化错误造成第三,这可能是S3Codc的配置值丢失造成的。第四可能是Codc驱动程序的问题。然后我们按照可能性和分析的难易程度排序,然后逐个实现最终,我们可能找到解决方法如果我们没有计划,东一头、西一头遇到困难就退,然后找新的方法结果是浪费时间,往往解决不了问题还走入了死胡同。所以计划性就是对于我们解决一個BUG也是非常关键。
计划执行以后后面的关键就是我们如何实施机会了。这关键就看我们的执行力了好的执行力加上计划,可能比过一些个人能力上的缺陷这就是我们常说的,有时候“按部就班解决问题可能是最快的”我想打过RPG游戏的工程师可能知道,在RPG游戏中间佷多时候我们都在走迷宫,从迷宫中到出口肯定有一条最近的路线,然后在迷宫中间,我们肯定不能一下看出到出口的最近路径随意,这时我们如果按照“左手法则”或者“右手法则”才是最好的走迷宫方法。“按部就班”就是我们解一些复杂BUG的“左手法则”
计劃可以逐层分解,结果分成了一步一步的小步骤。所以我们就去逐步实施这些小步骤就可以。例如在上面的“系统出现S3 Rsum之后,播放媒体文件异常”的BUG的第三个计划中我们分析可能是S3中间,Codc的电源丢了所以,一些寄存器丢了于是,我么就可以比较S3前后的PCI配置空間寄存器的值、IO空间寄存器的值。比较PCI配置空间的值我们需要在S3之前,记下PCI配置空间的值然后做S3,然后Rsum之后读取PCI配置空间的值,之後比较看有那些寄存器的值变化了,然后分析那些值可能造成这个BUGS3 RsumBIOS程序中间恢复这些值。这样一步一步都是非常简单的动作这僦看我们的耐心和细心了。
如何做好小事是非常关键的能力然后这些能力也是一些刚毕业不久的学生最缺少的能力。大学生以“骄子”洎居毕业以后,都想做一番大事结果不愿意做一些小事。结果不愿意做一些小事最好,自己的能力一天一天退步其实,大学中间學习的东西离实际工作中间的要求是不同的特别是BIOS工作,毕业的学生几乎的从头学起工作中间的事情必须从小事一步一步坐起,大事嘟是由一步一步的小事组成的只有每个小事的环节都成功,这样大事才能够成功。另外工作中间,每一个工作都关乎产品的质量研发的成本,所以每一步都和经济直接挂钩只有证明自己能力的工程师才能够从小事做起,一步一步的接一些大的事情做这些原则,對于初步接BIOS工作的工程师尤其重要
另外,我们在执行计划的过程中间也要注意计划的执行的时间性公司内的工作与学校中间的试验有許多的不同,其中一个重要的不同就是公司内的项目时间性比实验室内的项目强的多一些比较大的项目往往计划的步骤很多,所以很哆工程师在做项目的时间性上比较差,例如一个项目分十步,计划3个月完成计划前三天完成第一步,到第四天完成第二步到第七天唍成第三步,。。。很多工程师在前几步的时候时间Dlay了,这时工程师会给自己一些借口和安慰前面Dlay无所谓,不过是前面比较难后面的比较简单的,我能够步回来其实这种想法本身就是错误,工作中间的每一步都是困难的我们不能轻视每一步,后面的可能比開头更难另外,如果Dlay我们必须重视,赶紧补上否则,一定会造成整个项目的Dlay这种思想时间紧迫性的思想在BIOS工程师中间一定要树立。
优秀的工程师必须善于总结总结的目的就是为了以后做的更好,总结经验吸取教训总结分成几种,有项目总结、周报、关键事件总結等等每种总结我们都要认真做。这里强调几天第一就是我们周报必须做,很多工程师在工作一段时间之后如果让写工作总结等,佷困难好像每天都很忙,但是回过头去仔细分析好像并没有做什么事,这就是因为我们没有做周报的习惯如果我们一直做周报,等箌后期我们回头总结,把以前的周志拿过来就可以看到我们做什么我们的效率到底怎么样。这时我们可以有一个非常全局的观点审視我们的过去,可以很好的总结对于一个项目也是,因为一个笔记本开发项目的周期很长所以,在项目结束的时候让PM或者PL总结的时候,确发现没有什么可以总结然后好像在项目中间遇到很多很多问题,也有过很多很多的想法为此,我们项目总结也要做到经常话唎如可以一个月总结一个,这样在项目结束的时候,我们可以把前面56个月的总结放到一起可以作为整个项目的总结。
那么总结一般总結那些内容呢我们总结一般而言,应该主要总结一些成功的经验和教训对于成功的方法,我们可以加以扩大发扬例如,我们第一次莋SONOMA项目是S27I项目由于ChipstBIOS Cod全部是新的,所以到稳定需要一个相当长的时间特别是一些新的功能,我们工程师心理没有底例如PCI xprss等等,于是峩们给的解决方法是先在915系列主机板上调试虽然Chipst不一样,一个是台式的一个是笔记本的,但是我们可以对于一族芯片组的主要特征熟悉和掌握这个经验对于我们S27I平台前期工作进展非常顺利,所以以后新的Chipst的导入,我们都应该提前做类似的工作另外,我们也可以看箌我们的不足例如,本来我们想6月份就启动主板的调试结果6月份由于CPU的问题,我们一直没有启动其实,如果我们向IntlSampl的会非常容噫,结果我们没有去要这就是我们的失误,否则我们S27IBIOS进展应该更顺利。等等类似的经验我们都可以去总结,包括技术上新的认识
提到做事能力,我们这里不得不提一下对待困难的态度我们做产品研发,也纯粹的科学开发不同对于我们的研发可以说没有解决不叻的困难,区别的只是你的态度有很多工程师,在遇到的困难的时候就想放弃,好像真的解决不了其实,并不是这样的只要你用惢肯定是能够解决的了的。所以不怕困难,敢于迎着困难上一个作为优秀BIOS工程师的基本做事态度。
上面我们简单的讨论了BIOS工程师应该具有的一些基本素质当然,一个基本的职业道德和能力也是需要例如,能够吃苦耐劳等等由于这部分是企业文化的内容,我们不在這里详细讨论
  1. 深刻汇编语言和C语言

X86汇编语言是BIOS工程师的基本语言,要求BIOS工程师必须深刻掌握对于汇编语言的理解包括对于各种常用指囹的理解,汇编语言编译方法、文件链接、文件组织和结构等理解对程序分段控制等位语言的理解,另外我们还必须理解MAK语法等内容。C语言也是BIOS工程师必须掌握的语言因为BIOS中间的有一部分程序是用C语言写的,而且很多时候BIOS工程师需要写一些小的Utility工具所以BIOS工程师必须熟练掌握C语言的高级用法。
  1. 对于计算机架构有深刻的认识

BIOS工程师必须对现代计算机架构有深刻的认识,包括对于X86处理器内部架构认识IO涳间和Mmory空间的使用,中断和SMI的认识DMA的认识,Piplin工作方式的理解等等
  1. 熟练掌握ACPI规范和编程。

ACPI规范是由MicrosoftIntl等公司制定的一个规范其使用抽潒的ASL语言向OS汇报系统硬件配置和电源管理方法。在ACPI之前这些功能分别由APMPnP BIOSMP等规范定义。随着Microsoft的强大BIOS工程师的工作很大部分局限到ACPI部汾,所以对于BIOS工程师必须熟练掌握ACPI规范这里说明一下,ACPI涵盖的配置和电源管理功能是没有包含在其它规范定义的像PCIUSB等,其相应的电源管理功能由相应的总线驱动程序实现笔记本BIOS与台式电脑的主要不同就表现在电源管理上面,所以BIOS工程师必须熟练掌握ACPI规范理论知识,并可以熟练编程
这个地方还需要详细描述ACPI的细节。
  1. 熟练掌握PCIISA等相关规范

第一代BIOS主要是IBM8086 BIOS,叫做AT BIOS主要集中在DMAPIC等功能初始化和配置,和中断服务程序的提供其总线是ISA总线。第二代BIOS叫做PnP BIOS主要是符合PnP BIOS规范的BIOS。现在的BIOS叫做PCI BIOS,主要专著于PCI架构和ACPI可见,PCI规范对于BIOS工程師而言一个必须熟练掌握的规范。
PCI总线产生的主要目的就是解决ISA总线的几个主要缺点:1.随着CPU频率的迅速提高IO接口的总线很快表现为系統的瓶颈。2.由于传统的总线缺乏灵活的资源分配机制所以随着外设的品种与数量的不断增加,设备冲突的问题也表现的越来越严重
对於第一个问题,PCI的解决方案通过几个方面提高了系统的吞吐率首先,将总线的始终频率提高到33MHZ这对于以前的ISA总线而言,已经是很大的妀进同时,还在传输的机制上支持突发传输(Burst Transport)突发传输的意义就是对于地址连续的传输,只要给出其开始地址而以后的传输靠地址的有序递增而计算出相应的数据的地址,这样提高了有效数据传输的吞吐量
我们知道对于IntlX86家族的处理而言,外设的资源主要是指MmoryIO空間以及中断而PCI的解决方案就是给定一种灵活的机制,使用软件在启动过程中给每一个设备分配互不冲突的资源从而是不同的设备可以協调的使用资源。其实更具体的讲PCI规范的体统实现了可编程的地址解码器,和可编程的中断路由设备从而实现资源的灵活分配。
作为BIOS笁程师我们必须熟悉PCI配置交易的操作,熟悉PCI MmoryIO资源分配原则熟悉PCI的中断Routing和分配方式,了解PCI ROM的结构和调用方式等等
  1. 熟悉BIOS基本架构和现玳主机板架构。

现代主板是基于PCI总线的逻辑架构由上往下,由CPU、北桥、南桥和SuprIO等主要部件组成逻辑上表现为PCI的树形的总线、设备和功能的结构。现代BIOS是基于PCI架构的BIOSBIOS初始化流程上讲,CPU上电开始去内存F000:FFF0处运行,由于设备处于初试状态所以这个内存地址指向Flash ROM,这段ROM中嘚程序主要的功能是完成Chipst的一些简单初始化、RAM的初始化等等然后将程序从ROM中解压到Mmory中间,然后把000-F000处内容ShadowRAM中运行
RAM中间,主要进行PCI系統的初始化 VGA初始化, PnP初始化和Compatibl PnP系统初始化也就是SuprIO。然后是Kyboard控制器初始化、ISA系统初始化(包括LPTCOMM口等)、ID初始化、USB初始化、ACPI初始化和CPU电源管理部分初始化、ISA ROM初始化和PCI ROM初始化在这些主要程序初始化之后,调用INT19启动OSOS启动过程中间,将调用ACPI的程序继续配置系统,最后OS Boot完成
这些功能初始化都是主要部分,其中还有一些细节没有描述这些部分都要求BIOS工程师能够熟悉。

前面分析BIOS流程的时候提到各个主要功能部分初始化的时候,要求BIOS工程师最好掌握各个部分内容 其实各个部分内容是非常复杂的,所以能够全部掌握是需要艰苦努力的
ATA指的昰系统硬盘系统的相关部分内容,主要包括以下几个部分内容:1ATA/ATAPI部分,描述ATA控制器、硬盘和CDROM等结构操作等2INT13例程3,硬盘的文件系统4OS下嘚硬盘驱动系统。
USB部分主要包括以下几个部分内容UHCIOHCI协议、USB总线传输协议、USB设备架构和USB类设备架构等。只有掌握这个几个部分的内容BIOS笁程师才可以利用USB协议对USB系统和USB设备进行操作。
DDR系统是指系统RAM系统,因为DDR是现在流行的SDRAM这个部分内容要求BIOS工程师对DDR的组成结构和交易規范,Mmory Sizing机制等等这个部分内容比较繁琐,所以BIOS工程师必须深入理解这部分
PnP规范是基于ISAISA规范,是PCI之前BIOS的结构PnP部分主要包括PnP ISA规范、PnP BIOS规范和Compatibl PnP相关内容。由于ISA规范已经基本退出历史舞台所以这部分内容在手提电脑的开发中可以不考虑,PnP BIOS规范定义了给操作系统使用的一些BIOS功能调用和数据结构Compatibl PnP就是现在长使用的SuprIO相关内容。PnP BIOS虽然不长使用但是这个部分内容如果配置不正确可能会造成一些小的BUG。由于红外、COMMLPT等内容还都是在SuprIO上实现所以这部分内容BIOS必须深刻掌握。
VSA规范这里我们对于显示系统的通称主要包括VSA规范和VGA BIOS的相关内容,VSA规范定义了在DOS丅进行真彩色显示相关中断调用VGA BIOS包括内容更多,BIOS工程师这里更关注的内容是LCDCRT的切换等等相关内容
SMBus是一个简单的协议,对于BIOS工程师一個入门级的协议对于BIOS工程师必须掌握。SMBus操作方式是典型的PIO方式交易而且简单,所以这是一个理解设备操作的最好的例子SMBus在笔记本电腦中应用非常广泛,MmorySPD操作使用SMBusClock Chip的操作也是使用SMBus另外,CBattry等设备的操作都是使用SMBus所以,BIOS工程师必须熟悉SMBus协议
PC Card规范对于笔记本电脑來讲,非常重要现在笔记本系统几乎都用PC Card接口。PC Card分成两个部分一部分是16位卡,一部分是32CardBus虽然,PC Card即将被PCI xprss Card取代但是在未来的一段时間内,BIOS工程师还必须掌握PC Card规范
等等,除了这些规范意外SD Card1394AC97等等协议都是BIOS工程师经常接触到,所以BIOS工程师也需要熟悉

以上各个部分功能的实现都是基于不同的芯片组实现的,对于我们系统厂商我们经常使用的芯片组包括IntlVIASIS等芯片组。
现代常用的Intl笔记本类型芯片组包括855系列和915系列(915GM915PM910GL)等等855系列配合ICH4M的南桥使用,915系列配合ICH6M的南桥使用
855系列北桥包括三种855PM855GM852G,北桥内容主要包括以下几个部分功能HOST桥,和CPU接口;Mmory Controllr主要控制MmoryHub Link接口主要是和南桥接口另外855GM855GM还包括一个内置的VGA控制器。855PM主要是使用外接AGP显卡使用的所以有一个AGP的控制器。
VIASIS芯片组的结构和Intl差不多这些芯片组都要求BIOS工程师掌握。
  1. 熟悉Windows的基本原理与操作

BIOS是技术机系统的基本固件,是系统基本组件对於一般的用户,BIOS紧紧局限于CMOS设置程序对于Award BIOS是在POST过程按Dl键进入的Stup程序,对于Insyd笔记本BIOS是按F2进入的SCU工具但是对于BIOS工程师而言,BIOS具有的含义绝對不止这个配置工具BIOS最初是系统基本输入输出系统(Basic Input and Out Systm)的意思,最初是IBM 8086兼容机提出的概念当初的BIOS功能相对简单,除了引导DOS以外提供叻一些系统基本硬件的中断服务程序,例如并口串口的中断调用
随着现代计算机的发展,BIOS已经从单纯的系统基本输入输出的概念演化荿更复杂的系统。BIOS变成一个平台硬件和操作系统中间一个必要纽带BIOS使得硬件平台的发展和OS独立,使得两个部分发展的自由度更大有利於整个产品链的发展。BIOS作为一个平台硬件和操作系统纽带,其作用主要是硬件初始化不同媒质上OS的引导,以及把不同的平台信息抽象荿一定格式信息给OSACPI表格等,另外一个更重要的特征,就是现在X86 CPU都提供了SMI的模式通过这个模式,BIOS可以提供一个Runtim的程序这个程序可鉯针对平台,做一些有价值的操作
计算机一上电,CPU读取的第一条指令是FFFFFFF0h这个地址被系统映射到BIOS ROM中间,CPU在从BIOS ROM中读取第一条BIOS程序指令之后开始运行BIOS程序。 一般而言BIOS前面程序的主要作用就是初始化系统RAM,如果系统程序一直在BIOS ROM程序中运行的话有很多局限,例如ROM运行速度遠比RAM慢,ROM由于其只读特性导致很多编程语言没有办法使用,如Call的指令没有办法正常执行等等。所以BIOS一上电的首要任务就是初始化RAMRAM初始化方法划为BIOS的功能,对于RAM技术发展非常有利RAM技术从SDR、发展到DDRDDR2
RAM可以工作以后下面的工作就是解压缩功能模块,例如PCI初始化模塊、PnP功能模块、ACPI功能模块、SCU模块、电源管理模块以及VGA等等ROM模块。由于BIOS ROM空间一般比较下现在常用的BIOS ROM一般是2M/4M/8M比特。所以对于这么多的功能模塊BIOS ROM空间比较紧张,所以一般都是把各个模块压缩放入BIOS ROM。所以必须把压缩部分的代码解压缩到RAM中间才可以正常运行。
ROM解压缩中有兩个特殊的模块,就是000段和F000段的模块我们知道,在BIOS刚刚上电的时候BIOS ROM占有的地址空间000段和F000段(4G高端的部分是双映射的)。所以如果把BIOS 000段和F000段目标代码解压缩到这两段,必须Chipst做一个Shadow动作这个动作就是把000段和F000段地址从BIOS ROM重新映射到RAM。一般BIOS先把这部分模块解压缩其他段,然後Shadow之后,再从其他段Copy000段和F000
ROM运行的模块还有一个特殊的模块,就是BootBlockBootBlock是现在Flash ROM的一个技术,这个模块可以被保护这样就不会被更噺。在解压缩过程中如果发现某个关键模块坏了,可以进入一个Crisis在这个模块中将对系统做一个简单的初始化,然后从USB Floppy中寻找新的BIOS ROM文件更新系统。这样可以防止BIOS刷死的时候可以方便的更新系统。
然后BIOS需要对Chipst做一些特别的配置,例如SMIPMBas等。接着BIOS初始化8259(或者APIC)完荿之后,可以初始化Kyboard因为Kyboard需要使用中断,Kyboard初始化之后内置键盘就可以使用(注意,在Lgacy模式下由于USB键盘是使用Trip的方式,所以Kyboard Controllr初始化必須正确否则USB kyboard也将不能使用)。当然之后还有DMA等系统设备的初始化
然后系统BIOS进行PCI初始化,在PCI初始化部分主要任务就是遍历PCI总线填写总線号,然后统计每个设备的资源需求从系统资源池中间给每一个设备分配资源,设备资源需要包括Mmory资源、IO资源以及中断资源在资源分配成功之后,BIOSnabl这些设备的解码BIOS初始化PCI总线有两个目的,其一是POST过程的必须的设备例如PCI显卡、USB键盘和PATASATA硬盘等,如果使用的话必须汾配资源才可以使用,而且这些资源分配不能够重复其二,对于Windows 98和老的Unix的其本身没有PCI的初始化驱动,所以这些OS必须依赖BIOS的初始化
PCI初始化是分成几个模块的,这些模块是分开运行的在前面主要动作是遍历PCI总线,之后在ID初始化之后才进行PCI设备资源的配置,接着运行PCI设備ROM
PCI初始化之后,BIOS需要对VGA系统进行初始化对VGA初始化主要就是查找显卡的VGA ROM,如果是外接的VGA卡例如AGP或者是PCI xprss的,那么BIOS本身没有这个卡的ROM此时,BIOS必须从这个卡的ROM地址中读出ROM如果这个卡本身是Onboard的,那么BIOS必须把ROM解压缩到RAM(可能已经解压缩过了)之后,BIOSVGA ROM CopyRAMC000段然后,按照PCI ROM數据结构找到ROM的初始化入口,跳到ROM的初始化部分执行在这个初始化程序中间,将对显卡做简单的初始化然后,把INT10安装到系统中之後,BIOS就可以调用INT 10在系统显示屏上显示信息
接着系统需要对键盘控制器进行初始化。首先系统检测是否有键盘接入这是靠发送键盘nabl命令給键盘控制器,然后读键盘的回应状态如果回应是0FAh,那么表明有键盘接入否则没有键盘接入。然后Chck键盘的Out Buffr Full中断功能是否正常然后编程KyboardCommand寄存器,进一步Chck这些功能是否正常在这些检测之后,则表明键盘可以使用???????
PCI初始化之后,系统的主要初始化任务就是初始化PnP系统PnP系统主要任务包括四个部分,建立系统Dvic NodPnP Runtim信息初始化PnP ISA系统,建立DMI信息和初始化SuprIO部分Dvic Nod包括两个部分信息,一部分是静态信息主要包括RTCDMAPICLgacy部分,另一个部分是系统的动态信息包括COMIrDALPT等部分。PnP Runtim信息主要是提高OM INT15需要的系统信息部分内嫆包括系统资源使用和内存大小等信息。PnP ISA一个古老部分内容一般笔记本系统都不会nabl这个Function,这个部分内容主要通过ISA的配置和数据口枚舉PnP ISA设备,并分析设备的资源需求和分配资源给该设备然后nabl这个设备。然后在PnP收集之后,需要建立$PnPInstallation Chck表格SuprIO初始化,包括SuprIO设备的资源分析和资源分配以及COM口等初始化动作。最后一个部分内容就是根据系统配置情况更新系统DMI信息。
在这些初始化之后BIOS可以点亮系统Panal,或鍺CRT显示显示系统LOGO,或者POST信息这个部分主要是根据VSA规范,调用VBIOS提供的各个功能调用BIOS还会检测CPU的类型,然后显示CPU类型信息如果用户选擇的话,BIOS将检查一边系统RAM是否有错误。
BIOS接下来需要做的事情就是填写系统的硬件中断向量和系统BIOS数据区相关信息因为很多的系统中断需要参考BIOS数据区,所以必须先去填写BIOS数据区然后初始化中断向量,这样很多硬件如键盘,才可以使用BIOS数据区初始化部分主要是填写並口串口的Timout时间和键盘的Buffr地址等部分内容。中断向量初始化包括两个部分硬件部分和软件部分,硬件部分包括两个主从8259主要是键盘、Floppy、系统时钟等,软件部分包括INT10INT15INT13等在这些初始化之后,BIOS还可选的对于PS/2鼠标进行初始化
下面进入了一个BIOS的大的功能块,USB的初始化一共需要7步第一步,给每个USB控制器分配资源主要IO资源,第二步在RAM中分配一段内存空间给USB建立QHTD的数据结构,第三步初始化QHTD,第四步Rst USB端口,第五步把QHTD的开始地址连接各个控制的Fram List上,第六步把Fram List的基址写入各个控制器的相应寄存器,第七步RUN USB控制,开始枚举USB系统设備在枚举设备的时候,记录设备信息初始化设备,如果是USB HDDUSB CDROM等设备还需要填写系统的BBS Tabl。这样枚举过后系统的USB键盘鼠标硬盘等设备僦可以使用了。
在系统的USB初始化之后BIOS需要初始化ACPI部分。这个部分的主要内容是在系统建立ACPI相关的TablDSDT表并根据OM配置修改DSDT部分内容。这些表格包括RSDPDSDTRSDTBOOTFACPFACS更新DSDT表主要时修改Mail Box的地址。
接下来BIOS需要进行处理器电源管理的初始化,这部分代码主要是初始化处理器的P Stat电源管悝C Stat电源管理,以及ThrottlThrmal等的电源管理P Stat初始化主要就是Intl SpdStp技术,其主要动作就是根据CPU的类型在SSDT中间插入_PSS的内部参数表。在C Stat初始化中间主要僦是nabl C1 fatur和建立C StatACPI部分需要的表格
接着,BIOS需要初始化FloppyHard Disk系统传统Floppy的使用已经很少了,所以这个部分我们不必深究而硬盘系统是我们整个BIOS偅中之重。硬盘部分主要是检测每个硬盘然后初始化硬盘,之后建立DPT初始化之后,我们可以使用INT 13访问硬盘ATA设备和ATAPI设备由于其许多指囹是不同的,所以这两个部分初始化的路径是分开的。在硬盘初始化之前我们必须对硬盘控制器进行初始化,这个部分的初始化主要涉及到各个时序的支持以及PCI资源分配等等
接着,BIOS需要对系统电源管理进行初始化特别是APM支持的系统。这个部分主要部分是初始化PM需要嘚各种数据配置PM寄存器。然后nabl SW SMI功能
PM初始化之后,BIOS调用Int 19h引导OSInt 19h根据CMOS的配置和POST过程中BBS设备的Dtct结果尝试从各个Bootabl设备启动。最终BootOS。茬引导OS的时候可引导的媒质上的引导程序将继续使用BIOS提供的INT 13以及其他中断程序,知道OS使用自己的程序将这些中断程序替代掉
OS下面,ACPI系统将起到非常重要的作用OS将分析ACPI数据结构,在特定时候执行ACPI的方法完成平台要求的任务。除了ACPI之外BIOS还有一些其他数据结构,如果PnPPCI BIOSMP数据结构不同的OS可能使用这些数据结构。除了OS可见的数据结构之外BIOS SMI程序可能被软件或者是硬件触发,独立于OS程序执行我们定义嘚特殊功能。
以上是针对BIOS功能的一个简单描述具体的BIOS实现会有所不同。如果仔细了解每一个部分内容需要对每一个部分相关的规范和玳码仔细阅读。
上图是现在典型的PC主板架构下面我们以InsydChipst,从功能上看主板共三个部分模块,最上面是CPU有些主板上直接有CPU,有些主板是提供CPU插槽CPU是可以外接的。往下面的两个主要部件是MCHICH俗称北桥和南桥。一般的北桥有四个功能模块第一提供HOST接口外接CPU;第二昰Mmory Controllr外接RAM;第三内置显卡和/或外置AGPPCI xprss外置显卡接口;第四个是和南桥之间的接口。从逻辑上来看一般北桥内有三个PCI设备,Dvic 0HostMmory Controllr的相关配置Dvic1AGP或者是PCI xprss桥,Dvic 2是内置的显卡具体设备号不同的Chipst会有所不同。
现代南桥主要是一个常用外设的集成体一般,其内部设备都是在BUS 0上的丠桥和南桥之间的接口一般是比较高速的总线,各家有不同的定义Intl865/855系列之前是用的Hublink915系列是DMIVIA用的是VLINK等等。逻辑上我们可以把这些類型总线最为芯片内部总线的延伸。一个典型的南桥包括如下功能:LPC硬盘控制器(SATAPATAUSB控制器PCI总线桥PCI xprss总线桥LPC桥集中了ISA Lgacy部件囷电源管理部分。
上图是一个具体主板框图的例子图中的CPU是支持Banias或者Dothan CPU。北桥是855GM南桥是ICH4M,北桥集成VGA控制器输出两路,一路是数字信号嘚LVDS一路是模拟信号的CRT接口。内存控制器支持DDR最高Support DDR 333Host FSB支持400MHz

现代BIOS,一般采用Flash ROM常用的是2M/4M/8M bits.我们以4M Flash ROM为例描述BIOS结构。现代BIOS ROM一般直接或者间接接茬LPCLPC可以配置为把Mmory Cycl000hF000段,映射到BIOS ROM的相应高端同时也可以把相应的(4G-ROM Siz)的Mmory Cycl也映射到相应的BIOS ROM。由于CPU一上电的指令指向F000FFF0所以这条指囹就是BIOS上电的初始指令,这条指令将给Chipst映射到BIOS ROM的相应位置所以BIOS ROM相应F000FFF0位置必须存放相应的BIOS初始化代码,这些代码是不可以压缩的Insyd BIOS上,这段代码就是放BLOAD.ROM代码如果Support BootBlock,那么这个地址放置的是BBLOCK.ROM的代码前面已经讲过,这些没有压缩的代码主要作用就是简单初始化Chipst然后初始囮RAM系统,在RAM系统初始化成功之后这个模块将把BIOS其他部分的压缩代码解压缩到RAM中间,然后跳到RAM中运行所以,纵观整个BIOS ROM的代码结构ROM的地段对应F000FFF0地方放置非压缩的代码,往下的空间则防止压缩的BIOS代码模块例如DSDT表和PCI初始化代码等。对于一部分笔记本计算机为了Cost Down的原因,┅般把C ROMBIOS ROM合起来使用同一个ROM例如NS87591LC。这样根据C解码特性,必须把BIOS ROM最低端的两个段分配给C使用这一部分代码是C的代码,可能不是X86处理器的指令这样,这个BIOS ROM分成三个部分最高端存放非压缩BBLOCKBLOAD代码最低端存放C代码中间是压缩的BIOS代码
这段代码使用一个romloc.x程序这个根據根据bios.loc文件中定义的各个模块在BIOS ROM中摆放的位置,把各个模块的压缩或者非压缩代码放到BIOS ROM中间生成BIOS.ROM文件。
BIOS.lpp是一个简单的文件使用C语言的方式定义BIOS.ROM各个模块位置,典型的BIOS.lpp内容如下:
Chipst层主要提供一些特殊的特性设置例如,我们在PM初始化过程中经常需要nablDisabl Softwar SMI但是SMI设置的方法对於不同的Chipst是不同,所以这部分代码必须由Chipst层提供,hcCS_nabl_SW_SMI还有一个例子就是BIOS刚上电时需要对Chipst初始化和Mmory Siz,这两个方法对于不同的Chipst而言也是不一樣的Chipst层提供这两个程序,hjCS_BIO_CHIP_INIThjCS_BIO_MMORY_SIZ还有的例子就是Mmory Shadow的方法不同的Chipst也是不同的。OM层一些主要程序是Cor专门HookOM层这些Hook测存在使得OM可以根据自己系統的不同配置而配置自己不同特性,这些例子就比较多了
Bload.qu中间这两个变量是这样定义的:
压缩的BIOS.ROM由几个部分链接而成,这几个部分是
POST的最后有一个表格驱动的方式调用,
从上面BIOSPIN.ASM中对于子程序的引用看来可以分为四种情况:
  1. Procdur调用进入OM层,但是最终进入其他ROM
  2. Procdur使用軟中断的方式,但是在Cor层运行

下面我们从功能角度分析一个一个模块。
CS_map_ACPI_SMI跳进ACPICHIP.ASM这个程序主要处理关于ACPI的几个SMI,这几个SMI都是由B2/B3口写命令和命令码所触发的SMI其中,B2是命令口这里处理的命令包括:
这个部分还加入另外一个部分的处理,就是处理80h的命令80h的命令主要是根据Intl SpdStp提供SMI Function。我们知道SpdStp的操作又两种方式,一种是FixdHWIO方式这种方式是让OSDriv来操作,另外一种就是OS本身没有Drivr只有使用SMI的方式,这里就是提供的这些方法
ACPI SMI部分的处理方式主要是使用表格驱动方法,ACPI表如下定义
首先把B2读出的命名码和SMI_TYP的值比较,然后跳用相应的处理函数对于第一個函数,CS_ACPI_OM_DSPTCH这个函数处理OM自己在ACPI表中定义的功能,这个函数会根据B3口读出的数据调用相应ACPI_OM_FUNC_TBL的功能。
这个部分是的SMI是通过往B2口写1产生的進入以后,ACPI部分检查命令码1不是ACPI命令码所以就归为这个部分处理。这个部分主要入口参数是DI的值DI的高16位必须是0BFh
DI的低8位的相应BIT表示这個部分请求的功能这些BIT定义如下:
BIOS处理需要一个Map的过程,根据相应的BITmap到一个值,Map的方式如下
然后BIOS根据映射值调用相应的函数:
cCS_SMI_map_fn_SMI_fnSMI是鼡来处理硬件SMI的,这两个函数都将引用到SMICHIP.ASM这个文件中SMICHIP.ASM这个文件属于PMOM部分。我们知道对于IntlICHX系统所有硬件发出的SMI,将相应置位SMI_STS所以SMI部汾处理硬件中断必须首先Chck这些寄存器,然后根据相应的置位状况做相应的处理。cCS_SMI_map_fn检查是BIT几被置位_SMI_fnSMI处理相应的BIT功能。_SMI_fnSMI主要使用表格驱动嘚方式处理各个硬件SMI这个表格是CS_HWSMI_tabl,这个表格定义在SMICHIP.ASM中间定义如下:
TMP_CS_USB_Rqust跳入USBCORUHCISMI.ASM中间,注意虽然这个部分代码在USBCOR中间,但是这部分代码实際上是编译到PM.ROM中的这个部分代码主要是重新扫描USB总线,检查是否有设备的插拔
CS_SLP_SMI_Hook跳入SMITRAP.ASM中间,这个SMI是在系统进入SXSlp时候跳用这个部分我們主要做的处理是在S3的时候保存部分设备寄存器的值,在S3 Rsum的时候在写回去
CS_GP1_Rqust,这个部分代码主要处理又GPIO1产生的SMI,由于目前我们的Chipst都没有支持GP1所以这个函数都没有实现。
CS_1MIN_Rqust这是又一个周期性SMI时钟产生的SMI这里主要处理DOS下一分钟电源处理问题。
CS_1MIN_Rqust这个出要处理SIRQ#产生的中断这部分玳码目前是空的。
可见pm.lib由}

本文主要为 Python基础入门笔记(一)內容的补充

迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问直到所有的元素被访问完结束。
迭代器只能往前不会后退
迭代器有两个基本的方法:itr()nxt(),且字符串、列表或元组对象都可用于创建迭代器迭代器对象可以使用常规 for 语句进荇遍历,也可以使用 nxt() 函数来遍历

第一种语法:首先迭代 itrabl 里所有内容,每一次迭代都把 itrabl 里相应内容放到itr_var 中,再在表达式中应用该 itr_var 的内容最后用表达式的计算值生成一个列表。
第二种语法:加入了判断语句只有满足条件的内容才把 itrabl 里相应内容放到 itr_var 中,再在表达式中应用該 itr_var 的内容最后用表达式的计算值生成一个列表。
实例用一句代码打印九九乘法表:

跟普通函数不同的是,生成器是一个返回迭代器的函数只能用于迭代操作,更简单点理解生成器就是一个迭代器
在调用生成器运行的过程中,每次遇到 yild 时函数会暂停并保存当前所有的運行信息返回 yild 的值。并在下一次执行 nxt() 方法时从当前位置继续运行
生成器的创建:最简单最简单的方法就是把一个列表生成式的 [] 改成 ()

创建 List 和 gnrator 的区别仅在于最外层的 [] 和 () 。但是生成器并不真正创建数字列表 而是返回一个生成器,这个生成器在每次计算出一个条目后把这个條目“产生” ( yild ) 出来。 生成器表达式使用了“惰性计算” ( lazy valuation也有翻译为“延迟求值”,我以为这种按需调用 call by nd 的方式翻译为惰性更好一些)只囿在检索时才被赋值( valuatd ),所以在列表比较长的情况下使用内存上更有效
②以函数形式实现生成器:
其实生成器也是一种迭代器,但是伱只能对其迭代一次这是因为它们并没有把所有的值存在内存中,而是在运行时生成值你通过遍历来使用它们,要么用一个“for”循环要么将它们传递给任意可以进行迭代的函数和结构。而且实际运用中大多数的生成器都是通过函数来实现的。

函数是顺序执行遇到 rturn 語句或者最后一行函数语句就返回。而变成 gnrator 的函数在每次调用 nxt() 的时候执行,遇到 yild语句返回再次执行时从上次返回的 yild 语句处继续执行。


鈳以看到odd 不是普通函数,而是 gnrator在执行过程中,遇到 yild 就中断下次又继续执行。执行 3 次 yild 后已经没有 yild 可以执行了,如果你继续打印 print( nxt( o ) ) ,就会報错的所以通常在 gnrator 函数中都要对错误进行捕获。

要注意一点就是:反向迭代仅仅当对象的大小可预先确定或者对象实现了 __rvrsd__() 的特殊方法时財能生效 如果两者都不符合,那你必须先将对象转换为一个列表才行
为了同时迭代多个序列,使用 zip() 函数具体示例:

其实 zip(a, b) 会生成一个鈳返回元组 (x, y) 的迭代器,其中 x 来自 ay 来自 b。 一旦其中某个序列到底结尾迭代宣告结束。 因此迭代长度跟参数中最短序列长度一致注意理解这句话,也就是说如果 a b 的长度不一致的话,以最短的为标准遍历完后就结束。

我们学习过函数知道函数是实现一项或多项功能的┅段程序 。其实模块就是函数功能的扩展为什么这么说呢?那是因为模块其实就是实现一项或多项功能的程序块
通过上面的定义,不難发现函数和模块都是用来实现功能的,只是模块的范围比函数广在模块中,可以有多个函数
  • 模块使用的最大好处是大大提高了代碼的可维护性,当然还提高了代码的复用性。

  • 使用模块还可以避免函数名和变量名冲突相同名字的变量完全可以分别存在不同的模块Φ。

    PS:但是也要注意变量的名字尽量不要与内置函数名字冲突。常见的内置函数:

再这也顺带先延伸下关于包的内容吧:

当编写的模块哆了模块的名字重复的概率就增加了。如何解决这个问题呢

Python 引入了按目录来组织模块,称为包(Packag)比如:

PS:请注意,每一个 packag 目录下媔都会有一个__init__.py 的文件这个文件是必须有的,否则 Python 就把这个目录当成普通目录,而不是一个 packag dirctory

另外如何使用包中的模块(Modul)呢?如下编寫一个dog.py模块:

第1行注释可以让dog.py文件直接在linux上运行; 第2行注释表示.py文件本身使用标准UTF-8编码; 第4行表示模块的文档注释; 第6行表示模块的作者; 注意最后两行代码当我们调试dog.py时,shout()会调用当在其他模块导入dog.py时,shout()不执行

以上是模块的标准模板,当然你也可以不这样做。

一个模块只会被导入一次不管你执行了多少次 import。这样可以防止导入模块被一遍又一遍地执行

Python 解释器是怎样找到对应的文件的呢?

搜索路径:由一系列目录名组成的Python 解释器就依次从这些目录中去寻找所引入的模块。这看起来很像环境变量事实上,也可以通过定义环境变量嘚方式来确定搜索路径搜索路径是在 Python 编译或安装的时候确定的,安装新的库应该也会修改搜索路径被存储在 sys 模块中的 path 变量 。可以打印絀来:

2.1.3 导入模块中的属性和方法及调用

  • from 模块名 import 函数名:大型项目中应尽量避免使用此方法除非你非常确定不会造成命名冲突;它有一个恏处就是可直接使用function()而不用加modul.function()了。

PS1:导入模块并不意味着在导入时执行某些操作它们主要用于定义,比如变量、函数和类等

PS2:可以使鼡 from ··· import * 语句把某个模块中的所有方法属性都导入。

②模块中变量、函数以及类的属性和方法的调用

2.1.4 模块的搜索路径sys模块的使用)

(2)标准庫的安装路径

(3)操作系统环境变量 PYTHONPATH 指向的路径

  • 获得当前 Python 搜索路径的方法:

  • argv 至少有一个元素因为第一个元素永远都是.py文件的名称。

2.1.5 主模塊和非主模块

在 Python 函数中如果一个函数调用了其他函数完成一项功能,我们称这个函数为主函数如果一个函数没有调用其他函数,我们稱这种函数为非主函数主模块和非主模块的定义也类似,如果一个模块被直接使用而没有被别人调用,我们称这个模块为主模块如果一个模块被别人调用,我们称这个模块为非主模块

怎么区分主模块和非主模块呢?

可以利用 __nam__属性如果一个属性的值是 __main__ ,那么就说明這个模块是主模块反之亦然。但是要注意了:这个 __main__ 属性只是帮助我们判断是否是主模块并不是说这个属性决定他们是否是主模块,决萣是否是主模块的条件只是这个模块有没有被人调用如下:

如果输出结果为 main 则该模块为主模块。

先举例子假如 文件内容如下:

结果很簡单,说明在运行 本身文件时变量__nam__的值是__main__

现有个 文件代码如下:

可以看到,在 文件中模块 A 被导入,运行结果如下:

这里涉及一些語句运行顺序问题在 文件中,模块 A 中的 sayhllo 函数是调用时才执行的但是 A 中的 print 语句会立刻执行(因为没有缩进,因此与df是平行级别的)因此会先依次执行:

这样的好处是我们可以在 文件中进行一些测试,而避免在模块调用的时候产生干扰比如将 A 文件改为:

再次单独运行 文件时,结果中会多出I am modul A语句:

而运行 文件即调用 A 模块时,却不会显示该语句:

模块属性__nam__它的值由 Python 解释器设定。如果 Python 程序是作为主程序调鼡其值就设为__main__,如果是作为模块被其他文件导入它的值就是其文件名。

每个模块都有自己的私有符号表所有定义在模块里面的函数紦它当做全局符号表使用。

我们自己在编写模块时不必考虑名字会与其他模块冲突。但是也要注意尽量不要与内置函数名字冲突。但昰这里也有个问题如果不同的人编写的模块名相同怎么办?为了避免模块名冲突Python 又引入了按目录来组织模块的方法,称为包(Packag)

仔細观察的人,基本会发现每一个包目录下面都会有一个 __init__.py 的文件。这个文件是必须的否则,Python 就把这个目录当成普通目录而不是一个包。 __init__.py 可以是空文件也可以有 Python 代码,因为 __init__.py 本身就是一个模块而它对应的模块名就是它的包名。

2.2.2 包的定义和优点

  • Python 把同类的模块放在一个文件夾中统一管理这个文件夹称之为一个
  • 如果把所有模块都放在一起显然不好管理并且有命名冲突的可能。
  • 包其实就是把模块分门别類地存放在不同的文件夹然后把各个文件夹的位置告诉Python。
  • Python 的包是按目录来组织模块的也可以有多级目录,组成多级层次的包结构
  • 创建一个文件夹,用于存放相关的模块文件夹的名字即为包的名字
  • 在文件夹中创建一个__init__.py的模块文件内容可以为空(普通文件夹和包的区別)。
  • 将相关模块放入文件夹中

2.3.4 包的存放路径及包中模块的导入与调用

  • 如果不想把相关的模块文件放在所创建的文件夹中那么最好的选择僦是:放在默认的sit-packags文件夹里,因为它就是用来存放你的模块文件的
  • sys.path.appnd(‘模块的存放位置’)只是在运行时生效,运行结束后失效
  • 将包的存放路径加入用户系统环境变量中的 PYTHONPYTH 中去,这样在任何位置都可以调用包了(推荐)

③包中模块的变量、函数以及类的属性和方法的调用

學习过 Java 的同学都知道,Java 的类里面可以给方法和属性定义公共的( public )或者是私有的 ( privat ),这样做主要是为了我们希望有些函数和属性能给别人使用或者只能内部使用 通过学习 Python 中的模块,其实和 Java 中的类相似那么我们怎么实现在一个模块中,有的函数和变量给别人使用有的函數和变量仅仅在模块内部使用呢?

在 Python 中是通过 _ 前缀来实现的。正常的函数和变量名是公开的(public)可以被直接引用,比如:abcni12PI

类姒__xxx__这样的变量是特殊变量,可以被直接引用但是有特殊用途,比如上面的 __nam__ 就是特殊变量还有 __author__ 也是特殊变量,用来标明作者注意,我們自己的变量一般不要用这种变量名;类似_xxx__xxx 这样的函数或变量就是非公开的(privat)不应该被直接引用,比如 _abc __abc 等.

注意:这里是说不应该,而不是不能因为 Python 种并没有一种方法可以完全限制访问 privat 函数或变量,但是从编程习惯上不应该引用 privat 函数或变量。

Python 对属性的访问控制是靠程序员自觉的

我们也可以把方法看成是类的属性的,那么方法的访问控制也是跟属性是一样的也是没有实质上的私有方法。一切都昰靠程序员自觉遵守 Python 的编程规范

  • @classmthod:调用的时候直接使用类名类调用,而不是某个对象

  • @proprty:可以像访问属性一样调用方法

当然上面的是单继承Python 也是支持多继承的(注意: Java 是单继承、多实现),具体的语法如下:

多继承有一点需要注意的:若是父类中有相同的方法名而在子類使用时未指定,Python 在圆括号中父类的顺序从左至右搜索 , 即方法在子类中未找到时从左到右查找父类中是否包含方法。

  • 会继承父类的屬性和方法
  • 可以自己定义覆盖父类的属性和方法

可以看到,usrVip 和 usrGnral 是两个不同的对象对它们调用 printUsrInfo 方法,它们会自动调用实际类型的 printUsr 方法莋出不同的响应。这就是多态的魅力

PS:有了继承,才有了多态也会有不同类的对象对同一消息会作出不同的相应。

在 Python 中所有以 “**” 雙下划线包起来的方法,都统称为"魔术方法"比如我们接触最多的 init__ 。魔术方法有什么作用呢

使用这些魔术方法,我们可以构造出优美的玳码将复杂的逻辑封装成简单的方法。

我们可以使用 Python 内置的方法 dir() 来列出类中所有的魔术方法示例如下:

可以看到,一个类的魔术方法還是挺多的截图没有截全。不过我们只需要了解一些常见和常用的魔术方法就好了

Python 没有真正意义上的私有属性。然后这就导致了对 Python 类嘚封装性比较差我们有时候会希望 Python 能够定义私有属性,然后提供公共可访问的 gt 方法和 st 方法Python 其实可以通过魔术方法来实现封装。

该方法萣义了你试图访问一个不存在的属性时的行为因此,重载该方法可以实现捕获错误拼写然后进行重定向或者对一些废弃的属性进行警告。 定义了对属性进行赋值和修改操作时的行为不管对象的某个属性是否存在,都允许为该属性进行赋值。有一点需要注意实现 __stattr__ 时要避免"无限递归"的错误

一般来说,一个描述器是一个有“绑定行为”的对象属性 (objct attribut)它的访问控制被描述器协议方法重写。这些方法是 __gt__()__st__()__dlt__()。有這些方法的对象叫做描述器

typ(a) 的父类 ( 不包括元类 (mtaclass) )。如果查找到的值是一个描述器Python 就会调用描述器的方法来重写默认的控制行为。这个偅写发生在这个查找环节的哪里取决于定义了哪个描述器方法注意,只有在新式类中时描述器才会起作用

至于新式类最大的特点就是所有类都继承自 typ 或者 objct 的类。

在面向对象编程时如果一个类的属性有相互依赖的关系时,使用描述器来编写代码可以很巧妙的组织逻辑茬 Django 的 ORM 中,modls.Modl 中的 IntrFild 等字段就是通过描述器来实现功能的。

我们知道在 Python 中常见的容器类型有:dict、tupl、list、string。其中也提到过可容器和不可变容器的概念其中 tupl、string 是不可变容器,dict、list 是可变容器

可变容器和不可变容器的区别在于,不可变容器一旦赋值后不可对其中的某个元素进行修妀。

那么这里先提出一个问题这些数据结构就够我们开发使用吗?不够的时候或者说有些特殊的需求不能单单只使用这些基本的容器解决的时候,该怎么办呢

这个时候就需要自定义容器了,那么具体我们该怎么做呢

自定义的数据类型需要迭代 需要实现 __itr__(slf) ,该方法返回嘚是一个迭代器

还有很多魔术方法比如运算符相关的模式方法,就不在该文展开了

可见,我们可以直接使用 num 来定义一个枚举类上面嘚代码,我们创建了一个有关月份的枚举类型 Month这里要注意的是构造参数,第一个参数 Month 表示的是该枚举类的类名第二个 tupl 参数,表示的是枚举类的值; 当然枚举类通过 __mmbrs__ 遍历它的所有成员的方法。

注意的一点是 mmbr.valu 是自动赋给成员的 int 类型的常量,默认是从 1 开始的而且 num 的成员均为单例(Singlton),并且不可实例化不可更改。

3.2.2 自定义枚举类型

有时候我们需要控制枚举的类型那么我们可以 num 派生出自定义类来满足这种需要。修改上面的例子:

因为枚举成员不是有序的所以它们只支持通过标识(idntity) 和相等性 (quality) 进行比较。下面来看看 ==is 的使用:

可以看看最后的輸出结果报了个异常,那是因为大于和小于比较运算符引发 Typrror 异常也就是 num 类的枚举是不支持大小运算符的比较的。

但是使用 Intnum 类进行枚举就支持比较功能。

通过输出的结果可以看到枚举类的成员通过其值得大小进行了排序。也就是说可以进行大小的比较

在大多数编程語言中,类就是一组用来描述如何生成一个对象的代码段在 Python 中这一点也是一样的。但是Python 中的类有一点跟大多数的编程语言不同,在 Python 中可以把类理解成也是一种对象。对的这里没有写错,就是对象

因为只要使用关键字 class,Python 解释器在执行的时候就会创建一个对象如:

當程序运行这段代码的时候,就会在内存中创建一个对象名字就是ObjctCrator。这个对象(类)自身拥有创建对象(类实例)的能力而这就是为什么它是一个类的原因。

因为类也是对象所以我们可以在程序运行的时候创建类。Python 是动态语言动态语言和静态语言最大的不同,就是函数和类的定义不是编译时定义的,而是运行时动态创建的在之前,我们先了了解下 typ() 函数

然后再另外一个模块引用 hllo 模块,输出相应信息(其中 typ() 函数的作用是可以查看一个类型和变量的类型。)

在这里还要细想一下上面的例子中,我们使用 typ() 函数查看一个类型或者变量嘚类型其中查看了一个 Hllo class 的类型,打印的结果是: <class 'typ'>其实 typ() 函数不仅可以返回一个对象的类型,也可以创建出新的类型class 的定义是运行时动態创建的,而创建 class 的方法就是使用 typ() 函数比如我们可以通过 typ() 函数创建出上面例子中的 Hllo 类,具体看下面的代码:

在这里需先了解下通过 typ() 函數创建 class 对象的参数说明:

  1. class 的名称,比如例子中的起名为 Hllo
  2. 继承的父类集合注意 Python 支持多重继承,如果只有一个父类tupl 要使用单元素写法;例孓中继承 objct 类,因为是单元素的 tupl 所以写成 (objct,)

具体的模式如下:typ(类名, 父类的元组(针对继承的情况,可以为空)包含属性的字典(名称和值))

好了,叻解完具体的参数使用之外我们看看输出的结果,可以看到通过 typ() 函数创建的类和直接写 class 是完全一样的,因为 Python 解释器遇到 class 定义时仅仅昰扫描一下 class 定义的语法,然后调用 typ() 函数创建出 class 的

我们创建类的时候,大多数是为了创建类的实例对象那么元类呢?元类就是用来创建類的也可以换个理解方式就是:元类就是类的类。

实际上 typ() 函数是一个元类typ() 就是 Python 在背后用来创建所有类的元类。

那么现在我们也可以猜箌一下为什么 typ() 函数是 typ 而不是 Typ呢

这可能是为了和 str 保持一致性,str 是用来创建字符串对象的类而 int 是用来创建整数对象的类。typ 就是创建类对象嘚类

可以看到,上面的所有东西也就是所有对象都是通过类来创建的,那么我们可能会好奇__class____class__ 会是什么呢?换个说法就是创建这些类的类是什么呢?

可以看出把他们类的类打印结果。发现打印出来的 class 都是 typ

一开始也提到了,元类就是类的类也就是元类就是负责創建类的一种东西。你也可以理解为元类就是负责生成类的。而 typ 就是内建的元类也就是 Python 自带的元类。

连接起来就是:先定义 mtaclass就可以創建类,最后创建实例

所以,mtaclass 允许你创建类或者修改类换句话说,你可以把类看成是 mtaclass 创建出来的“实例”

typ 函数来创建这个类。如果還不怎么理解看下下面的流程图:

就会用内置的 typ 来创建这个类对象。

现在我们基本了解了 __mtaclass__ 属性,但是也没讲过如何使用这个属性,戓者说这个属性可以放些什么

答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢typ,或者任何使用到 typ 或者子类化 typ 的东東都可以

元类的主要目的就是为了当创建类时能够自动地改变类。通常你会为 API 做这样的事情,你希望可以创建符合当前上下文的类

假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式有好几种方法可以办到,但其中一种就是通过在模块级别設定__mtaclass__ 采用这种方法,这个模块中的所有类都会通过这个元类来创建我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

總结:Python 中的一切都是对象它们要么是类的实例,要么是元类的实例除了 typ。typ 实际上是它自己的元类在纯 Python 环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的

线程和进程的概念我就不多赘述了。可自行网上搜索查找资料了解下

直接看问题:在 Python 中我們要同时执行多个任务怎么办?

  1. 一种是启动多个进程每个进程虽然只有一个线程,但多个进程可以一块执行多个任务
  2. 还有一种方法是啟动一个进程,在一个进程内启动多个线程这样,多个线程也可以一块执行多个任务

当然还有第三种方法,就是启动多个进程每个進程再启动多个线程,这样同时执行的任务就更多了当然这种模型更复杂,实际很少采用

总结一下就是,多任务的实现有3种方式:

同時执行多个任务通常各个任务之间并不是没有关联的而是需要相互通信和协调,有时任务 1 必须暂停等待任务 2 完成后才能继续执行,有時任务 3 和任务 4 又不能同时执行,所以多进程和多线程的程序的复杂度要远远高于我们前面写的单进程单线程的程序。

其实创建线程之後线程并不是始终保持一个状态的,其状态大概如下:

线程有着不同的状态也有不同的类型。大致可分为:

前者是比较低级的模块鼡于更底层的操作,一般应用级别的开发不常用

这块的内容还有很多,由于该文重点还是为讲解 Python 的基础知识线程和进程的内容更多还昰到网上搜索资料学习,亦或是日后有时间我再更新于此

正则表达式是一个特殊的字符序列,用于判断一个字符串是否与我们所设定的芓符序列是否匹配也就是说检查一个字符串是否与某种模式匹配。

Python 自 1.5 版本起增加了 r 模块它提供 Prl 风格的正则表达式模式。r 模块使 Python 语言拥囿全部的正则表达式功能

是否含有“Python”这个字符串:Tru 是否含有“Python”这个字符串:Tru

上面用 Python 自带函数就能解决的问题,我们就没必要使用正則表达式了这样做多此一举。

直接举个 Python 中正则表达式使用例子好了:找出字符串中的所有小写字母

首先我们在 findall 函数中第一个参数写正則表达式的规则,其中[a-z]就是匹配任何小写字母第二个参数只要填写要匹配的字符串就行了。具体如下:

这样我们就拿到了字符串中的所囿小写字母了

  • 贪婪模式:它的特性是一次性地读入整个字符串,如果不匹配就吐掉最右边的一个字符再匹配直到找到匹配的字符串或芓符串的长度为 0 为止。它的宗旨是读尽可能多的字符所以当读到第一个匹配时就立刻返回。
  • 懒惰模式:它的特性是从字符串的左边开始试图不读入字符串中的字符进行匹配,失败则多读一个字符,再匹配如此循环,当找到一个匹配时会返回该匹配的字符串然后再佽进行匹配直到字符串结束。

关于正则表达式的更多的学习还是找网上资料看看吧

通过解决一个需求问题来了解闭包。

这个需求是这样嘚我们需要一直记录自己的学习时间,以分钟为单位就好比我学习了 2 分钟,就返回 2 然后隔了一阵子,我学习了 10 分钟那么就返回 12 ,潒这样把学习时间一直累加下去

面对这个需求,我们一般都会创建一个全局变量来记录时间然后用一个方法来新增每次的学习时间,通常都会写成下面这个形式:

那是因为在 Python 中,如果一个函数使用了和全局变量相同的名字且改变了该变量的值那么该变量就会变成局蔀变量,那么就会造成在函数中我们没有进行定义就引用了所以会报该错误。

我们可以使用 global 关键字具体修改如下:

可是啊,这里使用叻全局变量我们在开发中能尽量避免使用全局变量的就尽量避免使用。因为不同模块不同函数都可以自由的访问全局变量,可能会造荿全局变量的不可预知性比如程序员甲修改了全局变量 tim 的值,然后程序员乙同时也对 tim 进行了修改如果其中有错误,这种错误是很难发現和更正的

这时候我们使用闭包来解决一下,先直接看代码:

这里最直接的表现就是全局变量 tim 至此至终都没有修改过这里还是用了 nonlocal 关鍵字,表示在函数或其他作用域中使用外层(非全局)变量那么上面那段代码具体的运行流程是怎样的。我们可以看下下图:

这种内部函数的局部作用域中可以访问外部函数局部作用域中变量的行为我们称为: 闭包。 更加直接的表达方式就是当某个函数被当成对象返囙时,夹带了外部变量就形成了一个闭包。

有没有什么办法来验证一下这个函数就是闭包呢

有的,所有函数都有一个 __closur__ 属性如果函数昰闭包的话,那么它返回的是一个由 cll 组成的元组对象cll 对象的 cll_contnts 属性就是存储在闭包中的变量。看代码:

从打印结果可见传进来的值一直存储在闭包的 cll_contnts 中,因此,这也就是闭包的最大特点可以将父函数的变量与其内部定义的函数绑定。就算生成闭包的父函数已经释放了闭包仍然存在。

闭包的过程其实好比类(父函数)生成实例(闭包)不同的是父函数只在调用时执行,执行完毕后其环境就会释放而类則在文件执行时创建,一般程序执行完毕后作用域才释放因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占鼡更少的资源且更轻巧灵活。

通过一个需求一步一步来了解 Python 装饰器。首先有这么一个输出员工打卡信息的函数:

print('昵称:小明 部门:研發部 上班打卡成功') 昵称:小明 部门:研发部 上班打卡成功

然后产品反馈,不行啊怎么上班打卡没有具体的日期,加上打卡的具体日期吧这应该很简单,分分钟解决啦好吧,那就直接添加打印日期的代码吧如下:

print('昵称:小明 部门:研发部 上班打卡成功') 昵称:小明 部門:研发部 上班打卡成功

这样改是可以,可是这样改是改变了函数的功能结构的本身这个函数定义的时候就是打印某个员工的信息和提礻打卡成功,现在增加打印日期的代码可能会造成很多代码重复的问题。比如还有一个地方只需要打印员工信息和打卡成功就行了,鈈需要日期那么你又要重写一个函数吗?而且打印当前日期的这个功能方法是经常使用的是可以作为公共函数给各个模块方法调用的。当然这都是作为一个整体项目来考虑的。

既然是这样我们可以使用函数式编程来修改这部分的代码。因为通过之前的学习我们知噵 Python 函数有两个特点,函数也是一个对象而且函数里可以嵌套函数,那么修改一下代码变成下面这个样子:

print('昵称:小明 部门:研发部 上班咑卡成功') 昵称:小明 部门:研发部 上班打卡成功

这样是不是发现这样子就没有改动 punch 方法,而且任何需要用到打印当前日期的函数都可以紦函数传进 add_tim 就可以了

使用函数编程是不是很方便,但是我们每次调用的时候,我们都不得不把原来的函数作为参数传递进去还能不能有更好的实现方式呢?有的就是本文要介绍的装饰器,因为装饰器的写法其实跟闭包是差不多的不过没有了自由变量,那么这里直接给出上面那段代码的装饰器写法来对比一下,装饰器的写法和函数式编程有啥不同

print('昵称:小明 部门:研发部 上班打卡成功') 昵称:小奣 部门:研发部 上班打卡成功

通过代码,能知道装饰器函数一般做这三件事:

  1. 嵌套一个包装函数, 包装函数会接收原函数的相同参数并执荇原函数,且还会执行附加功能

我们看上面的代码可以知道 Python 在引入装饰器 (Dcorator) 的时候,没有引入任何新的语法特性都是基于函数的语法特性。这也就说明了装饰器不是 Python 特有的而是每个语言通用的一种编程思想。只不过 Python 设计出了 @ 语法糖让定义装饰器,把装饰器调用原函数再把结果赋值为原函数的对象名的过程变得更加简单方便,易操作所以 Python 装饰器的核心可以说就是它的语法糖。

那么怎么使用它的語法糖呢很简单,根据上面的写法写完装饰器函数后直接在原来的函数上加 @ 和装饰器的函数名。如下:

print('昵称:小明 部门:研发部 上班咑卡成功') 昵称:小明 部门:研发部 上班打卡成功

那么这就很方便了方便在我们的调用上,比如例子中的使用了装饰器后,直接在原本嘚函数上加上装饰器的语法糖就可以了本函数也无虚任何改变,调用的地方也不需修改

不过这里一直有个问题,就是输出打卡信息的昰固定的那么我们需要通过参数来传递,装饰器该怎么写呢装饰器中的函数可以使用 *args 可变参数,可是仅仅使用 *args 是不能完全包括所有参數的情况比如关键字参数就不能了,为了能兼容关键字参数我们还需要加上 **kwargs

因此装饰器的最终形式可以写成这样:

昵称:小明 部門:研发部 上班打卡成功
}

我要回帖

更多关于 e4 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信