关于作者

姓名:

性别:男

出生日期:--

地区:浙江-杭州

联系电话:

QQ:--

婚否:保密
用户名:hsee
笔名:hsee
地区: 浙江-杭州
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



google

Google

互联网 本站

百度

baidu
互联网

QQ留言

给我QQ留言

手机所在地区查询

请输入要查询的手机号码(前七位):

音乐的国度

访问统计:
文章个数:12
评论个数:50
留言条数:20




Powered by BlogDriver 2.1

一个人混

 

々我们每个人都在行走,经过一个又一个驿站,我们是否留下点什么了,是否在我们走后有人会寻找到我们的踪迹呢☆★

文章

英语还是得好好学着呢  (作者置顶)

昨天去人民大会堂听了"二十一世纪的计算",来了很多牛人.微软高级副总裁Rick Rashid,图灵奖获得者Ronald L.Rivest,卡内基梅隆计算机系主任Jeannette M.Wing,Bill Gates技术顾问Alexnder Gounares,微软亚洲研究院院长沈向洋,美国国家科学院院士Stanley Osher,微软公司副总裁凌大任,多伦多大学计算机系Eugene Fiume.发现还是得好好学好计算机,不管以后怎么样,英语这东西还是得还好好.正如李开复在书中提到的一样,"我只要每天努力学习一小时,就能学好英语",要给自己信心,学习贵在信心和坚持.

- 作者: hsee 2005年11月2日, 星期三 15:06  回复(0) |  引用(1) 加入博采

明天回家了

暑假在学校过了一个月了  明天要回家了   总于还是想家了

- 作者: hsee 2005年07月31日, 星期日 03:45  回复(0) |  引用(1) 加入博采

李开复致大学生:大学4年应是这样度过

作者:李开复 来自:Blogchina

  今天,我回复了“开复学生网”开通以来的第1000个问题。关掉电脑后,始终有一封学生来信萦绕在我的脑海里,挥之不去:

  开复老师:

  就要毕业了。

  回头看自己所谓的大学生活,

  我想哭,不是因为离别,而是因为什么都没学到。

  我不知,简历该怎么写,若是以往我会让它空白。

  最大的收获也许是……对什么都没有的忍耐适应……

  这封来信道出了不少大三、大四学生的心声。大学期间,有许多学生放任自己、虚度光阴,还有许多学生始终也找不到正确的学习方向。当他们被第一次补考通知唤醒时,当他们收到第一封来自应聘企业的婉拒信时,这些学生才惊讶地发现,自己的前途是那么渺茫,一切努力似乎都为时已晚……

  这“第四封信”是写给那些希望早些从懵懂中清醒过来的大学生,那些从未贪睡并希望把握自己的前途和命运的大学生以及那些即将迈进大学门槛的未来大学生们的。在这封信中,我想对所有同学说:

  大学是人一生中最为关键的阶段。从入学的第一天起,你就应当对大学四年有一个正确的认识和规划。为了在学习中享受到最大的快乐,为了在毕业时找到自己最喜爱的工作,每一个刚进入大学校园的人都应当掌握七项学习:学习自修之道、基础知识、实践贯通、兴趣培养、积极主动、掌控时间、为人处事。只要做好了这七点,大学生临到毕业时的最大收获就绝不会是“对什么都没有的忍耐和适应”,而应当是“对什么都可以有的自信和渴望”。只要做好了这七点,你就能成为一个有潜力、有思想、有价值、有前途的快乐的毕业生。

  大学:人生的关键

  大学是人生的关键阶段。这是因为,进入大学是你终于放下高考的重担,第一次开始追逐自己的理想、兴趣。这是你离开家庭生活,第一次独立参与团体和社会生活。这是你不再单纯地学习或背诵书本上的理论知识,第一次有机会在学习理论的同时亲身实践。这是你第一次不再由父母安排生活和学习中的一切,而是有足够的自由处置生活和学习中遇到的各类问题,支配所有属于自己的时间。

  大学是人生的关键阶段。这是因为,这是你一生中最后一次有机会系统性地接受教育。这是你最后一次能够全心建立你的知识基础。这可能是你最后一次可以将大段时间用于学习的人生阶段,也可能是最后一次可以拥有较高的可塑性、集中精力充实自我的成长历程。这也许是你最后一次能在相对宽容的,可以置身其中学习为人处世之道的理想环境。

  大学是人生的关键阶段。在这个阶段里,所有大学生都应当认真把握每一个“第一次”,让它们成为未来人生道路的基石;在这个阶段里,所有大学生也要珍惜每一个“最后一次”,不要让自己在不远的将来追悔莫及。在大学四年里,大家应该努力为自己编织生活梦想,明确奋斗方向,奠定事业基础。

  大学四年每个人都只有一次,大学四年应这样度过……

  自修之道:从举一反三到无师自通

  记得我在哥伦比亚大学任助教时,曾有位中国学生的家长向我抱怨说:“你们大学里到底在教些什么?我孩子读完了大二计算机系,居然连VisiCalc 都不会用。”

  我当时回答道:“电脑的发展日新月异。我们不能保证大学里所教的任何一项技术在五年以后仍然管用,我们也不能保证学生可以学会每一种技术和工具。我们能保证的是,你的孩子将学会思考,并掌握学习的方法,这样,无论五年以后出现什么样的新技术或新工具,你的孩子都能游刃有余。”

  她接着问:“学最新的软件不是教育,那教育的本质究竟是什么呢?”

  我回答说:“如果我们将学过的东西忘得一干二净时,最后剩下来的东西就是教育的本质了。”

  我当时说的这句话来自教育家B. F. Skinner的名言。所谓“剩下来的东西”,其实就是自学的能力,也就是举一反三或无师自通的能力。大学不是“职业培训班”,而是一个让学生适应社会,适应不同工作岗位的平台。在大学期间,学习专业知识固然重要,但更重要的还是要学习独立思考的方法,培养举一反三的能力,只有这样,大学毕业生才能适应瞬息万变的未来世界。我认识的不少在中国读完大学来美国念研究生的朋友。他们认为来美国后,不论是学习,工作还是生活他们最缺乏的是独立思考的能力因为在国内时他们很少独立思考和独立决策。

  上中学时,老师会一次又一次重复每一课里的关键内容。但进了大学以后,老师只会充当引路人的角色,学生必须自主地学习、探索和实践。走上工作岗位后,自学能力就显得更为重要了。微软公司曾做过一个统计:在每一名微软员工所掌握的知识内容里,只有大约10%是员工在过去的学习和工作中积累得到的,其他知识都是在加入微软后重新学习的。这一数据充分表明,一个缺乏自学能力的人是难以在微软这样的现代企业中立足的。

  自学能力必须在大学期间开始培养。许多同学总是抱怨老师教得不好,懂得不多,学校的课程安排也不合理。我通常会劝这些学生说:“与其诅咒黑暗,不如点亮蜡烛”。 大学生不应该只会跟在老师的身后亦步亦趋,而应当主动走在老师的前面。例如,大学老师在一个课时里通常要涵盖课本中几十页的信息内容,仅仅通过课堂听讲是无法把所有知识学通、学透的。最好的学习方法是在老师讲课之前就把课本中的相关问题琢磨清楚,然后在课堂上对照老师的讲解弥补自己在理解和认识上的不足之处。

  中学生在学习知识时更多地是追求“记住”知识,而大学生就应当要求自己“理解”知识并善于提出问题。对每一个知识点,都应当多问几个“为什么”。一旦真正理解了理论或方法的来龙去脉,大家就能举一反三地学习其他知识,解决其他问题,甚至达到无师自通的境界。

  事实上,很多问题都有不同的思路或观察角度。在学习知识或解决问题时,不要总是死守一种思维模式,不要让自己成为课本或经验的奴隶。只有在学习中敢于创新,善于从全新的角度出发思考问题,学生潜在的思考能力、创造能力和学习能力才能被真正激发出来。

  《礼记.学记》上讲:“独学而无友,则孤陋而寡闻”。也就是说,大学生应当充分利用学校里的人才资源,从各种渠道吸收知识和方法。如果遇到好的老师,你可以主动向他们请教,或者请他们推荐一些课外的参考读物。除了资深的教授以外,大学中的青年教师、博士生、硕士生乃至自己的同班同学都是最好的知识来源和学习伙伴。每个人对问题的理解和认识都不尽相同,只有互帮互学,大家才能共同进步。

  有些同学曾告诉我说,他们很羡慕我在读书时能有一位获得过图灵奖的大师传道授业。其实,虽然我非常推崇我的老师,但他在大学期间并没有教给我多少专业知识。他只是给我指明了大方向,让我分享他的经验,给我提供研究的资源,并教我做人的方法。他没有时间也没有必要指导我学习具体的专业知识。我在大学期间积累的专业知识都是通过自学获得的。刚入门时,我曾多次红着脸向我的师兄请教最基本的知识内容,开会讨论时我曾问过不少肤浅的问题,课余时间我还主动与同学探讨、切磋。“三人行必有我师”,大学生的周围到处是良师益友。只要珍惜这些难得的机会,大胆发问,经常切磋,我们就能学到最有用的知识和方法。

  大学生应该充分利用图书馆和互联网,培养独立学习和研究的本领,为适应今后的工作或进一步的深造做准备。首先,除了学习老师规定的课程以外,大学生一定要学会查找书籍和文献,以便接触更广泛的知识和研究成果。例如,当我们在一门课上发现了自己感兴趣的课题,就应当积极去图书馆查阅相关文献,了解这个课题的来龙去脉和目前的研究动态。熟练和充分地使用图书馆资源,这是大学生特别是那些有志于科学研究的大学生的必备技能之一。读书时,应尽量多读一些英文原版教材。有些原版教材写得深入浅出,附有大量实例,比中文教材还适于自学。其次,在书本之外,互联网也是一个巨大的资源库,大学生们可以借助搜索引擎在网上查找各类信息。“开复学生网”开通半年以来,我发现很多同学其实并没有很好地掌握互联网的搜索技巧,有时他们提出的问题只要在搜索引擎中简单检索一下,就能轻易找到答案。还有些同学很容易相信网上的谣言,而不会利用搜索引擎自己查考、求证。除了搜索引擎以外,网上还有许多网站和社区也是很好的学习园地。

  自学时,不要因为达到了学校的要求就沾沾自喜,也不要认为自己在大学里功课好就足够了。在二十一世纪的今天,人才已经变成了一个国际化的概念。当你对自己的成绩感到满意时,我建议你开始自学一些国际一流大学的课程。例如,美国麻省理工学院(MIT)的开放式课程已经在网上无偿发布出来,大家不妨去看看MIT的网上课程,做做MIT的网上试题。当你可以自如地掌握MIT课程时,你就可以更加自信地面对国际化的挑战了。

  总之,善于举一反三,学会无师自通,这是大学四年中你可以送给自己的最好的礼物。

  基础知识:数学、英语、计算机、互联网

  我曾经说过,中国学生的一大优势是扎实的基础知识,如数学、物理等。但是,最近几年,同学们在目睹了很多速成的例子(如丁磊、陈天桥等)之后,也迫切希望能驶上成功的快车道。这渐渐形成了一种追求速成的浮躁风气。有许多大学生梦想在毕业后就立即能做“经理”、“老板”,还有许多大学生入学时直接选择了“管理”专业,因为他们认为从这样的专业毕业后马上就可以成为企业的管理者。可不少学生进入了管理专业后,才发现自己对本专业的学习毫无兴趣。其实,管理专业和其他专业一样,都是传授基础知识和基本方法的地方,没有哪个专业可以保证学生在毕业时就能走上领导岗位。无论同学们所学的是哪个专业,大学毕业才是个人事业的真正开始。想做企业领导或想做管理工作的同学也必须从基层做起,必须首先在人品方面学会做人,在学业方面打好基础。

  如果说大学是一个学习和进步的平台,那么,这个平台的地基就是大学里的基础课程。在大学期间,同学们一定要学好基础知识其中包括数学、英语、计算机和互联网的使用,以及本专业要求的基础课程(如商学院的财务、经济等课程)。在科技发展日新月异的今天,应用领域里很多看似高深的技术在几年后就会被新的技术或工具取代。只有对基础知识的学习才可以受用终身。另一方面,如果没有打下好的基础,大学生们也很难真正理解高深的应用技术。最后,在许多的中国大学里,教授对基础课程也比对最新技术有更丰富的教学经验。

  数学是理工科学生必备的基础。多学生在高中时认为数学是最难学的,到了大学里,一旦发现本专业对数学的要求不高,就会彻底放松对数学知识的学习,而且他们看不出数学知识有什么现实的应用或就业前景。但大家不要忘记,绝大多数理工科专业的知识体系都建立在数学的基石之上。例如,要想学好计算机工程专业,那至少要把离散数学(包括集合论、图论、数理逻辑等)、线性代数、概率统计和数学分析学好;要想进一步攻读计算机科学专业的硕士或博士学位,可能还需要更高的数学素养。同时,数学也是人类几千年积累的智慧结晶,学习数学知识可以培养和训练人的思维能力。通过对几何的学习,我们可以学会用演绎、推理来求证和思考的方法;通过学习概率统计,我们可以知道该如何避免钻进思维的死胡同,该如何让自己面前的机会最大化。所以,大家一定要用心把数学学好,不能敷衍了事。学习数学也不能仅仅局限于选修多门数学课程,而是要知道自己为什么学习数学,要从学习数学的过程中掌握认知和思考的方法。

  二十一世纪里最重要的沟通工具就是英语。有些同学在大学里只为了考过四级、六级而学习英语,有的同学仅仅把英语当作一种求职必备的技能来学习,甚至还有人认为学习和使用英语等于崇洋媚外。其实,学习英语的根本目的是为了掌握一种重要的学习和沟通工具。在未来的几十年里,世界上最全面的新闻内容,最先进的思想和最高深的技术,以及大多数知识分子间的交流都将用英语进行。因此,除非你甘心做一个与国际脱节的人,英语学习是至关重要的。在软件行业里,不但编程语言是以英语为基础设计出来的,最重要的教材、论文、参考资料、用户手册等资源也大多是用英语写就的。学英语绝不等于崇洋媚外。中国正在走向世界,中国需要学习西方的先进思想和先进科学技术,学好英语才是真正的爱国。

  很多中国留学生的英语考试成绩不错,也高分考过四级、六级、托福,但是留学美国后上课时却很难听懂课程内容,和外国同学交流时就更加困难。我们该如何学好英语呢?既然英语是最重要的沟通工具,那么,最重要的学习方法就是尽量与实践结合起来,不能只“学”不“用”,更不能只靠背诵的方式学习英语。读书时,大家尽量阅读原版的专业教材(如果英语不够好,可以先从中英对照的教材看起),并适当地阅读一些自己感兴趣的专业论文,这可以同时提高英语和相关专业的知识水平。其次,提高英语听说能力的最好方法是直接与那些以英语为母语的外国人对话。现在有很多在中国学习和工作的外国人,他们中的不少人为了学中文,很愿意与中国学生对话、交流,这是很好的学习机会。此外,大家不要把学英语当作一件苦差事,完全可以用有趣的方法学习英语。例如,可以多看一些名人的对话或演讲,多看一些小说、戏剧甚至漫画。初学者可以找英文原版的教学节目和录像来学习,有一定基础的则应该看英文电视或电影。看一部英文电影时,最好先在有字幕的时候看一遍,同时查考生词、熟悉句式,然后在不加字幕的情况下再看一遍,仅靠耳朵去听。听英文广播也是很好的练习英文听力的方法,大家每天最好能抽出半小时到一小时的时间收听广播并尽量理解其中的内容,有必要的话还可以录下来反复收听。在互联网上也有许多互动式的英语学习网站,大家可以在网站上用游戏、自我测试、双语阅读等方式提升英语水平。总之,勇于实践、持之以恒是学习英语的必由之路。

  信息时代已经到来,大学生在信息科学与信息技术方面的素养也已成为他们进入社会的必备基础之一。虽然不是每个大学生都需要懂得计算机原理和编程知识,但所有大学生都应能熟练地使用计算机、互联网、办公软件和搜索引擎,都应能熟练地在网上浏览信息和查找专业知识。在二十一世纪里,使用计算机和网络就像使用纸和笔一样是人人必备的基本功。不学好计算机,你就无法快捷全面地获得自己需要的知识或信息。

  最后,每个特定的专业也有它自己的基础课程。以计算机专业为例,许多大学生只热衷于学习最新的语言、技术、平台、标准和工具,因为很多公司在招聘时都会要求这些方面的基础或经验。这些新技术虽然应该学习,但计算机基础课程的学习更为重要,因为语言和平台的发展日新月异,但只要学好基础课程(如数据结构、算法、编译原理、计算机原理、数据库原理等)就可以万变不离其宗。有位同学生动地把这些基础课程比拟为计算机专业的内功,而把新的语言、技术、平台、标准和工具比拟为外功。那些只懂得追求时髦的学生最终只知道些招式的皮毛,而没有内功的积累,他们是不可能成为真正的高手的。

  虽然我一向鼓励大家追寻自己的兴趣,但在这里仍需强调,生活中有些事情即便不感兴趣也是必须要做的。例如,打好基础,学好数学、英语和计算机的使用就是这一类必须做的事情。如果你对数学、英语和计算机有兴趣,那你是幸运儿,可以享受学习的乐趣;但就算你没有兴趣,你也必须把这些基础打好。打基础是苦功夫,不愿吃苦是不能修得正果的。

  实践贯通:“做过的才真正明白”

  上高中时,许多学生会向老师提出“为什么?有什么用?”的问题,通常,老师给出的答案都是“不准问”。进入大学后,这些问题的答案应该是“不准不问”。在大学里,同学们应该懂得每一个学科的知识、理论、方法与具体的实践、应用如何结合起来,尤其是工科的学生更是如此。

  有一句关于实践的谚语是这样说的:“我听到的会忘掉,我看到的能记住,我做过的才真正明白。”

  无论学习何种专业、何种课程,如果能在学习中努力实践,做到融会贯通,我们就可以更深入地理解知识体系,可以牢牢地记住学过的知识。因此,我建议同学们多选些与实践相关的专业课。实践时,最好是几个同学合作,这样,既可经过实践理解专业知识,也可以学会如何与人合作,培养团队精神。如果有机会在老师手下做些实际的项目,或者走出校门打工,只要不影响课业,这些做法都是值得鼓励的。外出打工或做项目时,不要只看重薪酬待遇(除非生活上确实有困难),有时候,即便待遇不满意,但有许多培训和实践的机会,我们也值得一试。

  以计算机专业为例,实践经验对于软件开发来说更是必不可少的。微软公司希望应聘程序员的大学毕业生最好有十万行的编程经验。理由很简单:实践性的技术要在实践中提高。计算机归根结底是一门实践的学问,不动手是永远也学不会的。因此,最重要的不是在笔试中考高分,而是实践能力。但是,在与中国学生的交流过程中,我很惊讶地发现,中国某些学校计算机系的学生到了大三还不会编程。这些大学里的教学方法和课程的确需要更新。如果你不巧是在这样的学校中就读,那你就应该从打工、自学或上网的过程中寻求学习和实践的机会。在网上可以找到许多实践项目,例如,有一批爱好编程的学生建立了一个讨论软件技术的网站(www.diyinside.com),在其中共享他们的知识和实践经验,并成功举办了很多次活动(如在各大高校举办校园技术教育会议),还出版了帮助学生提高技术、解答疑难方面的图书,该网站有多位成员获得了“微软最有价值的专家”的称号。

  培养兴趣:开拓视野,立定志向

  孔子说:“知之者不如好之者,好之者不如乐之者。”我在“给中国学生的第三封信”中曾深入论述了快乐和兴趣是一个人成功的关键。如果你对某个领域充满激情,你就有可能在该领域中发挥自己所有的潜力,甚至为它而废寝忘食。这时候,你已经不是为了成功而学习,而是为了“享受”而学习了。在“第三封信”中,我也曾谈到我自己是如何在大学期间放弃了我不感兴趣的法律专业而进入我所热爱的计算机专业学习的。

  有些同学问我,如何像我一样能找到自己的兴趣呢?我觉得,首先要客观地评估和寻找自己的兴趣所在:不要把社会、家人或朋友认可和看重的事当作自己的爱好;不要以为有趣的事就是自己的兴趣所在,而是要亲身体验它并用自己的头脑做出判断;不要以为有兴趣的事情就可以成为自己的职业,例如,喜欢玩网络游戏并不代表你会喜欢或有能力开发网络游戏;不要以为有兴趣就意味着自己有这方面的天赋,不过,你可以尽量寻找天赋和兴趣的最佳结合点,例如,如果你对数学有天赋但又喜欢计算机专业,那么你完全可以做计算机理论方面的研究工作。

  最好的寻找兴趣点的方法是开拓自己的视野,接触众多的领域。唯有接触你才能尝试,唯有尝试你才能找到自己的最爱。而大学正是这样一个可以让你接触并尝试众多领域的独一无二的场所。因此,大学生应当更好地把握在校时间,充分利用学校的资源,通过使用图书馆资源、旁听课程、搜索网络、听讲座、打工、参加社团活动、与朋友交流、使用电子邮件和电子论坛等不同方式接触更多的领域、更多的工作类型和更多的专家学者。当年,如果我只是乖乖地到法律系上课,而不去尝试旁听计算机系的课程,我就不会去计算机中心打工,也不去找计算机系的助教切磋,就更不会发现自己对计算机的浓厚兴趣。

  通过开拓视野和接触尝试,如果你发现了自己真正的兴趣爱好,这时就可以去尝试转系的可能性、尝试课外学习、选修或旁听相关课程;你也可以去找一些打工或假期实习的机会,进一步理解相关行业的工作性质;或者,努力去考自己感兴趣专业的研究生,重新进行一次专业选择。其实,本科读什么专业并不能完全决定毕业后的工作方向,正如我所强调的那样,大学期间的学习过程培养的是你的学习能力,只要具备了这种能力,即使从事的是全新的工作,你也能在边做边学的过程中获取足够的知识和经验。

  除了“选你所爱”,大家也不妨试试“爱你所选”。有些同学后悔自己在入学时选错了专业,以至于对所学的专业缺乏兴趣,没有学习动力;有些同学则因为追寻兴趣而“走火入魔”,毕业后才发现荒废了本专业的课程;另一些同学因为在学习上遇到了困难或对本专业抱有偏见,就以兴趣为借口,不愿意面对自己的专业。这些做法都是不正确的。在大学中,转系可能并不容易,所以,大家首先应尽力试着把本专业读好,并在学习过程中逐渐培养自对本专业的兴趣。此外,一个专业里可能有很多不同的领域,也许你对专业里的某一个领域会有兴趣。现在,有很多专业发展了交叉学科,两个专业的结合往往是新的增长点。因此,只要多接触、多尝试,你也许就会碰到自己真正感兴趣的方向。“数字笔”的发明人王坚博士在微软亚洲研究院负责用户界面的研究,可是谁又能想到他从本科到博士所学的都是心理学专业,而用户界面又正是计算机和心理学专业的最佳结合点。另一方面,就算你毕业后要从事其他的行业,你依然可以把自己的专业读好,这同样能成为你在新行业中的优势。例如,有一位同学不喜欢读工科,想毕业后进入服务业发展,我就建议他先把工科读好,将来可以在服务业中以精通技术作为自己的特长。

  人生的路很长,每个人都可以有很多不同的兴趣爱好。在追寻兴趣之外,更重要的是要找寻自己终身不变的志向。有一本书的作者曾访问了几百个成功者,问他们有哪件事是他们今天已经懂得,但在年轻时却留下了遗憾的事情。在受访者的回答中,最多的一种是:“希望在年轻时就有前辈告诉我、鼓励我去追寻自己的理想和志向。”相比之下,兴趣固然关键,但志向更为重要。例如,我的志向是“使影响力最大化”,多年以来,我有许多兴趣爱好,如语音识别、对弈软件、多媒体、研究到开发的转换、管理学、满足用户的需求、演讲和写作、帮助中国学生等等,兴趣可以改变,但我的志向是始终不渝的。因此,大家不必把某种兴趣当作自己最后的目标,也不必把任何一种兴趣的发展道路完全切断,在志向的指引下,不同的兴趣完全可以平行发展,实在必要时再做出最佳的抉择。志向就像罗盘,兴趣就像风帆,两者相辅相成、缺一不可,它们可以让你驶向理想的港湾。

  积极主动:果断负责,创造机遇

  创立“开复学生网”时,我的初衷是“帮助学生帮助自己”。但让我很惊讶的是,更多的学生希望我直接帮他们做出决定,甚至仅在简短的几句自我介绍后就直接对我说:“只有你能告诉我,我该怎么做”。难道一个陌生人会比你更知道自己该怎么做吗?我慢慢认识到,这种被动的思维方式是从小在中国的教育环境中培养出来的。被动的人总是习惯性地认为他们现在的境况是他人和环境造成的,如果别人不指点,环境不改变,自己就只有消极地生活下去。持有这种态度的人,事业还没有开始,自己就已经被击败,我从来没见过这样消极的人可以取得持续的成功。

  从大学的第一天开始,你就必须从被动转向主动,你必须成为自己未来的主人,你必须积极地管理自己的学业和将来的事业,理由很简单:因为没有人比你更在乎你自己的工作与生活。“让大学生活对自己有价值”是你的责任。许多同学到了大四才开始做人生和职业规划,而一个主动的学生应该从进入大学时就开始规划自己的未来。

  积极主动的第一步是要有积极的态度。大家可以用我在“第三封信”里推荐的方法,积极规划自己的人生目标,追寻兴趣并尝试新的知识和领域。纳粹德国某集中营的一位幸存者维克托.弗兰克尔曾说过:“在任何特定的环境中,人们还有一种最后的自由,就是选择自己的态度。”

  积极主动的第二步是对自己的一切负责,勇敢面对人生。不要把不确定的或困难的事情一味搁置起来。比如说,有些同学认为英语重要,但学校不考试就不学英语;或者,有些同学觉得自己需要参加社团磨练人际关系,但是因为害羞就不积极报名。但是,我们必须认识到,不去解决也是一种解决,不做决定也是一个决定,这样的解决和决定将使你面前的机会丧失殆尽。对于这种消极、胆怯的作风,你终有一天会付出代价的。

  积极主动的第三步是要做好充分的准备:事事用心,事事尽力,不要等机遇上门;要把握住机遇,创造机遇。中国科技大学校长朱清时院士在大三时被分配到青海做铸造工人。但他不像其他同学那样放弃学习,整天打扑克、喝酒。他依然终日钻研数理化和英语。六年后,中国科学院要在青海做一个重要的项目,这时朱校长就脱颖而出,开始了他辉煌的事业。很多人可能说他运气好,被分配到缺乏人才的青海,才有这机会。但是,如果他没有努力学习,也无法抓住这个机遇。所以,做好充分的准备,当机遇来临时,你才能抓住它。

  积极主动的第四步是“以终为始”,积极地规划大学四年。任何规划都将成为你某个阶段的终点,也将成为你下一个阶段的起点,而你的志向和兴趣将为你提供方向和动力。如果不知道自己的志向和兴趣,你应该马上做一个发掘志向和兴趣的计划;如果不知道毕业后要做什么,你应该马上制定一个尝试新领域的计划;如果不知道自己最欠缺什么,你应该马上写一份简历,找你的老师、朋友打分,或自己审阅,看看哪里需要改进;如果毕业后想出国读博士,你应该想想如何让自己在申请出国前有具体的研究经验和学术论文;如果毕业后想进入某个公司工作,你应该收集该公司的招聘广告,以便和你自己的履历对比,看自己还欠缺哪些经验。只要认真制定、管理、评估和调整自己的人生规划,你就会离你自己的目标越来越近。

  掌控时间:事分轻重缓急,人应自控自觉

  除了积极主动的态度,大学生还要学会安排自己的时间,管理自己的事务。一位同学是这么描述大学生活的:

  “大学和高中相比似乎没有什么太大的区别,每天依旧是学习,每次考试后依旧是担心考试成绩……不同的只是大学里上网的时间和睡觉的时间多了很多,压力也小了很多。”

  这位同学并不明白,“时间多了很多”正是大学与高中之间巨大的差别。时间多了,就需要自己安排时间、计划时间、管理时间。

  安排时间出了做一个时间表外,更重要的是“事分轻重缓急”。在《高效能人士的七个习惯》一书中,作者史蒂芬.柯维提出,“重要事”和“紧急事”的差别是人们浪费时间的最大理由之一。因为人的惯性是先做最紧急的事,但这么做会导致一些重要的事被荒废掉。例如,我认为这篇文章里谈到的各种学习都是“重要的”,但它们不见得都是老师布置的必修课业,采纳我的建议的同学们依然会因为考试、交作业等紧急的事情而荒废了打好基础、学习做人等重要的事情。因此,每天管理时间的一种好方法是,早上确定今天要做的紧急事和重要事,睡前回顾一下,这一天有没有做到两者的平衡。

  每个人都有许多“紧急事”和“重要事”,想把每件事都做到最好是不切实际的。我建议大家把“必须做的事”和“尽量做的事”分开。必须做的事要做到最好,但尽量做的事尽力而为即可。建议大家用良好的态度和宽广的胸怀接受那些你暂时不能改变的事情,多关注那些你能够改变的事情。此外,还要注意生物钟的运行规律,按时作息,劳逸结合,这样才能在学习时有最好的状态。

  大学四年是最容易迷失方向的时期。大学生必须有自控的能力,让自己交些好朋友,学些好习惯,不要沉迷于对自己无益的习惯(如网络游戏)里。一位积极、主动的中国学生在“开复学生网”上劝告其他同学:“不要玩游戏,至少不要玩网络游戏。我所认识的专业水平比较高的大学朋友中没有一个玩网络游戏的。沉迷于网络游戏是对于现实的逃避,是不愿面对自己不足的一面。我认为,要脱离网络游戏,就得珍惜自己宝贵的大学时间,找到自己感兴趣的方向,做一些有意义并能给自己带来满足感的事情。”

  为人处事:培养友情,参与群体

  很多大学生入校时都是第一次离开父母,离开自己生长的环境。进入校园开始集体生活后,如何与同学、朋友以及社团的同事相处就成为了大学生学习内容的一部分。大学是大家最后一次可以在相对宽松的环境中学习、培养、训练如何与人相处的机会。在未来,人们在社会里、在工作中与人相处的能力会变得越来越重要,甚至超过了工作本身。所以,大学生要好好把握机会,培养自己的交流意识和团队精神。

  “人际交往能力不够强,人际圈子不够广,但又没有什么特长可以引起大家的注意,在社团里也不知道怎么和其他人有效地建立联系。”这是一些大学生在人际交往方面经常遇到的困惑。对于如何在大学期间提高人际交往能力,我的建议是:

  第一,以诚待人,以责人之心责己、以恕己之心恕人。对别人要抱着诚挚、宽容的胸襟,对自己要怀着自我批评、有过必改的态度。与人交往时,你怎样对待别人,别人也会怎样对待你。这就好比照镜子一样,你自己的表情和态度,可以从他人对你流露出的表情和态度中一览无遗。你若以诚待人,别人也会以诚待你。你若敌视别人,别人也会敌视你。最真挚的友情和最难解的仇恨都是由这种“反射”原理逐步造成的。因此,当你想修正别人时,你应该先修正自己。你想别人怎么对你,你就应该怎么对人。你想他人理解你,你就要首先理解他人。

  第二,培养真正的友情。如果能做到第一点,很多大学时的朋友就会成为你一辈子的知己。在一起求学和寻求自身发展的道路上,这样的友谊弥足珍贵。交朋友时,不要只去找与你性情相近或只会附和你的人做朋友。好朋友有很多种:乐观的朋友、智慧的朋友、脚踏实地的朋友、幽默风趣的朋友、激励你上进的朋友、提升你能力的朋友、帮你了解自己的朋友、对你说实话的朋友等等。此外,大学时谈恋爱也可以教你如何照顾别人,增进同理心和自控力,但恋爱这件事要随缘,不必为了谈恋爱而谈恋爱。

  第三,学习团队精神和沟通能力。社团是微观的社会,参与社团是步入社会前最好的磨练。在社团中,可以培养团队合作的能力和领导才能,也可以发挥你的专业特长。但更重要的是,你要做一个诚心诚意的服务者和志愿者,或在担任学生工作时主动扮演同学和老师之间沟通桥梁的角色,并以此锻炼自己的沟通能力,为同学和老师服务。这样的学习过程也不会很轻松,挫折是肯定有的,但是不要灰心,大学社团里的人际交往是一种不用“付学费”的学习,犯了错误也可以重头来过。

  第四,从周围的人身上学习。在班级里、社团中,多观察周围的同学,特别是那些你觉得交往能力和沟通能力特别强的同学,看他们是如何与人相处的。比如,看他们如何处理交往中的冲突、如何说服他人和影响他人、如发挥自己的合作和协调能力、如何表达对他人的尊重和真诚、如何表示赞许或反对,如何在不冒犯他人的情况下充分展示个性等等。通过观察和模仿,你渐渐地会发现,自己的人际交往能力会有意想不到的改进。在学校里,每一个朋友都可以成为你的良师,他们的热心、幽默、机智、博学、正直、沟通、礼貌等品德都可以成为你的学习对象。同时那些你不喜欢的人和事也可以为你敲响警钟,警告你千万不要做那样的人和事。当然,你也应当慷慨地帮助每一个朋友,试着做他们的良师和模范。

  第五,提高自身修养和人格魅力。如果觉得没有特长、没有爱好可能会成为自己人际交往能力提高的一个障碍,那么,你可以有意识地去选择和培养一些兴趣爱好。共同的兴趣和爱好也是你与朋友建立深厚感情的途径之一。很多在事业上有所建树的人都不是只会闭门苦读的书呆子,他们大多都有自己的兴趣和爱好。我在微软亚洲研究院的同事中就有绘画、桥牌和体育运动方面的高手。业余爱好不仅是人际交往的一种方式,还可以让大家发掘出自己在读书以外的潜能。例如,体育锻炼既可以发挥你的运动潜能,也可以培养你的团队合作精神。如果真的没有什么兴趣爱好,那么,多读些好书丰富自己的知识也可以改进自己的人际交往能力,因为没有什么比智慧和渊博更能体现一个人的人格魅力了。

  所以,学会与人相处,这也是大学中的一门“必修课”。

  对大学生们的期望

  踏入大学校门时,你还是一个忙碌的、青涩的、被动的、为分数读书的、被家庭保护着的中学毕业生。

  就读大学时,你应当掌握七项学习,学好自修之道、基础知识、实践贯通、兴趣培养、积极主动、掌控时间、为人处事。

  经过大学四年,你会从思考中确立自我,从学习中寻求真理,从独立中体验自主,从计划中把握时间,从交流中锻炼表达,从交友中品味成熟,从实践中赢得价值,从兴趣中攫取快乐,从追求中获得力量。

  离开大学时,只要做到了这些,你最大的收获将是“对什么都可以拥有的自信和渴望”。你就能成为一个有潜力、有思想、有价值、有前途的中国未来的主人翁。

  所以,我认为大学四年应是这样度过。

 李开复博士:现任微软公司全球副总裁,是微软亚洲研究院的首任院长。在学术领域,他是攻坚挫锐的科研天才;在管理层面,他又是运筹帷幄的领军人物。作为一位天资卓越的华裔学者,他正在创造着一个又一个奇迹。同时,李开复博士极为关注中国教育,先后给中国学生写了三封饱含关切之情的来信,在国内青年学生中产生了巨大影响。他还为中国学生开通了“开复学生网(www.kaifulee.com)”。近日,李开复博士又撰写了“给中国大学生的第四封信”,通过具有广泛影响力的《中国青年报》、《中国青年》杂志、《大学生》杂志、《文汇读书周报》等媒体刊登。

