2009年3月31日星期二

摧毁华尔街的秘密数学公式

从上世纪80年代中期起,华尔街就开始依赖金融工程精英们来创造各种新的获利途径。他们创造金钱的方法一直成功运转了这么多年,直到其中一种"突然"引发了这场全球性的经济灾难。

一年前,人们总认为像李祥林(DavidX.Li)这样的数学天才可能会在某日得到诺贝尔奖的眷顾,因为金融经济学者,甚至华尔街的这类人才的确此前也获得过诺贝尔经济学奖。李祥林的开创性工作是衡量投资风险,而在金融领域,他的成果与以前获得过诺贝尔奖的学者的贡献相比更有影响力、更快速地得到广泛应用。然而,当晕头转向的银行家、政治家、监管者和投资者在这场自"大萧条"以来最严重的金融大崩溃的废墟中寻找事发根源时,他可能更应该庆幸的是自己还有一份金融业的工作。

李祥林从事的研究是确定资产间的相关性(correlation),也就是将一些完全不同的事件之间的关联度用数学模型来量化。这是金融领域中的一大难题,但他构建的被称为高斯相依函数的公式能以数学手段令极其复杂的风险比以前更容易和精确地被衡量。基于这一公式,金融机构能够大胆地出售各种新型证券和金融衍生品,将金融市场扩张至几乎不可思议的水平。

从债券投资者到华尔街的银行,从评级机构到监管机构,几乎每一个人都在使用李祥林的公式。很快,利用这一公式来衡量风险的方法已经在金融领域深入人心,并且帮人们赚到了大量金钱,使得任何对此公式的局限性的警告都被人们忽视了。

然而,突然间,使用这一公式的人们发现,金融市场开始出乎他们意料之外地变化。小小的裂缝在2008年演变成了巨大的峡谷,瞬间吞噬了成千上万亿的资金,将全球银行体系推向了崩溃的边缘,并引发了这场波及全球各个角落的经济危机。

可以肯定地说,李祥林在短期内都不可能获得诺贝尔经济学奖的眷顾了。而这场金融大海啸也使得金融经济学此前受人们顶礼膜拜、坚信不疑的地位不复存在。

为何数学公式的影响如此之大

令人们惊诧的问题是,一个数学公式怎会给金融界带来如此毁灭性的结果?答案隐藏在让养老基金、保险公司和对冲基金向企业、各国家和购房者发放数万亿美元贷款的庞大债券市场中。一个企业若要发行债券借款,投资者会严密审查公司账目,以确认公司能有足够资金偿还贷款。若放款人认为贷款的风险很高,他们索要的利息率也会更高。

债券投资者都是在赌"大概率事件",如果债券违约的概率是1%,而他们可以获得额外2%的利息,他们就会蜂拥而上购买该债券。这就好比一个赌场,人们不介意偶尔输掉一些钱,只要大多数的时间里,他们都在赢钱。

债券投资者通常也对由数百乃至上千个住房按揭贷款构成的资产池进行投资。现在涉及的这类活动总规模大得惊人:美国购房人所欠下总债务已达11万亿美元。然而,按揭贷款资产池的情况比债券市场更混乱。这类投资中,因购房者每月集体偿还的现金量,是取决于已获得再融资的购房人数量和因违约未还款人的函数,因此投资不存在保证性的确定利率。同样,如此借贷活动也无固定的还款到期日。因购房人以无法预测的时间偿还按揭,例如购房者决定出售房产,因此池内的还款总数也是无规律可循。最令人头痛的问题是,尚无法找到给违约出现机会确定一个单个概率值的办法(即概率越高、贷款损失风险越大)。

华尔街解决的办法是,通过一个称之为划分等级(tranching)的办法,它将整个池内各类资产进行分级,创建以标注3A评级的无风险的安全债券。位于第一级别的投资者能够最先获得偿还债息,其他类别投资者虽因违约风险较高而评级稍低,但可收取更高的利率。

评级机构和投资者之所以对3A级的债券感到放心是因为他们相信,成百上千的贷款购房者不会在同一时间内发生违约行为。某人可能会丢掉工作,其他人可能生病。但这些都是不会给按揭贷款资产池整体带来重大影响的个体不幸事件。但所有的灾难性事件并非都是个体性,等级划分做法并未解决资产池风险的全部问题。

房价可能下跌的事件会在同时影响到一大批人。如某购房者家附近住房价值下跌,此人住房的资产净值也同样会下降,他(她)周边邻居的房产会跟着下跌的可能性很大。一旦此购房人还款违约,周边邻居违约的可能性也很大。这就是所谓的相关性,即一个变量变化与另一些变量的关系和影响程度,度量此关系和关系程度高低是确定按揭贷款债券风险大小的重要部分。

只要投资者能够对风险定价,他们就愿意冒险。他们厌恶的是不确定性,即无法确定风险大小。正因如此,债券投资者、按揭贷款放款者拼命地想要找到能够度量、模拟相关性,并对其进行定价的方法。在计量模型应用于金融市场前,令投资者对按揭贷款资产池中投资感到安全的唯一时刻是不存在风险,即这类债券都是由联邦政府通过房地美和房利美两家企业进行隐形担保。

随着全球金融市场在上世纪90年代快速扩张,数以万亿计美元要进入市场,若投资者能够找到确定任何资产间的相关关系的方法,这些资金便能顺利进入市场。但这是个折磨人的痛苦问题,特别是考虑到成百上千类资产在时刻不停波动和变化。无论是谁解决了这样一个问题不仅会赢得华尔街永恒的感谢,而且非常可能会引起诺贝尔奖委员会的关注。

理解相关性概念

为了让大家更好地理解"相关性"这个概念,我们举一个比较简单的例子:假设一个在读小学的小孩叫爱丽丝,她父母今年离婚的可能性是5%,她头上长虱子的可能性是5%,她看到她的老师踩到香蕉皮摔倒的可能性是5%,她获得班级朗读比赛冠军的可能性是5%。假设投资者们要交易一种基于爱丽丝身上发生的这些事件的概率的证券,他们的出价可能差不多。

我们考虑两个小孩,不仅仅是爱丽丝,还有她的同桌布兰妮。假设布兰妮的父母离婚了,爱丽丝的父母离婚的概率是多少?大多数情况下还应该是5%,这就说明在这件事上,他们的相关性可能接近于0;如果布兰妮头上长了虱子,爱丽丝长虱子的可能性就会高很多,可能是50%,这说明他们的相关性是0.5左右;如果布兰妮看到老师摔倒,因为她们是同桌,爱丽丝也看到的概率可能是95%,这时她们的相关性接近1;如果布兰妮获得了班上朗读比赛冠军,爱丽丝获得冠军的可能性就是0,这件事上她们的相关性是-1。

如果投资者交易的证券是基于这些事件同时发生在这两个小孩身上的概率,他们的判断就很可能大不相同了,因为各类事件中,两个小孩的相关性各不相同。

但这是一个非常不精确的科学。单单是要确定一个人发生某件事的概率是5%,就已经要费很大精力来搜集历史数据进行统计和误差分析了,而还要在此基础上判断另一个人在这一个人发生这件事的条件下的概率就更加复杂,而且也缺乏相关的历史数据,因此出现误差的可能性也就更大。

在住房抵押贷款市场里,这种相关性的计算更是难上加难。首先要计算某个地区房价下跌的概率,你可以观察房价的历史走势来推测未来,但一个国家的宏观经济形势对房价的影响也极其重要。在此基础上我们还要判断,如果一个州的某个房子价格下跌,另一个州一间同样的房子价格下跌的概率又是多少?

李祥林取得突破性进展

李祥林,上世纪60年代出生在中国农村,成绩优异,获得了南开大学的经济学硕士学位,后来去美国留学,获得魁北克拉瓦尔大学的MBA学位。在此之后他继续深造,先后获得了加拿大滑铁卢大学的精算学硕士学位和统计学博士学位。1997年他在加拿大帝国商业银行开始了他的金融职业生涯,后来就职于巴克莱资本,并在2004年负责重建其数量分析小组。

李祥林的学术背景在华尔街的精英中非常典型。由于从事学术研究的收入远不如华尔街投行和对冲基金给出的薪水高,从上世纪80年代开始,大量数理背景的高级人才进入华尔街,从事金融衍生工具的创造、定价和套利。

此时巧合的是,在摩根大通工作的李祥林在《固定收益杂志》上发表了一份名为"论违约相关性:相依函数方法"的论文。论文以相对简单的数学方法(当然是相对华尔街精英人士的水平来说),不用参考历史违约数据,而是使用了一种金融衍生产品——信用违约互换(CDS)的市场价格数据作为判断违约相关性的依据。

如果你是一个投资者,你可以选择直接把钱借给借款者,也可以选择向贷款者卖出CDS产品。它相当于对贷出的款项进行一种保险,以防借款者出现违约情况。这两种方法都可以收取固定的收益——利息或者是保费。两者收益接近,但由于针对同一个借款者可以售出无限个CDS,CDS产品的供应并不受限于债券发行的数量,所以处于开创阶段的CDS市场以异乎寻常的速度增长,所向披靡,规模大大超过了作为其基础资产的债券市场。

当一种CDS的价格上涨,这表明其标的资产违约的可能性上升。李祥林的突破在于,他不去浪费时间等待搜集足够的实际违约数据,因为实际违约在现实中比较少,取而代之,他利用CDS市场的历史数据作为判断依据。假设有两个借款者,我们很难通过他们过去实际违约的情况来计算他们的违约相关性,因为或许他们过去没有违约过。但我们可以通过观察针对这两位借款者的CDS的历史价格变化,如果走势较为一致,那么可以证明他们的相关性较大。李祥林利用这种价格走势的相关性作为"捷径",假设了金融市场,这里特别是CDS市场,能够正确地对违约的可能做出相应的价格反映。

这是对一个复杂问题进行的巧妙简单化。而且李祥林不仅仅简化了相关性的计算,他还决定完全不考虑资产池中的各个贷款之间复杂的关系变化。例如,如果资产池中的贷款数目增加,会发生什么变化?如果你将负相关性的贷款和正相关性的贷款组合放在一起,整个资产池的风险又如何变化?他说,都不用管这些。我们只用管一个最终的相关性数据,一个简单明了的数据就代表所有我们需要考虑的东西。

这一发明使市场迅速发展

这一公式的发明对资产证券化市场具有闪电效应。有了这一风险定价公式,华尔街的精英们看到了新的无限可能。他们马上着手创造大量新的3A证券。而像穆迪这样的评级机构再也不用苦恼这些证券背后所代表的资产的各种风险,他们只需要考虑这个简单的相关性数据,然后就出来了一个评级,告知人们这些资产的风险到底多高。

因此,几乎任何资产都可以捆绑在一起变成3A证券——公司债券、银行贷款、住房抵押证券等等。这样形成的资产池一般被称为债务抵押证券(CDO),通过将资产池分级,打造出3A级的证券,即使这个证券的组成资产没有一个是3A级的。那么对于资产池中较低级别的证券怎么办?他们也想出了好办法:把多种CDO资产池中低级别的证券再捆绑在一起,组成一个资产池,再次进行分级。这样组成的投资工具叫做CDO2。到此为止,已经没有人真正知道这个产品包含了什么基础资产。但他们并不在乎,一切只需要李祥林的相依函数公式(CopulaFunction)就可以了。

这些年里,CDS和CDO市场相互依存,共同壮大。数据显示,2001年年底,在外流通的CDS总额高达9200亿美元。到2007年年底,这一数字飙升至62万亿美元。同样,CDO市场总规模在2000年仅为2750亿美元,到2006年扩大至47000亿美元。

这些市场发展的基础就是李祥林的公式。如果你问一些市场参与者,他们都会用"极好、简洁、好处理"这一类词来形容这一公式。这一公式几乎是普遍适用,于是,无论是银行打包新的债券,还是交易员、对冲基金对这些债券进行复杂的交易时,大家都会用到这一公式。

公式背后的隐忧

曾在穆迪的学术顾问研究委员会任职、现任美国斯坦福大学金融学教授的达雷尔•达菲(DarrellDuffie)指出,CDO市场几乎完全依赖这一相关性模型,高斯相依(Gaussiancopula)一词已经成为全球金融界普遍接受的词汇,就连经纪商都依据这一公式对某个级别的债券进行报价。正如衍生品大师珍妮•塔瓦科里(JanetTavakoli)所描述的那样,基于相关性的交易已经像一个极具传染性的思想病毒,遍布金融市场的每个角落。

其实早在1998年,李祥林发明这一函数之前,数量金融学的顾问和讲师魏尔莫特(PaulWilmott)就指出,金融数量之间的相关性是出了名的不稳定,任何理论都不能建立在这样不可预测的参数之上。这样的声音不止一个,在美国金融行业繁荣的数年里,每个人都可以说出一大堆理由证明这一函数公式并不完美,它无法应对不可预知的情况:它假定相关性是一个常量而不是变量。投行也经常打电话给斯坦福大学的达菲教授,邀请他解释这一公式。每一次,他都会警告投行,这一公式并不适用于风险管理和估价。

现在看来,对这些警告的充耳不闻简直是笨透了。但在当时,这确实是一件很简单的事。投行们并没有理会这些警告,一方面是因为手握控制大权的经理们并不懂金融工程精英的各派争论,也无法理解各种数学模型的真正含义;另一方面,他们赚了太多钱,贪欲已经无法让他们停下来了。

在金融市场,风险是永远无法消除的。我们只能努力建立一个市场,让不想承担风险的人们将风险转嫁给爱冒险的人。在CDO市场,人们用这一公式让自己相信自己没有风险,但实际上,他们只是99%的时候没有风险。一旦1%的可能出现,他们就会前功尽弃、尸骨无存。

李祥林的公式被用于对上亿的住房贷款组成的CDO资产池进行定价。因为他的公式以相关CDS的历史价格走势为基础,因此相关性的计算只能局限于CDS出现之后的年代。而在过去不到十年的时间里,房价一直在上涨,因此住房贷款之间违约的相关性就相对比较小。一旦房市的繁荣时代结束,整个国家的房价都下降,房贷违约的相关性就会骤然飙升。

其实对房贷进行资产证券化的银行也明白,这一公式对房价的上涨有很强的敏感性。一旦房价下跌,所有被评为3A的无风险债券都会瞬间崩塌,没有退路可寻。尽管如此,他们都不愿意停止制造CDO。面对眼前大把利润的诱惑,没有人抵抗得住,他们要做的就是一边享受暴利一边祈求房价继续上涨。

谁应该被指责?

2005年秋,李祥林曾在《华尔街日报》表示,很少人真正理解了这一公式的核心。金融领域中,大多数人都认为李祥林不应该被指责。毕竟,他只是发明了这一数学模型。我们应该指责的是那些滥用模型的金融机构,是他们的贪欲导致整个金融界盲目地逐利,对这一模型的局限忽略不计,对外界的警告充耳不闻。

李博士目前已经淡出人们对当前讨论金融危机原因的讨论,并于去年离开美国回到中国。

在现实金融世界中,太多金融分析人士只看到他们眼前的毫无生命的数字,而忘却了这些数字所代表的有形和真实的现实。他们认为,能够仅靠只有数年价值的数据来模拟计算,再定出那些每10000年才可能发生一次事件的概率。人们此后就以如此概率进行投资,而不思考一下这些数据究竟是否有实际意义。正如李博士本人对自己的模型的表态,最危险的事情莫过于人们盲目地相信模型能给他们带来所希望的结果。

2009年3月30日星期一

工作以后十不要 减少奋斗30年

