﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-jjczlzn</title><link>http://www.cnitblog.com/jjczlzn/</link><description /><language>zh-cn</language><lastBuildDate>Wed, 29 Apr 2026 05:57:17 GMT</lastBuildDate><pubDate>Wed, 29 Apr 2026 05:57:17 GMT</pubDate><ttl>60</ttl><item><title>error C2533:constructors not allowed a return type</title><link>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/175.html</link><dc:creator>生活，工作，思考</dc:creator><author>生活，工作，思考</author><pubDate>Fri, 20 May 2005 07:20:00 GMT</pubDate><guid>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/175.html</guid><wfw:comment>http://www.cnitblog.com/jjczlzn/comments/175.html</wfw:comment><comments>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/175.html#Feedback</comments><slash:comments>21</slash:comments><wfw:commentRss>http://www.cnitblog.com/jjczlzn/comments/commentRss/175.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jjczlzn/services/trackbacks/175.html</trackback:ping><description><![CDATA[VC++ 6.0编程的时候,遇到如下奇怪的错误,而且构造函数并没有返回值:<BR>error C2533: 'cirqueue::cirqueue' : constructors not allowed a return type<BR>好不容易才发现,在类定义或者申明时,结束的地方忘了加个';<img src ="http://www.cnitblog.com/jjczlzn/aggbug/175.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jjczlzn/" target="_blank">生活，工作，思考</a> 2005-05-20 15:20 <a href="http://www.cnitblog.com/jjczlzn/archive/2005/05/20/175.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>猎人和猎狗的故事(an old story)!!!</title><link>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/172.html</link><dc:creator>生活，工作，思考</dc:creator><author>生活，工作，思考</author><pubDate>Fri, 20 May 2005 03:57:00 GMT</pubDate><guid>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/172.html</guid><wfw:comment>http://www.cnitblog.com/jjczlzn/comments/172.html</wfw:comment><comments>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/172.html#Feedback</comments><slash:comments>38</slash:comments><wfw:commentRss>http://www.cnitblog.com/jjczlzn/comments/commentRss/172.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jjczlzn/services/trackbacks/172.html</trackback:ping><description><![CDATA[<P>猎人和猎狗的故事(an old story)!!!</P>
<P>(这条文章已经被阅读了 256 次) 时间：2003/10/27 12:58pm　来源：gogo </P>
<P><BR>猎人和猎狗的故事<BR>一、目标<BR>　　一条猎狗将兔子赶出了窝，一直追赶它，追了很久仍没有捉到。<BR>　　牧羊看到此种情景，讥笑猎狗说：“你们两个之间小的反而跑得快得多。”<BR>　　猎狗回答说：“你不知道我们两个跑的目的是完全不同的 ！我仅仅为了一顿饭而跑，他却是为了性命而跑呀！”</P>
<P>二、动力</P>
<P>　　这话被猎人听到了，猎人想：猎狗说的对啊，那我要想得到更多的猎物，得想个好法子。<BR>　　于是，猎人又买来几条猎狗，凡是能够在打猎中捉到兔子的，就可以得到几根骨头，捉不到的就没有饭吃。这一招果然有用，猎狗们纷纷去努力追兔子，因为谁都不愿意看着别人有骨头吃，自己没的吃。就这样过了一段时间，问题又出现了。大兔子非常难捉到，小兔子好捉。但捉到大兔子得到的奖赏和捉到小兔子得到的骨头差不多，猎狗们善于观察，发现了这个窍门，专门去捉小兔子。慢慢地，大家都发现了这个窍门。猎人对猎狗说最近你们捉的兔子越来越小了，为什么猎狗们说反正没有什么大的区别，为什么费那么大的劲去捉那些大的呢？</P>
<P>三、长期的骨头</P>
<P>　　猎人经过思考后，决定不将分得骨头的数量与是否捉到兔子挂钩，而是采用每过一段时间，就统计一次猎狗捉到兔子的总重量的方法。按照重量来评价猎狗，决定其在一段时间内的待遇。<BR>　　于是猎狗们捉到兔子的数量和重量都增加了。<BR>　　猎人很开心。但是过了一段时间，猎人发现，猎狗们捉兔子的数量又少了，而且越有经验的猎狗，捉兔子的数量下降的就越利害。于是猎人又去问猎狗。<BR>　　猎狗说：“我们把最好的时间都奉献给了您，主人，但是我们随着时间地推移会变老，当我们捉不到兔子的时候，您还会给我们骨头吃吗？”</P>
<P>四、骨头与肉兼而有之</P>
<P>　　猎人做了论功行赏的决定。分析与汇总了所有猎狗捉到兔子的数量与重量，规定如果捉到的兔子超过了一定的数量后，即使捉不到兔子，每顿饭也可以得到一定数量的骨头。猎狗们都很高兴，大家都努力去达到猎人规定的数量。一段时间过后，终于有一些猎狗达到了猎人规定的数量。这时，其中有一只猎狗说：“我们这么努力，只得到几根骨头，而我们捉的猎物远远超过了这几根骨头，我们为什么不能给自己捉兔子呢”于是，有些猎狗离开了猎人，自己捉兔子去了。<BR>五 有权分享猎人意识到猎狗正在流失，并且那些流失的猎狗像野狗一般和自己的猎狗抢兔子。情况变得越来越糟，猎人不得已引诱了一条野狗，问他到底野狗比猎狗强在那里。野狗说：“猎狗吃的是骨头，吐出来的是肉啊！”接着又道：“也不是所有的野狗都顿顿有肉吃，大部分最后骨头都没的舔！不然也不至于被你诱惑。”于是猎人进行了改革，使得每条猎狗除基本骨头外，可获得其所猎兔肉总量的n%，而且随着服务时间加长，贡献变大，该比例还可递增，并有权分享猎人总兔肉的m%。就这样，猎狗们与猎人一起努力，将野狗们逼得叫苦连天，纷纷强烈要求重归猎狗队伍。</P>
<P>　　故事还在继续</P>
<P>　　只有永远的利益，没有永远的朋友</P>
<P>　　日子一天一天地过去，冬天到了，兔子越来越少，猎人们的收成也一天不如一天。而那些服务时间长的老猎狗们老得不能捉到兔子，但仍然在无忧无虑地享受着那些他们自以为是应得的大份食物。终于有一天猎人再也不能忍受，把它们扫地出门，因为猎人更需要身强力壮的猎狗……</P>
<P>　　Birth of Micro Bone Co.</P>
<P>　　被扫地出门的老猎狗们得到了一笔不菲的赔偿金，于是他们成立了Micro Bone公司。他们采用连锁加盟的方式招募野狗，向野狗们传授猎兔的技巧，他们从猎得的兔子中抽取一部分作为管理费。当赔偿金几乎全部用于广告后，他们终于有了足够多的野狗加盟。公司开始赢利。一年后，他们收购了猎人的家当。</P>
<P>　　Development of Micro Bone Co.</P>
<P>　　Micro Bone公司许诺给加盟的野狗能得到公司n%的股份。这实在是太有诱惑力了。这些自认为是怀才不遇的野狗们都以为找到了知音：终于做公司的主人了，不用再忍受猎人们呼来唤去的不快，不用再为捉到足够多的兔子而累死累活，也不用眼巴巴地乞求猎人多给两根骨头而扮得楚楚可怜。这一切对这些野狗来说，比多吃两根骨头更加受用。于是野狗们拖家带口地加入了Micro Bone，一些在猎人门下的年轻猎狗也开始蠢蠢欲动，甚至很多自以为聪明实际愚蠢的猎人也想加入。好多同类型的公司像雨后春笋般地成立了，Bone Ease，Bone.com，China Bone……一时间，森林里热闹起来。明星的诞生猎人凭借出售公司的钱走上了老猎狗走过的路，最后千辛万苦地要与Micro Bone公司谈判的时候，老猎狗出人意料地答应了猎人，把Micro Bone公司卖给了他。老猎狗们从此不再经营公司，转而开始写自传《老猎狗的一生》，又写：《如何成为出色的猎狗》、《如何从一只普通猎狗成为一只管理层的猎狗》、《猎狗成功秘诀》、《成功猎狗500条》、《穷猎狗，富猎狗》，并将老猎狗的故事搬上屏幕，取名《猎狗花园》，3只老猎狗成了家喻户晓的明星。收版权费，没有风险，利润更高。</P>
<P>ps：干活的总是拿得少的，拿得多的都是不干活的。 <BR></P><img src ="http://www.cnitblog.com/jjczlzn/aggbug/172.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jjczlzn/" target="_blank">生活，工作，思考</a> 2005-05-20 11:57 <a href="http://www.cnitblog.com/jjczlzn/archive/2005/05/20/172.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个人的奋斗历程 </title><link>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/171.html</link><dc:creator>生活，工作，思考</dc:creator><author>生活，工作，思考</author><pubDate>Fri, 20 May 2005 03:39:00 GMT</pubDate><guid>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/171.html</guid><wfw:comment>http://www.cnitblog.com/jjczlzn/comments/171.html</wfw:comment><comments>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/171.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.cnitblog.com/jjczlzn/comments/commentRss/171.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jjczlzn/services/trackbacks/171.html</trackback:ping><description><![CDATA[<P>【程序人生】 一个人的奋斗历程&nbsp;&nbsp;&nbsp; Fenng（转贴） <BR>&nbsp; <BR>关键字&nbsp;&nbsp;&nbsp;&nbsp; 经历　成功　IC <BR>&nbsp; <BR>出处&nbsp;&nbsp;&nbsp;&nbsp; <A href="http://www.linuxforum.net/forum/gshowflat.php?Cat=&amp;Board=job&amp;Number=451197&amp;page=0&amp;view=col">http://www.linuxforum.net/forum/gshowflat.php?Cat=&amp;Board=job&amp;Number=451197&amp;page=0&amp;view=col</A>... <BR>&nbsp; </P>
<P><BR>Fenng按：在LinuxForum上看到这个文章，收藏，作为纪念．作者的一些想法值得借鉴．标题是我给加的．</P>
<P>这些日子我一直在写一个实时操作系统内核，已有小成了，等写完我会全部公开，希望能够为国内IT的发展尽自己一份微薄的力量。最近看到很多学生朋友和我当年一样没有方向 ，所以把我的经历写出来与大家共勉，希望能给刚如行的朋友们一点点帮助。 一转眼我在IT行业学习工作已经七年多了，这期间我做过网页，写过MIS、数据库，应用程序，做过通信软件、硬件驱动、协议栈，到现在做操作系统内核和IC相关开发，这中间走了很多弯路，也吃了不少苦。 </P>
<P>我上的是一个三流的高校，就连同一个城市的人多数都不知道。因为学校不好也就没 有指望能靠学校名气找一个好工作。所有的希望都寄托在自己的努力上了，大一开学前的假期我就开始了学习，记得我买的第一本书是《计算机基础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，结果可想 <BR>而知。大一学完了C、X86的汇编、数据结构、C++。由于精力都花在自学上了，大一下四门课挂了彩，三类学校就是这点好，挂上一二十门也照样毕业。不过扯远点说，我那么刻苦都及不了格，可见我们国家的计算机教育有多死板。 </P>
<P>大二准备学VC和BC，当时难以取舍，后来选了VC，不为别的，只为书店里两本书，VC 那本便宜6块钱。我的努力在班上无人能及，学的日夜不分，大三有了计算机后更是如此， 很多次父亲半夜教训我说我不要命了，我一直觉得自己基础差，记忆又不行，条件也不好 ，所以觉得只有多花点时间才能赶上别人。居然后来有许多朋友说我有学计算机的天赋， 让我哭笑不得。我用的是486，16M内存，1G硬盘，当时同学们的配置都是P166MMX，我安装 一个Windows NT4.0需要一个通宵，编译一个BC5.0向导生成的程序需要近两个小时，我的显示器是个二手的，辐射非常大，开机屏幕冒火花，看起来很酷的：），有一次程序写的太久，觉得怎么白色的编辑器背景变成了紫色，以为显示器坏了，后来才发现眼睛不行了，不过说来也奇怪，到今天我的视力还能保持1.5，真是个奇迹。但是就是那台破机器陪伴了我两年，让我学会了VC、Delphi、SQLServer等。后来那台机器给我阿姨打字用，据她说一天她正打的开心，一股青烟夹着火苗从显示器钻出来，之后它才寿终正寝。 </P>
<P>大三假期找了个机会在一个计算机研究所实习，与其说实习不如说是做义工，工作了两个月一分钱没有拿。但是这两个月对我的发展帮助很大，让我早一步了解了社会，刚去的时候我当然是一窍不通，在那里我熟悉了网络，学会了Delphi和Oracle。由于工作很认真， 得到了比较好的评价，在一位长者的引荐下，我开始和他们一起做项目，这使我在大三大四就有了自己的收入，大四又找了两家MIS公司兼职，虽然钱不多，但是在学生期间有1000多的收入我已经非常满足了，我终于用自己赚的钱把计算机换了。大四下开始找工作，这时我的工作经验已经比较多（当然现在想想非常幼稚），开始听父母的想去那个研究所， 实习过那个部门也希望我能去，但是不知道为什么最后不了了之，这种单位就是比较官僚 ，我一气之下就到了我兼职的一个公司做MIS的TeamLeader。在大三到毕业一年的时间，做过了各种MIS，从煤气、烟厂、公安、铁路、饮食到高校，什么有钱做什么，工作也很辛苦 ，经常加班和熬通宵，从跟客户谈需求到设计、编码、测试、交付都要上。那时觉得很有成就感，觉得自己还不错，现在想想真是很肤浅。 </P>
<P>刚走上工作岗位的学生很容易被误导，各种开发工具让人眼花缭乱，同时也觉得很受 公司器重，但这样工作永远是一个低层次的开发者。不要跟我说什么系统分析有多么多么重要，多么多么难。你以为自己跟用户谈需求做设计就是系统分析和设计了吗，国内又有几个公司能够做的很到位很规范？我是ISO9000内审员，也在Rational公司受过多次培训，拿了4个证书，还有一个公司让我去做CMM。这些我听过很多，但是很多事情到国内就变了性质，一个公司不是通过了ISO9000或者CMM就能规范了，我现在在一家有几十年历史的外企工作，里面的管理不是一般国内企业能及的。作为一个毕业不久以前没有步入过社会的学生，几乎不可能在很短的时间掌握系统分析和设计，面向对象、UML只是一个工具，关键是人本身的思想，不是说你熟悉了C++、Rose就能够做出好的设计，相反如果你具备了很高的素质，你可以用C写出比别人用C++更加模块化的程序。 </P>
<P>话说远一些，国内软件开发行业有一个怪圈，很多人觉得VC &gt; Delphi &gt; VB，真是很搞笑。这几个软件我都做过开发，说白了他们都是工具，应该根据应用的需要选择采用哪个，而不是觉得哪个上层次。如果你因为用某个开发工具很有面子而选择的话，只能说明你很浅薄。如果说层次，那么这些工具都不上层次，因为它们用来用去都是一些系统的API，微软的朋友不会因为你记住他们多少个API或者多少个类就会觉得你很了不起，你永远只是他们的客户，他们看重的是你口袋里的银子。我也做过系统内核，我也封装过很多API，同样我也不会看重那些使用这些API做二次开发的客户，除非他能够作出自己独到的设计。</P>
<P>至于有人认为C++ &gt; C那更是让人笑掉大牙，不妨你去打听一下，现在有几个操作系统内核是用C++写的，又有几个实时系统用的是C++，当然我也不是说C++不好，但是目前的内核和实时系统中C++还无法与C匹敌，至于说C++适合做应用系统的开发那是另外一回事。所以我的观点是不在于你用什么工具和语言，而在于你干什么工作。你的设计体现了你的技术层次。 </P>
<P>这样干了一年我觉得非常苦闷，做的大多数都是熟练工种的活，个人技术上没有太多 的提高也看不到方向。所以决定离开这个城市去上海，寻求更好的发展，并且打算放弃我以前的MIS转到通信行业。 </P>
<P>写到这里不能不提到我女朋友，我们是在来上海前半年认识的，她大四在我公司实习，公司派她给我写文档，我们的感情发展的很快。她告诉我很多事情，她家原本是改革开放的第一批暴发户，她母亲爱打牌，输掉了几百万，还欠了很多债，她有男朋友，但是她对他没有感情，只因为他给了她母亲两万多块钱，后来还强迫她写了四万块的借条，她男朋友背叛过她并且不止一次打她，现在逼她结婚不然就要她还钱。这人居然还是一个高校的老师！她母亲把父亲给她的学费花了，因为拖欠学费她没有办法拿到毕业证。她母亲现在有病需要钱，我拿出了自己的一点积蓄并且跟朋友们接了一些，替她交了学费并给她母亲看 病（后来才知道看病的钱又不知所终，就连她母亲是不是有病我都不知道，但她也是没有办法）。这个时候我家知道了一些事情，坚决反对我和她在一起，她原来的男朋友也极力破坏。无奈之下我们决定早一定离开这个伤心的城市，并且瞒着我们家。由于时间仓促，我只准备了4000块钱，她仅有的几百块钱也被她母亲要去了，我买了三张票，一张是中午的，两张是晚上的，中午我的家人把我送上船，他们一离开我就下了船，我和她乘坐晚上的船离开了这个我和她生活了很多年的城市，带走的只是一身债务。没有来过上海的我们两个性倔强，都不愿意去麻烦同学和朋友。来到上海是傍晚6点半，我们都不知道该去哪里，我们找了一个20块钱的旅馆，这个房间连窗户都没有，7月份的天气酷热难耐，房间里非常闷热。第二天我们开始租房子，因为身上的钱不多，我们基本都是步行，花了一个星期时间，不知道在浦东转了多少圈后找到了一个400块的房子，但是我们都不了解上海是付三压一，还要付半个月的中介费，买了一些锅碗瓢盆后，我们身上只有800块钱了，工作都还没有着落，这800块钱要支持到我们拿到第一个月工资，为了省钱我们 <BR>自己做饭，每天买菜只花两块钱，她非常喜欢吃（也可能她在大学经常挨饿的愿意），看到她现在这样省吃俭用我真的很不忍心。她以前的男朋友也没有放过她，经常打电话来骚扰，并且来上海看她，还说了不少恐吓她的话，她过于善良，说他以前毕竟帮助过她，叫我不要与他一般见识。以后的每天在家就是苦等面试通知，原本我想迅速找一家MIS公司解决眼前的困难，但是她坚持让我不要放弃自己的理想，终于功夫不负有心人，我找到了一家通信公司，4000块的工资虽然赶不上MIS公司给我开出的价位，但也够在上海生存。她也找到了工作，第一天上班她哭了，这是她来上海第一次流泪，我心里很难受也很感动。</P>
<P>由于是全新的行业，我把自己降到了零点，我学的VC、Delphi、数据库派不上用场， 摆在我面前的是嵌入式、协议、信令一些我从未接触过的知识。我知道我没有退路，于是拼命的学习，我把自己当做一个应届毕业生一样，一分努力一分收获，半年过去我终于熟悉了工作，并且得到了公司的表彰，薪水也加了一级。后面的日子里我们省吃俭用，把欠朋友的1万多块钱还了，日子终于上了正轨。这时女朋友告诉我她想考研究生，我也很支持，于是她辞职在家备考。 </P>
<P>另外，在这里我要感谢我的ProjectManager，他原来是一个大通信公司的产品经理， 对人非常和善，我从他那里学到了很多知识，而且他也给了我许许多多无私的帮助。在工作上他给我充分的空间和信任。记得公司安排我维护一个接入服务器软件，由于代码量不算太小（5万行），资料和文档都不齐全，我维护起来非常吃力，所以想重新把它做一遍， 公司领导不太支持，可能觉得工作量太大，但是他极力支持我，私下里他让我放手去做， 我的维护工作他挤时间做。在他的支持下，我花了半年时间完成了接入服务器的软件，并且实现了一个相对完整的TCP/IP协议栈。在这里我学会了嵌入式系统设计、驱动开发、TCP/IP和很多通信的知识，我花了一年时间终于使自己从MIS开发转到了通信行业，并且站稳了脚跟。我的开发大量是对硬件的直接操作，不再受微软的操作系统，VC、Delhpi这些开发工具的约束，我终于看到了另外一片天空。 </P>
<P>我做事情喜欢追根问底，随着开发的深入，软件开发与硬件联系越来越紧密，硬件知 识的匮乏又对我的发展产生了障碍，而且芯片技术基本上掌握在国外公司的手里，这对做系统级设计是一个非常大的制约，一个新产品出来，第一道利润（也往往是最丰厚的利润）常常都被IC公司如Intel、Motorola赚去了，国内的厂商只能喝点汤。所以我决心解决自己的硬件技术障碍，并打算离开通信行业，进入IC设计相关领域。 </P>
<P>当然我明白如果我对硬件了解的非常少，没有哪家IC公司会仁慈到招我这样一个一窍不通的人来培训。所以我必须努力打好基础，学一些相关知识为以后做准备。就像我开始从MIS转到通信一样，我看过大量通信方面的书，并且给一个ISP做过RADIUS计费分拣台，在这样的背景下这家通信公司才给了我这个机会。我在的通信公司是做系统设计的，有不少PCB Layout硬件人员，平常我就注意向他们学习，由于我做的是软件，在公司看硬件资料不好意思，所以开始只好在家看，刚来上海工作我连续一年都在加班，后来不加了，因为我要挤出时间学习，通常我12点左右睡，第二天5点半起，我上班比较早，地铁上如果人不多我也用来看书。学习当然不会是一帆风顺的，有些实在不懂的问题就积累起来问硬件人员，他们的帮助使我学习进度快了很多，因为在没有人点拨的情况下自学，我的一半时间是花在解决疑难问题上，但这种问题经常是别人的一句话就可以让我豁然开朗，我非常庆幸我有这样的学习环境。在后面的一年里，我学会了看硬件原理图，学会了简单的硬件设计（模拟电路方面还有不小的差距），事情就是这样的，当你安安份份做软件，别人永远认为你是软件开发人员，在你开始学习硬件时别人未必会认同，有位中兴通讯的朋友还对我说过，一个人不可能把所有东西都学完。我也明白这一点，但我希望自己做的更好。但当你熟悉硬件后大家又会觉得你好像原本就是软硬件都懂的，同事们也都习以为常了。这个时候我可以把硬件资料堂堂正正的拿到公司看，没有人再大惊小怪了。 让我比较自豪的是我通过自己的努力做了一个IAD（软交换的终端设备）系统方案，包含软硬件的选型、设计等内容，这个方案得到了公司和同事们的认同，让我感到非常欣慰。 </P>
<P>技术是相辅相成的，当我的硬件有了一定的进步后，我的软件设计也有了很大的提高 ，我可以从更深层次理解问题，我做的接入服务器CPU是Motorola PowerPC860，熟悉的朋友都知道860 QMC与软件的批量数据传输通常采用BD表的方式，硬件人员做驱动的时候习惯采用固定BD表，每接收或发送数据都将数据从BD表拷贝到用户Buffer，或从用户Buffer拷贝到BD表，由于理解的比较深入，我自己重新实现了这个过程，采用动态BD表的方式，驱动从一个网口接收数据，提交给我的软件进行三层交换，直至从另外的接口发送出去，没有进行一次拷贝。这样的设计大大提高了性能，使系统的指标接近理论值。软硬件的结合使我的设计水平上了一个台阶。我现在写的这个操作系统，编译后我把程序反编译成汇编，找出其中不优化的代码，然后在C程序中进行调整。举个例子，很多CPU没有专门的乘法指令，这个大家应该都知道，在这种CPU上进行一个乘法操作常常会花费大量的指令周期， 有的朋友会说这个我知道，我会尽量避免采用×号，但是事情往往不是那么简单，你知道 <BR>C语言中数组的下标操作是怎么实现的吗？仔细看看反汇编的代码你就会明白，同样是通过下标的定位操作，C编译器会有时候会产生位移指令，但有时候会用乘法实现，两者效率往往是天壤之别，所以明白这些问题你才能将系统性能提升到极致。? <BR>些问题就不多说了，有兴趣的话以后可以共同探讨。 </P>
<P>话说远一点，我由衷的希望在软件上做的比较深入的朋友们有机会学学硬件以及其它 相关知识，尤其是做底层开发和嵌入式设计的。这对软件技术的提高有非常大的帮助，否则很多事情你只知道该这样但不会明白为什么该这样。我这个观点在我现在的IC公司Project Manager那里也得到了验证。他告诉我们公司现在的802.11芯片产品的软件经理原本是做该芯片硬件设计的，某某某原本是做软件的，现在在做IC，类似的例子还有很多，只是在国内这样的风气不是非常流行。 </P>
<P>我有一些心得体会与大家分享，只有当我干好本职工作后，我才会学习与工作关系不 大的技术，这样公司的上司才不至于反感，在入门阶段的问题我通常不去问那些资深人士 ，而是问一些资历比较浅的朋友，比如刚毕业不久的学生，因为他们往往会跟你详细的讲解，而资深人士通常觉得你的问题太简单，所以回答的也很简单，我又不好意思多问。等技术上了一定的层次后我才会问他们，他们也能给你比较深入的回答。另外，有些朋友说我机会比较好，他们也希望能从事新的工作可惜没有机会，我听了只有苦笑，我的机会了解的人都应该知道，我没有出生在什么IT世家：）也没有谁一路提拔我，所有的路都是自己走出来的，我母亲去世比较早，我的后母（我叫她阿姨）看着我努力过来的，一次她看我大年30还在写程序，她说像我这样努力木头都能学出来。 </P>
<P>我的最终目的是IC而不是PCB，所以我下一步的准备开始学习IC设计的知识。公司的同事没有懂IC设计的，后面的路又要靠自己了，我买了不少相关的书，在网上也查了很多的资料，我花了大量的时间去学习VHDL，并且用软件进行了一些简单的设计和仿真（没有设计ASIC，只是针对FPGA），随着学习的深入，我渐渐明白了IC设计的基本流程，同时也明白了这条路的艰辛。这个时候我已经做好了跳槽的准备，我向一家业界又一定知名度的IC设计公司投了简历，并通过了漫长的面试（4个多小时）。其他的一切我都比较满意，唯独薪资差强人意，我也明白原因，因为我是这个行业的新人，我没有经验，我再一次将自己清零了。公司老板问我6000多一个月能不能接受，我知道他也是照章办事。想想我通信行业的朋友们，基本上都是年薪10万以上，月薪过万的也比比皆是，朋友们也帮我介绍了不少待遇不错的公司，我该怎么选择，当时我很犹豫，我热爱我的事业，我向往我的追求， 但我也是一个普通的人，我也需要养家糊口，我也想早一点买房买车。生活给我出了一道难题。 </P>
<P>爱因斯坦在63岁时说过“一个人没有在30岁以前达成科学上的最大成就，那他永远都不会有。”这句话给了我很大的压力和震动，我马上就26岁了，离30只有四年时间，我必须抓紧这几年宝贵的时间，努力达到我技术上的最高峰。为了这个理想，为了能离自己的梦更近一些，我选择了这家IC公司，我明白自己的薪资和公司刚进来的硕士研究生相差无几， 但为了今后的发展只能忍受，一切又得重新开始。换行业是一个非常痛苦的过程，尤其从一个春风得意的位置换到一个陌生的岗位，感觉象从温暖的被子里钻出来跳进冰水中，让人难以接受。在原来那家通信公司，我是唯一两年时间涨了五次工资的员工，公司和同事都给了我极大的认可，工作上也常常被委以重任。但现在这一切都成了过去，在新的公司我只是一个新人,没有人知道也没有人在意我过去的成绩。我决定重新开始，我把自己看作新毕业的学生，我要用自己的努力得到公司的认可。进入新的行业是非常痛苦的，我告诉自己必须忍受这一切，虽然外面有很多诱惑，但是既然作出了选择我就不允许自己轻易放弃。 </P>
<P><BR>我现在已经在这家新公司上了一个多月的班，开始非常艰难，现在慢慢适应了。第一 个月结束时，Team Leader找我谈话，说我是新进员工中最优秀的一个，我心里很欣慰，这也算对我努力的一个肯定吧。在这里还要感谢我的女朋友，她给了我很大的支持和鼓舞， 每次在我动摇的时候她都在鼓励我，让我坚持自己的理想，刚来上海是她让我不要勉强去做MIS，这次也是她让我顶住了月薪过万的诱惑，没有她我可能不会有今天的成绩。 现在的公司有自己的操作系统，自己的CPU、DSP和其它芯片，在这里我能学到世界上最先进的技术，我们的设计开发不再完全依赖别人的硬件和系统，这让我很开心。我打算等工作步入正轨后，全力学习新的知识，实现我的理想。 <BR>在后面的两年里我给自己定下了几个目标： <BR>一.努力做好本职工作，在工作上得到公司和同事们的认同； <BR>二.努力学习IC硬件设计知识，多向同事请教，并利用一切机会多实践； <BR>三.实现我的实时操作系统的主要部分，完成TCP/IP协议栈模块，并免费发布源代码； <BR>四.和我女朋友结婚并买一套小房子，这是最重要的，因为我明白事业是可以重来的，但是珍贵的感情很难失而复得。 </P>
<P>在这里提一下我现在开发的操作系统，它是一个实时嵌入式系统，目前支持以下特性： </P>
<P>a.支持时间片轮转调度和基于优先级调度，最多64个优先级； <BR>b.抢占式实时内核； <BR>c.为了便于移植，主体用标准C实现； <BR>d.汇编代码非常少，不到100行； <BR>e.支持任务管理，各任务有独立的堆栈； <BR>f.进程同步和通信目前完成了Semaphore，Message Queue正在调试； <BR>g.实现了定时系统调用； <BR>h.可以在windows上仿真调试 <BR>我还打算下一步实现优先级反转保护，Event Flag，Data Pipe，内存管理（以前实现过）、驱动接口等。 在这之后我还会努力完善它，比如加入文件系统，协议栈、调试接口等。希望朋友们提出自己的意见和建议，在此不胜感激！ </P>
<P>后记： </P>
<P>就像有的朋友说的，我的经历或许会给一些朋友产生误导，在这里我必须说明一下。 我来上海以前学习过于拼命，常常晚上只睡3个多小时，我身高1米71，那时只有108斤（我现在130多），家人也说我这样拼命活不过60岁，但是当时的我太固执，我对他们说只要能实现理想活50岁我就够了。那时的拼命使我的身体受到了影响，有一次早上突然腰肌剧痛难忍，痛的我倒在床上站不起来。虽然我现在已经比较注意，但有时候还会隐隐作痛。后来在女朋友说服了我，来上海以后我不再如此。我经常引用父亲的一句话“身体是革命的本钱”。 </P>
<P>而且我也发现拼命不是办法，我可以熬一两个通宵，最多的一次我连续工作了三天三夜， 但是我半个月都没有恢复过来，这样是不是得不偿失？学习工作应该是一个长期的过程， 像马拉松而不是百米冲刺。我现在非常注意调整学习和工作的强度，我要保证每天尽量有相对充沛的精力，一些年轻的朋友觉得自己也应该拼命努力，这让我多少有些担心，如果我的故事能让你在学习工作上多一点兴趣，我会感到很开心，但如果误导了某些朋友，让你做一些不值得的付出，我会感到很内疚。 </P>
<P>技术没有贵贱只分，我以前换行业是因为自己的兴趣所致，而不是对哪个行业有什么 偏见。我希望我的经历不要给朋友一个错误的导向，觉得我始终向更高的技术发展。其实各行各业做到顶尖都是很困难的。话又说回来虽然技术没有贵贱，但是门槛是有高低的， 无论如何，做IC的门槛要比做网页的高，这一点无可否认。国家各种人才都是需要的，但是作为个人奋发向上的想法还是应该有的，努力在自己喜欢的行业上做的更好，而不应该停留在比较肤浅的层次上。 </P>
<P>我是一个自己觉得比较有自知之明的人，或许我最大的优点就是知道自己有很多缺点 ：）。我的故事中很多的曲折和错误都是由我的缺点造成的，希望大家用审慎的眼光看待我的经历，不要被我的“花言巧语”所迷惑。我学习有些随心所欲，这给我带来了无尽的麻烦，也大大阻碍的我的发展。记得我小时候成绩比较出色，但是后来学习严重偏科，导致我中学成绩一再滑坡，也没有考上什么好的学校，小时候的一个朋友，当时的成绩和我相仿，但是没有我这个缺点，她上了清华，后来在去了美国深造，在一个著名导师手下研究理论科学，这未尝不是一条更好的出路。另外我的学习方法也是在不断改善中的，过去 的学习过于讲究数量和时间，那样学习既苦而已效率不高，现在我非常注意学习的效率和技巧，这样才是学习的捷径（当然不是指投机取巧），比如说学一相对陌生的技术，如果有条件，不妨问一问有经验的人，不需要问很多，往往他不经意的几句话会给你非常大的帮助，甚至超过你看一个星期的书。带着这样的思想再去学习你会节省很多时间，这样何乐不为呢？这些年中我学了不少的东西，由于开始非常盲目，所以学的东西杂乱无章，现在回想起来让我啼笑皆非，我把大量的时间浪费在一些没有必要深入了解的知识上，毕竟一个人的精力是有限度的。很多朋友很我一样都背过五笔字形，的确它是个不错的输入法，但是对一个研发人员它绝对不值得你去背，你的时间应该花在有价值的地方。我这样的事情还做过很多，我背过CCED、WPS的命令和快捷键，在dBase基本退出历史舞台后我还花了很多时间去学习它的使用。所以我的学习在前期缺乏规划，没有明确的短期目的、中期目标，只有一个虚无飘渺的长期的理想。这就像做设计一样，好的设计是从需求抽象到代码有很多过程，而不能得到了需求就立刻开始开始编码。 </P>
<P>当然这么些年的学习和工作多多少少有些收获，下面我说说我的一些学习的心得，这 些方法未必正确，我也在不断探索和改进中。我的学习和工作有相对明确的目标，我不会一时心动而去学习某一技术，在下决定之前我会考虑很多，包括长期的发展，个人路线的规划，需要付出的代价、可能遇到的困难及解决的办法等等，在决定后还会制定更加明确的计划，包括短期、中期和长期的，身边可以利用到的资源（包括好的书籍、资料、软硬件环境，也包括有经验的朋友或者师长），以及每一个阶段是怎么过渡到高一阶段的计划，往往在一个学习阶段一旦上路后会走的相对顺利，但是跨阶段通常比较麻烦，比如从学习基础知识转到实践。另外我买书也有自己的方法，现在世面上高质量的书远不如低质量书多，对于一个陌生的技术，往往在第一次买书会选择错误，即使买到一本好书但是它的方向也未必适合你，所以我通常会先在网上查找一些该技术的介绍，有了一点点概念后再去买一本比较薄、相对便宜并且内容相对泛泛而谈的书，这是国内作者最善于写的书：） ，再把它浏览一遍后我就会基本明白这门技术的要点，后面买书和制定计划就会明确的多。否则一开始就想找本好书往往比较困难，而且买回来后努力学习，有时候学了一半才发现是本低质量的书或者是相对过时技术，让人非常懊恼。另外让有经验的人帮你介绍，通常也是一个不错的选择。 </P>
<P>有些朋友想学通信、嵌入式开发，但总觉得自己没有软硬件环境，我就按我的了解给 大家介绍一下怎么建立这样的环境，当然我了解的只是我学习和工作的方向。通信我做的是数据网方面的工作，包括TCP/IP、二三层交换、对接入网、H.323和软交换也有一点认识。这些软硬件环境都是可以在PC上构建的。你甚至可以在一个没有网卡的PC上建立一个包含多个路由器、接入服务器、VoIP网关、网守、主机等的仿真网络环境，而且与实际的网络相当接近，当然这需要你有清晰的网络概念和一定的网络知识，我一直在努力开发一套软件将这个过程简化，目前试验已经做完，我可能会将它融入我的操作系统外围扩展软件中。这样的方法我无法用简单的语句讲的很清楚，我可以说一下大概的思想，就是在PC上实现仿真网卡，（知道Windows怎么在没有网卡的机器实现虚拟网卡技术的朋友都应该会明白），然后每一个仿真网卡对应一个虚拟设备，如路由器或者主机。你也可以借助第三方工具完成部分工作，如VmWare等。我现在就是利用一个仿真网卡做自己的开发的。 </P>
<P>至于嵌入式开发环境更加容易实现，PC就是一个非常大的硬件平台，现有的嵌入式操 作系统通常都支持X86，你可以在上面做开发，通过软盘Boot或者使用虚拟机装载，我用VxWorks做了试验，在一台PC上跑Windows和VxWorks两个系统。另外Windows上的兼容DOS的16位仿真X86环境也为很多操作系统提供了绝佳的试验环境，我的操作系统在Windows上就是这样实现的。Linux在嵌入式中应用也比较广泛，它在网上有大量的资料，而且也相对比较容易实践。同时很多完善的嵌入式开发环境支持软件仿真，如Tornado、WinCE等。 </P>
<P>&nbsp;<BR></P><img src ="http://www.cnitblog.com/jjczlzn/aggbug/171.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jjczlzn/" target="_blank">生活，工作，思考</a> 2005-05-20 11:39 <a href="http://www.cnitblog.com/jjczlzn/archive/2005/05/20/171.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>how to write a os</title><link>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/170.html</link><dc:creator>生活，工作，思考</dc:creator><author>生活，工作，思考</author><pubDate>Fri, 20 May 2005 03:37:00 GMT</pubDate><guid>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/170.html</guid><wfw:comment>http://www.cnitblog.com/jjczlzn/comments/170.html</wfw:comment><comments>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/170.html#Feedback</comments><slash:comments>12</slash:comments><wfw:commentRss>http://www.cnitblog.com/jjczlzn/comments/commentRss/170.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jjczlzn/services/trackbacks/170.html</trackback:ping><description><![CDATA[<P>QUOTE<BR>作者：伊梅 本文选自：开放系统世界——赛迪网 2002年10月10日</P>
<P><BR>自由软件社区是一个充满自由和梦想的地方，在10余年的时间里它创造了一个又一个奇迹。然而，这些奇迹的创造者不只是Stallman，也不只是Linus Torvalds，而是活跃在世界各地的不计其数的开发人员。 </P>
<P>在使用各种功能强大的自由软件时，我总会对其开发者充满崇敬之情，期盼有朝一日自己也能成为他们中的一员。很多对自由社区充满向往之情的人，虽然也想努力融身于其中，但又不知该怎么做。那么，就请与我们一起从编写一个简单的操作系统开始吧！ </P>
<P><BR>我们要做的事情</P>
<P><BR>有人可能担心自己既没有学过计算机原理，也没有学过操作系统原理，更不懂汇编语言，对C语言也一知半解，能写操作系统吗？答案是没问题。我将带大家一步一步完成自己的操作系统。当然如果学一学上述内容再好不过。 </P>
<P>首先要明确处理器(也就是CPU)控制着计算机。对PC而言，启动的时候，CPU都处在实模式状态，相当于只是一个Intel 8086处理器。也就是说，即使你现在拥有一个奔腾处理器，它的功能也只能是8086级别。从这一点上来讲，可以使用一些软件把处理器转换到著名的保护模式。只有这样，我们才可以充分利用处理器的强大功能。 </P>
<P>编写操作系统开始是对BIOS控制，取出存储在ROM里的程序。BIOS是用来执行POST(Power On Self Test，自检)的。自检是检查计算机的完整性(比如外设是否工作正常、键盘是否连接等)。这一切完成以后，你就会听到PC喇叭发出一声清脆的响声。如果一切正常，BIOS就会选择一个启动设备，并且读取该设备的第一扇区(即启动扇区)，然后控制过程就会转移到指定位置。启动设备可能是一个软盘、光盘、硬盘，或者其它所选择的设备。在此我们把软盘作为启动设备。如果我们已经在软盘的启动扇区里写了一些代码，这时它就被执行。因此，我们的目的很明确，就是往软盘的启动扇区写一些程序。 </P>
<P>首先使用8086汇编来写一个小程序，然后将其拷贝至软盘的启动扇区。为了实现拷贝，要写一个C程序。最后，使用软盘启动计算机。 </P>
<P><BR>需要的工具</P>
<P><BR>● as86：这是一个汇编程序，它负责把写的代码转换成目标文件。 </P>
<P>● ld86：这是一个连接器，as86产生的目标代码由它来转换成真正的机器语言。机器语言是8086能够解读的形式。 </P>
<P>● GCC：著名的C编程器。因为我们需要写一个C程序将自己的OS转移到软盘中。 </P>
<P>● 一张空软盘：它用于存储编写的操作系统，也是启动设备。 </P>
<P>● 一台装有Linux的计算机：这台机器可以很旧，386、486都可以。 </P>
<P>在大部分标准Linux发行版中都会带有as86和ld86。在我使用的Red Hat 7.3中就包含有这两个工具，并且在默认的情况下，它已经安装在机器里。如果使用的Linux没有这两个工具，可以从网上下载(<A href="http://www.cix.co.uk/~mayday/">http://www.cix.co.uk/~mayday/</A>)，这两个工具都包含在一个名为bin86的软件包中。此外，有关的文档也可以在网上获得(<A href="http://www.linux.org/docs/ldp/howto/Assembly-HOWTO/as86.html">www.linux.org/docs/ldp/howto/Assembly-HOWTO/as86.html</A>)。 </P>
<P><BR>开始工作</P>
<P><BR>使用一个你喜欢的编辑器输入以下内容： </P>
<P>entry start<BR>start:<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov ax,#0xb800<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov es,ax<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; seg es<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov [0],#0x41<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; seg es<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mov [1],#0x1f<BR>loop1: jmp loop1</P>
<P>&nbsp;</P>
<P>这是as86可以读懂的一段汇编程序。第一个句子指明了程序的入口点，声明整个过程从start处开始。第二行指明了start的位置，说明整个程序要从start处开始执行。0xb800是显存的开始地址。#表明其后是一个立即数。执行语句： </P>
<P>mov ax,#oxb800</P>
<P>&nbsp;</P>
<P>ax寄存器的值就变为0xb800，这就是显存的地址。下面再将这个值移至es寄存器，es是附加段寄存器。请记住8086有一个分段的体系结构。它的各段寄存器为代码段、数据段、堆栈段和附加段，对应的寄存器名称分别为cs、ds、ss和es。事实上，我们把显存地址送入了附加段，因此，任何送入附加段的东西都会被送到显存中。 </P>
<P>要在屏幕上显示字符，就需要向显存中写两个字节。前一个是所要显示字符的ASCⅡ值，第二个字节表示该字符的属性。属性包括字符的前景色、背景色及是否闪烁等等。seg es指明下一个将要执行的指令是指向es段的。所以，我们把值0x41(在ASCⅡ中表示的字符是A)送到显存的第一个字节中。接下来要把字符的属性送到下一个字节当中。在此输入的是0x1f，该属性指的是在蓝色背景下显示白色的字符。因此，如果执行这个程序，就可以在屏幕上得到显示在蓝底上的一个白色的A。接着是一个循环。因为在执行完显示字符的任务后，要么让程序结束，要么使用一个循环使其永远运行下去。把该文件命名为boot.s，然后存盘。 </P>
<P>此处显存的概念说得不是很清楚，有必要进一步解释一下。假设屏幕由80列×25行组成，那么第一行就需要160字节，其中一个字节用于表示字符，另外一个字节用于表示字符的属性。如果要在第三行显示某一字符的话，就要跳过显存的第0和1字节(它们是用于显示第1列的)，第2和3字节(它们是用于显示第2列的)，然后把需要显示字符的ASCⅡ码值入第4字节，把字符的属性写入第5字节。 </P>
<P><BR>把程序写至启动扇区</P>
<P><BR>下面写一个C程序，把我的操作系统写入软盘第一扇区。程序内容如下： </P>
<P>#include&nbsp; /* unistd.h 需要这个文件 */<BR>#include&nbsp;&nbsp;&nbsp;&nbsp; /* 包含有read和write函数 */<BR>#include <BR>int main()<BR>{<BR>&nbsp; char boot_buf[512];<BR>&nbsp;&nbsp; int floppy_desc, file_desc;<BR>&nbsp; file_desc = open("./boot", O_RDONLY);<BR>&nbsp; read(file_desc, boot_buf, 510);<BR>&nbsp; close(file_desc);<BR>&nbsp; boot_buf[510] = 0x55;<BR>&nbsp; boot_buf[511] = 0xaa;<BR>&nbsp; floppy_desc = open("/dev/fd0", O_RDWR);<BR>&nbsp; lseek(floppy_desc, 0, SEEK_CUR);<BR>&nbsp; write(floppy_desc, boot_buf, 512);<BR>&nbsp; close(floppy_desc);<BR>}</P>
<P>&nbsp;</P>
<P>首先，以只读模式打开boot文件，然后在打开文件时把文件描述符复制到file_desc变量中。从文件中读取510个字符，或者读取直到文件结束。在本例中由于文件很小，所以是读取至文件结束。然后关闭文件。 </P>
<P>最后4行代码打开软盘驱动设备(一般来说是/dev/fd0)。使用lseek找到文件开始处，然后从缓冲中向软盘写512个字节。 </P>
<P>在read、write、open和lseek的帮助页中，可以看到与函数所有有关的参数及其使用方法。程序中有两行比较难懂： </P>
<P>boot_buf[510] = 0x55;<BR>boot_buf[511] = 0xaa;</P>
<P>&nbsp;</P>
<P>该信息是用于BIOS的，如果它识别出该设备是一个可启动的设备，那么在第510和511的位置，该值就应该是0x55和0xaa。程序会把文件boot读至名为boot_buf的缓冲中。它要求改变第510和第511字节，然后把boot_buf写至软盘之上。如果执行代码，软盘上的前512字节就包含了启动代码。最后，把文件存为write.c。 </P>
<P><BR>编译运行</P>
<P><BR>使用下面的命令把文件变为可执行文件： </P>
<P>as86 boot.s -o boot.o<BR>ld86 -d boot.o -o boot<BR>cc write.c -o write</P>
<P>&nbsp;</P>
<P>首先将boot.s文件编译成目标文件boot.o，然后将该文件连接成最终的boot文件。最后C程序编译成可执行的write文件。 </P>
<P>插入一个空白软盘，运行以下程序： </P>
<P>./write</P>
<P>&nbsp;</P>
<P>重新启动电脑，进行BIOS的界面设置，并且把软盘设为第一个启动的设备。然后插入软盘，电脑从软盘上启动。 </P>
<P>启动完成后，在屏幕上可以看到一个字母A(蓝底白字)，启动速度很快，几乎是在瞬间完成。这就意味着系统已经从我们制作的软盘上启动了，并且执行了刚才写入启动扇区的程序。现在，它正处在一个无限循环的状态。所以，如果想进入Linux，必需拿掉软盘，并且重启机器。 </P>
<P>至此，这个操作系统就算完成了，虽然它没有实现什么功能，但是它已经可以启动机器了<BR>上一期，我讲述了如何在软盘的启动扇区写一些代码，然后再从软盘启动的过程。制作好一个启动扇区，在切换到保护模式之前，我们还应该知道如何使用BIOS中断。BIOS中断是一些由BIOS提供的、为了使操作系统的创建更容易的低级程序。在本文中，我们将学习处理BIOS的中断。 </P>
<P><BR>为什么要用BIOS</P>
<P><BR>BIOS会把启动扇区拷贝至RAM中，并且执行这些代码。除此之外，BIOS还要做很多其它的事情。当一个操作系统刚开始启动时，系统中并没有显卡驱动、软盘驱动等任何驱动程序。因此，启动扇区中不可能包含任何一个驱动程序，我们要采取其它的途径。这个时候，BIOS就可以帮助我们了。BIOS中包含有各种可以使用的程序，包括检测安装的设备、控制打印机、计算内存大小等用于各种目的的程序。这些程序就是所说的BIOS中断。 </P>
<P><BR>如何调用BIOS中断</P>
<P><BR>在一般的程序设计语言中，函数的调用是一件非常容易的事情。比如在C语言中，如果有一个名为display的程序，它带有两个参数，其中参数noofchar表示显示的字符数，参数attr表示显示字符的属性。那么要调用它，只需给出程序的名称即可。对于中断的调用，我们使用的是汇编语言中的int指令。 </P>
<P>比如，在C语言中要显示一些东西时，使用的指令如下所示： </P>
<P>display(nofchar，attr)；</P>
<P><BR>而使用BIOS时，要实现相同功能使用的指令如下： </P>
<P>int 0x10</P>
<P><BR>如何传递参数</P>
<P><BR>在调用BIOS中断之前，我们需要先往寄存器中送一些特定的值。假设要使用BIOS的中断13h，该中断的功能是把数据从软盘传送至内存之中。在调用该中断之前，要先指定拷贝数据的段地址，指定驱动器号、磁道号、扇区号，以及要传送的扇区数等等。然后，就要往相应的寄存器送入相应的值。在进行下面的步骤前，读者有必要对这一点有比较明确地认识。 </P>
<P>此外，一个比较重要的事实是同一个中断往往可以实现各种不同的功能。中断所实现的确切功能取决于所选择的功能号，功能号一般都存在ah寄存器之中。比如中断13h可以用于读磁盘、写磁盘等功能，如果把3送入ah寄存器中，那么中断选择的功能就是写磁盘；如果把2送入ah寄存器中，选择的功能则是读磁盘等。 </P>
<P><BR>我们要做的事情</P>
<P><BR>这次我们的源代码由两个汇编语言程序和一个C程序组成。第一个汇编文件是引导扇区的代码。在引导扇区中，我们写的代码是要把软盘中第二扇区拷贝至内存段的0x500处(地址是0x5000，即偏移地址为0)。这时我们需要使用BIOS的中断13h。这时启动扇区的代码就会把控制权转移至0x500处。在第二个汇编文件中，代码会使用BIOS中断10h在屏幕上显示一个信息。C程序实现的功能则是把可执行的文件1拷贝至启动扇区，把可执行的文件2拷贝至软盘的第二扇区。 </P>
<P><BR>启动扇区代码</P>
<P><BR>使用中断13h，启动扇区把软盘第二扇区里的内容加载至内存的0x5000处(段地址为0x500)。下面的代码是用于实现这一目的的代码，将其保存至文件sbect.s中。 </P>
<P>LOC1=0x500<BR>entry start<BR>start:<BR>mov ax,#LOC1<BR>mov es,ax<BR>mov bx,#0 <BR>mov dl,#0 <BR>mov dh,#0 <BR>mov ch,#0 <BR>mov cl,#2 <BR>mov al,#1 <BR>mov ah,#2 <BR>int 0x13<BR>jmpi 0,#LOC1</P>
<P><BR>上面代码第一行类似于一个宏。接下去的两行则是把值0x500加载至es寄存器中，这是软盘上第二扇区代码将拷贝到的地方(第一扇区是启动扇区)。这时，把段内的偏移设为0。 </P>
<P>接下来把驱动器号送入dl寄存器中，其中磁头号送入dl寄存器中，磁道号送入ch寄存器中，扇区号送入cl寄存器中，扇区数送入al寄存器之中。我们想要实现的功能是把扇区2、磁道号为0、驱动器号为0的内容送至段地址0x500处。所有这些参数都和1.44MB的软盘相对应。 </P>
<P>把2送入ah寄存器中，是选择了由中断13h提供的相应功能，即实现从软驱转移数据的功能。 </P>
<P>最后调用中断13h，并且转至偏移为0的段地址0x500处。 </P>
<P><BR>第二个扇区的代码</P>
<P><BR>第二个扇区中的代码如下所示(把这些代码保存至文件sbect2.s之中)： </P>
<P>entry start<BR>start:<BR>mov ah,#0x03 <BR>xor bh,bh<BR>int 0x10</P>
<P>mov cx,#26 <BR>mov bx,#0x0007 <BR>mov bp,#mymsg<BR>mov ax,#0x1301 <BR>int 0x10</P>
<P>loop1: jmp loop1<BR>mymsg:<BR>.byte 13,10<BR>.ascii “Operating System is Loading......”</P>
<P><BR>上面代码将被加载至段地址为0x500处，并且被执行。在这段代码中，使用了中断10h来获取目前的光标位置，然后显示信息。 </P>
<P>从第3行到第5行用于得到目前光标的位置，在此中断10h选用的是功能3。然后，清除了bh寄存器的内容，并把字符串送至ch寄存器中。在bx中，我们送入了页码及显示的属性。此处，我们想要在黑背景上显示白色的字符。然后，把要显示字符的地址送到bp之中，信息由两个字节组成，其值分别为13的10，它们分别对应回车和LF(换行)的ASCⅡ值。接下来是一个由29个字符组成的串；在下面实现的功能是输出字符串然后移动光标；最后是调用中断，然后进入循环。 </P>
<P><BR>C程序代码</P>
<P><BR>C程序的源代码如下所示，将其存储为write.c文件。 </P>
<P>#include &lt;sys/types.h&gt; /* unistd.h needs this */<BR>#include &lt;unistd.h&gt; /* contains read/write */<BR>#include &lt;fcntl.h&gt;<BR>int main()<BR>{<BR>char boot_buf[512];<BR>int floppy_desc, file_desc;<BR>file_desc = open(“./bsect”, O_RDONLY);<BR>read(file_desc, boot_buf, 510);<BR>close(file_desc);<BR>boot_buf[510] = 0x55;<BR>boot_buf[511] = 0xaa;<BR>floppy_desc = open(“/dev/fd0”, O_RDWR);<BR>lseek(floppy_desc, 0, SEEK_SET);<BR>write(floppy_desc, boot_buf, 512);<BR>file_desc = open(“./sect2”, O_RDONLY);<BR>read(file_desc, boot_buf, 512);<BR>close(file_desc);<BR>lseek(floppy_desc, 512, SEEK_SET);<BR>write(floppy_desc, boot_buf, 512);<BR>close(floppy_desc);<BR>}</P>
<P><BR>在上一期中，我曾经介绍过如何操作能启动的软盘。现在这一个过程稍微有点不同，首先把由bsect.s编译出来的可执行文件bsect拷贝至软盘的启动扇区。然后再把由sect2.s产生的可执行文件sect2拷贝至软盘的第二个扇区。 </P>
<P>把上述文件置于同一目录之下，然后分别对其进行编译，方法如下所示： </P>
<P>as86 bsect.s -o bsect.o<BR>ld86 -d bsect.o -o bsect</P>
<P><BR>对sect2.s文件重复以上的操作，得出可执行文件sect2。编译write.c，插入软盘后执行write文件，命令如下所示： </P>
<P>cc write.c -o write<BR>./write</P>
<P><BR>下一步我们要做的事情</P>
<P><BR>从软盘启动以后，可以看到显示出来的字符串。这是使用了BIOS中断来完成的。下一期要做的事情是在这个操作系统中实现实模式向保护模式的转换。</P>
<P>&nbsp;</P>
<P><BR>--------------------------------------------------------------------------------<BR>htldm 回复于：2002-12-17 15:22:55 <BR>现在，这个操作系统已经越来越接近当年Linus Torvalds的那个具有“历史意义”的Linux内核了。因此，要马上把这个系统切换到保护模式之下。 </P>
<P>什么是保护模式</P>
<P>自从1969年推出第一个微处理器以来，Intel处理器就在不断地更新换代，从8086、8088、80286，到80386、80486、奔腾、奔腾Ⅱ、奔腾4等，其体系结构也在不断变化。80386以后，提供了一些新的功能，弥补了8086的一些缺陷。这其中包括内存保护、多任务及使用640KB以上的内存等，并仍然保持和8086家族的兼容性。也就是说80386仍然具备了8086和80286的所有功能，但是在功能上有了很大的增强。早期的处理器是工作在实模式之下的，80286以后引入了保护模式，而在80386以后保护模式又进行了很大的改进。在80386中，保护模式为程序员提供了更好的保护，提供了更多的内存。事实上，保护模式的目的不是为了保护程序，而是要保护程序以外的所有程序（包括操作系统）。 </P>
<P>简言之，保护模式是处理器的一种最自然的模式。在这种模式下，处理器的所有指令及体系结构的所有特色都是可用的，并且能够达到最高的性能。 </P>
<P>保护模式和实模式</P>
<P>从表面上看，保护模式和实模式并没有太大的区别，二者都使用了内存段、中断和设备驱动来处理硬件，但二者有很多不同之处。我们知道，在实模式中内存被划分成段，每个段的大小为64KB，而这样的段地址可以用16位来表示。内存段的处理是通过和段寄存器相关联的内部机制来处理的，这些段寄存器（CS、DS、SS和ES）的内容形成了物理地址的一部分。具体来说，最终的物理地址是由16位的段地址和16位的段内偏移地址组成的。用公式表示为： </P>
<P>物理地址=左移4位的段地址+偏移地址。 </P>
<P>在保护模式下，段是通过一系列被称之为“描述符表”的表所定义的。段寄存器存储的是指向这些表的指针。用于定义内存段的表有两种：全局描述符表(GDT)和局部描述符表(LDT)。GDT是一个段描述符数组，其中包含所有应用程序都可以使用的基本描述符。在实模式中，段长是固定的(为64KB)，而在保护模式中，段长是可变的，其最大可达4GB。LDT也是段描述符的一个数组。与GDT不同，LDT是一个段，其中存放的是局部的、不需要全局共享的段描述符。每一个操作系统都必须定义一个GDT，而每一个正在运行的任务都会有一个相应的LDT。每一个描述符的长度是8个字节，格式如图3所示。当段寄存器被加载的时候，段基地址就会从相应的表入口获得。描述符的内容会被存储在一个程序员不可见的影像寄存器(shadow register)之中，以便下一次同一个段可以使用该信息而不用每次都到表中提取。物理地址由16位或者32位的偏移加上影像寄存器中的基址组成。实模式和保护模式的不同可以从图1和图2中很清楚地看出来。</P>
<P><BR>图1 实模式的寻址</P>
<P><BR>图2 保护模式下的寻址</P>
<P><BR>图3 段描述俯的格式</P>
<P>此外，还有一个中断描述符表(IDT)。这些中断描述符会告诉处理器到那里可以找到中断处理程序。和实模式一样，每一个中断都有一个入口，但是这些入口的格式却完全不同。因为在切换到保护模式的过程中没有使用到IDT，所以在此就不多做介绍了。 </P>
<P>进入保护模式</P>
<P>80386有4个32位控制寄存器，名字分别为CR0、CR1、CR2和CR3。CR1是保留在未来处理器中使用的，在80386中没有定义。CR0包含系统的控制标志，用于控制处理器的操作模式和状态。CR2和CR3是用于控制分页机制的。在此，我们关注的是CR0寄存器的PE位控制，它负责实模式和保护模式之间的切换。当PE=1时，说明处理器运行于保护模式之下，其采用的段机制和前面所述的相应内容对应。如果PE=0，那么处理器就工作在实模式之下。 </P>
<P>切换到保护模式，实际就是把PE位置为1。为了把系统切换到保护模式，还要做一些其它的事情。程序必须要对系统的段寄存器和控制寄存器进行初始化。把PE位置1后，还要执行跳转指令。过程简述如下： </P>
<P>1.创建GDT表; </P>
<P>2.通过置PE位为1进入保护模式; </P>
<P>3.执行跳转以清除在实模式下读取的任何指令。 </P>
<P>下面使用代码来实现这个切换过程。 </P>
<P>需要的东西</P>
<P>◆ 一张空白软盘 </P>
<P>◆ NASM编译器 </P>
<P>下面是整个程序的源代码： </P>
<P>org 0x07c00; 起始地址是0000:7c00 <BR>jmp short begin_boot ; 跳过其它的数据，跳转到引导程序的开始处<BR>bootmesg db "Our OS boot sector loading ......"<BR>pm_mesg db "Switching to protected mode ...."<BR>dw 512 ; 每一扇区的字节数<BR>db 1 ; 每一簇的扇区数<BR>dw 1 ; 保留的扇区号<BR>db 2<BR>dw 0x00e0 <BR>dw 0x0b40 <BR>db 0x0f0 <BR>dw 9 <BR>dw 18 <BR>dw 2 ; 读写扇区号<BR>dw 0 ; 隐藏扇区号<BR>print_mesg :<BR>mov ah,0x13 ; 使用中断10h的功能13，在屏幕上写一个字符串<BR>mov al,0x00 ; 决定调用函数后光标所处的位置<BR>mov bx,0x0007 ; 设置显示属性<BR>mov cx,0x20 ; 在此字符串长度为32 <BR>mov dx,0x0000 ; 光标的起始行和列<BR>int 0x10 ; 调用BIOS的中断10h<BR>ret ; 返回调用程序<BR>get_key :<BR>mov ah,0x00 <BR>int 0x16 ; Get_key使用中断16h的功能0，读取下一个字符<BR>ret<BR>clrscr :<BR>mov ax,0x0600 ; 使用中断10h的功能6，实现卷屏，如果al=0则清屏<BR>mov cx,0x0000 ; 清屏<BR>mov dx,0x174f ; 卷屏至23，79<BR>mov bh,0 ; 使用颜色0来填充<BR>int 0x10 ; 调用10h中断<BR>ret<BR>begin_boot :<BR>call clrscr ; 先清屏<BR>mov bp,bootmesg ; 提供串地址<BR>call print_mesg ; 输出信息<BR>call get_key ; 等待用户按下任一键<BR>bits 16<BR>call clrscr ; 清屏<BR>mov ax,0xb800 ; 使gs指向显示内存<BR>mov gs,ax ; 在实模式下显示一个棕色的A<BR>mov word [gs:0],0x641 ; 显示<BR>call get_key ; 调用Get_key等待用户按下任一键<BR>mov bp,pm_mesg ; 设置串指针<BR>call print_mesg ; 调用print_mesg子程序<BR>call get_key ; 等待按键<BR>call clrscr ; 清屏<BR>cli ; 关中断<BR>lgdt[gdtr] ; 加载GDT <BR>mov eax,cr0 <BR>or al,0x01 ; 设置保护模式位<BR>mov cr0,eax ; 将更改后的字送至控制寄存器中<BR>jmp codesel:go_pm<BR>bits 32<BR>go_pm : <BR>mov ax,datasel <BR>mov ds,ax ; 初始化ds和es，使其指向数据段<BR>mov es,ax <BR>mov ax,videosel ; 初始化gs，使其指向显示内存<BR>mov gs,ax <BR>mov word [gs:0],0x741 ; 在保护模式下显示一个白色的字符A<BR>spin : jmp spin ; 循环<BR>bits 16<BR>gdtr :<BR>dw gdt_end-gdt-1 ; gdt的长度<BR>dd gdt ; gdt的物理地址<BR>gdt<BR>nullsel equ $-gdt ; $指向当前位置，所以nullsel = 0h<BR>gdt0 ; 空描述符<BR>dd 0 <BR>dd 0 ; 所有的段描述符都是64位的<BR>codesel equ $-gdt ; 这是8h也就是gdt的第二个描述符<BR>code_gdt <BR>dw 0x0ffff ; 段描述符的界限是4Gb<BR>dw 0x0000 <BR>db 0x00 <BR>db 0x09a <BR>db 0x0cf <BR>db 0x00 <BR>datasel equ $-gdt <BR>data_gdt <BR>dw 0x0ffff <BR>dw 0x0000 <BR>db 0x00 <BR>db 0x092<BR>db 0x0cf<BR>db 0x00<BR>videosel equ $-gdt <BR>dw 3999 <BR>dw 0x8000 ; 基址是0xb8000<BR>db 0x0b<BR>db 0x92 <BR>db 0x00 <BR>db 0x00<BR>gdt_end<BR>times 510-($-$$) db 0 <BR>dw 0x0aa55</P>
<P><BR>把上面的代码存在一个名为abc.asm的文件之中，使用命令nasm abc.asm，将得出一个名为abc的文件。然后插入软盘，输入命令：dd if=abc of=/dev/fd0。该命令将把文件abc写入到软盘的第一扇区之中。然后重新启动系统，就会看到如下的信息： </P>
<P>*Our os booting................ <BR>* A (棕色) <BR>* Switching to protected mode.... <BR>* A (白色)</P>
<P><BR>对代码的解释</P>
<P>上面给出了所有的代码，下面我对上述代码做一些解释。 </P>
<P>◆ 使用的函数 </P>
<P>下面是代码中一些函数的说明： </P>
<P>print_mesg 该子程序使用了BIOS中断10h的功能13h，即向屏幕写一字符串。属性控制是通过向一些寄存器中送入不同的值来实现的。中断10h是用于各种字符串操作，我们把子功能号13h送到ah中，用于指明要打印一个字符串。al寄存器中的0说明了光标返回的起始位置，0表示调用函数后光标返回到下一行的行首。如果al为1则表示光标位于最后一个字符处。 </P>
<P>显存被分成了几页，在同一时刻只能显示其中的一页。bh指明的是页号；bl则指明要显示字符的颜色；cx指明要显示字符串的长度；dx指明光标的位置(即起始的行和列)。所有相关寄存器初始化完成以后，就可以调用BIOS中断10h了。 </P>
<P>get_key 使用中断16h的子功能00h，从屏幕得到下一个字符。 </P>
<P>clrscr 该函数使用了中断10h的另外一个子功能06h，用于输出开始前清屏。初始化时给al中送入0。寄存器cx和dx指明要清屏的屏幕范围，在本例中是整个屏幕。寄存器bh指明屏幕填充的颜色，在本例中是黑色。 </P>
<P>◆ 其它内容 </P>
<P>程序一开始是一条短跳转指令，跳到begin_boot处。在实模式下，在此打印一个棕色的“A”，并且设置一个GDT。切换到保护模式，并且打印一个白色的“A”。这两种模式使用的都是自己的寻址方法。 </P>
<P>在实模式下，使用段寄存器gs指示显存位置，我们使用的是CGA显卡(默认基址是0xb8000)。在代码中是不是漏了一个0呢？没有，因为实模式下会提供一个附加的0。这种方式也被80386继承下来了。A的ASCⅡ是0x41，0x06指明了需要一个棕色的字符。该显示会一直持续直至按下任意键。下面要在屏幕上显示一句话，告诉使用者下面马上要进入保护模式了。 </P>
<P>启动到保护模式，在进行切换时不希望此时有中断的影响，故要关闭所有的中断(使用cli来实现)。然后对GDT初始化。在整个切换过程中，对4个描述符进行了初始化。这些描述符对代码段(code_gdt)、数据和堆栈段(data_gdt)，以及为了访问显存而对显示段进行初始化。此外，还会对一个空描述符进行初始化。 </P>
<P>GDT的基址要加载至GDTR系统寄存器之中。gdtr段的第一个字加载的是GDT的大小，在下一个双字中则加载的是基址。然后，lgdt指令把把gdt段加载至GDTR寄存器中。现在已经做好了切换到保护模式前的所有准备。最后一件事情就是把CR0寄存器的PE位置1。不过，即使这样还没有处于保护模式状态之下。 </P>
<P>设置了PE位以后，还需要通过执行JMP指令来清除处理器指令预取队列。在80386中，使用指令前总是先将其从内存中取出，并且进行解码和寻址。然而，当进入保护模式以后，预取指令信息(它还处于实地址模式)就无效了。使用JMP指令的目的就是强迫处理器放弃无效的信息。 </P>
<P>现在，已经在保护模式下了。那么，如何检测是在保护模式状态之下呢？让我们来看一看屏幕上这个白色的字母A。在这里，使用了数据段选择符(datase1)对数据段和附加段进行了初始化，使用显示段选择符(videose1)对gs进行了初始化。告示的字符“A”其ASCⅡ值和属性位于[gs:0000]处，也就是b8000:0000处。循环语句使得该字符一直在屏幕上显示，直至重新启动系统。 </P>
<P>下一步要做的事</P>
<P>现在，这个操作系统已经工作在保护模式下了，但是实际上它并不实现什么具体的功能。你可以在这个基础上为它增加各种操作系统所具有的功能。我们自己动手写操作系统到此也就告一段落。 <BR></P><img src ="http://www.cnitblog.com/jjczlzn/aggbug/170.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jjczlzn/" target="_blank">生活，工作，思考</a> 2005-05-20 11:37 <a href="http://www.cnitblog.com/jjczlzn/archive/2005/05/20/170.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>编译原理学习导论</title><link>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/169.html</link><dc:creator>生活，工作，思考</dc:creator><author>生活，工作，思考</author><pubDate>Fri, 20 May 2005 03:35:00 GMT</pubDate><guid>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/169.html</guid><wfw:comment>http://www.cnitblog.com/jjczlzn/comments/169.html</wfw:comment><comments>http://www.cnitblog.com/jjczlzn/archive/2005/05/20/169.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/jjczlzn/comments/commentRss/169.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/jjczlzn/services/trackbacks/169.html</trackback:ping><description><![CDATA[<P>大学课程为什么要开设编译原理呢？这门课程关注的是编译器方面的产生原理和技术问题，似乎和计算机的基础领域不沾边，可是编译原理却一直作为大学本科的必修课程，同时也成为了研究生入学考试的必考内容。编译原理及技术从本质上来讲就是一个算法问题而已，当然由于这个问题十分复杂，其解决算法也相对复杂。我们学的数据结构与算法分析也是讲算法的，不过讲的基础算法，换句话说讲的是算法导论，而编译原理这门课程讲的就是比较专注解决一种的算法了。在20世纪50年代，编译器的编写一直被认为是十分困难的事情，第一Fortran的编译器据说花了18年的时间才完成。在人们尝试编写编译器的同时，诞生了许多跟编译相关的理论和技术，而这些理论和技术比一个实际的编译器本身价值更大。就犹如数学家们在解决著名的哥德巴赫猜想一样，虽然没有最终解决问题，但是其间诞生不少名著的相关数论。</P>
<P>推荐参考书</P>
<P>虽然编译理论发展到今天，已经有了比较成熟的部分，但是作为一个大学生来说，要自己写出一个像Turboc C,Java那样的编译器来说还是太难了。不仅写编译器困难，学习编译原理这门课程也比较困难。</P>
<P>正是因为编译原理学习相对困难，那么就要求有好的教师和好的教材。教师方面不是我们能自己更改的，而在教材方面我们却可以按自己的意愿来阅读。我下面推荐几本好的编译原理的教材。我推荐的书籍都是国外的经典教材，因为在国内的教材中，确实还没发现什么让人满意的。</P>
<P>第一本书的原名叫《Compilers Principles,Techniques,and Tools》,另外一个响亮的名字就是龙书。原因是这本书的封面上有条红色的龙，也因为獗臼樵诒嘁朐?砘?×煊蛉肥堤?忻???所以很多国外的学者都直接取名为龙书。最近机械工业出版社已经出版了此书的中文版，名字就叫《编译原理》。该书出的比较早，大概是在85或86年编写完成的，作者之一还是著名的贝尔实验室的科学家。里面讲解的核心编译原理至今都没有变过，所以一直到今天，它的价值都非凡。这本书最大的特点就是一开始就通过一个实际的小例子，把编译原理的大致内容罗列出来，让很多编译原理的初学者很快心里有了个底,也知道为什么会有这些理论，怎么运用这些理论。而这一点是我感觉国内的教材缺乏的东西，所以国内的教材都不是写给愿意自学的读者，总之让人看了半天，却不知道里面的东西有什么用。</P>
<P>第二本书的原名叫《Modern Compiler Design》,中文名字叫做《现代编译程序设计》。该书由人民邮电出版社所出。此书比较关注的是编译原理的实践，书中给出了不少的实际程序代码，还有很多实际的编译技术问题等等。此书另外一个特点就是其“现代”而字。在传统的编译原理教材中，你是不可能看到如同Java中的“垃圾回收”等算法的。因为Java这样的解释执行语言是在近几年才流行起来的东西。如果你想深入学习编译原理的理论知识，那么你肯定得看前面那本龙书，如果你想自己动手做一个先进的编译器，那么你得看这本《现代编译程序设计》。</P>
<P>第三本书就是很多国内的编译原理学者都推荐的那本《编译原理及实践》。或许是这本书引入国内比较早吧，我记得我是在高中就买了这本书，不过也是在前段时间才把整本书看完。此书作为入门教程也的确是个不错的选择。书中给出的编译原理讲解也相当细致，虽然不如前面的龙书那么深入，但是很多地方都是点到为止，作为大学本科教学已经是十分深入了。该书的特点就是注重实践，不过感觉还不如前面那本《现代编译程序设计》的实践味道更重。此书的重点还是在原理上的实践，而非前面那本那样的技术实践。《编译原理及实践》在讲解编译原理的各个部分的同时，也在逐步实践一个现代的编译器Tiny C.等你把整本书看完，差不多自己也可以写一个Tiny C了。作者还对Lex和Yacc这两个常用的编译相关的工具进行了很详细的说明，这一点也是很难在国内的教材中看到的。</P>
<P>推荐了这三本教材，都有英文版和中文版的。很多英文好的同学只喜欢看原版的书，不我的感觉是这三本书的翻译都很不错，没有必要特别去买英文版的。理解理论的实质比理解表面的文字更为重要。</P>
<P>编译原理的实质</P>
<P>前面已经说过，学习编译原理其实也就是学习算法而已，没什么特别的。只不过这些算法的产生已经形成了一套理论。下面我来看看编译原理里面到底有什么高深的理论吧。</P>
<P>几乎每本编译原理的教材都是分成词法分析，语法分析（LL算法，递归下降算法，LR算法），语义分析，运行时环境，中间代码，代码生成，代码优化这些部分。其实现在很多编译原理的教材都是按照85,86出版的那本龙书来安排教学内容的，所以那本龙书的内容格式几乎成了现在编译原理教材的定式，包括国内的教材也是如此。一般来说，大学里面的本科教学是不可能把上面的所有部分都认真讲完的，而是比较偏重于前面几个部分。像代码优化那部分东西，就像个无底洞一样，如果要认真讲，就是单独开一个学期的课也不可能讲得清楚。所以，一般对于本科生，对词法分析和语法分析掌握要求就相对要高一点了。</P>
<P>词法分析相对来说比较简单。可能是词法分析程序本身实现起来很简单吧，很多没有学过编译原理的人也同样可以写出各种各样的词法分析程序。不过编译原理在讲解词法分析的时候，重点把正则表达式和自动机原理加了进来，然后以一种十分标准的方式来讲解词法分析程序的产生。这样的做法道理很明显，就是要让词法分析从程序上升到理论的地步。</P>
<P>语法分析部分就比较麻烦一点了。现在一般有两种语法分析算法，LL自顶向下算法和LR自底向上算法。LL算法还好说，到了LR算法的时候，困难就来了。很多自学编译原理的都是遇到LR算法的理解成问题后就放弃了自学。其实这些东西都是只要大家理解就可以了，又不是像词法分析那样非得自己写出来才算真正的会。像LR算法的语法分析器，一般都是用工具Yacc来生成，实践中完全没有比较自己来实现。对于LL算法中特殊的递归下降算法，因为其实践十分简单，那么就应该要求每个学生都能自己写。当然，现在也有不少好的LL算法的语法分析器，不过要是换在非C平台，比如Java,Delphi,你不能运用YACC工具了，那么你就只有自己来写语法分析器。</P>
<P>等学到词法分析和语法分析时候，你可能会出现这样的疑问：“词法分析和语法分析到底有什么？”就从编译器的角度来讲，编译器需要把程序员写的源程序转换成一种方便处理的数据结构（抽象语法树或语法树）,那么这个转换的过程就是通过词法分析和语法分析的。其实词法分析并非一开始就被列入编译器的必备部分，只是我们为了简化语法分析的过程，就把词法分析这种繁琐的工作单独提取出来，就成了现在的词法分析部分。除了编译器部分，在其它地方，词法分析和语法分析也是有用的。比如我们在DOS,Unix,Linux下输入命令的时候，程序如何分析你输入的命令形式，这也是简单的应用。总之，这两部分的工作就是把不“规则”的文本信息转换成一种比较好分析好处理的数据结构。那么为什么编译原理的教程都最终把要分析的源分析转换成“树”这种数据结构呢？数据结构中有Stack, Line,List…这么多数据结构，各自都有各自的特点。但是Tree这种结构有很强的递归性，也就是说我们可以把Tree的任何结点Node提取出来后，它依旧是一颗完整的Tree。这一点符合我们现在编译原理分析的形式语言，比如我们在函数里面使用函树，循环中使用循环，条件中使用条件等等，那么就可以很直观地表示在Tree这种数据结构上。同样，我们在执行形式语言的程序的时候也是如此的递归性。在编译原理后面的代码生成的部分，就会介绍一种堆栈式的中间代码，我们可以根据分析出来的抽象语法树，很容易，很机械地运用递归遍历抽象语法树就可以生成这种指令代码。而这种代码其实也被广泛运用在其它的解释型语言中。像现在流行的Java,.NET，其底层的字节码bytecode,可以说就是这中基于堆栈的指令代码的。</P>
<P>关于语义分析，语法制导翻译，类型检查等等部分，其实都是一种完善前面得到的抽象语法树的过程。比如说，我们写C语言程序的时候，都知道，如果把一个浮点数直接赋值给一个整数，就会出现类型不匹配，那么C语言的编译器是怎么知道的呢？就是通过这一步的类型检查。像C++语言这中支持多态函数的语言，这部分要处理的问题就更多更复杂了。大部编译原理的教材在这部分都是讲解一些比较好的处理策略而已。因为新的问题总是在发生，旧的办法不见得足够解决。</P>
<P>本来说，作为一个编译器，起作用的部分就是用户输入的源程序到最终的代码生成。但是在讲解最终代码生成的时候，又不得不讲解机器运行环境等内容。因为如果你不知道机器是怎么执行最终代码的，那么你当然无法知道如何生成合适的最终代码。这部分内容我自我感觉其意义甚至超过了编译原理本身。因为它会把一个计算机的程序的运行过程都通通排在你面前，你将来可能不会从事编译器的开发工作，但是只要是和计算机软件开发相关的领域,都会涉及到程序的执行过程。运行时环境的讲解会让你更清楚一个计算机程序是怎么存储，怎么装载，怎么执行的。关于部分的内容，我强烈建议大家看看龙书上的讲解，作者从最基本的存储组织，存储分配策略，非局部名字的访问，参数传递，符号表到动态存储分配(malloc,new)都作了十分详细的说明。这些东西都是我们编写平常程序的时候经常要做的事情，但是我们却少去探求其内部是如何完成。</P>
<P>关于中间代码生成，代码生成,代码优化部分的内容就实在不好说了。国内很多教材到了这部分都会很简单地走马观花讲过去，学生听了也只是作为了解，不知道如何运用。不过这部分内容的东西如果要认真讲，单独开一学期的课程都讲不完。在《编译原理及实践》的书上，对于这部分的讲解就恰到好处。作者主要讲解的还是一种以堆栈为基础的指令代码，十分通俗易懂，让人看了后，很容易模仿，自己下来后就可以写自己的代码生成。当然，对于其它代码生成技术，代码优化技术的讲解就十分简单了。如果要仔细研究代码生成技术，其实另外还有本叫做《Advance Compiler Desgin and Implement》,那本书现在由机械工业出版社引进的，十分厚重，而且是英文原版。不过这本书我没有把它列为推荐书给大家，毕竟能把龙书的内容搞清楚，在中国已经就算很不错的高手了，到那个时候再看这本《Advance Compiler Desgin and Implement》也不迟。代码优化部分在大学本科教学中还是一个不太重要的部分，就是算是实践过程中，相信大家也不太运用得到。毕竟，自己做的编译器能正确生成执行代码已经很不错了，还谈什么优化呢？</P>
<P>关于实践</P>
<P>编译原理的课程毕竟还只是讲解原理的课程，不是专门的编译技术课程。这两门课程是有很大的区别的。编译技术更关注实际的编写编译器过程中运用到的技术，而原理的课关注讲解其基本理论。但是计算机科学本身就是一门实践性很强的课程，如果能够学以致用，那才叫真正的学会。李阳在讲解疯狂英语的时候就说到，只要当你会实际中运用一个单词一个词组的时候你才能叫学会了这个单词或者词组，而不是只是知道了它的拼写和意思。其实任何学习都是一样的，如果缺少了实践的结合，你不能算学会。</P>
<P>编译原理的课程主要就是讲解编译器产生的理论和原理，那么很简单，自己写个编译器就是最好的实践过程了。不过你得小心，编译系统可能是所有软件系统中最复杂的系统之一,不然为什么大学里面还会把编译器的编写开成一门叫做编译原理的课程来讲？我很佩服那些学了操作系统原理就开始自己写操作系统，学了编译原理就开始自己写编译器的人们，确实，在中国，敢这么做的学生太少了。且不管你这样做能不能做成功，至少有了这个尝试，会让你的程序设计，系统规划安排的功底增进不少。我下面给出一些关于实践过程中可能会遇到的困难，希望能够在你陷入困境的前帮你一把。</P>
<P>1. Lex和Yacc. 这两工具是作为词法分析很语法分析的工具。如果你自己写一个编译器，我十分不建议你连词法分析这种事情都亲手来写。Lex和Yacc应该是作为每本编译原理的教材的必备内容，可是在国内的教材中缺很少看到。这两个工具是Unix系统下的小东西，如果你要在Windows中运用，那么你最好去下在cygwin这个软件。它是个在Windows下模拟Unix的东东，里面就包含了flex.exe和bison.exe(yacc)这两个工具.这两个工具使用起来还挺麻烦的(其实unix 下的很多十分有用的工具都是这样), 不过在《编译原理与实践》这本书上对于这两个工具的讲解十分详细,还列举了不少实际的例子。</P>
<P>2. 做解释型语言比做生成机器代码的编译器简单。虽然说，做解释型的编译器，像Java那样的，你还得自己去写解释器，不过这样你就不必去查找机器代码的资料了。如果你做生成的最终机器代码编译器可能会遇到问题还有就是寄存器为基础的代码生成方法。前面说过，如果你生成的是以堆栈为基础的代码，那么其代码生成过程十分简单，需要考虑的东西也不多，如果你考虑最终的机器代码生成的话，你必须考虑机器的寄存器如何分配等麻烦的问题。</P>
<P>3. 考虑用别人已经生成的语法文件，尽量不要自己动手写词法文件和语法文件.以前一个朋友曾经说过,写出一个好的程序语言的语法定义,就几乎完成了一个编译器的一半.确实是这样,语法文件的编写是个很难的事情.现在网上到处都可以找到比如C语言,C++,Java, Tiny C,Minus C等语言的词法文件和语法文件,你完全可以自己下下来来用.</P>
<P>在《编译原理及实践》的书中，作者给出了一个Tiny C的全部代码.我自我感觉作者的这个编译器做得很不错,相对于其它php,perl等语言的源代码来说,简单得多,容易看懂,而且很清晰地展现了一个完成的编译系统的实现过程.其源代码可以在作者的网站上下载</P><img src ="http://www.cnitblog.com/jjczlzn/aggbug/169.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/jjczlzn/" target="_blank">生活，工作，思考</a> 2005-05-20 11:35 <a href="http://www.cnitblog.com/jjczlzn/archive/2005/05/20/169.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>