- 作者: hsee 2005年06月22日, 星期三 03:33  回复(5) |  引用(1) 加入博采

一个程序员的奋斗历程

声明:此文为作者以第一人称自述,由lxceo转载. 来自:blogchina

这些日子我一直在写一个实时操作系统内核,已有小成了,等写完我会全部公开,希望能够为国内IT的发展尽自己一份微薄的力量。最近看到很多学生朋友和我当年一样没有方向 ,所以把我的经历写出来与大家共勉,希望能给刚如行的朋友们一点点帮助。 一转眼我在IT行业学习工作已经七年多了,这期间我做过网页,写过MIS、数据库,应用程序,做过通信软件、硬件驱动、协议栈,到现在做操作系统内核和IC相关开发,这中间走了很多弯路,也吃了不少苦。



我上的是一个三流的高校,就连同一个城市的人多数都不知道。因为学校不好也就没有指望能靠学校名气找一个好工作。所有的希望都寄托在自己的努力上了,大一开学前的假期我就开始了学习,记得我买的第一本书是《计算机基础DOS3.0》,大家别吓着了,其实当时已经普及了DOS6.22了,只是我在书店里看到了DOS4.0,5.0,6.0的书,以为像英语那样是第四、五、六册,记得当时到处找DOS1.0,现在想想也幸好我没有找到:)开学前我学完了PASCAL,那时既没有计算机也没有人可以请教,我连程序是什么的概念都没有, 只好死记硬背代码,然后拿纸写,我一直到大三才有了一台486,在这之前用纸写了多少程序我也记不清楚了,只知道最长的一个我拿A4大小的草稿纸写了30多页,我的C语言、C++ 、VC都是在这样的条件下入门的。所以说条件是可以克服的,希望我的经历多少给条件艰苦的同学们一点信心。第一次上机是在我姐夫的机房,我的心情激动的无与伦比,但是一上机我立刻傻了眼,他们用的是英文版的Win3.1,我的那点DOS知识都见了鬼,上机提心吊胆的一阵瞎摸,一不小心把Word弄成了全屏,怎么都还不了原,当时真是心急如焚,我以为机器被我弄坏了。第一个C语言程序,就是那个经典的HelloWorld,我调了几个星期,上机机会非常少,也没有书告诉我开发环境(TC2.0)需要设置,而且开始我都不知道有编译器,我甚至自作聪明把写好的程序扩展名从.c改成.exe,结果可想而知。大一学完了C、X86的汇编、数据结构、C++。由于精力都花在自学上了,大一下四门课挂了彩,三类学校就是这点好,挂上一二十门也照样毕业。不过扯远点说,我那么刻苦都及不了格,可见我们国家的计算机教育有多死板。

  大二准备学VC和BC,当时难以取舍,后来选了VC,不为别的,只为书店里两本书,VC 那本便宜6块钱。我的努力在班上无人能及,学的日夜不分,大三有了计算机后更是如此, 很多次父亲半夜教训我说我不要命了,我一直觉得自己基础差,记忆又不行,条件也不好 ,所以觉得只有多花点时间才能赶上别人。居然后来有许多朋友说我有学计算机的天赋, 让我哭笑不得。我用的是486,16M内存,1G硬盘,当时同学们的配置都是P166MMX,我安装 一个Windows NT4.0需要一个通宵,编译一个BC5.0向导生成的程序需要近两个小时,我的显示器是个二手的,辐射非常大,开机屏幕冒火花,看起来很酷的:),有一次程序写的太久,觉得怎么白色的编辑器背景变成了紫色,以为显示器坏了,后来才发现眼睛不行了,不过说来也奇怪,到今天我的视力还能保持1.5,真是个奇迹。但是就是那台破机器陪伴了我两年,让我学会了VC、Delphi、SQLServer等。后来那台机器给我阿姨打字用,据她说一天她正打的开心,一股青烟夹着火苗从显示器钻出来,之后它才寿终正寝。

大三假期找了个机会在一个计算机研究所实习,与其说实习不如说是做义工,工作了两个月一分钱没有拿。但是这两个月对我的发展帮助很大,让我早一步了解了社会,刚去的时候我当然是一窍不通,在那里我熟悉了网络,学会了Delphi和Oracle。由于工作很认真, 得到了比较好的评价,在一位长者的引荐下,我开始和他们一起做项目,这使我在大三大四就有了自己的收入,大四又找了两家MIS公司兼职,虽然钱不多,但是在学生期间有1000多的收入我已经非常满足了,我终于用自己赚的钱把计算机换了。大四下开始找工作,这时我的工作经验已经比较多(当然现在想想非常幼稚),开始听父母的想去那个研究所, 实习过那个部门也希望我能去,但是不知道为什么最后不了了之,这种单位就是比较官僚 ,我一气之下就到了我兼职的一个公司做MIS的TeamLeader。在大三到毕业一年的时间,做过了各种MIS,从煤气、烟厂、公安、铁路、饮食到高校,什么有钱做什么,工作也很辛苦 ,经常加班和熬通宵,从跟客户谈需求到设计、编码、测试、交付都要上。那时觉得很有成就感,觉得自己还不错,现在想想真是很肤浅。

  刚走上工作岗位的学生很容易被误导,各种开发工具让人眼花缭乱,同时也觉得很受 公司器重,但这样工作永远是一个低层次的开发者。不要跟我说什么系统分析有多么多么重要,多么多么难。你以为自己跟用户谈需求做设计就是系统分析和设计了吗,国内又有几个公司能够做的很到位很规范?我是ISO9000内审员,也在Rational公司受过多次培训,拿了4个证书,还有一个公司让我去做CMM。这些我听过很多,但是很多事情到国内就变了性质,一个公司不是通过了ISO9000或者CMM就能规范了,我现在在一家有几十年历史的外企工作,里面的管理不是一般国内企业能及的。作为一个毕业不久以前没有步入过社会的学生,几乎不可能在很短的时间掌握系统分析和设计,面向对象、UML只是一个工具,关键是人本身的思想,不是说你熟悉了C+、Rose就能够做出好的设计,相反如果你具备了很高的素质,你可以用C写出比别人用C++更加模块化的程序。

话说远一些,国内软件开发行业有一个怪圈,很多人觉得VC > Delphi > VB,真是很搞笑。这几个软件我都做过开发,说白了他们都是工具,应该根据应用的需要选择采用哪个,而不是觉得哪个上层次。如果你因为用某个开发工具很有面子而选择的话,只能说明你很浅薄。如果说层次,那么这些工具都不上层次,因为它们用来用去都是一些系统的API,微软的朋友不会因为你记住他们多少个API或者多少个类就会觉得你很了不起,你永远只是他们的客户,他们看重的是你口袋里的银子。我也做过系统内核,我也封装过很多API,同样我也不会看重那些使用这些API做二次开发的客户,除非他能够作出自己独到的设计。

  至于有人认为C++ > C那更是让人笑掉大牙,不妨你去打听一下,现在有几个操作系统内核是用C++写的,又有几个实时系统用的是C++,当然我也不是说C++不好,但是目前的内核和实时系统中C++还无法与C匹敌,至于说C++适合做应用系统的开发那是另外一回事。所以我的观点是不在于你用什么工具和语言,而在于你干什么工作。你的设计体现了你的技术层次。

  这样干了一年我觉得非常苦闷,做的大多数都是熟练工种的活,个人技术上没有太多 的提高也看不到方向。所以决定离开这个城市去上海,寻求更好的发展,并且打算放弃我以前的MIS转到通信行业。