第一:不要认为停留在心灵的舒适区域内是可以原谅的
每个人都有一个舒适区域,在这个区域内是很自我的,不愿意被打扰,不愿意被push,不愿意和陌生的面孔交谈,不愿意被人指责,不愿意按照规定的时限做事,不愿意主动的去关心别人,不愿意去思考别人还有什么没有想到。这在学生时代是很容易被理解的,有时候这样的同学还跟"冷酷""个性"这些字眼沾边,算作是褒义。然而相反,在工作之后,你要极力改变这一现状。否则,你会很快变成鸡尾酒会上唯一没有人理睬的对象,或是很快因为压力而内分泌失调。但是,如果你能很快打破之前学生期所处的舒适区域,比别人更快的处理好业务、人际、舆论之间的关系,那就能很快的脱颖而出。
在会议上,一个停留在心灵舒适区域的人会消极的听取领导的话语,消极的待命,很死的完成上级交给的事情,但从来不关心此事以外的任何事情,更不会想到多做一步,让接下来的别人的工作更加容易上手。而敢于打破这个舒适区域的人,敢于在适当的时候提出自己的看法和不理解,并在得到上级认可和指点之后把手头的工作尽快的完成,并随时接受别人的批评和调整。(注意:永远不要等待别人把你的想法说出来,这是典型的前者)
在工作上,当前者遇到一名新的同事,他会装作没有看见,继续自己的工作。殊不知新来的同事不久就变成了自己的上司。而后者则大方客气的自我介绍,并了解对方和自己的关系。
在聚会上,前者总是等待别人发言,并喜欢私下里评论对方的言语;如果这个桌子上没有人发言,那直到用餐结束,也没有人认识你。而后者是勇敢的和一同吃饭的开始介绍和闲谈,这看起来很困难,有时候会有失面子,但往往你会发现,对方是多么希望能和你说几句话。
以上只是很小的几个例子,但是要说明的是,大学生在走出校园的同时就要在工作上把校园中的"随意性"从身边赶走,尽早的冲出自己的舒适区域,开始做好和这个社会交流的准备。
第二:不要把"好像";"有人会……";"大概";"晚些时候";"或者";"说不定"之类放在嘴边。尤其是和上级谈论工作的时候
我十分痛恨听到的一句话是:"我晚些时候会把这个文件发给所有的人";因为这往往预示着我必须时刻提醒他不要忘记。同样,以下这些言辞也会让人觉得厌恶至极:
"到时候有人会把那些东西都准备好"
"大概是明天"
"明天或者后天客户会过来拜访"
"好像他说……"
一般是人都会这样说话的,因为这样第一给自己留下了广阔的余地,第二也不会给别人造成很大的压迫感,好像什么事情一定要弄个水落石出似的。说实话大学里面再用功的人都有一半是混的。一个人要么是在课堂上是混的,要么下课之后是混的。两个都没有带有混的色彩的人,要么是超级牛人,要么是神经病。所以,就因为人人都在混的,所以校园是一个浪漫的地方,校园也容易让人单纯。所以学生社团的工作往往是效率很低的,我现在回想起学校里做的工作,当时还觉得挺卖力的,但工作了之后才开始感觉到什么是效率。当你进入了用金钱计算时间的地方之后,你要尽可能的避免在学校里养成的这种习惯。如果上级问你什么时候能实施你给他的承诺,而你回答"今晚或者明天早上"这样的答案对于他来说完全等同于你没有回答,并且还给他留下了一个坏印象。(当然,这样的回答往往在学校社团,学生会工作中是常见的)
有一个寓言故事,一只小老鼠刚刚出世不久,老鼠妈妈问小老鼠:你现在能看见了吗? 小老鼠说:能。 老鼠妈妈说:那你能看到那块红薯吗? 小老鼠说:是的。 老鼠妈妈说:那是一块石头,这说明你不但还看不见东西,你连嗅觉都还没有。
似是而非的应答往往一样会暴露出你更多的弱点。可能是以下中的一个或几个:
1.你之前没有想到这个工作,或者一直在拖延。
2.你没有责任心,认为这些并不重要。
3.你应付上级。
4.你不敢说真话。
5.你喜欢逞能,答应一些做不到的事情。
6.你不能独立工作。
当你的上级在以上选项中怀疑的时候,潜意识中你已经同时具备了以上所有的弱点了。
相反的看来,这样的回答,总是让上司恼火。
第一,他的问题没有得到回答,只是起到了提醒你的作用。
第二,他依然需要记住提醒你,因为他不知道你是否真正已经落实了工作。
第三,他不知道有多少你已经做了的事情中,都是这样没有落实的。(这点非常致命)
第四,往往因为没有得到满意的答案,上司自己的计划不得不被耽搁或推迟或不能给出明朗的结束时间。
所以---------
甲问:你什么时候能把要这个漏洞修好?
乙说:我已经通知他们了,他们大概明天就会来修的。
一天后
甲问:维修公司什么时候回来,你找的是哪家维修公司?
乙说:好像他们说安排不出人来,如果可以的话,今天晚上或者明天下午就能过来。
一天后
甲问:漏洞怎么还没有修好?
乙说:我晚点再问问他们。
甲说:今天下午之前不解决,明天不用来上班了。
第三:不要拖延工作
很多人喜欢在学习和玩耍之间先选择后者,然后在最后时间一次性赶工把考试要复习的东西突击完成。但是在工作中请不要养成这样的习惯,因为工作是永远做不完的,容不得你"突击"。又或者,当你在徘徊和彷徨如何实施的时候,你的领导已经看不下去,自己去做了。----这是一个危险的信号。
往往我们总是想把事情从头到尾全部想好了,才开始走第一步-----就摔倒了。
举个例子: 我小学的时候第一次给我一个喜欢的女孩子打电话的时候,想象了各种情况-------1,她接电话的时候在做作业。2,她在做作业,她妈妈接的电话。3.她也很无聊,很想找人说话。4.她正在被父母训斥。 5.她正在想另外一个男孩。6.她父亲接电话。 7.她家正好来了什么亲戚,亲戚接了电话。 8.她接了电话,但父母就在身边,说话不方便。。。。。等等等等。我整整想了一个下午,想好了各种情况的心理准备和应对的策略。然后勇敢的拿起电话机,按下了那几个按钮。结果-------她不在家。
所以,当你徘徊不前而手足无措的时候,你要意识到你正在拖延工作。徘徊是因为害怕这个事情可能发生的后果需要自己承担或应付。工作的时候需要一种起码的自信,相信自己有能力,不管下一步是什么状况,我都能把它引导到我需要的那条线上去的。另外,告诉自己,不要想太多时间,如果不知道,就赶快求助,或想办法,苦恼和忧虑会给你更多的压力也会把剩下的时间蚕食殆尽。
另外,警告一下:永远不要想,我知道了,先把上级派的事情放一下,等这集《越狱》看完再说。----90%的情况下,你会忘记,或者来不及,因为这件事需要比你原先想象要更多的时间。说做就做,一直是很好的习惯。
第四:不要认为理论上可以实施就大功告成了
这点太重要了,往往当真正实施的人开始做了才会发现计划完全等于鬼话。如果不亲自实践,做计划的人会早晚被实施的鄙视。永远需要提升自己的办实事的能力,而不是空谈。
首先,如果你是做办公室工作的,或者做策划和计划的。请千万不要把你自己都认为不太可能或者很难做到的事情,让别人试试看。比如,用一个下午的时间在人流量很少的地方举办露天歌唱会。这会让执行的人觉得你在玩他,拿他做实验。没错,理论上,在任何地方都能举办歌唱会,但是,在不同的地方,执行的人的心情是不一样的。
其次,和执行的人讨论你的安排。比如,新来的你的下属,你可以安排她坐在任何地方,但是如果那是一个很难和大家接触的角落,这可能比你什么都不安排更差。的确,理论上一个人要坐下来,需要的只是空间。但事实上远远不止那些。
再次,不要奢望一切会随着你的计划进行。理论上这个会议会持续两个小时,但是,这是"不考虑在开场后的30分钟全场都在调试话筒",或者"场下没有提出如此尖锐的问题"的前提下的状态。 大学生已经习惯了把事情做到 "理论上看上去很美"的程度了。 论文,ppt讲演,考试,辩论赛…… 这些校园智商大比拼,都是教我们如何完美的做好"纸上谈兵"的功夫。 你一定要相信自己能"搞定"事情的能力比想象的弱。
如果你是在学校的学生,测试一下自己,能否能搞定以下这些状况:
1.学校要制作一套校服,由你去寻找供应商,砍价,至少有三家公司的报价。
2.学校保安抓住一个学生偷窃,怎么处理?
3.学校的一个很重要路段的路灯坏了,你能否让它三天内继续亮起来。
4.食堂需要请一位专门烧清真菜的厨师,一周内到岗位。
当你开始思考以上这样的问题的时候,你会发现,他的思路和"看过去两年这个公司的业绩趋向,做出一个下个季度的市场策划方案"要相差极大。你会发现后者只要你做到"看上去很完美",没有人知道按照你这样做结果会怎样。而上述的工作你只要一想,就会体会到不少的压力。因为你不处理好,结果就是明显的失败更大的问题就会相继发生。
对了,这种感觉就是"工作"给你的感觉!这就是"工作"和"纸上谈兵"的差别!
第五:不要让别人等你
在任何情况下都不要让别人放下手头的工作来等你。在大学中可能只是同寝室的人的几句半开玩笑的抱怨,在工作上很可能导致你的潜在合作伙伴的丢失。
你在做一个工作的同时要知道别人的进度,而永远不要落后。
这不像是在考试,你比别人做的慢,别人可以先交卷,你到时间了做不完你自己承受扣分。在工作中的情况是这样的:这是一场没有人能做完的考试,所有的人,都分配做一张试卷的不同部分,有的人分到的是阅读理解,有的人做的是完形填空,有的人做的是语法…… 然后大家做完了相互抄,这样,所有人都做完了。 如果大家都把各自的部分做完了,而你却还在没有做完,那么做得快的别人会开始做你的那部分题目,然后也是相互抄。慢慢地,大家会发现你的工作量完全可以由另外人来代替,整个团队中可以不需要你,这个时候,没有人从你这里得到试卷的答案,也没有人会给你他们的答案--------很不幸,你已经没有利用价值了。
请一定记住这个例子。
第六:不要认为细节不重要
在大学里,往往做事粗枝大叶,看看差不多就行了。相反,在企业里管理的精髓就在于将简单的事情做到细节。一个慌忙寻找保险箱钥匙的动作就很有可能丧失你晋升财务主管的机会。
公司的管理,其实需要的并不是把很难的事情做到90%----比如,优化管理层的核心工作流程、改变公司在当地*面前的形象,提高产品质量,改善工作环境…… 而管理要做的是把每个简单的事情做到100%-----比如,把公司的每个人的档案都按照一定的规律整齐的存放起来、在门卫设立一个外来人员的签到台、把会议室多余的椅子拿走、和电视台讲好下个礼拜三来公司做采访、把试用装送到客户手里、在生产的咖啡上加一个口子、给下一期的封面人物拍照……等等如此。 如果你能把所有细节的问题都如实做到,那你才有开口升职的本钱。
很多人在毕业的时候不知道自己将来要做什么,于是就和自己说:我以后做管理吧!做管理?问一下自己,如果,公司资产被偷窃了,所有员工士气低下,办公室杂乱无章,公司电梯又坏了,打印机没墨了,采购计划超支了,产品滞销了,客户迟到了……你愿意解决这样的问题,并从小事开始做起吗?想好了这些再考虑是否把管理看得太空洞了。
第七:不要表现得消极,仅仅因为你所做的事情不是你的兴趣所在
很显然,在学生时代,当做到自己喜欢的时候,我们会pay200%的精力去创造,但如果是枯燥的事务,我们便懒得理睬,最好能有办法应付过去。但在工作上80%你所做的事情都是繁琐而看似机械的,如果仅仅为此而表现的闷闷不乐,那么你会郁闷更久。要知道你的上司已经为这个项目够烦恼了,你还想让他看到你的表情吗?
学会喜欢自己的工作,并把注意力放在日常工作能学到些什么上去。如果现在你努力的抱怨工作,那么接下来你就是努力的寻找工作。尽量少用"有趣","好奇"之类的词语来描述自己想要的工作,而是"充实","有成就感","乐意"
之类。
想想以下职位,你会发现生活中很多工作不是在等你有很好的状态下让你做的很有趣的事情:
1.高速公路收费口的收费员:一天都是面对一个小窗口,把一张卡片送出去,这样要持续好几年。
2.学校食堂厨师:永远在烧大排和鸡腿。烧一年。
3.作家:交稿期要到了,我还在孕育灵感,两个星期没吃早饭了。
4.外科医生:刚刚睡着,马上叫我做一个3小时的手术。这样至少一周一次。
5.门市部销售:产品不好卖,8点上班来就坐在店门口,一个人,坐到晚上6点,今天没有一个人来,和昨天一样。
6.公交司机:我开车不用你指挥。这条线路我开了三年了。
7.宠物商店店员:生意不好,还要一早就过来听着20条狗的叫声一整天,听一年。
8.公司职员:晚上两点下班,第二天还要8点上班。关键是路上还要一小时。这样已经一个月了。
再想想自己是不是只是接触了这个工作一个月或者才碰到没几个困难,这个时候抱怨的声音最大。
千万不要想着去选择一个有趣的职业,因为没有那样的工作存在。没有哪一"种"行业是开心的,因为如果有,那所有人都去干那个了。最多试着问问自己本身的兴趣吧。self exploration。
第八:绝对不要把改善工作能力仅寄托在公司培训上
人绝对不可能经过一次培训就脱胎换骨。相反,集体培训上学到的东西往往是最用不上的信息。 就像食堂烧大锅菜一样,总没有你最想吃的菜,因为这样做容易,并且不容易得罪人。
很多学生很看重所选的公司有没有培训,这说明,你不但不知道这个公司做什么,你甚至不知道怎样学习这些技能。
我的感悟是如果你不知道怎么学你想要的知识,也不知道你想要的知识是什么,你只会做出两种行为:1。等待别人来教你,并且等待别人发现你不知道的地方。2.寻找现成的答案并且拷贝。期待公司培训的人,就很大概率上是第一种人(不排除极少真正优秀的公司培训)
许多的同学有这样的习惯思维:
因为,这个公司的培训能结束达到多少多少的程度
又因为,这个程度正好是我想达到的
所以我尽力进这家公司
因为我进了这家公司
所以它自然会使我达到了这个期望的程度。
我们把参加培训和达到效果很幼稚的画上了等号。其实往往集体培训上所得到的信息是最没有实际操作价值的。永远不要期望单靠听课,靠老师把同样的东西给所有的人,你会得到比别人更多。把更多的心思放在观察和思考自己的需要上,找到问题的所在再通过观察和实践得到的答案才是真正的知识。
所以,刚刚开始工作,什么都不会,不要认为那样是正常的,因为公司还没有培训过呢!等我接受培训了之后,我就全都会了。如果你一无所知还等待别人会可怜你的无知而施舍你知识,那你会为你的无知而付出更多的智商。
第九:不要推卸责任
推卸责任是害怕的条件反射。不要认为别人看不出这点。
我记得我小学里的一件事情。我一次作业没有带来,老师要训斥我,说:你怎么老是作业不带?
我当时说:不是。。。。 当我正要支支吾吾时候,老师说:什么不是?你带来了没有?
我说:没有
老师说:那不就是没有带!什么不是!就是!
之后我就发现,我只是害怕承担责任而条件反射似的就说了"不是",仔细观察一下周围,你会发现,身边有无数的人在用"不是"作为被责问之后的第一反应。
其实现在很多人面对工作也是这样,当上级责问的时候,很条件反射的就做出了推卸动作,然而这样的动作,接下来往往是无力的辩解,以及一些很粗糙的借口。这样会让上司感到你这个人很难沟通,并且很不真实。
另外一种情况,就是无论什么情况下,我指责一个人,他永远是强调客观。其实这点才是学生最典型的特征。这说明他太容易受到其他事物的影响,并受它们决定。如果你和上司之间会出现以下类型的对话,想想是不是需要改一下你的处事方法。
甲:为什么到现在还没有给副总看你的报告!
乙:刚才c在打印,我在等他结束,现在他大概好了吧,我去看看
乙:还有点东西要修改
乙:b也要把东西给副总,我因为等他
乙:a他说我报告不用给副总看(a是乙的同级同事)
乙:不知道副总在不在哦,他的门关着。
乙:d他叫我帮他打印文件!怪他!(d是乙的同级同事)
乙:我的杯子突然找不到了,在找杯子。
不愿意负责任的人的不利在于他会让上司怀疑他的忠诚程度,因为上司给他的命令往往会因为一个小事情而被搁置或者打折执行,转而被他人的意识所改变。
第十:不要对自己说"我是大学生"
这点包涵了很多信息。
1.不要认为自己有多清高
2.不要仍然以学生的标准要求自己
3.不要感觉低人一等
4.不要等待别人的关怀
5.不要把这个作为犯错误自我安慰的借口
6.不要忘记搞清楚,公司究竟给自己的待遇是多少,老练些,这不是在做志愿者。

解决ubuntu 8.10 Intrepid网速低的问题

ubuntu
8.10上网速度慢,本来以为就这速度,可是同样环境下,和别人的XP一比,明显慢很多。
究其原因:ubuntu 8.10
自动连接时将网卡的mtu值自动设置成了576,而在多数据网关,比如一些路由的mtu值是
1492或其它的。这样网关在转发数据时就重新封装。
MTU是Maximum Transmission
Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。
大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度。把本机的MTU设成比网关的MTU小或相同,就可以减少丢包。
测试网关的MTU大小:
  在本机打开dos窗口,执行:ping -f -l 1472 192.168.0.1
  其中192.168.0.1是网关IP地址,1472是数据包的长度。请注意,上面的参数是"-l"(小写的L),而不是"-1"。
  如果能ping通,表示数据包不需要拆包,可以通过网关发送出去。
  如果出现:Packet needs to be fragmented but DF set.
  表示数据包需要拆开来发送。此时,减少数据包长度,再执行上面的ping命令。从1400到1472之间多试几次,就能找到合适的数据包长度了。把数据包长度加上数据包头28字节,就得到MTU的值。

  如果检测到网关的MTU值是1500,不需要修改。
  如果网关有防火墙ping不通,可以试试直接把MTU设为1400。


解决办法:设置网卡的mtu值为 网关一磁的就可以了。一般为 1492 。
1:手动设置
$ sudo ifconfig eth0 mtu 1492
其中eth0 是网卡名称,如果你的不是这个可以做相应的更改,
不过这样每次重新连接时双要设置一下,比较麻烦。下面我提供的一个劳永逸的办法:

2:网络连接(网卡被激活时)自动设置 mtu 值
在/etc/network/if-up.d目录下新建一个脚本文件,比如 setmtu
$ sudo vi /etc/network/if-up.d/setmtu

将下面的代码加入setmtu 脚 本中
#!/bin/sh
ifconfig "$IFACE" mtu 1492
保存退出,然后给这个脚本中可执行权限
$ sudo chmod a+x /etc/network/if-up.d/setmtu
其中 "$IFACE" 是一个变量,返回的是当前激活的网卡名称 比如 eth0 eth1 。
这样即使你有多张网卡,有网卡被激活时就自动运行这个个脚本 设置mtu值为1492了

如果本地的DNS服务器不是很稳定(症状:打开firefox网页总是"页面被重置",每页都要刷新无数次,opera呢?是反应慢):
sudo apt-get install dnsmasq
1.编辑/etc/dnsmasq.conf,搜索"listen-address"把注释号去掉,并修改为:
listen-address=127.0.0.1
2.编辑/etc/dhcp3/dhclient.conf,修改为:
#supersede domain-name "fugue.com home.vix.com";
prepend domain-name-servers 127.0.0.1;
request subnet-mask, broadcast-address, time-offset, routers,
domain-name, domain-name-servers, host-name,
netbios-name-servers, netbios-scope;
3.编辑/etc/resolv.conf,在第一行添加 :
nameserver 127.0.0.1
sudo /etc/init.d/dnsmasq restart

网友回复:
我也有这种问题,改了mtu,还是不行,最后自己添加了一个网络连接,设置跟原来的一样,问题没了,很诡异。

2009年3月29日星期日

天秤座大剖析

1. 偏好低调,不爱出风头,喜欢安静

2.
想借钱找天平成功的机率比较高,如果你和平子交情不错,他们又有足够的钱借你。因为平子不懂怎么拒绝别人。借出去的钱还8好意思催别人还。

3. 宽容 平子只会对别人宽容却不会对自己宽容 对自己要求挺严格的
经常和自己过不去别问理由,平子自己也不知道
  
4. 天平不擅表达
如果你能感觉平子对你三分的喜爱,事实上会有五分;如果你能感觉到五分,事实上会有七分。天平是不坦白的,会淡化感情的表达,会压抑自己的情绪。
  
5. 审美观超强 爱一切美好的事物。比如帅哥美女,但只是欣赏,当作艺术品。
  
6. 能忍
比如明明想见一个人,却不会见面。比如明明想知道谁的消息,却什么都不问。除非不想忍。
  
7. 不爱发短信,不爱打电话 懒蛋一个。对特别的人会例外。
  
8. 自尊心很强! 强过金钱,强过事业,也强过爱情。
9.
天平需要慢慢相处,因为平子是个被动的星座,慢热的星座,放不开的星座,认识时间越久对你越好。如果只是你喜欢平子,平子却不喜欢,频频接触的结果则是会对你越来越冷淡。
  
10.
一见钟情很难发生在天平身上,平子的爱需要时间。会喜欢很多人,却很难爱上一个人。喜欢细水长流的感情,暖暖细流,长长久久。
  
11. 平子知道什么是感恩图报。你对他们仁,他们就对你义。
  
12. 平子的喜欢不等于爱。多情而不滥情。
  
13.
除了感性,天平也可以理性的可怕。会很现实的考虑两个人的将来,如果觉得没有未来,就放开。
  
14. 对感情有点迟钝。
  
15. 配合 平子喜欢配合别人,迁就别人。自我意识比较薄弱。
  
16. 其实天平不擅交际的,一般情况下不喜欢说太多。
小老实,小保守。虽然嘴巴小笨却知道什么话该说,什么话不该说。不过在人多的场合也不会扭扭捏捏小家子气,说话会大大方方。这也是平子气质出众人缘好的一个原因。
  
17. 天平重视内在多于外在,重视精神多于物质。
  
18. 天平对朋友没什么要求 人好就行
就算心直口快老得罪人也无所谓因为天平不爱生气 不会记仇
对谁都会宽容就算伤害过自己的人。只记得别人的好。
  
19. 讨厌谁不会表现出来,只是不爱和你说话,或者干脆不睬你。
  
20. 有了心爱的人后就会变得很没安全感,怕寂寞
  
21.
不要献媚的乱夸奖,平子没你想象的那么虚荣,也没你想象的那么白痴。他们能清楚的分出哪句是虚情,哪句是假意。
  
22. 天平的头脑中性冷静 不论男女分析对待问题不会因为性别问题而偏激
处变不惊天平做事情处理事情好像一直都会不紧不慢
就算在一种很混乱的情况下也能冷静的思考不知道为啥 归天性吧
  
23. 不喜欢做决定,小事情随便怎么样都行,没所谓。 大事情很喜欢听朋友的意见
如果你是天平可以信赖的人 他们会问你的意见你需要做的只是分析
决定天平会自己来做如果他们觉得你分析的有道理会在心里琢磨 可行就采纳
如果天平有了自己的想法一旦下了决定就算全天下反对 几乎没有改变的可能
一意孤行其实多多少少有些靠直觉行事,不过天平的直觉大多时候还蛮准的
  
24. 温柔 温柔是天平的本性 就算外表看来是阿飞或者小太妹里头绝对有温柔的一面
  
25. 天平喜欢和自己喜欢的人斗斗嘴 却不会大声吵架 或者说根本不会吵架
你想吵可以天平只会转头走人
  
26.平子有善辩的口才,被别人误会的时候却不爱解释。表问为什么,就是不爱解释。
  
27.
不喜欢伤害任何人,宁愿伤害自己。前景不乐观时会对喜欢的人冷静的说恨话,会口是心非。为的是不伤害任何人,结果反到是既伤了别人,伤得更多却也正是平子自己。
  
28.
吃软不吃硬的一族。你软,平子就对你没脾气。你硬,平子就比你还横。有项调查,十二星座谋杀记录最少的就是善良的天平,不过发现大多死亡记录里都是和对方同归于尽的。。。哈哈哈平子脾气倔,惹怒了绝对敢和你同归于尽。
  
29. 让天平消气很容易 不管男平子还是女平子也不管对异性或者同性 只要你撒撒娇
说两句软话保准天平不会再有脾气
  
30. 天平对朋友绝对够义气 不过很多时候天平往往会低估自己这一点
只有碰到事情才能发现偶原来有把钱借给朋友交学费 搞到最后自己的学费米钱交
  
31. 不管对朋友还是恋人 在外边天平绝对袒护的是你 就算错的是你
没道理的是你也会和你一起对付外敌 不过事后你们单独相处的时候
才会告诉你说他们觉得哪里哪里似乎是你的不妥
  
32.
不要因为在平子面前丢了人而自卑或在意,因为平子绝对不会在意。平子觉得有时候丢人也是可爱的一种表现。
  
33. 不会打击别人,开玩笑时候除外。
  
34.
一般人都觉得天平会做人,不会得罪人。那是因为天平觉得没有和他们认真的必要。其实天平说话会很直,只有对好朋友说话才会一根肠子通到底,老说大实话。太直接偶尔会小伤人,但都是为了朋友好。比如知道好朋友的男朋友和其他女生在一起,一定不会怕好朋友伤心而因此隐瞒,天平会直言不讳的帮好朋友看清事实,面对事实,减少以后的伤害。对此,平子当作身为朋友义不容辞的责任。
  
35.
天平不喜欢向别人提要求。对朋友一点要求都不会提。如果向你提要求,就代表对你的喜欢和信赖非同一般,不过就算再信赖一个人,天平也很少开口要求什么。因为天平是随遇而安的,心态很平静悠哉悠哉的
而且不喜欢欠任何人的人情
  
