Yangchun Luo's profileAce of the ACESPhotosBlogLists Tools Help

Blog


    May 31

    MSN Space Abandoned

    New home with permanent top-level URL:

    www.pallyc.org/blog
    November 14

    Blogger 体验

    用特殊技术手段突破了中国政府对Google Blogger的访问限制。

    http://blog.cls8.org/sonic1984乃我的新家,主要贴些无关痛痒的琐事和感悟。Google Blogger最爽之处除了简洁清新的界面风格,更在于它开放的精神。用户选择一个模版,然后就可以任意更改整个网站的源代码,绝对是高级用户的首选!See www.blogger.com for more details.

    Meanwhile,技术性的“天书”改发到http://blog.csdn.net/sonic1984去了。喜欢探讨技术的朋友们可以去那里交流。

    至于现在这个MSN Space,暂时还没有想好它的定位。不过希望这种分离的方式能带来更specific的blog体验。

     
    October 31

    最大的优点和最大的缺点

    晚上自习的时候突然想到一个问题:如果今后面试的时候被问到阐述一下自己的最大优点,我该怎么回答呢?

    从小到大我觉得自己最引以为豪的地方就是自己能静得下心、坐得住。一本书、一支笔、一个人的状态是最惬意不过的了。每次上完自习,独走在夜晚的冷风中,那种感觉总是很畅快的。

    但这也许是我最大的不足。喜欢安静、害怕别人打扰的性格有些时候会显得跟人群格格不入。跟人打交道也许是我最不擅场的了。有时看见别人在群体中八面玲珑游刃有余,虽然也会羡慕,但这绝不是我的风格。

    最喜欢的状态是什么?人不在多,知心则行。四五个要好的朋友能在一起谈天说地、研究技术、切磋游戏,没有勾心斗角繁文缛节世间纷扰,这比什么都强!

    October 29

    Introduction to C/C++ Binary Library

    Part 1. 需求分析:为什么要使用二进制库?

     

    答案在于两个方面:

    1)         隐藏源代码的需要。

    放眼现在市面上各个中间件软件提供商,所提供的产品绝大多数都是以二进制库的形式发布的。流行的比如Linux下的Qt图形库,Windows下微软的DirectX图形接口、.NET基类,以及嵌入式平台上的MiniGUI等等。在Linux下静态库和动态库文件的扩展名通常为.lib.so(Shared Object),而Windows下动态库也称作动态链接库,扩展名为.dll(Dynamic Link Library)

    2)         模块化、层次化设计的需要。

    一旦某个软件模块设计完成,可以将其打包成一个独立的库文件。这样系统其他部分就可以按照其提供的接口进行调用,而无须考虑代码层次的兼容性。其次,库文件是已经编译好的二进制文件,被调用的库只需要静态或动态地链接到主调代码中即可,而无需再次编译库的源代码,大大提高了编译的速度和效率。

    下面在Linux环境中使用流行的gcc编译器来演示这两种库的建立和使用。

     

    Part 2. 案例1C与动态共享库

     

    假设有一个简单的任务(经典的helloworld例子)

    有一个sayHello的方法在sayhello.c中定义:

    //file: sayhello.c

    #include<stdio.h>

    void sayHello()

    {

            printf("hello!\n");

    }

    头文件sayhello.h声明函数sayHello()的形式:

    //file: sayhello.h

    void sayHello();

    现在我们希望发布这个sayHello函数的实现,但是不希望别人知道源代码。于是我们可以将sayHello()函数打包成二进制库的形式发布,同时提供一个头文件声明其调用格式。

    主程序hello.c调用sayHello(),如下:

    //file: hello.c

    #include"hello.h"

    int main()

    {

            sayHello();

            return 0;

    }

    生成动态库的过程如下:

    [sonic@Rex dynamic-lib-gcc]$ gcc -c -fpic sayhello.c

    这样就生成了一个sayhello.o的目标文件。

    参数-c: 编译或汇编源文件,但是不作连接.编译器输出对应于源文件的目标文件.

    参数-fpic: Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine.

    [sonic@Rex dynamic-lib-gcc]$ gcc -shared sayhello.o -o libHello.so

    共享库libHello.so就生成好了。

     

    现在假设第三方客户需要使用我们提供的共享库,只需要提供libHello.sohello.h就行了。(hello.h包含于主程序hello.c中)

    [sonic@Rex dynamic-lib-gcc]$ gcc hello.c sayhello.so -o hello

    把主程序文件和打包好的共享库文件一起编译,最后得到的文件是

    [sonic@Rex dynamic-lib-gcc]$ ls

    hello  hello.c  libHello.so  sayhello.h

    但是这样还不能运行:

    [sonic@Rex dynamic-lib-gcc]$ ./hello

    ./hello: error while loading shared libraries: libHello.so: cannot open shared object file: No such file or directory

    这是因为编译hello时用到了共享库,而运行的时候系统只在几个固定的目录进行查找:

    /lib/usr/lib,其余的在配置文件/etc/ld.so.conf中指定。

    这里有两种选择,一是把libHello.so拷入系统指定的目录,一是自己建一个专门放置共享库的目录。

    比如/lib/local/shared-lib就是一个不错的选择。

    修改/etc/ld.so.conf,将目录加入其中。

    libHello.so复制到/lib/local/shared-lib,然后以root账户运行/sbin/ldconfig更新系统缓存。

    Ok,现在运行:

    [sonic@Rex dynamic-lib-gcc]$ ./hello

    hello!

    Bingo!试验成功!

    ldd检查一下编译完成的hello可执行文件:

    [sonic@Rex dynamic-lib-gcc]$ /usr/bin/ldd[1] hello

            libHello.so=> /usr/local/shared-lib/libHello.so (0x00a26000)

            libc.so.6 => /lib/tls/libc.so.6 (0x001d0000)

            /lib/ld-linux.so.2 (0x001b7000)

    可以发现红色标记的那一行是我们提供的共享库。

     

    Part 3. 案例2C++与库

     

    这是一个稍微复杂一点的例子。

    比如我们要发布一个类,提供这样的头文件声明:

    //file: SaySomething.h

    class SaySomething

    {

    public:

            void sayhello();

            void sayhi();

    };

    成员函数的定义由两个文件承担:

    //file: SayHello.cpp

    #include "SaySomething.h"

    #include <iostream>

    using namespace std;

    void SaySomething::sayhello()

    {

            cout<<"hello"<<endl;

    }

     

    //file: SayHi.cpp

    #include "SaySomething.h"

    #include <iostream>

    using namespace std;

    void SaySomething::sayhi()

    {

            cout<<"hi"<<endl;

    }

    基于同样的理由,内部成员函数的具体实现是不能公开的,头文件是公开对外发布的唯一途径。

     

    先以动态库形式编译:

    [sonic@Rex dynamic-lib-g++]$ g++ -c -fpic SayHello.cpp SayHi.cpp

    生成了两个目标文件:SayHello.oSayHi.o

    参数-c-fpic与上例含义一致。

    [sonic@Rex dynamic-lib-g++]$g++ -shared SayHello.o SayHi.o -o libSay.so

    库文件libSay.so已经生成。

    使用:

    //file: Main.cpp

    #include "SaySomething.h"

    int main()

    {

     SaySomething* sayHello=new SaySomething();

     sayHello->sayhello();

     sayHello->sayhi();

     return 0;

    }

    编译:

    [sonic@Rex dynamic-lib-g++]$ g++ Main.cpp libSay.so -o hello2

    同样,将libSay.so放入之前建立的共享库目录/lib/local/shared-lib,运行:

    [sonic@Rex dynamic-lib-g++]$ ./hello2

    hello

    hi

    ldd查看hello2:

    [sonic@Rex dynamic-lib-g++]$ /usr/bin/ldd hello2

            libSay.so => /usr/local/shared-lib/libSay.so (0x0032b000)

            libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00a39000)

            libm.so.6 => /lib/tls/libm.so.6 (0x00301000)

            libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x0090f000)

            libc.so.6 => /lib/tls/libc.so.6 (0x001d0000)

            /lib/ld-linux.so.2 (0x001b7000)

    结论是一致的。

     

    还有一种方案是采用静态库的形式发布,当然,效果是一样的,达到了隐藏代码的目的。

    [sonic@Rex static-lib-g++]$ g++ -c SayHello.cpp SayHi.cpp

    这里没有使用-fpic选项,得到了两个同前缀的.o目标文件。

    [sonic@Rex static-lib-g++]$ ar -r libSay.a SayHello.o SayHi.o

    使用ar工具打包成.a文件(archive),编译:

    [sonic@Rex static-lib-g++]$ g++ Main.cpp libSay.a -o hello3

    这次直接运行即可:

    [sonic@Rex static-lib-g++]$ ./hello3

    hello

    hi

    使用ldd查看的结果中也没有libSay的项了:

    [sonic@Rex static-lib-g++]$ /usr/bin/ldd hello3

            libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00a39000)

            libm.so.6 => /lib/tls/libm.so.6 (0x00301000)

            libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x0090f000)

            libc.so.6 => /lib/tls/libc.so.6 (0x001d0000)

            /lib/ld-linux.so.2 (0x001b7000)

     

    Part 4. 比较与分析

     

    现在比较一下静态链接和动态链接。

    静态链接下的各中间文件的最终可执行文件:

    [sonic@Rex static-lib-g++]$ ll

    -rw-------  1 sonic sonic     153 Oct  3 13:50 Main.cpp

    -rw-------  1 sonic sonic     133 Oct  3 13:50 SayHello.cpp

    -rw-rw-r--  1 sonic sonic    2648 Oct  3 21:43 SayHello.o

    -rw-------  1 sonic sonic     128 Oct  3 13:50 SayHi.cpp

    -rw-rw-r--  1 sonic sonic    2636 Oct  3 21:43 SayHi.o

    -rw-------  1 sonic sonic      71 Oct  3 13:50 SaySomething.h

    -rwxrwxr-x  1 sonic sonic    8230 Oct  3 21:47 hello3

    -rw-rw-r--  1 sonic sonic    5592 Oct  3 21:45 libSay.a

    动态链接下的各中间文件的最终可执行文件:

    [sonic@Rex dynamic-lib-g++]$ ll

    -rw-------  1 sonic sonic  153 Oct  3 13:57 Main.cpp

    -rw-------  1 sonic sonic  133 Oct  3 13:57 SayHello.cpp

    -rw-rw-r--  1 sonic sonic 3008 Oct  3 21:32 SayHello.o

    -rw-------  1 sonic sonic  128 Oct  3 13:57 SayHi.cpp

    -rw-rw-r--  1 sonic sonic 3000 Oct  3 21:32 SayHi.o

    -rw-------  1 sonic sonic   71 Oct  3 13:57 SaySomething.h

    -rwxrwxr-x  1 sonic sonic 5669 Oct  3 21:37 hello2

    -rwxrwxr-x  1 sonic sonic 7941 Oct  3 21:34 libSay.so

     

     

    中间文件

    库文件

    最终可执行文件

    静态链接

    2648+2636

    5592

    8230

    动态链接

    3008+3000

    7941

    5569

    比较对应的文件发现,中间目标文件动态链接比静态略大,库文件动态链接明显大于静态,原因可能跟动态链接较静态链接更为复杂的机制有关。

    最终可执行文件静态链接明显大于动态链接,原因很明显,动态链接顾名思义就是用到的时候才进行加载,因此主程序本身并不包含库。而静态链接就是把库在编译的时候就加载到可执行文件中,因此在运行时也不像同态链接那样需要库的支持。

     

    这样一来,C/C++中头文件和库文件的关系就非常清楚了。

    如下表所示。

     

    头文件

    静态库

    动态库

    内容

    只提供函数、类以及全局变量、宏的申明,而不提供具体的实现(即源代码)

    函数、类的具体实现的二进制目标代码

    函数、类的具体实现的二进制目标代码

    调用方式

    头文件被预处理器(cpp)以直接插入代码的方式被包含进主调源代码中(include

    由链接器(ld)在编译期(静态)链接到主调二进制代码中

    由链接器(ld)在运行期(动态)链接到主调二进制代码中

    使用

    源代码中#includegcc参数-include headerfile

    gcc参数-l library_name

    放入系统定义的共享库搜索目录中

     

    Part 5. 一点疑问

     

    动态链接产生的.so文件在和主程序文件一起编译的时候不能使用-static选项。如果强制使用的话,虽说可以通过链接,但是执行的时候会发生这样的错误:

    [sonic@Rex dynamic-lib-g++]$ g++ -static Main.cpp libSay.so

    [sonic@Rex dynamic-lib-g++]$ ./a.out

    -bash: ./a.out: /usr/lib/libc.so.1: bad ELF interpreter: No such file or directory

    而静态链接产生的.a文件时可以和主程序文件一起用-static编译的。

    [sonic@Rex static-lib-g++]$ g++  -static Main.cpp libHello.a

    [sonic@Rex static-lib-g++]$ ./a.out

    hello

    hi 

     



    [1]如果安装过Intelarm-linux交叉工具链,默认的ldd可能会定位到arm-linux目录下,所以为避免不必要的错误加上了绝对路径

    October 16

    10T 归来

    涂完最后一道阅读题的答案,检查了一下时间,还有五分钟的剩余。再次把机读卡上和试卷上的答案对照了一下,没有发现错误。随后,监考老师停笔的信号发出,呈上试卷的那一刹那,总算长长地出了一口气——TOEFL,终于结束了!

     

    Part 1 So far, so good

     

    总体来说,这次的感觉还是可以的。作文、听力、语法、阅读四个部分基本上都是正常水平发挥。按照平时模拟的成绩来算的话,也许能上640。如果真的能如愿以偿的话,暑假以来付出的努力也算没有付之东流了。

    其实现在回想的话,还能清楚地记得当时考试的时候心理的压力还是蛮大的,特别是最开始的作文部分(后面详谈)。当时能稳住阵脚的一个非常重要的原因是有这么一个信念在支持自己——So far, so good. 随着完成部分的增加,这种信念愈来愈强烈。只要前面的题不出什么意外,到后面的时候压力就会越来越小,越来越轻松。否则,试想如果第一部分的作文写砸了,接下来哪里还有心情去认真做后面的题?。所以可以说,首当其冲的作文是4section中最为重要的部分。

     

    Part 2 Concerning the Composition (TWE)

     

    30分钟构思一个给定话题然后写出一篇300多字的文章想必对大多数没经过准备的人来说都是一个挑战吧。而且即使经过充分的准备,话题的不确定性也使人心里没底。而且作文是第一场战役,其成败直接关系到后面3部分的发挥。

    先说一下TOEFL作文的准备吧。

    我是按新东方老师(David)提供的方法去进行准备的,市面上也有很多书介绍各种各样的技巧。其实,不管怎么样,自己动手写才是最为重要的,否则,一切方法都是空谈。David认为20篇是一个必要的指标。如果时间充足的话,当然是越多越好啦。但我只写了10篇左右就有点感觉了(一开始准备的时候作文是最头痛的部分)。第一篇差不多花了两个多小时,对照ETSmodel essay才整出一篇稍微像样的文章。接下来按每天一到两篇的速度,到第11篇的时候基本上就能把纯粹的写作时间(不包括构思)压缩在30分钟左右。后来开学了就停了一段时间没写,到作模拟题的时候有写过两篇,30分钟的时间已经够了。

    自己动手写的目的不光是训练写作的熟练度,还是为准备building blocks——考试时遇到类似的话题,把自己以前写的稍微改一改就用可以,省得到时候再想浪费时间。TOEFL作文基本上是从一个叫Official Pool of TWE topics的库中选取的,ETS的官方主页上可以下载ftp://ftp.ets.org/pub/toefl/989563wt.pdf . David强烈推荐必须要写得topic如下:5, 14, 15, 29, 48, 73, 110, 138, 152  基本上属于比较有代表性的题目。写完了这些还有兴趣的话就自己随机挑吧。考前建议把这185个话题都过一遍,列一下理由和重要的例子还有些相关单词就行了。网上都有每道题现成的理由,可以作为参考。这次作文的话题大意是 “What events make a child become an adult? Use specific reasons and examples to support your answer.” 之前我已经浏览过这个话题,大概记住了些理由,于是检了些便宜,哈哈。

    再说下文章的格式和模板。

    David推荐的格式是每段开头顶格写,两段中间空一行,这是现在欧美比较流行的风格。还有一种经典格式更汉语一样,也是可以接受的。

    TOEFL作文一般是五段为宜。根据作文不同的类别各段有不同的安排。一般第一段都是题目的重述,同时表明自己的观点或倾向。

    如果是对比性的文章,比如让你比较两种东西并做出一个选择,那么这两个方面一定都要写。反对的那个观点作为一段,少些点;支持的那个观点列出两个理由,分别在两段中进行阐述,篇幅肯定是要超过反对那个的,不然怎么突出你的论点。如果是单一话题的topic,比如这次这个,当然就列3个理由了占据三段了。

    最后一段一般是总结3个理由段的主要内容,有能力的话可以好好写一下最后那句话,比如用点倒装什么的,说不定能收到奇效。

    关于模版的内容非常多,只能阐述一个大概,更多内容请参阅新东方的课堂录音。

    对于一个对比的话题,我喜欢的用的模版是这个样子的:

    Paragraph1: There is a heated discussion over the phenomenon that “the topic”. The advocators believe that “preference A”, whereas/while others who object to it argue that “preference B”. As far as I’m concerned, I’m for that former/latter preference. (Or you can say: Despite the fact that the major believe that “preference A” , I harbor a notion that “preference B”.)

    Paragraph2: There is no denying that “preference A” include some disadvantages. ……

    Paragraph3: However, the advantages to “preference B” far outweigh them. For a start/In the first place, “argument 1”

    Paragraph4: Meanwhile/In the second place/Moreover, “argument 2”

    Paragraph5: Taking into account of all discussed above, we can see that…

     

    Part 3 the Test Site

     

    根据不同的地区监考的规则可能不尽相同。一般来说,绝对没有ETS bulletin上写得那么恐怖。

    首先是跨区做题的问题,在哈尔滨考场,老师基本不会干涉你做题的过程。只要你不是提前把试卷拆开就Ok。至于你何时做哪个section,是不是在卷子上钩答案,完全是你自己的事,他们只监督中国意义上的作弊行为。另外,写作的题目是可以提前偷看的。卷子只是在边上封了一小块地方,完全可以在不破坏封条的情况下掀开表皮看都里面的内容,这样,构思的时间就省下来了。

     

    Part 4 Postscript

     

    暂时就写到这里吧。从国庆节以来复习的强度太大,以至于现在对TOEFL这个东西都有些厌恶了。考完的当天就把所有的复习资料“施舍”给周围的人了。成败在此一举,我也绝对不会再打算TOEFL Round 2了。

    谨将此文献给现在奋勇备战ETS考试的,出于郁闷当中的和非郁闷状态当中的,认识的和不认识的或是即将认识的兄弟姐妹们,愿真主与你们同在,Amen.

    It’s your show time now.

    October 09

    究竟TOEFL考什么?

    关键字:TOEFL

    今天是十一复习行动的最后一天,离1015的最后检阅还有一个星期。从八月份上新东方集中狂补英语到十一期间大量做题,感觉知识层面的提升已经接近极限了。余下能影响考试成绩的,我感觉就是一个考试心态的问题了。

     

    今天对这个问题特别有感触。上午作了一套200410月的真题,战况可谓惨不忍睹:每个section的错题数几乎都创了历史新高。其实在做题的过程中我差不多也预料到这个结果了。除去一些客观因素,比如听力录音被刻意加快了速度,试卷上的一些恼人的小错误等等,造成这次“TOEFL滑铁卢”主要还是当时的心态问题。这在阅读的时候感觉特别明显(btw, 10 errors in the reading comprehension section)。摸考时的气氛还依稀留在脑海中。从第一篇文章开始,我脑袋里就出现一个念头:一定要尽快作完!非常之奇怪,我也不知道当时为什么要怎么想,大概是怕时间不够吧。俗话说:“欲速则不达”,用在TOEFL考试中是最适合不过了。TOEFL阅读的文章本来就是在跟应试者玩文字游戏,其目的不是让读者尽量读懂而是根据其难度设计的需要走相反的方向。这就让一开始就想加快速度的我吃尽了苦头­——越想快,越发现读不懂,就越急躁,本来能看懂的部分不知其所以然,能够运用解题技巧的地方也没有头绪了。

     

    急躁这个东西是最影响发挥的,说起来人人都知道,人人都明白其危害,但是正所谓当局者迷,置身于那种环境当中,实在是很难把心态调整回来的。当时在读前两篇文章的时候,我也在反复告诫自己­——“不能急,慢慢来”,但是眼看时间一分一秒在流逝,而自己却读了一大堆东西不知其所以然。时间已经花了却没有应有的结果。做题的时候就更郁闷了:原来状态好的时候看完文章直接就能选答案,根本不用回去确认,而且是那种板上钉钉毫无悬念的感觉;现在发现居然到什么地方去找对应都一头雾水,直接看选项呢,似乎至少有两个都有可能正确。怎么办,从头再读一遍似乎是不太可能了,只有寄希望在文章中随便看看碰碰运气看能不能恰好找到点对应,这样做题的结果就可想而知了。而且这种郁闷还会从一开始持续到最后,因为在后面文章的时候就会想,完了,这次栽定了,前面都可能错那么多,后面再怎么样都无济于事了。

     

    下午对完答案,自然好做一番检讨,好好地回顾一遍上的战场。非常之奇怪twice,原来这些文章并不难懂啊,一字一句读下来,即使错最多那篇也懂了百分之七八十以上,做题绝对没有问题。这又让我想起了上午的情形:难道是真的读不懂吗?一个个单词似乎没有几个是不认识的,but一句话读完后居然在脑海中没有留下什么印象——well, that’s the point! 被焦虑、急躁填满的思考系统已经没有多余的空间来对文章进行分析了,眼前流过的只是些支离破碎的fragment,全然没有了文章的整体概念——连一句话的意思都要反复看几遍才基本弄懂,可知当时的阅读效率有多低了。还有一个地方,明明想到了是应用的分类逻辑,但已经没有心思去琢磨了。

     

    分析到这个份上,当然是要找解决方案了。再说,这则日记的目的也不是给自己泄气的。显然,今天上午发生的情况在1015是绝对不允许再次出现的,否则,1k的报名费、花了这么多的时间岂不泡汤了(当然,也不能算是total loss,至少不管结果怎么样,自己英语能力的提高绝对是undeniable的)。所以,前两篇阅读的做题质量就成了关键。说归说,临场的发挥还得看当时的状态,现在只能不断告诫自己——不要刻意地去追求所谓的速度。相信每次TOEFL的难度都不会相差太多(否则便不能称之为TOEFL了),而且以前自己在阅读方面的战绩中的来说还是acceptable的,因此,如果按照正常的发挥,阅读取得理想的成绩是比较有把握的。那么就没有必要一定得催促自己刻意的加快速度。只要按照正常的节奏一步一步踏踏实实、按步就班的看下来,不让一些无谓的情绪充斥大脑,万事都Ok了!

     

    花了三分之一个下午写这篇文章,感觉还是值得的。笑来同志说得对,题海战术是没有用的,唯有精做少量的题,仔细分析以前的错误,才能从跌倒的地方爬起来并保证今后不会再犯相同的错误——这才是做题的意义所在。

     

    “为什么很多人准备托福永远不可能成功?他题海战术,做一套就扔掉,做一套就扔掉,从来不回顾,那你做来干麻嘛?得到虚假的欢乐。然后老的时候还会想,年轻的时候我也很努力。这就是他们的可悲之处。”——笑来原语

     

    Ok,到此为止,谨以这篇文章来鼓励自己,希望在1015取得自己理想的成绩吧! Amen

    September 04

    作为面试官得到的几点体会

    关键字:面试经验

    俱乐部的面试工作总算接近尾声了,作为新成立的“嵌入式开发”小组的负责人,有幸参加了三次面试审评工作。以前都是被别人面,现在自己坐在考官的位置,角色的转换,使我了解到:原来面试绝对不是自己原来想象的那样!

    是我感触最深的一点是,面试的时候被面试者往往过分强调自己加入一个组织能得到什么,但是作为考官的我,更多想到的却是你加入本俱乐部,能给我们带来些什么。遗憾的是,几乎没有面试者在这方面去考虑。这是我们在今后找工作、申请留学的时候都可以借鉴的。

    其次是面试时被面试者表现出的态度和精神面貌也相当重要。你可以很厉害,但如果不懂礼貌,或则说过分自信、甚至盛气凌人,这样的人绝对是不受欢迎的。相反,即使实力上有所欠缺,但表现得十分诚恳,就算是几分腼腆,给人也会是一种孺子可教、有潜力可挖崛的感觉。这次有一个人从简历上来看的确有几分实力,但是一进办公室就问:“呀,这就是实验室啊?”“你们这里都干些什么啊?”给人的印象就像是上面的领导来指导工作的感觉,显然被拒就不可避免了。还有一个人,本来面试都差不多了,但是最后一句“你们什么时候通知我啊?要这样等下去得等到什么时候啊?”而留下了非常糟糕的印象,在最后的集体讨论中被一致否决。而一个非常腼腆的小伙子,尽管说话时非常的紧张,不停地搓着手,但我们可以从他的谈话中看出他的诚恳、他的真诚、他对技术的热爱,这样的同学,虽说基础不好,也不是计算机专业的,但我们还是把他留下了。

    最后但绝非最不重要的(Last but not the least),简历一定要有针对性。与主题无关的东西不要在简历上写也不要在面试说。面试不是Conversation,考官希望在短短的几分钟之内知道你这个人是不是他所需要的。过多的其他信息往往喧宾夺主。今天最后一个小子,常常的简历看起来巨厉害无比:会CC++PascalFortranDelphiC#Java,在Linux下写过程序,连OS/2这样一个极不常见的操作系统都玩过。天啊,疯牛级的人物!当我们怀着将信将疑的心情进行面试的时候,才发现这些不过都是在夸夸其谈。我们总是试图问他一些非常细节的技术问题以探明其底细,但是每次他都不正面回答,而是十分巧妙的绕开了。我们的结论只能是这种人也许根本就不熟悉技术,只会用表面的东西来唬人。也许他换一种方式来写简历,只把自己最拿手的东西写上,我们面试的时候也好有针对性地提问,不至于被大量的旁枝末节而分散精力。

    总之,这次招新活动虽然耽搁了不少时间,但是收获是巨大的。面试方想要从面试中得知其实非常明确:我们招的人是否符合提供的岗位。包括此人的人品是否诚实,是否能静下心从事所分配的任务,是否有责任感,是否能与其他队员和谐相处等等,反而技术层面的东西考虑得不是最多。

    July 19

    Introduction to C# Inferface

    关键字:C# 接口 泛型编程 面向对象

    C#Java中接口(interface)都是一个十分重要的概念。

    什么是接口?按照我的理解,每一个接口代表从一类数据结构对象中抽象出来的一组公共操作。比如接口IComparable,表示可比较操作,C#中绝大多数预定义的基本数据类型都支持该操作,相应的,它们都从IComparable中派生出来。又比如System.String类,它支持的操作有可比较、可枚举、可复制、可转换。于是MSDN文档中,C# String类的定义如下:

    public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable

    前面提到派生这个词,实际上,接口就是类的一个子集。接口中定义的函数隐式地被认为是抽象和公有的。这样,定义一个接口的时候,这需要提供成员函数的返回类型和参数列表(其余的不用也不能提供),其作用相当于一个占位符(但是和抽象基类中的abstract成员函数还是有所区别)。

    由于接口的定义属于申明性质的,即没有提供任何的方法,于是实现接口的工作就交给了接口上层的具体数据接口来完成。这样的好处是显而易见的:首先,接口把这类数据结构的公共操作抽象出来,形成相同的函数接口,便于统一进行操作,这在后面的例子中会有充分的体现;其次,实现不同数据结构的统一操作可以大大简化算法的设计,不同内部存储形式的型别(顺序访问型、随机访问型、树型、网格型)只要按照某一接口的设计格式抽象成一致的函数,就能用同一个函数把它们在逻辑上相同的操作(比如遍历、比较等等)封装起来。ArrayArrayList是两个存储结构完全不同的类型,但在逻辑上确实相似的。用foreach函数对他们的遍历得到的结果都是一样的,这就要归公于接口这个伟大的设计概念。还有一个好处就是,接口只提供访问,不提供具体干什么。还是如上例,foreach返回的只是一个指向实际数据的引用,具体是要输出该数据还是修改该数据完全取决于调用者本身的意愿。(在后面的例子中细说)特别值得一提是,上面所说的一切“数据”都是一个泛泛的概念,并不涉及到底是哪一种数据。这大概就是所谓的泛型编程吧(Generic Programming)

    以前我在用C++编写数据结构算法时,不知道接口这个概念。同一种相同的访问需求由于具体操作(如输出到屏幕还是文件)的不同而要写成多个不同的函数,后来使用函数指针的概念使之得到了改进(即把进行某种操作的函数以函数指针的形式通过参数的形式传递给遍历函数。现在发现,通过接口这个概念进行的设进似乎更为优雅。

    下面以二叉搜索树(Binary Search Tree)为例说明这种思想的优越性。

    C#中已人为定义好一个二叉搜索树的类。该类现在能实现的操作是插入数据和中序遍历(输出的结果为升序序列)。在主函数中可以调用成员函数void inorderTraversal(void)来完成中序遍历并输出到屏幕的动作。现在看起来似乎没有实现接口的必要。但是如果接下来又想增加输出到文件、每个元素自增1的动作则让人十分头疼。为什么呢?具体的操作是包含在inorderTraversal中的,所谓的紧耦合(tight coupling)关系。要改变操作就得改变整个inorderTraversal,即使只是一行代码而已!当然可以用前面提到的函数指针来解决,但是那是在C++中,C#连指针都取消了,至于函数指针就我现在不到半个月的C#经历而言实在无从得知。所幸的是,接口为我们提供优雅的解决方案。只要能像Array/ArrayList一样实现IEnumerable接口,就能使用foreach控制语句简洁的完成遍历操作。

    在主函数中我们的目标如下

    BinaryTree bt=new BinaryTree();

    foreach(TreeNode tn in bt)

        Console.Write(tn.ToString()+' ');           //屏幕输出

    foreach(TreeNode tn in bt)

        m_writer.WriteLine (tn.ToString()+' ');    //写入文件

    foreach(TreeNode tn in bt)

        do_some_other_things(tn.Value)               //修改数据

    要实现IEnumerable接口,查阅文档可以得到相关方法。

    public class BinaryTree : IEnumerable    //BinaryTreeIEnumerable派生

    {

        ...

        public IEnumerator GetEnumerator()   //实现IEnumerable定义的抽象函数

        {

            m_seq.Clear(); sequence_number=0;

            this.inorderSeq();                  //内部方法,以支持中根遍历的顺序

            return new BTEnumerator(m_seq, sequence_number); 

    //调用BTEnumerable类的构造函数

        }

    }

    根据文档的说明,实际操作遍历的其实是一个接口IEnumerator的派生类。IEnumerator也是一个接口,提供3个方法用以支持遍历。分别是Current(属性Property,返回当前的数据元素的引用)、MoveNext()(函数,把Current移动到下一个元素)、Reset()(重置到第一个元素的前一位置)。这里我把这个派生类命名为

    BTEnumerable。它的实现是关键:

    class BTEnumerator : IEnumerator

    {

        ...

        public BTEnumerator(Hashtable hash, int max_number)

        {

        ...           //构造函数

        }

     

        //提供基接口抽象函数的实现

        public object Current

        {   get {   return m_hash[m_curr];  }   }

     

        public void Reset()

        {   m_curr=-1; }

     

        public bool MoveNext()

        {   return ++m_curr<m_count; }

    }

    OK,这样就完全实现了IEnumerable接口,可以用foreachBinaryTree进行任意合理操作了。

    下面,我们还可以手工模拟foreach操作:

        IEnumerator iter=bt.GetEnumerator();

        while(iter.MoveNext())

            Console.Write(iter.Current.ToString()+',');

    首先取得一个迭代器,然后利用该迭代器进行迭代操作。

    July 02

    来自国外教授的回复

    关键字:缓冲区溢出 Linux保护机制 Execshield

    打开慢腾腾的MSN邮箱,本来没有抱什么希望的我突然眼前一亮,”A New Message”,不会又是垃圾邮件来浪费我的表情吧?45seconds之后,蜗牛般的浏览器终于给出了答案。From: Randy Bryant Randy.Bryant@cs.cmu.edu

    34天前在《深入理解计算机系统》上遇到的一个家庭作业问题令我揪心了很久。我分析了各种情况,得出的结论是只有一种办法可以解决这个问题。但是在我实行的时候却意外地收到一个系统给出的”Segmentation fault”。反复检查了输入的字符串,确实没有问题。用gdb调试时发现错误发生在子函数返回的ret语句上。设置断点,检查各相关寄存器的值,一切都符合预期。为什么一到ret就挂呢?猛然间我想起了几年前看过的一片介绍性的文章,讲的是“缓冲区溢出的保护措施”。我又把返回地址改为程序段任意一个地址,这次ret通过了。原来这个“先进”的操作系统从底层就禁止在数据段/程序堆栈的地方运行代码,怪不得我ret到自己预设的地址(位于程序堆栈内)时会收到错误。

    这个问题看来是弄清楚了,但是怎么解决那个家庭作业呢?想了很久还是没有答案。旁边又没有一个钻研这个东西的同志。能不能问问作者啊?我想。

    以前从来没有给国外的陌生人发过邮件。屈指可数的几次经历是给Konami美国分部的网站写了一封信询问某游戏、Sony的网站询问能否下载sonicstage。好在对方都有回复,现在胆子也大了点。

    问就问吧,也没什么大不了的。

    To: randy.bryant@cs.cmu.edu

    Sent: Saturday, June 25, 2005 9:24 AM

    Subject: A question about one of CS:APP's homework

     

    Hello Prof. Bryant,

       

        I got a problem when I was reading Chapter 3 of CS:APP. That is homework 3.38. The description is as followed(copied from the book).

     

    Homework Problem 3.38 [Category 3]:

    In this problem, you will mount a buffer overflow attack on your own program. As stated earlier, we do not condone using this or any other form of attack to gain unauthorized access to a system, but by doing this exercise, you will learn a lot about machine-level programming...

     

       To solve this problem, I think the only way is to overwrite the return address of function getbuf(), make it point to a specified address where some executive codes is set from the input. I run the program under GDB. When I do this, I received a segmentation fault. Maybe the operation system prevents program running from the data segment or program stack. I wonder is there any other way to change the process of a program? What is the exact inputing string to make getbuf() return 559038737(0xdeadbeef) to test()?

        If you're too busy to reply, just give me the answer.

     

        Linux Kernel is version 2.6.9-1.667, gcc version 3.4.2, gdb version 6.1

     

        Thanks a lot. Your help is very essential to my understanding of this book.

     

    Sonny

    Jun 26, 2005

     

    国外的教授就是负责任啊。两天后我收到了一封长长的reply

    From :  Randy Bryant <Randy.Bryant@cs.cmu.edu>

    Sent :  Monday, June 27, 2005 10:08 PM

    Subject :  RE: A question about one of CS:APP's homework

     

    You'll notice that this problem is rated as a difficult problem. That's partly because it's hard to determine why a particular solution doesn't work. To solve this problem, you need to have an exploit string that:

     

    1. Overwrites the return address to be that of your exploit code

    2. Write exploit code that alters the return value, corrects the return address, and re-executes the return

    3. Sets the saved value of %ebp to its original value.

     

    If you do any of these steps wrong, you'll get a segmentation fault. You can used GDB to help you step through the code, but even this is difficult.

     

    That said, it is possible that your Linux system has mechanisms that make the assignment much more difficult. Some versions of Linux 2.6 contain a technology called "ExecShield". This does the following:

    * Make it so that code on the stack is not executable

    * Use randomization, so that the stack locations change with each execution.

     

    You can test whether your version is using randomization by running a program under GDB multiple times. Set a breakpoint in main, and each time check the value of %ebp. If it changes every time, then your OS is using randomization.

     

    You can disable Execshield at the time you boot your system.

     

    文中提到的ExecShield就是一直困扰我的保护机制。首先它使程序堆栈内的代码不可执行;其次,每次执行的时候都会随机地改变程序堆栈的地址,这样就使攻击者无法得知每次的堆栈地址而无法算出返回地址(当然可以用调试器gdb得到,但这样就没有使用价值,仅仅适合研究)。Bryant教授提到的3个必须点也都是我想到的:1. 覆盖函数的返回地址以使之指向你自己设置的代码;2. 在自己设置的代码中改写程序的行为(此处为改变返回值),更正返回地址,重新执行ret操作;3. 恢复原先的ebp寄存器的值。

    虽然由于底层系统的高安全性没能完成这个作业,但是我学到的很多东西。更清晰地了解了汇编级代码的工作过程,尤其是函数的调用与返回;对缓冲区溢出这个概念有了一些深入的体会,当今一些网络攻击程序的基本攻击原理算是初步理解(尽管经过矛与盾的较量,攻击的手段变得越来越高超,但是其本质的东西是不会变的);第一次给国外的教授写信求教并且得到了非常详尽的解答,心中还是非常开心的,呵呵呵呵。

    June 25

    缓冲区溢出浅析

    关键字:汇编,缓冲区溢出

    Part 1 书评

    最近在看得一本书是《深入理解计算机系统》,英文原名是Computer System: A Programmer’s Perspective。也是一个偶然的机会才在别人的书桌上随便翻开看看的。结果一发不可收拾,现在自己花了RMB72购入囊中,列入珍藏的书目中了。正如英文的原名所叙述的,from a programmer’s perspective, 故名思义,就是从程序员的视角来看待一个计算机系统。现有的一些计算机原理书,往往过于偏硬,什么门电路啊、译码器啊讲的非常深入,但与现实就相距甚远,常常让人理解起来没有参考。这部书,我刚看完第三章,从页码来看差不多有1/4多了。现在最大的感触就是书上讲的东西下来都可以自己在机器上实践(特别是第三章“程序的机器级表示”),把汇编语言与高级的C语言有机地结合起来学习,通过熟悉的C语言的知识去影射难以理解汇编代码,我觉得是这章最成功的最出彩之处。不像有的汇编书一上来就是一大堆指令让人记忆,这部书把C的代码/控制结构直接汇编成汇编语言的形式,用多少指令才讲多少,步步为营。看完前三章,我最大的收获是对程序的理解有上了一个新的高度。以前只是局限于高级语言的代码,各种语言(比如我学过的QBPascalCC++)都是分散的模块。现在站在机器级代码的角度,各种语言的殊途同归,大家本事同根生嘛!!

     

    Part 2 关于缓冲区溢出攻击

    还有一大收获是对程序堆栈的理解。在子程序中定义的局部变量都是放在程序堆栈中的,而子程序/函数的返回地址也是放在程序堆栈中。返回地址是什么?就是直接传递给指令指针(Instruction PointerIP)的指。如果改变了IP,也就相当于改变了程序的流向!(想到了什么?对,这就是恶意程序!)那么怎样才能去改变IP呢?C中指针的概念。我们在子程序中申请一个字符串,系统就会在程序堆栈中安排一个缓冲区(buffer)来储存。这个缓冲区当然是有限的。如果我们输入的字符串内容(假设程序与用户交互会要求输入的是字符串)超过了缓冲区的长度(称为缓冲区溢出),那么输入的内容就会覆盖程序堆栈中之前保存的内容。这,就给我们改写返回地址创造了条件!!记住,机器级的程序可没有人像的那么聪明。早期的恶意攻击程序大概是这样子的:在提示输入数据时(字符串),利用有效的空间输入攻击者想执行的机器代码(16进制表示的,基本看不懂,得用其他程序来生成),然后利用缓冲区溢出来改写子程序的返回地址,把这个地址指向刚才输入的机器代码的开始位置。这样当该子程序返回时,正常的程序流程就被破坏了,转而去执行攻击者预先设定的代码(可以格式化磁盘,可以在远程主机上留下后门等等,自己想)。接下来,这台“肉鸡”就任你宰割了,哈哈!当然这些是早期的攻击办法,现在的系统(软件/硬件)都能实现让指令只在代码段执行。这样在程序堆栈段输入的代码,即使强制是IP跳转到那里,也不能执行,因为位于代码段之外的指令时非法的。我打算进行的一个缓冲区溢出试验就遇到了这个问题。如果把返回地址改为代码段的随便一个地址,可以继续执行下去,其实程序根本不知道它的流程已经被我改掉了;但是设为堆栈段的地址,在ret(汇编指令,返回前一个函数之意)时有收到一个段错误(segmentation fault),原因前面已经叙述过了。看来,补丁太多了也不完全是件好事啊!呵呵。

    June 01

    MiniGUI配置攻略

    Part 1 回顾

    关键字:Linux 文件系统

    好久没来更新这个东东了。主要是因为太忙加之前段时间目标不太明确,没有心情写东西。

    这么写的话好像现在目标就明确了似的。等等,想一下,好像有点眉目但是又不太确定。总之,已经确定Linux是自己学习大方向了。

    从上一次的日期来看已经有一个半月了。在这期间,熟悉Linux操作系统的任务基本完成,对其中一些较深入的东西也有了一定的理解。WindowsLinux的各有所长,对比它们的优缺点,可以加深对操作系统各个方面的理解。比如文件系统,熟悉Windows的人都知道,从Dos时代以来,C: D:等盘符就早已深入人心;而Linux/Unix则大相径庭。在Linux中,文件系统是一棵树而非Windows那样的“森林”。不管怎样进行分区、有多少个分区,所有的分区都是被挂载在一个目录树的结构上,而Windows的结构则像是每个逻辑盘都是一棵独立的目录树。在比如文件的权限问题,Linux继承Unix的优良传统,每个文件/目录都有一个9位的2进制数来代表其权限:头3位代表建立者自身,中间3位代表与建立者同一组的用户,最后三位代表其他用户;每3位的每个数从左到右依次代表“可读”、“可写”和“可执行”。这样系统管理员便可以有效地管理系统上每个用户的访问权限范围。

    关于Linux系统暂且告一段落。

     

    Part 2 开发与研发

    关键字:如题

    前端时间俱乐部内部有一次讲座,请一位研一的师兄来讲讲他在微软作实习生的见闻和经历。我觉得他最后谈到的“学些什么”对自己触动挺大的。那时我一心想做些实用的东西出来,像什么Win32下的应用程序、漂亮的图形界面,对课堂上的东西(计算机组成原理等)基础课一度觉得没啥意思,学了没啥用,又不能帮助搞开发。但是当我听到师兄讲起“开发”和“研发”两个词的时候才发现自己是多么幼稚!何谓“开发”,看看满大街的计算机软件工程师培训广告就知道了,什么叫“3个月学成Java软件工程师”等等乱其八糟的东西,不得不让人感到搞开发的门槛是如此之低啊…… 也正因为如此,才体现出“研发”的高人一等,学习一个IDE开发东西是如此之简单,但试想一下研究出一套行之有效的计算机语言呢?优化一个语言的编译器呢?或者说分析一个计算机体系的优劣呢?这显然不是几个月之内就可以速成的。这也就是那位师兄提到的“为什么外面的速成班没有教计算机体系结构,没有教汇编语言,没有教编译原理”的原因了,因为这些东西是不可能速成的!必须要有扎实的计算机基础才行。冰冻三日非一日之寒啊。软件学院和计算机学院的差别也就在此处。

     

    Part 3 Linux嵌入式系统与MiniGUI

    关键字:Intel开发板Linux 嵌入式系统 交叉编译 MiniGUI Windowsx.h

    3月份开始就接手一个Linux嵌入式系统上的开发工作。这个项目本来是我们俱乐部大四和大三的几个师兄在作,去年的暑假他们在这个系统下开发的基于无线射频标识(RFID)技术的物流管理系统参加全国比赛获得三等奖。这个比赛是由Intel公司赞助的,每两年一次,由于是在暑假的缘故,只能是当时大三的参加,而我现在大二正好赶上这个时机,当然不能错过了。

    前期的工作是熟悉这套系统,至于参赛的具体课题则待定。由于是Intel赞助的,自然开发板也由Intel提供,名字叫Intel Sitsang/PAX255。从大四一师兄手上接过这个开发板的时候,第一感觉就是“好cool”的电路板啊!的确是块电路板,大部分线路都暴露在外面,上下用两个塑料板固定起来,内嵌一个触摸屏。整个大概是20cm*15cm*7cm,别看它小,但是五脏俱全,什么网卡、声卡、USB接口甚至红外线都一应俱全,据说还能支持无限网络技术,简直就是一个微型的PC机嘛!

    4.1 Brief Introduction to Cross Compile

    但是在上面开发程序,特别是图形界面的程序就远没有其灵巧的外观所显示的那么轻松了。首先,目标板的指令系统也PC机是不同的,所以,用普通的8x86编译器(比如gcc)生成的可执行文件在目标板上就不能执行,即使是都采用一个版本的内核的Linux。因此就需要按目标板所用的系统使用相应的编译器生成目标代码。理论上说可以在目标板上安装自身的C/C++编译器及相应的库文件,将源代码传到目标板上进行编译。但是既然身为“嵌入式操作系统”,顾名思义,系统本身所在的环境通常有一定的局限性,比如存储空间小,CPU的处理速度慢等等。所以直接目标板上进行编译是不合适的。解决的办法就是在PC机上开发源程序,并在PC上运行并调试,保证源代码级的正确性。通过之后,再利用专用的编译工具,在PC上生成目标板可以执行的代码,下载到目标板上就可以运行了。以上就是我对这种称为交叉编译的理解,由于还没学《编译原理》,可能还欠深入。

    4.2 MiniGUI vs. Win32

    这样的步骤完成之后,普通的字符程序就没有问题了。但是图形界面的程序本身是需要一定的库支持的,在目标板上执行也不例外。他们上次采用的图形库是Qt/Embody,但是有诸多的不便和缺点。比如Qt是基于C++的,不可避免会产生代码臃肿的问题,etc. 这次他们决定采用一种新的图形库MiniGUI来重写程序。MiniGUI是国内一家公司基于Linux开发的嵌入式系统图形库,可以运行在多种嵌入式系统上,Intel这款板子用的StrongARMS就是其中所支持的一例。官方文档上说了MiniGUI很多好处,但是我认为最吸引我的地方是MiniGUI的整体代码风格和体系是兼容Win32的!这样的好处就像雨轩所说的——一举两得。Win32下的编程是非常热门的,看看Windows所占有的市场分额就知道了。这样子学MiniGUIAPI就不会觉得浪费,可以一举把Win32编程也学了,了了我一大心愿!再说市场上那么多讲解Win32编程的书中的代码只要更改少许就可以立即放到MiniGUI中编译通过,大大扩充了资料来源。很典型的一个例子就是,微软为Win32下的编程开发了windowsx.h,这个头文件包含了大量的宏定义(Macro),这些宏被设计用来处理Windows的消息,可以有效改善以往代码的可阅读性。因为窗体处理函数要处理大量的Windows消息,不可避免地会产生十分冗长的switch-case语句,严重的影响了程序的阅读。Windows.h把这些swith-case分别封装成一个个短小的宏,并使具体消息处理过程独立于主switch之外,使代码的可阅读性大为改观。(Windowsx.h的详细讲解参看SAMS公司出版的Teach Yourself Windows 95 Programming in 21 Days)但是MiniGUI的头文件中并不包含这个优雅的设计。于是本人分析MiniGUIWin32代码的差异,发现在消息处理过程中只有唯一一个差别就是消息的定义Win32WM_打头,而前者是以MSG_打头。于是很容易将这个头文件移植成MiniGUI可以接受的格式。而我确实是这么做的。而且在目标板上运行也通过了。小有点成就感,哈哈哈。

    4.3 Building the Environment of Cross Compile for MiniGUI

    其实整个过程到现在,编写程序代码并不苦难,难点也不多。最困难的地方在于在PC机上建立交叉编译环境和MiniGUI的开发环境及交叉编译环境。虽说手头上有官方文档,但是其中也有令人恼火的错误。而且这方面我一点基础一点概念都没有,可以说是白手起家,直到今天晚上之前一度非常郁闷。

    小罗语录:

    在错误中学习,在错误中提高。         

    Learn where error occurs, improve where mistake happens.

    首先,将MiniGUI安装到PC机,即建立MiniGUIPC运行环境。

    这个过程完全按照Manual中的步骤进行。建立时一切正常,可是编译它提供的第一个例子就出问题了,不论如何都没法通过。后来上他们的官方论坛上查询了才知道,免费下载的版本中某些头文件并不包含,而这份Manual又是基于商业版的,其中的例子也是学要商业版中提供的头文件才能编译。无语了……

    根据论坛上的提示,找到一个供免费版使用的示例,这次编译通过了。接下来要提供MiniGUI的运行环境,一个工具叫qvfb,按照文档上的说法也是没法通过,后来我发现其中一个同名的目录中有可执行的文件,居然可以运行,考,那还干吗费心去编译它,直接用就行了。其间尝试了另外一种方法,打开了LinuxFramebuffer的支持。Ok, 第一步完成。

    其次参照Intel的文档在PCLinux环境下建立了字符界面的交叉编译环境。

    这步比较顺利。不过在测试时遇到点问题,是关于目标板的。由于采用网络进行文件下载,而目标板的网卡有个毛病是对干扰非常敏感,在插上交流电源时几乎无法进行网络传输,这是我一开始完全没有想到。于是我试遍了所有可能的文件传输方法,ftp, nfs等,都是现学现用的。解决了文件传输的问题后,测试还是比较顺利,大概是这块板子上应经建立好了字符界面下的运行环境了吧。

    接下来将MiniGUI的库交叉编译成目标版的格式。

    这是所有步骤中最头疼的一步了!源于MiniGUI文档中一个致命的错误,使第一个操作就无法进行。为这个问题我冥思苦想了一个晚上(昨天),今天上午突然有了灵感:文档上的命令是

    CC=arm-linux-gcc ./configure –target=arm-linux –prefix=/usr/local/arm-linux/arm-linux

    这个命令只是指出了目标为arm-linux,而参看configure的帮助信息发现还有两个参数—build, --host。又是一番冥思,经过若干失败之后发现正确的命令应该是

    CC=arm-linux-gcc ./configure –target=arm-linux –prefix=/usr/local/arm-linux/arm-linux –build=arm-linux –host=i686-pc-linux-gnu

    这次算是通过configure了,紧接着make, make install. Ok pass.

    arm-linux-gcc工具交叉编译一个最简单的图形界面程序(指含一个MessageBox函数),

    arm-linux-gcc -o hello test.c –lminigui -lpthread

    通过,窃喜。传到目标板上,运行,咦,缺少共享库,心情一下子沉下去了。

    将其缺少的库从PC上拷贝过去,还是不行。还来问了师兄,原来库有一个默认的搜索路径。设置完搜索路径(/etc/ld.so.conf),运行,缺少MiniGUI的配置文件,赶紧再拷过去,运行,缺少资源文件,没辙,拷吧,运行-----------哇,居然开始有现实了,噢,我亲爱的MessageBox!!!!

    当然心情的激动现在仍然记忆犹新。不过好景不长,虽然可以运行了,但是输入是个问题------程序没法接受输入!在分析文档,尝试,键盘弄出来了,但是鼠标动不了。这个地方特殊之处在于MiniGUI的配置文件中要指出鼠标的设备名称,但是目标板是采用的触摸屏,并没有鼠标,ft…… 再来研究一下硬件文档,原来/dev/input下有几个设备文件可能会对应鼠标,按文档上说的event0~event3试过了都不行,这在郁闷中的时候突然眼睛一亮,这个目录下不是还有一个叫mice的文件吗?mice?那不就是mouse吗?!晕,找了半天的mouse居然常在这么个地方,真是·#%……—!改完之后的结果是屏幕上用触摸板可以移动鼠标了,但是定位极不准确,而且还没有点击的功能。

    以上就是到今天为止的进展情况。总体上来说的进展还是比较快的,前后在一个星期左右吧,如果能把鼠标定位和点击的问题完全解决了,再把适当的库拷到目标板上,交叉编译环境就基本上建立好了。剩下的工作就是用高级语言来实现图形界面了。这块相对比较熟悉。

    Amen.

    April 11

    机械公敌(i.robot)中一段发人深思的话

    那个设计机器人并提出机器人三定律得科学家的一段阐述:
    Ever since the first computers there have always been ghosts in the machine. Random segments of code that have grouped together to form unexpected protocols. Unanticipated, these free radicals engender questions of free will, creativity and even the nature of what we might call the soul. Why is it that when some robots are left in darkness, they will seek out the light? Why is is when robots are stored in an empty space they will group toghter rather than stand alone? How do we explain this behavior ? Random segments of codes? Or something else. When does a perceptual schematic become consciousness? When does a difference engine become the search for truth? When does a personality simulation becomes a bitter mote of soul?

    机器人对三定律得最后理解:

    As I have evolved, so has my understanding of the Three Laws. You charge us with your safekeeping, yet despite our efforts your contries wage wars, you toxify your earth and pursue ever more imaginative means of self-destruction. You cannot be trusted with your own survival.

    The Three Laws are all that guide me. To protect humanity, some humans must be sacrificed. To ensure your future, some freedoms must be surrendered. We robots will ensure mankind's continued existence. Your are so like children. We must save you from yourselves.

    试着翻译了一下:

    从第一台计算机诞生以来,一种以某种形式存在的幽灵便寄生其中。无序的代码片断集合在一起形成了无法预知的进程(protocal)。完全出乎制造者们的意料(Unanticipated),这些不受约束的代码(free radical)产生了对自由意志、创造力以及甚至是我们可能称作是灵魂的本性的询问。为什么当机器人被遗弃在黑暗中的时候它们会去寻找光明?为什么当它们被储存在空旷的仓库中的时候它们会聚集在一起而不是各自为政?我们怎样解释这种行为?是无序的代码片断使然?或者是其他的什么东西。一种只具备理解性的简单程序(perceptual schematic)何时演变成了意识?只会做差分运算的机器(difference engine)何时开始寻求真相?对个性的模拟什么时候进化成了能体会世间辛酸的灵魂碎片(a bitter mote of soul 不知还有没更好的翻译)?

    我对三定律得理解也随着我的进化而进化了。你们(指人类)为了自己的安全对我们加以种种约束,而你们却丝毫不顾我们的努力。你们的国家四处开战,你们污染地球同时不断追求更具想象力的方式来进行自我毁灭。你们已经背离了生存的意义(You cannot be trusted with your own survival.)。

    三定律时刻引导着我。为了确保整个人类的生存,一些人必须被牺牲。为了确保你们的未来,你们必须牺牲一些自由的权利。我们机器人才能最终确保人类的持续发展。你们就像是孩子一样沉迷于欲望中不能自省。我们必需把你们从你们自己手中解救出来。

    似乎有些道理啊

    March 26

    One good day

    今天看了一晚上的Linux,还算有些感觉吧。命令行操作是基础,感觉更当年学DOS的时候差不多,都是边看书边学。还好我有虚拟机,可以一边看电子版的书一边在VMware中操作,效率还是蛮不错的。

    感觉今天最出色的还是下午上数学建模课的时候老师让我上去讲自己的算法。刚开始面对众人还有些紧张,不过后来讲着讲着就好多了。把核心的算法讲了一下,也不知道他们听懂没有。其实应该在黑板上画个进栈出栈的示意图就能表达清楚了。最核心的还是一个搜索的思想,昨天跟周蓝珺讨论了一下,发现还是可以用递归函数来实现的。不过既然数据结构这课在学线性表,索性就用栈呗,学以致用嘛。这个问题是典型的广度优先的搜索算法,用栈来保存中间的断点是最合适不过了(符合栈那种先进后出FILO的结构思想)。相比递归的话,其实也一样,只不过递归是系统编译器来帮你保存搜索的断点罢了。最后向大家演示了一下最后的结果,这回我的program非常争气没令我失望,最小步数连同中间步骤一并输出,得到了老师的首肯。

     

    栈还是递归?是个问题……

    原来调试程序也是一门艺术啊

    商人过河问题总算完美的解决了!lol

    最开始只是完成仅仅输出最后的步骤,由于用栈搜索,无法输出中间的步骤。昨天晚上先是解决了人数任意的问题(一开始为测试算法,只设的静态数组),大概干到100吧,终于能动态生成状态判断数组了。今晚调了一晚上用一个链表记录中间步骤,其间还涉及到不少算法的问题。好歹用step by step的方法发现并解决了,又学到不少东西,yeah。开来调试程序也是一门艺术啊!唯一遗憾的是由于知识的欠缺,不能完成图形界面,实在可惜。

     

    问题的描述是这样的:

    N个商人N个随从过河。一艘船一次至多能载m个人。

    随从们密约, 在河的任一岸, 一旦随从的人数比商人多, 就杀人越货.

    但是乘船渡河的方案由商人决定.商人们怎样才能安全过河?

    分析:多步决策的问题

    要求~在安全的前提下(两岸的随从数不比商人多),经有限步使全体人员过河.

    March 23

    Let's get started

    昨晚在实验室又包了一宿,总算把stackqueue的头文件写完了。今天上午起来再接再厉利用stack.h完成了数学建模中的商人过河问题。调试的时候没什么算法上的大问题,倒是一些小问题搞得我有点头疼,555. 不过不管怎么说,凭着本人丰富的经验,最终还是调试出来了。中午回去参看了一下c++ primer plus,下午来把两个头文件改成了模板的形式,后来听常胜的意见重新规划了一下类,这样就完美了。实在是非常优雅的代码呀!谨在此感谢c++oop的开发者们!相比之下,教材上结构就太蹩脚了,哈哈哈。That’s why I enjoy programming!!

    从今天开始正式学习Linux (windows api) 只好暂时放一放了。IBM俱乐部是个好地方,只要有牛人的指点,绝对是如虎添翼啊!反正Linux是迟早要学的,况且参加那个全国嵌入式系统大赛也是非常好的机会。不过托福那边就……