写到这里不能不提到我女朋友,我们是在来上海前半年认识的,她大四在我公司实习,公司派她给我写文档,我们的感情发展的很快。她告诉我很多事情,她家原本是改革开放的第一批暴发户,她母亲爱打牌,输掉了几百万,还欠了很多债,她有男朋友,但是她对他没有感情,只因为他给了她母亲两万多块钱,后来还强迫她写了四万块的借条,她男朋友背叛过她并且不止一次打她,现在逼她结婚不然就要她还钱。这人居然还是一个高校的老师!她母亲把父亲给她的学费花了,因为拖欠学费她没有办法拿到毕业证。她母亲现在有病需要钱,我拿出了自己的一点积蓄并且跟朋友们接了一些,替她交了学费并给她母亲看 病(后来才知道看病的钱又不知所终,就连她母亲是不是有病我都不知道,但她也是没有办法)。这个时候我家知道了一些事情,坚决反对我和她在一起,她原来的男朋友也极力破坏。无奈之下我们决定早一定离开这个伤心的城市,并且瞒着我们家。由于时间仓促,我只准备了4000块钱,她仅有的几百块钱也被她母亲要去了,我买了三张票,一张是中午的,两张是晚上的,中午我的家人把我送上船,他们一离开我就下了船,我和她乘坐晚上的船离开了这个我和她生活了很多年的城市,带走的只是一身债务。没有来过上海的我们两个性倔强,都不愿意去麻烦同学和朋友。来到上海是傍晚6点半,我们都不知道该去哪里,我们找了一个20块钱的旅馆,这个房间连窗户都没有,7月份的天气酷热难耐,房间里非常闷热。第二天我们开始租房子,因为身上的钱不多,我们基本都是步行,花了一个星期时间,不知道在浦东转了多少圈后找到了一个400块的房子,但是我们都不了解上海是付三压一,还要付半个月的中介费,买了一些锅碗瓢盆后,我们身上只有800块钱了,工作都还没有着落,这800块钱要支持到我们拿到第一个月工资,为了省钱我们自己做饭,每天买菜只花两块钱,她非常喜欢吃(也可能她在大学经常挨饿的愿意),看到她现在这样省吃俭用我真的很不忍心。她以前的男朋友也没有放过她,经常打电话来骚扰,并且来上海看她,还说了不少恐吓她的话,她过于善良,说他以前毕竟帮助过她,叫我不要与他一般见识。以后的每天在家就是苦等面试通知,原本我想迅速找一家MIS公司解决眼前的困难,但是她坚持让我不要放弃自己的理想,终于功夫不负有心人,我找到了一家通信公司,4000块的工资虽然赶不上MIS公司给我开出的价位,但也够在上海生存。她也找到了工作,第一天上班她哭了,这是她来上海第一次流泪,我心里很难受也很感动。

  由于是全新的行业,我把自己降到了零点,我学的VC、Delphi、数据库派不上用场, 摆在我面前的是嵌入式、协议、信令一些我从未接触过的知识。我知道我没有退路,于是拼命的学习,我把自己当做一个应届毕业生一样,一分努力一分收获,半年过去我终于熟悉了工作,并且得到了公司的表彰,薪水也加了一级。后面的日子里我们省吃俭用,把欠朋友的1万多块钱还了,日子终于上了正轨。这时女朋友告诉我她想考研究生,我也很支持,于是她辞职在家备考。

  另外,在这里我要感谢我的ProjectManager,他原来是一个大通信公司的产品经理, 对人非常和善,我从他那里学到了很多知识,而且他也给了我许许多多无私的帮助。在工作上他给我充分的空间和信任。记得公司安排我维护一个接入服务器软件,由于代码量不算太小(5万行),资料和文档都不齐全,我维护起来非常吃力,所以想重新把它做一遍, 公司领导不太支持,可能觉得工作量太大,但是他极力支持我,私下里他让我放手去做, 我的维护工作他挤时间做。在他的支持下,我花了半年时间完成了接入服务器的软件,并且实现了一个相对完整的TCP/IP协议栈。在这里我学会了嵌入式系统设计、驱动开发、TCP/IP和很多通信的知识,我花了一年时间终于使自己从MIS开发转到了通信行业,并且站稳了脚跟。我的开发大量是对硬件的直接操作,不再受微软的操作系统,VC、Delhpi这些开发工具的约束,我终于看到了另外一片天空。

  我做事情喜欢追根问底,随着开发的深入,软件开发与硬件联系越来越紧密,硬件知 识的匮乏又对我的发展产生了障碍,而且芯片技术基本上掌握在国外公司的手里,这对做系统级设计是一个非常大的制约,一个新产品出来,第一道利润(也往往是最丰厚的利润)常常都被IC公司如Intel、Motorola赚去了,国内的厂商只能喝点汤。所以我决心解决自己的硬件技术障碍,并打算离开通信行业,进入IC设计相关领域。

当然我明白如果我对硬件了解的非常少,没有哪家IC公司会仁慈到招我这样一个一窍不通的人来培训。所以我必须努力打好基础,学一些相关知识为以后做准备。就像我开始从MIS转到通信一样,我看过大量通信方面的书,并且给一个ISP做过RADIUS计费分拣台,在这样的背景下这家通信公司才给了我这个机会。我在的通信公司是做系统设计的,有不少PCB Layout硬件人员,平常我就注意向他们学习,由于我做的是软件,在公司看硬件资料不好意思,所以开始只好在家看,刚来上海工作我连续一年都在加班,后来不加了,因为我要挤出时间学习,通常我12点左右睡,第二天5点半起,我上班比较早,地铁上如果人不多我也用来看书。学习当然不会是一帆风顺的,有些实在不懂的问题就积累起来问硬件人员,他们的帮助使我学习进度快了很多,因为在没有人点拨的情况下自学,我的一半时间是花在解决疑难问题上,但这种问题经常是别人的一句话就可以让我豁然开朗,我非常庆幸我有这样的学习环境。在后面的一年里,我学会了看硬件原理图,学会了简单的硬件设计(模拟电路方面还有不小的差距),事情就是这样的,当你安安份份做软件,别人永远认为你是软件开发人员,在你开始学习硬件时别人未必会认同,有位中兴通讯的朋友还对我说过,一个人不可能把所有东西都学完。我也明白这一点,但我希望自己做的更好。但当你熟悉硬件后大家又会觉得你好像原本就是软硬件都懂的,同事们也都习以为常了。这个时候我可以把硬件资料堂堂正正的拿到公司看,没有人再大惊小怪了。 让我比较自豪的是我通过自己的努力做了一个IAD(软交换的终端设备)系统方案,包含软硬件的选型、设计等内容,这个方案得到了公司和同事们的认同,让我感到非常欣慰。
技术是相辅相成的,当我的硬件有了一定的进步后,我的软件设计也有了很大的提高 ,我可以从更深层次理解问题,我做的接入服务器CPU是Motorola PowerPC860,熟悉的朋友都知道860 QMC与软件的批量数据传输通常采用BD表的方式,硬件人员做驱动的时候习惯采用固定BD表,每接收或发送数据都将数据从BD表拷贝到用户Buffer,或从用户Buffer拷贝到BD表,由于理解的比较深入,我自己重新实现了这个过程,采用动态BD表的方式,驱动从一个网口接收数据,提交给我的软件进行三层交换,直至从另外的接口发送出去,没有进行一次拷贝。这样的设计大大提高了性能,使系统的指标接近理论值。软硬件的结合使我的设计水平上了一个台阶。我现在写的这个操作系统,编译后我把程序反编译成汇编,找出其中不优化的代码,然后在C程序中进行调整。举个例子,很多CPU没有专门的乘法指令,这个大家应该都知道,在这种CPU上进行一个乘法操作常常会花费大量的指令周期, 有的朋友会说这个我知道,我会尽量避免采用×号,但是事情往往不是那么简单,你知道C语言中数组的下标操作是怎么实现的吗?仔细看看反汇编的代码你就会明白,同样是通过下标的定位操作,C编译器会有时候会产生位移指令,但有时候会用乘法实现,两者效率往往是天壤之别,所以明白这些问题你才能将系统性能提升到极致。一些问题就不多说了,有兴趣的话以后可以共同探讨。

  话说远一点,我由衷的希望在软件上做的比较深入的朋友们有机会学学硬件以及其它 相关知识,尤其是做底层开发和嵌入式设计的。这对软件技术的提高有非常大的帮助,否则很多事情你只知道该这样但不会明白为什么该这样。我这个观点在我现在的IC公司Project Manager那里也得到了验证。他告诉我们公司现在的802.11芯片产品的软件经理原本是做该芯片硬件设计的,某某某原本是做软件的,现在在做IC,类似的例子还有很多,只是在国内这样的风气不是非常流行。

  我有一些心得体会与大家分享,只有当我干好本职工作后,我才会学习与工作关系不 大的技术,这样公司的上司才不至于反感,在入门阶段的问题我通常不去问那些资深人士 ,而是问一些资历比较浅的朋友,比如刚毕业不久的学生,因为他们往往会跟你详细的讲解,而资深人士通常觉得你的问题太简单,所以回答的也很简单,我又不好意思多问。等技术上了一定的层次后我才会问他们,他们也能给你比较深入的回答。另外,有些朋友说我机会比较好,他们也希望能从事新的工作惜没有机会,我听了只有苦笑,我的机会了解的人都应该知道,我没有出生在什么IT世家:)也没有谁一路提拔我,所有的路都是自己走出来的,我母亲去世比较早,我的后母(我叫她阿姨)看着我努力过来的,一次她看我大年30还在写程序,她说像我这样努力木头都能学出来。

  我的最终目的是IC而不是PCB,所以我下一步的准备开始学习IC设计的知识。公司的同事没有懂IC设计的,后面的路又要靠自己了,我买了不少相关的书,在网上也查了很多的资料,我花了大量的时间去学习VHDL,并且用软件进行了一些简单的设计和仿真(没有设计ASIC,只是针对FPGA),随着学习的深入,我渐渐明白了IC设计的基本流程,同时也明白了这条路的艰辛。这个时候我已经做好了跳槽的准备,我向一家业界又一定知名度的IC设计公司投了简历,并通过了漫长的面试(4个多小时)。其他的一切我都比较满意,唯独薪资差强人意,我也明白原因,因为我是这个行业的新人,我没有经验,我再一次将自己清零了。公司老板问我6000多一个月能不能接受,我知道他也是照章办事。想想我通信行业的朋友们,基本上都是年薪10万以上,月薪过万的也比比皆是,朋友们也帮我介绍了不少待遇不错的公司,我该怎么选择,当时我很犹豫,我热爱我的事业,我向往我的追求, 但我也是一个普通的人,我也需要养家糊口,我也想早一点买房买车。生活给我出了一道难题。

  爱因斯坦在63岁时说过“一个人没有在30岁以前达成科学上的最大成就,那他永远都不会有。”这句话给了我很大的压力和震动,我马上就26岁了,离30只有四年时间,我必须抓紧这几年宝贵的时间,努力达到我技术上的最高峰。为了这个理想,为了能离自己的梦更近一些,我选择了这家IC公司,我明白自己的薪资和公司刚进来的硕士研究生相差无几, 但为了今后的发展只能忍受,一切又得重新开始。换行业是一个非常痛苦的过程,尤其从一个春风得意的位置换到一个陌生的岗位,感觉象从温暖的被子里钻出来跳进冰水中,让人难以接受。在原来那家通信公司,我是唯一两年时间涨了五次工资的员工,公司和同事都给了我极大的认可,工作上也常常被委以重任。但现在这一切都成了过去,在新的公司我只是一个新人,没有人知道也没有人在意我过去的成绩。我决定重新开始,我把自己看作新毕业的学生,我要用自己的努力得到公司的认可。进入新的行业是非常痛苦的,我告诉自己必须忍受这一切,虽然外面有很多诱惑,但是既然作出了选择我就不允许自己轻易放弃。

我现在已经在这家新公司上了一个多月的班,开始非常艰难,现在慢慢适应了。第一 个月结束时,Team Leader找我谈话,说我是新进员工中最优秀的一个,我心里很欣慰,这也算对我努力的一个肯定吧。在这里还要感谢我的女朋友,她给了我很大的支持和鼓舞, 每次在我动摇的时候她都在鼓励我,让我坚持自己的理想,刚来上海是她让我不要勉强去做MIS,这次也是她让我顶住了月薪过万的诱惑,没有她我可能不会有今天的成绩。 现在的公司有自己的操作系统,自己的CPU、DSP和其它芯片,在这里我能学到世界上最先进的技术,我们的设计开发不再完全依赖别人的硬件和系统,这让我很开心。我打算等工作步入正轨后,全力学习新的知识,实现我的理想。

  在后面的两年里我给自己定下了几个目标:

  一.努力做好本职工作,在工作上得到公司和同事们的认同;

  二.努力学习IC硬件设计知识,多向同事请教,并利用一切机会多实践;

  三.实现我的实时操作系统的主要部分,完成TCP/IP协议栈模块,并免费发布源代码;

  四.和我女朋友结婚并买一套小房子,这是最重要的,因为我明白事业是可以重来的,但是珍贵的感情很难失而复得。

  在这里提一下我现在开发的操作系统,它是一个实时嵌入式系统,目前支持以下特性:

  a.支持时间片轮转调度和基于优先级调度,最多64个优先级;

  b.抢占式实时内核;

  c.为了便于移植,主体用标准C实现;

  d.汇编代码非常少,不到100行;

  e.支持任务管理,各任务有独立的堆栈;

  f. 进程同步和通信目前完成了Semaphore,Message Queue正在调试;

  g.实现了定时系统调用;

  h.可以在windows上仿真调试

  我还打算下一步实现优先级反转保护,Event Flag,Data Pipe,内存管理(以前实现过)、驱动接口等。 在这之后我还会努力完善它,比如加入文件系统,协议栈、调试接口等。希望朋友们提出自己的意见和建议,在此不胜感激!

  后记:

  就像有的朋友说的,我的经历或许会给一些朋友产生误导,在这里我必须说明一下。 我来上海以前学习过于拼命,常常晚上只睡3个多小时,我身高1米71,那时只有108斤(我现在130多),家人也说我这样拼命活不过60岁,但是当时的我太固执,我对他们说只要能实现理想活50岁我就够了。那时的拼命使我的身体受到了影响,有一次早上突然腰肌剧痛难忍,痛的我倒在床上站不起来。虽然我现在已经比较注意,但有时候还会隐隐作痛。后来在女朋友说服了我,来上海以后我不再如此。我经常引用父亲的一句话“身体是革命的本钱”。

  而且我也发现拼命不是办法,我可以熬一两个通宵,最多的一次我连续工作了三天三夜, 但是我半个月都没有恢复过来,这样是不是得不偿失?学习工作应该是一个长期的过程, 像马拉松而不是百米冲刺。我现在非常注意调整学习和工作的强度,我要保证每天尽量有相对充沛的精力,一些年轻的朋友觉得自己也应该拼命努力,这让我多少有些担心,如果我的故事能让你在学习工作上多一点兴趣,我会感到很开心,但如果误导了某些朋友,让你做一些不值得的付出,我会感到很内疚。

  技术没有贵贱之分,我以前换行业是因为自己的兴趣所致,而不是对哪个行业有什么偏见。我希望我的经历不要给朋友一个错误的导向,觉得我始终向更高的技术发展。其实各行各业做到顶尖都是很困难的。话又说回来虽然技术没有贵贱,但是门槛是有高低的, 无论如何,做IC的门槛要比做网页的高,这一点无可否认。国家各种人才都是需要的,但是作为个人奋发向上的想法还是应该有的,努力在自己喜欢的行业上做的更好,而不应该停留在比较肤浅的层次上。
我是一个自己觉得比较有自知之明的人,或许我最大的优点就是知道自己有很多缺点 :)。我的故事中很多的曲折和错误都是由我的缺点造成的,希望大家用审慎的眼光看待我的经历,不要被我的“花言巧语”所迷惑。我学习有些随心所欲,这给我带来了无尽的麻烦,也大大阻碍的我的发展。记得我小时候成绩比较出色,但是后来学习严重偏科,导致我中学成绩一再滑坡,也没有考上什么好的学校,小时候的一个朋友,当时的成绩和我相仿,但是没有我这个缺点,她上了清华,后来在去了美国深造,在一个著名导师手下研究理论科学,这未尝不是一条更好的出路。另外我的学习方法也是在不断改善中的,过去 的学习过于讲究数量和时间,那样学习既苦而已效率不高,现在我非常注意学习的效率和技巧,这样才是学习的捷径(当然不是指投机取巧),比如说学一相对陌生的技术,如果有条件,不妨问一问有经验的人,不需要问很多,往往他不经意的几句话会给你非常大的帮助,甚至超过你看一个星期的书。带着这样的思想再去学习你会节省很多时间,这样何乐不为呢?这些年中我学了不少的东西,由于开始非常盲目,所以学的东西杂乱无章,现在回想起来让我啼笑皆非,我把大量的时间浪费在一些没有必要深入了解的知识上,毕竟一个人的精力是有限度的。很多朋友很我一样都背过五笔字形,的确它是个不错的输入法,但是对一个研发人员它绝对不值得你去背,你的时间应该花在有价值的地方。我这样的事情还做过很多,我背过CCED、WPS的命令和快捷键,在dBase基本退出历史舞台后我还花了很多时间去学习它的使用。所以我的学习在前期缺乏规划,没有明确的短期目的、中期目标,只有一个虚无飘渺的长期的理想。这就像做设计一样,好的设计是从需求抽象到代码有很多过程,而不能得到了需求就立刻开始开始编码。

当然这么些年的学习和工作多多少少有些收获,下面我说说我的一些学习的心得,这 些方法未必正确,我也在不断探索和改进中。我的学习和工作有相对明确的目标,我不会一时心动而去学习某一技术,在下决定之前我会考虑很多,包括长期的发展,个人路线的规划,需要付出的代价、可能遇到的困难及解决的办法等等,在决定后还会制定更加明确的计划,包括短期、中期和长期的,身边可以利用到的资源(包括好的书籍、资料、软硬件环境,也包括有经验的朋友或者师长),以及每一个阶段是怎么过渡到高一阶段的计划,往往在一个学习阶段一旦上路后会走的相对顺利,但是跨阶段通常比较麻烦,比如从学习基础知识转到实践。另外我买书也有自己的方法,现在世面上高质量的书远不如低质量书多,对于一个陌生的技术,往往在第一次买书会选择错误,即使买到一本好书但是它的方向也未必适合你,所以我通常会先在网上查找一些该技术的介绍,有了一点点概念后再去买一本比较薄、相对便宜并且内容相对泛泛而谈的书,这是国内作者最善于写的书:) ,再把它浏览一遍后我就会基本明白这门技术的要点,后面买书和制定计划就会明确的多。否则一开始就想找本好书往往比较困难,而且买回来后努力学习,有时候学了一半才发现是本低质量的书或者是相对过时技术,让人非常懊恼。另外让有经验的人帮你介绍,通常也是一个不错的选择。

  有些朋友想学通信、嵌入式开发,但总觉得自己没有软硬件环境,我就按我的了解给 大家介绍一下怎么建立这样的环境,当然我了解的只是我学习和工作的方向。通信我做的是数据网方面的工作,包括TCP/IP、二三层交换、对接入网、H.323和软交换也有一点认识。这些软硬件环境都是可以在PC上构建的。你甚至可以在一个没有网卡的PC上建立一个包含多个路由器、接入服务器、VoIP网关、网守、主机等的仿真网络环境,而且与实际的网络相当接近,当然这需要你有清晰的网络概念和一定的网络知识我一直在努力开发一套软件将这个过程简化,目前试验已经做完,我可能会将它融入我的操作系统外围扩展软件中。这样的方法我无法用简单的语句讲的很清楚,我可以说一下大概的思想,就是在PC上实现仿真网卡,(知道Windows怎么在没有网卡的机器实现虚拟网卡技术的朋友都应该会明白),然后每一个仿真网卡对应一个虚拟设备,如路由器或者主机。你也可以借助第三方工具完成部分工作,如VmWare等。我现在就是利用一个仿真网卡做自己的开发的。

  至于嵌入式开发环境更加容易实现,PC就是一个非常大的硬件平台,现有的嵌入式操 作系统通常都支持X86,你可以在上面做开发,通过软盘Boot或者使用虚拟机装载,我用VxWorks做了试验,在一台PC上跑Windows和VxWorks两个系统。另外Windows上的兼容DOS的16位仿真X86环境也为很多操作系统提供了绝佳的试验环境,我的操作系统在Windows上就是这样实现的。Linux在嵌入式中应用也比较广泛,它在网上有大量的资料,而且也相对比较容易实践。同时很多完善的嵌入式开发环境支持软件仿真,如Tornado、WinCE等。

一个程序员的奋斗历程(续)
前段时间处理了很多事情,一直没有写下去,花光了所有的积蓄买了一套房子,同时把户口的事情也基本办完了,这几天稍微缓口气。昨天跟我的一个老上司见面聊了半天,心里感慨万千。他从外在条件看让不少外人羡慕,二十多岁做过到了863项目的负责人,博士毕业的爱人单位也非常好。现在三十出头的他在一个通信公司做产品经理,工资虽然不算高但也有一两万,而且还持有股份。但是我们了解的人才理解他的艰辛。“白领”这个词在一些人看来是仿佛是一个动人的光环,但是在我看来是一个无奈的名字,每天行走在大街上,来来往往的车流中有多少是“白领”的?又有几个“白领”住的起高档的住宅?在上海一套别墅300万不足为奇,按揭贷款下来总额接近600万,年薪二十万在上海算是一个中高级“白领”,高额的税金去掉了你百分之几十的收入后,这样算下来不吃不喝也要四十多年,加上生活的其他开支,注定了你与这样的住宅无缘。看着外面一套套别墅,一辆辆好车,我不知道它们是谁的,但我知道其中没有什么白领。我觉得自己很渺小,在这个喧闹的都市中我如同一只蚂蚁,但我有不甘于平凡,我不愿做一个单纯的“白领”。

  其实很多朋友并不了解我,我不是一个追逐时尚技术的人,我只是不愿意做一个所谓的“白领”,更加不愿意做一个单纯的“程序员”。我不甘愿平凡的生活一辈子。我在不断的努力,我的方向非常明确,我要做多数人不做和做不到的事情,很多朋友对我这样频繁的换方向不理解,觉得一个人只要熟悉一种技术就可以了,对于这样的看法我只能说你浅薄,现在的大的系统和产品往往都是软件、硬件和应用相结合的,我要做的不是哪个方面的专家,而是希望能够成为系统设计师。我不相信一个只精通发动机的专家能够设计一辆好车,同样我也不相信对硬件一窍不通的人能做出一个操作系统,或者一个对财会没有一点概念的人能设计出一个优秀的财务软件。在工作中我发现社会上非常缺乏边缘人才,尤其是在国内。在国外一个人软硬件兼修非常普遍。如果设计产品的人只了解他的专业那么是很难有出色的设计。所以我必须趁着自己年轻学的更加广泛一些,这样才能提高自己的综合素质,这也是为什么高校那么多非专业课程。学习工作了这些年,实际上都没有脱离IT这个行业,我现在的公司开发一个系统时,先是将最终功能列举清楚并分析可行性,然后划分哪些是用芯片实现,哪些是用硬件电路实现,哪些是用软件实现,这样的设计才能做出最好的系统。如果一个设计者单纯只懂一个方面是不可能做到这一点的。

  自负常常伴随着无知,记得我大学毕业时,论文答辩会上我和专家组组长争起来了,因为我对自己的设计非常得意,而他虽然是鸡蛋里挑骨头,但是由于知识非常有限,我无法回答他的问题,所以有些“恼羞成怒”。我原来一直喜欢用“所谓”最好的开发工具,记得做过一个愚蠢的设计,一个排课表的软件我用VC+Oracle开发。这些经历我牢记在心,时刻提醒自己学会谦虚。我的亲身经历加上我对一些身边朋友的观察发现这样一个现象。当一个人只会他认为最好的技术,而对其他的一无所知,这样的人经常是目空一切。