36.
不喜欢打探朋友的太多私事,因为觉得不礼貌。也不喜欢逼喜欢的人的讲不愿讲的,平子认为这是一份尊重。  
37.
对于恋人的过去,过去就是过去,天平在乎的是现在的坦诚。同样,平子不会把上一份感情留在现在生活里,不允许心里死的人和眼前喜欢的人相提并论,因为会告诉自己,现在爱的才是最好的。同样,天平死去的爱情,是绝对没有可能爱火重燃的。
  
38. 平很好骗,因为单纯。平子不会没事就想着别人是不是在骗自己。
  
39. 失恋的时候喜欢听悲伤地情歌。既然伤就伤个够吧.
还会删除对方的电话号码联系方式,以及丢掉有回忆的东西。
  
40.
天平对理财没什么概念。经常不知道自己帐户里有多少钱,也不知道花了多少钱。对父母挺舍得花钱。对好朋友和喜欢的人也不会把帐算的很清。
  
41.
你可以欠天平的钱或者什么,但是天平却不喜欢欠别人的。天平欠别人钱的时候会天天惦记着,老觉得不爽,比别人欠自己钱还不爽。
理性的天平更感性,有颗不切实际单纯美好的心灵。比如看到蓝蓝的天会感动,会觉得生活真美好。。。比如看到乞讨的老人,会觉得可怜,然后想着以后有能力要帮助好多好多可怜的人...
所以,当一个天平对你说他的愿望是"世界和平,人人幸福"的时候,不要怀疑它的真实性,因为那颗美好稚气的心绝对是真的
42.
天平是脆弱的一族,却不会让别人看到自己的weak,甚至最好的朋友。心里有什么憋闷,不喜欢找好朋友倾诉,有时候宁愿拉着一个陌生人倾诉。因为陌生人听完就不会再记得那谁的故事。平子会继续撑起他们所谓的坚强的壳子。
  
43.
有条不紊的平子很少很难会情绪失控,也很少有人能让天平情绪失控,如果有就是看的很重的事或人。失控时间不会很久,短些就是几秒钟,长些就是几分钟。然后很快又会恢复先前的平静和冷静。自我调节能力蛮强。
  
44. 心情低落或受伤的平子不需要任何人的安慰,更加不会自暴自弃一蹶不振,no
way!
需要的只是空间时间以及那颗乐观的心。会自我安慰,自己劝自己看开,自己为自己疗伤,自己把自己武装。这个时候最好让平子自己呆着,因为朋友的出现不会对平子有什么帮助,而且,平子更不希望朋友看到自己低落受伤的样子。
45.
天平是乐观的,不喜欢对人抱怨,更不喜欢听别人找自己诉苦。所以你最好别在平子面前抱怨,就算你是他们最好的朋友。当你抱怨第一句时平子会安慰你,抱怨第二句是会附和你,抱怨第三句的时候,保证天平已经失去了继续听下去的耐心,虽然不会表现出来,但你会发现平子已经开始不说话了,只是"恩恩恩""是吖""哦"。天平可以帮你出点子,做分析,但绝对受不了唠唠叨叨抱怨诉苦。
  
46.
天平吃醋时不爱说话,也不会明讲。只是你会发现平子有点气嘟嘟的一个人生闷气,说话语气有点阴阳怪调,自然就8会给你好脸色看了。
  
47.
少说承诺,一旦说出就会履行;很少说"我爱你",说了出来就是真的爱你。喜欢用行动来证明一切。责任心重。
  
48.
平子不爱怀疑别人,所以也别怀疑平子诚实度。怀疑的结果是让平子伤,生气,也会变得不再相信你。
  
49. 喜欢创造小惊喜,喜欢制作小浪漫。
  
50.
如果两个互相相爱的人,天平觉得自己的存在阻碍对方的前途或发展,会选择自动离开。很爱很爱你,所以愿意,舍得让你,往更多幸福的地方飞去.很爱很爱你,所以愿意不牵绊你,飞向幸福的地方去.
  
51.
甩掉天平很容易,只要给一个理由,就算是你编出来的。不管理由是真是假,但请你亲口讲出来,天平都会离开,因为这是平子拿来替你说服平子自己的理由。平子不会纠缠,也不会乞求爱,更不会要施舍的爱。爱里的平子异常要强。
52.
接受能力很强,什么都可以理解,什么都可以接受,但请你亲口说出来。因为即使平子猜得到,从别人那里听的到,也希望亲耳听你说出来。因为关于你,平子在乎的只是你
  
53.
如果你不能给平子什么,不要招惹他们,他们比你想象的单纯,也没你想象的坚强
  
54.
平子相信分手的恋人还可以做朋友,只要对方愿意,希望大家都好。如果曾经那些叫**,就不要带着恨吧。
  
55.
如果你表白后,一个平子对你说"不喜欢你"或者类似的话,就等于判了你死刑。这个时候不要想着继续努力来感动平子。没用的。平子就像弹簧,你施力越大,他们就把你弹的越远。平子对自己不喜欢的人可以很残忍,就算心里会内疚也会继续残忍,好像这也是平子最冷血的时候。如果还想做朋友,就什么都不要做。
  
56. 被平子爱的人是幸福的,他们会为你而活,请好好珍惜。
  
57.
爱上平子的人是痛苦的,如果平子爱的不是你。结果往往是对平子又爱又恨又不愿放弃。
  
58.
小事糊涂,大事聪明。有些平子看起来是正常人,可经过交谈后会发现有些很简单的东西都不知道。不要以为那是在装,平子就是这样,出人意料的单纯,大家都懂的事情会不懂。发觉了单纯之后也不要以为平子就蠢的什么都不懂,大事情照样可以比你更有智慧,更有主见,更有见地。平子没你想象的精明,他们本不是精打细算的人;平子没你想象的愚蠢,他们有的是大智慧。

at命令的用法

at命令可以在规定时刻执行指定的任务。
crontab命令可以周期性的执行一些人物。
区别:
at: 这个工作公执行一次就从Linux系统的流程中取消
crontab:这个工作将持续例行性的做一去.

用法一:
命令行输入:
at 17:20 tomorrow 回车会出现:
warning: commands will be executed using /bin/sh
at> 这时候输入到时间后要执行的命令,可以输多行,输完了ctrl+d.
就可以了

用法二:
将命令保存成文件再执行:
at -f 文件名 17:20 tomorrow 回车.
这个文件最好加个可执行权限.
另外,如果你要重启的话,还要考虑root权限的问题.

说明:你运行的程序到时会执行,但并不显示出来。要想显示出来,需要指定显示位置。如
at now + 1 minutes
warning: commands will be executed using /bin/sh
at>export DISPLAY=:0 && firefox
at><EOT>
一分钟后,就会看到firefox显示在第0个桌面。

或者你可以测试一下at命令,让它执行:
touch ~/amai

在x下,也可以用at命令作提醒用。
执行命令:
at 12:00
MAIL=/dev/null DISPLAY=:0 zenity --info --text "time to lunch"
at> [ Ctrl + d ]
ps. 在 at 提示下设置 MAIL=/dev/null 可让提醒信息结束后不发送 e-mail
执行结果:

图文并茂教你学游泳

本贴的目标对象是那些希望学会游泳,但身边又没有人教的,身材比较适中的人。
比较适中,指身材不是极度的胖,也不是极度的瘦。否则,我所讲述的内容可能不适合与你。需要说明的是,我所讲述的游泳方法主要目的是让你能在水池里自主的动起来,不会被淹死,并不是让你能像那些高手一样飞快的向前游起来,要达到这个程度,请报名参加游泳培训班。但是,如果掌握了基本的游泳方法,获得更高的提升也就不是空谈了。
我知道山上游泳高手一把一把的,如果你有更适合新手学会游泳的方法和建议,请不要吝啬。
首先说一下我自己的游泳状况,我打小在乡下长大,没有正规的学过游泳,我的一切游泳都起始于经典的狗刨式。样子难看,却安全牢靠。我的第一次游泳是5、6岁的时候被我老爸套上了救生圈往河里一推,然后在水里哇哇大哭...
两周以后因为不肯从河里出来,而被我爸爸拉上岸,一顿"桑活"然后照样哇哇大哭...
现在你已经知道了,我的游泳是野路子出身,但是那又怎么样呢?各位没有人来教会游泳的可不都是准备走野路子这条路吗?

因为是野路子,所以你别拿来跟标准游泳比较了,但是可能要比教科书上那些更好懂。(我没看过游泳教科书,不知道它写得是不是浅显)

器材准备:游泳装、游泳帽、游泳眼镜、鼻夹、耳塞
耳塞看你自己乐意,不要也无所谓,要买么也很便宜
鼻夹对新手有帮助,可以避免慌乱 因为它便宜,就要一个吧
游泳眼镜一定要有,也就几十块钱的东西,你少几包烟,少涂几次洗面奶就回来了。不用特别好,300、400的那是装B用的。防水性好,戴着舒服就成。一般新的泳镜内层都有防雾
涂层,尽量别用手去摸。低于40块的也别考虑了,橡胶边的很不可靠,还是要买硅胶边的。
有的游泳池不带泳帽不给下水的 买一个吧,硅胶的也就12块 布的才4块、5块
泳衣,建议买色彩鲜艳的。这样新手比较容易让救生员注意到你。
其实所有人都会游泳,只是有的人还不知道自己会游泳而已。


各位应该都能知道这一点:那些不会水的人,掉进了平静的河水里(进了奔腾的黄河,菲尔普斯、庄泳、北岛...下去了也一样死路一条),并不是直接就沉下去淹死了,而是先扑腾。扑腾到最后没有力气了,就淹死了。可见,只要你能动,就可以帮助自己浮起来。而会游泳的人就知道如何花较少的力气保持自己浮在水面上的状态。会不会游泳的差别仅此而已。

慌乱,是导致在水里出事的重要原因。所以:
第一步:请先克服自己在水里的慌乱。
方法不难,下到浅水区,戴上泳镜,吸一口气,憋住它,用优雅的动作把脑袋慢慢埋到水里,看看水下的世界是多么美妙。美女的大腿、帅哥的屁股...
当然,更要看看清楚壁砖... 熟悉了周围的环境,就不容易慌乱。
20秒钟以后,也许你已经觉得有些缺氧了,不要慌张,还用优雅的动作慢慢的抬起头,出了水面不要立即张嘴呼吸,继续憋气一秒或半秒。让你脑袋上的水流下来,不要灌进嘴巴里。(如果你连20秒也憋不住,去医院查心肺功能吧,别下水了)
注意,新手会因为觉得有些缺氧,非常着急的把脑袋顶出水面,紧急的吸气,是呛水的重要原因。一旦呛水,就会慌乱。所以,吸气这个动作,请尽量让自己保持优雅。
刚出水面,被你脑袋顶起来的水花还没掉下来,这个时候你猛吸气,它很容易就会进你的嘴。所以,注意这个细节,出水以后,再忍一秒钟。或者,出水的速度尽量的慢,让水花不要跟起来。
在你出水的时候,戴着眼镜的人也更容易知道自己脑袋处于什么状况了,闭着眼你都不知道周围水花状况如何。有些新手因为觉得反正还没学会游泳,泳镜以后买好了,我觉得其实这个是错的思路。
当你可以从容的把自己浸没在水里,又可以从容的出水,并且换气的时候不会被呛到,那么你已经离学会游泳不远了。容易吧?

第二步:原地沉浮换气
因为我假设的条件是没有人教你,所以这一步要优先学会。学会之后就淹不死了。
现在走到水大约齐胸的深度,靠近池壁,(其实这只是给你自己一个心理安全的暗示)要满足这个条件:慢慢一伸脚,可以轻松的站出水面。
还是跟刚才那样,吸气,屏住。然后,稍微弯曲一下腿。让自己整个人沉下去,但头不要低下去。这个时候你会发现,原来你不会一下子沉到底的。而是会随着缓慢的节奏,一起一伏的在水面上"藤"。好玩吧?
你有没有注意到,当你的嘴巴高出水面的时候,大致是多少时间?而你吸气的时候,你才用了多少时间?
其实,嘴巴浮出水面的时间,远远大于你吸气所需要的时间。你需要注意的是保持冷静,然后在脑袋呈上升相的时候,呼气出去,并吸气进来。然后又屏气,然后你会随着下一个沉浮节奏,脑袋又来到了水面以下。
如此循环往复。你会不会纳闷,那些人是怎么淹死的?原来在平静的水里,人手脚不动都不会死掉。
注意,有的人吸气量不大,浮动的时候嘴巴不完全露出水面,这个时候只要手往下轻轻一压,就会明显的出水了。(这个时候你还是保持腿轻微弯曲,暂时我们不需要腿的帮忙好了)
怎么样?容易吧?
在这个环节中,还是要注意的是,在嘴巴出水面之后,不要让自己呛到水,一呛水,新手立即就紧张了,后面的就全乱套了。
好了,现在假设你觉得嘴巴浮出水面的高度不够,你想浮得更高一些,很容易,再加上两条腿,往下轻轻的推蹬就可以。注意,是轻轻的。剧烈的蹬水会让你抬起来很高,却再沉下去的幅度也大。没必要。

我们回忆一下奥运会游泳比赛里那些高手,其实他们也是脑袋在水里一进一出的。虽然他们可以做到完全把脑袋始终保持在水面以上,但是没那个必要。出水那一点点时间足够我们换气了。

第三步:让自己有方向性的动起来。
既然你已经可以让自己在水里沉浮了,那么这个也就很容易了。只需要两手往后轻轻划动就可以了。
双手往前直伸 然后打开,往后推 收进来 再往前伸 如此循环
注意:这个时候我们还没有配合到脚的动作 蛙泳是泳池里很常见的泳姿
看看别人就知道了 在双手往前伸的时候,双腿往后推 轻轻的推
目前我们所有的动作都轻轻的来动的速度如何不重要,总之你在动了。
电视里,那些游泳高手人几乎是横在水中的,这样阻力最小。但我们目前没达到这个水准,就倾斜着前进吧。这样抬头换气也更容易。我们就仰着头往前划水好了,样子难看一些就难看一些好了。大家都是这么过来的。
有的人觉得自己怎么老在原地划水,那是因为你往后的划水动作跟你的腿部动作不协调。于是你前进的动力被自己的腿部动作给打乱了。这是没办法立即改正的,要在水里多练习。时间一长自己会纠正过来。
第四步:让自己能在向前滑动的过程中换气

如果在做这些动作的时候你的嘴巴都是在水面以上的,那么不存在这个问题,但事实上大部分情况下,我们游泳的时候总是嘴巴在水面上下起伏的。请结合第一第二部分,在嘴巴浮出水面的时候换气。轻轻的 这不难。
还有一种状况是,游的时候头是往下压的,低在水中,然后该换气的时候就抬不起来。为避免新手的这种状况,你尽量不要让自己身体横向的前进,就斜向的前进好了。虽然阻力大了点,速度慢了点,累也累了点,但是这容易让你换气。作为新手,只要能有所进展,就可以建立起信心,不会半途而废,学起来会更快。


请注意蛙泳高手在水里的姿势,他们几乎是平的
而你注意一下新手在水里的表现

新手在水里头往往是朝下的,背是弓的,这样的姿势,你换成北岛康建来,他也会觉得抬头很费力的

所以,我给新手的建议是:
不要让自己在水里呈水平位置,刚开始,大家没必要全力去模仿高手。
高手要唤气,也要把背仰起,然后抬头的。所以,新手可以一开始就始终让自己头向上拔高,手脚在下面折腾好了。
人在水里,需要让自己憋气成为一种很自然的感觉,就是感觉那种只要嘴唇边有水了,就立即闭气的感觉,不需要动脑子去指挥嘴。再加上在水里不会慌乱了,游泳这事情基本也就成了。

踩水的时候,请结合我说的第2步,在原地沉浮时候一起用 效果很好 可以很轻易的帮助嘴巴浮到水面以上
其实踩水是非常没有技术含量的,仅仅是你两腿往下轻轻的推动
但是,你可以这样尝试:在张开的两腿 在往下蹬的时候,有意识的让膝盖先并拢 然后才是两脚并到一起
注意,动作要轻轻的
否则你猛的浮起来了,必然紧接着再猛的沉下去 只要嘴巴略高出水面就可以了
也请注意这幅图片里,我故意把人的半个脑袋没在水里,也就是说,在水里,这样的嘴巴在水面偏下的位置是很正常的,不用惊慌。手或脚稍微加点力就上去了
不会水的人由于紧张,会猛的蹬脚划手,这样一瞬间他是起了水面了,可是并没有利用好这段时间换气。接着他又沉下水面了。于是呛水了,必然的导致了惊慌,于是全乱套了,动作更剧烈了。剧烈的动作必然跟随巨大的氧气消耗,越是缺氧越不容易保持冷静,最后全乱套了,形成一个恶性循环... 直到最后淹死。惨剧就是这么来的。
我一般在水里不踩水,只靠手 也不用往下,前后轻微摆动就可以让人浮起来,这样更省力 我是个懒惰胚,用脚太累了


C语言中可变参数的用法

一、什么是可变参数
我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:
int printf( const char* format, ...);
它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点"…"做参数占位符),实际调用时可以有以下的形式:
printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);
以上这些东西已为大家所熟悉。但是究竟如何写可变参数的C函数以及这些可变参数的函数编译器是如何实现,这个问题却一直困扰了我好久。本文就这个问题进行一些探讨,希望能对大家有些帮助.

二、写一个简单的可变参数的C函数
先看例子程序。该函数至少有一个整数参数,其后占位符…,表示后面参数的个数不定.
在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值.
函数代码如下:
//示例代码1:可变参数函数的使用
#include "stdio.h"
#include "stdarg.h"
void simple_va_fun(int start, ...)
{
va_list arg_ptr;
int nArgValue =start;
int nArgCout=0; //可变参数的数目
va_start(arg_ptr,start);
//以固定参数的地址为起点确定变参的内存起始地址。
do
{
++nArgCout;
printf("the %d th arg: %d\n",nArgCout,nArgValue);
//输出各参数的值
nArgValue = va_arg(arg_ptr,int);
//得到下一个可变参数的值
} while(nArgValue != -1);
return;
}
int main(int argc, char* argv[])
{
simple_va_fun(100,-1);
simple_va_fun(100,200,-1);
return 0;
}
下面解释一下这些代码
从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
⑴由于在程序中将用到以下这些宏:
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
va在这里是variable-argument(可变参数)的意思.
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.
⑵函数里首先定义一个va_list型的变量,这里是arg_ptr,这个变
量是存储参数地址的指针.因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。
⑶然后用va_start宏初始化⑵中定义的变量arg_ptr,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数.
⑷然后依次用va_arg宏使arg_ptr返回可变参数的地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。
⑸设定结束条件,这里的条件就是判断参数值是否为-1。注意被调的函数在调用时是不知道可变参数的正确数目的,程序员必须自己在代码中指明结束条件。至于为什么它不会知道参数的数目,读者在看完这几个宏的内部实现机制后,自然就会明白。

(二)可变参数在编译器中的处理
我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,
由于1)硬件平台的不同
2)编译器的不同,所以定义的宏也有所不同,下面看一下VC++6.0中stdarg.h里的代码(文件的路径为VC安装目录下的\vc98\include\stdarg.h)
typedef char * va_list;
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
下面我们解释这些代码的含义:
1、首先把va_list被定义成char*,这是因为在我们目前所用的PC机上,字符指针类型可以用来存储内存单元地址。而在有的机器上va_list是被定义成void*的
2、定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.这个宏的目的是为了得到最后一个固定参数的实际内存大小。在我的机器上直接用sizeof运算符来代替,对程序的运行结构也没有影响。(后文将看到我自己的实现)。
3、va_start的定义为 &v+_INTSIZEOF(v)
,这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以我们运行va_start(ap,
v)以后,ap指向第一个可变参数在的内存地址,有了这个地址,以后的事情就简单了。
这里要知道两个事情:
⑴在intel+windows的机器上,函数栈的方向是向下的,栈顶指针的内存地址低于栈底指针,所以先进栈的数据是存放在内存的高地址处。
(2)在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。
|——————————————————————————|
| 最后一个可变参数 | ->高内存地址处
|——————————————————————————|
...................
|——————————————————————————|
| 第N个可变参数 |
->va_arg(arg_ptr,int)后arg_ptr所指的地方,
| | 即第N个可变参数的地址。
|——————————————— |
………………………….
|——————————————————————————|
| 第一个可变参数 |
->va_start(arg_ptr,start)后arg_ptr所指的地方
| | 即第一个可变参数的地址
|——————————————— |
|———————————————————————— ——|
| |
| 最后一个固定参数 | -> start的起始地址
|—————————————— —| .................
|—————————————————————————— |
| |
|——————————————— | -> 低内存地址处