从第一个“Hello World”到今天的操作系统,前前后后写了很多代码,从这中间我也积累了很多心得。由于我是在没有人指导的情况下自学编码的,所以走了很多弯路,也犯了不少错误。最初我写程序全凭自己的感觉,写一个新程序对结构设计不很重视,以为学好语言,数据结构就可以写出好的程序,其实远不是这样的。没有设计的情况下,也可以写,但是程序无法写的很大、很复杂。我个人的经验是这样的系统超过8000行我就无法控制了,以前我用VC写过一个Windows下的应用程序,大概8000行左右我对它失去了控制,整个代码一团糟,这8000行倒是可以相对稳定的运行,但是我没有能力再增加什么新的代码,动辄前后冲突,要么就是新代码与旧设计格格不入,需要调整旧的程序。最开始我写程序喜欢追求代码的精巧,别人很多行写出来的代码自己只写很少就可以实现,感觉那样比较酷。其实这样也是非常错误的,我现在写程序非常注重结构设计,为了结构清晰我愿意牺牲一点效率。

  下面一段话是我写程序的座右铭,希望与大家共勉:

  Make it right before you make it faster.
  Keep it right when you make it faster.
  Make it clear before you make it faster.
  Do not sacrifice clarity for small gains in efficiency.
  Brian Kernighan

  另外补充一点:我和我的女朋友现在非常好,双方的家人都认可了,我们决定在近期结婚。

- 作者: hsee 2005年06月22日, 星期三 02:23  回复(27) |  引用(1) 加入博采

怎样成为优秀的软件模型设计者?
作者:Scott Ambler著,乐林峰 译 本文选自:www.umlchina.com

我们期待自己成为一个优秀的软件模型设计者,但是,要怎样做,又从哪里开始呢? 将下列原则应用到你的软件工程中,你会获得立杆见影的成果。


1. 人远比技术重要

你开发软件是为了供别人使用,没有人使用的软件只是没有意义的数据的集合而已。许多在软件方面很有成就的行家在他们事业的初期却表现平平,因为他们那时侯将主要精力都集中在技术上。显然,构件(components),EJB(Enterprise Java Beans)和代理(agent)是很有趣的东西。但是对于用户来说,如果你设计的软件很难使用或者不能满足他们的需求,后台用再好的技术也于事无补。多花点时间到软件需求和设计一个使用户能很容易理解的界面上。

2. 理解你要实现的东西

好的软件设计人员把大多数时间花费在建立系统模型上,偶尔写一些源代码,但那只不过是为了验证设计过程中所遇到的问题。这将使他们的设计方案更加可行。

3. 谦虚是必须的品格

你不可能知道一切,你甚至要很努力才能获得足够用的知识。软件开发是一项复杂而艰巨的工作,因为软件开发所用到的工具和技术是在不断更新的。而且,一个人也不可能了解软件开发的所有过程。在日常生活中你每天接触到的新鲜事物可能不会太多。但是对于从事软件开发的人来说,每天可以学习很多新东西(如果愿意的话)。

4. 需求就是需求

如果你没有任何需求,你就不要动手开发任何软件。成功的软件取决于时间(在用户要求的时间内完成)、预算和是否满足用户的需求。如果你不能确切知道用户需要的是什么,或者软件的需求定义,那么你的工程注定会失败。

5. 需求其实很少改变,改变的是你对需求的理解

Object ToolSmiths公司(www.objecttoolsmiths.com)的Doug Smith常喜欢说:“分析是一门科学,设计是一门艺术”。他的意思是说在众多的“正确”分析模型中只存在一个最“正确”分析模型可以完全满足解决某个具体问题的需要(我理解的意思是需求分析需要一丝不苟、精确的完成,而设计的时候反而可以发挥创造力和想象力 - 译者注)。

如果需求经常改动,很可能是你没有作好需求分析,并不是需求真的改变了。

你可以抱怨用户不能告诉你他们想得到什么,但是不要忘记,收集需求信息是你工作。

你可以说是新来的开发人员把事情搞得一团糟,但是,你应该确定在工程的第一天就告诉他们应该做什么和怎样去做。

如果你觉得公司不让你与用户充分接触,那只能说明公司的管理层并不是真正支持你的项目。

你可以抱怨公司有关软件工程的管理制度不合理,但你必须了解大多同行公司是怎么做的。

你可以借口说你们的竞争对手的成功是因为他们有了一个新的理念,但是为什么你没先想到呢?

需求真正改变的情况很少,但是没有做好需求分析工作的理由却很多。

6. 经常阅读

在这个每日都在发生变化的产业中,你不可能在已取得的成就上陶醉太久。

每个月至少读2、3本专业杂志或者1本专业书籍。保持不落伍需要付出很多的时间和金钱,但会使你成为一个很有实力的竞争者。

7. 降低软件模块间的耦合度

高耦合度的系统是很难维护的。一处的修改引起另一处甚至更多处的变动。

你可以通过以下方法降低程序的耦合度:隐藏实现细节,强制构件接口定义,不使用公用数据结构,不让应用程序直接操作数据库(我的经验法则是:当应用程序员在写SQL代码的时候,你的程序的耦合度就已经很高了)。

耦合度低的软件可以很容易被重用、维护和扩充。

8. 提高软件的内聚性

如果一个软件的模块只实现一个功能,那么该模块具有高内聚性。高内聚性的软件更容易维护和改进。

判断一个模块是否有高的内聚性,看一看你是否能够用一个简单的句子描述它的功能就行了。如果你用了一段话或者你需要使用类似“和”、“或”等连词,则说明你需要将该模块细化。

只有高内聚性的模块才可能被重用。

9. 考虑软件的移植性

移植是软件开发中一项具体而又实际的工作,不要相信某些软件工具的广告宣传(比如java 的宣传口号write once run many ? 译者注)。

即使仅仅对软件进行常规升级,也要把这看得和向另一个操作系统或数据库移植一样重要。

记得从16位Windows移植到32位windows的“乐趣”吗 ?当你使用了某个操作系统的特性,如它的进程间通信(IPC)策略,或用某数据库专有语言写了存储过程。你的软件和那个特定的产品结合度就已经很高了。

好的软件设计者把那些特有的实现细节打包隐藏起来,所以,当那些特性该变的时候,你的仅仅需要更新那个包就可以了。

10. 接受变化

这是一句老话了:唯一不变的只有变化。

你应该将所有系统将可能发生的变化以及潜在需求记录下来,以便将来能够实现(参见“Architecting for Change”,Thinking Objectively, May 1999)

通过在建模期间考虑这些假设的情况,你就有可能开发出足够强壮且容易维护的软件。设计强壮的软件是你最基本的目标。

11. 不要低估对软件规模的需求

Internet 带给我们的最大的教训是你必须在软件开发的最初阶段就考虑软件规模的可扩充性。

今天只有100人的部门使用的应用程序,明天可能会被有好几万人的组织使用,下月,通过因特网可能会有几百万人使用它。

在软件设计的初期,根据在用例模型中定义的必须支持的基本事务处理,确定软件的基本功能。然后,在建造系统的时候再逐步加入比较常用的功能。

在设计的开始考虑软件的规模需求,避免在用户群突然增大的情况下,重写软件。

12. 性能仅仅是很多设计因素之一

关注软件设计中的一个重要因素--性能,这好象也是用户最关心的事情。一个性能不佳的软件将不可避免被重写。

但是你的设计还必须具有可靠性,可用性,便携性和可扩展性。你应该在工程开始就应该定义并区分好这些因素,以便在工作中恰当使用。性能可以是,也可以不是优先级最高的因素,我的观点是,给每个设计因素应有的考虑。

13. 管理接口

“UML User Guide”(Grady Booch,Ivar Jacobson和Jim Rumbaugh ,Addison Wesley, 1999)中指出,你应该在开发阶段的早期就定义软件模块之间的接口。

这有助于你的开发人员全面理解软件的设计结构并取得一致意见,让各模块开发小组相对独立的工作。一旦模块的接口确定之后,模块怎样实现就不是很重要了。

从根本上说,如果你不能够定义你的模块“从外部看上去会是什么样子”,你肯定也不清楚模块内要实现什么。

14. 走近路需要更长的时间

在软件开发中没有捷径可以走。

缩短你的在需求分析上花的时间,结果只能是开发出来的软件不能满足用户的需求,必须被重写。

在软件建模上每节省一周,在将来的编码阶段可能会多花几周时间,因为你在全面思考之前就动手写程序。

你为了节省一天的测试时间而漏掉了一个bug,在将来的维护阶段,可能需要花几周甚至几个月的时间去修复。与其如此,还不如重新安排一下项目计划。

避免走捷径,只做一次但要做对(do it once by doing it right)。

15. 别信赖任何人

产品和服务销售公司不是你的朋友,你的大部分员工和高层管理人员也不是。

大部分产品供应商希望把你牢牢绑在他们的产品上,可能是操作系统,数据库或者某个开发工具。

大部分的顾问和承包商只关心你的钱并不是你的工程(停止向他们付款,看一看他们会在周围呆多长时间)。

大部分程序员认为他们自己比其他人更优秀,他们可能抛弃你设计的模型而用自己认为更好的。

只有良好的沟通才能解决这些问题。

要明确的是,不要只依靠一家产品或服务提供商,即使你的公司(或组织)已经在建模、文档和过程等方面向那个公司投入了很多钱。

16. 证明你的设计在实践中可行

在设计的时候应当先建立一个技术原型, 或者称为“端到端”原型。以证明你的设计是能够工作的。

你应该在开发工作的早期做这些事情,因为,如果软件的设计方案是不可行的,在编码实现阶段无论采取什么措施都于事无补。技术原型将证明你的设计的可行性,从而,你的设计将更容易获得支持。

17. 应用已知的模式

目前,我们有大量现成的分析和设计模式以及问题的解决方案可以使用。

一般来说,好的模型设计和开发人员,都会避免重新设计已经成熟的并被广泛应用的东西。
http://www.ambysoft.com/processPatternsPage.html 收藏了许多开发模式的信息。

18. 研究每个模型的长处和弱点

目前有很多种类的模型可以使用,如下图所示。用例捕获的是系统行为需求,数据模型则描述支持一个系统运行所需要的数据构成。你可能会试图在用例中加入实际数据描述,但是,这对开发者不是非常有用。同样,数据模型对描述软件需求来说是无用的。每个模型在你建模过程中有其相应的位置,但是,你需要明白在什么地方,什么时候使用它们。

19. 在现有任务中应用多个模型

当你收集需求的时候,考虑使用用例模型,用户界面模型和领域级的类模型。

当你设计软件的时候,应该考虑制作类模型,顺序图、状态图、协作图和最终的软件实际物理模型。

程序设计人员应该慢慢意识到,仅仅使用一个模型而实现的软件要么不能够很好地满足用户的需求,要么很难扩展。

20. 教育你的听众

你花了很大力气建立一个很成熟的系统模型,而你的听众却不能理解它们,甚至更糟-连为什么要先建立模型都不知道。那么你的工作是毫无意义的。

教给你开发人员基本的建模知识;否则,他们会只看看你画的漂亮图表,然后继续编写不规范的程序。

另外, 你还需要告诉你的用户一些需求建模的基础知识。给他们解释你的用例(uses case)和用户界面模型,以使他们能够明白你要表达地东西。当每个人都能使用一个通用的设计语言的时候(比如UML-译者注),你的团队才能实现真正的合作。

21. 带工具的傻瓜还是傻瓜

你给我CAD/CAM工具,请我设计一座桥。但是,如果那座桥建成的话,我肯定不想当第一个从桥上过的人,因为我对建筑一窍不通。

使用一个很优秀的CASE工具并不能使你成为一个建模专家,只能使你成为一个优秀CASE工具的使用者。成为一个优秀的建模专家需要多年的积累,不会是一周针对某个价值几千美元工具的培训。一个优秀的CASE工具是很重要,但你必须学习使用它,并能够使用它设计它支持的模型。

22. 理解完整的过程

好的设计人员应该理解整个软件过程,尽管他们可能不是精通全部实现细节。

软件开发是一个很复杂的过程,还记得《object-oriented software process》第36页的内容吗?除了编程、建模、测试等你擅长工作外,还有很多工作要做。

好的设计者需要考虑全局。必须从长远考虑如何使软件满足用户需要,如何提供维护和技术支持等。

23. 常做测试,早做测试

如果测试对你的软件来说是无所谓的,那么你的软件多半也没什么必要被开发出来。

建立一个技术原型供技术评审使用,以检验你的软件模型。

在软件生命周期中,越晚发现的错误越难修改,修改成本越昂贵。尽可能早的做测试是很值得的。

24. 把你的工作归档

不值得归档的工作往往也不值得做。归档你的设想,以及根据设想做出的决定;归档软件模型中很重要但不很明显的部分。 给每个模型一些概要描述以使别人很快明白模型所表达的内容。

25. 技术会变,基本原理不会

如果有人说“使用某种开发言、某个工具或某某技术,我们就不需要再做需求分析,建模,编码或测试”。不要相信,这只说明他还缺乏经验。抛开技术和人的因素,实际上软件开发的基本原理自20世纪70年代以来就没有改变过。你必须还定义需求,建模,编码,测试,配置,面对风险,发布产品,管理工作人员等等。

软件建模技术是需要多年的实际工作才能完全掌握的。好在你可以从我的建议开始,完善你们自己的软件开发经验。

以鸡汤开始,加入自己的蔬菜。然后,开始享受你自己的丰盛晚餐吧。

- 作者: hsee 2005年06月22日, 星期三 02:13  回复(0) |  引用(1) 加入博采

不懂二进制 照做程序员

作者: 杜奔放

国内的电脑教学走的是一条歧途。介绍电脑,从二进制讲起,教学编程,常用数学上的例子,什么八皇后,迷宫问题,把编程搞成走迷宫,很多数学基础不好的学员,很快就头晕眼花,只好半途而废。

国内的电脑教学走的是一条歧途。介绍电脑,从二进制讲起,教学编程,常用数学上的例子,什么八皇后,迷宫问题,把编程搞成走迷宫,很多数学基础不好的学员,很快就头晕眼花,只好半途而废。

其实,不懂二进制,没有数学基础,照样可以成为合格的程序员。在美国,很多大陆来的留学生,在国内是学文科的,数学基础很差,可是,照样做程序员,也胜任自己的工作。

这是为什么呢?因为社会分工越来越细。所以,你没有必要什么都知道,就象你开车不必知道怎么修车一样,编程也不必知道二进制,二进制是电脑内部的处理方式,你不会感觉到,按照术语,它对你来说是透明的。

同样,你也不必有数学基础,你可以不懂八皇后问题,可以不懂怎么走迷宫,因为,百分之九十五以上的编程是应用在商业上,而商业上的数学只用到加减乘除这些初等数学而已。

这不是说做程序员就没有一点要求了,程序员的基本要求是学习性强,逻辑性强。

学习性强指的是容易接受新东西。因为编程领域新的工具层出不穷。所以,要有接受新东西的能力,而不能墨守成规,思想僵化。

逻辑性强,是指电脑只能接受毫不含糊的指令,所以,你自己的逻辑一定要清楚,电脑才能理解你写的代码。一般说来,朦胧诗人是不适合做程序员的。电脑不爱朦胧。

如果你既爱学习,又有逻辑性,那么,程序员可能是一个适合你的职业,你永远不会感到枯燥,因为,软件一直在一个动态的环境里,而且,软件也在不断完善自己,让我们的生活更方便。这个过程是没有止境的。

来自:Blogchina
(原文链接)

- 作者: hsee 2005年06月22日, 星期三 02:12  回复(4) |  引用(1) 加入博采

汇编与C配合

1、先准备两个程序,一个汇编、一个C语言

  在汇编中没有定义变量,因为在一个模块中不会有问题;
  在C中定义了两个函数,一些局部变量,一些全局变量;
  这样我们要考虑的内容都完备了。
   ms.asm                    mc2.c                      
                                                   
  .386                       int sum(int i){          
  .model flat                int k = i;              
  extrn c m:near             int j = 0;              
  public  _start             int s = 0;              
  .code                      for(j=1;j<=k;j++) s+=j;  
_start:                 return s;                
  mov ax,cs             }                        
  mov ds,ax                                      
  call m                  int e = 1;              
stop:                      int f = 2;              
  jmp stop                int ar[6000000L];      
   end  _start                                          
                               extern void m(){         
                                  int d;                  
                                  long c;                  
                            c=1;                    

2、分别编译成obj文件

  ml /c /coff ms.asm             &nbs;       //指定生成coff格式的obj文件
  cl /c /Fomc.obj   mc2.c                //指定生成的obj文件名为mc.obj
  link /subsystem:windows ms.obj mc.obj  //这里使用32位的链接器,要设好lib路径

  现在得到ms.obj  mc.obj ms.exe  三个文件

3、分析一下源代码,显然程序入口点是_start(在使用/coff参数进行编译时必须有下划线),在汇编中

调用了C中的m函数,这是需要重定位的。在C中m调用了sum函数,这也是要重定位的。

4、现在利用VC6自带的dumpbin.exe工具,生成解析文件:

  dumpbin /all ms.obj>msobj.txt
  dumpbin /all mc.obj>mcobj.txt
  dumpbin /all ms.exe>msexe.txt

  现在得到三个解析文件,下面逐个分析
*******************************************************************************
*msobj.txt
*******************************************************************************
Microsoft (R) COFF Binary File Dumper Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file ms.obj

File Type: COFF OBJECT

FILE HEADER valueS
            14C machine (i386)       //机器类型为386
              3 number of sections   //ms.obj文件有三节
       41AABB57 time date stamp Mon Nov 29 14:01:59 2004
             B2 file pointer to symbol table  //符号表的文件偏移是 0B2H
              B number of symbols             //共 0BH=11 个符号
              0 size of optional header
              0 characteristics

//第一节的头部
SECTION HEADER #1
  .text name
      0 physical address
      0 virtual address
      D size of raw data                   //原始数据长度为 0DH=13 个字节
     8C file pointer to raw data           //其在文件内的偏移为 8Ch
     9A file pointer to relocation table   //其重定位表在文件内的偏移为9Ah
      0 file pointer to line numbers
      1 number of relocations              //需重定位的项有 1 项
      0 number of line numbers
60300020 flags
        Code                               //这是一个代码段
        4 byte align
        Execute Read

RAW DATA #1                                //这里列出了原始数据,恰好 13=0DH 个字节
 00000000: 66 8C C8 66 8E D8 E8 00 00 00 00 EB FE           f..f.........
                                |-->这是偏移7的位置,查下面的重定位表知道它需要重定位。
                                    当前值是 00 00 00 00 ,E8代表call


RELOCATIONS #1                             //这是重定位表
                                               Symbol    Symbol
Offset    Type              Applied To         Index     Name
--------  ----------------  -----------------  --------  ------
00000007  REL32                      00000000         7  _m     //清楚的指出_m需要重定位
   \                                                   \
    \-->在原始数据内的偏移是7                           \
                                                         \-->7表示_m在符号表中的索引号

//第二节的头部
SECTION HEADER #2
  .data name
      D physical address
      0 virtual address
      0 size of raw data
      0 file pointer to raw data
      0 file pointer to relocation table
      0 file pointer to line numbers
      0 number of relocations
      0 number of line numbers
C0300040 flags
        Initialized Data      //这节是初始化的数据段,也就是全局变量段,
        4 byte align          //上面所有的项都是0,说明汇编中没有定义全局变量
        Read Write            //注意,汇编中定义的_start是全局标号,并不是变量!!!


//第三节的头部
SECTION HEADER #3
.drectve name
      D physical address
      0 virtual address
      D size of raw data                  //原始数据共 0Dh
     A4 file pointer to raw data          //在obj文件中的偏移为0A4h
      0 file pointer to relocation table
      0 file pointer to line numbers
      0 number of relocations
      0 number of line numbers
    A00 flags
        Info                      //表明这只是一个信息段,即不是数据也不是代码,
        Remove                    //只是用来说明某种支持信息
        (no align specified)

RAW DATA #3              //看一下原始数据,原来是说明程序的入口点是_start,完全正确
 00000000: 2D 65 6E 74 72 79 3A 73 74 61 72 74 20           -entry:start

  Linker Directives
  -----------------
  -entry:start


//符号表
COFF SYMBOL TABLE
000 00000000 DEBUG  notype       Filename     | .file
   ms.asm
002 001220FC ABS    notype       Static       | @comp.id
003 00000000 SECT1  notype       Static       | .text
   Section length    D, #relocs    1, #linenums    0, checksum        0
005 00000000 SECT2  notype       Static       | .data
   Section length    0, #relocs    0, #linenums    0, checksum        0
007 00000000 UNDEF  notype ()    External     | _m
008 00000000 SECT1  notype ()    External     | start
009 00000000 SECT3  notype       Static       | .drectve
   Section length    D, #relocs    0, #linenums    0, checksum        0

//可以看到_m被说明为未定义(UNDEF)、外部变量(External)、是个函数 ( ())
//start定义在节1中(SECT1)、是个函数(())、可供外部使用(External)


//字符串信息为0,即不存在
String Table Size = 0x0 bytes

 Summary

          0 .data
          D .drectve
          D .text
******************************************************************************
*mc.obj
******************************************************************************
Microsoft (R) COFF Binary File Dumper Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file mc.obj

File Type: COFF OBJECT

FILE HEADER valueS
            14C machine (i386)           //机器类型为386
              2 number of sections       //mc.obj文件有2节
       41AABA2D time date stamp Mon Nov 29 13:57:01 2004
            158 file pointer to symbol table   //符号表的文件偏移是 158H
              C number of symbols              //共 0CH=12 个符号
              0 size of optional header
              0 characteristics


//第一节的头部
SECTION HEADER #1
.drectve name
      0 physical address
      0 virtual address
     26 size of raw data                     //原始数据长充为 26H=38 个字节
     64 file pointer to raw data             //其在文件内的偏移为 64h
      0 file pointer to relocation table
      0 file pointer to line numbers
      0 number of relocations
      0 number of line numbers
 100A00 flags
        Info                                 //这是一个信息段
        Remove
        1 byte align

//第一节的原始数据
RAW DATA #1                                               //原来是说明默认库的信息
 00000000: 2D 64 65 66 61 75 6C 74 6C 69 62 3A 4C 49 42 43  -defaultlib:LIBC
 00000010: 20 2D 64 65 66 61 75 6C 74 6C 69 62 3A 4F 4C 44   -defaultlib:OLD
 00000020: 4E 41 4D 45 53 20                                NAMES

  Linker Directives
  -----------------
  -defaultlib:LIBC
  -defaultlib:OLDNAMES


//第二节的头部
SECTION HEADER #2
  .text name
      0 physical address
      0 virtual address
     A6 size of raw data                  //原始数据长充为 0A6H=166 个字节
     8A file pointer to raw data          //其在文件内的偏移为 8Ah
    130 file pointer to relocation table  //其重定位表在文件内的偏移为130h
      0 file pointer to line numbers
      4 number of relocations             //需重定位的项有4项
      0 number of line numbers
60500020 flags
        Code                              //这是一个代码段
        16 byte align                     //对齐方式是以16个字节的小段边缘对齐
        Execute Read                      //该代码 可读、可执行
                                          //这点可通过编译参数/SECTION来改变

//第二节的原始数据
//使用W32Dasm打开mc.obj文件,输入偏移地址为8Ah(见第二节的头部说明),反编译下面这段
//与汇编生成的lst文件对比,可以看出下面的数据从偏移0开始的55 8B到偏移44H的5D C3是sum
//函数的数据。紧跟其后直至最后的是函数 m 的代码
//这里可以看出,32位编译器把所有的代码按它们在源代码中出现的顺序“堆积”在obj文件中

RAW DATA #2
 00000000: 55 8B EC 83 EC 0C 8B 45 08 89 45 F4 C7 45 F8 00  U......E..E..E..
 00000010: 00 00 00 C7 45 FC 00 00 00 00 C7 45 F8 01 00 00  ....E......E....
 00000020: 00 EB 09 8B 4D F8 83 C1 01 89 4D F8 8B 55 F8 3B  ....M.....M..U.;
 00000030: 55 F4 7F 0B 8B 45 FC 03 45 F8 89 45 FC EB E4 8B  U....E..E..E....
 00000040: 45 FC 8B E5 5D C3 55 8B EC 83 EC 08 C7 45 FC 01  E...].U......E..
 00000050: 00 00 00 C7 45 F8 01 00 00 00 C7 05 00 00 00 00  ....E...........
 00000060: 01 00 00 00 C7 05 00 00 00 00 01 00 00 00 6A 05  ..............j.
 00000070: E8 00 00 00 00 83 C4 04 C7 45 FC 00 00 00 00 EB  .........E......
 00000080: 09 8B 45 FC 83 C0 01 89 45 FC 81 7D FC 80 Cheesy 5B  ..E.....E..}.

- 作者: hsee 2005年06月16日, 星期四 14:36  回复(1) |  引用(1) 加入博采

BIOS和DOS建立的中断向量表

绝对地址 16进制 10进制 有关内容
00H 0H 0  0做除数时处理器发出的中断 溢出条件是: 除法类型 结果--有符号字 <-128 | >+127有符号双字 <-32768 | >+32767无符号字 | >255无符号双字 | >65535--DOS设有INT 00H处理程序,系统将显示“Divide Overflo”, 结束当前程序的执行。
04H 1H 1 单步调试时处理器发出的中断
08H 2H 2 非屏蔽中断
0CH 3H 3 调试程序设置断点时处理器发出的中断
10H 4H 4 发生算术溢出时处理器发出的中断
14H 5H 5 调用BIOS的屏幕拷贝操作
18-1FH 6-7H 6-7 保留单元
20H 8H 8 每1/18.2秒定时器发出的中断
24H 9H 9 按压或释放键时产生的中断
28H 0AH 10 保留单元
2CH 0BH 11 通讯设备使用的硬件中断
30H 0CH 12 通讯设备使用的硬件中断
34H 0DH 13 交替打印时硬件产生的中断
38H 0EH 14 软驱操作结束时产生的硬件中断
3CH 0FH 15 打印机发出警告信号时产生的硬件中断
40H 10H 16 BIOS的显示I/O功能调用
44H 11H 17 BIOS设备确认调用
48H 12H 18 BIOS确认内存空间大小的功能调用
4CH 13H 19 BOIS的磁盘I/O功能调用
50H 14H 20 BIO的RS-232串行I/O功能调用
54H 15H 21 在PC和XT机上是BIOS磁带I/O功能调用。在AT机上是AT扩充服务功能调用。
58H 16H 22 BIOS的键盘I/O功能调用
5CH 17H 23 BIOS的打印机I/O功能调用
60H 18H 24 ROM的BASIC解释和程序功能调用
64H 19H 25 BIOS的装载引导服务调用
68H 1AH 26 BIOS的日期时钟功能调用
6CH 1BH 27 Ctrl+Break处理程序功能调用。当键入Ctrl+Break键时指向可执行的程序入口初始化BIOS使该向量指向一条TRET指令。用户可修改该向量,使它指向自己的程序。
70H 1CH 28 指向每1/18.2秒时可执行的服务程序的入口。初始化时该向量指向一条IRET指令。用户可修改该向量,使它指向自己的Ctrl+Break 处理程序。
74H 1DH 29 指向显示控制器初始化参数。BIOS使这个向量指向ROM驻留表。
78H 1EH 30 指向软盘参数表。BIOS使这个向量指向ROM 驻留表,但是DOS把它改为指向DOS的RAM 驻留表。
7CH 1FH 31 指向一点阵表。在这个表中,BIOS可以找到字符集后128个字符的点阵
80H 20H 32 终止程序的DOS功能调用
84H 21H 33 任何种DOS功能调用 
88H 22H 34 指向DOS的结束地址
8CH 23H 35 指向DOS的Ctrl+Break处理程序
90H 24H 36 指向DOS的严重错误处理程序
94H 25H 37 DOS绝对磁盘读调用
98H 26H 38 DOS绝对磁盘写调用
9CH 27H 39 程序终止,但仍驻留内存的DOS功能调用
9DH 28H 40 DOS空闲
9EH 29H 41 支持驱动器程序输出
9FH-A1H 2AH-2CH 42-44 保留单元
A2H 2DH 45 DOS构件接口
A3H 2EH 46 COMMAND.COM退回入口
A4H 2FH 47 多路中断(空闲信号)
A5-FFH 30-3FH 48-63 为DOS保留的单元
100H 40H 64 保留单元
104H 41H 65 指向硬盘0的参数表,BOIS使这个向量指向ROM驻留的表。
108-10FH 42-43H  66-67 保留单元
110H 44H 68 PC机使用,用于指向低分辩率图形字符参数表
114H 45H 69 保留单元
118H 46H 70 指向硬盘1的参数表,BIOS使这个向量指向 ROM驻留的表。
11CH 47H 71 保留单元
120H 48H 72 PC机使用,用于把PC机的键盘代码变换为标准的键盘代码。
124H 49H 73 指向键盘增强服务变换表
128-17FH 4A-5FH 74-95 保留单元
180-19FH 60-67H 96-103 为用户程序保留的单元
1A0-1BFH 68-6FH 104-111 未使用
1C0H 70H 112 硬件中断(IRQ--interrupt request) 8--实时时钟中断
1C4H 71H 113 硬件中断9
1C8H 72H 114 硬件中断10
1CCH 73H 115 硬件中断11
1D0H 74H 116 硬件中断12
1D4H 75H 117 硬件中断13--BIOS把这个中断向量重定向为非屏蔽中断(NMI)
1D8H 76H 118 硬件中断14
1DCH 77H 119 硬件中断15
1E0-1FFH 78-7FH 120-127 未使用
200-217H 80-85H 128-133 为BASIC保留
218-3C3H 86-F0H 134-240 BASIC程序运行时提供给BASIC解释程序作用
3C4-#FFH F1-FFH <> 241-255 未作用

- 作者: hsee 2005年06月16日, 星期四 14:35  回复(0) |  引用(1) 加入博采

用汇编编写DOS下的内存驻留程序

绪言
0.1 内存驻留与中断
内存驻留程序英文叫Terminate and Stay Resident Program,缩写为TSR.这些程序加载进内存,执行完后,就驻留在内存里,当满足条件时,调到前台来执行。
内存驻留程序的常用形式有:
>诸如Borland 的SideKick弹出式实用程序
>日历系统
>网络服务器
>通讯程序
>本地的DOS扩展(如CCDOS,UCDOS等中文系统都属于这个范畴)
>一些可恶的人利用TSR技术制作很多可恶的病毒程序,几乎所有的病毒程序都是TSR程序.
就象多任务系统调度一个进程有一个调度程序一样,在PC中从前台程序进入到一个TSR,也要有一个调度者,只是PC操作系统的调度不称为调度程序,而只称为触发机制.触发机制调度TSR执行在PC机上党称为激活一个TSR.触发机制主要有以下几种:
>硬件中断:党用的是键盘中断INT 9H,时钟中断INT 8H,通讯中断INT 14H,磁盘中断INT 13H等等.
>软件中断:党用的是键盘中断INT 16H,时钟中断INT 1CH,DOS中断INT 21H,等等.
>以上各种的结合.
从以上的触发机制可以看出,TSR和PC机的中断系统有着密切的关系.每种激活方式实际上都是与中断有关的.常用特殊的击键序列的识别码是通过截获INT 9H和INT 16H来实现.实际上不管TSR程序的哪一个环节,都与中断有着密切的关系.因此在具体进行TSR和程序设计之前,先介绍PC中断系统.在此只作简单说明.
在PC机内存的最低端(0000H开始)的1K字节中,存放着256个指针即常说的中为向量或中断矢量(Interrupt vertor),每个中断向量都指向一个子程序,该程序称为中断处理程序(Interrup handler).一个中断向量由四个字节组成,有一个字是中断处理程序的偏移量值,后一个字是中断处理程序的段值.256中断向量一起称为中断向量表.
手式计算中断向量的首址,可通过以下的公式来求得:
X号中断向量的首址=0000H:X*4
当产生一个中断时,处理器都按顺序执行以下步骤:
>在堆栈上压入处理器的标志(相当于指令PUSHF).
>在堆栈上压入当前CS和IP值(相当于指令PUSH CS和PUSH IP).
>关闭中断(CLI)
>从中断向量加载的CS和IP,执行中断处理程序.
当执行完中断处理程序后,一般用IRET返回,它的作用是:
>从堆栈上取出保存的IP和CS(相当于指令POP CS和PUSH CS).
>同时恢复中断前的处理器标志(相当于指令POPF).
中断有多种分类,由触发的原因和实现的性质来分,可分为硬件中断和软件中断,从操作系统分层实现来说,可以分成BIOS中断,BOS中断和用户中断.
一方面,BIOS和DOS通过中断系统向用户提供一个操作系统功能界面.也就是说用户(一般来说是前台程序)的功能主要是通过调用DOS和BIOS的中断服务来实现的,具体来说就是通过INT指令来实现的.另一方面,BIOS和DOS由中断系统所构成,BIOS对硬件成为高层的功能,并通过中断的形式向用户提供.
如果在当前程序执行的同时,能将一块代码放在内存,把中断向量指向代码中的子程序,那么在当前程序执行中产生中断时,就有可能执行不属于当前程序和操作系统的代码,产生的中断可能是当前程序产生的软件中断,也可能是由硬件产生的硬件中断.这就是单任务的PC操作系统可能执行多于一个进程的简单说明.
在PC中断系统中有几个中断具有周期性,即INT 8H,INT 1CH和INT 28H.它们或者周期性被执行用于时间计时,或者周期性产生用于等待.它们是在实现TSR时进行轮询触发的基础.键盘中断(INT 9H和INT 16H)当用户击键时发生,利用它们是进行热键处理的基础.串行口通讯也是触发的一个重要机制.此外众多的软件中断也是触发的媒介.

0.2 DOS的可重入性分析
一个多任务操作系统之所以能使多个进行并存,是因为操作系统的大部分代码是可以了重的,对于临界资源有相应的PV操作,使得当调度一个新的进程时,能完整地保存前一个里程的现场,当再一次调度被挂起的进程时能象没有被中断一样继续执行.
对于PC机来说,代码的重入性比较弱,对临界资源没有PC操作.当我们用中断程序启动用户的TSR时,如果只保存标志和寄存器,以及当前进程一些信息,那么只保存了当前程序的一部分现场,DOS的临界资源不会自动保存.在进行TSR设计时,一定要了解PC操作系统的重入性和临界资源.
重入性总是体现在代码上,所谓可重入代码的指这样的代码,即该代码被执行时还没有从中退出,由于某种原因又一次或者多次进入相同的代码,该代码每次的执行结果都是正确的,就说该代码是可重入的.相反,如果结果不正确,那么就就该代码是不可重入的.下面是一个可重入的子程序的例子:
Add proc near
cmp DS:word ptr [si],0
je DonotAddTheValue
add ax,DS:word ptr [si]
DonotAddTheValue:
ret
Add endp
上面的例子不管在其中任何一处再一次执行该子程序,执行结果不变.为了说明,只举多种可能性中的一种.
mov ds,0100h ;ds=0100h
mov si,0010h ;si=0010h
mov ax,0001h ;ax,=0001h
call Add
cmp 0100h:word ptr [0010h],0 ;Call Add subroutine
push ds ;Interrupted
push si
push ax
mov ds,0200h ;ds=0200h
mov si,000h ;si=0020h
mov ax,0003h ;ax=0003h
call Add
cmp 0200h:word ptr [0020h],0 ;0200:0020h=0004h
jne
add ax,0200h:word ptr [0020h] ;ax=0007h
ret ;Return
pop ax ;ax=0001h
pop si ;si=0010h
pop ds ;ds=0100h
iret ;Return to Add subroutine
jne
add ax,0100h:word ptr [0100h] ;ax= 0001h
;0100h:0010h= 0002h
;----------------------------------------
;ax = 0003h
ret
mov bx,ax
而下面的子程序是不可重入的:
Add proc near
mov Temp,ax
mov ax,DS:word ptr [si]
cmp ax,0
je DonotTheValue
add ax,Temp
DonotTheValue:
ret
Temp:
dw 0
Add endp
可以利用检查可重入子程序的方法检查这个子程序的不可重入性,尝试一下在" mov ax,DS:word ptr [si]"指令后再次执行该子程序,那么就会出第一次调用返回的结果不对.
mov ds,0100h ;ds=0100h
mov si,0010h ;si=0010h
mov ax,0001h ;ax,=0001h
call Add
mov Temp,ax ;Call Add subroutine
;Temp=0001h
mov ax,0100h:word ptr [0010h] ;0100h:0010h=0002h
;ax=2
push ds ;Interrupted
push si
push ax
mov ds,0200h ;ds=0200h
mov si,0020h ;si=0020h
mov ax,0003h ;ax=0003h
call Add
mov Temp,ax ;Temp=0003h
mov ax,0200h:word ptr [0020h] ;0200h:0020h=0004h
cmp ax,0 ;ax=0004h
jne ;Not equal ,add
add ax,Temp ;ax=0007h
ret ;Return to the interrupted point
pop ax ;ax=0002h
pop si ;si=0010h
pop ds ;ds=0100h
iret ;Return to Add subroutine
cmp ax,0 ;ax=2
jne ;No equal,add
add ax,Temp ;ax =0002h
;0100h:0010h =0003h
;----------------------------------------
;ax =0005h
ret
mov bx,ax
上面执行的结果是AX=5,实上正确的结果应该是AX=3,这是由于当Add子程序从中断子程序再一次被调用时,修改了Temp的值,当从中断返回时不能正确恢复其值.
解决的方法是把Temp放在堆栈中,当每次Add子程序被调用时Temp的地址都不一样,因此原调用的Temp值不会被第二次在中断中调用的Add所破坏.
Add proc near
push bp ;Store BP
sub sp,2 ;distribute a byte space in the stack
mov bp,sp ;SS:BP point to the stack head
temp equ SS:word ptr [BP+0] ;Explain the pointer to SS:BP
mov Temp,ax
mov ax,DS:word ptr [si]
cmp ax,0
je DonotAddTheValue
add ax,Temp
DonotAddTheValue:
add sp,2 ;Release the dsitributed space in the stack
pop bp ;Restore BP
ret
Add endp
对于DOS来说,DOS的内存数据就象Temp变量,它被分配在数据区,而不在堆栈上,因此DOS从总体上是不可重入的.从最后的一个例子看来.重入性跟堆栈有很大的关系.可重入代码允许在任何时候被中断,其所有的变量都存放在该代码的私有堆栈中.DOS是一个单任务的操作系统,在执行INT 21H的代码时是不允许中断DOS,并再次调用INT 21H的.每个时该最多有一个进程在调用DOS的代码.
对DOS的重入性,以及相应所作的处理总结如下:
>当通过INT 21H调用DOS时,DOS会使三个内部栈之一:I/O栈,磁盘栈和辅助栈.功能00H到处0CH使用I/O栈,除了不致命错误处理程序以外使用磁盘栈,致命错误处理程序使用辅助栈.在这种栈切换模式下,如果前台处在INT 22H中,而TSR调用了使用相同栈的DOS功能, 就会使前台程序保存栈中的数据被TSR的数据覆盖掉;但如果调用不同栈的DOS功能,那将是安全的.INT 21H中的几个功能调即33H,50H, 51H,62H,和64H由于非常简单,使用用户栈,因此在任何情况下都是可重入的.避免这种不可重入的简单方法是当前台程序正处在INT 21H 中时,不要调用INT 21H.或者如果前台程序正在处理INT 21H时,只允许调用不同栈的INT 21H功能.
>DOS数据区中有一个InDOS标志,也探源为DOS安全标志,表示当前访问DOS功能是来否安全.由于DOS不可重入,它指示当前是 否处于DOS中,激活TSR和代码可检查该标志(34H),如果DOS忙,则不能激活使用INT 21H 调用的TSR.
>当前台程序执行能设置错误状态的DOS功能时,DOS会把扩展错误信息存放起来,正常情况下,前台程序可以读取扩展错误信息; 如果在前台程序读取信息之前激活TSR,且TSR也执行能报告错误信息的DOS功能,则后来的错误信息会覆盖原来的错误信息,前台程序就会得不到正确的错误信息.因此必须在激活TSR之前保存(59H)这些错误信息,并在退出以前把它们恢复(5D0AH)成原来的值.
>大多硬件中断如INT 13H,INT 0BH和INT 0CH等都是不可重往返.如果设置一引起寄存器,而在此时被TSR打断,执行类似的设置 ,就会出现非常情况,端口是不会自动保持值的.在进入这些中断时设置一个进入的标志,如果TSR检查到标志已置,则不调用相应的中断.
>最好也不要重入INT 10H,INT 25H,和INT 26H中断.在进入这些中断时设置一个进入的标志,如果TSR检查到标志已置,则不调用 相应的中断.
>最好能接管INT 1BH,INT 23H和INT 24H中断.
>保存DOS的数据交换区(SDA)可以安全地使用的DOS的功能.SDA保存了DOS几乎所有内部数据,如果保存(5D06H)和恢复 (5D0BH)SDA ,DOS就变成在任何时候都可以重入的了.当DOS处在关键区中时,调用INT 2AH.一旦处在关键区中,就不能改变SDA.在关键区的结束处会 调用INT21H的81H和82H功能.
0.3 内存驻留程序设计一般过程
驻留程序分成两个部分,即暂驻部分和驻留部分.驻留程序要完成安装检测,激活和删除等过程.
基本上可抽象成以下几个过程:
>取中断向量
>保存旧的中断向量
>设置或恢复中断向量
>中断处理程序的链接
>检测是来呀已驻留
>执行终止并驻留
>TSR的删除
删除TSR比较复杂,必须按下列步骤进行:
>检查中断向量是否已经被替换.如果没有替换,就恢复所有的中断向量;如果某个中断向量被替换,则跳过下面各步,不能删除该 TSR.
>TSR的PSP中偏移量16H存放着父进程的PSP.把这个值改为当前进程的地址.
>把当前PSP设为TSR的PSP
>执行INT 21H的4CH功能,释放TSR占用的内存,关闭所有文件,并使用PSP中存放的父进程地址和终止地址.
>这里控制返回到初始进程中,当前PSP也指向初始进程,所有寄存器值包括SS和SP都不确定.
在执行完上述步骤后,要恢复寄存器.
如果要无条件地删除TSR,必须监控每个TSR对中断向量表,内存控制块和设备驱动程序链的修改.
0.5 缩写语表
ASCIZ: 以零结束的ASCII字符串.
BPD: "BIOS Parameter Block (BIOS 参数块)"的缩写.含有对驱动器的低级参数的说明.
CDS: "Current Directory Structure(当前目录结构)"的缩写,含有某个逻辑驱动器的当前目录,类型和其它信息.
DPB: "DOS Drive Parameter Block(DOS驱动器参数块)"的缩写,含有某个逻辑驱动器的介质说明及一些内部信息.
DPL: "DOS Parameter List (DOS参数表)"的缩写,该数据结构用来传递参数给SHARE和网络功能调用.
DTA: "Disk Transfer Address(磁盘传输地址)"的缩写,指示对磁盘进行数据读写的功能调用不必显式地给出缓冲区地址.
FAT: "File Allocation Table(文件分配表)"的缩写,磁盘的文件分配表记录了所使用的簇信息.
FCB: "File Control Block(文件控制块)"的缩写,在DOS的1.X版本中,用FCB来记录文件打开的状态..
IFS: "Installable File System(可安装的文件系统)"的缩写,它允许一个非DOS格式的介质被DOS所使用. 大多数情况下IFS 与网络驱动器非常相似,尽管IFS最典型的情况是一个本地驱动器而不是一个远程驱动器.
JFT: "Job File Table(工作文件表)或Open File Table(打开文件表)"的缩写,程序PSP中的JFT可用来将文件句柄转换成SFT值.
NCB: "Network control Block(网络控制块)"的缩写.NCB可用传递对NETBIOS的请求和接受来自NETBIOS处理程序的状态信息.
PSP: "Porgram Segment Prefix(程序段前缀)"的缩写.当程序被装入时,PSP为一个预留的256字节的数据区它包含了程序调用时 的命令行内容和一些DOS的内部信息.
SDA: "DOS Swappable Data Area (DOS对换数据区)"的缩写.SDA中包含有DOS内部使用的记录某个正在处理的功能调用状态的 所有变量.
SFT: "System File Table(系统文件表)"的缩写,SFT是一个DOS内部数据结构,在DOS 2+版本的句柄功能调用中用于管理某个已打 开文件的状态,这就和在DOS1.X中,FCB管理已打开文件状态一样.
基本原理
2.1 8086/8088
IBM PC中央处理单元(Central Processing Unit)是微处理器Inter 8088,8088是8086是小的版本.对于编写程序而言,两者几乎完全相同.两者之间的差别是在于:它们对外的沟通.8086和外界沟通时是经由16 位的输入输出通道,内存存取也是每次以16位为单位,8088和8086极为相似,但是它和外界沟通时就必须经由16位的通道.
2.1.1 寄存器
8086/8088的结构简单,其中包含了一组一般用途的16位寄存器.AX,BX,CX,DX,BP,SI,DI.其中AX,BX,CX,DX还可以分成8位的寄存器,譬如:AX可分为AH,AL;BX可分为BH,BL;CX可分为CH,CL;DX可分为DH,DL.寄存器BP,SI,DI的用途也没有特别的限制, 但是却不能分成两个字节.另外寄存器SP主要是用来当做堆栈指针.除此之外,还有四个非常重要的段寄存器(Segment Register):CS,DS,SS,ES.指令指针(Instru -ction pointer)IP是用来控制目前CPU执行到哪一个指令.
8086设计时考虑到要和8位的CPU8080兼容.8位的计算机是使用两个字节(亦即16位)来定址,因此其定址空间可以达6K字节.16位的CPU 在地址设定上选择了完全不同的方法.CPU以段(Segment)为单位,每一段范围内包括64K字节,而内存中则可以包含许多段.所以,操作系统可以在一个段内执行.而使用者的程序则可以在另一个段内执行.在一个段内,程序包可以把计算机视为只有64K字节内存空间.因此原先8位计算机上执行的程序就可以很容易地移植到16位计算机上.除此之外,内存段也可以彼此重叠,因而两个不同的程序就可以共用某一块内存.段值是以寄存器来设定的,而实际的地址值则是把段值(16位)往左移4位,然后再加上16位的位移(Offset),因此构成20位的地址值.所以8086可以直接做20位的地址,也就是可能存取到一兆字节的内存.在这一兆字节的内存中,IBM PC保留了最前面的320K字节给系统的ROM BIOS和显示内存,因此使用者最多也就能使用640K字节.
2.1.2 寻址方式
寻址方式(Addressing mode)是一台计算机上许多复杂操作的关键所在.8086提供了以下几种寻址方法:立即寻址,内存间接寻址, 寄存器间接寻址等.
立即寻址,直接使用数字.
内存间接寻址,数值存放在数据段中的某个位置.
mov bx,foo
foo dw 5
寄存器间接寻址.有两种寄存器可以使用在这种寻址方式下:基址寄存器(Base Register)和索引寄存器(Index Register).基址寄存 器分别是BX和BP,索引寄存器则是SI和DI.在这种寻址方式下,寄存器存放了数据段中的地址值.
mov ax,0F000h
mov es,ax
mov si,0FFFEh
mov dl,byte ptr es:[si]
上面的程序使用间接寻址方式,由寄存器SI读出位于F000:FFFE位置的数据.寄存器间接存取时,最多只能使用玛个基址寄存器各 一个索引寄存器.
以上的寻址方式可以做不同的结合,因此组合后的结果很多.
2.1.3 标志
8086有9个一位的标志(Flag),它们可以用指示CPU的各种状态.以下是9个标志的简介:
CF(Carry Flag):CF为1时就表示算术运算的结果超出正确的长度.
PF(Parity Flag):PF为1就表示使用偶校验,PF为0就表示使用奇校验.
AF(Auxiliary Carry Flag):和CF相同,只是它使用在低4位的结果.AF通常都使用在20位的地址计算上.
ZF(Zero Flag):ZF为1就表示运算结果是0,否则ZF就为0.
SF(Sign Flag):SF为1就表示运算结果的最高位是1,否则SF就为0.
TF(Trap Flag):TF为1,CPU就单步地执行,在这种模式下每完成一个指令就发生一个特殊的中断.
IF(Interrupt Enable Flag):IF为1,允许CPU接收外界的中断,否则IF就为0.
DF(Direction Flag):这个标志使用在循环指令,譬如:MOVS,MOVSB,MOVSW,CMPS,CMPSB和CMPSW.如果DF为1,循环运行时就使地 址值往前增加.如果DF为0,则使地址往后减少.
OF(Over Flag):OF为1,表示一个考虑正负号的运算超出了正确的字节的长度.
2.1.4 循环
所有的循环指令都是以CX作为计数器.一个循环会反复地执行直到CX等于某一特定值为止.以下的程序就是利用反复地相加,完成 两个数的相乘.
mov ax,0
mov cx,4
next: add ax,6
loop next
在上面的程序中,LOOP指令执行时会把CX减1,并且检查CX的内容;如果CX等于0,就转移到下一条指令,否则就跳到NEXT标示的地方 执行.
也可以用下面的程序完成相同的功能:
mov ax,0
mov cx,4
next:
add ax,6
dec cx
cmp cx,0
jne next
2.1.5 内存的数据结构
8088是以字节为存取数据的基本单位.计算机的存储结构是8位的字节,但是CPU本身处理数据则是以16位为单位.在内存中,都遵 循一个原则,即:高高低低的存储方式.高字节对应高地址,低字节对应低地址.
下面是一个简单程序,在AX中放入一个字节的内容并显示:
cseg segment
org 100h
assume cs:cseg,ds:cseg
start:
mov bx,cs
mov ds,bx
mov ah,'H'
mov al,'L'
mov test,ax
mov al,[si] ;First byte of test
call dchar
mov al,[si+1] ;Second byte of test
call dchar
ret
;Display the character contained in AL
dchar proc
push ax
push bx
mov bh,1
mov ah,0eh
int 10h
pop bx
pop ax
ret
dchar endp
test dw ?
cseg ends
end start