(4)
va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。
因此,现在再来看va_arg()的实现就应该心中有数了:
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
这个宏做了两个事情,
①用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值
②计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。
(5)va_end宏的解释:x86平台定义为ap=(char*)0;使ap不再
指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.
在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型.
关于va_start, va_arg, va_end的描述就是这些了,我们要注意的
是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.

(三)可变参数在编程中要注意的问题
因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,
可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能
地识别不同参数的个数和类型.
有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数
printf是从固定参数format字符串来分析出参数的类型,再调用va_arg
的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.
例如,在C的经典教材《the c programming
language》的7.3节中就给出了一个printf的可能实现方式,由于篇幅原因这里不再叙述。
(四)小结:
1、标准C库的中的三个宏的作用只是用来确定可变参数列表中每个参数的内存地址,编译器是不知道参数的实际数目的。
2、在实际应用的代码中,程序员必须自己考虑确定参数数目的办法,如
⑴在固定参数中设标志—— printf函数就是用这个办法。后面也有例子。
⑵在预先设定一个特殊的结束标记,就是说多输入一个可变参数,调用时要将最后一个可变参数的值设置成这个特殊的值,在函数体中根据这个值判断是否达到参数的结尾。本文前面的代码就是采用这个办法.
无论采用哪种办法,程序员都应该在文档中告诉调用者自己的约定。
3、实现可变参数的要点就是想办法取得每个参数的地址,取得地址的办法由以下几个因素决定:
①函数栈的生长方向
②参数的入栈顺序
③CPU的对齐方式
④内存地址的表达方式
结合源代码,我们可以看出va_list的实现是由④决定的,_INTSIZEOF(n)的引入则是由③决定的,他和①②又一起决定了va_start的实现,最后va_end的存在则是良好编程风格的体现,将不再使用的指针设为NULL,这样可以防止以后的误操作。
4、取得地址后,再结合参数的类型,程序员就可以正确的处理参数了。理解了以上要点,相信稍有经验的读者就可以写出适合于自己机器的实现来。下面就是一个例子
(五)扩展——自己实现简单的可变参数的函数。
下面是一个简单的printf函数的实现,参考了<The C Programming
Language>中的156页的例子,读者可以结合书上的代码与本文参照。
#include "stdio.h"
#include "stdlib.h"
void myprintf(char* fmt, ...)
//一个简单的类似于printf的实现,//参数必须都是int 类型
{
char* pArg=NULL; //等价于原来的va_list
char c;

pArg = (char*) &fmt; //注意不要写成p = fmt
!!因为这里要对//参数取址,而不是取值
pArg += sizeof(fmt); //等价于原来的va_start

do
{
c =*fmt;
if (c != '%')
{
putchar(c); //照原样输出字符
}
else
{
//按格式字符输出数据
switch(*++fmt)
{
case 'd':
printf("%d",*((int*)pArg));
break;
case 'x':
printf("%#x",*((int*)pArg));
break;
default:
break;
}
pArg += sizeof(int); //等价于原来的va_arg
}
++fmt;
}while (*fmt != '\0');
pArg = NULL; //等价于va_end
return;
}
int main(int argc, char* argv[])
{
int i = 1234;
int j = 5678;

myprintf("the first test:i=%d\n",i,j);
myprintf("the secend test:i=%d; %x;j=%d;\n",i,0xabcd,j);
system("pause");
return 0;
}
在intel+win2k+vc6的机器执行结果如下:
the first test:i=1234
the secend test:i=1234; 0xabcd;j=5678;

物理内存与线性地址的关系

如果你不懂什么是逻辑地址、线性地址、物理地址和虚拟地址,请先看:
http://linux.chinaunix.net/bbs/viewthread.php?tid=919019&extra=page%3D1%26amp%3Bfilter%3Ddigest

在硬件工程师和普通用户看来,内存就是插在或固化在主板上的内存条,它们有一定的容量——比如64
MB。但在应用程序员眼中,并不过度关心插在主板上的内存容量,而是他们可以使用的内存空间——他们可以开发一个需要占用1
GB内存的程序,并让其在OS平台上运行,哪怕这台运行主机上只有128
MB的物理内存条。而对于OS开发者而言,则是介于二者之间,他们既需要知道物理内存的细节,也需要提供一套机制,为应用程序员提供另一个内存空间,这个
内存空间的大小可以和实际的物理内存大小之间没有任何关系。
我们将主板上的物理内存条所提供的内存空间定义为物理内存空间;将
应用程序员看到的内存空间定义为线性空间。物理内存空间大小在不同的主机上可以是不一样的,随着主板上所插的物理内存条的容量不同而不同;但为应用程序员
提供的线性空间却是固定的,不会随物理内存的变化而变化,这样才能保证应用程序的可移植性。尽管物理内存的大小可以影响应用程序运行的性能,并且很多情况
下对物理内存的大小有一个最低要求,但这些因素只是为了让一个OS可以正常的运行。
线性空间的大小在32-bit平台上为4
GB的固定大小,对于每个进程都是这样(一个应用可以是多进程的,在OS眼中,是以进程为单位的)。也就是说线性空间不是进程共享的,而是进程隔离的,每
个进程都有相同大小的4
GB线性空间。一个进程对于某一个内存地址的访问,与其它进程对于同一内存地址的访问绝不冲突。比如,一个进程读取线性空间地址1234ABCDh可以读
出整数8,而另外一个进程读取线性空间地址1234ABCDh可以读出整数20,这取决于进程自身的逻辑。
在任意一个时刻,在
一个CPU上只有一个进程在运行。所以对于此CPU来讲,在这一时刻,整个系统只存在一个线性空间,这个线性空间是面向此进程的。当进程发生切换的时候,
线性空间也随着切换。所以结论就是每个进程都有自己的线性空间,只有此进程运行的时候,其线性空间才被运行它的CPU所知。在其它时刻,其线性空间对于
CPU来说,是不可知的。所以尽管每个进程都可以有4
GB的线性空间,但在CPU眼中,只有一个线性空间的存在。线性空间的变化,随着进程切换而变化。
尽管线性空间的大小和物理内存的大小之间没有任何关系,但使用线性空间的应用程序最终还是要运行在物理内存中。应用所给出的任何线性地址最终必须被转化
为物理地址,才能够真正的访问物理内存。所以,线性内存空间必须被映射到物理内存空间中,这个映射关系需要通过使用硬件体系结构所规定的数据结构来建立。
我们不妨先称其为映射表。一个映射表的内容就是某个线性内存空间和物理内存空间之间的映射关系。OS
Kernel一旦告诉某个CPU一个映射表的位置,那么这个CPU需要去访问一个线性空间地址时,就根据这张映射表的内容,将这个线性空间地址转化为物理
空间地址,并将此物理地址送到地址线,毕竟地址线只知道物理地址。
所以,我们很容易得出一个结论,如果我们给出不同的映射表,
那么CPU将某一线性空间地址转化的物理地址也会不同。所以我们为每一个进程都建立一张映射表,将每个进程的线性空间根据自己的需要映射到物理空间上。既
然某一时刻在某一CPU上只能有一个应用在运行,那么当任务发生切换的时候,将映射表也更换为响应的映射表就可以实现每个进程都有自己的线性空间而互不影
响。所以,在任意时刻,对于一个CPU来说,也只需要有一张映射表,以实现当前进程的线性空间到物理空间的转化。
--------------------------------------------------------------------------------
2. OS Kernel Space & Process Space
由于OS
Kernel在任意时刻都必须存在于内存中,而进程却可以切换,所以在任意时刻,内存中都存在两部分,OS
Kernel和用户进程。而在任意时刻,对于一个CPU来说只存在一个线性空间,所以这个线性空间必须被分成两部分,一部分供OS
Kernel使用,另一部分供用户进程使用。既然OS
Kernel在任何时候都占用线性空间中的一部分,那么对于所有进程的线性空间而言,它们为OS
Kernel所留出的线性空间可以是完全相同的,也就是说,它们各自的映射表中,也分为两部分,一部分是进程私有映射部分,对于OS
Kernel映射部分的内容则完全相同。
从这个意义上来说,我们可以认为,对于所有的进程而言,它们共享OS
Kernel所占用的线性空间部分,而每个进程又各自有自己私有的线性空间部分。假如,我们将任意一个4
GB线性空间分割为1 GB的OS Kernel空间部分和3
GB的进程空间部分,那么所有进程的4 GB线性空间中1 GB的OS
Kernel空间是共享的,而剩余的3
GB进程空间部分则是各个进程私有的。Linux就是这么做的,而Windows NT则是让OS
Kernel和进程各使用2 GB线性空间。
--------------------------------------------------------------------------------
3. Segment Mapping & Page Mapping
所有的线性空间的内容只有被放置到物理内存中才能够被真正的运行和操作。所以,尽管OS
Kernel和进程都被放在线性空间中,但它们最终必须被放置到物理内存中。所以OS
Kernel和所有的进程都最终共享物理内存。在现阶段,物理内存远没有线性空间那么大——线性空间是4
GB,而物理内存空间往往只有几百兆,甚至更小。另外即使物理内存有4
GB,但由于每个进程都可以有3 GB线性空间(假如进程私有线性空间是3
GB的话),如果把所有进程的线性空间内容都放在物理内存中,明显是不现实的。所以OS
Kernel必须将某些进程暂时用不到的数据或代码放在物理内存之外,将有限的内存提供给当前最需要的进程。另外,由于OS
Kernel在任何时候都有可能运行,所以OS
Kernel最好被永远放在物理内存中。我们仅仅将进程数据进行换入换出。

线性空间到物理空间的映射需要映射表,映射表的内容是将某段线性空间映射到相同大小的物理内存空间上。从理论上,我们可以使用两种映射方法:变长映射,和
定长映射。变长映射指的是根据不同的需要,将一个一个变长段映射到物理内存上,其格式可以如下(线性空间段起始地址,物理空间段起始地址,段长度)。假如
一个进程有3个段:10M的数据段,5M的代码段,和8K的堆栈段,那么就可以在映射表中建立3项内容,每一项针对一个段。这看起来没有问题。但假如现在
我们的实际的内存只有32M,其中10M被内核占用,留给进程的物理空间只有22M,那么此进程在运行时,就占据了10M+5M+8K的内存空间。随后当
进程发生切换时,假如另一个进程和其有相同的内存要求,那么剩余的22M-(10M+5M+8K)明显就不够用了,这时只能将原进程的某些段换出,并且必
须是整段的换出。这就意味着我们必须至少换出一个10M的数据段,而换出的成本很高,因为我们必须将这10M的内容拷贝到磁盘上,磁盘I/O是很慢的。
所以,使用变长的段映射的结果就是一个段要么被全部换入,要么被全部换出。但在现实中,一个程序中并非所有的代码和数据都能够被经常访问,往往被经常访
问的只占全部代码数据的一部分,甚至是一小部分。所以更有效的策略是我们最好只换出那些并不经常使用的部分,而保留那些经常被使用的部分。而不是整个段的
换入换出。这样可以避免大块的慢速磁盘操作。
这就是定长映射策略,我们将内存空间分割为一个个定长块,每个定长块被称为一个
页。映射表的基本格式为(物理空间页起始地址),由于页是定长的,所以不需要指出它的长度,另外,我们不需要在映射表中指定线性地址,我们可以将线性地址
作为索引,到映射表中检索出相应的物理地址。当使用页时,其策略为:当换出的时候,我们只将那些不活跃的,也就是不经常使用的页换出,而保留那些活跃的
页。在换入的时候,只有被请求访问的页才被换入,没有被请求访问的页将永远不会被换入到物理内存。这就是请求页(Demand
Page)算法的核心思想。
这就引出一个页大小的问题:首先我们不可能以字节为单位,这样映射表的大小和线性空间大小相同——
假如整个线性空间都被映射的话——我们不可能将全部线性空间用作存放这个映射表。由此,我们也可以得知,页越小,则映射表的容量越大。而我们不能让映射表
占用太多的空间。但如果页太大,则面临着和不定长段映射同样的问题,每次换出一个页,都需要大量的磁盘操作。另外,由于为一个进程分配内存的最小单位是
页,假如我们的页大小为4 MB,那么即使一个进程只需要使用4
KB的内存,也不得不占用整个4
MB页,这明显是一种很大的浪费。所以我们必须在两者之间进行折衷,一般平台所规定的页大小为1
KB到8 KB,IA-32所规定的页大小为4 KB。(IA-32也支持4
MB页,你可以根据你的OS的用途进行选择,一般都是使用4 KB页)。
--------------------------------------------------------------------------------
4. Page Table
假如使用4 KB的页,那么对于4
GB的线性空间,则需要1,048,576个页表实体,每个表项占用4个字节,则需要4,194,304个字节。仅仅页表就占用4
MB空间,这是一个很大的需求。但如果确确实实一个进程需要使用全部线性空间的话,那么这4
MB的页表空间投入也是必要的。
但在现实中,很少有那个程序需要使用这么大空间,一般的程序往往很小,从几KB到几MB,再使用这么大的页表就纯粹是一种浪费。那我们该怎么办?
一种策略是建立变长页表——我们只建立所需长度的页表。但这种策略带来很大的限制,并且仍然会造成比较大的空间浪费。由于页表机制是使用线性地址作为索
引,到页表中进行检索。那么如果我们想让OS
Kernel使用C0000000h-FFFFFFFFh,也就是3 G
http://www.xrss.cn/Dev/PC/2007111117550.Html

二进制与文本文件的区别

http://topic.csdn.net/t/20050518/17/4017296.html
文本文件也称ASCII文件,在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII码。例如,数5678的存储形式为:
ASC码:  00110101 00110110 00110111 00111000
↓     ↓    ↓    ↓
十进制码: 5     6    7    8
共占用4个字节。ASCII码文件可在屏幕上按字符显示,
例如源程序文件就是ASCII文件,用DOS命令TYPE可显示文件的内容。
由于是按字符显示,因此能读懂文件内容。
二进制文件是按二进制的编码方式来存放文件的。
例如,
数5678的存储形式为:
00010110 00101110只占二个字节。
二进制文件虽然也可在屏幕上显示,但其内容无法读懂。
C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。
输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。
因此也把这种文件称作"流式文件"。
------------
数据就是数据,关键看你怎么使用了,如果说区别,就是文本文件载入的时候,会把回车换行2个符号转换成回车符号,而吃掉换行符,存盘的时候把这个吃掉的换行符吐回去...
至于也可以把文本文件当初数据文件来读,纯粹是看应用了。
就好比有人问10进制,16进制的区别,纯粹就是一回事 !!!
都是数据,只不过看问题的角度不同而已,只是你观察角度不同而已。他们都是一样的。
好比'1'
你用文本文件看是'1',你用16进制工具看是0x31
实际上都是0x31,或者是0x0011 0001
-------------
没有区别,从文件系统的角度,所有的文件都是一个顺序的比特集合,区别在于程序如何解读这些数据,一段数据可以看成是文本,图像或者声音
------------------
说到底计算机存储的文件都是以二进制形式存储的,但是区别是,习惯上认为:
1)、文本文件
文本文件是包含用户可读信息的文件。这些文件以ASCII码方式存储,可显示和打印。文本文件的行不能包括空字符(即码中的NULL),行的最大
长度(包括换行符在内)也不能超过(LINE_MAX)所定义的字节数。不过文本文件中并不限制使用除空字符以外的控制字符或其它不可打印字符。
(2)、二进制文件(实际上这种说法不准确,因为文本文件也是用二进制代码存放的,称为非文本文件比较好)
二进制文件是包含计算机可读信息的文件。二进制文件可以是可执行的文件,使系统根据其中的指令完成某项工作。命令和程序都是以可执行的而进制文件方式存储。二进制文件没有行的长度限制,也可包含空字符。
--------------
http://www.net0791.com/article/45480.htm
看个例子:
#include<stdio.h>
int main()
{
FILE *fp;
int i = 12;
int j = 12;
fp = fopen("01.txt","wb");
fprintf(fp,"%d",i);
fputc('\n',fp);
fwrite(&j,sizeof(int),1,fp);
fclose(fp);
return 0;
}
看输出结果。
linux:~/test # od -x 01.txt
0000000 3231 0c0a 0000 0000
即使是用二进制打开,但如果你用fputc,fputs,fprintf这些函数,其实还是和用文本文件打开一样。
只有用到fwrite/fread函数,才会看到一个整形占4个字节。
按二进制写文件指的是直接按照数据在内存中的表现形式写入文件。例如,如果int型数据在内存中用
4 个字节表示,则写这个int数据的时候直接把对应的内存中 4
个字节的内容写入文件。在此过程中数据不需要做任何转换,所以效率较高。

据有字符型和非字符型(数)两种。按文本方式写文件指的是将数据转换为对应的字符型数据之后再写入文件。对于字符型数据,由于其本身就是ASCII码字
符,一般不必转换,直接写入文件。但是,由于不同的系统对于换行符('\n')有不同的处理(转换)方式,在有的系统(如Windows)下也会对
'\ n' 作适当的转换。
对于非字符型数据,都要进行转换处理。例如:int m = 12; 以及 double f =
2.3;,分别按照 "%d"、"%lf" 方式将 m 和 f 写入文件的时候,写入的分别是
'1'、'2' 两个字符以及 '2'、'.'、 '3'
等三个字符的ASCII码值。显然,如果按照二进制方式写的话,在文件中一般 m 要占
4 个字节、f 要占 8 个字节
------------------
http://www.80diy.com/home/20020504/20/698376.html
PS:
binary file中有许多是不可见字符(它本来就不是用来看的),
而text file都是可见字符(这些字符可能属于不同的字符集)Top

你自己慢慢会明白其实人们做的这种区分本身就很模糊的。
比如说word文件,他应该算是是二进制文件,但它里面却又一大块unicode的数据区。
所有文件都能以二进制方式打开,也都能以文本方式打开。
只不过人们通常把仅由可见字符构成的文件称为"文本文件。
二进制与文本文件的区别
http://topic.csdn.net/t/20050518/17/4017296.html
文本文件也称ASCII文件,在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII码。例如,数5678的存储形式为:
ASC码:  00110101 00110110 00110111 00111000
↓     ↓    ↓    ↓
十进制码: 5     6    7    8
共占用4个字节。ASCII码文件可在屏幕上按字符显示,
例如源程序文件就是ASCII文件,用DOS命令TYPE可显示文件的内容。
由于是按字符显示,因此能读懂文件内容。
二进制文件是按二进制的编码方式来存放文件的。
例如,
数5678的存储形式为:
00010110 00101110只占二个字节。
二进制文件虽然也可在屏幕上显示,但其内容无法读懂。
C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。
输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。
因此也把这种文件称作"流式文件"。
------------
数据就是数据,关键看你怎么使用了,如果说区别,就是文本文件载入的时候,会把回车换行2个符号转换成回车符号,而吃掉换行符,存盘的时候把这个吃掉的换行符吐回去...
至于也可以把文本文件当初数据文件来读,纯粹是看应用了。
就好比有人问10进制,16进制的区别,纯粹就是一回事 !!!
都是数据,只不过看问题的角度不同而已,只是你观察角度不同而已。他们都是一样的。
好比'1'
你用文本文件看是'1',你用16进制工具看是0x31
实际上都是0x31,或者是0x0011 0001
-------------
没有区别,从文件系统的角度,所有的文件都是一个顺序的比特集合,区别在于程序如何解读这些数据,一段数据可以看成是文本,图像或者声音
------------------
说到底计算机存储的文件都是以二进制形式存储的,但是区别是,习惯上认为:
1)、文本文件
文本文件是包含用户可读信息的文件。这些文件以ASCII码方式存储,可显示和打印。文本文件的行不能包括空字符(即码中的NULL),行的最大
长度(包括换行符在内)也不能超过(LINE_MAX)所定义的字节数。不过文本文件中并不限制使用除空字符以外的控制字符或其它不可打印字符。
(2)、二进制文件(实际上这种说法不准确,因为文本文件也是用二进制代码存放的,称为非文本文件比较好)
二进制文件是包含计算机可读信息的文件。二进制文件可以是可执行的文件,使系统根据其中的指令完成某项工作。命令和程序都是以可执行的而进制文件方式存储。二进制文件没有行的长度限制,也可包含空字符。
--------------
http://www.net0791.com/article/45480.htm
看个例子:
#include<stdio.h>
int main()
{
FILE *fp;
int i = 12;
int j = 12;
fp = fopen("01.txt","wb");
fprintf(fp,"%d",i);
fputc('\n',fp);
fwrite(&j,sizeof(int),1,fp);
fclose(fp);
return 0;
}
看输出结果。
linux:~/test # od -x 01.txt
0000000 3231 0c0a 0000 0000
即使是用二进制打开,但如果你用fputc,fputs,fprintf这些函数,其实还是和用文本文件打开一样。
只有用到fwrite/fread函数,才会看到一个整形占4个字节。
按二进制写文件指的是直接按照数据在内存中的表现形式写入文件。例如,如果int型数据在内存中用
4 个字节表示,则写这个int数据的时候直接把对应的内存中 4
个字节的内容写入文件。在此过程中数据不需要做任何转换,所以效率较高。

据有字符型和非字符型(数)两种。按文本方式写文件指的是将数据转换为对应的字符型数据之后再写入文件。对于字符型数据,由于其本身就是ASCII码字
符,一般不必转换,直接写入文件。但是,由于不同的系统对于换行符('\n')有不同的处理(转换)方式,在有的系统(如Windows)下也会对
'\ n' 作适当的转换。
对于非字符型数据,都要进行转换处理。例如:int m = 12; 以及 double f =
2.3;,分别按照 "%d"、"%lf" 方式将 m 和 f 写入文件的时候,写入的分别是
'1'、'2' 两个字符以及 '2'、'.'、 '3'
等三个字符的ASCII码值。显然,如果按照二进制方式写的话,在文件中一般 m 要占
4 个字节、f 要占 8 个字节
------------------
http://www.80diy.com/home/20020504/20/698376.html
PS:
binary file中有许多是不可见字符(它本来就不是用来看的),
而text file都是可见字符(这些字符可能属于不同的字符集)Top

你自己慢慢会明白其实人们做的这种区分本身就很模糊的。
比如说word文件,他应该算是是二进制文件,但它里面却又一大块unicode的数据区。
所有文件都能以二进制方式打开,也都能以文本方式打开。
只不过人们通常把仅由可见字符构成的文件称为"文本文件。
二进制与文本文件的区别
http://topic.csdn.net/t/20050518/17/4017296.html
文本文件也称ASCII文件,在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII码。例如,数5678的存储形式为:
ASC码:  00110101 00110110 00110111 00111000
↓     ↓    ↓    ↓
十进制码: 5     6    7    8
共占用4个字节。ASCII码文件可在屏幕上按字符显示,
例如源程序文件就是ASCII文件,用DOS命令TYPE可显示文件的内容。
由于是按字符显示,因此能读懂文件内容。
二进制文件是按二进制的编码方式来存放文件的。
例如,
数5678的存储形式为:
00010110 00101110只占二个字节。
二进制文件虽然也可在屏幕上显示,但其内容无法读懂。
C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。
输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。
因此也把这种文件称作"流式文件"。
------------
数据就是数据,关键看你怎么使用了,如果说区别,就是文本文件载入的时候,会把回车换行2个符号转换成回车符号,而吃掉换行符,存盘的时候把这个吃掉的换行符吐回去...
至于也可以把文本文件当初数据文件来读,纯粹是看应用了。
就好比有人问10进制,16进制的区别,纯粹就是一回事 !!!
都是数据,只不过看问题的角度不同而已,只是你观察角度不同而已。他们都是一样的。
好比'1'
你用文本文件看是'1',你用16进制工具看是0x31
实际上都是0x31,或者是0x0011 0001
-------------
没有区别,从文件系统的角度,所有的文件都是一个顺序的比特集合,区别在于程序如何解读这些数据,一段数据可以看成是文本,图像或者声音
------------------
说到底计算机存储的文件都是以二进制形式存储的,但是区别是,习惯上认为:
1)、文本文件
文本文件是包含用户可读信息的文件。这些文件以ASCII码方式存储,可显示和打印。文本文件的行不能包括空字符(即码中的NULL),行的最大
长度(包括换行符在内)也不能超过(LINE_MAX)所定义的字节数。不过文本文件中并不限制使用除空字符以外的控制字符或其它不可打印字符。
(2)、二进制文件(实际上这种说法不准确,因为文本文件也是用二进制代码存放的,称为非文本文件比较好)
二进制文件是包含计算机可读信息的文件。二进制文件可以是可执行的文件,使系统根据其中的指令完成某项工作。命令和程序都是以可执行的而进制文件方式存储。二进制文件没有行的长度限制,也可包含空字符。
--------------
http://www.net0791.com/article/45480.htm
看个例子:
#include<stdio.h>
int main()
{
FILE *fp;
int i = 12;
int j = 12;
fp = fopen("01.txt","wb");
fprintf(fp,"%d",i);
fputc('\n',fp);
fwrite(&j,sizeof(int),1,fp);
fclose(fp);
return 0;
}
看输出结果。
linux:~/test # od -x 01.txt
0000000 3231 0c0a 0000 0000
即使是用二进制打开,但如果你用fputc,fputs,fprintf这些函数,其实还是和用文本文件打开一样。
只有用到fwrite/fread函数,才会看到一个整形占4个字节。
按二进制写文件指的是直接按照数据在内存中的表现形式写入文件。例如,如果int型数据在内存中用
4 个字节表示,则写这个int数据的时候直接把对应的内存中 4
个字节的内容写入文件。在此过程中数据不需要做任何转换,所以效率较高。

据有字符型和非字符型(数)两种。按文本方式写文件指的是将数据转换为对应的字符型数据之后再写入文件。对于字符型数据,由于其本身就是ASCII码字
符,一般不必转换,直接写入文件。但是,由于不同的系统对于换行符('\n')有不同的处理(转换)方式,在有的系统(如Windows)下也会对
'\ n' 作适当的转换。
对于非字符型数据,都要进行转换处理。例如:int m = 12; 以及 double f =
2.3;,分别按照 "%d"、"%lf" 方式将 m 和 f 写入文件的时候,写入的分别是
'1'、'2' 两个字符以及 '2'、'.'、 '3'
等三个字符的ASCII码值。显然,如果按照二进制方式写的话,在文件中一般 m 要占
4 个字节、f 要占 8 个字节
------------------
http://www.80diy.com/home/20020504/20/698376.html
PS:
binary file中有许多是不可见字符(它本来就不是用来看的),
而text file都是可见字符(这些字符可能属于不同的字符集)Top

你自己慢慢会明白其实人们做的这种区分本身就很模糊的。
比如说word文件,他应该算是是二进制文件,但它里面却又一大块unicode的数据区。
所有文件都能以二进制方式打开,也都能以文本方式打开。
只不过人们通常把仅由可见字符构成的文件称为"文本文件。

10个方法提高你的编程生产力

1. 一天最多阅读两次新闻
信息爆炸的年代新闻数量多不胜数,不要阅读过多的新闻,我一般一天阅读两次新闻,早晨和下午各打开一次google
reader,更多的新闻阅读将会严重降低工作效率。
2。给自己精心准备一个工作开始的起点
写程序一旦进入状态,毫无疑问效率是非常高的,脑子里面每个细胞似乎都在奋斗。但是如何快速进入完美的工作状态?我的经验是,每当我离开工作的时候,比如
中午午餐时间前或者一天工作结束时候,我会故意遗留一个未完成小任务在我的程序里面。当我回来工作的时候,我能够迅速知道从哪里开始,专心致志解决完这个
小任务,我的大脑差不多已经完成热身了,马上能够进入真正的工作。
3。用笔画出来,做好预先研究工作
怎么说呢,就是脑子的想法尽可能的用笔画出来,形象化的图形能够很好的帮助你思考总结。
对于复杂的工作,预先做好研究工作,比如一个难度很大的算法程序,我会先搞懂最难的技术问题才开始写代码。
4。建立一个完美的工作环境
大多数我们都是在公司工作,工作环境不能由我们决定,但是至少我们可以在自己家里弄一个完美的工作环境。我心目中好的工作环境包括:
a)一个大电脑显示器,一张大桌子
b)一个舒服的电脑椅
c)有益工作的背景音乐,这个因人而异
d)一套好的音响
e)阳光充足的窗口
f)大的开放的空间
g)安静,很少有人在旁边走动
h)和外界通风良好
i)房间是现代装饰风格
5。工作时间关掉IM工具
不管什么理由,都要坚决关掉所有的IM工具
6。工作时间只回复和处理紧急邮件
不要让邮件打断你的工作节奏,工作时间只回复和处理紧急邮件
7。减少开会,一周一次会议或者更少
保持沟通效率高效,但不是更多的会议。减少开会,一周一次会议或者更少。
8。每两周参加一次社交活动
程序员的生活是比较单调的,我所说的社交活动并不是和同事,工作伙伴之间的交流,也不是你依然坐在电脑桌前玩游戏。而是走出你的办公室,和你工作以外的朋友在一起交流,量身打造你自己的情感需求。
9。放松的夜晚
没有比7×24小时连续工作更糟了。长时间处于兴奋和焦虑状态会极大影响你长期的工作效率。每天休息一段时间,散步,阅读,享受生活,你会发现你的创造力提高了。
10。每周3次,每次20分钟的体育运动
体育运动并不是浪费时间,定期的体育活动会让你精力更加充沛,头脑反映更加灵活。保持至少每周3次,每次20分钟的体育运动。

GCC使用入门

一、GCC简介
通 常所说的GCC是GUN Compiler
Collection的简称,除了编译程序之外,它还含其他相关工具,所以它能把易于人类使用的高级语言编写的源代码构建成计算机能够直接执行的二进制代
码。GCC是Linux平台下最常用的编译程序,它是Linux平台编译器的事实标准。同时,在Linux平台下的嵌入式开发领域,GCC也是用得最普遍
的一种编译器。GCC之所以被广泛采用,是因为它能支持各种不同的目标体系结构。例如,它既支持基于宿主的开发(简单讲就是要为某平台编译程序,就在该平
台上编译),也支持交叉编译(即在A平台上编译的程序是供平台B使用的)。目前,GCC支持的体系结构有四十余种,常见的有X86系列、Arm、
PowerPC等。同时,GCC还能运行在不同的操作系统上,如Linux、Solaris、Windows等。
除了上面讲的之外,GCC除了支持C语言外,还支持多种其他语言,例如C++、Ada、Java、Objective-C、FORTRAN、Pascal等。
本系列文章中,我们不仅介绍GCC的基本功能,还涉及到一些诸如优化之类的高级功能。另外,我们还考察GCC的一些映像操作工具,如size和objcopy等,这将在后续的文章中加以介绍。

二、程序的编译过程
对于GUN编译器来说,程序的编译要经历预处理、编译、汇编、连接四个阶段。从功能上分,预处理、编译、汇编是三个不同的阶段,但GCC的实际操作上,它可以把这三个步骤合并为一个步骤来执行。下面我们以C语言为例来谈一下不同阶段的输入和输出情况。

预处理阶段,输入的是C语言的源文件,通常为*.c。它们通常带有.h之类头文件的包含文件。这个阶段主要处理源文件中的#ifdef、
#include和#define命令。该阶段会生成一个中间文件*.i,但实际工作中通常不用专门生成这种文件,因为基本上用不到;若非要生成这种文件
不可,可以利用下面的示例命令:
gcc -E test.c -o test.i
在编译阶段,输入的是中间文件*.i,编译后生成汇编语言文件*.s
。这个阶段对应的GCC命令如下所示:
GCC -S test.i -o test.s
在汇编阶段,将输入的汇编文件*.s转换成机器语言*.o。这个阶段对应的GCC命令如下所示:
GCC -c test.s -o test.o
最后,在连接阶段将输入的机器代码文件*.s(与其它的机器代码文件和库文件)汇集成一个可执行的二进制代码文件。这一步骤,可以利用下面的示例命令完成:
GCC test.o -o test
上面介绍了GCC编译过程的四个阶段以及相应的命令。下面我们进一步介绍常用的GCC的模式。
三、GCC常用模式
这里介绍GCC追常用的两种模式:编译模式和编译连接模式。下面以一个例子来说明各种模式的使用方法。为简单起见,假设我们全部的源代码都在一个文件test.c中,要想把这个源文件直接编译成可执行程序,可以使用以下命令:
$ GCC -o test
这里test.c是源文件,生成的可执行代码存放在一个名为test
的文件中(该文件是机器代码并且可执行)。-o
是生成可执行文件的输出选项。如果我们只想让源文件生成目标文件(给文件虽然也是机器代码但不可执行),可以使用标记-c
,详细命令如下所示:
$ GCC -c test.c
默认情况下,生成的目标文件被命名为test.o,但我们也可以为输出文件指定名称,如下所示:
$ GCC -c test.c -o
上面这条命令将编译后的目标文件命名为mytest.o,而不是默认的test.o。
迄今为止,我们谈论的程序仅涉及到一个源文件;现实中,一个程序的源代码通常包含在多个源文件之中,这该怎么办?没关系,即使这样,用GCC处理起来也并不复杂,见下例:
$ GCC -o test first.c second.c third.c

要注意的是,要生成可执行程序时,一个程序无论有有一个源文件还是多个源文件,所有被编译和连接的源文件中必须有且仅有一个main函数,因为main函
数是该程序的入口点(换句话说,当系统调用该程序时,首先将控制权授予程序的main函数)。但如果仅仅是把源文件编译成目标文件的时候,因为不会进行连
接,所以main函数不是必需的。
四、常用选项
许多情况下,头文件和源文件会单独存放在不同的目录中。例如,假设存放源文件的子目录名为./src,而包含文件则放在层次的其他目录下,如./inc。当我们在./src
目录下进行编译工作时,如何告诉GCC到哪里找头文件呢?方法如下所示:
$ gcc test.c –I../inc -o test
上面的命令告诉GCC包含文件存放在./inc
目录下,在当前目录的上一级。如果在编译时需要的包含文件存放在多个目录下,可以使用多个-I
来指定各个目录:
$ gcc test.c –I../inc –I../../inc2 -o test
这里指出了另一个包含子目录inc2,较之前目录它还要在再上两级才能找到。
另外,我们还可以在编译命令行中定义符号常量。为此,我们可以简单的在命令行中使用-D选项即可,如下例所示:
$ gcc -DTEST_CONFIGURATION test.c -o test
上面的命令与在源文件中加入下列命令是等效的:
#define TEST_CONFIGURATION
在编译命令行中定义符号常量的好处是,不必修改源文件就能改变由符号常量控制的行为。

五、警告功能

GCC在编译过程中检查出错误的话,它就会中止编译;但检测到警告时却能继续编译生成可执行程序,因为警告只是针对程序结构的诊断信息,它不能说明程序一
定有错误,而是存在风险,或者可能存在错误。虽然GCC提供了非常丰富的警告,但前提是你已经启用了它们,否则它不会报告这些检测到的警告。
在众多的警告选项之中,最常用的就是-Wall选项。该选项能发现程序中一系列的常见错误警告,该选项用法举例如下:
$ gcc -Wall test.c -o test
该选项相当于同时使用了下列所有的选项:
◆unused-function:遇到仅声明过但尚未定义的静态函数时发出警告。
◆unused-label:遇到声明过但不使用的标号的警告。
◆unused-parameter:从未用过的函数参数的警告。
◆unused-variable:在本地声明但从未用过的变量的警告。
◆unused-value:仅计算但从未用过的值得警告。
◆Format:检查对printf和scanf等函数的调用,确认各个参数类型和格式串中的一致。
◆implicit-int:警告没有规定类型的声明。
◆implicit-function-:在函数在未经声明就使用时给予警告。
◆char-subscripts:警告把char类型作为数组下标。这是常见错误,程序员经常忘记在某些机器上char有符号。
◆missing-braces:聚合初始化两边缺少大括号。
◆Parentheses:在某些情况下如果忽略了括号,编译器就发出警告。
◆return-type:如果函数定义了返回类型,而默认类型是int型,编译器就发出警告。同时警告那些不带返回值的
return语句,如果他们所属的函数并非void类型。
◆sequence-point:出现可疑的代码元素时,发出报警。
◆Switch:如果某条switch语句的参数属于枚举类型,但是没有对应的case语句使用枚举元素,编译器就发出警告(在switch语句中使用default分支能够防止这个警告)。超出枚举范围的case语句同样会导致这个警告。
◆strict-aliasing:对变量别名进行最严格的检查。
◆unknown-pragmas:使用了不允许的#pragma。
◆Uninitialized:在初始化之前就使用自动变量。
需要注意的是,各警告选项既然能使之生效,当然也能使之关闭。比如假设我们想要使用-Wall来启用个选项,同时又要关闭unused警告,利益通过下面的命令来达到目的:
$ gcc -Wall -Wno-unused test.c -o test
下面是使用-Wall选项的时候没有生效的一些警告项:
◆cast-align:一旦某个指针类型强制转换时,会导致目标所需的地址对齐边界扩展,编译器就发出警告。例如,某些机器上只能在2或4字节边界上访问整数,如果在这种机型上把char
*强制转换成int *类型, 编译器就发出警告。
◆sign-compare:将有符号类型和无符号类型数据进行比较时发出警告。
◆missing-prototypes
:如果没有预先声明函数原形就定义了全局函数,编译器就发出警告。即使函数定义自身提供了函数原形也会产生这个警告。这样做的目的是检查没有在头文件中声明的全局函数。
◆Packed:当结构体带有packed属性但实际并没有出现紧缩式给出警告。
◆Padded:如果结构体通过充填进行对齐则给出警告。
◆unreachable-code:如果发现从未执行的代码时给出警告。
◆Inline:如果某函数不能内嵌(inline),无论是声明为inline或者是指定了-finline-functions
选项,编译器都将发出警告。
◆disabled-optimization:当需要太长时间或过多资源而导致不能完成某项优化时给出警告。
上面是使用-Wall选项时没有生效,但又比较常用的一些警告选项。本文中要介绍的最后一个常用警告选项是-Werror。使用该选项后,GCC发现可疑之处时不会简单的发出警告就算完事,而是将警告作为一个错误而中断编译过程。该选项在希望得到高质量代码时非常有用。

六、小结
本文介绍了GCC的基本编译过程和编译模式,并详细阐述了GCC的一些常用选项以及警告功能。这些是在利用GCC进行应用编程时最基本也最常用的一些内容,我们会在后续文章中继续介绍GCC的调试和优化技术。
该命令将同时编译三个源文件,即first.c、second.c和
third.c,然后将它们连接成一个可执行程序,名为test。
Linux联盟收集整理 ,转贴请标明原始链接,如有任何疑问欢迎来本站Linux论坛讨论
http://www.xxlinux.com/linux/article/development/soft/20061029/5551.html

单字节大小枚举及按位对齐

在linux下定义一个枚举,我们用gcc编译时,得到的大小默认为4个字节,但如何让这个枚举大小为1个字节呢?下面我们看看一个例子test.c:
#include <stdio.h>
enum he {
AGE = 0x01,
NAME = 0x02
};
enum she {
AGE1 = 0x03,
NAME2 = 0x04
};
struct bear {
char x;
char y;
enum he he;
enum she she;
int z;
};
int main(int argc, char **argv)
{
int size = 0;
struct bear bear;
size = sizeof(struct bear);
printf("size is:%d\n", size);
return 0;
}
用gcc
test.c时,得出来的结构体的大小为16个字节,两个char型为2个字节,由于第二个char型后面为4个字节的enum型,所以按默认的4个字节对齐,前两个char型为4个字节。再加上两个enum,一个int型,所以总共16个字节。
然而在"#include <stdio.h>"下行加上#pragma pack(1)
编译运行后得到得结构体的大小为14个字节。因为这是按1个字节对齐,两个char型为两个字节,加两个enum,一个int共14个字节。
上面说的是按位对齐,而要把enum变为单个字节,则只需在上面的代码编译时,再加上-fshort-enums参数就行了:
gcc test.c -fshort-enums
运行后得到此结构体的大小只有8个字节了。上面的两个enum型就成就单字节的枚举了。
下面对-fshort-enums在gcc手册中的解释:
-fshort-enums
Allocate to an "enum" type only as many bytes as it needs for the
declared range of possible values. Specifically, the "enum" type will be
equivalent to the smallest integer type which has enough room.
Warning: the -fshort-enums switch causes GCC to generate code that is
not binary compatible with
code generated without that switch. Use it to conform to a non-default
application binary interface.
http://linux0818.bokee.com/viewdiary.179592916.html

[转贴]一个老程序员的心里话说到人的心坎

诸位,咱当电子工程师也是十余年了,不算有出息,环顾四周,也没有看见几个有出息的!回顾工程师生涯,感慨万千,愿意讲几句掏心窝子的话,也算给咱们师弟师妹们提个醒,希望他们比咱们强!
[1]
好好规划自己的路,不要跟着感觉走!根据个人的理想决策安排,绝大部分人并不指望成为什么院士或教授,而是希望活得滋润一些,爽一些。那么,就需要慎重安
排自己的轨迹。从哪个行业入手,逐渐对该行业深入了解,不要频繁跳槽,特别是不要为了一点工资而转移阵地,从长远看,这点钱根本不算什么,当你对一个行业
有那么几年的体会,以后钱根本不是问题。频繁地动荡不是上策,最后你对哪个行业都没有摸透,永远是新手! 
  