三 中断矢量
3.1 IBM PC提供的中断
IBM PC有两种基本形态的中断.如果是由外围设备所产生的中断就叫做硬件中断(Hardware interrupt),譬如:键盘,磁盘机和时钟等外围设备都可以产生硬件中断.外围设备所产生的中断信号都连接到中断控制器,中断控制器可以根据它们之间的重要性来安排优先顺序,以便使CPU有效地处理这些硬件信号.另一种中断是软件中断(Software interrupt),软件中断也叫做陷井(Trap),它是由执行中的软件所产生.虽然软件包中断的处理方式和硬件中断完全相同,但是通常软件中断是希望执行操作系统所提供的服务.
表3.1是IBM PC所提供的中断,这些中断是根据中断号码和中断矢量(Interrupt vector)排列.
IBM PC的用户或是编写应用程序的程序人员很少会直接接触到硬件中断,除非是使用某些特殊的硬件,或是需要较严格的要求时,最常被修改的硬件中断是敲键盘所产生的中断(9H),尤其是文本编辑的程序.大体而言,只有硬件设计者基是系统程序人员才会注意到所有在硬件中断;编写内存驻留程序的设计人员则只使用到部分硬件中断而已,尤其是:键盘中断和计时器(Timer)的中断.
反之,软件中断对于任何编写汇编程序的人,甚至对编写高级语言程序的人都相当的重要.软件中断是应用程序进入到IBM PC操作系统的接口,经由这些接口应用程序才可以执行所要求的系统服务.
其中软件中断中最重要,同时也是最常被汇编语言程序设计师所用到是DOS INT 21H.这个中断是执行DOS系统调用的软件中断,它可以让应用程序执行任何DOS的操作.
接下来最有用的软件中断是ROM-BIOS(基本输入输出系统)所提供的中断.这些软件中断是IBM PC所提供的的低层次服务,譬如:键盘输入,显示器输出和磁盘机的输入与输出等.
3.2 键盘输入的方法
以下就以IBM PC从键盘读取字符为例子,来说明中断的工作方式.IBM PC从键盘读取字符时,使用了两种不同形式中断,亦即:硬件中断和软件中断.当使用者从键盘敲下一个键时,键盘的线路就会送出一个信号.这个信号会造成硬件中断发生,从而触发低层次的键盘中断处理程序开始执行.这个中断处理程序马上从键盘的硬件读取使用者所敲入的字符,然后把它放到一个队列中,如果这个队列填满时,键盘中断处理程序会使IBM PC发出一声响.键盘中断处理程序做完这些事情之后,它就把控制权交还给原先被中断的程序.如果有一个程序希望从键盘读取一个字符时,它就发出适当的软件中断信号,这时候就由相对应的中断处理程序去检查键盘队列,并且传回队列中的第一个字符.
上面所介绍的键盘输入工作方式,在中断驱动系统中很普遍地采用.这和做法可以把实际上需要输入的应用程序和实际上执行输入的处理部分分开来.这种做法也可以用在其它不同形式的输入和输出外围设备.
3.3 改变输入矢量
中断矢量储存在IBM PC最前面的400H个字节中.每一个矢量的长度是四个字节组成,这四个字节内所存放的是中断处理程序执行的地址值.其中前两个字节包含地址值的位移(Offset)部分,后面的两个字节则包含了段(Segment)部分.
中断矢量有两种修改方法.可以直接地设置中断矢量的地址值,或是使用DOS所提供的系统调用设置中断矢量的地址值.
3.3.1 直接设置中断矢量
因为中断矢量只是存放地址值的存储位置,因此我们可以直接地把地址存放到存储位置中.以下是一个小例子:
mov ax,0
mov es,ax
mov word ptr es:24,offset Keyboard
mov word ptr es:26,seg Keyboard
在许多情况下,上面的程序都可以正确地执行.但是如果上面的程序正在执行时突然敲下一个键的话,就可能会问题;而最糟的情况是发生:第三个MOV已经执行完毕,而第四个MOV尚未执行时.如果在此时敲下任何键的话,键盘中断矢量都没有任何意义,而造成整个系统死机.因此我们可以在设置中断矢量时,让中断无效,譬如:
mov ax,0
mov es,ax
cli
mov word ptr es:24,offset Keyboard
mov word ptr es:26,seg Keyboard
上面的做法在大部分的情况下都可以正确地执行.但是CLI这个指令无法停止NMI中断(不可屏蔽中断),因此如果发生NMI中断时就 没用办法.下面的这一种做法虽然比较复杂,但是对于所有的中断都有效,这包括了NMI中断在内:
mov word ptr kbd-ptr[0],offset Keyboard
mov word ptr kbd-ptr[2],seg Keyboard
mov di,0 ;Use Di to Set ES to zero
mov es,di ;Set ES to destination segment
mov di,24 ;Set DI to destination offset
mov si,offset kbdptr ;set SI to source offset
mov cx,2 ;Set word count to 2
cld ;Set direction to forward
cli ;Disable interrupts
rep movsw ;Copy the new vector
sti ;Enable interrupts
kbdptr dd ?
上面的程序中,kbdptr是两个字节(WORD)的指针(Pointer),其中包含了键盘 中断处理程序的起始志趣值.REP这个指令将根据寄存器CX所设置的次数来重复执行MOVSW,而整个指令就如同单一的指令一样.NMI中断不能够发生在一个完整的指令中.因为地址值搬移的操作都能包含在一个单一指令中,因此可以免除任何中断的干扰.
3.3.2 使用DOS来设置中断矢量
因为要想安全地设置中断矢量需要一些技巧,因此DOS提供了一项特殊的服务,以帮助程序人员安全地设置中断矢量,如果只使用 DOS所提供的这项服务来设定中断矢量的话,那么就不必担心会发生前面所叙述的差错.DOS同时也提供了:读取中断矢量的服务.因为读取中断矢量的内容不会修改系统的状态;因此若直接写程序读取,也很安全.但是如果你要自己直接读取中断矢量的内容时,就必须计算出中断矢量的位置.而DOS已经提供了这项服务.
使用DOS所提供的系统调用,来读取中断矢量的内容时,必须利用INT 21H中的函数35H(读取中断矢量),个函数热气矢量号码来 计算中断矢量的地址,然后返回其中的内容.以下就是一个例子:
Old_Keyboard_IO dd ?
mov al,16h
mov ah,35h
int 21h
mov word ptr Old_Keyboard_IO,bx ;Offset of interrupt handler
mov word ptr Old_Keyboard_IO,es ;Segment of interrupt handler
用DOS来设置中断矢量例子:
New_Keyboard_IO dd ?
mov word ptr New_Keyboard_IO,bx ;Offset of interrupt handler
mov word ptr New_Keyboard_IO,es ;Segment of interrupt handler
mov al,16h
mov ah,25h
int 21h
3.4 检查中断矢量
这里都是采用COM格式编程,可以建立一个BAT文件来处理写好的程序,以减少击键次数.设BAT文件名为MAKE.BAT:
MASM %1
LINK %1
EXE2BIN %1.EXE %1.COM
如果写好的程序名为MACRO.ASM,则可敲入:
C:\MAKE MACRO.ASM
即可.
3.5 显示中断矢量
下面这个例子可以列出所有的重要的中断矢量内容,在刚刚打开PC时,并且没有执行任何驻留程序时,可以发现所有的中断矢量段值都相同,这些地址值所存放的是ROM的程序.当你修改中断矢量之后,就可以利用这个程序观察到中断矢量的变化.以下就是IVEC.ASM的内容:
cseg segment para public 'CODE'
org 100h
jmp start
assume cs:cseg,ds:cseg
start:
mov bx,cs ;Make data seg be the same as
mov ds,bx ;the code seg
call vectors
waitIn:
mov ah,0bh
int 21h
cmp al,0ffh
jne waitIn
mov ah,4ch
int 21h
;****************************************************************************
;Scan through display table,prinying two vectors per line
;If any record has an interrupt #=zero,this indicates
;end of the table.
;****************************************************************************
mov di,offset disptab ;Pointer to start of table
mov dh,0 ;Zero out top half of DX
vloop:
mov dl,[di] ;Get the interrupt number
cmp dl,0 ;If it's zero,we are done
je vdone ;so exit loop
add di,1 ;Advance pointer 1 byte
mov si,[di] ;Get pointer to description
call dvector ;Call the display routine
add di,2 ;Get the interrupt number
mov dl,[di] ;Advance to the next record
cmp dl,0 ;If it's zero,we are done
je vdone ;so exit loop
add di,1 ;Advance pointer 1 byte
mov si,[di] ;get pointer to description
call dvector ;Call the display routine
add di,2 ;Advance to the next record
jmp vloop
vdone: ;Print final CRLF
ret
vectors endp
;----------------------------------------------------------------------------
;Displays an interrupt vector.Display is in the form of
;,,:
;where ,and
;are all dexadecimal numbers
;Call with
;DX -interrupt number
;DS:SI -pointer to banner string
;----------------------------------------------------------------------------
dvector proc near
call dstring ;Display the string in DS:SI
call dbyte ;Display the byte in DL
call dspace ;Display a space
call dspace
;
mov al,dl ;move the interrupt number to AL
mov ah,35h ;Function is Get interrupt vector
int 21h
mov dx,bx ;Move BX to DX so we can display
call ddword ;double-word in ES:DX
call dEndFra
call dcrlf ;Display a newline
ret
dvector endp
;----------------------------------------------------------------------------
;DS:SI points to ASCII string to be printed
;----------------------------------------------------------------------------
dstring proc near
push si
push ax
dis: mov al,[si] ;Fetch the next character
cmp al,0 ;If it's zero,we are done
je disdone
call dchar ;If not,point it
inc si ;Advance pointer to nest char
jmp dis
disdone:pop ax
pop si
ret
dstring endp
;----------------------------------------------------------------------------
;ES:DX contains double word to be displayed
;----------------------------------------------------------------------------
ddword proc near
push dx ;Save offset temporarily
mov dx,es ;Move segment to DX
call dsword ;Display segment
call dcolon ;Print a ";"
; call dcrlf
pop dx ;Restore offset to DX
call dsword ;Display offset
ret
ddword endp
;----------------------------------------------------------------------------
;DX containes single word to be displayed
;----------------------------------------------------------------------------
dsword proc near
push dx ;Save low byte temporarily
mov dl,dh ;Move high byte to low byte
call dbyte ;Display high byte
pop dx ;Restore low byte to DL
call dbyte ;Display low byte
ret
dsword endp
;----------------------------------------------------------------------------
;DL contains byte to be displayed
;----------------------------------------------------------------------------
dbyte proc near
push ax ;Save any registers used
push dx
push si
push dx ;Save low nybble temporarily
push cx ;Save CX
mov cl,4 ;Set shift count to 4
shr dx,cl ;Shift high nybble into low nybble
and dx,0fh ;Mask out all but low nybble
mov si,dx ;Use low nybble as index into
mov al,hextab[si] ;hexadecimal character table
call dchar ;Display character
pop cx ;Restore CX
pop dx ;Restore low nybble
and dx,0fh ;Mask out all but low nybble
mov si,dx ;Use low nybble as an index into
mov al,hextab[si] ;hexadecimal character table
call dchar ;Display character
pop si ;Restore registers
pop dx
pop ax
ret
dbyte endp
;----------------------------------------------------------------------------
;Display a ":"
;----------------------------------------------------------------------------
dcolon proc near
mov al,':'
call dchar
ret
dcolon endp
;----------------------------------------------------------------------------
;Display a " "
;----------------------------------------------------------------------------
dspace proc near
mov al,' '
call dchar
ret
dspace endp
;----------------------------------------------------------------------------
;Display a Carriage Return/Line Feed
;----------------------------------------------------------------------------
dcrlf proc near
mov al,0dh
call dchar
mov al,0ah
call dchar
ret
dcrlf endp
;----------------------------------------------------------------------------
;Display the character contained in AL
;----------------------------------------------------------------------------
dchar proc near
push ax
push bx
mov bh,1
mov ah,0eh
int 10h
pop bx
pop ax
ret
dchar endp
;----------------------------------------------------------------------------
;Data define
;----------------------------------------------------------------------------
hextab db '0123456789ABCDEF',0
disptab db 05h ;Print screen
dw v05
db 19h ;Bootstrap loader
dw v19
db 08h ;Timer tick
dw v08
db 1ah ;Real_time clock
dw v1a
db 09h ;Keyboard input
dw v09
db 1bh ;CTRL_Break handler
dw v1b
db 0bh ;Comm.port 1
dw v0b
db 1ch ;Timer control
dw v1c
db 0ch ;Comm.port 0
dw v0c
db 1dh ;Pointer to video parameter table
dw v1d
db 0dh ;Hard disk controller
dw v0d
db 1eh ;Pointer to disk parameter table
dw v1e
db 0eh ;Floppy disk controller
dw v0e
db 1fh ;Pointer graphics character table
dw v1f
db 0fh ;Printer controller
dw v0f
db 20h ;Program terminate
dw v20
db 10h ;Video driver
dw v10
db 21h ;DOS universal function
dw v21
db 11h ;Equipment check
dw v11
db 2h ;Pointer to termination handler
dw v22
db 12h ;Memorey size check
dw v12
db 23h ;Pointer to Ctrl_C handler
dw v23
db 13h ;Disk driver
dw v13
db 24h ;Pointer to critical error handler
dw v24
db 14h ;Communications driver
dw v14
db 25h ;Absolute disk read
dw v25
db 15h ;Cassette driver
dw v15
db 26h ;Absolute disk write
dw v26
db 16h ;Keyboard driver
dw v16
db 27h ;Terminate and stay resident
dw v27
db 17h ;Printer driver
dw v17
db 2fh ;Print spooler
dw v2f
db 18h ;Rom basic
dw v18
db 0
dw 0
v05 db 186,5 dup (20h),'Print screen:',26 dup (20h),0
v08 db 186,5 dup (20h),'Timer tick controller:',17 dup (20h),0
v09 db 186,5 dup (20h),'Keyboard input:',24 dup (20h),0
v0b db 186,5 dup (20h),'Communication port 1:',18 dup (20h),0
v0c db 186,5 dup (20h),'Communication port 0:',18 dup (20h),0
v0d db 186,5 dup (20h),'Hard disk controller:',18 dup (20h),0
v0e db 186,5 dup (20h),'Floppy disk controller:',16 dup (20h),0
v0f db 186,5 dup (20h),'Printer controller:',20 dup (20h),0
v10 db 186,5 dup (20h),'Video driver:',26 dup (20h),0
v11 db 186,5 dup (20h),'Equipment check:',23 dup (20h),0
v12 db 186,5 dup (20h),'Memory size check:',21 dup (20h),0
v13 db 186,5 dup (20h),'Disk driver:',27 dup (20h),0
v14 db 186,5 dup (20h),'Communication driver:',18 dup (20h),0
v15 db 186,5 dup (20h),'Cassette driver:',23 dup (20h),0
v16 db 186,5 dup (20h),'Keyboard driver:',23 dup (20h),0
v17 db 186,5 dup (20h),'Printer driver:',24 dup (20h),0
v18 db 186,5 dup (20h),'ROM BASIC:',29 dup (20h),0
v19 db 186,5 dup (20h),'Bootstrap loader:',22 dup (20h),0
v1a db 186,5 dup (20h),'Real_time clock:',23 dup (20h),0
v1b db 186,5 dup (20h),'Ctrl_break handler:',20 dup (20h),0
v1c db 186,5 dup (20h),'Timer control:',25 dup (20h),0
v1d db 186,5 dup (20h),'Video parameter table:',17 dup (20h),0
v1e db 186,5 dup (20h),'Disk parameter:',24 dup (20h),0
v1f db 186,5 dup (20h),'Graphic character table:',15 dup (20h),0
v20 db 186,5 dup (20h),'Programe terminate:',20 dup (20h),0
v21 db 186,5 dup (20h),'DOS universal function:',16 dup (20h),0
v22 db 186,5 dup (20h),'Terminate vector:',22 dup (20h),0
v23 db 186,5 dup (20h),'Ctrl_C vector:',25 dup (20h),0
v24 db 186,5 dup (20h),'Critical error vector:',17 dup (20h),0
v25 db 186,5 dup (20h),'Absolute disk read:',20 dup (20h),0
v26 db 186,5 dup (20h),'Absolute disk write:',19 dup (20h),0
v27 db 186,5 dup (20h),'Terminate and stay resident:',11 dup (20h),0
v2f db 186,5 dup (20h),'Print spooler:',25 dup (20h),0
cseg ends
end start