[2]可以做技术,切不可沉湎于技术。千万不可一门心思钻研技术!给自己很大压力,如果你的心思全部放在这上面,那么注定你将成为孔乙己一类的人物!适可而止为之,因为技术只不过是你今后前途的支柱之一,而且还不是最大的支柱,除非你只愿意到老还是个工程师!   
[3]
不要去做技术高手,只去做综合素质高手!在企业里混,我们时常瞧不起某人,说他"什么都不懂,凭啥拿那么多钱,凭啥升官!"这是普遍的典型的工程师的迂腐
之言。8051很牛吗?人家能上去必然有他的本事,而且是你没有的本事。你想想,老板搞经营那么多年,难道见识不如你这个新兵?人家或许善于管理,善于领
会老板意图,善于部门协调等等。因此务必培养自己多方面的能力,包括管理,亲和力,察言观色能力,攻关能力等,要成为综合素质的高手,则前途无量,否则只
能躲在角落看示波器!技术以外的技能才是更重要的本事!!从古到今,美国***,一律如此!   
[4]多交社会三教九流的朋友!不要
只和工程师交往,认为有共同语言,其实更重要的是和其他类人物交往,如果你希望有朝一日当老板或高层管理,那么你整日面对的就是这些人。了解他们的经历,
思维习惯,爱好,学习他们处理问题的模式,了解社会各个角落的现象和问题,这是以后发展的巨大的本钱,没有这些以后就会笨手笨脚,跌跌撞撞,遇到重重困
难,交不少学费,成功的概率大大降低!
[5]知识涉猎不一定专,但一定要广!多看看其他方面的书,金融,财会,进出口,税务,法律等等,为以后做一些积累,以后的用处会更大!会少交许多学费!! 
[6]
抓住时机向技术管理或市场销售方面的转变!要想有前途就不能一直搞开发,适当时候要转变为管理或销售,前途会更大,以前搞技术也没有白搞,以后还用得着。
搞管理可以培养自己的领导能力,搞销售可以培养自己的市场概念和思维,同时为自己以后发展积累庞大的人脉!应该说这才是前途的真正支柱!!!   
[7]
逐渐克服自己的心里弱点和性格缺陷!多疑,敏感,天真(贬义,并不可爱),犹豫不决,胆怯,多虑,脸皮太薄,心不够黑,教条式思维。。。这些工程师普遍存
在的性格弱点必须改变!很难吗?只在床上想一想当然不可能,去帮朋友守一个月地摊,包准有效果,去实践,而不要只想!不克服这些缺点,一切不可能,甚至连
项目经理都当不好--尽管你可能技术不错!   
[8]工作的同时要为以后做准备!建立自己的工作环境!及早为自己配置一个工作环境,
装备电脑,示波器(可以买个二手的),仿真器,编程器等,业余可以接点活,一方面接触市场,培养市场感觉,同时也积累资金,更重要的是准备自己的产品,咱
搞技术的没有钱,只有技术,技术的代表不是学历和证书,而是产品,拿出象样的产品,就可技术转让或与人合作搞企业!先把东西准备好,等待机会,否则,有了
机会也抓不住!   
[9]要学会善于推销自己!不仅要能干,还要能说,能写,善于利用一切机会推销自己,树立自己的品牌形象,很必
要!要创造条件让别人了解自己,不然老板怎么知道你能干?外面的投资人怎么相信你?提早把自己推销出去,机会自然会来找你!搞个个人主页是个好注意!!特
别是培养自己在行业的名气,有了名气,高薪机会自不在话下,更重要的是有合作的机会...   
[10]该出手时便出手!永远不可能有100%把握!!!条件差不多就要大胆去干,去闯出自己的事业,不要犹豫,不要彷徨,干了不一定成功,但至少为下一次冲击积累了经验,不干永远没出息,而且要干成必然要经历失败。不经历风雨,怎么见彩虹,没有人能随随便便成功!

Linux下的段错误的原因及调试

http://www.yuanma.org/data/2008/0818/article_3139.htm
简而言之,产生段错误就是访问了错误的内存段,一般是你没有权限,或者根本就不存在对应的物理内存,尤其常见的是访问0地址.
一般来说,
段错误就是指访问的内存超出了系统所给这个程序的内存空间,通常这个值是由gdtr来保存的,他是一个48位的寄存器,其中的32位是保存由它指向的
gdt表,后13位保存相应于gdt的下标,最后3位包括了程序是否在内存中以及程序的在cpu中的运行级别,指向的gdt是由以64位为一个单位的表,
在这张表中就保存着程序运行的代码段以及数据段的起始地址以及与此相应的段限和页面交换还有程序运行级别还有内存粒度等等的信息。一旦一个程序发生了越界
访问,cpu就会产生相应的异常保护,于是segmentation fault就出现了.
在编程中以下几类做法容易导致段错误,基本是是错误地使用指针引起的
1)访问系统数据区,尤其是往 系统保护的内存地址写数据
最常见就是给一个指针以0地址
2)内存越界(数组越界,变量类型不一致等) 访问到不属于你的内存区域
解决方法

们在用C/C++语言写程序的时侯,内存管理的绝大部分工作都是需要我们来做的。实际上,内存管理是一个比较繁琐的工作,无论你多高明,经验多丰富,难免
会在此处犯些小错误,而通常这些错误又是那么的浅显而易于消除。但是手工"除虫"(debug),往往是效率低下且让人厌烦的,本文将就"段错误"这个内
存访问越界的错误谈谈如何快速定位这些"段错误"的语句。
下面将就以下的一个存在段错误的程序介绍几种调试方法: 1 dummy_function
(void)
2 {
3 unsigned char *ptr = 0x00;
4 *ptr = 0x00;
5 }
6
7 int main (void)
8 {
9 dummy_function ();
10
11 return 0;
12 }
作为一个熟练的C/C++程序员,以上代码的bug应该是很清楚的,因为它尝试操作地址为0的内存区域,而这个内存区域通常是不可访问的禁区,当然就会出错了。我们尝试编译运行它:xiaosuo@gentux
test $ ./a.out
段错误
果然不出所料,它出错并退出了。
1.利用gdb逐步查找段错误:
这种方法也是被大众所熟知并广泛采用的方法,首先我们需要一个带有调试信息的可执行程序,所以我们加上"-g
-rdynamic"的参数进行编译,然后用gdb调试运行这个新编译的程序,具体步骤如下:xiaosuo@gentux
test $ gcc -g -rdynamic d.c
xiaosuo@gentux test $ gdb ./a.out
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you
are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db
library "/lib/libthread_db.so.1".
(gdb) r
Starting program: /home/xiaosuo/test/a.out
Program received signal SIGSEGV, Segmentation fault.
0x08048524 in dummy_function () at d.c:4
4 *ptr = 0x00;
(gdb)
哦?!好像不用一步步调试我们就找到了出错位置d.c文件的第4行,其实就是如此的简单。
从这里我们还发现进程是由于收到了SIGSEGV信号而结束的。通过进一步的查阅文档(man
7
signal),我们知道SIGSEGV默认handler的动作是打印"段错误"的出错信息,并产生Core文件,由此我们又产生了方法二。
2.分析Core文件:
Core文件是什么呢?The default action of certain signals is to cause a
process to terminate and produce a core dump file, a disk file containing
an image of the process's memory at the time of termination. A list of the
signals which cause a process to dump core can be found in signal(7).
以 上资料摘自man page(man 5
core)。不过奇怪了,我的系统上并没有找到core文件。后来,忆起为了渐少系统上的拉圾文件的数量(本人有些洁癖,这也是我喜欢Gentoo的原因
之一),禁止了core文件的生成,查看了以下果真如此,将系统的core文件的大小限制在512K大小,再试:xiaosuo@gentux
test $ ulimit -c
0
xiaosuo@gentux test $ ulimit -c 1000
xiaosuo@gentux test $ ulimit -c
1000
xiaosuo@gentux test $ ./a.out
段错误 (core dumped)
xiaosuo@gentux test $ ls
a.out core d.c f.c g.c pango.c test_iconv.c test_regex.c
core文件终于产生了,用gdb调试一下看看吧:xiaosuo@gentux test $ gdb ./a.out
core
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you
are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db
library "/lib/libthread_db.so.1".
warning: Can't read pathname for load map: 输入/输出错误.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0 0x08048524 in dummy_function () at d.c:4
4 *ptr = 0x00;
哇,好历害,还是一步就定位到了错误所在地,佩服一下Linux/Unix系统的此类设计。
接着考虑下去,以前用windows系统下的ie的时侯,有时打开某些网页,会出现"运行时错误",这个时侯如果恰好你的机器上又装有windows的编译器的话,他会弹出来一个对话框,问你是否进行调试,如果你选择是,编译器将被打开,并进入调试状态,开始调试。
Linux下如何做到这些呢?我的大脑飞速地旋转着,有了,让它在SIGSEGV的handler中调用gdb,于是第三个方法又诞生了:
3.段错误时启动调试:#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
void dump(int signo)
{
char buf[1024];
char cmd[1024];
FILE *fh;
snprintf(buf, sizeof(buf), "/proc/%d/cmdline", getpid());
if(!(fh = fopen(buf, "r")))
exit(0);
if(!fgets(buf, sizeof(buf), fh))
exit(0);
fclose(fh);
if(buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = '\0';
snprintf(cmd, sizeof(cmd), "gdb %s %d", buf, getpid());
system(cmd);
exit(0);
}
void
dummy_function (void)
{
unsigned char *ptr = 0x00;
*ptr = 0x00;
}
int
main (void)
{
signal(SIGSEGV, &dump);
dummy_function ();
return 0;
}
编译运行效果如下:xiaosuo@gentux test $ gcc -g -rdynamic f.c
xiaosuo@gentux test $ ./a.out
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you
are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db
library "/lib/libthread_db.so.1".
Attaching to program: /home/xiaosuo/test/a.out, process 9563
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0xffffe410 in __kernel_vsyscall ()
(gdb) bt
#0 0xffffe410 in __kernel_vsyscall ()
#1 0xb7ee4b53 in waitpid () from /lib/libc.so.6
#2 0xb7e925c9 in strtold_l () from /lib/libc.so.6
#3 0x08048830 in dump (signo=11) at f.c:22
#4 <signal handler called>
#5 0x0804884c in dummy_function () at f.c:31
#6 0x08048886 in main () at f.c:38
怎么样?是不是依旧很酷?
以上方法都是在系统上有gdb的前提下进行的,如果没有呢?其实glibc为我们提供了此类能够dump栈内容的函数簇,详见/usr/include/execinfo.h(这些函数都没有提供man
page,难怪我们找不到),另外你也可以通过gnu的手册进行学习。
4.利用backtrace和objdump进行分析:
重写的代码如下:#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
/* A dummy function to make the backtrace more interesting. */
void
dummy_function (void)
{
unsigned char *ptr = 0x00;
*ptr = 0x00;
}
void dump(int signo)
{
void *array[10];
size_t size;
char **strings;
size_t i;
size = backtrace (array, 10);
strings = backtrace_symbols (array, size);
printf ("Obtained %zd stack frames.\n", size);
for (i = 0; i < size; i++)
printf ("%s\n", strings[i]);
free (strings);
exit(0);
}
int
main (void)
{
signal(SIGSEGV, &dump);
dummy_function ();
return 0;
}
编译运行结果如下:xiaosuo@gentux test $ gcc -g -rdynamic g.c
xiaosuo@gentux test $ ./a.out
Obtained 5 stack frames.
./a.out(dump+0x19) [0x80486c2]
[0xffffe420]
./a.out(main+0x35) [0x804876f]
/lib/libc.so.6(__libc_start_main+0xe6) [0xb7e02866]
./a.out [0x8048601]
这次你可能有些失望,似乎没能给出足够的信息来标示错误,不急,先看看能分析出来什么吧,用objdump反汇编程序,找到地址0x804876f对应的代码位置:xiaosuo@gentux
test $ objdump -d a.out
8048765: e8 02 fe ff ff call 804856c <signal@plt>
804876a: e8 25 ff ff ff call 8048694 <dummy_function>
804876f: b8 00 00 00 00 mov $0x0,%eax
8048774: c9 leave
我们还是找到了在哪个函数(dummy_function)中出错的,信息已然不是很完整,不过有总比没有好的啊!
后记:
本文给出了分析"段错误"的几种方法,不要认为这是与孔乙己先生的"回"字四种写法一样的哦,因为每种方法都有其自身的适用范围和适用环境,请酌情使用,或遵医嘱。

EMCV:可在DSP上运行的OpenCV

EMCV项目主页: http://sf.net/projects/emcv
EMCV全称为Embedded Computer Vision Library,是一个可在TI
DM64x系列DSP上运行的计算机视觉库。EMCV提供了跟OpenCV完全一致的函数接口,通过EMCV,你可以轻松的将你的OpenCV算法移植到DSP,甚至不用改一行代码。
目前EMCV已经支持IplImage, CvMat,CvSeq等基本数据结构,
可使用cvCreateImage等创建和释放图像,以及contour检测等。
EMCV刚刚启动,希望得到您的代码贡献:
目前您可以通过subversion客户端获取源代码,源代码位于
https://emcv.svn.sourceforge.net/svnroot/emcv

在C6000系列DSP中使用EMCV,
请参考:http://www.opencv.org.cn/forum/viewtopic.php?f=9&t=4638

如何测试程序在DSP上的运行时间?

如何测试程序在DSP上的运行时间?
1、用CLOCK()准确吗?
2、用CCS上的工具,但如何使用?

在你要测试时间的程序的两端,用Project Toolbar上的Toggle profile
point按键,加上profile
print,像断点一样,不过是绿色的。然后在profiler菜单中选择Enable Clock和view
clock,在view clock窗口中可以看到程序运行的时间/周期(时钟)数。在view
clock窗口中第2行显示的时间就是程序从第1行所在位置到第2行位置的时间,也就是你要测定的时间。这是ti推荐的方法,但我发现数据不准确,比实际的要大。如果用profiler得到的数据比你的要求好要少的话,那就说明已经达到了要求,如果比你的大则比一定达不到要求。

周期测定可以利用DSP的定时器来实现!在你要测定开始的地方设定定时器并启动,在你结束的地方关闭定时器即可.不过要关闭中断哟?

用clock函数是可行的,尤其是优化以后的代码很难确定PROFILE的位置。你看一下.asm的输出就知道了,clock没有这个问题。有一点要注意,clock函数如果直接在DSP上跑时间是不准的,必须用load6x命令在DOS或命令行状态下加载,这样结果就准的多。当然,调用函数本身的开销也是要考虑的。

数汇编代码测程序运行时间的方法是不行的:)因为指令所耗费周期数是不一样的,并不是都是一个周期啊,你数指令条数判断运行时间不准确啊,而且小程序你还可以用数的方法,大的怎么办呢:)看看指令耗费周期数的大致规律啊
指令所耗费周期数规律:主要跟操作多少及有关系,简单的加减乘和逻辑运算一般是单周期,延迟跳转指令也是1周期,复杂的并行运算则因操作数据所在存储区(片内、外)而分成一周期和两周期,改变程序计数器的指令一般如跳转,返回等耗费4周期,软件陷阱则因操作多而需要5个周期。

什么是字节对齐,为什么要对齐?

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。

二.字节对齐对程序的影响:
先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
int a;
char b;
short c;
};

4Byte
|---------|
| a |
|----|----|
| b -| c -|
|---------|


struct B
{
char b;
int a;
short c;
};

|--------|
|b |
|--------|
| a |
|--------|
| c |
|--------|

现在已知32位机器上各种数据类型的长度如下:
char:1 (有符号无符号同)
short:2 (有符号无符号同)
int:4 (有符号无符号同)
long:4 (有符号无符号同)
float:4 double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12

结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:

#pragma pack (2) /*指定按2字节对齐*/
struct B
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct B)值是8。


修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct B
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct B)值为7。

后面我们再讲解#pragma pack()的作用.

三.编译器是按照什么样的原则进行对齐的?

先让我们看四个重要的基本概念:
1.数据类型自身的对齐值:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员和结构体的有效对齐值:数据成员(数据类型)和数据结构的自身对齐值和指定对齐值中小的那个值。(数据成员对齐了数据结构自然也就对齐了)

有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示"对齐在N上",也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)。这样就不难理解上面的几个例子的值了。

例子分析:
分析例子B;
struct B
{
char b;
int a;
short c;
};
假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为
2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,
0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B
共有12个字节,sizeof(struct
B)=12;其实如果就这一个就来说它已将满足字节对齐了,
因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=
0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0x0006、0x0007中,符合
0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C
只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.


四.如何修改编译器的默认对齐值?
1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code
Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.

---------------------------------------------------------
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。

---------------------------------------------------------
· __attribute((aligned
(n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
· __attribute__
((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。


五.针对字节对齐,我们在编程中如何考虑?
如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做法是显式的插入reserved成员:
struct A{
char a;
char reserved[3];//使用空间换时间
int b;
}

reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.


六.字节对齐可能带来的隐患:
代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;

p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最后两句代码,从奇数边界去访问unsigned
short型变量,显然不符合对齐的规定。在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

七.如何查找与字节对齐方面的问题:
如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3.
如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。

DSP代码移植

基于DSP系统开发的视频编解码系统,国内几乎都是走的移植,优化的路线,并且移植的代码,都是开源的。毕竟花费大量的人力,物力去开发一套自己的代码,并不见得比一些成熟的开源代码效率更高,健壮性更好。更何况开发速度对于一个产品的发展而言,更是重要。
目前对于H.264而言,移植的代码主要有JM,x264和T264。移植的时候,就需要对各个代码进行测试,以确定要移植的代码。相对而言,JM
的移植更容易,但效率比较差,如果基于科学研究,移植JM的比较多,多见于各高校的研究人员。对企业而言,考虑到实时性的要求,移植以X264和T264
居多。
将视频编解码移植到DSP的时候,考虑到DSP系统资源的宝贵,主要考虑的因素是系统空间,包括程序空间和数据空间,所以需要对原始的C代码,进行评估,这就需要对于所移植的代码有一个比较详细的了解。代码空间一般可以通过map文件进行估算。数据空间的估计,需要计算程序中内存的使用情况,除了
malloc申请的空间,还包括静态数组,主要是H.264标准中的各种表格数组以及一些全局变量等等。
准备好了这些,就可以开始移植了,移植,也是一个考验你的过程。
做好了移植的准备工作,就进入了开发过程的第一个重要阶段---移植。
移植开发的时候,最好准备两个版本,一个纯C代码,在VC下编译,运行,另一个是VDSP下的版本(ccs同理),VC版本主要是验证代码运行是否正确,
VDSP版本就是移植以后的版本,两个版本同步更新,即尽量保持两个版本的一致性,但能够同时在VC和VDSP下运行。在移植过程中,一般会遇到的问题如下:
1.头文件的不同,一般问题都是linux下的头文件,在VDSP中没有存在。最典型的就是inttypes.h

stdint.h,这种头的作用主要是定义了8字符,16字符,32字符,64字符的数据类型,移植的时候,可以自己建一个头文件或者直接在其他的头文件中把这些数据类型的定义加进去,这样的话,就不会出现问题。其他的类似
,要么找相应的头文件替换,要么干脆自己定义。
2.Int64_t和Uint64_t 的问题, 在第一步中,其实也存在这个问题,
不过我最初是用long和Unsigned long
来代替,不过这样的话,编译是可以通过,但仔细分析,其实是有问题的。一般来讲,64位数的用途有两个,第一种是这个数字可能比较大,当累积到一定的程度,可能超过32位,这种情况下,可以用32位代替,不过最好加上注释,告诉自己这个数可能越界,在后面调试的时候,要提示自己注意一下。另一种用途,是开发者为了速度的要求,对一些变量复制的时候,使用了强制性的指针赋值,这种情况下,就不能直接该成32位数据了,那样的话,虽然编译通过,后面运行,肯定有错误的。这种情况下,可以使用32位数据类型,分两条语句对变量赋值,当然,这是个时候要千万注意,不要把地址搞错了。
3.
Inline的问题,移植以后,编译的时候Inline经常会报错。虽然有编译选项可以去掉错误,不过你如果和我一样不熟悉的话,直接去掉
Inline关键字,到后面随着对VDSP熟悉以后,如果有优化的需要,再按照VDS
P的语法,为自己想要嵌入的函数增加Inline关键字。
经过上面的修改,一般情况下,编译就没有问题了,当然,这只是移植的第一步。距离成功,还很远!
1.
配置LDF文件。因为刚移植的代码,往往数据和程序都非常大,所以,SRAM里面肯定是放不下的,这个时候,链接就会有问题。刚开始的时候,最好把所有的程序和数据都放在SDRAM里面去,这样的,链接就不会有问题了。Stack和heap情况类似,开始的时候,都先放到SDRAM。开始的时候,你需要的是一个可以运行正确的程序,速度倒在其次。
2.Malloc的问题。DSP下的开发,malloc都是一个需要解决的问题。动态申请内存,就算可以运行,结果往往也是不对的。所以,最好进行静态分配,用数组的形式分配,这样做的好处是可以方便自己管理,那些数组多大,放在那里,自己都很清楚,因为优化的时候,有一些是要放在SRAM中,另外一些特别大的才放在SDRAM中,这样才能取的比较好的效果,另外,静态数组也稳定性一些,不需要记着去释放。
3.文件操作。在VDSP的SETTING下,有一个STDIO的开关,其实可以支持文件操作,但是我调试的时候发现,有些情况下是有问题的。比如我在一个循环中使用fread,但是他只有第一次的读取是有效的,但有些时候,它好像又可以。所以,你调试的时候,如果发现结果和VC下运行的不同,可以重点看看,是不是这里出了问题。
4.调试跟踪。经过上面的准备,程序已经可以运行了。你可以在Simulator下仿真,或者板子上直接仿真。在SI下,速度会很慢,不过
Sesion里面,有一个blackfin
family那个sision,速度还可以,当然,有板子会更好。我们开发的时候,我使用板子的时间总共不到两个月,所以浪费了很多时间,现在回头看看,好心痛。
调试结果OK了的话,说明移植已经成功了。就可以进入下一个最主要的阶段---优化了

DSP CACHE操作选项说明

直写式(write
through),也叫写透。即CPU在向Cache写入数据的同时,也把数据写入主存以保证Cache和主存中相应单元数据的一致性,其特点是简单可靠,但由于CPU每次更新时都要对主存写入,速度必然受影响。    
回写式(write
back)即CPU只向Cache写入,并用标记加以注明,直到Cache中被写过的块要被进入的信息块取代时,才一次写入主存。这种方式考虑到写入的往往是中间结果,每次写入主存速度慢而且不必要。其特点是速度快,避免了不必要的冗余写操作,但结构上较复杂。 
当CPU要进行写入操作时,只把数据写入Cache,而不直接写入主内存。这时,Cache与主内存之间会出现暂时不一致的数据块。当Cache中的不一致数据块将要被替代时,再把数据写回主内存,从而使Cache中的数据与主内存中的数据又再保持一致。在此方式下,需要在Cache中加入一个控制位(Dirty
bit),若Cache中的某数据块是由CPU写入的,则控制位=1,否则控制位=0。发生块替代时,Cache先检查被替代块的控制位,若控制位=0,则无需把内容写回主内存,若控制位=1,则执行写回操作。这样做的好处是当CPU多次刷新同一数据块时,只需把最后的结果写回主内存即可,从而避免了重复写入,因而具有较高的效率。在早期版本的BIOS中,用户还可以对采用写通方式还是写回方式进行设置,但在新版本的BIOS中已取消了这一设置。
可以认为Dirty表示cache中的数据被更改了
invalidate()则将内存中数据写入高速缓冲存储器。
因此,可以简单认为:在写之前需要invaliate,(invalidate里包含了writeback操作)
若一个cache是dirty的,则若需要对这块cache进行写操作时,需要将它writeback。

CCS中各个项目文件的作用

1.include头文件(.h)的主要作用
头文件,一般用于定义程序中的函数、参数、变量和一些宏单元,同库函数配合使用。因此,在使用库时,必须用相应的头文件说明。

2.DSP/BIOS CONFIG FILES
开发基于DSP/BIOS的程序保存BIOS配置之后自动产生的文件。在保存BIOS配置时候会产生program.cbd/programcfg.h54/programcfg.s54/programcfg.cmd/programcfg.h/programcfg_c.c(C5000的example,program为你的项目名),这些文件被分配在project不同目录。

3.Source Files
源程序,实现DSP系统指定功能的主要代码部分

4.Program.cmd
链接文件,在源文件经过汇编器(Assembler)输出的OBJ Files(目标文件)需要通过Linker(链接器)才能得到OUT files,在链接阶段Linker根据.cmd里面存储区、Section分配以及lib来链接rst.lib、csl.lib、DSP/BIOS library跟对段的重定位

5.Library Filers的作用
Rst.lib:C语言实时运行支持库,建立C运行环境,由_c_int00主要完成设置堆栈指针、初始化全局变量、调用main()
Csl.lib:芯片支持库,实现片内外设的操作
DSPLIB/IMGLIB:信号处理库,利用针对不同DSP的优化的函数进行数学运算
通用的视/音LIB:例如263、264、jpeg、G.7XX、mp3、wmv。。。
其他自己封装的LIB:例如bsl.lib(board source library),对DSP系统板上资源的操作。

其他文件:
.opt工程关于开发环境的参数文件。如工具条位置等信息;
.aps (AppStudio File),资源辅助文件,二进制格式,一般不用去管他.
.clw ClassWizard信息文件,实际上是INI文件的格式,有兴趣可以研究一下.有时候ClassWizard出问题,手工修改CLW文件可以解决.如果此文件不存在的话,每次用ClassWizard的时候绘提示你是否重建.
.dsp (DeveloperStudio Project):项目文件,文本格式,不过不熟悉的话不要手工修改.DSW(DeveloperStudio
Workspace)是工作区文件,其他特点和DSP差不多.
.plg 是编译信息文件,编译时的error和warning信息文件(实际上是一个html文件),一般用处不大.在Tools->Options里面有个选项可以控制这个文件的生成.
.hpj (Help Project)是生成帮助文件的工程,用microsfot Help Compiler可以处理.
.mdp (Microsoft DevStudio
Project)是旧版本的项目文件,如果要打开此文件的话,会提示你是否转换成新的DSP格式.
.bsc 是用于浏览项目信息的,如果用Source Brower的话就必须有这个文件.如果不用这个功能的话,可以在Project Options里面去掉Generate Browse Info File,可以加快编译速度.
.map 是执行文件的映像信息纪录文件,除非对系统底层非常熟悉,这个文件一般用不着.
.pch (Pre-Compiled File)是预编译文件,可以加快编译速度,但是文件非常大.
.pdb (Program Database)记录了程序有关的一些数据和调试信息,在调试的时候可能有用.
.exp 只有在编译DLL的时候才会生成,记录了DLL文件中的一些信息.一般也没什么用.
.ncb 无编译浏览文件(no compile browser)。当自动完成功能出问题时可以删除此文件。build后会自动生成。

转一个哥们儿的简历

http://www.soyudesign.com/zblog/post/209.html

求职意向: 嵌入式 or DSP开发工程师

教育背景: 2002年9月-2006 年7月:西安电子科技大学

项目经验:

1.
智能视频监控产品:基于DM642平台,负责软硬件及相关算法开发工作(以及相关的技术文档编写):

1) 硬件方面:

电路图PCB改版设计(使用PROTEL),硬件电路板焊接调试,

CPLD(ALTERA MAX II EMP240)程序编写,实现看门狗功能等(Verilog语言);

2) 软件方面:

DM642主程序框架的搭建,基于DSP/BIOS,采用RF5框架,

DM642驱动程序编写(EDMA,串口及云台控制,I2C,网络,FLASH,BOOT烧写启动,音频视频输入输出mini驱动),

PC端网络接收程序编写(基于TI及合众达demo程序修改);

3) 算法方面:

MPEG4算法:编码算法在DM642平台的移植及优化(程序结构调整,已经汇编优化等),

音频编解码算法在DM642平台的移植(G.711、G.726等算法),

智能视频算法:参与技术讨论(区域入侵、阴影去除、目标跟踪、人脸识别等算法),DM642平台部分算法的验证及移植(验证基于模板匹配的跟踪算法,移植mean
shift跟踪算法)。

2.
智能交通项目:基于DM642平台(硬件由大恒图像公司负责提供),负责软件及算法开发工作(以及相关的技术文档编写):

1) 软件方面:

DM642 主程序框架的搭建,基于DSP/BIOS,

DM642 FLASH烧写及boot实现,利用flashburn工具烧写;

2) 算法方面:

参与技术讨论(车牌定位分隔识别,车流密度检查等算法),

车牌字符识别算法在PC上实现及在DM642平台的移植(主要利用特征提取模板匹配算法)。

3.
DAVINCI开发项目:是与北京维德利公司智能视频监控产品的合作项目,基于DM6441
DAVINCI平台(硬件及相应linux驱动由上海通力公司负责提供),负责智能视频算法的移植整合以及linux下应用软件的开发工作:

1) 算法方面:

智能视频算法(区域入侵、摄像头诊断、丢包检测算法)在DAVINCI平台的移植,按照TI
xDM算法标准对这些算法进行打包;

2) 软件方面:

对应上面的算法编写相应的ARM端嵌入式应用程序,实现从视频驱动获取视频图像传给DSP算法端以及将算法处理结果输出等。

4. ADI BF561开发项目:基于ADI BlackFin BF561平台,负责软件及算法开发工作:

1) 软件方面:

主程序框架的搭建,

视频输入输出驱动的开发;

2) 算法方面:

摄像头消抖算法的移植验证。

5. PC上MPEG4编解码开发项目:

1) 编码方面:

算法优化,涉及到程序结构的调整,汇编优化(mmx、sse、sse2等),

2) 解码方面:

算法的实现以及优化,使用了intel的IPP库,解码速度为axis公司解码器速度的2/3。

6. 基于硬件压缩的视频监控产品:

1) Z1510 MPEG1视频监控产品:电路图设计
PCB,DOS应用程序(嵌入式)及驱动开发(cf卡、视频输入)。

2) SAA6752
MPEG2视频监控产品:电路图设计、PCB、DOS应用程序(嵌入式)及驱动开发(cf卡、视频输入)。

3) FIC8120 MPEG4 IP camera产品:电路图设计,PCB,linux
(arm9)应用程序及驱动开发(按键、 LED、 Flash、
boot、视频输入(SAA7113)输出(SAA7121))。

7. 其他的一些项目及产品开发等工作:

1) DSP5402的USB项目:处理器为TI
TMS320VC5402,usb接口芯片为PDIUSBD12,负责dsp端usb驱动程序以及PC端测试应用程序的编写(驱动采用D12通用驱动);

2) 使用过的其他器件:

ARM:S3C2410、S3C2440(驱动及应用程序),

单片机:c51、c8051F、MSP430、AVR、68HC等系列(软件硬件都做),

FPGA:EP1C3T144、EP1C6Q240(硬件以及Verilog程序,熟悉NIOS硬核),

DSP:TMS320F2407(驱动程序);

3) 使用的开发工具:

PC软件开发:

VC、C++builder、Matlab,

嵌入式软件开发:

Keil、IAR430、CodeWarrior、ICC、ADS、CCS、Visualdsp、VMware,

嵌入式硬件开发:

PROTEL、OrCad、Quartus、

熟练使用示波器外用表等硬件调试工具,熟练器件焊接。

4) 维护公司服务器(server 2003)以及网站(论坛后台)等。

自我评价:

知识结构比较完善,自学能力强,富有创造性思维,敢于做尝试性挑战性工作。
附录

大学期间的项目经验:
2004.9 -2004.12《声音定向系统》

实现功能:探测10米范围内声源方位。负责系统方案制定及硬件电路设计工作,该项目获2004年"首届全国大学生
IC设计大赛" 本科组一等奖。
在此期间还参加了"中国集成电路设计产业发展十周年高层论坛暨中国半导体行业协会集成电路设计分会'2004年会"。
成员:3人;
2004.12 《墙体探测器》

实现功能:探测墙内7厘米范围是否有异物,作品以"构思新颖,独特创新"获2004年校"星火杯"特等奖。
2005.1 -2005.5 《智能媒体播放器》

负责脉搏采集部分方案制定和模拟电路设计,该项目获2005
"DSP西部高校邀请赛"团体入围奖。 成员:6人;
2005.6 《3D显示器》

参加全国挑战杯项目,负责系统分析及硬件电路设计,并参与机械部分设计
成员:3人;
2005.9 《简易频谱分析仪》

此为参加全国电子设计竞赛所选题目,基本完成题目要求,获陕西赛区二等奖。主要负责主控芯片的编程工作,采用MSP430F149单片机作为主控芯片,
EP1C6Q240 FPGA实现外围控制和部分高速处理。
成员:3人;
2004.8 《基于DSP5416的脉象采集及分析系统》

项目方案来自《智能媒体播放器》,根据其心情识别部分所做改动,负责其系统硬件设计及编程,(算法来自《智能媒体播放器》心情识别部分)
成员:2人;
2003.12 《USB串口并口转换器》

使用PDIUSBD12,完成USB1.1接口转换功能;
2003.12 《USB2.0接口应用》

使用ISP1581 ,采用51单片机作为主控芯片,完成 USB2.0数据收发;
2004.3 《三角法激光测距仪》

负责硬件电路部分设计,该项目是与西安某公司合作;
2005.10 《nRF905无线收发模块》

功能:采用TI5402
DSK板,结合本模块实现语音信号的采集传输及回放,参与电路测试及软件调试,该项目以被列为TI-XD
DSP 实验室学生试验题目。成员:3人。
2006.1 -2006.6《TPMS汽车轮胎压力监测系统》

这是毕业设计所选题目,采用FREESCALE方案,所用主要芯片为:MC68hc908RF2,MC33594,MPXY8020A
,完成系统样机。

Linux/Unix环境下的Make和Makefile语法和详解