四 基本的驻留程序
4.1 一个基本的COM程序
 DOS之下有两种形式的可执行文件,这两种文件分别是COM文件和EXE文件.其中, COM文件可以迅速地加载和执行,但是其大小不能超过64K字节,只能有一个段,代码段.而且起始地址为100H指令必须为程序的启动指令.EXE文件可以加载到许多个段中,因此程序的大小没有限制,但是程序加载的过程就比较慢,而且对于内存驻留程序来说还会造成更大的麻烦.
 以下是一个可以正确执行的COM文件,但其内容是空的;只是一个COM文件的框架,可以把你写的任何应用部分加在这个文件中,形成一个COM格式的内存驻留程序:
  ;Section 1
  cseg segment
   assume cs:cseg,ds:cseg
    org 100h
  ;Section 2
  start: 
   ret
  ;Section  3
  cseg ends
   end start
 上面的程序可以分成三部分,第一部分定义了代码段和数据段分别放在程序中的位置,以及执行代码的起始地址.第二部分是可执行的程序,在这个例子只一个RET指令而已.第三部分是程序包段的终结,其中END叙述包含了程序开始执行地址.
  若是把上面的程序经过汇编连接,你会发现所产生的COM文件只有一个字节长.这是因为所产生的COM文件没有程序段前缀(Programsegmetn  profix),因为在DOS下所有和COM文件都有相同的程序段前缀.当DOS加载一个COM文件到内存中时,就会自动地产生一份正确的程序段前缀.一个程序在执行的过程中,可以根据需要修改其程序段前缀,但是在一开始,所有COM文件的程序前缀都是相同的.下面是程序前缀的格式.
    偏移位置    含义
    0000H     程序终止处理子程序地址(INT 20H)
    0002H     分配段的结束地址,段值
    0004H     保留
    0005H     调用DOS的服务
    000AH     前一个父程序的IP和CS
    000EH     前一个父程序的CONTROL_C处理子程序地址
    0012H     前一个父程序包的硬件错误处理子程序地址
    0016H     保留
    002CH     环境段的地址值
    005EH     保留
    005CH     FCB1
    006CH `    FCB2
    0080H     命令行的参数和磁盘转移区域
4.2 一个最小的内存驻留程序
 上面的程序只是一个一般的DOS程序而已.并不是内存驻留的.以下是一个基本的内存驻留程序结构:
         ;Section 1
     cseg segment
      assume cs:cseg;ds:cseg
      org 100h
     start:    ;Section 2
      nop   
     done:    ;Section 3
      mov dx,offset done
      int 27h
         ;Section 4
     cseg ends
      end start
  和前一个程序相比,这个程序只是增加了一个DONE部分.这个部分使用了INT 27H这个中断调用,来终止并驻留在内存(Terminate and Stay Resident)中.使用INT 27H这个中断调用时,必须设定好一个指针,让这个指针指向内存中可以使用的部分,事实上,这就相当于设置一个COM文件可加载的位置.另外DOS还提供了INT 21H,AH=31H(驻留程序,Keep process),但是使用这个中断调用时,我们必须设定所保留的内存大小,而不是设定一个指针;另外这个中断调用会送出退出码.
 使用INT 27H时,必须设定一个指针指向可用存储位置的开头,以便让DOS用来加载稍后执行的程序.DOS本身有一个指针,这个指针是加载COM文件或EXE文件时的基准地址值.INT尿27H 会改变这个指针或为新的数值.同时造成新指针和旧指针之间的存储空间无法让DOS使用因此这样做会造成可用存储位置愈来愈少.
 调用INT 27H时所使用的指针是个FAR指针,其中DX存放的是位移指针(Offset pointer),它可以指到64K字节之内的范围.而DOS是段指针(Segment pointer),它可以指到IBM PC中640K字节的任何一个段.在上面的例子中,DS的内容不必另外设定,因为当COM文件加载时,DS的内容就CS的内容相同了.
 经常在编写汇编程序时,常犯的一个错误就是:把assume ds:cseg这个叙述误认为是,存放某一预设值到DS中,事实上,汇编语言程序中的Assume叙述不会产生任何的程序代码,这个功能是告诉汇编器做某些必要的假设,以便正确地汇编程序.譬如以下的程序:
     cseg segment
      .............
      assume ds:cseg
      mov ah,radix
      .............
     radix db 16
      .............
     cseg ends
  上面的程序汇编时,当汇编器看到mov ah,radix这个指令时,它就根据assume ds:cseg来产生一定形式的赋值指令.在面的Assume ds:cseg叙述是告诉汇编器,数据段就位于目前的代码段中.这是内存留程序的一项重要关键.如果DS的内容和CS不相同时,无论是否有assume 叙述,程序执行时都会失败.
4.3 改良的内存驻留程序
 上面所介绍的内存驻留程序实际上没有做任何事,只是驻留在内存中而已.事实上, 在START和END之间放入任何程序代码,都只会执行一次而已然后就永远驻留在内存中,除非是使用转移指令转到START的地址去,否则将永远无法被使用.还要注意一点,START的地址值并非固定不变,它会根据程序执行时计算机的状态而改变.
 下面的这个程序只是把需要驻留的程序代码装载好,但是并不会执行.
         ;Section 1
     cseg segment
      assume cs:cseg,ds:cseg
      org  100h
         ;Section 2
     start: 
      jmp initialize
         ;Section 3
     app_start:
      nop
     initialize:
         ;Section 4
      mov dx,offset initialize
      int  27h
         ;Section 5
     cseg ends
      end start
 上面的程序一开始执行时就传到initialize标志的地方,装置好驻留在内存的应用部分.原先的DONE已经改成initialize,而驻留在内存的程序代码则放在App_Start 和Initialize之间.
  另外,你也许注意到了,程序的起始地址并不是Initialize而是Start.这是因为所有COM程序的起始地址都是100H;而上面的程序中 Start是放在100H的地方.如果把Initialize放在End之后,Initialize就变成起始地址,但是这样的程序无法透过 EXE2BIN转换成COM文件了.如果无法产生COM文件时,那么就必须直接处理段的内容.
4.4 减少内存的额外负担
 到目前为止,都没有接触到程序前缀,当使用INT 27H时,事实上是把指针以前的东西都保留在内存中,这也包括了COM的程序段前缀.因为COM文件执行完毕后,才可以把程序段前缀移掉.
  从上面的事实可以看出:如果程序段前缀只能在COM装置程序结束后才可以移去,那么就可以由驻留在内存中的程序代码完成.要做到这一点,可以把整个程序往下移动256个字节.但又如何做到这一点呢?我们可以设定一个标志(Flag),用来指示这个程序是否执行过.如果这个驻留程序或是第一次执行时,就把整个程序往下移动256个字节,以便把程序段前缀移去.但是如果驻留程序在装置好之后,经过一段长时间仍然没有被执行时,怎么办呢?如果同时载入了好几个驻留程序时,双该如何呢?这些重要的事情都需要使用不同的程序代码来解决.如果说这些程序代码超出了256字节时,那么所占用的存储位置就超出程序段前缀所浪费的空间.有些人用一些比较简短的代码来解决这个问题,但是还是比较麻烦.因此对于大部分的内存驻留程序而言,除非存储空间太少,以至于256字节变得很重要,否则最好不要去处理程序段前缀,这样子会让你的程序简洁而且容易阅读.
4.5 使用驻留程序
 上面介绍了如何把程序加载到内存,并且让它永远留在内存中,接下来,介绍如何来使用驻留在内存中的程序.
  内存驻留程序的使用方法和它原先的设计有密切的关系.譬如,截获键盘输入的程序就必须通过键盘输入的软件中断,或是敲键盘所产生的硬件中断来使用.其它的驻留程序可能就必须靠:系统时钟,系统调用,或是其它的中断才有办法使用.这些驻留程序必须要和以上的使用方法连结;而且在驻留程序安装好之后,至少必须建立一种使用的管道,否则驻留程序将无法使用.
 IBM PC必须经由事件来驱动,譬如:键盘,系统时钟,或是软件中断.这些事件可以被截获,然后根据所发生的事件来执行一定的动作.因此必须让中断事件发生时,先执行我们的程序,而非系统的程序.
  譬如,当我们设计一个截获键盘输入的驻留程序时,就必须把驻留程序和执行键盘输入的系统调用连结起来.当DOS或是应用程序希望从键盘读取一个字符时,它就必须执行INT 16H调用.因此如果我们能够在调用INT 16H时,先执行我们的驻留程序,那么驻留程序就可能变成应用程序和操作系统间的桥梁.
 可以使用INT 21H中断调用中AH=25H来完成以上的要求.设置中断矢量可以更改INT 16H原先的中断矢量内容,让它改为指向我们的程序.譬如以下的例子所示:
     cseg segment
      assume cs:cseg,ds:cseg
      org  100h
     start: 
      jmp Initialize 
           ;Section 1
     new_keyboard_io proc far
      sti
      nop 
      iret
     new_keyboard_io endp 
           ;Section 2
     Initialize:
      mov dx,offset new_keyboard_io
      mov al,16h
      mov ah,25h
      int 21h
           ;Section 3
      mov dx,offset Initialize
      int 27h
     cseg ends
      end start
  上面的程序和4.3的程序结构是一样的,但是仍然有一些重要的改变.在Section 1和Section 2.在Section 1把驻留部分修改成子程序形式(Procedure),这样做是为了增加程序的可读性.另外,驻留部分多加了两个指令,STI和IRET.其中STI是设置中断标志(Set Interrupt Flag)和起始中断(Enable interrupts).
 当CPU发生中断时,它就关闭中断标志,因此CPU就不再接受中断.事实上,CPU会专心地为目前发生的中断服务.当CPU停止接受中断时,任何硬件中断的信号都会被忽略,譬如:键盘,时钟脉冲,磁盘机信号,调制解调器的中断.如果CPU一直不接受中断,那么就会漏掉一些重要的信息,计算机系统也可能因此而死机.因此虽然CPU可以停止接受中断一段时间,但是却不能够久.
 第二个重要的指令是IRET,从中断返回(Return from interrupt).IRET的功能和RET极相似,RET是用来从被调用的子程序中返回,而IRET则是用来从中断程序返回.但是使用IRET返回时,它会从堆栈中先取出返回的地址值,然后再取出CPU的状态标志(State Flag).CPU的状态标志在CPU接受中断时,会自动地推入堆栈中.因此执行IRET指令后,CPU的状态就恢复成未中断前的状态;也就是说CPU就可以继续接受外界的中断(CPU状态标志中断包括了中断标志).严格地说,STI和IRET在这个例子中都是多余的,但是对于实际的中断处理程序而言,这两个指令都很重要.
 另外,使用设置中断矢量的中断调用时,暂存器AL必须存入所要设置的中断矢量,而中断矢量指针则必须放到暂存器DS:DX中.
4.6 连接中断处理程序
 若是把前一节的程序拿来执行时,键盘是无法输入的,事实上,处理键盘的硬件中断处理程序会继续地读取敲入的字符,并且放到等待队列中,直到队列填满为止;但是由于读取等待队列的软件中断INT 16H已经被改变了,因此队列的内容就永远取不出来.
 现在写一个中断处理程序,这个中断处理程序只是调用原先的键盘中断处理程序,一旦做到这一点之后,接下来就可以根据键盘的输入做修改.以下就是调用原先键盘处理程序的驻留程序: 
     cseg segment
      assume cs:cseg,ds:cseg
      org  100h
     start: 
      jmp Initialize
     Old_Keyboard_IO dd ? 
           ;Section 1
     new_keyboard_io proc far
      sti     
           ;Section 2
      pushf
      assume ds:nothing
      call  Old_Keyboard_IO
      nop 
      iret
     new_keyboard_io endp 
           ;Section 3
     Initialize:
      assume&nbp;cs:cseg,ds:cseg
      mov bx,cs
      mov ds,bx
      mov al,16h
      mov ah,35h
      int 21h
      mov word ptr Old_Keyboard_IO,bx
      mov word ptr Old_Keyboard_IO[2],es
           ;End Section 3
      mov dx,offset new_keyboard_io
      mov al,16h
      mov ah,25h
      int 21h
      mov dx,offset Initialize
      int 27h
     cseg ends
      end start
 上面的程序中,第一部分是两个字(Double word),这是用来存放旧的键盘中断矢量.因为COM的程序都只限制在一个段中,因此数据段和代码段都在同一段中.而原先的中断处理程序和我们所编写的中断处理程序未必会在同一段中,所以必须使用双字来储存地址值.
 双字Old_Keyboard_IO可以放在驻留程序中的任何地方;但是一般来说,放在Jmp Initialize 之后会比较方便;因为如果必须使用DEBUG来检查程序的话,可以比较容易调试.
  上面程序中的第二部分是驻留程序的主体,其中包括了一个调用原先键盘中断处理程序的模拟中断.因为原先的键盘中断处理程序必须使用INT的方式调用,而不是使用CALL的指令调用;因此必须先使用PUSHF把CPU状态标志压入堆栈中,然后配合上CALL来模拟INT的动作.
 注意一点,assume ds:nothing这一行是汇编指示,而不是程序代码.它是用来告诉汇编器在产生下一行机器码时,不要更会目前DS的内容;这样做才可以让汇编器为下一个指令产生双字的地址值.
  当Call Old_Keyboard_IO指令执行时,控制权就转移到旧的键盘中断处理程序.而当这个中断调用执行完时,它就执行IRET指令,于是控制权又交还到目前的驻留程序.这样做,不但可以让原先的键盘中断程序包为我们工作,同时也可以掌握控制权.如果只使用IMP指令,跳到旧的键盘中断处理程序包去,而不把CPU状态标志推入堆栈中,那么一旦执行到IRET时,就真正返回到中断的状态.
 上面程序中的第三部分是启动代码部分,在这一部分中,设定好新的中断矢量,同时把旧的中断矢量存放在驻留程序代码中,以便让驻留程序使用.
4.7 检查驻留程序
 到目前为止,已经成功地把驻留程序加在应用程序和DOS的键盘输入之间;接下来可以修改输入的字符.在这一节中,我们准备截获键盘的输入,并且把"Y"改成"y","y"改成"Y".
 以下是程序代码:
     cseg segment
      assume cs:cseg,ds:cseg
      org  100h
     start: 
      jmp Initialize
     Old_Keyboard_IO dd ?           
     new_keyboard_io proc far
      assume cs:cseg,ds:cseg
      sti     
           ;Section 1
      cmp ah,0
      je ki0
      assume ds:nothing
      jmp  Old_Keyboard_IO
           ;Section 2
     ki0:
      pushf
      assume  ds:nothing
      call  Old_Keyboard_IO
      cmp  al,'y'
      jne  ki1
      mov  al,'y'
      jmp  kidone
     ki1: 
      cmp  al,'Y'
      jne  kidone
      mov  al,'y'
     kidone:      
      iret
     new_keyboard_io endp 
           ;Section 3
     Initialize:
      assume cs:cseg,ds:cseg
      mov bx,cs
      mov ds,bx
      mov al,16h
      mov ah,35h
      int 21h
      mov word ptr Old_Keyboard_IO,bx
      mov word ptr Old_Keyboard_IO[2],es
           ;End Section 3
      mov dx,offset new_keyboard_io
      mov al,16h
      mov ah,25h
      int 21h
      mov dx,offset Initialize
      int 27h
     cseg ends
      end start
  在面的程序第一部分主要是检查AH是否等于0(读取字符).如果AH不等于0,就用旧的中断处理程序来处理其它的功能:1H(读取键盘状态),2H(读取键盘标志).在这里,使用JMP指令,而非使用CALL来模拟软件中断;因此原先的中断处理程序结束后,就直接返回到中断前的状态.
 程序的第二部分是处理AH=0H时的情形.首先程序中断模拟一个软件中断来调用旧的键盘处理程序,是为了在读完字符之后,控制权能交还到我们的驻留程序,接下来的几行程序是检查读到的字符是不是"Y"和"y",如果是的话就修改它.
 可以借执行这个程序,来验证其是否正确.除此之外,也可以证明,在操作系统和应用程序之间可以加入一层控制码.这一层控制码可以先选择性地加强或取代某些DOS的功能,修改结果以满足我们的要求.
五 键盘输入扩充程序
有了前一节的基本驻留程序为基础,就可以建立起不同的应用程序.接下来,就写一个驻留程序,把用户敲入的字符,用一系列的字符来取代.这样可以减少用户的击键次数.
首先,先复习一下前一节的驻留程序的格式,如下所示:
cseg segment
assume cs:cseg,ds:cseg
org 100h
start:
jmp Initialize
Old_Keyboard_IO dd ?
;Section 1
new_keyboard_io proc far
sti
;Section 2
pushf
assume ds:nothing
call Old_Keyboard_IO
nop
iret
new_keyboard_io endp
;Section 3
Initialize:
assume cs:cseg,ds:cseg
mov bx,cs
mov ds,bx
mov al,16h
mov ah,35h
int 21h
mov word ptr Old_Keyboard_IO,bx
mov word ptr Old_Keyboard_IO[2],es
;End Section 3
mov dx,offset new_keyboard_io
mov al,16h
mov ah,25h
int 21h
mov dx,offset Initialize
int 27h
cseg ends
end start
只要New_keyboard_IO这个程序,就可以把以上的程序变成许多不同的键盘应用程序.在开始设计之前,必须解决一些问题.
首先,必须决定哪些键可以用来加以扩充.如果把一般的英文字母或是数目字做为扩充字符的话可能会出现一些问题.如果是对控制字符做扩充,应该不会有什么问题,但是DOS把某些控制字符视为特殊的功能.譬如Control_H,IBM PC本身有一组自己独有和增加字符(extended character),譬如:功能键(F1到F10),以及ALT键和其它组合所产生的字符等.这些增加字符通常都是使用在文书编辑程序中,这些字符比较适合用来作为扩充字符用.这组字符是由两个码组成,前面一个码永远是0,因此DOS可以很容易加以分辨.而且使用这些字符作为扩充字符对DOS的使用也不会产生太大的影响.下面是扩充字符组的第二个码大小:
1 2 Paoudo_NULL 3 4 5
6 7 8 9 10
11 12 13 14 15 Shift_Tab 16 Alt_Q 17 Alt_W 18 Alt_E 19 Alt_R 20 Alt_T 21 Alt_Y 22 Alt_U 23 Alt_I 24 Alt_O 25 Alt_P 26 27 28 29 30 Alt_A
31 Alt_S 32 Alt_D 33 Alt_F 34 Alt_G 35 Alt_H
36 Alt_J 37 Alt_K 38 Alt_L 39 40
41 42 43 44 Alt_Z 45 Alt_X
46 Alt_C 47 Alt_V 48 49 50
51 52 53 54 55
56 57 58 59 F1 60 F2
61 F3 62 F4 63 F5 64 F6 65 F7
66 F8 67 F9 68 F10 69 70
71 HOME 72 UpArrow 73 PgUp 74 75 LeftArrow
76 77 RightArrow 78 79 End 80 DownArrow
81 PgDn 82 Insert 83 Delete 84 Shift_F1 85 Shift_F2
86 Shift_F3 87 Shift_F4 88 Shift_F5 89 Shift_F6 90 Shift_F7
91 Shift_F8 92 Shift_F9 93 Shift_F10 94 Control_F1 95 Control_F2
96 Control_F3 97 Control_F4 98 Control_F5 99 Control_F6 100 Control_F7
101 Control_F8 102 Control_F9 103 Control_F10 104 Alt_F1 105 Alt_F2
106 Alt_F3 107 Alt_F4 108 Alt_F5 109 Alt_F6 110 Alt_F7
111 Alt_F8 112 Alt_F9 113 Alt_F10 114 Control_PrtSc 115 Control_LArrow
116 Control_RArrow 117 Control_End 118 Control_PgDn 119 Control_Home 120 Alt_1
121 Alt_2 122 Alt_3 123 Alt_4 124 Alt_5 125 Alt_6
126 Alt_7 127 Alt_8 128 Alt_9 129 Alt_0 130 Alt_Hyphan
131 Alt_Space 132 Control_PgUp
接下来,需要决定把扩充字符扩充成什么样的字符串.譬如,所扩充的字符串以什么作结尾?有一个可能的选择是:回车键(Carriage Return,ASCII码0DH).这种选择很合乎逻辑,因为一般的指令都能是以回车键做结尾.但是,如果选择回车键名做扩充字符串的结尾,那么就很难表示许多行的扩充字符串.另外一个选择是使用$作为扩充字符串的结尾.但是,因为有些DOS的系统调用使用$作为字符结尾;因此如果采用$时,那么扩充字符串中就不能有$出现.
C语言中都是采用ASCII码的0做为字符串的结尾,这种形式的字符串称为ASCII字符串(ASCII零结尾).使用ASCII字符串格式,就可以表示所有的可见字符和不可见字符,因为从键盘不可能输入ASCII码为0的字符.
下面的例子中,把F1这个键(扩充码59)定义为DIR指令.也可以把F1定义成以下的指令:
MASM MACRO;
LINK MACRO;
EXE2BIN MACRO.EXE MACRO.COM;
上面的指令中,每一行都是以回车键作结尾的.
最后要做的是,解决将扩充的字符返回给DOS的问题.通常每当在键盘敲入一个键时,DOS就会从键盘输入队列取得一个字符.因此必须设法欺骗DOS,让它接受一连串的字符.
DOS借检查键盘的状态来判断,是否有字符输入,ROM BIOS上的键盘输入功能在没有输入字符时就把ZF(Zero Flag)设定为1,否则就把ZF设定为0.如果可以控制这个功能,反复地欺骗DOS目前有字符要输入,然后把预的字符串传回给DOS,那么就可以让 DOS接受任何数量的字符.
5.1 基本的扩充程序
可以把上面的空的New_Keyboard_IO程序,改用以下的程序来代替.
New_Keyboard_IO proc far
sti
cmp ah,0 ;A read request?
je ksread
cmp ah,1 ;A status request?
je ksstat
assume ds:nothing ;Let original routine
jmp Old_Keyboard_IO ;Do remaining subfunction
ksRead:
call keyRead ;Get next char to return
iret
ksstat:
call keyStat ;GetStatus
ret 2 ;It's important!!
New_Keyboard_IO endp
上面的New_Keyboard_IO程序中,把0H(读取字符)和1H(取得键盘状态)这两项功能自行处理.这个程序很简单,但是其中有一个关键点.当我们处理取得键盘状态的功能时,因为原先的键盘中断处理程序是利用ZF返回键盘状态,因此程序包中也必须保有这种特性,如果使用IRET返回的话,那么设定好ZF就会因为CPU状态标志从堆栈中取出,而恢复成未中断前的状态.
为了解决这个问题可以使用RET的参数来设置.这个参数是用来指示从堆栈中取出多少个字节.通常这是用在高级语言的子程序返回时,用来从堆栈中除去一些参数或是变数.在这里我们希望用来移去原先中断时堆栈的CPU状态,这样才有办法把改变的ZF传回,因此在这里使用了RET 2这个指令.
上面的程序码中调用到Keyread和KeyStat这两个子程序,其内容如下所示:
assume ds:nothing
;If expansion is in progress,return a fake status
;of ZF=0,indicatin gthat a character is ready to be
;read,If expansion is not in progress,then return
;the actual status from the keyboard
KeyStat proc
cmp cs:current,0
jne FakeStat
pushf ;Let original routine
call Old_Keyboard_IO ;get keyboard status
ret
FakeStat:
mov bx,1 ;Fake a "char ready"
cmp bx,0 ;by clearing ZF
KeyStat endp
;Read a character from the keyboard input queue,
;if not expanding or the expansion string.
;if expansion is in progress
KeyRead proc
cmp cs:current,0
jne ExpandChar
ReadChar:
mov cs:current,0 ;Slightly peculiar
pushf ;Let original routine
call Old_Keyboard_IO ;Get keyboard status
cmp al,0
je Extended
ReadDone:
ret
Expanded:
cmp ah,59 ;Is this character to expand?
jne ReadDone ;If not,then return it normally
;If so,then start expanding
mov cs:current,offset string
ExpandChar:
push si
mov si,cs:current
mov al,cs:[si]
inc cs:current
pop si
cmp al,0 ;Is this end of string?
je ReadChar ;If so,then read a real char?
ret
KeyRead endp
;Pointer to where we are in the expansion string
current dw 0
;String we will return when an F1 is typed
;0DH is ASCII carriage return
string db 'DIR',0dh,0
上面的程序中,使用了一个指针current,这个指针指向传给DOS的下一个字符.如果current等于0时,就表示扩充字符没了.如果 current不等于0,那么current所指的字符就会被传回,除非所指到的字符是ASCII 0,如果current所指到的字符是ASCII 0,那么就必须把current设定成0.
状态检查程序KeyStat和字符输入程序KeyRead都各有两个部分,一部分是当current等于0,另一部分则是当current等于0.
如果current等于0,也就是没有扩充字符时,那么状态检查程序就需调用旧的键盘输入程序,来检查目前键盘输入队列的状态.如果current不等于0,ZF就必须设定成0,以表示目前有字符输入.ZF要设定成0或1,可以先执行某一运算让结果为0或非0即可.
键盘输入程序是整个程序最复杂的部分.这个程序决定了下个送给DOS的字符是什么.如果扩充字符送完时,就调用旧的键盘输入程序取得下一个输入的字符.无论从键盘输入的字符是什么,都必须检查是否是希望扩充的字符.键盘输入程序是把输入的结果放在寄存器AL中.如果输入的字符是增加字符时(如F1),那么 AL的内容是0,增加的字符码则放在AH中.
如果读到的字符是希望扩充的字符F1,那么就必须开始进行扩充工作.这时候就必须把指针current指到扩充字符串的开头.大多数人常犯的一个错误是: 使用mov cs:current,string而不是mov cs:current,offset string.这两者的差别在于前者是错误的,因为它的意思是把一个字节的内容移到一个字节之中,汇编器会强迫两者的形式吻合.后者则是正确的,因为我们希望做的是把式string指向的地址值移到current之中.
当我们在进行扩充时,就把指针current所指的字节内容移到AL中,只要AL的内容不是0,就不必管AH的内容是什么.如果AL是0的话,就表示已经到了扩充字符的结尾了.这表示不应该传回0,而必须重新调用Old_Keyboard_IO ,以便从键盘取得输入字符.
在程序包KeyRead中有一行指令比较特殊,你也许注意到了,在进入KeyRead,当确定current为0时,接下来又把current设定成0. 这样做虽然有些奇怪,却没有任何伤害;但是对于扩充字符串到达结尾时,却很有用.当我们到达扩充字符串的结尾时,current的内容将指到字符串结尾的下一个位置,而不是0.因此必把current设定为0,可以先跳到某一位置把current设定为0,然后再跳到ReadChar.而采取前面程序的做法时,只是浪费一行毫无伤害的指令,却可以使程序变得简明.
在这个程序中,每次使用到内存的内容时,都必须牵涉到段值,这一点相当重要.当计算机的控制权转移到我们的程序中时,我们对于DS的内容是不知道的.但是有两件事可以确定:第一,DS的内容对我们的程序几乎没有任何用;第二,DS的内容对于被中断的程序可能很重要.因此我们必须保证每次使用到内存位置时, 都是使用目前的段,亦即以目前的CS值为标准.必须要确定:如果使用到任何寄存器的话那么在程序结束前,必须恢复其值.
5.2 多键扩充程序
上面的程序是把某一个特殊键扩充成一个字符串.如果要把一组特殊键扩充成其个别的扩充字符串,该如何做呢?
一个比较常见的做法是,修改上面的程序,让它接受被扩充字符以被扩充字符串为参数.譬如,如果这个程序名为MACRO,那么可以在AUTOEXEC.BAT中定义以下的指令:
........
MACRO F1 DIR
MACRO F2 DIR/W
MACRO F3 DIR *.ASM
MACRO F4 DIR *.COM
MACRO F5 DIR *.EXE
........
这种做法是把MACRO这程序一个个留在内存中,至于每一个所做的扩充字符串则分别定义在AUTOEXEC.BAT中,因此可以AUTOEXEC.BAT 以的内容.来改变扩充字符的意思.每当执行AUTOEXEC.BAT的MACRO时,就把一个新的键盘程序和BIOS中的键盘处理程序连结起来.第二次执行MACRO则是在新的键盘处理程序上加上第二层的键盘处理程序,以后依次类推.每一个输入字符都必须经过一层一层的键盘处理程序,以过滤出被扩充字符.
这种键盘程序一层一层加上去的做法只能使用在希望被扩充字符不多时,因为 每一个希望被扩充字符需要将近一百个字节的驻留程序代码,如果要为128个功能键产生个别的扩充字符时,那么就要耗费13K字节的内存,显然可以采纳别的比较节省内存的方法.
如果可以在一个小程序中辨认出一个字符,那么也应该可以辨认出一个以上的字符.然后使用所辨认出的字符转换成索引值.再从一个由字符串所组的表格中,找出所扩充的字符串.
一个字符串本身占用一个字节,而指到字符串的指针则占用两个字节,如果有128个字符需要扩充时,则总共需要284个字节.另外原先的程序大约需要增加 50个字节.因此整个程序的大小就变成大约半K字节.假设每一个扩充字符串占用20个字节,那么128个扩充键就需2.5K字节,这和程序代码的0.5K 字节加起来,总共也不过3K字节,还比前一种方法少10K字节.
上面的单键扩充程序转换成多键扩充程序时,只要修改其中的KeyRead这个程序以及数据区的内容即可.以下就是修改后的内容:
;Read a character from the keyboard input queue,
;if not expanding or the expansion string.
;if expansion is in progress
KeyRead proc
cmp cs:current,0
jne ExpandChar
ReadChar:
mov cs:current,0 ;Slightly peculiar
pushf ;Let original routine
call Old_Keyboard_IO ;Get keyboard status
cmp al,0
je Extended
jmp ReadDone
Extended:
cmp byte ptr cs:[si],0 ;Is this end of table?
je ReadDone
cmp ah,cs:[si]
je StartExpand
add si,3
jmp NextExt
StartExtend:
push bx
add si,1
mov bx,cs:[si]
mov cs:current,bx ;If so,start expanding
ExpandChar:
mov si,cs:current
mov al,cs:[si]
inc cs:current
cmp al,0 ;Is this end of string?
je ReadChar ;If so,then read a real char?
ReadDone:
pop si
ret
KeyRead endp
current dw 0
KeyTab db 59
dw dir_cmd
db 60
dw dir_wide
db 61
dw dir_asm
db 62
dw dir_com
db 63
dw dir_exe
db 50
dw make_macro
db 0 ;This must be last in key table
dir_cmp db 'DIR',0dh,0
dir_wide db 'DIR/W',0dh,0
dir_asm db 'DIR *.ASM',0dh,0
dir_com db 'DIR *.COM',0dh,0
dir_exe db 'DIR *.EXE',0dh,0
make_macro db 'MASM MACRO;',0dh,0
db 'LINK MACRO;',0dh,0
db 'EXE2BIN MACRO.EXE MACRO.COM',0dh,0
上面的程序是节省了一点的时间,但是对于和用户界面而言则变得比较不方便,因为把功能键的定义移到汇编语言的程序中.但是可以高法改写这个程序,让它在初次执行时从一个文件装载所定义的字符患上 .这样做并不会改变驻留程序代码的大小,因为装载文件的起始码可以在执行完后抛弃,因此不必占用驻留程序代码的位置.
5.3 单键扩充程序
以下是单键扩充成命令字符串的程序内容:
cseg segment
assume cs:cseg,ds:cseg
org 100h
Start:
jmp Initialize
Old_Keyboard_IO dd ?
assume ds:nothing
New_Keyboard_IO proc far
sti
cmp ah,0 ;Is this call a read request?
je ksRead
cmp ah,1 ;Is it a status request?
je ksStat ;Let original routine
jmp Old_Keyboard_IO ;handle remianing subfunction
ksRead:
call KeyRead ;Get next character to return
iret
ksStat:
call KeyStat ;Return appropriate status
ret 2 ;Important!!!
New_Keyboard_IO endp
KeyRead Proc near
cmp cs:current,0
jne ExpandChar
ReadChar:
mov cs:current,0 ;Slightly peculiar
pushf ;Let original routine
call Old_Keyboard_IO ;Determine keyboard status
cmp al,0
je Extended
ReadDone:
ret
Extended:
cmp ah,59 ;Is this character to expand?
jne ReadDone ;If not,return it normally
;If so,start expanding
mov cs:current,offset String
ExpandChar:
push si
mov si,cs:current
mov si,cs:[si]
inc cs:current
pop si
cmp al,0 ;Is this end of string?
je ReadChar ;If so,then read a real char?
ret
KeyRead endp
KeyStat proc near
cmp cs:current,0
jne FakeStat
pushf ;Let original routine
call Old_Keyboard_IO ;Determine keyboard
ret
FakeStat:
mov bx,1 ;Fake a "Character ready" by clearing ZF
cmp bx,0
ret
KeyStat endp
current dw 0
string db 'masm macro;',0dh
db 'link macro;',0dh
db 'exe2bin macro.exe macro.com',0dh,0
Initialize:
assume cs:cseg,ds:cseg
mov bx,cs
mov ds,bx
mov al,16h
mov ah,35h
int 21h
mov word ptr Old_Keyboard_IO,bx
mov word ptr Old_Keyboard_IO,es
mov dx,offset New_Keyboard_IO
mov al,16h
mov ah,25h
int 21h
mov dx,offset Initialize
int 27h
cseg ends
end Start
5.4 一般的键盘扩充程序Mactab.asm
以下和程序可以把由表的查询,将任意娄的扩充键扩充成命令字符串:
cseg segment
assume cs:cseg,ds:cseg
org 100h
Start:
jmp Initialize
Old_Keyboard_IO dd ?
assume ds:nothing
cmp byte ptr cs:[si],0 ;end of table
je ReadDone
cmp ah,cs:[si]
je StartExpand
add si,3
jmp NextExt
StartExpand:
add si,1
push bx
mov bx,cs:[si]
mov cs:current,bx
pop bx
ExpandChar:
mov si,cs:current
mov al,cs:[si]
inc cs:current
cmp al,0 ;end of string 2
je ReadChar ;then read real char
ReadDone:
pop si
ret 3
KeyRead endp
current dw 0
KeyTab db 59
dw dir_cmd
db 60
dw dir_wide
db 61
dw dir_asm
db 62
dw dir_com
db 63
dw dir_exe
db 50
dw make_macro
db 0 ;This must be last in key table
dir_cmp db 'DIR',0dh,0
dir_wide db 'DIR/W',0dh,0
dir_asm db 'DIR *.ASM',0dh,0
dir_com db 'DIR *.COM',0dh,0
dir_exe db 'DIR *.EXE',0dh,0
make_macro db 'MASM MACRO;',0dh,0
db 'LINK MACRO;',0dh,0
db 'EXE2BIN MACRO.EXE MACRO.COM',0dh,0
New_Keyboard_IO proc far
sti
cmp ah,0 ;Is this call a read request?
je ksRead
cmp ah,1 ;Is it a status request?
je ksStat ;Let original routine
jmp Old_Keyboard_IO ;handle remianing subfunction
ksRead:
call KeyRead ;Get next character to return
iret
ksStat:
call KeyStat ;Return appropriate status
ret 2 ;Important!!!
New_Keyboard_IO endp
KeyStat proc near
cmp cs:current,0
jne FakeStat
pushf ;Let original routine
call Old_Keyboard_IO ;Determine keyboard
ret
FakeStat:
mov bx,1 ;Fake a "Character ready" by clearing ZF
cmp bx,0
ret
KeyStat endp
;Read a character from the keyboard input queue,
;if not expanding or the expansion string.
;if expansion is in progress
KeyRead proc
cmp cs:current,0
jne ExpandChar
ReadChar:
mov cs:current,0 ;Slightly peculiar
pushf ;Let original routine
call Old_Keyboard_IO ;Get keyboard status
cmp al,0
je Extended
ReadDone:
ret
Expanded:
cmp ah,59 ;Is this character to expand?
jne ReadDone ;If not,then return it normally
;If so,then start expanding
mov cs:current,offset string
ExpandChar:
push si
mov si,cs:current
mov al,cs:[si]
inc cs:current
pop si
cmp al,0 ;Is this end of string?
je ReadChar ;If so,then read a real char?
ret
KeyRead endp
Initialize:
assume cs:cseg,ds:cseg
mov bx,cs
mov ds,bx
mov al,16h
mov ah,35h
int 21h
mov word ptr Old_Keyboard_IO,bx
mov word ptr Old_Keyboard_IO,es
mov dx,offset New_Keyboard_IO
mov al,16h
mov ah,25h
int 21h
mov dx,offset Initialize
int 27h
cseg ends
end Start

- 作者: hsee 2005年06月16日, 星期四 14:28  回复(5) |  引用(1) 加入博采

汇编语言的准备知识(2)
汇编指令的操作数可以是内存中的数据, 如何让程序从内存中正确取得所需要的数据就是对内存的寻址.

INTEL 的CPU 可以工作在两种寻址模式:实模式和保护模式. 前者已经过时,就不讲了, WINDOWS 现在是32位保护模式的系统, PE 文件就基本是运行在一个32位线性地址空间, 所以这里就只介绍32位线性空间的寻址方式.

其实线性地址的概念是很直观的, 就想象一系列字节排成一长队,第一个字节编号为0, 第二个编号位1, .... 一直到4294967295(十六进制FFFFFFFF,这是32位二进制数所能表达的最大值了). 这已经有4GB的容量! 足够容纳一个程序所有的代码和数据. 当然, 这并不表示你的机器有那么多内存. 物理内存的管理和分配是很复杂的内容, 初学者不必在意, 总之, 从程序本身的角度看, 就好象是在那么大的内存中.

在INTEL系统中, 内存地址总是由"段选择符:有效地址"的方式给出.段选择符(SELECTOR)存放在某一个段寄存器中, 有效地址则可由不同的方式给出. 段选择符通过检索段描述符确定段的起始地址, 长度(又称段限制), 粒度, 存取权限, 访问性质等. 先不用深究这些, 只要知道段选择符可以确定段的性质就行了. 一旦由选择符确定了段, 有效地址相对于段的基地址开始算. 比如由选择符1A7选择的数据段, 其基地址是400000, 把1A7 装入DS中, 就确定使用该数据段. DS:0 就指向线性地址400000. DS:1F5278 就指向线性地址5E5278. 我们在一般情况下, 看不到也不需要看到段的起始地址, 只需要关心在该段中的有效地址就行了. 在32位系统中, 有效地址也是由32位数字表示, 就是说, 只要有一个段就足以涵盖4GB线性地址空间, 为什么还要有不同的段选择符呢? 正如前面所说的, 这是为了对数据进行不同性质的访问. 非法的访问将产生异常中断, 而这正是保护模式的核心内容, 是构造优先级和多任务系统的基础. 这里有涉及到很多深层的东西, 初学者先可不必理会.

有效地址的计算方式是: 基址+间址*比例因子+偏移量. 这些量都是指段内的相对于段起始地址的量度, 和段的起始地址没有关系. 比如, 基址=100000, 间址=400, 比例因子=4, 偏移量=20000, 则有效地址为:

100000+400*4+20000=100000+1000+20000=121000. 对应的线性地址是400000+121000=521000. (注意, 都是十六进制数).

基址可以放在任何32位通用寄存器中, 间址也可以放在除ESP外的任何一个通用寄存器中. 比例因子可以是1, 2, 4 或8. 偏移量是立即数. 如: [EBP+EDX*8+200]就是一个有效的有效地址表达式. 当然, 多数情况下用不着这么复杂, 间址,比例因子和偏移量不一定要出现.

内存的基本单位是字节(BYTE). 每个字节是8个二进制位, 所以每个字节能表示的最大的数是11111111, 即十进制的255. 一般来说, 用十六进制比较方便, 因为每4个二进制位刚好等于1个十六进制位, 11111111b = 0xFF. 内存中的字节是连续存放的, 两个字节构成一个字(WORD), 两个字构成一个双字(DWORD). 在INTEL架构中, 采用small endian格式, 即在内存中,高位字节在低位字节后面. 举例说明:十六进制数803E7D0C, 每两位是一个字节, 在内存中的形式是: 0C 7D 3E 80. 在32位寄存器中则是正常形式,如在EAX就是803E7D0C. 当我们的形式地址指向这个数的时候,实际上是指向第一个字节,即0C. 我们可以指定访问长度是字节, 字或者双字. 假设DShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifEDX]指向第一个字节0C:

mov AL, byte ptr DShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifEDX] ;把字节0C存入AL
mov AX, word ptr DShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifEDX] ;把字7D0C存入AX
mov EAX, dword ptr DShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifEDX] ;把双字803E7D0C存入EAX

在段的属性中,有一个就是缺省访问宽度.如果缺省访问宽度为双字(在32位系统中经常如此),那么要进行字节或字的访问,就必须用byte/word ptr显式地指明.

缺省段选择:如果指令中只有作为段内偏移的有效地址,而没有指明在哪一个段里的时候,有如下规则:

如果用ebp和esp作为基址或间址,则认为是在SS确定的段中;
其他情况,都认为是在DS确定的段中。

如果想打破这个规则,就必须使用段超越前缀。举例如下:

mov eax, dword ptr [edx] ;缺省使用DS,把DShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifEDX]指向的双字送入eax
mov ebx, dword ptr EShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifEDX] ;使用ES:段超越前缀,把EShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifEDX]指向的双字送入ebx

堆栈:

堆栈是一种数据结构,严格地应该叫做“栈”。“堆”是另一种类似但不同的结构。SS 和 ESP 是INTEL对栈这种数据结构的硬件支持。push/pop指令是专门针对栈结构的特定操作。SS指定一个段为栈段,ESP则指出当前的栈顶。push xxx 指令作如下操作:

把ESP的值减去4;
把xxx存入SS http://www.netxeyes.org/image/bbs3000/angel_smile.gifESP]指向的内存单元。

这样,esp的值减小了4,并且SShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifESP]指向新压入的xxx. 所以栈是“倒着长”的,从高地址向低地址方向扩展。pop yyy 指令做相反的操作,把SShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifESP]指向的双字送到yyy指定的寄存器或内存单元,然后把esp的值加上4。这时,认为该值已被弹出,不再在栈上了,因为它虽然还暂时存在在原来的栈顶位置,但下一个push操作就会把它覆盖。因此,在栈段中地址低于esp的内存单元中的数据均被认为是未定义的。

最后,有一个要注意的事实是,汇编语言是面向机器的,指令和机器码基本上是一一对应的,所以它们的实现取决于硬件.有些看似合理的指令实际上是不存在的,比如:

mov DShttp://www.netxeyes.org/image/bbs3000/angel_smile.gifedx], dshttp://www.netxeyes.org/image/bbs3000/angel_smile.gifecx] ;内存单元之间不能直接传送
mov DS, 1A7 ;段寄存器不能直接由立即数赋值
mov EIP, 3D4E7 ;不能对指令指针直接操作.

- 作者: hsee 2005年06月16日, 星期四 14:03  回复(5) |  引用(1) 加入博采