无论是在Linux还是在Unix环境中,make都是一个非常重要的编译命令。不管是自己进行项目开发还是安装应用软件,我们都经常要用到
make或make
install。利用make工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用make和
makefile工具就可以简洁明快地理顺各个源文件之间纷繁复杂的相互关系。而且如此多的源文件,如果每次都要键入gcc命令进行编译的话,那对程序员
来说简直就是一场灾难。而make工具则可自动完成编译工作,并且可以只对程序员在上次编译后修改过的部分进行编译。因此,有效的利用make和
makefile工具可以大大提高项目开发的效率。同时掌握make和makefile之后,您也不会再面对着Linux下的应用软件手足无措了。
但令人遗憾的是,在许多讲述Linux应用的书籍上都没有详细介绍这个功能强大但又非常复杂的编译工具。在这里我就向大家详细介绍一下make及其描述文件makefile。
Makefile文件
Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。而makefile
文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。makefile
文件是许多编译器–包括 Windows NT
下的编译器–维护编译信息的常用方法,只是在集成开发环境中,用户通过友好的界面修改
makefile 文件而已。
在 UNIX 系统中,习惯使用 Makefile 作为 makfile
文件。如果要使用其他文件作为 makefile,则可利用类似下面的 make
命令选项指定 makefile 文件:
$ make -f Makefile.debug
例如,一个名为prog的程序由三个C源文件filea.c、fileb.c和filec.c以及库文件LS编译生成,这三个文件还分别包含自己的
头文件a.h
、b.h和c.h。通常情况下,C编译器将会输出三个目标文件filea.o、fileb.o和filec.o。假设filea.c和fileb.c都要
声明用到一个名为defs的文件,但filec.c不用。即在filea.c和fileb.c里都有这样的声明:
#include "defs"
那么下面的文档就描述了这些文件之间的相互联系:
———————————————————
#It is a example for describing makefile
prog : filea.o fileb.o filec.o
cc filea.o fileb.o filec.o -LS -o prog
filea.o : filea.c a.h defs
cc -c filea.c
fileb.o : fileb.c b.h defs
cc -c fileb.c
filec.o : filec.c c.h
cc -c filec.c
———————————————————-
这个描述文档就是一个简单的makefile文件。
从上面的例子注意到,第一个字符为 #
的行为注释行。第一个非注释行指定prog由三个目标文件filea.o、fileb.o和filec.o链接生成。第三行描述了如何从prog所依赖的
文件建立可执行文件。接下来的4、6、8行分别指定三个目标文件,以及它们所依赖的.c和.h文件以及defs文件。而5、7、9行则指定了如何从目标所
依赖的文件建立目标。
当filea.c或a.h文件在编译之后又被修改,则 make
工具可自动重新编译filea.o,如果在前后两次编译之间,filea.C 和a.h
均没有被修改,而且test.o还存在的话,就没有必要重新编译。这种依赖关系在多源文件的程序编译中尤其重要。通过这种依赖关系的定义,make
工具可避免许多不必要的编译工作。当然,利用Shell脚本也可以达到自动编译的效果,但是,Shell
脚本将全部编译任何源文件,包括哪些不必要重新编译的源文件,而 make
工具则可根据目标上一次编译的时间和目标所依赖的源文件的更新时间而自动判断应当编译哪个源文件。
Makefile文件作为一种描述文档一般需要包含以下内容:
◆ 宏定义
◆ 源文件之间的相互依赖关系
◆ 可执行的命令
Makefile中允许使用简单的宏指代源文件及其相关编译信息,在Linux中也称宏为变量。在引用宏时只需在变量前加$符号,但值得注意的是,如果变量名的长度超过一个字符,在引用时就必须加圆括号()。
下面都是有效的宏引用:
$(CFLAGS)
$2
$Z
$(Z)
其中最后两个引用是完全一致的。
需要注意的是一些宏的预定义变量,在Unix系统中,$*、$@、$?和$<四个特殊宏的值在执行命令的过程中会发生相应的变化,而在GNU
make中则定义了更多的预定义变量。关于预定义变量的详细内容,宏定义的使用可以使我们脱离那些冗长乏味的编译选项,为编写makefile文件带来很
大的方便。
———————————————————
# Define a macro for the object files
OBJECTS= filea.o fileb.o filec.o
# Define a macro for the library file
LIBES= -LS
# use macros rewrite makefile
prog: $(OBJECTS)
cc $(OBJECTS) $(LIBES) -o prog
……
———————————————————
此时如果执行不带参数的make命令,将连接三个目标文件和库文件LS;但是如果在make命令后带有新的宏定义:
make "LIBES= -LL -LS"
则命令行后面的宏定义将覆盖makefile文件中的宏定义。若LL也是库文件,此时make命令将连接三个目标文件以及两个库文件LS和LL。
在Unix系统中没有对常量NULL作出明确的定义,因此我们要定义NULL字符串时要使用下述宏定义:
STRINGNAME=
Make命令
在make命令后不仅可以出现宏定义,还可以跟其他命令行参数,这些参数指定了需要编译的目标文件。其标准形式为:
target1 [target2 …]:[:][dependent1 …][;commands][#…]
[(tab) commands][#…]
方括号中间的部分表示可选项。Targets和dependents当中可以包含字符、数字、句点和"/"符号。除了引用,commands中不能含有"#",也不允许换行。
在通常的情况下命令行参数中只含有一个":",此时command序列通常和makefile文件中某些定义文件间依赖关系的描述行有关。如果与目
标相关连的那些描述行指定了相关的command序列,那么就执行这些相关的command命令,即使在分号和(tab)后面的aommand字段甚至有
可能是NULL。如果那些与目标相关连的行没有指定command,那么将调用系统默认的目标文件生成规则。
如果命令行参数中含有两个冒号"::",则此时的command序列也许会和makefile中所有描述文件依赖关系的行有关。此时将执行那些与目标相关连的描述行所指向的相关命令。同时还将执行build-in规则。
如果在执行command命令时返回了一个非"0″的出错信号,例如makefile文件中出现了错误的目标文件名或者出现了以连字符打头的命令字符串,make操作一般会就此终止,但如果make后带有"-i"参数,则make将忽略此类出错信号。
Make命本身可带有四种参数:标志、宏定义、描述文件名和目标文件名。其标准形式为:
Make [flags] [macro definitions] [targets]
Unix系统下标志位flags选项及其含义为:
-f
file  指定file文件为描述文件,如果file参数为"-"符,那么描述文件指向标准输入。如果没有"-f"参数,则系统将默认当前目录下名为
makefile或者名为Makefile的文件为描述文件。在Linux中, GNU make
工具在当前工作目录中按照GNUmakefile、makefile、Makefile的顺序搜索
makefile文件。
-i   忽略命令执行返回的出错信息。
-s   沉默模式,在执行之前不输出相应的命令行信息。
-r   禁止使用build-in规则。
-n   非执行模式,输出所有执行命令,但并不执行。
-t   更新目标文件。
-q   make操作将根据目标文件是否已经更新返回"0″或非"0″的状态信息。
-p   输出所有宏定义和目标文件描述。
-d   Debug模式,输出有关文件和检测时间的详细信息。
Linux下make标志位的常用选项与Unix系统中稍有不同,下面我们只列出了不同部分:
-c dir   在读取 makefile 之前改变到指定的目录dir。
-I dir   当包含其他 makefile文件时,利用该选项指定搜索目录。
-h   help文挡,显示所有的make选项。
-w   在处理 makefile 之前和之后,都显示工作目录。
通过命令行参数中的target
,可指定make要编译的目标,并且允许同时定义编译多个目标,操作时按照从左向右的顺序依次编译target选项中指定的目标文件。如果命令行中没有指定目标,则系统默认target指向描述文件中第一个目标文件。
通常,makefile 中还定义有 clean
目标,可用来清除编译过程中的中间文件,例如:
clean:
rm -f *.o
运行 make clean 时,将执行 rm -f *.o
命令,最终删除所有编译过程中产生的所有中间文件。
隐含规则
在make
工具中包含有一些内置的或隐含的规则,这些规则定义了如何从不同的依赖文件建立特定类型的目标。Unix系统通常支持一种基于文件扩展名即文件名后缀的隐
含规则。这种后缀规则定义了如何将一个具有特定文件名后缀的文件(例如.c文件),转换成为具有另一种文件名后缀的文件(例如.o文件):
.c:.o
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
系统中默认的常用文件扩展名及其含义为:
.o  目标文件
.c  C源文件
.f  FORTRAN源文件
.s  汇编源文件
.y  Yacc-C源语法
.l  Lex源语法
在早期的Unix系统系统中还支持Yacc-C源语法和Lex源语法。在编译过程中,系统会首先在makefile文件中寻找与目标文件相关的.C
文件,如果还有与之相依赖的.y和.l文件,则首先将其转换为.c文件后再编译生成相应的.o文件;如果没有与目标相关的.c文件而只有相关的.y文件,
则系统将直接编译.y文件。
而GNU make
除了支持后缀规则外还支持另一种类型的隐含规则–模式规则。这种规则更加通用,因为可以利用模式规则定义更加复杂的依赖性规则。模式规则看起来非常类似于
正则规则,但在目标名称的前面多了一个 %
号,同时可用来定义目标和依赖文件之间的关系,例如下面的模式规则定义了如何将任意一个
file.c 文件转换为 file.o 文件:
%.c:%.o
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
#EXAMPLE#
下面将给出一个较为全面的示例来对makefile文件和make命令的执行进行进一步的说明,其中make命令不仅涉及到了C源文件还包括了
Yacc语法。本例选自"Unix Programmer's Manual 7th Edition, Volume 2A" Page
283-284
下面是描述文件的具体内容:
———————————————————
#Description file for the Make command
#Send to print
P=und -3 | opr -r2
#The source files that are needed by object files
FILES= Makefile version.c defs main.c donamc.c misc.c file.c \
dosys.c gram.y lex.c gcos.c
#The definitions of object files
OBJECTS= vesion.o main.o donamc.o misc.o file.o dosys.o gram.o
LIBES= -LS
LINT= lnit -p
CFLAGS= -O
make: $(OBJECTS)
cc $(CFLAGS) $(OBJECTS) $(LIBES) -o make
size make
$(OBJECTS): defs
gram.o: lex.c
cleanup:
-rm *.o gram.c
install:
@size make /usr/bin/make
cp make /usr/bin/make ; rm make
#print recently changed files
print: $(FILES)
pr $? | $P
touch print
test:
make -dp | grep -v TIME>1zap
/usr/bin/make -dp | grep -v TIME>2zap
diff 1zap 2zap
rm 1zap 2zap
lint: dosys.c donamc.c file.c main.c misc.c version.c gram.c
$(LINT) dosys.c donamc.c file.c main.c misc.c version.c \
gram.c
rm gram.c
arch:
ar uv /sys/source/s2/make.a $(FILES)
———————————————————-
通常在描述文件中应象上面一样定义要求输出将要执行的命令。在执行了make命令之后,输出结果为:
$ make
cc -c version.c
cc -c main.c
cc -c donamc.c
cc -c misc.c
cc -c file.c
cc -c dosys.c
yacc gram.y
mv y.tab.c gram.c
cc -c gram.c
cc version.o main.o donamc.o misc.o file.o dosys.o gram.o \
-LS -o make
13188+3348+3044=19580b=046174b
最后的数字信息是执行"@size
make"命令的输出结果。之所以只有输出结果而没有相应的命令行,是因为"@size
make"命令以"@"起始,这个符号禁止打印输出它所在的命令行。
描述文件中的最后几条命令行在维护编译信息方面非常有用。其中"print"命令行的作用是打印输出在执行过上次"make
print"命令后所有改动过的文件名称。系统使用一个名为print的0字节文件来确定执行print命令的具体时间,而宏$?则指向那些在print
文件改动过之后进行修改的文件的文件名。如果想要指定执行print命令后,将输出结果送入某个指定的文件,那么就可修改P的宏定义:
make print "P= cat>zap"
在Linux中大多数软件提供的是源代码,而不是现成的可执行文件,这就要求用户根据自己系统的实际情况和自身的需要来配置、编译源程序后,软件才能使用。只有掌握了make工具,才能让我们真正享受到到Linux这个自由软件世界的带给我们无穷乐趣。

quickfix提高vim编程编译调试效率

以前使用vim编程时,在终端下面至少2个tab,一个编辑一个编译,编译出错时,切换到编辑tab页进行差错纠正。今天又学习了vim的quickfix模式,quickfix是vim自带的,无需插件。

vim由一个程序员开发,而且为更多的程序员所使用,所以在vim中加强了对软件开发的支持,quickfix模式的引入就是一个例子。所谓quickfix模式,它和Normal模式、Insert模式没什么关系,它只是一种加快你开发速度的工作方式。
Quickfix模式的主要思想是保存一个位置列表,然后提供一系列命令,实现在这个位置列表中跳转。
位置列表的产生可以从编译器的编译输出信息中获得,也可以由grep命令的输出信息中获得。

[编译]
通常,我们在开发过程中,经常要写代码,编译,修改编译错误,这个过程会数十遍上百遍的重复。如果你是根据编译器输出的错误信息,打开出错的文件,找到出错的行,然后再开始修改,那效率未免太低下了。
利用vim的quickfix模式,可以大大加快这一过程,你可以在vim启动编译,然后vim会根据编译器输出的错误信息,自动跳到第一个出错的地方,让你进行修改;修改完后,使用一个快捷键,跳到下一个错误处,再进行修改,方便的很。
为了做到这一点,你首先要定义编译时所使用的程序,对大多数使用Makefile的项目来说,vim的缺省设置"make"已经可以满足要求了。如果你的项目需要用一个特殊的程序进行编译,就需要修改'makeprg'选项的值。
大家在学编程时大概都读过"hello world"程序,我们就以这个简单的例子为例,讲一下quickfix模式的用法。

该程序的内容如下,里面包含了三个小小的错误:
/* hello world demo */
#include <stdio.h"
int main(int argc, char **argv)
{
int i;
print("hello world\n");
return 0;
}

输入编译命令了:
:make
在使用":make"时,vim会自动编译,并把编译输出重定向到一个临时文件中,当编译出现错误时,vim会从上述临时文件中读出错误信息,根据这些信息形成quickfix列表,并跳转到第一个错误出现的地方。
对于我们上面的程序来说,光标会停在第三行,也就是第一个出错的位置,vim同时会提示出错信息。如果你没看清出错信息,可以输入":cc"命令,vim会更次显示此信息,或者干脆使用":cw"命令,打开一个quickfix窗口,把所有的出错信息显示出来,见下图:


现在我们知道错在哪儿了,修正一下,然后使用":cn"命令(或者在Quickfix List对应行上输入回车)跳到下一个出错的地方,以此类推,直到修正全部错误。
好了,千辛万苦,我们的hello world终于工作了。乍一看这个例子,似乎Quickfix并没有提高什么效率,但如果你的错误出现在多个不同目录的不同文件里,它可以帮你省很多时间,使你可以集中精力在修正bug上。
vim可以同时记住最新的10个错误列表,也就是说你最近10次使用":make"命令编译所遇到的错误都保存着,可以使用":colder"和":cnewer"命令,回到旧的错误列表,或者到更新的错误列表。

在quickfix模式里经常用到的命令有:
:cc 显示详细错误信息 ( :help :cc )
:cp 跳到上一个错误 ( :help :cp )
:cn 跳到下一个错误 ( :help :cn )
:cl 列出所有错误 ( :help :cl )
:cw 如果有错误列表,则打开quickfix窗口 ( :help :cw )
:col 到前一个旧的错误列表 ( :help :col )
:cnew 到后一个较新的错误列表 ( :help :cnew )

更多的命令,以及这些命令更详细的解释,请参见手册。
对于经常用到的命令,最好提供更方便的使用方法,在我的vimrc中的定义:
autocmd FileType c,cpp map <buffer> <leader><space> :w<cr>:make<cr>
nmap <leader>cn :cn<cr>
nmap <leader>cp :cp<cr>
nmap <leader>cw :cw 10<cr>

现在使用",<space>"(先按,再按空格)就可以编译,使用",cp"和",cn"跳到上一个和下一个错误,使用",cw"来打开一个quickfix窗口。这下顺手多了!
如果你希望跳转到出错的文件时,使用一个分隔的窗口打开,请参阅'switchbuf'选项的值。
在vim7中,每个窗口都可以拥有自己的位置列表,这样,你就能够同时打开多个位置列表了,而quickfix列表在整个vim中只有一个。你可以使用位置列表来显示编译错误信息,具体命令参阅手册:":help location-list"以及":help :lmake"。

结合文章http://dongpingli.blogspot.com/2009/03/makefile_27.html编写的通用makefile,这下在写新程序时,直接copy makefile到程序目录,在vim里面写完程序后直接make,然后查错修改在一个tab下,省得来回tab切换,效率会提高不少。

2009年3月28日星期六

如何精确测量程序运行时间

对于一个嵌入式程序员来说,"我的程序到底运行多快",是我们最为关心的问题,因为速度,实时性,永远是嵌入式设备性能优化的基本立足点之一。
可惜的是,我们平时常用的测试运行时间的方法,并不是那么精确的。换句话说,想精确获取程序运行时间,不是那么容易的。也许你会想,程序不就是一条条指令
么,每一条指令序列都有固定执行时间,为什么不好算?真实情况下,我们的计算机并不是只运行一个程序的,进程的切换,各种中断,共享的多用户,网络流量,
高速缓存的访问,转移预测等,都会对计时产生影响。

可惜的是,在性能测量领域,我们有gprof,有intel的vtune,却缺少相应
的,广泛流传的参考文献。如果你希望能建立起自己的工具,或者对具体的测量方式感兴趣,那么本文也许会对你有帮助。我想,应该有很多人希望知道计时机制的
原理,因为针对不同的系统,环境,会有不同的解决方案。本文主要针对Linux和X86体系环境,主要思想来源于"Computer
System A Programmer's
Perspective",夹杂了一些自己的理解,并试图给出我自己写的一个通用测量工具,支持用户自配置。本文有时的对象是程序有时描述对象是进程,这个请自行理解,因为一个程序就是在一个进程里面执行的。

进程调度和模式切换

在介绍具体方法之前,先简单说几句。


于进程调度来讲,花费的时间分为两部分,第一是计时器中断处理的时间,也就是当且仅当这个时间间隔的时候,操作系统会选择,是继续当前进程的执行,还是切
换到另外一个进程中去。第二是进程切换时间,当系统要从进程A切换到进程B时,它必须先进入内核模式将进程A的状态保存,然后恢复进程B的状态。因此,这个切换过程是有内核活动来消耗时间的。具体到进程的执行时间,这个时间也包括内核模式和用户模式两部分,模式之间的切换也是需要消耗时间,不过都算在进程执行时间中了。

其实模式切换非常费时,这也是很多程序中都要采用缓冲区的原因,例如,如果每读一小段文件什么的就要调用一次
read之类的内核函数,那太受影响了。所以,为了尽量减少系统调用,或者说,减少模式切换的次数,我们向程序(特别是IO程序)中引入缓冲区概念,来缓
解这个问题。

一般来说呢,向处理器发送中断信号的计时器间隔通常是1-10ms,太短,切换太多,性能可能会变差,太长呢,如果在任务间切换频繁,又无法提供在同时执行多任务的假象。这个时间段,也决定了一些我们下面要分析的不同方法衡量时间的差异。
方法一:间隔计数


们都知道,Linux下有一个命令是专门提供一个进程的运行时间的,也就是time。time可以测量特定进程执行时所需消耗的时间及系统资源等,这个时
间还可以分内核时间和用户态时间两部分呈现给你。它是怎么做到的呢?其实很简单,操作系统本身就是用计时器来记录每个进程使用的累计时间,原理很简单,计
时器中断发生时,操作系统会在当前进程列表中寻找哪个进程是活动的,一旦发现,哟,进程A跑得正欢,立马就给进程A的计数值增加计时器的时间间隔(这也是
引起较大误差的原因,想想)。当然不是统一增加的,还要确定这个进程是在用户空间活动还是在内核空间活动,如果是用户模式,就增加用户时间,如果是内核模
式,就增加系统时间。

原理很简单吧?但是相信一点,越简单的东西,是不会越精确的,人品守恒,能量守恒,难度也当然会守恒了啊。下面就简
单分析一下,为啥这玩意精度不高吧。举个例子,如果我们有一个系统,计时器间隔为10ms,系统里面跑了一个进程,然后我们用这种方法分析时间,测出
70ms,想一想,实际会有几种结果?具体点,我们用这种方法对进程计时,在某个计时器中断时,系统发现,咦,有一个进程开始跑了,好,给进程的计数值加
上10ms。但是实际上呢,这个进程可能是一开始就跑起来了,也肯能是在中断的前1ms才开始跑的。不管是什么原因,总之中断时候它在跑,所以就得加
10ms。当中断发生时发现进程切换了,同理,可能是上一个中断之后1ms进程就切换了,也可能人家刚刚才切换。

所以呢,如果一个进程的
运行时间很短,短到和系统的计时器间隔一个数量级,用这种方法测出来的结果必然是不够准确的,头尾都有误差。不过如果程序的时间足够长,这种误差有时能够
相互弥补,一些被高估一些被低估,平均下来刚好,呵呵。从理论上,我们很难分析这个误差的值,所以一般只有程序到达秒的数量级时,用这种方式测试程序时间
才有意义。

说了半天,难道这方法没优点了?不,这个世界没有纯善,也没有纯恶。这方法最大的优点是,它的准确性不是非常依赖于系统负载。那什么方法依赖于系统负载呢?接下来我们会讲到:)

理论陈述结束,我想应该开始关注实现方法了吧。其实超级简单,两种方法:
直接调用time命令(一堆鸡蛋)
使用tms结构体和times函数

说说正经点的第二个方法吧。在Linux中,提供了一个times函数,原型是

clock_t times( struct tms *buf )

这个tms的结构体为

struct tms
{
clock_t tms_utime; // user time
clock_t tms_stime; // system time
clock_t tms_cutime; // user time of reaped children
clock_t tms_cstime; // system time of reaped children
}

怎么使用就不用这里教了吧?不过要说明一下的是,这里的cutime和cstime,都是对已经终止并回收的时间的累计,也就是说,times不能监视任何正在进行中的子进程所使用的时间。
方法二:周期计数


才谈了半天间隔计数的不足之处,哪有不足,那就有弥补的方法,特别实在万能的Linux中:)
为了给计时测量提供更高的准确度,很多处理器还包含一个运行在时钟周期级别的计时器,它是一个特殊的寄存器,每个时钟周期它都会自动加1。这个周期计数器
呢,是一个64位无符号数,直观理解,就是如果你的处理器是1GHz的,那么需要570年,它才会从2的64次方绕回到0,所以你大可不必考虑"万一溢出
怎么办"此类问题。

看到这里,也许你会想,哇塞,很好很强大嘛,时钟周期,这都精确到小数点后面多少位来着了?这下无论是多快的用时多短
的程序,我们也都能进行时间测量了。Ohyeah。等等,刚才我们说过什么来着?守恒定律啊!功能强大的东西,其他方面必有限制嘛。看到上面的介绍,聪明
的你一定能猜出来这种方法的限制是什么了,那就是,hardware
dependent。首先,并不是每种处理器都有这样的寄存器的,其次,即使大多数都有,实现机制也不一样,因此,我们无法用统一的,与平台无关的接口来
使用它们。怎么办?这下,就要祭出上古传说中的神器:汇编了。当然,我们在这里实际用的是C语言的嵌入汇编:

void counter( unsigned *hi, unsigned *lo )
{
asm("rdtsc; movl %%edx,%0; movl %%eax, %1″
: "=r" (*hi), "=r" (*lo)
:
: "%edx", "%eax");
}

第一行的指令负责读取周期计数器,后面的指令表示将其转移到指定地点或寄存器。这样,我们将这段代码封装到函数中,就可以在需要测量的代码前后均加上这个函数即可。最后得到的hi和lo值都是两个,除了相减得到间隔值外,还要进行一些处理,在此先按下不表。

不得不提出的是,周期计数方式还有一个问题,就是我们得到了两次调用counter之间总的周期数,但我们不知道是哪个进程使用了这些周期,或者说处理器是在内核还是在用户模式中。还记得刚才我们讲间隔计数方式么?这玩意的好处就是它是操作系统控制给进程计时的,我们可以知道具体哪个进程,哪个模式。但是周期计数只测量经过的时间,他不管你是哪个进程使用的。所以,用周期计数的话,我们必须很小心。举个例子

double time()
{
start_counter();
p();
get_counter();
}

这样一段程序,如果机器的负载很重,会导致P运行时间很长,而其实P函数本身是不需要运行这么长时间的,而是上下文切换等过程将它的时间拖长了。

而且,转移预测(想一想,如果转移方向和目的预测错误)和高速缓存的命中率,对这个计数值也会有影响。通常情况下,为了减少高速缓存不命中给我们程序执行时间带来的影响,可以执行这样的代码:

double time_warm( void )
{
p();
start_counter();
p();
get_counter();
}

原因不用我再解释了吧?它让指令高速缓存和数据高速缓存都得到了warm-up。

好,接下来又有问题。如果我们的应用,是属于那种每次执行都希望访问新的数据的那种呢?在这种情况下,我们希望让指令高速缓存warm-up,而数据高速缓存不能warm-up,很明显,time_warm函数低估我们的运行时间了。让我们进行进一步修改:

double time_cold( void )
{
p();
clear_cache();
start_counter();
p();
get_counter();
}

注意,我们加入了一个清除数据缓存的函数。这个函数的具体实现很简单,依情况而定,比如举个例子

volatile int tmp;
static int dummy[N]; // N是你需要清理缓存的字节数

void clear_cache( void )
{
inti, sum = 0;
for( i=1;i<N;i++ )
dummy[i] = 2;
for( i=1;i<N;i++ )
sum += dummy[i];
tmp = sum;
}

具体原理很简单,我们在定义一个数组并在其上执行一个计算,计算过程中的数据会覆盖高速数据缓存中原有的数据。每一次的store和load都会让高速数据缓存cache这个数组,而定义为volatile的tmp则保证这段代码不会被优化。

这样做,是不是就万无一失了呢?不是的,因为大多数处理器,L2高速缓存是不分指令和数据的,这样clear_cache会让所有P的指令也被清除,只不过:L1缓存中的指令还会保留而已。

其实上面提到的诸多原因,都是我们不能控制的,我们无法控制让高速缓存去加载什么,不去加载什么,加载时去掉什么,保留什么。而且,这些误差通常都是会过高估计真实的运行时间。那么具体使用时,有没有什么办法来改善这种情况呢?有,就是The
K-Best Measurement
Scheme。这玩意其实很麻烦,所以我在具体实践中都不用它,附上一个文档,有兴趣的朋友可以下载下来看一下。

我不喜欢间隔计数的小适用范围,也不喜欢周期计数的麻烦性,相信读到这里的99%的读者也和我一种感受吧。OK,最后我们要介绍的,就是一个可移植性更好,相对较准确的方法。
方法三:gettimeofday函数计时

gettimeofday是一个库函数,包含在time.h中。它的功能是查询系统时钟,以确定当前的日期和时间。它很类似于刚才所介绍的周期计时,除了测量时间是以秒为单位,而不是时钟周期为单位的。原型如下:

struct timeval
{
long tv_sec;
long tv_usec;
}

int gettimeofday( struct timeval *tv, NULL )


个机制呢,具体的实现方式在不同系统上是不一样的,而且虽然披着一个usec(us)的老虎皮,其实没这么精确。具体的精确程度,是和系统相关的,比如在
Linux下,是用周期计数来实现这个函数的,所以和周期计数的精确度差不多,但是在Windows
NT下,使用间隔计数实现的,精确度就很低了(所以啊,万恶的ms啊)。

具体使用的时候,就是开始来一个gettimeofday( tvstart, NULL
),结束来一个gettimeofday( tvend, NULL
),完了sec域和usec域相减的差值就是计时时间。

如何,很方便吧?应该说在Linux下,这是最有效而方便的计时方式了。从测试情况看,精确度也不错。这种价格便宜量又足的东西嘛,大家可以随便多用。
总结

这次的总结很简单:没有一个计时方法是完美的,我们所要作的,就是理解本质后,在特定的系统上去寻找特定的好方法。

vim的颜色配置colorsceme

装了最新的vim7.2,到了挑选颜色配置的时候了,murphy用了一段时间,但也有些许不足之处。卡耐基梅隆大学的哥们儿列出了vim各种颜色配置效果图,方便大家去挑选适合自己眼睛的配置:
http://www.cs.cmu.edu/~maverick/VimColorSchemeTest/index-c.html
如果还有不满意之处,可以自行修改配色文件,定制各种标志符的高亮颜色,vim配色参考网页:
http://likunarmstrong.googlepages.com/vim-color.html