<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<feed xmlns="http://www.w3.org/2005/Atom">

	<title>GNOME-CN Planet</title>
	<link rel="self" href="http://planet.gnome-cn.org/atom.xml"/>
	<link href="http://planet.gnome-cn.org/"/>
	<id>http://planet.gnome-cn.org/atom.xml</id>
	<updated>2008-12-19T02:49:08+00:00</updated>
	<generator uri="http://www.planetplanet.org/">Planet/1.0 +http://www.planetplanet.org</generator>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-并发(三)(上)</title>
		<link href="http://www.limodev.cn/blog/?p=455"/>
		<id>http://www.limodev.cn/blog/?p=455</id>
		<updated>2008-12-18T13:42:36+00:00</updated>
		<content type="html">&lt;p&gt;嵌套锁与装饰模式&lt;/p&gt;
&lt;p&gt;在生产者-消费者的练习中，当由双向链表的实现者负责加锁时，一般都会遇到莫名其妙的死锁问题。有的读者可能已经查出来了原因是嵌套的加锁。比如在dlist_insert中调用了dlist_length，进入dlist_insert时已经加了一次锁，再调用dlist_length时又加了一次锁，这时就出现了死锁问题。&lt;/p&gt;
&lt;p&gt;初学者遇到这个问题的时候，通常的做法是在调用dlist_length之前先解锁，调用完dlist_length后再重新加锁。这样是存在问题的：一个原子操作变成了几个原子操作，数据完整性得不到保证，在你重新加锁之前，其它线程可能利用这个空隙做了些别的事情。&lt;/p&gt;
&lt;p&gt;有效解决这个问题的办法有两个，其一是实现一个内部版本的dlist_length，它在里面不加锁。其二是使用嵌套锁，允许同一个线程多次加锁。pthread有嵌套锁的实现，不过我们在这里不用它，原因是我们要提供一个更通用的解决方案。现在我们不再满足于实现一个双向链表，而是要实现一个跨平台的基础函数库。&lt;/p&gt;
&lt;p&gt;在这里我们请读者实现一个嵌套锁，要求如下：&lt;/p&gt;
&lt;p&gt;o 嵌套锁仍然兼容Locker接口。&lt;br /&gt;
o 嵌套锁的实现不依赖于特定平台。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-并发(二)(下)</title>
		<link href="http://www.limodev.cn/blog/?p=448"/>
		<id>http://www.limodev.cn/blog/?p=448</id>
		<updated>2008-12-17T13:58:10+00:00</updated>
		<content type="html">&lt;p&gt;面对这个需求，一些初学者可能有点蒙了。以前在学校的时候，对于课本后面的练习，我总是信心百倍，原因很简单，我确信这些练习不管它的出现方式有多么不同，但总是与前面学过的知识有关。记得《如何求解问题—现代启发式方法》中说过，正是这种练习的方式妨碍了我们解决问题的能力，在现实中解决问题时通常没有这么幸运。在《系统程序员成长计划》我把练习放前面，目标就是刺激读者去思考，在学习知识的同时学习解决问题的方法。&lt;/p&gt;
&lt;p&gt;这里我们应该怎么分析呢？要在双向链表里加锁，第一是要区分单线程和多线程，要链接同一个库，而且不能用宏来控制。第二是不能依赖于特定平台，而锁本身恰恰又是依赖于平台的。怎么办？很明显这两个需求都要求锁的实现可以变化的：单线程版本它什么都不做，多线程版本中，不同的平台有不同的实现。&lt;/p&gt;
&lt;p&gt;我们要做的就是隔离变化。变化怎么隔离？前面我们已经练习过几次用回调函数来隔离变化了，所有的读者都会想到这个方法，因为锁无非是具有两个功能：加锁和解锁，我们把它抽象成两个回调函数就行了。&lt;/p&gt;
&lt;p&gt;这种方法是可行的。这里的情况与前面相比有点特殊：前面的回调函数都是些独立功能的函数，每个回调函数都有自己的上下文，而这里的多个回调函数具有相关的功能，并且共享同一个上下文(锁)。其次是这里的上下文(锁)是一个对象，有自己的生命周期，完成自己的使命后就应该被销毁。&lt;/p&gt;
&lt;p&gt;这里我们引入接口(interface)这个术语，接口其实就是一个抽象的概念，它只定义调用者和实现者之间的契约，而不规定实现的方法。比如这里的锁就是一个抽象的概念，它有加锁/解锁两个功能，这是调用者和实现者之间的契约。但光有这个概念不能做任何事情，只有具体的锁才能被使用。至于具体的锁，不同的平台有不同的实现，但调用者不用关心。正因为调用者不用关心接口的实现方法，接口成了隔离变化最有力的武器。&lt;/p&gt;
&lt;p&gt;在这里，锁是一个接口，双向链表是锁的调用者，有基于不同方式实现的锁。通过接口，双向链表把锁的变化隔离开来：区分单线程和多线程，隔离平台相关性。在C语言中，接口的朴素定义是：一组相关的回调函数及其共享的上下文。我们看看锁这个接口怎么定义：&lt;/p&gt;
&lt;pre&gt;
struct _Locker;
typedef struct _Locker Locker;

typedef Ret  (*LockerLockFunc)(Locker* thiz);
typedef Ret  (*LockerUnlockFunc)(Locker* thiz);
typedef void (*LockerDestroyFunc)(Locker* thiz);

struct _Locker
{
    LockerLockFunc    lock;
    LockerUnlockFunc  unlock;
    LockerDestroyFunc destroy;

    char priv[0];
};
&lt;/pre&gt;
&lt;p&gt;这里要注意三个问题：&lt;/p&gt;
&lt;p&gt;o 接口一定要足够抽象，不能依赖任何具体实现的数据类型。接口一旦与某个具体实现关联了，另外一种实现就会遇到麻烦。比如这里你使用了pthread_mutex_t，那你要实现一个win32下的锁怎么办呢。&lt;/p&gt;
&lt;p&gt;o 接口不能有create函数，但一定要有destroy函数。我们说过对象有自己的生命周期，创建它，使用它，然后销毁它。但接口只是一个概念，不可能通过这个概念凭空创建一个对象出来，对象只能通过具体实现来创建，所以接口不应该出现create自己的函数。一旦对象被创建出来，使用者应该在不再需要它时销毁它，在销毁对象时，如果还要知道它的实现方式才能销毁它，那就造成了调用者和实现者之间不必要的耦合，因此接口都要提供一个destroy函数，调用者可以直接销毁它。&lt;/p&gt;
&lt;p&gt;o 这里的priv用来存放上下文信息，也就是具体实现需要用到的数据结构。像前面的回调函数一样，我们可以用一个void* ctx的成员来保存上下文信息。我们使用的char priv[0];技巧，有点额外的好处：只需要一次内存分配，而且可以分配刚好够用的长度(0到任意长度)。&lt;/p&gt;
&lt;p&gt;前面我们使用回调函数，调用时要判断回调函数是否为空，每个地方都要重复这个动作，所以我们把这些判断集中起来好了：&lt;/p&gt;
&lt;pre&gt;
static inline Ret locker_lock(Locker* thiz)
{
    return_val_if_fail(thiz != NULL &amp;#038;&amp;#038; thiz-&gt;lock != NULL, RET_INVALID_PARAMS);

    return thiz-&gt;lock(thiz);
}

static inline Ret locker_unlock(Locker* thiz)
{
    return_val_if_fail(thiz != NULL &amp;#038;&amp;#038; thiz-&gt;unlock != NULL, RET_INVALID_PARAMS);

    return thiz-&gt;unlock(thiz);
}

static inline void locker_destroy(Locker* thiz)
{
    return_if_fail(thiz != NULL &amp;#038;&amp;#038; thiz-&gt;destroy != NULL);

    thiz-&gt;destroy(thiz);

    return;
}
&lt;/pre&gt;
&lt;p&gt;下面我们来看看基于pthread_mutex的实现：&lt;/p&gt;
&lt;p&gt;o 在locker_pthread.h中，提供一个创建函数。&lt;/p&gt;
&lt;pre&gt;
Locker* locker_pthread_create(void);
&lt;/pre&gt;
&lt;p&gt;o 在locker_pthread.c中，实现这些回调函数：&lt;/p&gt;
&lt;p&gt;定义私有数据结构：&lt;/p&gt;
&lt;pre&gt;
typedef struct _PrivInfo
{
    pthread_mutex_t mutex;
}PrivInfo;
&lt;/pre&gt;
&lt;p&gt;创建对象：&lt;/p&gt;
&lt;pre&gt;
Locker* locker_pthread_create(void)
{
    Locker* thiz = (Locker*)malloc(sizeof(Locker) + sizeof(PrivInfo));

    if(thiz != NULL)
    {
        PrivInfo* priv = (PrivInfo*)thiz-&gt;priv;

        thiz-&gt;lock    = locker_pthread_lock;
        thiz-&gt;unlock  = locker_pthread_unlock;
        thiz-&gt;destroy = locker_pthread_destroy;

        pthread_mutex_init(&amp;#038;(priv-&gt;mutex), NULL);
    }

    return thiz;
}
&lt;/pre&gt;
&lt;p&gt;实现几个回调函数：&lt;/p&gt;
&lt;pre&gt;
static Ret  locker_pthread_lock(Locker* thiz)
{
    PrivInfo* priv = (PrivInfo*)thiz-&gt;priv;

    int ret = pthread_mutex_lock(&amp;#038;priv-&gt;mutex);

    return ret == 0 ? RET_OK : RET_FAIL;
}
…
&lt;/pre&gt;
&lt;p&gt;我简单说一下里面几个问题：&lt;/p&gt;
&lt;p&gt;o malloc(sizeof(Locker) + sizeof(PrivInfo)); 前面的char priv[0]并不占空间，这是C语言新标准定义的，用于实现变长的buffer，它在这里的长度由sizeof(PrivInfo)决定。&lt;/p&gt;
&lt;p&gt;o PrivInfo* priv = (PrivInfo*)thiz-&gt;priv; 这里的thiz-&gt;priv只是一个定位符，实际上等于(size_t)thiz+sizeof(Locker)，帮我们定位到私有数据的内存地址上。&lt;/p&gt;
&lt;p&gt;使用方法：&lt;/p&gt;
&lt;p&gt;单线程版本：&lt;/p&gt;
&lt;pre&gt;
DList* dlist = dlist_create(NULL, NULL, locker_pthread_create());
&lt;/pre&gt;
&lt;p&gt;多线程版本：&lt;/p&gt;
&lt;pre&gt;
DList* dlist = dlist_create(NULL, NULL, NULL);
&lt;/pre&gt;
&lt;p&gt;接口在软件设计中占有非常重要的地位，它是隔离变化和降低复杂度最有力的武器，差不多所有的设计模式都与接口有关。后面我们会反复的练习，这里请读者仔细体会一下。&lt;/p&gt;
&lt;p&gt;本节示例代码请到&lt;a href=&quot;http://www.limodev.cn/bbs/download/file.php?id=11&quot;&gt;&lt;strong&gt;这里&lt;/strong&gt;&lt;/a&gt;下载。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-并发(二)(上)</title>
		<link href="http://www.limodev.cn/blog/?p=446"/>
		<id>http://www.limodev.cn/blog/?p=446</id>
		<updated>2008-12-16T13:28:26+00:00</updated>
		<content type="html">&lt;p&gt;在生产者-消费者的练习中，大部分人选择了由调用者来加锁：作为生产者，往双向链表里插入数据时，先加锁，插入数据，然后解锁。作为消费者，从双向链表里取数据时，先加锁，删除数据，然后解锁。这是合理的，不过有点麻烦：每个调用者都要做这些动作，如果其中一个调用者忘记了解锁的步骤，就会造成死锁。而且调用者必须要清楚自己是在多线程下工作，这些代码放到单线程的环境中就不能使用了。&lt;/p&gt;
&lt;p&gt;在很多情况下由实现者来加锁是比较好的选择，那样对调用者更为友好，可以避免出现一些不必要的错误。比如像目前Linux下流行的DBUS，它是一套进程间通信框架，它支持单线程和多线程版本，但调用者不需要明确加锁/解锁，也不需要连接不同的库或者用宏来控制，单线程版本和多线程版本的不同只是在一个初始化函数上。&lt;/p&gt;
&lt;p&gt;这里我们请读者对前面实现的双向链表做点改进：&lt;/p&gt;
&lt;p&gt;o 支持多线程和单线程版本。对于多线程版本，由实现者(在链表)加锁/解锁，对于单线程版本，其性能不受影响(或很小)。&lt;br /&gt;
o区分单线程版本和多线程版本时，不需要链接不同的库，或者要宏来控制，完全可以在运行时切换。&lt;br /&gt;
o 保持双向链表的通用性，不依赖于特定的平台。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-写得又快又好的秘诀(二)</title>
		<link href="http://www.limodev.cn/blog/?p=419"/>
		<id>http://www.limodev.cn/blog/?p=419</id>
		<updated>2008-12-16T00:20:41+00:00</updated>
		<content type="html">&lt;p&gt;1.好与快的关系&lt;/p&gt;
&lt;p&gt;几年前和一个朋友聊天时，他抱怨他的上司说，要我写得好又要写快，那怎么可能呢？我当时一愣，反问到，写不好怎么可能写得快？他也一愣。&lt;/p&gt;
&lt;p&gt;传统观点认为在功能、成本(人*时间)和质量这个铁三角中，提高质量就意味投入更多成本或者减少一些功能。在功能不变的情况下，不可能在提高质量的同时降低开成成本(对个人来讲就是缩短开发时间)。我的朋友持的正是这种传统观点。&lt;/p&gt;
&lt;p&gt;而根据我的经验来看，结论恰恰相反。每次我想以牺牲质量来加快速度的时候，结果反而花了更多时间，甚至可能到最后搞不定而放弃了。有了多次这样的经验之后，我决定把每一步都做好，从开发的前期来看，我花的时间比别人多一点，但从整个任务来看，我反而能以别人几倍的速度完成任务。时间长了，我形成了这样的观念：只有写得好才可能写得快。&lt;/p&gt;
&lt;p&gt;两种观点截然相反，所以我们都愣了。虽然我相信我的经验没有错，但传统的铁三角定律是大师们总结出来的，也不可能出错。那是怎么回事呢？我开始到处查资料，但是没有一个人支持我的观点。我又不想这样放弃，后来我用了一个简单的办法进行推理，结果证明两个观点都有各自的适用范围。&lt;/p&gt;
&lt;p&gt;这个推理过程很简单，把两种观点推向极端：&lt;/p&gt;
&lt;p&gt;先看看以牺牲质量来追求进度的例子。我以前参加过两个大项目，其一个项目的BUG总数达到17000多个，耗时近三年后项目被取消。另一个项目的BUG总数也超过10000个，三年之后带着很多BUG发布了，结果可想而知，产品很快从市场上消失了。这两个项目在开始阶段都制定了极其可笑的项目计划，为了赶在这个根本不可能的最后期限前，都采用了牺牲质量的方式来提高开发速度，前期进展都很“顺利”，基本功能点很快就完成了，但是项目马上陷入了无止境的debug之中，开发人员的士气一下跌到谷底，管理层开始暴跳如雷。&lt;/p&gt;
&lt;p&gt;如果这两个项目有超过170000个BUG，即使项目不取消，再做时间十年也做不完。由此可见：质量低到一定限度时，降低质量会延长项目时间，如果质量降到最低，那项目永远也不可能完成。这和我的观点一致：写不好就写不快。&lt;/p&gt;
&lt;p&gt;再看看追求完美质量的例子。以前参与一个手机模拟器的开发，我们很快达到88%的真实度，半年之后达到95%的真实度，客户要98%的真实度。但是怎么努力也达不到这个标准，花了极大的代价才达到96%多一点，到后来项目被取消了。&lt;/p&gt;
&lt;p&gt;如果要达到99%的真实度，即使项目不取消，再做十年也做不完。由此可见：质量高到一定程度，提高质量会延长项目时间，如果质量要高到最高，那任务远也不可能完成。这和传统观点一致，提高质量就要延长开发时间。&lt;/p&gt;
&lt;p&gt;从两个极端往中间走，我们可以找到一个中间质量点。低于这个质量点，想以牺牲质量来赶进度，那只会适得其反，质量越低耗时越长。高于这个质量点，想提高质量就得增加成本，质量越高开发时间越长。这样两种观点就统一起来了。&lt;/p&gt;
&lt;p&gt;如果在大多数项目中，这个中间质量点是可以作为高质量接受的，那我们就找到了又快又好的最佳方法。这个质量点到底是多少？呵，我可以告诉你，那是87.5。但是谁知道怎么去度量呢？没有人知道，只能凭感觉和经验了。&lt;/p&gt;
&lt;p&gt;2.我们的时间花在哪里&lt;/p&gt;
&lt;p&gt;经过这段时间的练习，大多数人都体会到敲代码不是耗费时间最多的地方，一个高效率的程序员，并不是打字比别人快，而他节省了别人浪费了的时间。我常说达到别人五倍的效率并不难，因为在软件开发中，大部分人的大部分时间都浪费掉了，你只要把别人浪费的时间省下来，你的效率就提高上去了。像在优化软件性能时采用的方法一样，要优化程序员的性能，我们要找出性能的瓶颈。也就是弄清楚我们的时间花在哪些地方，然后想办法省掉某些浪费了的时间。根据我的经验，耗费时间最多的地方有：&lt;/p&gt;
&lt;p&gt;o 分析 &lt;/p&gt;
&lt;p&gt;需求分析通常是SPEC工程师(或者所谓的系统分析员)的任务，程序员也会参与到这个过程中，但程序员的任务主要是理解需求，然后分析如何实现它们，这个分析工作也就是软件设计。无论你是在计算机上用设计工具画出正规的软件架构图，还在纸上用自然语言描述出算法的逻辑，甚至在脑海中一闪而过的想法都是设计。设计其实就是打草稿，把你的想法进行推敲，最后得到可行的方案。设计文档只是设计的结果，是设计的表现形式，没有写设计文档，并不代表没有做设计(但是写设计文档可以加深你的思考)。&lt;/p&gt;
&lt;p&gt;设计本身是一个思考过程，需要耗费大量时间，对于新手来说更是如此。前面几节中的需求并不难，理解它们只需要很少的时间，但要花不少时间去思考其实现的方法。这个时间因人而异，有的读者到最后也没有想出办法，这没有关系，没有人天生就会的，不会的原因只是因为你暂时还不知道常用的设计方法，甚至连基本数据结构和算法都不熟悉。&lt;/p&gt;
&lt;p&gt;在后面的章节中，我们会一步步的深入学习各种常用设计方法，反复练习基本数据和算法，熟能生巧，软件设计也一样，在你什么都不懂的时候，不可能做出好的设计。你要学习各种经典的设计方法，开始可能生搬硬套，多写多练多思考，到后来就随心所欲了，设计的时间就会大大缩短。&lt;/p&gt;
&lt;p&gt;o测试 &lt;/p&gt;
&lt;p&gt;要写得好自然离不开测试，初学者都有这个概念。他们忠实的使用了教科书上讲的方法，用scanf输入数据，做些操作之后，用printf打印来，这是一个完美的输入-处理-输出的过程。测试也就是要保证正确的输入能产生正确的输出，这种方法的原理是没有错的，但它们确实耗费了我们大量时间。&lt;/p&gt;
&lt;p&gt;如果测试只需要做一次，这种方法还是可取的，问题是每次修改之后都要重复这个过程，耗费的时间就更多了。这种工作单调乏味，而且很难坚持做下去，单元测试做得不全面，就有更多BUG等着就调试了。时间久了，或者换人维护了，谁也搞不清楚什么样输入产生什么样的输出，结果可能是连测试也省了，那就等着把大量的时间浪费在调试上吧。总而言之，这种测试方法不好，我们需要更有效的测试方法才行。&lt;/p&gt;
&lt;p&gt;o调试 &lt;/p&gt;
&lt;p&gt;测试时发现程序有BUG，自然要用调试器了，对一些人来说，调试是一件充满挑战和乐趣的事。而对大部分人来说，特别是对我这种做过两年专职调试的人来说，调试是件无趣无聊无用的工作。熟练使用调试器是必要的，在分析现有软件时，调试器是非常有用的工具。但在开发新软件时，调试器在浪费我们的时间。&lt;/p&gt;
&lt;p&gt;调试器是最后一招，只有迫不得已时才使用。一位敏捷方法的高手说他已经记不得上次使用调试器是什么时候了，我想这就是为什么敏捷方法能够提高开发速度的原因吧。因为没有什么比一次性写好，不用调试器更快的方法了。&lt;/p&gt;
&lt;p&gt;知道了浪费时间的地方，接下来几节中，我们将介绍避免浪费时间的方法。学完这些方法之后，我希望读者也能达到普通工程师五倍的效率，呵，读完本系列文章后之，希望你会达到更高。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-并发(一)(下)</title>
		<link href="http://www.limodev.cn/blog/?p=442"/>
		<id>http://www.limodev.cn/blog/?p=442</id>
		<updated>2008-12-15T13:11:11+00:00</updated>
		<content type="html">&lt;p&gt;Linux下的多线程编程使用pthread(POSIX Thread)函数库，使用时包含头文件pthread.h，链接共享库libpthread.so。这里顺便说一下gcc链接共享库的方式：-L用来指定共享库所在目录，系统库目录不用指定。-l用来指定要链接的共享库，只需要指定库的名字就行了，如：-lpthread，而不是-llibpthread.so。看起来有点怪，这样做的原因是共享库通常带有版本号，指定全文件名就意味着你要绑定到特定版本的共享库上，只指定名字则在可以运行时通过环境变量来选择要使用的共享库，这样能够给软件升级带来的方便。&lt;/p&gt;
&lt;p&gt;pthread函数库的使用相对比较简单，读者可以在终端下运行man pthread_create阅读相关函数的手册，也可以到网上找些例子参考。具体使用方法我们就不讲了，这里介绍一下初学者常犯的错误：&lt;/p&gt;
&lt;p&gt;o 用临时变量作为线程参数的问题。&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdio.h&gt;
#include &amp;lt;pthread.h&gt;
#include &amp;lt;assert.h&gt;

void* start_routine(void* param)
{
    char* str = (char*)param;

    printf(&quot;%s:%s\n&quot;, __func__, str);

    return NULL;
}

pthread_t create_test_thread()
{
    pthread_t id = 0;
    char str[] = &quot;it is ok!&quot;;

    pthread_create(&amp;#038;id, NULL, start_routine, str);

    return id;
}

int main(int argc, char* argv[])
{
    void* ret = NULL;

    pthread_t id = create_test_thread();

    pthread_join(id, &amp;#038;ret);

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;分析：由于新线程和当前线程是并发的，谁先谁后是无法预测的。可能create_test_thread 已经执行完了，str已经被释放了，新线程才拿到这参数，此时它的内容已经无法确定了，打印出的字符串自然是随机的。&lt;/p&gt;
&lt;p&gt;o 线程参数共享的问题。&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdio.h&gt;
#include &amp;lt;pthread.h&gt;
#include &amp;lt;assert.h&gt; 

void* start_routine(void* param)
{
    int index = *(int*)param;

    printf(&quot;%s:%d\n&quot;, __func__, index);

    return NULL;
}

#define THREADS_NR 10

void create_test_threads()
{
    int i = 0;

    void* ret = NULL;

    pthread_t ids[THREADS_NR] = {0};

    for(i = 0; i  THREADS_NR; i++)
    {
        pthread_create(ids + i, NULL, start_routine, &amp;#038;i);
    }

    for(i = 0; i  THREADS_NR; i++)
    {

        pthread_join(ids[i], &amp;#038;ret);
    }

    return ;
}

int main(int argc, char* argv[])
{
    create_test_threads();

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;分析：由于新线程和当前线程是并发的，谁先谁后是无法预测的。i在不断变化，所以新线程拿到的参数值是无法预知的，打印出的字符串自然也是随机的。&lt;/p&gt;
&lt;p&gt;o 虚假并发。&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdio.h&gt;
#include &amp;lt;pthread.h&gt;
#include &amp;lt;assert.h&gt;

void* start_routine(void* param)
{

    int index = *(int*)param;

    printf(&quot;%s:%d\n&quot;, __func__, index);

    return NULL;
}

#define THREADS_NR 10

void create_test_threads()
{
    int i = 0;
    void* ret = NULL;

    pthread_t ids[THREADS_NR] = {0};

    for(i = 0; i  THREADS_NR; i++)
    {
        pthread_create(ids + i, NULL, start_routine, &amp;#038;i);
        pthread_join(ids[i], &amp;#038;ret);
    }

    return ;
}

int main(int argc, char* argv[])
{
    create_test_threads();

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;分析：因为pthread_join会阻塞直到线程退出，所以这些线程实际上是串行执行的，一个退出了，才创建下一个。当年一个同事写了一个多线程的测试程序，就是这样写的，结果没有测试出一个潜伏的问题，直到产品运行时，这个问题才暴露出来。&lt;/p&gt;
&lt;p&gt;访问线程共享的数据时要加锁，让访问串行化，否则就会出问题。比如，可能你正在访问的双向链表的某个结点时，它已经被另外一个线程删掉了。加锁的方式有很多种，像互斥锁(mutex= mutual exclusive lock)，信号量(semaphore)和自旋锁(spin lock)等都是常用的，它们的使用同样很简单，我们就不多说了。&lt;/p&gt;
&lt;p&gt;在加锁/解锁时，初学者常犯两个错误：&lt;/p&gt;
&lt;p&gt;o 存在遗漏解锁的路径。初学者常见的做法就是，进入某个临界函数时加锁，在函数结尾的地方解锁，我甚至见过这种写法：&lt;/p&gt;
&lt;pre&gt;
{
/*这里加锁*/
…
    return …;
/*这里解锁*/
}
&lt;/pre&gt;
&lt;p&gt;如果你也犯了这种错误，应该好好反思一下。有时候，return的地方太多，在某一处忘记解锁是可能的，就像内存泄露一样，只是忘记解锁的后果更严重。像下面这个例子：&lt;/p&gt;
&lt;pre&gt;
Ret dlist_insert(DList* thiz, size_t index, void* data)
{
    DListNode* node = NULL;
    DListNode* cursor = NULL;

    return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);

    dlist_lock(thiz);

    if((node = dlist_create_node(thiz, data)) == NULL)
    {
        dlist_unlock(thiz);
        return RET_OOM;
    }

    if(thiz-&gt;first == NULL)
    {
        thiz-&gt;first = node;

        dlist_unlock(thiz);
        return RET_OK;
    }
    ...

    dlist_unlock(thiz);

    return RET_OK;
}
&lt;/pre&gt;
&lt;p&gt;如果一个函数有五六个甚至更多的地方返回，遗忘一两个地方是很常见的，即使没有忘记，每个返回的地方都要去解锁和释放相关资源也是很麻烦的。在这种情况下，我们最好是实现单入口单出的函数，常见的做法有两种：&lt;/p&gt;
&lt;p&gt;一种是使用goto语句（在linux内核里大量使用）。示例如下：&lt;/p&gt;
&lt;pre&gt;
Ret dlist_insert(DList* thiz, size_t index, void* data)
{
    Ret ret = RET_OK;
    DListNode* node = NULL;
    DListNode* cursor = NULL;

    return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);

    dlist_lock(thiz);

    if((node = dlist_create_node(thiz, data)) == NULL)
    {
        ret = RET_OOM;
        goto done;
    }

    if(thiz-&gt;first == NULL)
    {
        thiz-&gt;first = node;

        goto done;
    }
    ...
done:
    dlist_unlock(thiz);

    return ret;
}
&lt;/pre&gt;
&lt;p&gt;另外一种是使用do{}while(0);语句，出于受教科书的影响(不要用goto语句)，我习惯了这种做法。示例如下：&lt;/p&gt;
&lt;pre&gt;
Ret dlist_insert(DList* thiz, size_t index, void* data)
{
    Ret ret = RET_OK;
    DListNode* node = NULL;
    DListNode* cursor = NULL;

    return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);

    dlist_lock(thiz);

    do
    {
        if((node = dlist_create_node(thiz, data)) == NULL)
        {
            ret = RET_OOM;
            break;
        }

        if(thiz-&gt;first == NULL)
        {
            thiz-&gt;first = node;
            break;
        }
	...
    }while(0);

    dlist_unlock(thiz);

    return ret;
}
&lt;/pre&gt;
&lt;p&gt;o 加锁顺序的问题。有时候为了提高效率，常常降低加锁的粒度，访问时不是用一个锁锁住整个数据结构，而是用多个锁来控制数据结构各个部分。这样一个线程访问数据结构的这部分时，另外一个线程还可以访问数据结构的其它部分。但是在有的情况下，你需要同时锁几个锁，这时就要注意了：所有线程一定要按相同的顺序加锁，相反的顺序解锁。否则就可能出现死锁，两个线程都拿到对方需要的锁，结果出现互相等待的情况。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-写得又快又好的秘诀(六)</title>
		<link href="http://www.limodev.cn/blog/?p=435"/>
		<id>http://www.limodev.cn/blog/?p=435</id>
		<updated>2008-12-15T12:47:10+00:00</updated>
		<content type="html">&lt;p&gt;Save your work&lt;/p&gt;
&lt;p&gt;“Ernst和Young所在的小组决定使用正规的开发理论—他们常用削减法，分阶段进行开发并具有中途交付能力。他们的步骤包括细致的分析和设计—正如本章描写的基本原则一样。而其他竞争者径直开始了编码，在开始几个小时里，Ernst和Young小组落后了。但到中午时Ernst和Young小组却是遥遥领先了，而到了这一天的最后，他们却失败了。导致失败的原因不是因为他们的正规方法，而是他们偶然错误的把工作文件覆盖了，最终他们比午餐时所做的估计少交付了一些功能，他们是被没有使用有效的源程序版本控制这个典型的错误给打败了。”&lt;/p&gt;
&lt;p&gt;&amp;#8211;摘自《快速软件开发》&lt;/p&gt;
&lt;p&gt;前段时间看探索频道的《荒野求生秘技(Man &amp;amp; wild)》，我很喜欢这个节目也喜欢那个英国佬，甚至连重播都不会放过。他展示在沙漠、丛林、冰河和雪山等各种环境的求生秘技，他吃蜘蛛、白蚁、蝎子和蜥蜴，边吃边说这东西很恶心，但是里面含有非常的维生素，蛋白质和糖份，能够Save your life，所以要吃下去。&lt;/p&gt;
&lt;p&gt;在Man &amp;amp; Code的世界里，环境好多了，不用面临危险，寻找水源和食物根本不需要什么秘诀。这里我们不需要求生秘技去Save your life，但我们需要一些习惯去Save your work。我说过作为一名高效的程序员，不是因为他打字比别人快，而是因为他省下了别人浪费的时间，有什么比成果被毁，从头再来更浪费时间呢？下面我介绍一些习惯，它们简单有效，根本算不上什么秘技，但它们能够Save your work，让你的工作稳步前进。&lt;/p&gt;
&lt;p&gt;o 随时存盘&lt;/p&gt;
&lt;p&gt;每次停电时，我都会听到有人惊呼，完了，我的代码没有保存！补回半小时或一个小时的工作不难，在一个好的工作环境里，这种情况一年也就会遇到几次，浪费的时间完全可以忽略不计。但是那种感觉很难受，会影响你的工作情绪，无缘无故的让你重做你的工作，和因为要改进去重做完全是两回事。在我以前工作过的一个公司，有段时间经常跳闸，每周都要停好几次，怎么也找不到原因，后来请人来查，据说是线路太长，静电引起的跳闸。经过那段时间的折磨，我养成了一个习惯：写代码的时候，平均30秒钟存盘一次。现在遇到停电，别人惊呼的时候，我开始闭目养神了。&lt;/p&gt;
&lt;p&gt;o 使用版本控制系统&lt;/p&gt;
&lt;p&gt;和一些老程序员聊天时(呵，其实我也老了)，他们经常问起我们项目有没有使用版本控制系统，我说当然有了，大二的时候就我用Sourcesafe来管理用powerbuilder写的代码了，后来的工作中一直在使用不同的版本控制系统。接着他们开始讲述他们惨痛的经历…这些经历小则让他们项目延期，大则导致整个项目失败。&lt;/p&gt;
&lt;p&gt;版本控制系统有很多功能，但对我个人来说，它最重要的功能是备份代码。每完成一个小功能，我都会把它提交(checkin)进去，如果我不小心删除了本地文件，或者某个做尝试的修改失败了，我可以恢复代码到前一个版本。不同团队有不同的规则，有的团队是不允许这样checkin的，他们只允许checkin经过严格测试的代码。如果是那样，你可以在本地建立自己的版本控制系统，初学者在学习时也可以这样做。现在有很多免费的版本控制系统可用，像CVS、SVN和GIT等等，我个人习惯用CVS，SVN是CVS的改进版，将来肯定会替代CVS的，所以推荐大家使用它。&lt;/p&gt;
&lt;p&gt;o 定期备份&lt;/p&gt;
&lt;p&gt;温伯格在《Quality Software Management: System Thinking》讲了一个有趣的故事，他以前去研究一些失败的案例，发现这些项目的失败都是因为欠佳的运气引起的：比如遭受到洪水、地震、火灾和流行感冒等灾害，项目主管们把自己描述成外部问题的受害者。他又对另外一些成功的项目进行研究，发现其中有些项目同样经历这些自然灾害，但是他们成功的完成了任务。区别只是在于成功项目的主管，采用积极预防措施，定期备份代码，把它们放到不同的地点。&lt;/p&gt;
&lt;p&gt;以前在学校的时候，我有两台电脑，一台赛扬和一台486。我经常在上面重装系统，一会儿装Linux，一会儿装NT，一会儿又装Netware。虽然我经常把代码备份到不同的分区上，结果还不小心把所有分区全干掉了，让我痛心不已。那只是写的一些小程序，重写一遍问题也不大，但是对于专业程序员或一个软件团队来说，重写整个代码就不能接受了，所以需要更可靠的备份机制。&lt;/p&gt;
&lt;p&gt;使用源代码管理系统还不能保证代码的安全，比如服务器硬盘损坏和办公室发生火灾等都是可能发生。团队里一定要有人负责定期备份源代码管理系统系统上的资料，作为初学者也应该有这种意识。另外，我发现有些朋友把重要的资料放在邮箱里，现在的邮箱容量很大，因为提供商会定期备份，非常安全，这倒是一个不错的主意。&lt;/p&gt;
&lt;p&gt;o 状态不好就做点别的&lt;/p&gt;
&lt;p&gt;女同胞有定期状态不佳的时候，男同胞也不是每天状态都很好。感冒了、丢东西了、或者家人争吵了，都会影响你的状态。状态不好的时候做事，往往是进一步退两步，甚至犯下严重的错误。有次我得了重感冒，居然在服务器的根目录下运行rm * -rf(删除全部文件)，由于删除的时间太长，才让我发现删错地方了，吓得我出了一身冷汗，还好那台服务器不是运行着源代码管理系统，但还是浪费了我两天时间去重建服务器上的环境。&lt;/p&gt;
&lt;p&gt;状态不好的时候编程也会犯一些低级错误，让你花费更多时间去调试。总要言之，状态不好的去做重要的事有害无益，这时你不防去做点别做的，比如看看其它模块的代码之类的，甚至完全放松去休息都比犯下严重的错误强。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-并发(一)(上)</title>
		<link href="http://www.limodev.cn/blog/?p=438"/>
		<id>http://www.limodev.cn/blog/?p=438</id>
		<updated>2008-12-14T12:27:32+00:00</updated>
		<content type="html">&lt;p&gt;这几年并发技术受到前所未有的关注：CPU进入多核时代，连手机芯片都使用三核的CPU(AP+BP+DSP集成到一颗芯片)了。天生具有并发能力的语言ErLang逐渐成为热点。网格和云计算开始进入实用阶段。还有一些新技术更是让我闻所未闻，初学者也不用被这些铺天盖地的名词吓倒。据笔者的经验来看，这些技术或许能够改变产业的格局，对人类生活造成重大影响，但从实现角度来看并不无多少革命，相反大部分都是传统技术的改进和应用。这几年我一直在研究开源的基础软件，实际上我没有发现多少“新”东西或者核心技术。要说真正的核心还是如序言中说的：战胜复杂度和应对变化。&lt;/p&gt;
&lt;p&gt;作为系统程序员，掌握基础理论和经典的设计方法，比去追逐一些所谓的新技术要实用得多，基础打扎实了，学习新知识也是很容易的事。在接下来几节中，我们一起来学习传统的并发编程知识。在这里我们请读者完成下列任务：&lt;/p&gt;
&lt;p&gt;了解linux下的多线程编程的基本方法，以双向链表为载体实现传统的生产者-消费者模型：一个线程往双向链表中加东西，另外一个线程从这里面取。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-写得又快又好的秘诀(五)</title>
		<link href="http://www.limodev.cn/blog/?p=431"/>
		<id>http://www.limodev.cn/blog/?p=431</id>
		<updated>2008-12-09T14:38:34+00:00</updated>
		<content type="html">&lt;p&gt;自动测试&lt;/p&gt;
&lt;p&gt;手工测试比没有测试强一点，但是它存在的问题让它很难在实践中应用：手工输入数据的过程单调乏味，很难长期坚持。每次都要重新输入数据，浪费大量时间。测试用例不能累积，测试往往不完整。用人脑判断输出的正误，浪费人力也存在误差。要写得好测试自然不能省，要写得快就需要更好的测试方法。&lt;/p&gt;
&lt;p&gt;更好的测试方法当然是自动测试了。幸运的是，刚进入这个行业我就接触了自动的测试 (呵，读本文的初学者就更幸运了)，我的第一份正式工作是在测试组写测试程序。当时测试组也算是人才济济了，居然有几个北大毕业的，不过她们都不懂Linux，所以我被指派去为移植到Linux上的模块写测试程序。这些模块都有测试程序，但这些测试程序的功能太弱了，我的上司要求开发人员改进，但那些开发人员太自以为是了，根本不理我们，所以我们只好自己重写这些测试程序。模块很多，大概有50多个模块，熟悉这些模块也需要不少时间，按每两个工作日写一个测试程序，上司给我5个月时间。&lt;/p&gt;
&lt;p&gt;记得第一个模块是RDFParser，RDF(资源描述框架)是XML的一种应用，RDFParser实际上是一个XML解析器，并包装成RDF要求的接口。由于我对C/C++还不太熟悉，对RDF更不熟悉了，花了两周时间才写出这个测试程序。运行起来有些不正常，我确信不是测试程序的问题，就去请开发人员帮忙来看一下。负责RDFParser的那个程序员是人大毕业，我没有见过第二个比他更自以为是的程序员了，他刚在我座位上坐下就很大声说，你们QA的人太蠢了！&lt;/p&gt;
&lt;p&gt;当时一听就愣了，不过我是新来的，见上司都没反应，自然就忍了。我列举了一些证据是模块里面的问题，他听也不听，只是不断重复的说，不可能是我程序的问题，你们QA的人太蠢了，总是浪费我的时间。过了一会儿，他终于闭上了嘴巴，又等了一会儿才说，等会儿重新发个版本给你吧。后来又请他过来四五次，结果每次都是他的问题。&lt;/p&gt;
&lt;p&gt;之后我再没有听到他说过你们QA的人太蠢了的话。为了避免让他抓到把柄来嘲笑测试组，我决定请他来查问题之前做更详细的测试。当时我写的测试程序和现在初学者写的测试程序没有两样，都是从教科书上学来的，先通过scanf从终端输入数据，调用被测函数，再把结果printf出来，这花了我太多时间。想到后面还有50多个模块的测试程序要写，这样下去不行，一定得想个办法。&lt;/p&gt;
&lt;p&gt;后来我把输入的数据和期望的结果都写到一个INI文件中，测试程序从这个文件中读入数据，运行测试，再和预期结果比较，整个过程都自动化了。写了一个INI文件的解析器花了我一周时间，又重写了那个测试程序，整整花了我一个月时间完成RDFParser的测试程序。进度自然大落后了，还好上司知道后并没有责备我，让我慢慢做就好了。&lt;/p&gt;
&lt;p&gt;写第二个测试程序时把INI解析的代码拷贝过去，再加一些调用模块的代码就写好了，第三个也是如此。写了几个之后，我发现了INI解析有个BUG，结果每个测试程序我都要去修改，想到维护起来太麻烦了，就把INI解析器的接口规范化了，编译成一个独立共享库。又写了几个测试程序，我写烦了，原因是测试程序无非就是读入数据，调用被测函数，再检查结果，这个过程太无聊了。想到后面还要把这个过程重复几十遍，郁闷了几天之后，突然灵机一动，我决定写了一个代码产生器来产生这些代码。开始的代码产生器用C写的，用一个简单的规则来描述被测函数，通过这些规则来产生测试程序。我把这些东西和INI解析器放在一个独立的库中，把它叫作TesterFrameWork，经过几个测试程序的验证和完善，后来利用这个TesterFrameWork，只要一两个小时就能完成一个测试程序了。有次请开发人员那边一个高手帮我查一个问题，他看一会儿我的TesterFrameWork之后，盯着我说，你太聪明了。我笑了笑说，刚刚开始写C/C++程序。&lt;/p&gt;
&lt;p&gt;一年之后我知道了有个CPPUnit之后，为了赶时髦我把TesterFrameWork改名为CxxUnit，非典的时候放假无聊就把它重写了一遍放在cosoft上了(之后没有管过它，或许还在吧)。&lt;/p&gt;
&lt;p&gt;一个大系统很难自动测试，而一个独立的模块则是最佳的自动测试单元。自动测试和单元测试几乎成了等价的概念，很多人都以为自动测试就是利用CPPUnit这样的单元测试框架写个测试程序而已，这完全是错误的，就像有人以为有个设计文档的模板，照着填空就能填出好设计一样。&lt;/p&gt;
&lt;p&gt;我自己实现过单元测试框架，不是像有些人出于模仿去实现，而完全出于实际的需要，后来我也研究其它测试框架，应该说我对测试程序框架的认识比一般程序员要深刻。我认为测试程序框架可以减化一些测试程序的工作，但它与自动测试没有密切关系，用不用测试程序框架完全是个人喜好。用测试程序框架未必能写出好的测试程序，就像用C++未必能写出好的面向对象的程序一样。&lt;/p&gt;
&lt;p&gt;虽然我顺利的完成了那个写测试程序的任务，但我一直被一个问题困扰：如何写测试用例，如何去检测结果？这是测试程序框架帮不上忙的。写测试用例还好说，通过边界值法，等价类法和路径覆盖法找到最常用的测试用例。检测结果呢？有人说很简单啊，判断返回值就好了。那我问一下dlist_insert返回OK，就真的OK了吗？如果一个函数根本没有返回值，那你怎么判断呢？&lt;/p&gt;
&lt;p&gt;测试程序框架是敏捷论者提倡的，在我看来它根本不够敏捷：你要去学习它，了解它的运行机制，要包含它的头文件，链接它的库，有比不用它更敏捷么？重要的是它根本帮不上什么有用的忙。前面的问题折磨了我一段时间，于是得出一个可能有点偏激的结论：测试程序框架都是愚蠢的，你真正需要的，它根本帮不了你(我知道这样说会得罪一些用测试程序框架的朋友，如果你想找我讨论的话，请看完本节的附带示例代码再说)。&lt;/p&gt;
&lt;p&gt;就在那个时候，我看到了孟岩老师翻译的《契约式设计(Design by Contract)》，读完之后豁然开朗。或许我还没有明白契约式设计的本质，但我确实知道了写自动测试程序的方法，下面我介绍一下：&lt;/p&gt;
&lt;p&gt;o 在设计时，每个函数只完成单一的功能。单一功能的函数容易理解，也容易预测其行为。对测试来说，给定一些输入数据，就知道它的输出和影响，这样函数是最容易测试的。&lt;/p&gt;
&lt;p&gt;o 在设计时，把函数分为查询和命令两类。查询函数只查询对象的状态，而不改变对象的状态。命令函数则只修改对象的状态，只返回其操作是否成功的标志，而不返回对象的状态。比如，dlist_length查询双向链表的长度，它不修改双向链表的任何状态。dlist_delete修改对象的状态(删除结点)，并返回其操作是否成功，而不返回当前长度或者删除的结点之类的状态。&lt;/p&gt;
&lt;p&gt;o 在设计时，把查询分为基本查询和复合查询两类。基本查询函数只查询单一的状态，而复合查询可以同时查询多个状态。比如，window_get_width返回窗口的宽度，这是基本查询函数，widget_get_rect返回窗口的左上角坐标，宽度和高度，这是复合查询函数。&lt;/p&gt;
&lt;p&gt;o在实现时，检验输入数据，确认使用者正确的调用了函数。契约式设计规定了调用者和实现者双方的责任，调用者需要使用正确的参数，才能保证有正确的结果。政治家告诉我们，信任但要检查，所以作为实现者就需要检查输入参数是否违背了契约。那怎么检查呢？有人说，如果检查到无效参数就返回一个错误码。这当然可以，只是不太好，因为大多数人都没有检查返回值的习惯，如果每个地方都检查函数的返回值，也是件很繁琐的事，代码看起来也比较乱。通常我们只检查一些关键的地方，对于无效参数这样的错误，可能就无声无息的隐藏起来了，这样不好，因为隐藏得越深，发现的时间越晚，修改的代价越大。&lt;/p&gt;
&lt;p&gt;在C++和JAVA里，如果参数不正确，通常是throw一个无效参数之类的异常，C语言里面没有异常这个概念，我们需要其它办法才行。有人推荐用assert来检查，这是一个好办法，assert只在调试版本中有效(没有定义NDEBUG)，这样任何无效调用都在调试版本中暴露出来了。如果配合前面返回错误码的方法，在发布版本中也可能避免程序粗暴的死掉。使用方法如下：&lt;/p&gt;
&lt;pre&gt;
    assert(thiz != NULL);
    if(thiz == NULL)
    {
        return DLIST_RET_INVALID_PARAMS;
    }
&lt;/pre&gt;
&lt;p&gt;我一直使用这种方法，但是有个问题：无法用自动测试验证assert是否正常的触发了，当用错误的参数测试时，我期望assert被触发，但如果assert被触发了，自动程序测试就死掉了，自动测试程序死掉了，就无法继续验证下一个assert。这是一个悖论！&lt;/p&gt;
&lt;p&gt;后来我从glib里面学了一招，它检查时不用assert，只是打印出一个警告，代码也简明了，按它的方式，我们这样检查：&lt;/p&gt;
&lt;pre&gt;
return_val_if_fail(cursor != NULL, DLIST_RET_INVALID_PARAMS);
&lt;/pre&gt;
&lt;p&gt;我们需要定义两个宏，一个用于无返回值的函数，一个用于有返回值的函数：&lt;/p&gt;
&lt;pre&gt;
#define return_if_fail(p) if(!(p)) \
    {printf(&quot;%s:%d Warning: &quot;#p&quot; failed.\n&quot;, \
        __func__, __LINE__); return;}
#define return_val_if_fail(p, ret) if(!(p)) \
    {printf(&quot;%s:%d Warning: &quot;#p&quot; failed.\n&quot;,\
__func__, __LINE__); return (ret);}
&lt;/pre&gt;
&lt;p&gt;这样一来，遇到无效参数时，可以看到一个警告信息，同时又不会影响自动测试。&lt;/p&gt;
&lt;p&gt;o在测试时，用查询来验证命令。命令一般都有返回值，但只检查返回值是不够的。比如dlist_delete返回OK，它真的OK了吗？我们信任它，但还是要检查。怎么检查？很简单，用查询函数来检查对象的状态是不是预期的。&lt;/p&gt;
&lt;p&gt;对于dlist_delete，我们预期：&lt;/p&gt;
&lt;pre&gt;
1.输入无效参数，期望返回DLIST_RET_INVALID_PARAMS。
2.输入正确参数，期望：
	函数返回DLIST_RET_OK
	双向链表的长度减一。
	删除的位置的下一个元素被移到删除的位置。
&lt;/pre&gt;
&lt;p&gt;在测试程序中检查时，因为任何不符合期望的结果都是BUG，所以我们用assert检查。这样有问题马上暴露出来了，定位错误比较容易，通常都不需要调试器。我们这样来检查：&lt;/p&gt;
&lt;pre&gt;
    assert(dlist_length(dlist) == (n-i));
    assert(dlist_delete(dlist, 0) == DLIST_RET_OK);
    assert(dlist_length(dlist) == (n-i-1));
    if((i + 1)  n)
    {
         assert(dlist_get_by_index(dlist, 0, (void**)&amp;#038;data) == DLIST_RET_OK);
         assert((int)data == (i+1));
    }
&lt;/pre&gt;
&lt;p&gt;(完整的例子请看本节的示例代码)&lt;/p&gt;
&lt;p&gt;o在测试时，用基本查询去验证复合查询。基本查询和复合查询返回的应该一致。比如：&lt;/p&gt;
&lt;pre&gt;
    Rect rect = {0};
    widget_get_rect(widget, &amp;#038;rect);
    assert(widget_get_width(widget) == rect.width);
    assert(widget_get_height(widget)== rect.height);
&lt;/pre&gt;
&lt;p&gt;o在测试时，预期结果依赖其执行上下文，我们要按逻辑组织测试用例。前面调用的函数可能改变了对象的状态，为了简化测试，在每组测试用例开始时，都重置对象到初始状态。&lt;/p&gt;
&lt;p&gt;o 在测试时，第一次只写基本的测试用例，以后逐渐累积，每次发现新的BUG就把相应的测试用例加进去。每次修改了代码就运行一遍自动测试，保证修改没有引起其它副作用。&lt;/p&gt;
&lt;p&gt;按着上面的原则，应付正常模块的测试没有问题了，但是下面的情况仍然比较棘手：&lt;/p&gt;
&lt;p&gt;o 带有GUI的应用程序。有GUI的程序会给自动的输入数据和检查结果带来困难，有些工具可以部分解决这个问题，特别是针对Win32下的GUI，我很少在Windows下写程序，所以对这方面了解不多。不过最好的办法还是用MVC模型等分离界面和实现，因为界面通常相对比较简单，可以手工测试，而实现的逻辑比较复杂，这部分可以自动测试。后面我们会专门讲解分离界面和实现的方法。&lt;/p&gt;
&lt;p&gt;o 有随机数据输入。如果有些输入数据是内部随机产生的，那你根本无法预测它的输出结果和影响。比如游戏随机的步骤和无线网络信号的变化。对于我们可以控制的随机数据，可以提供额外的函数去获取这些数据。对于无法控制的随机输入数据，可以把它们隔离开，在自动测试中，使用固定的数据。&lt;/p&gt;
&lt;p&gt;o 多线程运行的程序。多线程的程序也很难自动测试，比如向链表中插入一个元素，当你检查的时候，根本无法知道链表的长度是否增加，也无法知道刚才插入的位置是否是你插入的元素，因为这个时候，可能有另外一个线程已经把它删除了，或者又加入了新的数据。不过在单线程的自动测试通过之后，多线程的问题会大大减少，剩下的问题我们可以通过其它方式加以避免。&lt;/p&gt;
&lt;p&gt;写自动测试程序会花你一些时间，但这个投资能带来最大的回报：减少后面调试时的浪费，提高代码的质量，更重要的是你可以安稳的睡个觉了。&lt;/p&gt;
&lt;p&gt;本节示例请到&lt;strong&gt;&lt;a href=&quot;http://www.limodev.cn/bbs/download/file.php?id=10&quot;&gt;这里&lt;/a&gt;&lt;/strong&gt;下载。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">KJAVA虚拟机Hack笔记-用GTK+实现绘图操作</title>
		<link href="http://www.limodev.cn/blog/?p=429"/>
		<id>http://www.limodev.cn/blog/?p=429</id>
		<updated>2008-12-08T13:38:40+00:00</updated>
		<content type="html">&lt;p&gt;绘图操作是在mutable image上进行的，也就是画在GdkPixmap上的，由于GdkPixmap没有画圆和椭圆的函数，我选择用cairo来实现。大部分函数的实现很直观，调用cairo相应的函数就行了，gxpport_draw_arc比较麻烦一点，因为gxpport_draw_arc支持画椭圆，而cairo没有函数直接对应，我们可以通过cairo的变换函数来实现：&lt;/p&gt;
&lt;pre&gt;
void gxpport_draw_arc(
  jint pixel, const jshort *clip,
  gxpport_mutableimage_native_handle dst,
  int dotted, int x, int y, int width, int height,
  int startAngle, int arcAngle)
{
    cairo_t* cr  = gxpport_get_dst_cairo(dst);
    double start = ANGLE_FROM_DEGREE(startAngle);
    double arc   = ANGLE_FROM_DEGREE(arcAngle);

    cairo_init_style(cr, pixel, clip, dotted);
    cairo_save (cr);
    cairo_translate (cr, x + width / 2.0, y + height / 2.0);
    cairo_scale (cr, width / 2.0, height / 2.0);
    cairo_arc(cr, 0.0, 0.0, 1., start, arc);
    cairo_restore (cr);
    cairo_stroke (cr);

    return;
}
&lt;/pre&gt;
&lt;p&gt;其它函数的实现类似，这里不再重复了。另外有两个问题值得一提：&lt;/p&gt;
&lt;p&gt;o 目标pixmap可能为空，这时要画在当前的窗口上，我们调用自己实现的lfpport_get_active_screen来获得当前窗口。&lt;/p&gt;
&lt;p&gt;o其次是与GTK的绘制机制有关。要往GdkWinow上绘制东西，只能在expose消息里做，在expose之前GTK会clear窗口上的内容，之后才把绘制的内容刷新到屏幕上。而JAVA的绘制操作显然不是在expose消息里执行的。为了解决这个问题，我们需要建立另外一个pixmap，JAVA先绘制到这个pixmap上，而在expose消息里从这个pixmap拷过去。&lt;/p&gt;
&lt;p&gt;我们这样处理窗口的expose事件：&lt;/p&gt;
&lt;pre&gt;
static gboolean frame_on_expose_event                 (GtkWidget       *widget,
                                        GdkEventExpose  *event,
                                        gpointer         user_data)
{
    int w = 0;
    int h = 0;
    (void)event;
    (void)user_data;

    GdkDrawable* drawable = GDK_DRAWABLE(widget-&gt;window);
    GdkDrawable* back_buffer = g_object_get_data(G_OBJECT(drawable), &quot;back_buffer&quot;);
    GdkGC* gc = gdk_gc_new(drawable);

    if(back_buffer != NULL)
    {
        gdk_drawable_get_size(drawable, &amp;#038;w, &amp;#038;h);
        gdk_draw_drawable(drawable, gc, back_buffer, 0, 0, 0, 0, w, h);
    }
    g_object_unref(G_OBJECT(gc));

    return TRUE;
}
&lt;/pre&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-写得又快又好的秘诀(四)</title>
		<link href="http://www.limodev.cn/blog/?p=425"/>
		<id>http://www.limodev.cn/blog/?p=425</id>
		<updated>2008-12-07T11:09:21+00:00</updated>
		<content type="html">&lt;p&gt;避免常见错误&lt;/p&gt;
&lt;p&gt;在C语言中，内存错误是最为人诟病的。这些错误让项目延期或者被取消，引发无数的安全问题，甚至出现人命关天的灾难。抛开这些大道理不谈，它们确实浪费了我们大量时间，这些错误引发的是随机现象，即使有一些先进工具的帮助，为了找到重现的路径，花上几天时间也不足为怪。如果能够在编写代码的时候避免这些错误，开发效率至少提高一倍以上，质量可以提高几倍了。这里列举一些常见的内存错误，供新手参考。&lt;/p&gt;
&lt;p&gt;o 内存泄露&lt;/p&gt;
&lt;p&gt;大家都知道，在堆上分配的内存，如果不再使用了，应该把它释放掉，以便后面其它地方可以重用。在C/C++中，内存管理器不会帮你自动回收不再使用的内存。如果你忘了释放不再使用的内存，这些内存就不能被重用了，这就造成了所谓的内存泄露。&lt;/p&gt;
&lt;p&gt;把内存泄露列为首位，倒并不是因为它有多么严重的后果，而因为它是最为常见的一类错误。一两处内存泄露通常不至于让程序崩溃，也不会出现逻辑上的错误，加上进程退出时，系统会自动释放该进程所有相关的内存(共享内存除外)，所以内存泄露的后果相对来说还是比较温和的。但是，量变会导致质变，一旦内存泄露过多以致于耗尽内存，后续内存分配将会失败，程序可能因此而崩溃。&lt;/p&gt;
&lt;p&gt;现在PC机的内存够大了，加上进程有独立的内存空间，对于一些小程序来说，内存泄露已经不是太大的威胁。但对于大型软件，特别是长时间运行的软件，或者嵌入式系统来说，内存泄露仍然是致命的因素之一。&lt;/p&gt;
&lt;p&gt;不管在什么情况下，采取谨慎的态度，杜绝内存泄露的出现，都是可取的。相反，认为内存有的是，对内存泄露放任自流都不是负责的。尽管一些工具可以帮助我们检查内存泄露问题，我认为还是应该在编程时就仔细一点，及早排除这类错误，工具只是用作验证的手段。&lt;/p&gt;
&lt;p&gt;o 内存越界访问&lt;/p&gt;
&lt;p&gt;内存越界访问有两种：一种是读越界，即读了不属于自己的数据，如果所读的内存地址是无效的，程度立刻就崩溃了。如果所读内存地址是有效的，在读的时候不会出问题，但由于读到的数据是随机的，它会产生不可预料的后果。另外一种是写越界，又叫缓冲区溢出，所写入的数据对别人来说是随机的，它也会产生不可预料的后果。&lt;/p&gt;
&lt;p&gt;内存越界访问造成的后果非常严重，是程序稳定性的致命威胁之一。更麻烦的是，它造成的后果是随机的，表现出来的症状和时机也是随机的，让BUG的现象和本质看似没有什么联系，这给BUG的定位带来极大的困难。&lt;/p&gt;
&lt;p&gt;一些工具可以够帮助检查内存越界访问的问题，但也不能太依赖于工具。内存越界访问通常是动态出现的，即依赖于测试数据，在极端的情况下才会出现，除非精心设计测试数据，工具也无能为力。工具本身也有一些限制，甚至在一些大型项目中，工具变得完全不可用。比较保险的方法还是在编程是就小心，特别是对于外部传入的参数要仔细检查。&lt;/p&gt;
&lt;p&gt;我们来看一个例子：&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdlib.h&gt;
#include &amp;lt;string.h&gt;

int main(int argc, char* argv[])
{
    char str[10];
    int array[10] = {0,1,2,3,4,5,6,7,8,9};

    int data = array[10];
    array[10] = data;

    if(argc == 2)
    {
        strcpy(str, argv[1]);
    }

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;这个例子中有两个错误是新手常犯的：&lt;/p&gt;
&lt;p&gt;其一：int array[10] 定义了10个元素大小的数组，由于C语言中数组的索引是从0开始的，所以只能访问array[0]到array[9]，访问array[10]就造成了越界错误。&lt;/p&gt;
&lt;p&gt;其二：strcpy(str, argv[1]);这里是否存在越界错误依赖于外部输入的数据，这样的写法在正常下可能没有问题，但受到一点恶意攻击就完蛋了。除非你确定输入数据是在你控制内的，否则不要用strcpy、strcat和sprintf之类的函数，而要用strncpy、strncat和snprintf代替。&lt;/p&gt;
&lt;p&gt;o 野指针。&lt;/p&gt;
&lt;p&gt;野指针是指那些你已经释放掉的内存指针。当你调用free(p)时，你真正清楚这个动作背后的内容吗？你会说p指向的内存被释放了。没错，p本身有变化吗？答案是p本身没有变化。它指向的内存仍然是有效的，你继续读写p指向的内存，没有人能拦得住你。&lt;/p&gt;
&lt;p&gt;释放掉的内存会被内存管理器重新分配，此时，野指针指向的内存已经被赋予新的意义。对野指针指向内存的访问，无论是有意还是无意的，都为此会付出巨大代价，因为它造成的后果，如同越界访问一样是不可预料的。&lt;/p&gt;
&lt;p&gt;释放内存后立即把对应指针置为空值，这是避免野指针常用的方法。这个方法简单有效，只是要注意，当然指针是从函数外层传入的时，在函数内把指针置为空值，对外层的指针没有影响。比如，你在析构函数里把this指针置为空值，没有任何效果，这时应该在函数外层把指针置为空值。&lt;/p&gt;
&lt;p&gt;o 访问空指针。&lt;/p&gt;
&lt;p&gt;空指针在C/C++中占有特殊的地址，通常用来判断一个指针的有效性。空指针一般定义为0。现代操作系统都会保留从0开始的一块内存，至于这块内存有多大，视不同的操作系统而定。一旦程序试图访问这块内存，系统就会触发一个异常/信号。&lt;/p&gt;
&lt;p&gt;操作系统为什么要保留一块内存，而不是仅仅保留一个字节的内存呢？原因是：一般内存管理都是按页进行管理的，无法单纯保留一个字节，至少要保留一个页面。保留一块内存也有额外的好处，可以检查诸如p=NULL; p[1]之类的内存错误。&lt;/p&gt;
&lt;p&gt;在一些嵌入式系统(如arm7)中，从0开始的一块内存是用来安装中断向量的，没有MMU的保护，直接访问这块内存好像不会引发异常。不过这块内存是代码段的，不是程序中有效的变量地址，所以用空指针来判断指针的有效性仍然可行。&lt;/p&gt;
&lt;p&gt;o 引用未初始化的变量。&lt;/p&gt;
&lt;p&gt;未初始化变量的内容是随机的(有的编译器会在调试版本中把它们初始化为固定值，如0xcc)，使用这些数据会造成不可预料的后果，调试这样的BUG也是非常困难的。&lt;/p&gt;
&lt;p&gt;对于态度严谨的程度员来说，防止这类BUG非常容易。在声明变量时就对它进行初始化，是一个好的编程习惯。另外也要重视编译器的警告信息，发现有引用未初始化的变量，立即修改过来。&lt;/p&gt;
&lt;p&gt;在下面这个例子中，全局变量g_count是确定的，因为它在bss段中，自动初始化为0了。临时变量a是没有初始化的，堆内存str是没有初始化的。但这个例子有点特殊，因为程序刚运行起来，很多东西是确定的，如果你想把它们当作随机数的种子是不行的，因为它们还不够随机。&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdlib.h&gt;
#include &amp;lt;string.h&gt;

int g_count;

int main(int argc, char* argv[])
{
    int a;
    char* str = (char*)malloc(100);

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;o 不清楚指针运算。&lt;/p&gt;
&lt;p&gt;对于一些新手来说，指针常常让他们犯糊涂。&lt;/p&gt;
&lt;p&gt;比如int *p = …; p+1等于(size_t)p + 1吗&lt;/p&gt;
&lt;p&gt;老手自然清楚，新手可能就搞不清了。事实上, p+n 等于 (size_t)p + n * sizeof(*p)&lt;/p&gt;
&lt;p&gt;指针是C/C++中最有力的武器，功能非常强大，无论是变量指针还是函数指针，都应该非常熟练的掌握。只要有不确定的地方，马上写个小程序验证一下。对每一个细节了然于胸，在编程时会省下不少时间。&lt;/p&gt;
&lt;p&gt;o 结构的成员顺序变化引发的错误。&lt;/p&gt;
&lt;p&gt;在初始化一个结构时，老手可能很少像新手那样老老实实的，一个成员一个成员的为结构初始化，而是采用快捷方式，如：&lt;/p&gt;
&lt;pre&gt;
Struct s
{
    int   l;
    char* p;
};

int main(int argc, char* argv[])
{
    struct s s1 = {4, &quot;abcd&quot;};

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;以上这种方式是非常危险的，原因在于你对结构的内存布局作了假设。如果这个结构是第三方提供的，他很可能调整结构中成员的相对位置。而这样的调整往往不会在文档中说明，你自然很少去关注。如果调整的两个成员具有相同数据类型，编译时不会有任何警告，而程序的逻辑可能相距十万八千里了。&lt;/p&gt;
&lt;p&gt;正确的初始化方法应该是（当然，一个成员一个成员的初始化也行）：&lt;/p&gt;
&lt;pre&gt;
struct s
{
    int   l;
    char* p;
};

int main(int argc, char* argv[])
{
    struct s s1 = {.l=4, .p = &quot;abcd&quot;};

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;(有的编译器可能不支持新标准)&lt;/p&gt;
&lt;p&gt;o 结构的大小变化引发的错误。&lt;/p&gt;
&lt;p&gt;我们看看下面这个例子：&lt;/p&gt;
&lt;pre&gt;
struct base
{
    int n;

};

struct s
{
    struct base b;
    int m;
};
&lt;/pre&gt;
&lt;p&gt;在OOP中，我们可以认为第二个结构继承了第一结构，这有什么问题吗？当然没有，这是C语言中实现继承的基本手法。&lt;/p&gt;
&lt;p&gt;现在假设第一个结构是第三方提供的，第二个结构是你自己的。第三方提供的库是以DLL方式分发的，DLL最大好处在于可以独立替换。但随着软件的进化，问题可能就来了。&lt;/p&gt;
&lt;p&gt;当第三方在第一个结构中增加了一个新的成员int k;，编译好后把DLL给你，你直接把它给了客户了，让他们替换掉老版本。程序加载时不会有任何问题，在运行逻辑可能完全改变！原因是两个结构的内存布局重叠了。&lt;/p&gt;
&lt;p&gt;解决这类错误的唯一办法就是重新编译全部代码。由此看来，动态库并不见得可以动态替换，如果你想了解更多相关内容，建议你阅读《COM本质论》。&lt;/p&gt;
&lt;p&gt;o 分配/释放不配对。&lt;/p&gt;
&lt;p&gt;大家都知道malloc要和free配对使用，new要和delete/delete[]配对使用，重载了类new操作，应该同时重载类的delete/delete[]操作。这些都是书上反复强调过的，除非当时晕了头，一般不会犯这样的低级错误。&lt;/p&gt;
&lt;p&gt;而有时候我们却被蒙在鼓里，两个代码看起来都是调用的free函数，实际上却调用了不同的实现。比如在Win32下，调试版与发布版，单线程与多线程是不同的运行时库，不同的运行时库使用的是不同的内存管理器。一不小心链接错了库，那你就麻烦了。程序可能动则崩溃，原因在于在一个内存管理器中分配的内存，在另外一个内存管理器中释放时就会出现问题。&lt;/p&gt;
&lt;p&gt;o 返回指向临时变量的指针&lt;/p&gt;
&lt;p&gt;大家都知道，栈里面的变量都是临时的。当前函数执行完成时，相关的临时变量和参数都被清除了。不能把指向这些临时变量的指针返回给调用者，这样的指针指向的数据是随机的，会给程序造成不可预料的后果。&lt;/p&gt;
&lt;p&gt;下面是个错误的例子：&lt;/p&gt;
&lt;pre&gt;
char* get_str(void)
{
    char str[] = {&quot;abcd&quot;};

    return str;
}

int main(int argc, char* argv[])
{

    char* p = get_str();

    printf(&quot;%s\n&quot;, p);

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;下面这个例子没有问题，大家知道为什么吗？&lt;/p&gt;
&lt;pre&gt;
char* get_str(void)
{
    char* str = {&quot;abcd&quot;};

    return str;
}

int main(int argc, char* argv[])
{

    char* p = get_str();

    printf(&quot;%s\n&quot;, p);

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;o 试图修改常量&lt;/p&gt;
&lt;p&gt;在函数参数前加上const修饰符，只是给编译器做类型检查用的，编译器禁止修改这样的变量。但这并不是强制的，你完全可以用强制类型转换绕过去，一般也不会出什么错。&lt;/p&gt;
&lt;p&gt;而全局常量和字符串，用强制类型转换绕过去，运行时仍然会出错。原因在于它们是放在.rodata里面的，而.rodata内存页面是不能修改的。试图对它们修改，会引发内存错误。&lt;/p&gt;
&lt;p&gt;下面这个程序在运行时会出错：&lt;/p&gt;
&lt;pre&gt;
int main(int argc, char* argv[])
{
    char* p = &quot;abcd&quot;;
    *p = '1';

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;o 误解传值与传引用&lt;/p&gt;
&lt;p&gt;在C/C++中，参数默认传递方式是传值的，即在参数入栈时被拷贝一份。在函数里修改这些参数，不会影响外面的调用者。如：&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdlib.h&gt;
#include &amp;lt;stdio.h&gt;

void get_str(char* p)
{

    p = malloc(sizeof(&quot;abcd&quot;));

    strcpy(p, &quot;abcd&quot;);

    return;
}

int main(int argc, char* argv[])
{
    char* p = NULL;

    get_str(p);

    printf(&quot;p=%p\n&quot;, p);

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;在main函数里，p的值仍然是空值。当然在函数里修改指针指向的内容是可以的。&lt;/p&gt;
&lt;p&gt;o 重名符号。&lt;/p&gt;
&lt;p&gt;无论是函数名还是变量名，如果在不同的作用范围内重名，自然没有问题。但如果两个符号的作用域有交集，如全局变量和局部变量，全局变量与全局变量之间，重名的现象一定要坚决避免。gcc有一些隐式规则来决定处理同名变量的方式，编译时可能没有任何警告和错误，但结果通常并非你所期望的。&lt;/p&gt;
&lt;p&gt;下面例子编译时就没有警告：&lt;/p&gt;
&lt;p&gt;t.c&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdlib.h&gt;
#include &amp;lt;stdio.h&gt;

int count = 0;

int get_count(void)

{
    return count;
}

main.c

#include &amp;lt;stdio.h&gt;

extern int get_count(void);

int count;

int main(int argc, char* argv[])
{
    count = 10;

    printf(&quot;get_count=%d\n&quot;, get_count());

    return 0;

}
&lt;/pre&gt;
&lt;p&gt;如果把main.c中的int count;修改为int count = 0;，gcc就会编辑出错，说multiple definition of `count&amp;#8217;。它的隐式规则比较奇妙吧，所以还是不要依赖它为好。&lt;/p&gt;
&lt;p&gt;o 栈溢出。&lt;/p&gt;
&lt;p&gt;我们在前面关于堆栈的一节讲过，在PC上，普通线程的栈空间也有十几M，通常够用了，定义大一点的临时变量不会有什么问题。&lt;/p&gt;
&lt;p&gt;而在一些嵌入式中，线程的栈空间可能只5K大小，甚至小到只有256个字节。在这样的平台中，栈溢出是最常用的错误之一。在编程时应该清楚自己平台的限制，避免栈溢出的可能。&lt;/p&gt;
&lt;p&gt;o 误用sizeof。&lt;/p&gt;
&lt;p&gt;尽管C/C++通常是按值传递参数，而数组则是例外，在传递数组参数时，数组退化为指针（即按引用传递），用sizeof是无法取得数组的大小的。&lt;/p&gt;
&lt;p&gt;从下面这个例子可以看出：&lt;/p&gt;
&lt;pre&gt;
void test(char str[20])
{
    printf(&quot;%s:size=%d\n&quot;, __func__, sizeof(str));
}  

int main(int argc, char* argv[])
{
    char str[20]  = {0};

    test(str);

    printf(&quot;%s:size=%d\n&quot;, __func__, sizeof(str));

    return 0;
}

[root@localhost mm]# ./t.exe
test:size=4
main:size=20
&lt;/pre&gt;
&lt;p&gt;o 字节对齐。&lt;/p&gt;
&lt;p&gt;字节对齐主要目的是提高内存访问的效率。但在有的平台(如arm7)上，就不光是效率问题了，如果不对齐，得到的数据是错误的。&lt;/p&gt;
&lt;p&gt;所幸的是，大多数情况下，编译会保证全局变量和临时变量按正确的方式对齐。内存管理器会保证动态内存按正确的方式对齐。要注意的是，在不同类型的变量之间转换时要小心，如把char*强制转换为int*时，要格外小心。&lt;/p&gt;
&lt;p&gt;另外，字节对齐也会造成结构大小的变化，在程序内部用sizeof来取得结构的大小，这就足够了。若数据要在不同的机器间传递时，在通信协议中要规定对齐的方式，避免对齐方式不一致引发的问题。&lt;/p&gt;
&lt;p&gt;o 字节顺序。&lt;/p&gt;
&lt;p&gt;字节顺序历来是设计跨平台软件时头疼的问题。字节顺序是关于数据在物理内存中的布局的问题，最常见的字节顺序有两种：大端模式与小端模式。&lt;/p&gt;
&lt;p&gt;大端模式是高位字节数据存放在低地址处，低位字节数据存放在高地址处。&lt;/p&gt;
&lt;p&gt;小端模式指低位字节数据存放在内存低地址处，高位字节数据存放在内存高地址处；&lt;/p&gt;
&lt;p&gt;在普通软件中，字节顺序问题并不引人注目。而在开发与网络通信和数据交换有关的软件时，字节顺序问题就要特殊注意了。&lt;/p&gt;
&lt;p&gt;o 多线程共享变量没有用valotile修饰。&lt;/p&gt;
&lt;p&gt;关键字valotile的作用是告诉编译器，不要把变量优化到寄存器里。在开发多线程并发的软件时，如果这些线程共享一些全局变量，这些全局变量最好用valotile修饰。这样可以避免因为编译器优化而引起的错误，这样的错误非常难查。&lt;/p&gt;
&lt;p&gt;o 忘记函数的返回值&lt;/p&gt;
&lt;p&gt;函数需要返回值，如果你忘记return语句，它仍然会返回一个值，因为在i386上，EAX用来保存返回值，如果没有明确返回，EAX最后的内容被返回，所以EAX的内容是随机的。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-写得又快又好的秘诀(三)</title>
		<link href="http://www.limodev.cn/blog/?p=423"/>
		<id>http://www.limodev.cn/blog/?p=423</id>
		<updated>2008-12-04T14:51:22+00:00</updated>
		<content type="html">&lt;p&gt;代码阅读法&lt;/p&gt;
&lt;p&gt;软件工程实践已经证明Code Review是提高代码质量最有效的手段之一，极限编程(XP)更是把Code Review推向极致，形成著名的结对编程工作方式，两个程序员在一台电脑前面工作，一个人编写程序，另一个Review输入每一行代码，写程序人的专注于目前细节上的工作，Review的人同时要从高层次考虑如何改进代码质量，两个人的角色会经常互换。&lt;/p&gt;
&lt;p&gt;可惜我即没有结对编程的经验，也没有在CMM3(及以上)团队中工作过。不过现在我要介绍比结对编程更敏捷更轻量级，但是同样有效的Review方法。这种方法不需要其他程序员配合，有你自己就够了。为了把这种方法与传统的Code Review区分开来，我把它称为代码阅读法吧。&lt;/p&gt;
&lt;p&gt;很多初学者包括一些有经验的程序员，在敲完代码的最后一个字符后，马上开始编译和运行，迫不急待的想看到自己的工作成果。快速反馈有助于满足自己的成就感，但是同时也会带来一些问题：&lt;/p&gt;
&lt;p&gt;让编译器帮你检查语法错误可以省些时间，但程序员往往太专注这些错误了，以为改完这些错误就万事大吉了。其实不然，很多错误编译器是发现不了的，像内存错误和线程死锁等等，这些错误可能逃过简单的测试而遗留在代码中，直到集成测试或者软件发布之后才暴露出来，那时就要花更大代价去修改它们了。&lt;/p&gt;
&lt;p&gt;修改完编译错误之后就是运行程序了，运行起来有错误，就轮到调试器上场了。花了不少时间去调试，发现无非是些低级错误，或许你会自责自己粗心大意，但是下次可能还是犯同样的错误。更严重的是这种debug &amp;#038; fix的方法，往往是头痛医头脚痛医脚，导致低质量的软件。&lt;/p&gt;
&lt;p&gt;让编译器帮你检查语法错误，让调试器帮你查BUG，这是天经地义的事，但这确实是又慢又烂的方法。就像你要到离家东边1000米的地方开会，结果你往西边走，又是坐车又是搭飞机，花了一周时间，也绕着地球转了一周，终于到了会议室，你还大发感慨说，现代的交通工具真是发达啊。其实你往东走，走路也只要十多分钟就到了。不管你的调试技巧有多高，都不如一次性写好更高效。&lt;/p&gt;
&lt;p&gt;我以前也一样，想赶时间结果花了更多时间，在经过很多痛苦的经历之后，我开始学会放松自己，让自己慢下来。写完程序之后，我会花些时间去阅读它，一遍两遍甚至多遍之后，才开始编译它，只要有时间，在通过测试之后，我还会阅读它们，每读一遍都有不同的收获，有时候会发现一些错误，有时候会做些改进，有时候也有新的想法。&lt;/p&gt;
&lt;p&gt;下面是我在阅读自己代码时的一些方法：&lt;/p&gt;
&lt;p&gt;o检查常见错误。&lt;/p&gt;
&lt;p&gt;第一遍阅读时主要关注语法错误、代码排版和命名规则等等问题，只要看不顺眼就修改它们。读完之后，你的代码很少有低级错误，看起来也比较干净清爽。第二遍重点关注常见编程错误，比如内存泄露和可能的越界访问，变量没有初始化，函数忘记返回值等等，在后面的章节中，我会介绍这些常见错误，避免这些错误可以为你省大量的时间。如果有时间，在测试完成之后，还可以考虑是否有更好的实现方法，甚至尝试重新去实现它们。说了读者可能不相信，在学习编程的前几年，我经常重写整个模块，只我觉得能做得更好，能验证我的一些想法，或提高我的编程能力，即使连续几天加班到晚上十一点，我也要重写它们。&lt;/p&gt;
&lt;p&gt;o模拟计算机执行。&lt;/p&gt;
&lt;p&gt;常见错误是比较死的东西，按照检查列表一条一条的做就行了。有些逻辑通常不是这么直观的，这时可以自己模拟计算机去执行，假想你自己是计算机，读入这些代码时你会怎么处理。这种方法能有效的完善我们的思路，考虑不同的输入数据，各种边界值，这能帮助我们想到一些没有处理的情况，让程序的逻辑更严谨。&lt;/p&gt;
&lt;p&gt;o假想讲给朋友听。&lt;/p&gt;
&lt;p&gt;据说在Code Review时发现错误的，往往不是Review的人而是程序员自己。我也有很多这样的经历，在讲给别人听的时候，别人还没有听明白，自己已经发现里面存在的错误了。上大学时，我常常把写的或者学到的东西讲给隔壁寝室的一个同学听，他说他从我这里学到很多知识，其实我从讲的过程中，经常发现一些问题，对提高自己的能力大有帮助。可惜并不是随时都能找到好的听众，幸好我们有另外一个替代办法，记得刚开始写程序时看过一本书(忘记名字了)，作者说他在写程序时，常常把思路讲给他的布娃娃听。我没有布娃娃当听众，讲给鼠标听总是有点怪怪的，所以就假想旁边有个朋友，我把自己的思路讲给他听，同时也假想他来质疑我。这种方法很效，能够让自己的思路更清晰，据说一些大师也经常使用这种方法。&lt;/p&gt;
&lt;p&gt;这种代码阅读法会花你一些时间，但是可以省下更多调试时间，而且能够提高代码质量，可以说是名符其实的“又快又好的” 秘诀之一。至于读几遍合适，要根据情况而定，个人觉得读两到三遍是最佳的投资。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">KJAVA虚拟机Hack笔记-实现mutable image</title>
		<link href="http://www.limodev.cn/blog/?p=421"/>
		<id>http://www.limodev.cn/blog/?p=421</id>
		<updated>2008-12-03T14:04:37+00:00</updated>
		<content type="html">&lt;p&gt;mutable image在这里的意思是说可以在上面进行绘制操作的图片，它有点像VC中的DC，可以在上面贴图或者画直线填充矩形等等。在GTK+中实现的话，自然就用GdkPixmap了，GdkPixmap从GdkDrawable继承过来的，提供了各种常用的绘图操作。&lt;/p&gt;
&lt;p&gt;gxpport_create_mutable 调用gdk_pixmap_new创建一个pixmap就好了，当然要记得检查资源限制，这个检查都是一样的，后面不再重复了。&lt;/p&gt;
&lt;p&gt;gxpport_render_mutableregion 把一个pixmap绘制到另外一个pixmap上，调用gdk_draw_drawable就行了。transform的实现也不难，不过我还没有做，想到时候用cairo的变换来实现。&lt;/p&gt;
&lt;p&gt;gxpport_render_mutableimage 先取出source的宽高，再调用gxpport_render_mutableregion。&lt;/p&gt;
&lt;p&gt;gxpport_get_mutable_argb 先调用gdk_drawable_get_image，再调用gdk_image_get_pixel取出pixel数据。&lt;/p&gt;
&lt;p&gt;gxpport_destroy_mutable 调用g_object_unref就行了。&lt;/p&gt;
&lt;p&gt;gxpport_render_immutableimage和gxpport_render_immutableregion 调用gdk_draw_pixbuf就行了。在基于DirectFB的GTK+中，我遇到一点小问题，基本图形操作，比如画直线和显示文字等是调用cairo实现的。结果发现显示文字之后，再显示图片时颜色就不对了。调试了好久，才发现是因为显示文字时修改了Surface的Blit Flag。为了正常显示图片，在draw_pixbuf时需要调用gdk_gc_set_function设置blit flag为正常拷贝。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">程序员，为你的程序而骄傲吧</title>
		<link href="http://www.limodev.cn/blog/?p=417"/>
		<id>http://www.limodev.cn/blog/?p=417</id>
		<updated>2008-12-02T14:20:56+00:00</updated>
		<content type="html">&lt;p&gt;一些人认为程序员都有自大狂倾向，我不知道这种想法源于何处有何根据。谦虚是中华民族的传统美德，我们不但从小到大都受这类教育，而且在关于软件的方面的书中，也不忘提醒我们要谦虚谨慎，一些前辈更是无私的和我们分享要谦虚的经验。&lt;/p&gt;
&lt;p&gt;在这样的多重教育下，难道我们还会顽固不改吗？每次代码评审时，我们都会说，不好意思，代码写得比较烂。每次移交工作时，我们都会说，不好意思，工作做得不好。每次发布软件时，我们都会说，不好意思，里面还有很多问题。你看，我们是何等的谦虚，也是何等的诚实(不是真的很烂吗)！&lt;/p&gt;
&lt;p&gt;不是自大，相反，我们是太谦虚了。程序员会说，放心吧，这是我做的设计，这是我写的代码。或者是说，我是这方面的专家，有问题只管找我。你听过类似的话吗？至少我是很少听到的。当然，或许在简历上这样写过，但扪心自问，那是自大还是骗人？&lt;/p&gt;
&lt;p&gt;谦虚已经不再是促进我们进步的力量，而是成了掩饰我们不负责任的外衣。代码评审前先谦虚一下，即使在评审过程中发现大量问题，也心安理得，反而证明了我们诚实的品质。在移交工作时谦虚一下，把烂摊子推给别人，我们不再内疚。在发布软件时，谦虚一下，把软件中的BUG视为理所当然。&lt;/p&gt;
&lt;p&gt;我对Donald E. Knuth的景仰并非源于他的几本巨著，因为总共看了不到200页，也不是源于他那套著名的排版软件，因为从来都没有用过。而是源于他说过的一句话:&lt;/p&gt;
&lt;p&gt;“我确信TEX的最后一个错误已经在1985年11月27日被发现并排除掉了。但是如果出于目前尚不知道的原因，TEX仍然潜伏有错误，我非常愿意付给第一个发现者$20.48元。（这一金额已是以前的两倍。我打算在本年内再增加一倍。你看我是多么自信！）”&lt;/p&gt;
&lt;p&gt;大师李敖说: 表面上我是非常的狂傲，而我内心冷静得不得了。这话同样适用于Knuth。Knuth在说前面那句话之前，不知道把TEX的代码和设计检查了多少遍，考虑过多少种可能性，所以他确信没有BUG了。&lt;/p&gt;
&lt;p&gt;Knuth这样说，决不是出于冲动或者虚荣心，而是出于一种敢于承担责任的勇气，一种对自己作品的自信，一种追求完美的态度。这种勇气、这种自信和这样态度正是我们所缺乏的！&lt;/p&gt;
&lt;p&gt;试想，如果他说，我已经在TEX里发现了大量BUG，根据经验表明，发现的BUG越多，说明软件里残留的BUG也越多。这没有办法，测试只能证明软件有BUG，而不能证明软件没有BUG，大家先用着吧。&lt;/p&gt;
&lt;p&gt;对比前后两者，我们是喜欢前者的勇气和自信还是后者的谦虚？至少我更欣赏前者的勇气和自信，更景仰那他那种追求完美的态度。&lt;/p&gt;
&lt;p&gt;现实中这样的程序员太少了。以前有位同事，他是linux组的leader。当时我刚毕业，负责写一些小工具和测试程序。他负责一个重要模块，估计有三万来行代码，让我去测试它。我说现在太忙，可能没有时间。他微微一笑说，其实也不用测试，没什么问题的。没多久他走了，我继续呆了两年多，事实证明他说的没错，三万行代码中出现的BUG不超过5个！&lt;/p&gt;
&lt;p&gt;没见过第二敢这样说的人，大家都是谦虚的好孩子。&lt;/p&gt;
&lt;p&gt;这到底是水平问题，还是态度问题？什么时候我们才那样的水平，什么时候我们才那样的态度？什么时候我们才敢说，这是我写的，没有问题，放心的用吧！什么时候我们才会为我们的作品而骄傲？&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">共享库的初始化和~初始化函数分析</title>
		<link href="http://www.limodev.cn/blog/?p=415"/>
		<id>http://www.limodev.cn/blog/?p=415</id>
		<updated>2008-12-02T14:18:14+00:00</updated>
		<content type="html">&lt;p&gt;Win32下可以通过DllMain来初始化和~初始化动态库，而Linux下则没有与之完全对应的函数，但可以通过一些方法模拟它的部分功能。有人会说，很简单，实现_init/_fini两个函数就行了。好，我们来看看事实是不是这样的。&lt;/p&gt;
&lt;p&gt;很多资料上都说可以利用_init/_fini来实现，而我从来没有测试成功过，原因是这两个函数都已经被gcc占用了。比如：&lt;/p&gt;
&lt;pre&gt;
test.c

#include &amp;lt;stdio.h&gt;

void _init(void)
{
    printf(&quot;%s&quot;, __func__);
}

void _fini(void)
{
    printf(&quot;%s&quot;, __func__);
}
&lt;/pre&gt;
&lt;p&gt;编译结果：&lt;/p&gt;
&lt;pre&gt;
[root@localhost soinit]# gcc -g test.c -shared -o libtest.so

/tmp/cc4DUw68.o(.text+0x0): In function `_init':
/work/test/soinit/test.c:5: multiple definition of `_init'
/usr/lib/gcc/i386-redhat-linux/4.0.0/../../../crti.o(.init+0x0): first defined here
/tmp/cc4DUw68.o(.text+0x1d): In function `_fini':
/work/test/soinit/test.c:10: multiple definition of `_fini'
/usr/lib/gcc/i386-redhat-linux/4.0.0/../../../crti.o(.fini+0x0): first defined here
collect2: ld returned 1 exit status
&lt;/pre&gt;
&lt;p&gt;由此可见，这两个符号已经被编译器的脚手架代码占用了，我们不能再使用。编译器用这两个函数做什么？我们能不能抢占这两个函数，不用编译器提供的，而用我们自己的呢？先看看这两个的实现：&lt;/p&gt;
&lt;pre&gt;
00000594 _fini&gt;:

 594:   55                      push   %ebp
 595:   89 e5                   mov    %esp,%ebp
 597:   53                      push   %ebx
 598:   50                      push   %eax
 599:   e8 00 00 00 00          call   59e _fini+0xa&gt;
 59e:   5b                      pop    %ebx
 59f:   81 c3 02 11 00 00       add    $0x1102,%ebx
 5a5:   e8 de fe ff ff          call   488 __do_global_dtors_aux&gt;
 5aa:   58                      pop    %eax
 5ab:   5b                      pop    %ebx
 5ac:   c9                      leave
 5ad:   c3                      ret   

 0000041c _init&gt;:

 41c:   55                      push   %ebp
 41d:   89 e5                   mov    %esp,%ebp
 41f:   83 ec 08                sub    $0x8,%esp
 422:   e8 3d 00 00 00          call   464 
 427:   e8 b8 00 00 00          call   4e4 
 42c:   e8 2b 01 00 00          call   55c __do_global_ctors_aux&gt;
 431:   c9                      leave
 432:   c3                      ret
&lt;/pre&gt;
&lt;p&gt;从以上代码中可以看出，这两个函数是用来初始化/~初始化全局变量/对象的，抢占这两个函数可能导致初始化/~初始化全局变量/对象出错。所以不能再打_init/_fini的主意，那怎么办呢？&lt;/p&gt;
&lt;p&gt;使用全局对象&lt;/p&gt;
&lt;pre&gt;
test.cpp

#include &amp;lt;stdio.h&gt;

class InitFini
{
public:
    InitFini()
    {
        printf(&quot;%s\n&quot;, __func__);
    }

    ~InitFini()
    {
        printf(&quot;%s\n&quot;, __func__);
    }
};

static InitFini aInitFini;

extern &quot;C&quot; int test(int n)
{
    return n;
} 

main.c

int test(int n);
int main(int argc, char* argv[])
{
    test(1);
    return 0;
}
&lt;/pre&gt;
&lt;p&gt;测试结果：&lt;/p&gt;
&lt;pre&gt;
[root@localhost soinit]# ./t.exe

InitFini

~InitFini
&lt;/pre&gt;
&lt;p&gt;那么这两个函数是怎么被调用的呢？我们在gdb里看看：&lt;/p&gt;
&lt;pre&gt;
Breakpoint 3, InitFini (this=0xa507bc) at test.cpp:7
7                       printf(&quot;%s\n&quot;, __func__);

Current language:  auto; currently c++
(gdb) bt

#0  InitFini (this=0xa507bc) at test.cpp:7
#1  0x00a4f5e0 in __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535) at test.cpp:15
#2  0x00a4f611 in global constructors keyed to test () at test.cpp:21
#3  0x00a4f66a in __do_global_ctors_aux () from ./libtest.so
#4  0x00a4f4a9 in _init () from ./libtest.so
#5  0x002c8b4b in call_init () from /lib/ld-linux.so.2
#6  0x002c8c4a in _dl_init_internal () from /lib/ld-linux.so.2
#7  0x002bb83f in _dl_start_user () from /lib/ld-linux.so.2 

Breakpoint 4, ~InitFini (this=0x0) at test.cpp:9
9               ~InitFini()
(gdb) bt
#0  ~InitFini (this=0x0) at test.cpp:9
#1  0x00a4f5b3 in __tcf_0 () at test.cpp:15
#2  0x00303e6f in __cxa_finalize () from /lib/libc.so.6
#3  0x00a4f532 in __do_global_dtors_aux () from ./libtest.so
#4  0x00a4f692 in _fini () from ./libtest.so
#5  0x002c9058 in _dl_fini () from /lib/ld-linux.so.2
#6  0x00303c69 in exit () from /lib/libc.so.6
#7  0x002eddee in __libc_start_main () from /lib/libc.so.6
#8  0x080483b5 in _start ()
&lt;/pre&gt;
&lt;p&gt;从以上信息可以看出，正是从_init/_fini两个函数调用过来的。&lt;/p&gt;
&lt;p&gt;使用gcc扩展&lt;/p&gt;
&lt;p&gt;毫无疑问，以上方法可行，但是在C++中才行。而C语言中，根本没有构造和析构函数。怎么办呢？这时我们可以使用gcc的扩展：&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdio.h&gt;

__attribute ((constructor)) void test_init(void)
{
    printf(&quot;%s\n&quot;, __func__);
}

__attribute ((destructor)) void test_fini(void)
{
    printf(&quot;%s\n&quot;, __func__);
}

int test(int n)
{
    return n;
}
&lt;/pre&gt;
&lt;p&gt;测试结果：&lt;/p&gt;
&lt;pre&gt;
[root@localhost soinit]# ./t.exe

test_init
test_fini
&lt;/pre&gt;
&lt;p&gt;我们不防也看看这两个函数是怎么被调到的：&lt;/p&gt;
&lt;pre&gt;
Breakpoint 3, test_init () at test.c:6
6               printf(&quot;%s\n&quot;, __func__);
(gdb) bt
#0  test_init () at test.c:6
#1  0x00860586 in __do_global_ctors_aux () from ./libtest.so
#2  0x00860439 in _init () from ./libtest.so
#3  0x002c8b4b in call_init () from /lib/ld-linux.so.2
#4  0x002c8c4a in _dl_init_internal () from /lib/ld-linux.so.2
#5  0x002bb83f in _dl_start_user () from /lib/ld-linux.so.2
(gdb) c 

Breakpoint 4, test_fini () at test.c:11
11              printf(&quot;%s\n&quot;, __func__);
(gdb) bt
#0  test_fini () at test.c:11
#1  0x008604d3 in __do_global_dtors_aux () from ./libtest.so
#2  0x008605ae in _fini () from ./libtest.so
#3  0x002c9058 in _dl_fini () from /lib/ld-linux.so.2
#4  0x00303c69 in exit () from /lib/libc.so.6
#5  0x002eddee in __libc_start_main () from /lib/libc.so.6
#6  0x080483b5 in _start ()
&lt;/pre&gt;
&lt;p&gt;从以上信息可以看出，也是从_init/_fini两个函数调用过来的。&lt;/p&gt;
&lt;p&gt;总结：正如一些资料上所说的，在linux下，_init/_fini是共享库的初始化和~初始化函数。但这两个函数是给gcc用的，我们不能直接使用它们，但可以用本文中提到另外两种方法来实现。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">分布式计算的基本原理</title>
		<link href="http://www.limodev.cn/blog/?p=413"/>
		<id>http://www.limodev.cn/blog/?p=413</id>
		<updated>2008-12-02T14:03:38+00:00</updated>
		<content type="html">&lt;p&gt;从最近几次MMI设计会议讨论的结果来看，嵌入式程序员对于分布式计算知之甚少。他们对分布式计算有种恐惧，所以对分布式架构极力排斥。而他们的人数又占绝对优势，讨论N次，MMI的架构还是没有确定下来。分布式计算已经进入桌面环境，不是企业应用的专利了，像GNOME(GNU Network Object Model Environment)的名字本身就暗示着分布式计算了。本文介绍一下分布式的基本原理，揭开分布式神秘的面纱，让嵌入式程序员熟悉一下。&lt;/p&gt;
&lt;p&gt;分布式计算可以分为以下几类：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;传统的C/S模型。&lt;/strong&gt;如HTTP/FTP/SMTP/POP/DBMS等服务器。客户端向服务器发送请求，服务器处理请求，并把结果返回给客户端。客户端处于主动，服务器处于被动。这种调用是显式的，远程调用就是远程调用，本地调用就是本地调用，每个细节你都要清楚，一点都含糊不得。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;集群技术。&lt;/strong&gt;近年来PC机的计算能力飞速发展，而服务器的计算能力，远远跟不上客户端的要求。这种多对一的关系本来就不公平，人们已经认识到靠提高单台服务器的计算能力，永远满足性能上的要求。一种称集群的技术出现了，它把多台服务器连接起来，当成一台服务器来用。这种技术的好处就是，不但对客户来说是透明的，对服务器软件来说也是透明的，软件不用做任何修改就可以在集群上运行。集群技术的应用范围也仅限于此，只能提高同一个软件的计算能力，而对于多个不同的软件协同工作无能为力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通用型分布式计算环境。&lt;/strong&gt;如CORBA/DCOM/ RMI/ DBUS等，这些技术(规范)差不多都有具有网络透明性，被调用的方法可能在另外一个进程中，也可能在另外一台机器上。调用者基本上不用关心是本地调用还是远程调用。当然正是这种透明性，造成了分布式计算的滥用，分布式计算用起来方便，大家以为它免费的。实际上，分布式计算的代价是可观的，据说跨进程的调用，速度可能会降低一个数量级，跨机器的调用，速度可能降低两个数量级。一些专家都建议减少使用分布式计算，即使要使用，也要使用粗粒度的调用，以减少调用的次数。&lt;/p&gt;
&lt;p&gt;还其一些混合形式(SOAP?)，这里不再多说。我们主要介绍第三种分布式模型，这类分布式模型即适用于企业级应用，也适用于桌面应用。有的专注于企业级应用（如CORBA），有的专注于桌面环境（如DBUS）。它们的实现原理都差不多，基本上都基于传统的RPC或者仿RPC实现的，下面介绍一下它们的基本原理。&lt;/p&gt;
&lt;p&gt;我们先看一下分布式的最简模型：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://www.limodev.cn/gallery/albums/blog-pictures/o_rpc.jpg&quot; alt=&quot;model&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在传统的方法中，调用一个对象的函数很简单：创建这个对象，然后调用它的函数就行了。而在分布式的环境中，对象在另外一个进程中，完全在不同的地址空间里，要调用它的函数可能有点困难了。&lt;/p&gt;
&lt;p&gt;看看传统的C/S模型的请求方式，客户端把参数通过网络发给服务器，服务器根据参数要求完成相应的服务，然后把结果返回给客户端，客户端拿到结果了，一次请求算完成。由此看来，调用远程对象似乎并不难，问题在于这种方式不是网络透明的，每一个细节你都要自己处理，非常复杂。&lt;/p&gt;
&lt;p&gt;要简化软件的设计，当然是网络操作透明化，调用者和实现者都无需关心网络操作。要做到这一点，我们可以按下列方法：&lt;/p&gt;
&lt;p&gt;在客户端要引入一个代理(Proxy)对象。它全权代理实际对象，调用者甚至都不知道它是一个代理，可以像调用本地对象一样调用这个对象。当调用者调用Proxy的函数时，Proxy并不做实际的操作，而是把这些参数打包成一个网络数据包，并把这个数据包通过网络发送给服务器。&lt;/p&gt;
&lt;p&gt;在服务器引入一个桩(Stub)对象，Stub收到Proxy发送的数据包之后，把数据包解开，重新组织为参数列表，并用这些参数就调用实际对象的函数。实际对象执行相关操作，把结果返回给Stub，Stub再把结果打包成一个网络数据包，并把这个数据包通过网络发送给客户端的Proxy。&lt;/p&gt;
&lt;p&gt;Proxy收到结果数据包后，把数据包解开为返回值，返回给调用者。至此，整个操作完成了。怎么样，简化吧。&lt;/p&gt;
&lt;p&gt;Proxy隐藏了客户端的网络操作，Stub隐藏了服务器端的网络操作，这就实现了网络透明化。你也许会说，根本没有简化，只是把网络操作隔离开了，仍然要去实现Proxy和Stub两个对象，一样的麻烦。&lt;/p&gt;
&lt;p&gt;没错。不过仔细研究一下Proxy和Stub的功能，我们会发现，对于不同对象，这些操作都差不多，无非就是打包和解包而已，单调重复。单调重复的东西必然有规律可循，有规律可循就可以用代码产生器自动产生代码。&lt;/p&gt;
&lt;p&gt;像DCOM和CORBA等也确实是这样做的，先用IDL语言描述出对象的接口，然后用IDL编译器自动产生Proxy和Stub代码，整个过程完全不需要开发人员操心。&lt;/p&gt;
&lt;p&gt;打包和解包的专业术语叫做marshal和unmarshal，中文常用翻译为列集和散集。不过这两个词太专业了，翻译成中文之后更加让人不知所云。我想还是用打包和解包两个词更通俗一点。&lt;/p&gt;
&lt;p&gt;在以上模型中，调用对象的方法，确实做到了网络透明化。读者可以会问，我要访问对象的属性怎么办呢？对象的属性就是变量，变量就一块内存区域，内存区域在不同的进程里完全是独立的，这看起来确实是一个问题。还记得很多关于软件设计书籍里面讲过的吗：不要暴露对象属性，调用者若要访问对象的属性，通过get/set方法去访问。这样不行了吗，对属性的访问转换为对对象方法的调用。&lt;/p&gt;
&lt;p&gt;OK，调用对象的方法和访问对象的属性都解决了。还有重要的一点，如何创建对象呢。因为实际的对象并不固定在某台机器上，它的位置可能是动态的。甚至Proxy本身也不知道Stub运行在哪里。如果要让调用者来指定，创建对象的过程仍未达到网络透明化。通常的做法是引入一个第三方中介，这个第三方中介是固定的，可以通过一定的方法找到它。第三方中介负责在客户端的Proxy和服务器的Stub之间穿针引线。第三方中介通常有两种：一种是只负责帮客户端找到服务器，之后客户端与服务器直接通信。另外一种就是不但负责找到服务器，而且负责转发所有的请求。&lt;/p&gt;
&lt;p&gt;以上的模型仍然不完整，因为现实中的对象并不是一直处理于被动的地位。而是在一定的条件下，会主动触发一些事件，并把这些事件上报给调用者。也就是说这是一个双向的动作，单纯的C/S模型无法满足要求，而要采用P2P的方式。原先的客户端同时作为一个服务器存，接受来自己服务器的请求。像COM里就是这样做的，客户端要注册对象的事件，就要实现一个IDispatch接口，给对象反过来调用。&lt;/p&gt;
&lt;p&gt;自己实现时还要考虑以下几点：&lt;/p&gt;
&lt;p&gt;o 传输抽象层。分布可能是跨进程也可能是跨机器。在不同的情况下，采用不同的通信方式，性能会有所不同。做一个传输抽象层，在不同的情况下，可选用不同的传输方式，是一种好的设计。&lt;/p&gt;
&lt;p&gt;o 文本还是二进制。把数据打包成文本还是二进制？打包成文本的好处是，可移植性好，由于人也可以看懂，调试方便。坏处是速度稍慢，打包后的数据大小会明显变大。采用二进制的好处是，速度快，打包后的数据大小与打包前相差不大。坏处是不易调试，可移植性较差。&lt;/p&gt;
&lt;p&gt;o 字节顺序和字节对齐。若采用二进制方式传输，可移植性是个问题。因为不同的机器上，字节顺序和字节对齐的方式都有些差异，在数据包中要加入这些说明，以提高可移植性。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">插件式设计的架构模型与实例</title>
		<link href="http://www.limodev.cn/blog/?p=411"/>
		<id>http://www.limodev.cn/blog/?p=411</id>
		<updated>2008-12-02T13:56:00+00:00</updated>
		<content type="html">&lt;pre&gt;
           ----Do not call us, we will call you
&lt;/pre&gt;
&lt;p&gt;插件式设计近年来非常流行，其中eclipse起了推波助澜的作用，提到插件式就会不由自主的想到eclipse。其实插件式设计并不是什么新事物，早在几十年前就有了。像X Server就是基于插件式设计的，除了核心功能外，它所有的扩展功能和设备驱动都是以插件方式加入进来的。&lt;/p&gt;
&lt;p&gt;基于插件的设计好处很多：把扩展功能从框架中剥离出来，降低了框架的复杂度，让框架更容易实现。扩展功能与框架以一种很松的方式耦合，两者在保持接口不变的情况下，可以独立变化和发布。公开插件接口，让第三方有机会扩展应用程序的功能，有财大家一起发。另外，还可以让开源与闭源共存于一套软件，你的插件是开源还是闭源，完全由你自己决定。&lt;/p&gt;
&lt;p&gt;基于插件设计并不神秘，相反它比起一团泥的设计更简单，更容易理解。各种基于插件设计的架构都有自己的特色，但从总体架构上看，其模型都大同小异。这里我们介绍一个简单的模型，并给出几个实例，希望对新手有所启发。&lt;/p&gt;
&lt;p&gt;1. 基本架构&lt;/p&gt;
&lt;p&gt;插件式设计的应用程序，基本上可以用上图来表示。当然，此图是一种较高层次的表示，实际的设计会更复杂一些。我们在这里为了阐述方便，不用故意搞得那么复杂。&lt;/p&gt;
&lt;p&gt;应用程序由应用程序框架、插件接口、插件和公共函数库四部分组成。&lt;/p&gt;
&lt;p&gt;应用程序框架负责应用程序的整体运作，它清楚程序整个流程，但并不知道每个过程具体要做什么。它在适当的时候调用一些插件，来完成真正的功能。&lt;/p&gt;
&lt;p&gt;插件接口是一个协议，可能用IDL描述，可能是头文件，也可能一段文字说明。插件按照这个协议实现出来，就可以加入到应用程序中来。当然，对于复杂的系统，插件接口可能有多个，各自具有独立的功能。&lt;/p&gt;
&lt;p&gt;插件是完成实际功能的实体，实现了要求的插件接口。尽管实现什么以及怎么实现，完全是插件自己的自由。在实际情况来，一般还是有些限制，因为插件接口本身可能就是一个限制。如，实现编译功能的插件，自然不能实现成一个聊天功能的插件。&lt;/p&gt;
&lt;p&gt;公共函数库是一组函数或者类，应用程序框架和插件都可以调用。它通常是一个独立的动态库（DLL）。应用程序框架本身是公用的，是代码复用的一种方式。但并不是所有可复用代码都可以放在框架中，特别是插件会用到的公共代码，那会造成插件对框架的依赖。把这些公共代码提取到一个独立的库中，是一种好的方法。&lt;/p&gt;
&lt;p&gt;另外，值得补充说明一下的是插件接口。插件接口通常有两种：&lt;/p&gt;
&lt;p&gt;通用插件接口：这一类插件接口是通用的，你无法从接口函数看出这个插件的功能。它的接口函数通常有这些函数：&lt;/p&gt;
&lt;p&gt;init : 用于初始化插件，通常在插件被加载时调用。&lt;/p&gt;
&lt;p&gt;deinit：用于反初始化插件，通常在插件被卸载时调用。&lt;/p&gt;
&lt;p&gt;run：让插件起动。&lt;/p&gt;
&lt;p&gt;stop：让插件停止。&lt;/p&gt;
&lt;p&gt;至于插件要完成什么功能，要插到哪里，在init函数里决定，它调用公共函数库里的函数把自己注册到框架中某个位置。&lt;/p&gt;
&lt;p&gt;专用插件接口：这一类插件接口是专用的，看到它的接口函数说明，你就可以大致了解它的功能了。&lt;/p&gt;
&lt;p&gt;加入插件的方式通常采用配置信息来实现，配置信息可以是注册表，也可以配置文件。也可以动态注册进来，或者把插件放到指定的位置。&lt;/p&gt;
&lt;p&gt;下面我们来看几个实例：&lt;/p&gt;
&lt;p&gt;2. 桌面设计&lt;/p&gt;
&lt;p&gt;最近一段时间完成了桌面模块的设计和实现。按照以往的经验，桌面模块通常是变化最多的一个模块，SPEC总是在不断的调整的效果，不同客户要求实现具有个性化的桌面，直到产品快发布了，桌面的SPEC还在不停的修改。另外，在智能手机中，桌面占有特殊的地位，很多东西都可能往桌面里塞，桌面不但是各种功能的大杂烩，还是一些系统消息的中转站。&lt;/p&gt;
&lt;p&gt;这个任务比较棘手，所以在设计时就分外小心。首先想到的就是采用插件式设计，把外围功能独立出来，尽量简化框架的实现。&lt;/p&gt;
&lt;p&gt;插件：每一个最小功能单元都是一个插件，它可以是可见的，也可以是不可的，也可以是动态变化的。比如时间、电池电量、网络连接、信号强弱、新事件(如SMS、MMS、EMAL、ALARM和未接电话等)、应用程序快捷方式、左右操作按钮和其它处理系统事件的功能单元。每个插件都用一个.desktop来描述，这是遵循freedesktop.org的标准的。&lt;/p&gt;
&lt;p&gt;桌面框架包括：状态栏、开始菜单、操作栏、桌面区、事件管理器和主题管理器。而状态栏、开始菜单、操作栏、桌面区和事件管理器都是容器，容纳各种插件。对于可见的插件，可以有自己的表现方式，也可以采用通用的表现方式。&lt;/p&gt;
&lt;p&gt;公共函数库：一些抽象的类、实现插件的辅助类以及其它一些可能被公用的类。&lt;/p&gt;
&lt;p&gt;插件接口：对于不可见的插件要求实现事件处理功能，可见的插件还要求实现绘制功能。&lt;/p&gt;
&lt;p&gt;3. 模拟器设计&lt;/p&gt;
&lt;p&gt;一个同事负责设计另外一个平台的PC模拟环境设计。在我的建议下，他对架构作了调整。调整后的架构非常简单，也可以认为是插件式的设计，它由下面几部分组成：&lt;/p&gt;
&lt;p&gt;应用程序框架：负责模拟器基本功能，如模拟键盘和显示设备、换肤功能等。&lt;/p&gt;
&lt;p&gt;插件：就是被模拟的平台，如microwindow及相应的手机应用程序。尽管运行时通常只有一个插件运行，这样做仍然有意义，如果要换成minigui或者其它平台时，模拟器不需要作任何修改。&lt;/p&gt;
&lt;p&gt;公共函数库：它由应用程序框架初始化一些信息和回调函数，然后供插件(即microwindow)调用，插件利用它来实现显示和输入等驱动程序。&lt;/p&gt;
&lt;p&gt;插件接口：如起动和停止模拟平台等。&lt;/p&gt;
&lt;p&gt;4. GIMP&lt;/p&gt;
&lt;p&gt;GIMP是一个功能强大的图形图像编辑器，典型的基于插件式的设计，在《unix编程艺术》中，作为插件式设计示例介绍过。&lt;/p&gt;
&lt;p&gt;o 应用程序框架：GUI&lt;/p&gt;
&lt;p&gt;o 插件：完成图像的各种转换和处理功能，如模糊、去斑和色彩调整等。&lt;/p&gt;
&lt;p&gt;o 公共函数库：放在libgimp.so里。&lt;br /&gt;
o 插件接口：对GIMP感兴趣的朋友，可以到官方网站上去阅读更多的文档。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">KJAVA虚拟机Hack笔记-实现immutableimage</title>
		<link href="http://www.limodev.cn/blog/?p=408"/>
		<id>http://www.limodev.cn/blog/?p=408</id>
		<updated>2008-12-01T13:37:12+00:00</updated>
		<content type="html">&lt;p&gt;immutable image在这里的意思是说不能在上面进行绘制操作的图片，比如画直线和填充矩形等等。immutable image实际上就是图片在内存里面的表示，有点像VC中的Bitmap，用GTK+中实现的话，当然首选GdkPixbuf了。&lt;/p&gt;
&lt;p&gt;里面最重要的函数要数gxpport_decodeimmutable_from_selfidentifying了，这个函数用来加载图片数据到一个immutable image对象中，，由于图片数据已经读入到内存中了，为了简单起见，我们把它写回到临时文件里，临时文件系统是用内存来模拟的，所以对性能的影响也是不会太大。实现如下：&lt;/p&gt;
&lt;p&gt;得到文件格式：&lt;/p&gt;
&lt;pre&gt;
err = imgdcd_image_get_info(srcBuffer, (unsigned int)length, &amp;#038;format, &amp;#038;w, &amp;#038;h);
&lt;/pre&gt;
&lt;p&gt;加载图片到GdkPixbuf中：&lt;br /&gt;
对于jpg、png和gif文件，我们用下列方法加载：&lt;/p&gt;
&lt;pre&gt;
pixbuf = gdk_pixbuf_load_from_data(&quot;jpg&quot;, (const char*)srcBuffer, length);
pixbuf = gdk_pixbuf_load_from_data(&quot;png&quot;, (const char*)srcBuffer, length);
pixbuf = gdk_pixbuf_load_from_data(&quot;gif&quot;, (const char*)srcBuffer, length);
&lt;/pre&gt;
&lt;p&gt;gdk_pixbuf_load_from_data的实现就是先把文件写入临时文件，再调用gdk_pixbuf_new_from_file加载文件到GdkPixbuf中。&lt;/p&gt;
&lt;p&gt;对于RAW的数据，调用gdk_pixbuf_new_from_data加载。&lt;/p&gt;
&lt;p&gt;另就是要检查资源限制：&lt;/p&gt;
&lt;pre&gt;
        w = gdk_pixbuf_get_width(pixbuf);
        h = gdk_pixbuf_get_height(pixbuf);

        res_size = w * h * 4;
        if (midpCheckResourceLimit(RSC_TYPE_IMAGE_IMMUT, res_size) == 0)
        {
            g_object_unref(G_OBJECT(pixbuf));
            *creationErrorPtr = IMG_NATIVE_IMAGE_RESOURCE_LIMIT;
            return;
        }

        *creationErrorPtr = IMG_NATIVE_IMAGE_NO_ERROR;
        midpIncResourceCount(RSC_TYPE_IMAGE_IMMUT, res_size);
&lt;/pre&gt;
&lt;p&gt;如果虚拟机所用的资源超过指定值时，释放GdkPixbuf并返回错误RESOURCE_LIMIT。&lt;/p&gt;
&lt;p&gt;其它函数实现就更简单了：&lt;/p&gt;
&lt;p&gt;gxpport_createimmutable_from_immutableregion 调用gdk_pixbuf_new_subpixbuf从pixbuf上拷贝一块数据，再根据transform做些转换。&lt;/p&gt;
&lt;p&gt;gxpport_decodeimmutable_from_argb 调用gdk_pixbuf_new_from_data生成pixbuf对象。&lt;/p&gt;
&lt;p&gt;gxpport_get_immutable_argb 把pixbuf中的数据转换成argb格式的buffer。&lt;/p&gt;
&lt;p&gt;gxpport_destroy_immutable 调用g_object_unref就行了。&lt;/p&gt;
&lt;p&gt;gxpport_decodeimmutable_to_platformbuffer 原样保存就好了。&lt;/p&gt;
&lt;p&gt;gxpport_loadimmutable_from_platformbuffer 调用gxpport_decodeimmutable_from_selfidentifying加载数据。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-写得又快又好的秘诀(一)</title>
		<link href="http://www.limodev.cn/blog/?p=406"/>
		<id>http://www.limodev.cn/blog/?p=406</id>
		<updated>2008-11-30T10:00:28+00:00</updated>
		<content type="html">&lt;p&gt;“快”是指开发效率高，“好”是指软件质量高。呵呵，写得又快又好的人就是高手了。记得这是林锐博士下的定义，读他那篇著名的《C/C++高质量编程》时，我还是个初学者，印象特别深。我现在仍然赞同他的观点，不过这里标题改为成为高手的秘诀，感觉就有点像标题党了，所以还是用比较通俗的说法吧。废话少说，请读者回顾一下这段时间的编程经验，回答下面两个问题：&lt;/p&gt;
&lt;p&gt;1.快与好是什么关系？写得快就不能写得好？写得好就不能写得快？还是写得好才能写得快？是不是绕晕了？不过这确实是值得思考的问题。&lt;/p&gt;
&lt;p&gt;2.我们的时间花在哪里了？记得刚来深圳时到华为面试，面试的人是我的学长。他问我，你一天能写多少行代码？我想了想说，100行吧。他用看外行的眼光看着我说，能写100行吗？我知道说错话了，赶快补充说，嗯，从整个项目来看可能没有吧。他才点了点头。一天只写100行代码？初学者可能觉得不可思议，以同时应付10个网友聊天的速度，写100行代码不用三分钟。不过，经过这段时间的练习后，我们想大家已经明白，敲代码不是花时间最多的地方，那时间又花到哪里去了呢？&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">大内高手—栈/堆</title>
		<link href="http://www.limodev.cn/blog/?p=402"/>
		<id>http://www.limodev.cn/blog/?p=402</id>
		<updated>2008-11-30T09:59:26+00:00</updated>
		<content type="html">&lt;p&gt;o 栈&lt;/p&gt;
&lt;p&gt;栈作为一种基本数据结构，我并不感到惊讶，用来实现函数调用，这也司空见惯的作法。直到我试图找到另外一种方式实现递归操作时，我才感叹于它的巧妙。要实现递归操作，不用栈不是不可能，而是找不出比它更优雅的方式。&lt;/p&gt;
&lt;p&gt;尽管大多数编译器在优化时，会把常用的参数或者局部变量放入寄存器中。但用栈来管理函数调用时的临时变量（局部变量和参数）是通用做法，前者只是辅助手段，且只在当前函数中使用，一旦调用下一层函数，这些值仍然要存入栈中才行。&lt;/p&gt;
&lt;p&gt;通常情况下，栈向下（低地址）增长，每向栈中PUSH一个元素，栈顶就向低地址扩展，每从栈中POP一个元素，栈顶就向高地址回退。一个有兴趣的问题：在x86平台上，栈顶寄存器为ESP，那么ESP的值在是PUSH操作之前修改呢，还是在PUSH操作之后修改呢？PUSH ESP这条指令会向栈中存入什么数据呢？据说x86系列CPU中，除了286外，都是先修改ESP，再压栈的。由于286没有CPUID指令，有的OS用这种方法检查286的型号。&lt;/p&gt;
&lt;p&gt;一个函数内的局部变量以及其调用下一级函数的参数，所占用的内存空间作为一个基本的单元，称为一个帧(frame)。在gdb里，f 命令就是用来查看指定帧的信息的。在两个frame之间通过还存有其它信息，比如上一层frame的分界地址(EBP)等。&lt;/p&gt;
&lt;p&gt;关于栈的基本知识，就先介绍这么多，我们下面来看看一些关于栈的技巧及应用：&lt;/p&gt;
&lt;p&gt;1.  backtrace的实现&lt;/p&gt;
&lt;p&gt;callstack调试器的基本功能之一，利用此功能，你可以看到各级函数的调用关系。在gdb中，这一功能被称为backtrace，输入bt命令就可以看到当前函数的callstack。它的实现多少有些有趣，我们在这里研究一下。&lt;/p&gt;
&lt;p&gt;我们先看看栈的基本模型&lt;/p&gt;
&lt;pre&gt;
参数N -------------↓高地址
参数…--------------函数参数入栈的顺序与具体的调用方式有关
参数 3
参数 2
参数 1
-----------------------------------------------------------
EIP-----------------返回本次调用后，下一条指令的地址
EBP-----------------保存调用者的EBP，然后EBP指向此时的栈顶。
临时变量1
临时变量2
临时变量3
临时变量…
临时变量N-----------↓低地址
&lt;/pre&gt;
&lt;p&gt;要实现callstack我们需要知道以下信息：&lt;/p&gt;
&lt;p&gt;o 调用函数时的指令地址（即当时的EIP）。&lt;/p&gt;
&lt;p&gt;o 指令地址对应的源代码代码位置。&lt;/p&gt;
&lt;p&gt;关于第一点，从上表中，我们可以看出，栈中存有各级EIP的值，我们取出来就行了。用下面的代码可以轻易实现：&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdio.h&gt;

int backtrace(void** BUFFER, int SIZE)
{
    int  n = 0;
    int* p = &amp;#038;n;
    int  i = 0;
    int ebp = p[1];
    int eip = p[2];

    for(i = 0; i &amp;lt; SIZE; i++)
    {
        BUFFER[i] = (void*)eip;

        p = (int*)ebp;

        ebp = p[0];

        eip = p
    }

    return SIZE;
}

#define N 4

static void test2()
{

    int i = 0;
    void* BUFFER[N] = {0};

    backtrace(BUFFER, N);

    for(i = 0; i &amp;lt; N; i++)
    {
        printf(&quot;%p\n&quot;,  BUFFER[i]);
    }

    return;
}

static void test1()
{
    test2();
}

static void test()
{
    test1();
}

int main(int argc, char* argv[])
{
    test();

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;程序输出：&lt;/p&gt;
&lt;pre&gt;
0x8048460
0x804849c
0x80484a9
0x80484cc
&lt;/pre&gt;
&lt;p&gt;关于第二点，如何把指令地址与行号对应起来，这也很简单。可以从MAP文件或者ELF中查询。Binutil带有一个addr2line的小工具，可以帮助实现这一点。&lt;/p&gt;
&lt;pre&gt;
[root@linux bt]# addr2line  0x804849c -e bt.exe

/root/test/bt/bt.c:42
&lt;/pre&gt;
&lt;p&gt;2.  alloca的实现&lt;/p&gt;
&lt;p&gt;大家都知道动态分配的内存，一定要释放掉，否则就会有内存泄露。可能鲜有人知，动态分配的内存，可以不用释放。Alloca就是这样一个函数，最后一个a代表auto，即自动释放的意思。&lt;/p&gt;
&lt;p&gt;Alloca是在栈中分配内存的。即然是在栈中分配，就像其它在栈中分配的临时变量一样，在当前函数调用完成时，这块内存自动释放。&lt;/p&gt;
&lt;p&gt;正如我们前面讲过，栈的大小是有限制的，普通线程的栈只有10M大小，所以在分配时，要量力而行，且不要分配过大内存。&lt;/p&gt;
&lt;p&gt;Alloca可能会渐渐的退出历史舞台，原因是新的C/C++标准都支持变长数组。比如int array[n]，老版本的编译器要求n是常量，而新编译器允许n是变量。编译器支持的这一功能完全可以取代alloca。&lt;/p&gt;
&lt;p&gt;这不是一个标准函数，但像linux和win32等大多数平台都支持。即使少数平台不支持，要自己实现也不难。这里我们简单介绍一下alloca的实现方法。&lt;/p&gt;
&lt;p&gt;我们先看看一个小程序，再看看它对应的汇编代码，一切都清楚了。&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdio.h&gt;

int main(int argc, char* argv[])
{
    int  n = 0;
    int* p = alloca(1024);

    printf(&quot;&amp;#038;n=%p p=%p\n&quot;, &amp;#038;n, p);

    return 0;
}

汇编代码为：

int main(int argc, char* argv[])
{

 8048394:       55                      push   %ebp
 8048395:       89 e5                   mov    %esp,%ebp
 8048397:       83 ec 18                sub    $0x18,%esp
 804839a:       83 e4 f0                and    $0xfffffff0,%esp
 804839d:       b8 00 00 00 00          mov    $0x0,%eax
 80483a2:       83 c0 0f                add    $0xf,%eax
 80483a5:       83 c0 0f                add    $0xf,%eax
 80483a8:       c1 e8 04                shr    $0x4,%eax
 80483ab:       c1 e0 04                shl    $0x4,%eax
 80483ae:       29 c4                   sub    %eax,%esp
        int  n = 0;
 80483b0:       c7 45 fc 00 00 00 00    movl   $0x0,0xfffffffc(%ebp)
        int* p = alloca(1024);
 80483b7:       81 ec 10 04 00 00       sub    $0x410,%esp
 80483bd:       8d 44 24 0c             lea    0xc(%esp),%eax
 80483c1:       83 c0 0f                add    $0xf,%eax
 80483c4:       c1 e8 04                shr    $0x4,%eax
 80483c7:       c1 e0 04                shl    $0x4,%eax
 80483ca:       89 45 f8                mov    %eax,0xfffffff8(%ebp)

        printf(&quot;&amp;#038;n=%p p=%p\n&quot;, &amp;#038;n, p);
 80483cd:       8b 45 f8                mov    0xfffffff8(%ebp),%eax
 80483d0:       89 44 24 08             mov    %eax,0x8(%esp)
 80483d4:       8d 45 fc                lea    0xfffffffc(%ebp),%eax
 80483d7:       89 44 24 04             mov    %eax,0x4(%esp)
 80483db:       c7 04 24 98 84 04 08    movl   $0x8048498,(%esp)
 80483e2:       e8 d1 fe ff ff          call   80482b8 &amp;lt;printf@plt&gt;
        return 0;
 80483e7:       b8 00 00 00 00          mov    $0x0,%eax

}
&lt;/pre&gt;
&lt;p&gt;其中关键的一条指令为：sub    $0&amp;#215;410,%esp&lt;/p&gt;
&lt;p&gt;由此可以看出实现alloca，仅仅是把ESP减去指定大小，扩大栈空间（记记住栈是向下增长），这块空间就是分配的内存。&lt;/p&gt;
&lt;p&gt;3.  可变参数的实现。&lt;/p&gt;
&lt;p&gt;对新手来说，可变参数的函数也是比较神奇。还是以一个小程序来说明它的实现。&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdio.h&gt;
#include &amp;lt;stdarg.h&gt;

int print(const char* fmt, ...)
{
    int n1 = 0;
    int n2 = 0;
    int n3 = 0;
    va_list ap;
    va_start(ap, fmt);

    n1 = va_arg(ap, int);
    n2 = va_arg(ap, int);
    n3 = va_arg(ap, int);

    va_end(ap);

    printf(&quot;n1=%d n2=%d n3=%d\n&quot;, n1, n2, n3);

    return 0;
}

int main(int arg, char argv[])
{
    print(&quot;%d\n&quot;, 1, 2, 3);

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;我们看看对应的汇编代码：&lt;/p&gt;
&lt;pre&gt;
int print(const char* fmt, ...)
{

 8048394:       55                      push   %ebp
 8048395:       89 e5                   mov    %esp,%ebp
 8048397:       83 ec 28                sub    $0x28,%esp
        int n1 = 0;
 804839a:       c7 45 fc 00 00 00 00    movl   $0x0,0xfffffffc(%ebp)
        int n2 = 0;
 80483a1:       c7 45 f8 00 00 00 00    movl   $0x0,0xfffffff8(%ebp)
        int n3 = 0;
 80483a8:       c7 45 f4 00 00 00 00    movl   $0x0,0xfffffff4(%ebp)
        va_list ap;
        va_start(ap, fmt);
 80483af:       8d 45 0c                lea    0xc(%ebp),%eax
 80483b2:       89 45 f0                mov    %eax,0xfffffff0(%ebp)

        n1 = va_arg(ap, int);
 80483b5:       8b 55 f0                mov    0xfffffff0(%ebp),%edx
 80483b8:       8d 45 f0                lea    0xfffffff0(%ebp),%eax
 80483bb:       83 00 04                addl   $0x4,(%eax)
 80483be:       8b 02                   mov    (%edx),%eax
 80483c0:       89 45 fc                mov    %eax,0xfffffffc(%ebp)
        n2 = va_arg(ap, int);
 80483c3:       8b 55 f0                mov    0xfffffff0(%ebp),%edx
 80483c6:       8d 45 f0                lea    0xfffffff0(%ebp),%eax
 80483c9:       83 00 04                addl   $0x4,(%eax)
 80483cc:       8b 02                   mov    (%edx),%eax
 80483ce:       89 45 f8                mov    %eax,0xfffffff8(%ebp)
        n3 = va_arg(ap, int);

 80483d1:       8b 55 f0                mov    0xfffffff0(%ebp),%edx
 80483d4:       8d 45 f0                lea    0xfffffff0(%ebp),%eax
 80483d7:       83 00 04                addl   $0x4,(%eax)
 80483da:       8b 02                   mov    (%edx),%eax
 80483dc:       89 45 f4                mov    %eax,0xfffffff4(%ebp)

       va_end(ap);
       printf(&quot;n1=%d n2=%d n3=%d\n&quot;, n1, n2, n3);
 80483df:       8b 45 f4                mov    0xfffffff4(%ebp),%eax
 80483e2:       89 44 24 0c             mov    %eax,0xc(%esp)
 80483e6:       8b 45 f8                mov    0xfffffff8(%ebp),%eax
 80483e9:       89 44 24 08             mov    %eax,0x8(%esp)
 80483ed:       8b 45 fc                mov    0xfffffffc(%ebp),%eax
 80483f0:       89 44 24 04             mov    %eax,0x4(%esp)
 80483f4:       c7 04 24 f8 84 04 08    movl   $0x80484f8,(%esp)
 80483fb:       e8 b8 fe ff ff          call   80482b8 &amp;lt;printf@plt&gt;

        return 0;
 8048400:       b8 00 00 00 00          mov    $0x0,%eax
}

int main(int arg, char argv[])
{
 8048407:       55                      push   %ebp
 8048408:       89 e5                   mov    %esp,%ebp
 804840a:       83 ec 18                sub    $0x18,%esp
 804840d:       83 e4 f0                and    $0xfffffff0,%esp
 8048410:       b8 00 00 00 00          mov    $0x0,%eax
 8048415:       83 c0 0f                add    $0xf,%eax
 8048418:       83 c0 0f                add    $0xf,%eax
 804841b:       c1 e8 04                shr    $0x4,%eax
 804841e:       c1 e0 04                shl    $0x4,%eax
 8048421:       29 c4                   sub    %eax,%esp
        int n = print(&quot;%d\n&quot;, 1, 2, 3);
 8048423:       c7 44 24 0c 03 00 00    movl   $0x3,0xc(%esp)
 804842a:       00
 804842b:       c7 44 24 08 02 00 00    movl   $0x2,0x8(%esp)
 8048432:       00
 8048433:       c7 44 24 04 01 00 00    movl   $0x1,0x4(%esp)
 804843a:       00
 804843b:       c7 04 24 0b 85 04 08    movl   $0x804850b,(%esp)
 8048442:       e8 4d ff ff ff          call   8048394 &amp;lt;print&gt;
 8048447:       89 45 fc                mov    %eax,0xfffffffc(%ebp)

        return 0;
 804844a:       b8 00 00 00 00          mov    $0x0,%eax

}
&lt;/pre&gt;
&lt;p&gt;从汇编代码中，我们可以看出，参数是逆序入栈的。在取参数时，先让ap指向第一个参数，又因为栈是向下增长的，不断把指针向上移动就可以取出所有参数了。&lt;/p&gt;
&lt;p&gt;o 堆&lt;/p&gt;
&lt;p&gt;在内存分配算法一节中再详细讲解。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">大内高手—内存模型</title>
		<link href="http://www.limodev.cn/blog/?p=400"/>
		<id>http://www.limodev.cn/blog/?p=400</id>
		<updated>2008-11-30T09:47:03+00:00</updated>
		<content type="html">&lt;p&gt;了解linux的内存模型，或许不能让你大幅度提高编程能力，但是作为一个基本知识点应该熟悉。坐火车外出旅行时，即时你对沿途的地方一无所知，仍然可以到达目标地。但是你对整个路途都很比较清楚的话，每到一个站都知道自己在哪里，知道当地的风土人情，对比一下所见所想，旅程可能更有趣一些。&lt;/p&gt;
&lt;p&gt;类似的，了解linux的内存模型，你知道每块内存，每个变量，在系统中处于什么样的位置。这同样会让你心情愉快，知道这些，有时还会让你的生活轻更松些。看看变量的地址，你可以大致断定这是否是一个有效的地址。一个变量被破坏了，你可以大致推断谁是犯罪嫌疑人。&lt;/p&gt;
&lt;p&gt;Linux的内存模型，一般为：&lt;/p&gt;
&lt;pre&gt;
地址------------------------作用--------------------------说明

&gt;=0xc0000000---------内核虚拟存储器-------------用户代码不可见区域
&amp;lt; 0xc0000000----------Stack（用户栈）------------ESP指向栈顶，向下增长
-------------------------------------------------空闲内存
&gt;=0x40000000---------文件映射区------------------mmap的空间
&amp;lt; 0x40000000-------------------------------------空闲内存
---------------------Heap(运行时堆)--------------通过brk/sbrk系统调用扩大堆，向上增长。
---------------------.data、.bss(读写段)---------从可执行文件中加载
&gt;=0x08048000---------.init、.text、.rodata-------从可执行文件中加载
&amp;lt; 0x08048000---------保留区域--------------------
&lt;/pre&gt;
&lt;p&gt;很多书上都有类似的描述，本图取自于《深入理解计算机系统》p603，略做修改。本图比较清析，很容易理解，但仍然有两点不足。下面补充说明一下：&lt;/p&gt;
&lt;p&gt;1. 第一点是关于运行时堆的。&lt;/p&gt;
&lt;p&gt;为说明这个问题，我们先运行一个测试程序，并观察其结果：&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdio.h&gt;

int main(int argc, char* argv[])
{

    int  first = 0;
    int* p0 = malloc(1024);
    int* p1 = malloc(1024 * 1024);
    int* p2 = malloc(512 * 1024 * 1024 );
    int* p3 = malloc(1024 * 1024 * 1024 );

    printf(&quot;main=%p print=%p\n&quot;, main, printf);
    printf(&quot;first=%p\n&quot;, &amp;#038;first);
    printf(&quot;p0=%p p1=%p p2=%p p3=%p\n&quot;, p0, p1, p2, p3);

    getchar();

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;运行后，输出结果为：&lt;/p&gt;
&lt;pre&gt;
main=0x8048404 print=0x8048324
first=0xbfcd1264
p0=0x9253008 p1=0xb7ec0008 p2=0x97ebf008 p3=0x57ebe008
&lt;/pre&gt;
&lt;p&gt;o main和print两个函数是代码段(.text)的，其地址符合表一的描述。&lt;/p&gt;
&lt;p&gt;o first是第一个临时变量，由于在first之前还有一些环境变量，它的值并非0xbfffffff，而是0xbfcd1264，这是正常的。&lt;/p&gt;
&lt;p&gt;o p0是在堆中分配的，其地址小于0&amp;#215;4000 0000，这也是正常的。&lt;/p&gt;
&lt;p&gt;o 但p1和p2也是在堆中分配的，而其地址竟大于0&amp;#215;4000 0000，与表一描述不符。&lt;/p&gt;
&lt;p&gt;原因在于：运行时堆的位置与内存管理算法相关，也就是与malloc的实现相关。关于内存管理算法的问题，我们在后继文章中有详细描述，这里只作简要说明。在glibc实现的内存管理算法中，Malloc小块内存是在小于0&amp;#215;4000 0000的内存中分配的，通过brk/sbrk不断向上扩展，而分配大块内存，malloc直接通过系统调用mmap实现，分配得到的地址在文件映射区，所以其地址大于0&amp;#215;4000 0000。&lt;/p&gt;
&lt;p&gt;从maps文件中可以清楚的看到一点：&lt;/p&gt;
&lt;pre&gt;
00514000-00515000 r-xp 00514000 00:00 0
00624000-0063e000 r-xp 00000000 03:01 718192     /lib/ld-2.3.5.so
0063e000-0063f000 r-xp 00019000 03:01 718192     /lib/ld-2.3.5.so
0063f000-00640000 rwxp 0001a000 03:01 718192     /lib/ld-2.3.5.so
00642000-00766000 r-xp 00000000 03:01 718193     /lib/libc-2.3.5.so
00766000-00768000 r-xp 00124000 03:01 718193     /lib/libc-2.3.5.so
00768000-0076a000 rwxp 00126000 03:01 718193     /lib/libc-2.3.5.so
0076a000-0076c000 rwxp 0076a000 00:00 0
08048000-08049000 r-xp 00000000 03:01 1307138    /root/test/mem/t.exe
08049000-0804a000 rw-p 00000000 03:01 1307138    /root/test/mem/t.exe
09f5d000-09f7e000 rw-p 09f5d000 00:00 0          [heap]
57e2f000-b7f35000 rw-p 57e2f000 00:00 0
b7f44000-b7f45000 rw-p b7f44000 00:00 0
bfb2f000-bfb45000 rw-p bfb2f000 00:00 0          [stack]
&lt;/pre&gt;
&lt;p&gt;2. 第二是关于多线程的。&lt;/p&gt;
&lt;p&gt;现在的应用程序，多线程的居多。表一所描述的模型无法适用于多线程环境。按表一所述，程序最多拥有上G的栈空间，事实上，在多线程情况下，能用的栈空间是非常有限的。为了说明这个问题，我们再看另外一个测试：&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdio.h&gt;
#include &amp;lt;pthread.h&gt;

void* thread_proc(void* param)
{
    int  first = 0;
    int* p0 = malloc(1024);
    int* p1 = malloc(1024 * 1024);

    printf(&quot;(0x%x): first=%p\n&quot;,    pthread_self(), &amp;#038;first);
    printf(&quot;(0x%x): p0=%p p1=%p \n&quot;, pthread_self(), p0, p1);

    return 0;
}

#define N 5

int main(int argc, char* argv[])
{

    int first = 0;
    int i= 0;
    void* ret = NULL;
    pthread_t tid[N] = {0};

    printf(&quot;first=%p\n&quot;, &amp;#038;first);

    for(i = 0; i &amp;lt; N; i++)
    {
        pthread_create(tid+i, NULL, thread_proc, NULL);
    } 

    for(i = 0; i &amp;lt; N; i++)
    {
        pthread_join(tid[i], &amp;#038;ret);
    }

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;运行后，输出结果为：&lt;/p&gt;
&lt;pre&gt;
first=0xbfd3d35c

(0xb7f2cbb0): first=0xb7f2c454
(0xb7f2cbb0): p0=0x84d52d8 p1=0xb4c27008
(0xb752bbb0): first=0xb752b454
(0xb752bbb0): p0=0x84d56e0 p1=0xb4b26008
(0xb6b2abb0): first=0xb6b2a454
(0xb6b2abb0): p0=0x84d5ae8 p1=0xb4a25008
(0xb6129bb0): first=0xb6129454
(0xb6129bb0): p0=0x84d5ef0 p1=0xb4924008
(0xb5728bb0): first=0xb5728454
(0xb5728bb0): p0=0x84d62f8 p1=0xb7e2c008
&lt;/pre&gt;
&lt;p&gt;我们看一下:&lt;/p&gt;
&lt;p&gt;主线程与第一个线程的栈之间的距离：0xbfd3d35c - 0xb7f2c454=0&amp;#215;7e10f08=126M&lt;/p&gt;
&lt;p&gt;第一个线程与第二个线程的栈之间的距离：0xb7f2c454 - 0xb752b454=0xa01000=10M&lt;/p&gt;
&lt;p&gt;其它几个线程的栈之间距离均为10M。&lt;/p&gt;
&lt;p&gt;也就是说，主线程的栈空间最大为126M，而普通线程的栈空间仅为10M，超这个范围就会造成栈溢出。&lt;/p&gt;
&lt;p&gt;栈溢出的后果是比较严重的，或者出现Segmentation fault错误，或者出现莫名其妙的错误。&lt;/p&gt;
&lt;p&gt;(测试环境RH9，其它环境会有些差异)&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">大内高手—序</title>
		<link href="http://www.limodev.cn/blog/?p=398"/>
		<id>http://www.limodev.cn/blog/?p=398</id>
		<updated>2008-11-30T09:32:55+00:00</updated>
		<content type="html">&lt;p&gt;我一直认为作为一个在linux下工作的C程序员，若对内存有深刻的认识，不但程序的性能会更高，运行更稳定，编程速度也会更快。反之亦有相反的效果，有时一些内存错误让你摸不着头脑，不但大大降低开发速度，开发出来的软件稳定性也值得怀疑。&lt;/p&gt;
&lt;p&gt;为了提高组员的编程水平，去年我制定了一系列的培训计划，并负责主讲部分重要课程，其中基础类课程中，有一堂关于内存的课程。当时的讲稿写得很粗略，后来的新同事说看不明白，我决定重新整理一下，放在BLOG，希望对新手有所帮助。&lt;/p&gt;
&lt;p&gt;至于文章的标题，基本上是为了搞笑。最近重温星爷经典《大内密探零零发》，这是以大内高手作为文章的标题原因之一。另外也可以这样理解，内指内存，至于大字，修饰内存指其容量大，修饰内存高手形容水平很高，两种理解都可以。&lt;/p&gt;
&lt;p&gt;其中包括下列文章：&lt;/p&gt;
&lt;p&gt;1. 大内高手—内存模型&lt;/p&gt;
&lt;p&gt;o 单线程模型&lt;/p&gt;
&lt;p&gt;o 多线程模型&lt;/p&gt;
&lt;p&gt;2. 大内高手—栈/堆&lt;/p&gt;
&lt;p&gt;o backtrace的实现&lt;/p&gt;
&lt;p&gt;o alloca的实现&lt;/p&gt;
&lt;p&gt;o 可变参数的实现。&lt;/p&gt;
&lt;p&gt;o malloc/free系列函数简介&lt;/p&gt;
&lt;p&gt;o new/delete系列操作符简介&lt;/p&gt;
&lt;p&gt;3. 大内高手—全局内存&lt;/p&gt;
&lt;p&gt;o .bss说明&lt;/p&gt;
&lt;p&gt;o .data说明&lt;/p&gt;
&lt;p&gt;o .rodata说明&lt;/p&gt;
&lt;p&gt;o violatile关键字说明&lt;/p&gt;
&lt;p&gt;o static关键字说明&lt;/p&gt;
&lt;p&gt;o const 关键字说明&lt;/p&gt;
&lt;p&gt;4. 大内高手—内存分配算法&lt;/p&gt;
&lt;p&gt;o 标准C(glibc)分配算法&lt;/p&gt;
&lt;p&gt;o STL(STLPort)分配算法&lt;/p&gt;
&lt;p&gt;o OS内部分配算法（伙伴/SLAB）&lt;/p&gt;
&lt;p&gt;5. 大内高手—惯用手法&lt;/p&gt;
&lt;p&gt;o 引用计数&lt;/p&gt;
&lt;p&gt;o 预先分配&lt;/p&gt;
&lt;p&gt;o 内存池&lt;/p&gt;
&lt;p&gt;o 会话池&lt;/p&gt;
&lt;p&gt;o …&lt;/p&gt;
&lt;p&gt;6. 大内高手—共享内存与线程局部存储&lt;/p&gt;
&lt;p&gt;7. 大内高手—自动内存回收机制&lt;/p&gt;
&lt;p&gt;8. 大内高手—常见内存错误&lt;/p&gt;
&lt;p&gt;9. 大内高手—常用调试工具&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">The first Beijing GNOME Users Group meeting</title>
		<link href="http://blogs.sun.com/emily/entry/the_first_beijing_gnome_users"/>
		<id>http://blogs.sun.com/emily/entry/the_first_beijing_gnome_users</id>
		<updated>2008-11-27T09:53:42+00:00</updated>
		<content type="html">&lt;p&gt;The first Beijing GNOME Users Group meeting was held on Nov 26th, 2008 in a restaurant in Beijing. For the first BeijingGUG meeting, we invite core remembers to discuss and plan the future program and activities of BeijingGUG.&lt;br /&gt;&lt;br /&gt;Below is the meeting minutes:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Time&lt;/b&gt;: 7:00pm - 9:30pm Wednesday Nov 26th, 2008&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Venue&lt;/b&gt;: Shenbidebao restaurant around Dongzhimen&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Attendees&lt;/b&gt;: Elly Liu, Emily Chen, Hong Yang, Lingtao Pan, Junyi Tang, Dalong Cheng, Sen Zhang, Botu Sun, Vincent Du&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Schedule&lt;/b&gt;:&lt;br /&gt;&lt;br /&gt;7:00 ~ 7:45pm&amp;nbsp;&amp;nbsp;&amp;nbsp; Dinner together&lt;br /&gt;&lt;br /&gt;7:45 ~ 8:00pm&amp;nbsp;&amp;nbsp;&amp;nbsp; Round table self-introduction&lt;br /&gt;&lt;br /&gt;8:00 ~ 9:00pm&amp;nbsp;&amp;nbsp;&amp;nbsp; Talk about below areas and assign responsible people for each areas&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * Regular meeting date, time and location&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - The third Wednesday every month, start from 7pm. In the first four weeks, we will choose venue locations based on people's interests and transportations.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - The second Beijing GNOME Users Group meeting will host at Wudaokou Study bar&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * Build website and communication channel &lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Responsible people: Yang Hong, Junyi Tang and Vincent Du&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - BBS will be the main communication channel, we will also use mail list and IRC as official communication channel.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - IRC #beijinggug @irc.gnome.org&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - gnome-cn-list@gnome.org&amp;nbsp;&amp;nbsp;&amp;nbsp; - Subscribe from &lt;a href=&quot;http://mail.gnome.org/mailman/listinfo/gnome-cn-list&quot;&gt;here&lt;/a&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * Art design&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Responsible person : Ziyan Shieh&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Take a look at his first art work for our Beijing GNOME Users Group from here:&lt;a href=&quot;http://blogs.sun.com/emily/resource/wall_paper.jpg&quot;&gt;Wallpaper&amp;nbsp; &lt;/a&gt;&amp;amp; &lt;a href=&quot;http://blogs.sun.com/emily/resource/index_page_simple.jpg&quot;&gt;Webpage&lt;/a&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * Technical speeches&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - The next topic will be delivered by Lingtao Pan from Tsinghua University&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Core members will prepare technical speeches at least one every month&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Call for more talks&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - We also have topic wish list and call for speakers on those topics&amp;nbsp; &lt;/p&gt;
  &lt;p&gt; &lt;b&gt;* Work on GNOME projects&lt;/b&gt;&lt;/p&gt;
  &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Start from localization, Accessibility and Mobility &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Call for leaders for those projects &lt;b&gt;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * Organizing&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Responsible people: Emily, Pockey and Dalong Cheng&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Organizors will responsible for every monthly meeting like: Choose venue, send out invitation, confirm with speakers, make schedule&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * Promotion&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Everyone is responsible for promote Beijing GNOME Users Group&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Blog is an effective way to promote&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * Mentor program&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Responsible people: Pockey Lam, Ray Wang and Emily&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Looking for mentors&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; * Photograph and Video&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; - Responsible people: Elly and Emily&lt;br /&gt;&lt;br /&gt;Take a look at the &lt;a href=&quot;http://blogs.sun.com/emily/resource/1stBeijinggug.jpg&quot;&gt;photo&lt;/a&gt; of the first Beijing GNOME Users Group, totally 9 people :&lt;br /&gt;&lt;/p&gt; 
  &lt;p&gt;It was a good beginning for the first ever Beijing GNOME Users Group, we are looking forward to the future meetings. &amp;nbsp;&lt;/p&gt;</content>
		<author>
			<name>emilychen</name>
			<uri>http://blogs.sun.com/emily/</uri>
		</author>
		<source>
			<title type="html">Emily</title>
			<subtitle type="html">Emily Chen's Blog</subtitle>
			<link rel="self" href="http://blogs.sun.com/emily/feed/entries/atom"/>
			<id>http://blogs.sun.com/emily/feed/entries/atom</id>
			<updated>2008-11-28T03:11:38+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">Test simple blog 3.0beta</title>
		<link href="http://www.gnome-cn.org/resources/blog/yangh/test-simple-blog-3.0beta"/>
		<id>http://www.gnome-cn.org/resources/blog/yangh/test-simple-blog-3.0beta</id>
		<updated>2008-11-27T01:30:30+00:00</updated>
		<content type="html">昨天参加第一次 BeijingGUG 集会</content>
		<author>
			<name>YangH</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">13109</title>
			<subtitle type="html">have one more time!</subtitle>
			<link rel="self" href="http://www.gnome-cn.org/resources/blog/yangh/RSS"/>
			<id>http://www.gnome-cn.org/resources/blog/yangh/RSS</id>
			<updated>2008-12-19T02:48:29+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">KJAVA虚拟机Hack笔记-实现MIDP的SLAVE事件模型</title>
		<link href="http://www.limodev.cn/blog/?p=396"/>
		<id>http://www.limodev.cn/blog/?p=396</id>
		<updated>2008-11-26T13:10:40+00:00</updated>
		<content type="html">&lt;p&gt;MIDP有两种事件模型，一种称为MASTER模型，在这种模型中，以虚拟机为主GUI为从，事件的主循环在虚拟机中实现。另外一种称为SLAVE，在这种模型中，以GUI为主虚拟机为从，主循环在GUI中实现，目前只有QTE使用这种方式。我想GTK+和QTE的事件处理很类似，猜测GTK+也应该采用SLAVE模型。&lt;/p&gt;
&lt;p&gt;移植到GTK+上时，我们仿照QTE建立events/slavemode_port/linux_gtk目录。主要实现两个函数就行了：&lt;/p&gt;
&lt;p&gt;1. midp_slavemode_port_schedule_vm_timeslice 这是虚拟机调度的定时器，这个可以用glib的timeout来实现。&lt;/p&gt;
&lt;pre&gt;
void midp_slavemode_port_schedule_vm_timeslice(void)
{
    g_source_remove(g_schedule_tinfo.timer_id);
    g_schedule_tinfo.timer_id = g_timeout_add(g_schedule_tinfo.time_slice,
        schedule_timer, &amp;#038;g_schedule_tinfo);

    return;
}

static gboolean schedule_timer(gpointer data)
{
    jlong ms = 0;
    ScheduleTimerInfo* info = (ScheduleTimerInfo*)data;

    if(info-&gt;vm_stoped)
    {
        return FALSE;
    }

    midp_checkAndResume();

    ms = JVM_TimeSlice();
    if (midp_getSRState() == SR_SUSPENDED)
    {
        ms = SR_RESUME_CHECK_TIMEOUT;
    }

    if (ms = -2)
    {
        info-&gt;vm_stoped = TRUE;
    }
    else if(ms == -1)
    {
        gtk_main_quit();
    }
    else
    {
        ms = ms &amp;#038; 0x7fffffff;
        info-&gt;timer_id = g_timeout_add(ms, schedule_timer, data);
    }

    return FALSE;
}
&lt;/pre&gt;
&lt;p&gt;这里要注意的是，JVM_TimeSlice返回-2表示虚拟机暂停了，返回-1表示要退出虚拟机，其它的则表示下一个定时器的时间，默认时间片长度为SR_RESUME_CHECK_TIMEOUT。&lt;/p&gt;
&lt;p&gt;2.midp_slavemode_port_event_loop 这个就是GUI事件的主循环，实现很简单，直接调用gtk_main就好了。这时还要加一个初始的定时器。&lt;/p&gt;
&lt;pre&gt;
void midp_slavemode_port_event_loop(void)
{
    g_schedule_tinfo.vm_stoped = FALSE;
    g_schedule_tinfo.time_slice = SR_RESUME_CHECK_TIMEOUT;
    g_schedule_tinfo.timer_id = g_timeout_add(g_schedule_tinfo.time_slice,
        schedule_timer, &amp;#038;g_schedule_tinfo);

    gtk_main();

    g_schedule_tinfo.vm_stoped = TRUE;

    return;
}
&lt;/pre&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">gdbserver调试共享库</title>
		<link href="http://www.limodev.cn/blog/?p=393"/>
		<id>http://www.limodev.cn/blog/?p=393</id>
		<updated>2008-11-26T13:06:16+00:00</updated>
		<content type="html">&lt;p&gt;在开发嵌入式系统时，调试往往是一大难题。面试过不少嵌入式linux工程师，当问及调试手段时，他们的调试手段一般是两种：首先是在PC上的模拟环境中运行，若有问题，可以很方便的调试。其次，若在板子上运行时才出错，就用printf输出log信息，根据log信息定位错误。有少部分人用gdbserver调试板子上的程序，但问到如何在共享库里设置断点时，都说没有办法。&lt;/p&gt;
&lt;p&gt;去年，Tinyx的一个内存越界BUG，花了我2天时间。gcc的一个浮点数BUG让我查了3天时间。这类BUG在PC上根本重现不了，在板子上用printf要花费大量的时间才能把错误的范围缩小一点。后来想了想，与其花时间去加printf，还不如把gdbserver调试共享库的问题解决了，可以为以后的调试节省不少时间。&lt;/p&gt;
&lt;p&gt;在网上找了半天资料，没有什么收获，看来只好自己动手研究。花了一个周末的时间去研究gdbserver的运行方式。办法是找到了，不过仍然有点麻烦，等有时间了，修改一下gdb的代码，把这个过程自动化了。&lt;/p&gt;
&lt;p&gt;先调试运行gdbserver，对gdbserver有了一些感性认识，然后研究linux-low.c中的代码。原来，设置断点只是在对应的内存中写入断点指令（x86上为0xcc）。&lt;/p&gt;
&lt;p&gt;gdbserver为什么不能在共享库中设置断点呢？设置断点只是写内存，调试时，所有的代码段都是可写的，在exe中可以设置断点，没有理由不让在共享库中设置啊。所以这应该与是否是共享库关系不大。&lt;/p&gt;
&lt;p&gt;猜测可能是符号与地址对应关系有误，如果你的本意为function1设置断点，结果gdb搞错了，设置一个毫不相干的地方，可能永远都不会执行到那里，这个断点自然没什么效果。&lt;/p&gt;
&lt;p&gt;如果是这样，有两种方法可以解决：要么手动计算符号的地址，再设置断点，当然这样太累。另外就让gdb自动对应起来。经过反得尝试，用下列方法可以在共享库中设置断点，虽然有点麻烦，还是可行的。&lt;/p&gt;
&lt;p&gt;1. 准备工作，编写下面几个文件：&lt;/p&gt;
&lt;p&gt;test.c:&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;stdlib.h&amp;gt;

int test(int a, int b)
{
    int s = a + b;

    printf(&quot;%d\n&quot;, s);

    return s;
}
&lt;/pre&gt;
&lt;p&gt;main.c:&lt;/p&gt;
&lt;pre&gt;
#include &amp;lt;sstdlib.h&amp;gt;

extern int test(int a, int b);

int main(int argc, char* argv[])
{
    int s = test(10, 20);

    return s;
}
&lt;/pre&gt;
&lt;p&gt;Makefile:&lt;/p&gt;
&lt;p&gt;all: so main&lt;br /&gt;
so:&lt;br /&gt;
    gcc -g test.c -shared -o libtest.so&lt;/p&gt;
&lt;p&gt;main:&lt;br /&gt;
    gcc -g main.c -L./ -ltest -o test.exe&lt;/p&gt;
&lt;p&gt;clean:&lt;br /&gt;
    rm -f *.exe *.so&lt;/p&gt;
&lt;p&gt;（为了便于演示，整个过程在PC上测试，后来证实在实验板上能够正常工作）&lt;/p&gt;
&lt;p&gt;2. 编译并设置环境变量&lt;/p&gt;
&lt;p&gt;make&lt;/p&gt;
&lt;p&gt;export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./&lt;/p&gt;
&lt;p&gt;3. 运行gdbserver&lt;br /&gt;
gdbserver localhost:2000 ./test.exe&lt;/p&gt;
&lt;p&gt;4. 运行gdb客户端&lt;/p&gt;
&lt;p&gt;gdb&lt;/p&gt;
&lt;p&gt;symbol-file test.exe&lt;br /&gt;
target remote localhost:2000&lt;br /&gt;
b main&lt;br /&gt;
c&lt;/p&gt;
&lt;p&gt;5. 查看libtest.so的代码在内存中的位置。&lt;/p&gt;
&lt;p&gt;（从gdbserver的输出或者用ps可以得到test.exe的进程ID，这里假设PID是11547）&lt;/p&gt;
&lt;p&gt;cat /proc/11547/maps&lt;/p&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;p&gt;00624000-0063e000 r-xp 00000000 03:01 718192     /lib/ld-2.3.5.so&lt;br /&gt;
0063e000-0063f000 r-xp 00019000 03:01 718192     /lib/ld-2.3.5.so&lt;br /&gt;
0063f000-00640000 rwxp 0001a000 03:01 718192     /lib/ld-2.3.5.so&lt;br /&gt;
00642000-00766000 r-xp 00000000 03:01 718193     /lib/libc-2.3.5.so&lt;br /&gt;
00766000-00768000 r-xp 00124000 03:01 718193     /lib/libc-2.3.5.so&lt;br /&gt;
00768000-0076a000 rwxp 00126000 03:01 718193     /lib/libc-2.3.5.so&lt;br /&gt;
0076a000-0076c000 rwxp 0076a000 00:00 0&lt;br /&gt;
00bbe000-00bbf000 r-xp 00bbe000 00:00 0&lt;br /&gt;
00fcc000-00fcd000 r-xp 00000000 03:01 1238761    /root/test/gdbservertest/libtest.so&lt;br /&gt;
00fcd000-00fce000 rwxp 00000000 03:01 1238761    /root/test/gdbservertest/libtest.so&lt;br /&gt;
08048000-08049000 r-xp 00000000 03:01 1238765    /root/test/gdbservertest/test.exe&lt;br /&gt;
08049000-0804a000 rw-p 00000000 03:01 1238765    /root/test/gdbservertest/test.exe&lt;br /&gt;
b7f8a000-b7f8b000 rw-p b7f8a000 00:00 0&lt;br /&gt;
b7f99000-b7f9a000 rw-p b7f99000 00:00 0&lt;br /&gt;
bfd85000-bfd9a000 rw-p bfd85000 00:00 0          [stack]&lt;/p&gt;
&lt;p&gt;由此可以知道：libtest.so的代码在00fcc000-00fcd000之间。&lt;/p&gt;
&lt;p&gt;6. 查看libtest.so的.text段在内存中的偏移位置：&lt;/p&gt;
&lt;p&gt;objdump -h libtest.so |grep .text&lt;/p&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;p&gt;9 .text         00000130  00000450  00000450  00000450  2**4&lt;/p&gt;
&lt;p&gt;即偏移位置为0&amp;#215;00000450&lt;/p&gt;
&lt;p&gt;7. 回到gdb窗口，加载libtest.so的符号表。&lt;/p&gt;
&lt;p&gt;add-symbol-file libtest.so 0&amp;#215;00fcc450&lt;/p&gt;
&lt;p&gt;(这里0&amp;#215;00fcc450 = 0&amp;#215;00fcc000 + 0&amp;#215;00000450)&lt;/p&gt;
&lt;p&gt;8. 在共享库的函数中设置断点。&lt;/p&gt;
&lt;p&gt;b test&lt;/p&gt;
&lt;p&gt;9. 继续调试，可以发现在共享库中设置的断点，能够正常工作。&lt;/p&gt;
&lt;p&gt;这个方法仍然有点麻烦，写在这里算抛砖引玉吧，望大家不吝赐教，谢谢。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">KJava虚拟机hack笔记-基于GTK的移植</title>
		<link href="http://www.limodev.cn/blog/?p=390"/>
		<id>http://www.limodev.cn/blog/?p=390</id>
		<updated>2008-11-25T14:39:22+00:00</updated>
		<content type="html">&lt;p&gt;CLDC只依赖于硬件平台和操作系统，不依赖于GUI，在移植到GTK+上时不需要做任何修改。MIDP则与GUI密切相关，在移植到GTK+上时主要修改这部分内容。因为MIDP没有基于GTK+的实现，不是简单的编译一下就行的，而是要动手写上万行的代码。这里面我们从总体介绍一下移植Phoneme_Feature到GTK上的方法：&lt;/p&gt;
&lt;p&gt;1.建立编译环境。&lt;br /&gt;
为了简单，我们把midp/build/linux_qte_gcc拷贝一份到midp/build/linux_gtk_gcc，把文件名和文件内容中的qte全部改为gtk。再修改gtk.gmk:&lt;/p&gt;
&lt;p&gt;MAINWINDOW_TITLE=&amp;#8221;$(PROJECT_NAME_SHORT) $(BUILD_ID)&amp;#8221;&lt;/p&gt;
&lt;p&gt;EXTRA_INCLUDES  += $(shell pkg-config &amp;#8211;cflags gtk+-2.0)&lt;br /&gt;
EXTRA_CFLAGS    += -DMAINWINDOW_TITLE=\&amp;#8221;$(MAINWINDOW_TITLE)\&amp;#8221;&lt;br /&gt;
LIBS            += $(shell pkg-config &amp;#8211;libs gtk+-2.0)&lt;/p&gt;
&lt;p&gt;2.建立配置文件(midp/src/configuration/configuration_xml/linux_gtk), 先从midp/src/configuration/configuration_xml/stubs拷贝一份。&lt;/p&gt;
&lt;p&gt;3.实现事件队列(midp/src/events/eventqueue_port/linux_gtk)，先从midp/src/events/eventqueue_port/stubs拷贝一份，然后编写里面需要的函数。&lt;/p&gt;
&lt;p&gt;4.实现事件主循环(midp/src/events/slavemode_port/linux_gtk)，仿照QTE创建相应的文件和目录，编写midp_slavemode_port.h中需要的函数。&lt;/p&gt;
&lt;p&gt;5.实现平台相关的控件(midp/src/highlevelui/lfpport/linux_gtk)，仿照QTE创建相应的文件和目录，编写highlevelui/lfpport/include/中头文件中需要的所有函数。&lt;/p&gt;
&lt;p&gt;6.实现马达/铃音等相关函数(midp/src/highlevelui/annunciator/linux_gtk)，先从midp/src/highlevelui/annunciator/stubs拷贝一份，然后编写里面需要的函数。&lt;/p&gt;
&lt;p&gt;7.实现图形图像绘制渲染函数(midp/src/lowlevelui/platform_graphics_port/linux_gtk)，先从midp/src/lowlevelui/platform_graphics_port/stubs拷贝一份，然后编写里面需要的函数。&lt;/p&gt;
&lt;p&gt;8.实现运行本地命令的函数(midp/src/ams/platform_request/linux_gtk)，把qte的实现拷贝过来，做简单修改即可。&lt;/p&gt;
&lt;p&gt;9.实现唤醒和挂起虚拟机的函数(midp/src/core/suspend_resume/sr_vm/linux_gtk)，把qte的实现拷贝过来，做简单修改即可。&lt;/p&gt;
&lt;p&gt;9.建立资源文件(midp/src/ams/appmanager_ui_resources/linux_gtk)，先把midp/src/ams/appmanager_ui_resources/linux_qte拷贝一份就行了。&lt;/p&gt;
&lt;p&gt;10.实现push_timer(midp/src/push/push_timer/linux_gtk)，先把midp/src/push/push_timer/linux_fb拷贝一份。&lt;/p&gt;
&lt;p&gt;相应目录中的Makefile也要修改，这个都很简单，仿照qte的实现就行了。在后面的BLOG中， 我们将详细介绍上述的各个子系统的实现方法。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">答复：我不会OOO，仍然可以XXX</title>
		<link href="http://www.limodev.cn/blog/?p=388"/>
		<id>http://www.limodev.cn/blog/?p=388</id>
		<updated>2008-11-25T14:36:53+00:00</updated>
		<content type="html">&lt;p&gt;按照《审死官》里的读法，标题可以读着：答复：我不会圈圈圈，仍然可以叉叉叉。圈圈叉叉并不特指某个东西，而是一个通配符。代表诸如：我不懂COM原理，仍然调用COM组件。我不懂数据结构，仍然可以写程序。我记不得常用API，仍然照样用IDE。如此等等。&lt;/p&gt;
&lt;p&gt;我是个爱好和平的人，不喜欢和别人口诛笔伐，几乎从来没有写过主动攻击别人的文章。亚里斯多德说，我爱更老师，更爱真理。让我套用为，我爱和平，更爱真理。若只是为了一已之私利，我不会写这篇文章的。这里阐明一些个人认为正确的观点，想和所有有兴趣的朋友讨论一下。但就事论事，不要进行人身攻击，没有必要引发一场口水战。&lt;/p&gt;
&lt;p&gt;那些朋友说的也是事实，这不懂某些知识，确实仍然可以做相关的工作。这让我想起一个小故事：记得上大二时，认识了一位开家电维修店的教授，我想跟他学电器维修。他同意了，条件是要我坚持看两年时间的电路图。我找了一本长虹电视机的电路图，看了几天，发现太难了，两周过去了，一个也没看明白。&lt;/p&gt;
&lt;p&gt;我找到那位教授，说，为什么要看电路图，小学生连欧姆都不知道是什么，更别说看懂电路图了，他们为什么也可以修家电？你不想教我就算了，干嘛要让我看两年时间的电路图。教授回答说，小学生能和教授相比吗？对小学生来说，书上有的案例，他可以修，与案例有一点差别，他就摸不着头脑了。而教授，对电器里面电路清清楚楚，不管是哪里坏了，什么现象，把几个关键点的电压电流一量，就找到问题的根源了。&lt;/p&gt;
&lt;p&gt;用Window当然不需要了解操作系统原理，用浏览器当然不需要明白文档对象模型，把excel嵌入到word里也不需要知道OLE，用PhotoShop的人不需要熟悉图形学，用VS 2005的人不需要精通编译原理。这没有错，对用户而言，他们不需要知道他们不必知道的东西，这是用户友好性，值得提倡。而作为一个软件工程师，你应该明白操作系统原理，应该了解编译原理，应该熟悉相关的东西。&lt;/p&gt;
&lt;p&gt;我不想说别人浮躁，我自己也很浮躁，说别人浮躁只是五十步笑百步而已，这个年头谁不浮躁呢? 但从实用的观点来说，不知道某些知识，并不值得引以自豪。理论有没有用，要不要弄清楚某个原理，要不要记住最常用的API。这都是仁者见仁，智者见智的问题。后面列举一些的个人经验，合则取之。&lt;/p&gt;
&lt;p&gt;从毕业后到现在，我一直带在身边的书，只有一本数据结构。前前后后，为了学习的目的，我把里面大部分数据结构和算法写过三遍，这还不包括工作中零零散散写过的。开始是只是为了熟悉其原理，后来开始想怎么把代码写得更优雅一点，组织得更合理一些。这反反复复耗了我大量业余时间，事实证明这些时间没有白费。后来的经验告诉我，差不多所有程序，都是由这些基本算法和数据结构组合起来的。对这些基本算法和数据结构掌握得比较扎实，不但自己写程序可以更快，阅读别人的程序也更容易。如果你还坚持说，不懂数据结构仍然可以写程序。我会说，你永远没有别人写得快，没有别人写得好，程序没有别人的快，没有别人的稳定。&lt;/p&gt;
&lt;p&gt;后来我又花了不少时间去学习设计模式。设计模式不是时髦，而是实用的东西，绝不止几个概念而已。我不但认真的读完了那本书，为了实践，总是找机会去用它们，那怕只是自己写个小程序，也不忘与设计模式挂钩。现在再看那些程序，或许有些丑陋，但正是那些练习让我熟练的掌握了设计模式。如今我再也不会动则就用设计模式了，因为我知道了什么地方该用，什么地方不该用。回想起来，设计模式在很多地方它帮我的大忙。如果你还在说，不懂设计模式，仍然可以写程序了。我只能替你感到惋惜，这么好的东西，你却固执的视而不见。&lt;/p&gt;
&lt;p&gt;以前公司一个产品是基于Apache的，我只是负责维护mod_proxy模块。我的任务是修改它，让它支持HTTPS。不去研究apache的架构，不去阅读其它部分的代码，我仍然可以完成的这个任务。但我利用大量业余时间去研究apache的架构，去阅读HTTP协议，我明白了它的并发模式，明白了它的配置机制，明白了它的模块加载机制，明白了它的LOG机制，明白了作为一个服务器的基本架构。这对于完成自己的任务是有帮助的，更重要的是从中学到的知识，对后来的工作也有帮助。刚到深圳时，在新一家公司，我的第一个任务是开发一个自动编译服务器，整个过程都是自动化的，还是支持多用户排队编译。与apache相比，这自然是小菜了，包括需求分析、设计编码和用户手册，不到十天时间就搞掂了。&lt;/p&gt;
&lt;p&gt;有段时间我维护html tidy，那本是w3开发的一个小工具，功能是用来修复HTML网页中的错误，并输出到一个XHTML文件。它相当于一个HTML语言解析器，用到编译原理的知识。而我对编译原理了解不多，如果凑合着修改BUG，问题也不大。我还是选择了学习编译原理，至少花了半年业余时间学习相关理论，自己又写了几个微型语言的解释器，感觉自己成半个编译原理专家。我发现这些知识非常有用，特别是状态机，对于字符串处理非常方便。又利用编译原理写过一些代码产生器，用它们来产生那些类似而又不同的代码，为我节省了大量的时间。如果当初我固执的认为理论没有用，一定错过了这样一件强大的武器，那是多么可惜的事情啊。&lt;/p&gt;
&lt;p&gt;我维护过一个XMLTransformer，它的功能是调用xerces的函数把XML和XSLT文件解析成DOMTree，然后调用xalan的函数把这两颗DOMTree转换成一个HTML文件。其实只要知道xerces的接口，不明白xerces的内部工作机制，完全可以胜任自己的工作。我还是花了不少时间去研究xerces的代码，此时我还没有看过《设计模式》，看了SAX解析器的代码，我明白了Builder模式，后来屡试不爽，用这种方式，我写一个解析apache配置文件的模块，一个解析INI配置文件的模块，一个解析XML配置文件的模块，还有一个LRC歌词解析器。你看，时间没有白费吗。&lt;/p&gt;
&lt;p&gt;有段时间老大让我去写microwindow的控件测试程序，我花了不少时间去研究microwindow的实现，明白了它消息机制，明白了各个控件的实现机制，记住了各个控件的风格、消息、通知，总结出它们的规律，写了一个通用的测试程序。不但提前完成了那个任务，这对于后来我用win32 SDK写程序帮助也很大。因为熟悉常用的控件和API，编程速度大为提高，出错几率也大为降低。&lt;/p&gt;
&lt;p&gt;我也曾去研究过Wine的代码，主要是想知道COM的实现原理，这完全是为了满足我的好奇心。看似无关紧要的知识，也后来竟然派上了大用场。为了简化进程间通信机制，我们在一个嵌入式系统中，实现一套进程间通信机制，大大简化了进程间的过程调用，成为那个平台的基本架构之一。同样是因为兴趣，我研究了gdb和ptrace的实现，学习到的知识也在后来的工作中派上了用场。&lt;/p&gt;
&lt;p&gt;还有很多这样的例子，说这些无意炫耀自己，我只想说明这样一个事实：书到用时方恨少。很多知识，知道比不知道好，知道了至少就多一种选择，甚至可能在你意想不到的时候派上用场。&lt;/p&gt;
&lt;p&gt;作为程序员，固执的不去学习，或者因为门户之见而把自己限于一个小范围，个人认为不是明智之举。一句话，知道它们，我们可能做得更好，不知道它们，决不可能做得更好！&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">几则开发技巧</title>
		<link href="http://www.limodev.cn/blog/?p=386"/>
		<id>http://www.limodev.cn/blog/?p=386</id>
		<updated>2008-11-25T14:34:10+00:00</updated>
		<content type="html">&lt;p&gt;o 使用gtk_idle_add实现异步signal。&lt;/p&gt;
&lt;p&gt;最近开发桌面模块时，遇到一个棘手的问题：向DirectFB的窗口管理器注册了顶层窗口改变的事件。当前顶层窗口切换时，窗口管理器回调我设置的回调函数，在回调函数中又要调用窗口管理器的函数，以获取顶层窗口的信息。整个过程是同步调用的，即直接调用函数，这会重入一个窗口管理器函数，造成死锁。&lt;/p&gt;
&lt;p&gt;后来通过gtk_idle_add把同步操作转换成异步操作，解决了这个问题。在Window上， SendMessage和PostMessage分别对应于同步和异步消息。而在GTK+中，它所有的signal都是同步，要实现异步的signal，最简单的办法就使用gtk_idle_add。&lt;/p&gt;
&lt;p&gt;o 使用gtk_quit_add释放资源。&lt;/p&gt;
&lt;p&gt;在开发桌面模块时，遇到另外一个问题：在注销时，退出桌面，这时要释放一些资源，包括关闭一些GtkWidget。这些操作是在退出gtk主循环后处理的，关闭GtkWidget时，总是会会死掉。看样子，在此之前，GtkWidget已经被非正常关闭了。所谓非正常，是说资源被销毁了，但destroy函数并没有被调用。&lt;/p&gt;
&lt;p&gt;后来发现，在退出主循环时，所有的GUI资源都被释放掉了，DirectFB已经销毁，之后再访问GUI资源，后果无法预料。这样的操作只能在主循环之退出前调用，要做到这一点，可以通过gtk_quit_add增加了一个释放函数，在退出主循环之前被自动调用。一切OK了。&lt;/p&gt;
&lt;p&gt;o 调试用libtool生成的可执行文件。&lt;/p&gt;
&lt;p&gt;用libtool产生的可执行文件，分为两层，外层是一个脚本文件，内层才是ELF文件。ELF文件放在.lib目录中，在linux下，以.开头的文件都是隐藏的，所以正常情况下看不到。一般都通过脚本文件运行，脚本文件会处理共享库相关的一些设置，比如设置库的路径等等。&lt;/p&gt;
&lt;p&gt;不知道内幕的新手，往往尝试用gdb去调试脚本文件，面对莫名其妙的错误束手无策。即使知道.lib下的文件才是真正的可执行文件，去调试那个ELF文件仍然很麻烦，你必须要手工去设置库的路径。&lt;/p&gt;
&lt;p&gt;其实不用那么麻烦，脚本文件最终不是要执行真正的ELF文件吗？用vim打开那个文件，我们发现它调用exec去执行真正的ELF文件，把exec换成gdb，然后再运行这个脚本文件，不用其它任何设置，自动进入调试器。当然，你可以把这个文件拷贝一份，一个用于正常执行，一个用于调试执行。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">在linux终端下运行X Window程序</title>
		<link href="http://www.limodev.cn/blog/?p=384"/>
		<id>http://www.limodev.cn/blog/?p=384</id>
		<updated>2008-11-25T14:32:03+00:00</updated>
		<content type="html">&lt;p&gt;作为Linux程序员，在大多数情况，在终端下干活（编程），可能更方便一点。服务器在机房里，大家都连接到同一台服务器上，而本机在Windows下运行一个终端（如SecureCRT），这是典型的配置。&lt;/p&gt;
&lt;p&gt;如果开发的软件是不带GUI界面的，一点问题也没有，至少我自己这样做了几年了。而最近要编写GTK+程序，麻烦就来了。先是尝试在VMWare运行Linux上，当然可以，不过编译太慢了。加上我习惯于写一点，就编译、测试，编译太慢浪费我太多时间，只好另想办法。&lt;/p&gt;
&lt;p&gt;我们知道X Window是C/S模型的，应用程序在一台电脑上运行，而显示在另外一台电脑上。 所以，解决上述问题最简单的方法就是，在本机(Windows)下安装一个X Window Server，把在服务器上运行程序显示在本机上。&lt;/p&gt;
&lt;p&gt;在Windows下运行的X Window Server有不少，一些是商业版的，一些是免费的。最常用的免费X Window Server可能是Xcygwin，可以在http://x.cygwin.com/ 网站上免费下载。&lt;/p&gt;
&lt;p&gt;下载后直接安装，和安装普通的cygwin没有什么差别。只要做些配置，这里我们假设：&lt;/p&gt;
&lt;p&gt;Linux服务器IP为：10.20.30.246&lt;br /&gt;
Windows客户机IP为：10.20.30.243&lt;/p&gt;
&lt;p&gt;在Windows端的Xcygwin下：&lt;/p&gt;
&lt;p&gt;o 增加认证信息，允许IP为10.20.30.246的机器访问 Xserver。&lt;/p&gt;
&lt;p&gt;$ xhost +10.20.30.246&lt;/p&gt;
&lt;p&gt;o 启动X Window Server&lt;/p&gt;
&lt;p&gt;$/usr/X11R6/bin/startxwin.bat&lt;/p&gt;
&lt;p&gt;在Linux服务器的终端下：&lt;/p&gt;
&lt;p&gt;o 设置DISPLAY环境变量&lt;/p&gt;
&lt;p&gt;[root@linux usr]# export DISPLAY=10.20.30.243:0&lt;/p&gt;
&lt;p&gt;o 运行应用程序&lt;/p&gt;
&lt;p&gt;[root@linux usr]# gtk-demo&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">SAFEARRAY使用方法示例</title>
		<link href="http://www.limodev.cn/blog/?p=376"/>
		<id>http://www.limodev.cn/blog/?p=376</id>
		<updated>2008-11-25T14:20:13+00:00</updated>
		<content type="html">&lt;p&gt;SAFEARRAY不是很好用，一些函数不太直观，一不小心就着了它的道。上次学习编写Google桌面插件时就遇到了麻烦，本来应该是vsa.vt = VT_ARRAY | VT_BSTR，结果写成了vsa.vt = VT_ARRAY，让我查了好久才找到原因。这里整理一下，作为备忘。&lt;/p&gt;
&lt;p&gt;o 创建&lt;/p&gt;
&lt;pre&gt;
long i = 0;
VARIANT va = {0};
va.vt = VT_BSTR;

SAFEARRAYBOUND bounds[1] = {0};
bounds[0].cElements = 5;
SAFEARRAY* psa = SafeArrayCreate(VT_VARIANT, 1, bounds);
&lt;/pre&gt;
&lt;p&gt;o 存入元素&lt;/p&gt;
&lt;pre&gt;
for(i = 0; i  5; i++)
{
     va.bstrVal = SysAllocString(L&quot;test&quot;);
     SafeArrayPutElement(psa, &amp;#038;i, &amp;#038;va);
}
&lt;/pre&gt;
&lt;p&gt;o 获取元素&lt;/p&gt;
&lt;pre&gt;
for(i = 0; i  5; i++)
{
     va.bstrVal = SysAllocString(L&quot;test&quot;);
     SafeArrayGetElement(psa, &amp;#038;i, &amp;#038;va);
     SysFreeString(va.bstrVal);
}
&lt;/pre&gt;
&lt;p&gt;o 销毁&lt;/p&gt;
&lt;p&gt;SafeArrayDestroy(psa);&lt;/p&gt;
&lt;p&gt;o 生成VARIANT变量&lt;/p&gt;
&lt;pre&gt;
VARIANT vsa = {0};
vsa.vt = VT_ARRAY | VT_BSTR;
vsa.parray = psa;
&lt;/pre&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">实现COM消息广播</title>
		<link href="http://www.limodev.cn/blog/?p=372"/>
		<id>http://www.limodev.cn/blog/?p=372</id>
		<updated>2008-11-25T14:16:55+00:00</updated>
		<content type="html">&lt;p&gt;大家都知道，为ActiveX控件添加事件处理函数是件容易的事情，IDE已经提供相应的Wizard，为ActiveX控件添加事件处理函数和为一般控件添加事件处理函数没有什么两样。而为普通COM组件添加事件处理函数，就没有这么直观了，必须手工编写相关代码。&lt;/p&gt;
&lt;p&gt;如果完全手工去编写这些代码，可以说是相当的麻烦，实际上相当编写另外一个COM组件给原组件调用，至少要实现IDispatch接口才行。不过在ATL的帮助下，事情简化了很多。&lt;/p&gt;
&lt;p&gt;另外一方面，一个组件的事件，只有对应的客户端才能收到，如何把事件变为广播消息，让所有的客户端都知道呢？这个问题容易解决：组件端记录所有的客户端，把事件发给每一个客户端就行了。下面我们看一个简单的例子。&lt;/p&gt;
&lt;p&gt;一、实现COM组件服务端&lt;/p&gt;
&lt;p&gt;(COM组件作为一个服务器在一个单独的EXE内部运行。)&lt;/p&gt;
&lt;p&gt;o 用VC6新建一个ATL项目server，服务器类型为Executable。&lt;/p&gt;
&lt;p&gt;o Insert à New ATL ObjectàObjectsàSimple Object&lt;/p&gt;
&lt;p&gt;o 名称为Chat，在属性中选中Support Connection Points(即支持事件)。&lt;/p&gt;
&lt;p&gt;o 为IChat增加接口函数：HRESULT Send(BSTR str);&lt;/p&gt;
&lt;p&gt;o 为_IchatEvents增加事件：HRESULT OnMessage(BSTR str);&lt;/p&gt;
&lt;p&gt;o ReBuild All&lt;/p&gt;
&lt;p&gt;o 右键点击类Cchat, Implement Connection Points&lt;/p&gt;
&lt;p&gt;o 为Cchat增加一个静态成员，记录所有Cchat对象实例。static std::list s_AllInstances;&lt;/p&gt;
&lt;p&gt;o 实现Send函数，该函数中对所有Cchat对象实例触发OnMessage事件。&lt;/p&gt;
&lt;pre&gt;STDMETHODIMP CChat::Send(BSTR str)
{
     // TODO: Add your implementation code here
     for(std::list::iterator iter = s_AllInstances.begin(); iter != s_AllInstances.end(); iter++)
     {
          CChat* obj = *iter;

          obj-&gt;Fire_OnMessage(str);
     }

     return S_OK;
}
&lt;/pre&gt;
&lt;p&gt;二、实现客户端&lt;/p&gt;
&lt;p&gt;用VC6创建ATL/WTL项目，应用程序类型为Dialog based。&lt;/p&gt;
&lt;p&gt;让CmainDlg继承IdispEventImpl接口。&lt;/p&gt;
&lt;pre&gt;class CMainDlg : public CAxDialogImpl, public CUpdateUI,
          public CMessageFilter, public CIdleHandler,
          public IDispEventImpl0, CMainDlg, &amp;#038;DIID__IChatEvents, &amp;#038;LIBID_SERVERLib&gt;&lt;/pre&gt;
&lt;p&gt;增加事件函数描述信息。&lt;/p&gt;
&lt;p&gt;_ATL_FUNC_INFO g_OnMessageInfo = {CC_STDCALL, VT_EMPTY, 1, {VT_BSTR} };&lt;/p&gt;
&lt;p&gt;实现消息处理函数，不要忘了加_stdcall修饰。&lt;/p&gt;
&lt;pre&gt;void _stdcall OnRecv(BSTR str)
{
     USES_CONVERSION;
     CListBox lb(GetDlgItem(IDC_LIST_ALL_MESSAGE));
     lb.InsertString(-1, OLE2A(str));
     return ;

}&lt;/pre&gt;
&lt;p&gt;增加事件映射。SINK_ENTRY_INFO 的第一个参数要与IdispEventImpl的第一个参数一致，其取值没有限制。&lt;/p&gt;
&lt;pre&gt;
     BEGIN_SINK_MAP(CMainDlg)
          SINK_ENTRY_INFO(0, DIID__IChatEvents, 1, OnRecv, &amp;#038;g_OnMessageInfo)
     END_SINK_MAP()
&lt;/pre&gt;
&lt;p&gt;调用组件函数。&lt;/p&gt;
&lt;pre&gt;     LRESULT OnSend(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL&amp;#038; bHandled)
     {
          char szBuff[1024] = {0};
          sprintf(szBuff, &quot;(%d):&quot;, GetCurrentProcessId());

          this-&gt;GetDlgItemText(IDC_EDIT_MESSAGE, szBuff+strlen(szBuff), 1000);

          m_ichat-&gt;Send(CComBSTR(szBuff));
          // TODO : Add Code for control notification handler.
          return 0;
     }&lt;/pre&gt;
&lt;p&gt;在OnInitDialog中增加初始化代码。&lt;/p&gt;
&lt;pre&gt;     if(SUCCEEDED(m_ichat.CoCreateInstance(CComBSTR(L&quot;Server.Chat&quot;))))
     {
          if(FAILED(DispEventAdvise(m_ichat, &amp;#038;DIID__IChatEvents)))
          {
              MessageBox(&quot;DispEventAdvise failed!&quot;);
          }
     }
     else
     {
         MessageBox(&quot;CoCreateInstance failed!&quot;);
     }&lt;/pre&gt;
&lt;p&gt;在CloseDialog中增加~初始化代码。&lt;/p&gt;
&lt;pre&gt;if(FAILED(DispEventUnadvise(m_ichat, &amp;#038;DIID__IChatEvents)))
{
    MessageBox(&quot;DispEventUnadvise failed!&quot;);
}
&lt;/pre&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-你的数据放在哪里(下)</title>
		<link href="http://www.limodev.cn/blog/?p=362"/>
		<id>http://www.limodev.cn/blog/?p=362</id>
		<updated>2008-11-24T14:05:17+00:00</updated>
		<content type="html">&lt;p&gt;对于初学者来说这道题有点难度，很少有人能完全做对的。不过没关系，我们的目标并不是要难倒读者，而是要刺激读者去思考，加深学习的印象。有了前面两次的经验，我想没有人再去写一个dlist_to_upper的函数了，大家都会调用dlist_foreach来实现。不过新的问题又出现了，初学者常犯的错误有以下几种：&lt;/p&gt;
&lt;p&gt;1.	转换大写的方法不对。&lt;/p&gt;
&lt;pre&gt;
    char* p = str;
    if(p != NULL)
    {
        while(*p != '\0')
        {
            if('a' = *p &amp;#038;&amp;#038; *p = 'z')
            {
                *p = *p - ('a' - 'A');
            }
            p++;
        }
}
&lt;/pre&gt;
&lt;p&gt;这是我们在课本里学到的写法，但在工程中是不能这样做的。因为大小写字母在不同语言中的定义是不一样的，’a’是一个字符常量，它的值在任何时候都是97，但在不同语言中，97却不一定代表’a’。我们不能简单的认为在97(‘a’)-122(‘z’)之间的字符就是小写字母，而是应该调用标准C函数islower来判断，同样转换为大写应该调用toupper而不是减去一个常量。&lt;/p&gt;
&lt;p&gt;2.	在双向链表中存放常量字符串，转换时出现段错误。&lt;/p&gt;
&lt;pre&gt;
    DList* dlist = dlist_create();
    dlist_append(dlist, &quot;It&quot;);
    dlist_append(dlist, &quot;is&quot;);
    dlist_append(dlist, &quot;OK&quot;);
    dlist_append(dlist, &quot;!&quot;);
    dlist_foreach(dlist, str_toupper, NULL);
    dlist_destroy(dlist);
&lt;/pre&gt;
&lt;p&gt;运行时会出现Segmentation fault错误。原因是”It”等字符串是常量，常量是不能修改的。&lt;/p&gt;
&lt;p&gt;3.	在双向链表中存放的是临时变量，转换后发现所有字符串都一样。&lt;/p&gt;
&lt;pre&gt;
    char str[256] = {0};
    DList* dlist = dlist_create();
    strcpy(str, &quot;It&quot;);
    dlist_append(dlist, str);
    strcpy(str, &quot;is&quot;);
    dlist_append(dlist, str);
    strcpy(str, &quot;OK&quot;);
    dlist_append(dlist, str);
    strcpy(str, &quot;!&quot;);
    dlist_append(dlist, str);
    dlist_foreach(dlist, str_toupper, NULL);
    dlist_foreach(dlist, str_print, NULL);
    dlist_destroy(dlist);
&lt;/pre&gt;
&lt;p&gt;运行时发现打印出几个感叹号。原因是dlist_append时没有拷贝一份，所以在dlist中存放的是同一个地址。而且这个dlist在当前函数返回后，里面保存的数据都无效了，因为这些数据指向的是临时变量。&lt;/p&gt;
&lt;p&gt;4.	存放时拷贝了数据，但没有free分配的内存。&lt;/p&gt;
&lt;pre&gt;
    DList* dlist = dlist_create();
    dlist_append(dlist, strdup(&quot;It&quot;));
    dlist_append(dlist, strdup(&quot;is&quot;));
    dlist_append(dlist, strdup(&quot;OK&quot;));
    dlist_append(dlist, strdup(&quot;!&quot;));
    dlist_foreach(dlist, str_toupper, NULL);
    dlist_foreach(dlist, str_print, NULL);
    dlist_destroy(dlist);
&lt;/pre&gt;
&lt;p&gt;这里看起来工作正常了，但存在内存泄露的BUG。strdup调用malloc分配了内存，但没有地方去free它们。&lt;/p&gt;
&lt;p&gt;初学者对内存和指针只有一知半解的认识，常常犯一些连自己都莫名其妙的错误。为了避免这些不必要的错误，今天我们要学习各种数据存放的位置以及它们的特性，让初学者对编程有更进一步的认识。在程序中，数据存放的位置主要有以下几个：&lt;/p&gt;
&lt;p&gt;1.未初始化的全局变量(.bss段)&lt;/p&gt;
&lt;p&gt;已经记不清bss代表Block Storage Start还是Block Started by Symbol。像我这种没有用过那些史前计算机的人，终究无法明白这样怪异的名字，记不住也是不足为奇的。不过没有关系，重要的是，我们要清楚什么数据是存放在bss段中的，这些数据有什么样的特点以及如何使用它们。&lt;/p&gt;
&lt;p&gt;通俗的说，bss段是用来存放那些没有初始化的和初始化为0的全局变量的。它有什么特点呢，让我们来看看一个小程序的表现。&lt;/p&gt;
&lt;pre&gt;
int bss_array[1024 * 1024];

int main(int argc, char* argv[])
{
    return 0;
}
# gcc -g bss.c -o bss.exe
# ls -l bss.exe
-rwxrwxr-x 1 root root 5975 Nov 16 09:32 bss.exe
# objdump -h bss.exe |grep bss
24 .bss          00400020  080495e0  080495e0  000005e0  2**5
&lt;/pre&gt;
&lt;p&gt;变量bss_array的大小为4M，而可执行文件的大小只有5K。 由此可见，bss类型的全局变量只占运行时的内存空间，而不占用文件空间。&lt;/p&gt;
&lt;p&gt;现代大多数操作系统，在加载程序时，会把所有的bss全局变量清零。但为保证程序的可移植性，手工把这些变量初始化为0也是一个好习惯，这样这些变量都有个确定的初始值。&lt;/p&gt;
&lt;p&gt;当然作为全局变量，在整个程序的运行周期内，bss数据是一直存在的。&lt;/p&gt;
&lt;p&gt;2.初始化过的全局变量 (.data段)&lt;/p&gt;
&lt;p&gt;与bss相比，data段就容易明白多了，它的名字就暗示着里面存放着数据。当然，如果数据全是零，为了优化考虑，编译器把它当作bss处理。通俗的说，data段用来存放那些初始化为非零的全局变量。它有什么特点呢，我们还是来看看一个小程序的表现。&lt;/p&gt;
&lt;pre&gt;
int data_array[1024 * 1024] = {1};

int main(int argc, char* argv[])
{
    return 0;
}
# ls -l data.exe
-rwxrwxr-x 1 root root 4200313 Nov 16 09:34 data.exe
# objdump -h data.exe |grep \\.data
23 .data         00400020  080495e0  080495e0  000005e0  2**5
&lt;/pre&gt;
&lt;p&gt;仅仅是把初始化的值改为非零了，文件就变为4M多。由此可见，data类型的全局变量是即占文件空间，又占用运行时内存空间的。&lt;/p&gt;
&lt;p&gt;同样作为全局变量，在整个程序的运行周期内，data数据是一直存在的。&lt;/p&gt;
&lt;p&gt;3.常量数据 (.rodata段)&lt;/p&gt;
&lt;p&gt;rodata的意义同样明显，ro代表read only，rodata就是用来存放常量数据的。关于rodata类型的数据，要注意以下几点：&lt;/p&gt;
&lt;p&gt;o 常量不一定就放在rodata里，有的立即数直接和指令编码在一起，存放在代码段(.text)中。&lt;/p&gt;
&lt;p&gt;o 对于字符串常量，编译器会自动去掉重复的字符串，保证一个字符串在一个可执行文件(EXE/SO)中只存在一份拷贝。&lt;/p&gt;
&lt;p&gt;o rodata是在多个进程间是共享的，这样可以提高运行空间利用率。&lt;/p&gt;
&lt;p&gt;o 在有的嵌入式系统中，rodata放在ROM(或者norflash)里，运行时直接读取，无需加载到RAM内存中。&lt;/p&gt;
&lt;p&gt;o 在嵌入式linux系统中，也可以通过一种叫作XIP（就地执行）的技术，也可以直接读取，而无需加载到RAM内存中。&lt;/p&gt;
&lt;p&gt;o 常量是不能修改的，修改常量在linux下会出现段错误。&lt;/p&gt;
&lt;p&gt;由此可见，把在运行过程中不会改变的数据设为rodata类型的是有好处的：在多个进程间共享，可以大大提高空间利用率，甚至不占用RAM空间。同时由于rodata在只读的内存页面(page)中，是受保护的，任何试图对它的修改都会被及时发现，这可以提高程序的稳定性。&lt;/p&gt;
&lt;p&gt;字符串会被编译器自动放到rodata中，其它数据要放到rodata中，只需要加const关键字修饰就好了。&lt;/p&gt;
&lt;p&gt;4.代码 (.text段) &lt;/p&gt;
&lt;p&gt;text段存放代码(如函数)和部分整数常量，它与rodata段很相似，相同的特性我们就不重复了，主要不同在于这个段是可以执行的。&lt;/p&gt;
&lt;p&gt;5.	栈(stack) &lt;/p&gt;
&lt;p&gt;栈用于存放临时变量和函数参数。栈作为一种基本数据结构，我并不感到惊讶，用来实现函数调用，这也司空见惯的作法。直到我试图找到另外一种方式实现递归操作时，我才感叹于它的巧妙。要实现递归操作，不用栈不是不可能，只是找不出比它更优雅的方式。&lt;/p&gt;
&lt;p&gt;尽管大多数编译器在优化时，会把常用的参数或者局部变量放入寄存器中。但用栈来管理函数调用时的临时变量（局部变量和参数）是通用做法，前者只是辅助手段，且只在当前函数中使用，一旦调用下一层函数，这些值仍然要存入栈中才行。&lt;/p&gt;
&lt;p&gt;通常情况下，栈向下（低地址）增长，每向栈中PUSH一个元素，栈顶就向低地址扩展，每从栈中POP一个元素，栈顶就向高地址回退。一个有兴趣的问题：在x86平台上，栈顶寄存器为ESP，那么ESP的值在是PUSH操作之前修改呢，还是在PUSH操作之后修改呢？PUSH ESP这条指令会向栈中存入什么数据呢？据说x86系列CPU中，除了286外，都是先修改ESP，再压栈的。由于286没有CPUID指令，有的OS用这种方法检查286的型号。&lt;/p&gt;
&lt;p&gt;要注意的是，存放在栈中的数据只在当前函数及下一层函数中有效，一旦函数返回了，这些数据也自动释放了，继续访问这些变量会造成意想不到的错误。&lt;/p&gt;
&lt;p&gt;6.堆(heap)&lt;/p&gt;
&lt;p&gt;堆是最灵活的一种内存，它的生命周期完全由使用者控制。标准C提供几个函数：&lt;/p&gt;
&lt;p&gt;malloc 用来分配一块指定大小的内存。&lt;br /&gt;
realloc 用来调整/重分配一块存在的内存。&lt;br /&gt;
free   用来释放不再使用的内存。&lt;br /&gt;
…&lt;/p&gt;
&lt;p&gt;使用堆内存时请注意两个问题：&lt;/p&gt;
&lt;p&gt;alloc/free要配对使用。内存分配了不释放我们称为内存泄露(memory leak)，内存泄露多了迟早会出现Out of memory的错误，再分配内存就会失败。当然释放时也只能释放分配出来的内存，释放无效的内存，或者重复free都是不行的，会造成程序crash。&lt;/p&gt;
&lt;p&gt;分配多少用多少。分配了100字节就只能用100字节，不管是读还是写，都只能在这个范围内，读多了会读到随机的数据，写多了会造成的随机的破坏。这种情况我们称为缓冲区溢出(buffer overflow)，这是非常严重的，大部分安全问题都是由缓冲区溢出引起的。&lt;/p&gt;
&lt;p&gt;手工检查有没有内存泄露或者缓冲区溢出是很困难的，幸好有些工具可以使用，比如linux下有valgrind，它的使用方法很简单，大家下去可以试用一下，以后每次写完程序都应该用valgrind跑一遍。&lt;/p&gt;
&lt;p&gt;最后，我们来看看在linux下，程序运行时空间的分配情况：&lt;/p&gt;
&lt;pre&gt;
# cat /proc/self/maps 

00110000-00111000 r-xp 00110000 00:00 0          [vdso]
009ba000-009d6000 r-xp 00000000 08:01 768759     /lib/ld-2.8.so
009d6000-009d7000 r--p 0001c000 08:01 768759     /lib/ld-2.8.so
009d7000-009d8000 rw-p 0001d000 08:01 768759     /lib/ld-2.8.so
009da000-00b3d000 r-xp 00000000 08:01 768760     /lib/libc-2.8.so
00b3d000-00b3f000 r--p 00163000 08:01 768760     /lib/libc-2.8.so
00b3f000-00b40000 rw-p 00165000 08:01 768760     /lib/libc-2.8.so
00b40000-00b43000 rw-p 00b40000 00:00 0
08048000-08050000 r-xp 00000000 08:01 993652     /bin/cat
08050000-08051000 rw-p 00007000 08:01 993652     /bin/cat
0805f000-08080000 rw-p 0805f000 00:00 0          [heap]
b7fe8000-b7fea000 rw-p b7fe8000 00:00 0
bfee7000-bfefc000 rw-p bffeb000 00:00 0          [stack]
&lt;/pre&gt;
&lt;p&gt;每个区间都有四个属性：&lt;/p&gt;
&lt;p&gt;r 表示可以读取。&lt;br /&gt;
w 表示可以修改。&lt;br /&gt;
x 表示可以执行。&lt;br /&gt;
p/s 表示是否为共享内存。&lt;/p&gt;
&lt;p&gt;有文件名的内存区间，属性为r—p表示存放的是rodata。&lt;br /&gt;
有文件名的内存区间，属性为rw-p表示存放的是bss和data&lt;br /&gt;
有文件名的内存区间，属性为r-xp表示存放的是text数据。&lt;br /&gt;
没有文件名的内存区间，表示用mmap映射的匿名空间。&lt;br /&gt;
文件名为[stack]的内存区间表示是栈。&lt;br /&gt;
文件名为[heap]的内存区间表示是堆。&lt;/p&gt;
&lt;p&gt;对内存的掌握是系统程序员必备的技能，希望读者多加体会。本节示例代码请到&lt;a href=&quot;http://www.limodev.cn/bbs/download/file.php?id=7&quot;&gt;&lt;strong&gt;这里下载&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-Don’t Repeat Yourself(DRY)(下)</title>
		<link href="http://www.limodev.cn/blog/?p=355"/>
		<id>http://www.limodev.cn/blog/?p=355</id>
		<updated>2008-11-24T12:38:45+00:00</updated>
		<content type="html">&lt;p&gt;实现这两个函数并不是件难事，但真正写好的人并不多。初学者通常的做法有两种：&lt;/p&gt;
&lt;p&gt;1.各写一个独立的函数。dlist_find_max用来找出最大值，dlist_sum用来求和。这种做法和前面写dlist_print时所犯的错误一样，会造成重复的代码，让dlist的实现随着应用环境的变化而变化。&lt;/p&gt;
&lt;p&gt;2.采用回调函数法。细心的初学者会发现，这两个函数的实现与dlist_print的实现很类似，无非是print那行代码要换成别的功能。能想到这一点很好，不过在真正动手时，发现每个回调函数都要保存一些中间数据。大部分人选择了用全局变量来保存，这可以实现要求的功能，但违背了禁用全局变量原则。&lt;/p&gt;
&lt;p&gt;这两个函数没有什么实用价值，但是通过它们我们可以学习几点：&lt;/p&gt;
&lt;p&gt;1.不要编写重复的代码&lt;/p&gt;
&lt;p&gt;按传统的方法写出dlist_find_max之后，每个人都知道这个函数与dlist_print很类似，在写出dlist_sum之后，那种感觉就更明显了。在这个时候，不应该停下来，而是要想办法把这些重复的代码抽出来。即使因为经验所限，也要极力去想思考和查资料。&lt;/p&gt;
&lt;p&gt;写重复的代码很简单，甚至凭本能都可以写出来。但要想成为优秀的程序员，你一定要克服自己的惰情，因为重复的代码造成很多问题：&lt;/p&gt;
&lt;p&gt;重复的代码更容易出错。在写类似代码的时候，几乎所有人(包括我)都会选择Copy&amp;#038;Paste的方法，这种方法很容易犯一些细节上的错误，如果某个地方修改不完整，那就留下了”不定时”的炸弹，说不定什么时候会暴露出来。&lt;/p&gt;
&lt;p&gt;重复的代码经不起变化。无论是修改BUG，还是增加新特性，往往你要修改很多地方，如果忘掉其中之一，你同样得为此付出代价。请记住古惑仔的话，出来混迟早是要还的。大师们说过，在软件中欠下的BUG，你会为此还得更多。&lt;/p&gt;
&lt;p&gt;去除重复代码往往不是件简单的事情，需要更多思考和更多精力，不过事实证明这是最值得的投资。在这里，我们要怎么抽取这些重复的代码呢？&lt;/p&gt;
&lt;p&gt;这三个函数无非是要遍历双向链表并做一些事情，遍历双向链表我们可以提供一个dlist_foreach函数，至于要做什么，这是千变万化的行为，可以通过回调函数让调用者去做。&lt;/p&gt;
&lt;p&gt;2.任何回调函数都要有上下文&lt;/p&gt;
&lt;p&gt;大部分初学者都选择了回调函数法，不过都无一例外的选择了用全局变量来保存中间数据，这里我不想再强调全局变量的坏处了，记性不好的读者可以看看前面的内容。我们要说的是，在这种情况下，如何避免使用全局变量。&lt;/p&gt;
&lt;p&gt;很简单，给回调函数传递额外的参数就行了。这个参数我们称为回调函数的上下文，变量名用ctx(context的缩写)。要在这个上下文中存放什么东西呢？那得根据具体的回调函数而定，为了能保存任何数据类型，我们选择void*表示这个上下文。&lt;/p&gt;
&lt;p&gt;下面我们看看怎么实现这个dlist_foreach:&lt;/p&gt;
&lt;pre&gt;
DListRet dlist_foreach(DList* thiz, DListVisitFunc visit, void* ctx)
{
    DListRet ret = DLIST_RET_OK;
    DListNode* iter = thiz-&gt;first;

    while(iter != NULL &amp;#038;&amp;#038; ret != DLIST_RET_STOP)
    {
        ret = visit(ctx, iter-&gt;data);

        iter = iter-&gt;next;
    }

    return ret;
}
&lt;/pre&gt;
&lt;p&gt;visit是回调函数，ctx就是我们说的上下文。要特别强调的一点是，ctx应该作为回调函数的第一个参数。为什么呢？在前面我们讲过的面向对象的函数命名规则中，我们以thiz作为函数的第一个参数，而thiz通常也就是函数的上下文。如果在这里恰好ctx==thiz，就不需要因为参数顺序不同而做转换了。&lt;/p&gt;
&lt;p&gt;实现求和的回调函数：&lt;/p&gt;
&lt;pre&gt;
static DListRet sum_cb(void* ctx, void* data)
{
    long long* result = ctx;
    *result += (int)data;

    return DLIST_RET_OK;
}
&lt;/pre&gt;
&lt;p&gt;调用foreach:&lt;br /&gt;
long long sum = 0;&lt;br /&gt;
dlist_foreach(thiz, sum_cb, &amp;#038;sum);&lt;/p&gt;
&lt;p&gt;是不是很简单？以后在使用回调函数时，记得多加一个ctx参数，即使暂时用不着，留着方便以后扩展。好了，请读者用类似的方法实现查找最大值的功能吧。&lt;/p&gt;
&lt;p&gt;3.只做份内的事&lt;/p&gt;
&lt;p&gt;我见到不少任劳任怨的程序员，别人让他做什么他就做什么，不管是不是份内的事，不管是上司要求的还是同事要求的，都来者不拒。别人说需要一个XXX功能的函数，他就写一个函数在他的模块里，日积月累后，他的模块变得乱七八糟的，成了大杂烩。我亲眼见过在系统设置和桌面两个模块里，提供很多毫不相干的函数，这些函数造成不必要的耦合和复杂度。&lt;/p&gt;
&lt;p&gt;在这里也是一样的，求和和求最大值不是dlist应该提供的功能，放在dlist里面实现是不应该的。为了能实现这些功能，我们提供一种满足这些需求的机制就好了。热心肠是好的，但一定不能违背原则，否则就费力不讨好了。&lt;/p&gt;
&lt;p&gt;本节的示例请到这里&lt;a href=&quot;http://www.limodev.cn/bbs/download/file.php?id=6&quot;&gt;&lt;strong&gt;下载&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-你的数据放在哪里(上)</title>
		<link href="http://www.limodev.cn/blog/?p=359"/>
		<id>http://www.limodev.cn/blog/?p=359</id>
		<updated>2008-11-23T13:18:58+00:00</updated>
		<content type="html">&lt;p&gt;需求简述&lt;/p&gt;
&lt;p&gt;这里我们请读者实现下列功能：&lt;/p&gt;
&lt;p&gt;对一个存放字符串的双向链表，把存放在其中的字符串转换成大写字母。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-Don’t Repeat Yourself(DRY)(上)</title>
		<link href="http://www.limodev.cn/blog/?p=351"/>
		<id>http://www.limodev.cn/blog/?p=351</id>
		<updated>2008-11-19T23:01:24+00:00</updated>
		<content type="html">&lt;p&gt;需求简述&lt;/p&gt;
&lt;p&gt;这里我们请读者实现下列功能：&lt;/p&gt;
&lt;p&gt;对一个存放整数的双向链表，找出链表中的最大值。&lt;br /&gt;
对一个存放整数的双向链表，累加链表中所有整数。&lt;/p&gt;
&lt;p&gt;多写多练，不要偷懒，写完之后请仔细思考一下有无改进的余地。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">把共享库(SO)加载到指定的内存地址</title>
		<link href="http://www.limodev.cn/blog/?p=342"/>
		<id>http://www.limodev.cn/blog/?p=342</id>
		<updated>2008-11-19T12:05:44+00:00</updated>
		<content type="html">&lt;p&gt;一位朋友最近遇到一个棘手的问题，希望把共享库(SO)加载到指定的内存地址，目的可能是想通过prelink来加快应用程序的起动速度。他问我有没有什么方法。我知道Windows下是可以的，比如在VC6里设置/base的值就行了，所以相信在linux下也是可行的。&lt;/p&gt;
&lt;p&gt;VC有编译选项可以设置，猜想gcc也应该有吧。gcc本身只是一个外壳，链接工作是由于ld完成的，当然是应该去阅读ld命令行选项文档。很快发现ld有个—image-base选项，可以设置动态库的加载地址。&lt;/p&gt;
&lt;p&gt;通过Xlinker把这个参数传递给ld，但是ld不能识别这个选项：&lt;/p&gt;
&lt;p&gt;gcc -g -shared test.c -Xlinker &amp;#8211;image-base -Xlinker 0&amp;#215;00c00000 -o libtest.so&lt;br /&gt;
/usr/bin/ld: unrecognized option &amp;#8216;&amp;#8211;image-base&amp;#8217;&lt;br /&gt;
/usr/bin/ld: use the &amp;#8211;help option for usage information&lt;br /&gt;
collect2: ld returned 1 exit status&lt;/p&gt;
&lt;p&gt;再仔细看手册，原来这个选项只适用于PE文件，PE文件是Windows下专用的，在linux下自然用不了，看来得另想办法。&lt;/p&gt;
&lt;p&gt;我知道ld script可以控制ld的行为，于是研究ld script的写法，按照手册里的说明，写了一个简单的ld script:&lt;/p&gt;
&lt;pre&gt;
     SECTIONS
     {
       . = 0x00c00000;
       .text : { *(.text) }
       .data : { *(.data) }
       .bss : { *(.bss) }
     }
&lt;/pre&gt;
&lt;p&gt;按下列方式编译：&lt;/p&gt;
&lt;p&gt;gcc -shared -g -Xlinker &amp;#8211;script -Xlinker ld.s test.c -o libtest.so&lt;br /&gt;
gcc -g main.c -L./ -ltest -o test.exe&lt;/p&gt;
&lt;p&gt;用ldd查看，加载地址正确。&lt;/p&gt;
&lt;pre&gt;
[root@localhost lds]# ldd test.exe
        linux-gate.so.1 =&gt;  (0x00aff000)
        libtest.so =&gt; ./libtest.so (0x00c00000)
        libc.so.6 =&gt; /lib/libc.so.6 (0x007fa000)
        /lib/ld-linux.so.2 (0x007dd000)
&lt;/pre&gt;
&lt;p&gt;但运行时会crash:&lt;/p&gt;
&lt;p&gt;[root@localhost lds]# ./test.exe&lt;br /&gt;
Segmentation fault&lt;/p&gt;
&lt;p&gt;调试运行可以发现：&lt;/p&gt;
&lt;pre&gt;
(gdb) r

Starting program: /work/test/lds/test.exe
Reading symbols from shared object read from target memory...done.
Loaded system supplied DSO at 0x452000

Program received signal SIGSEGV, Segmentation fault.

0x007e7a10 in _dl_relocate_object () from /lib/ld-linux.so.2
(gdb) bt
#0  0x007e7a10 in _dl_relocate_object () from /lib/ld-linux.so.2
#1  0x007e0833 in dl_main () from /lib/ld-linux.so.2
#2  0x007f056b in _dl_sysdep_start () from /lib/ld-linux.so.2
#3  0x007df48f in _dl_start () from /lib/ld-linux.so.2
#4  0x007dd847 in _start () from /lib/ld-linux.so.2
&lt;/pre&gt;
&lt;p&gt;猜想可能是ld.s写得不全，导致一些信息不正确。于是用ld –verbose导出一个默认的ld script。不出所料，默认的ld script非常冗长，下面是开头一段：&lt;/p&gt;
&lt;pre&gt;
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT(&quot;elf32-i386&quot;, &quot;elf32-i386&quot;,

          &quot;elf32-i386&quot;)
OUTPUT_ARCH(i386)
ENTRY(_start)
SEARCH_DIR(&quot;/usr/i386-redhat-linux/lib&quot;); SEARCH_DIR(&quot;/usr/local/lib&quot;); SEARCH_DIR(&quot;/lib&quot;); SEARCH_DIR(&quot;/usr/lib&quot;);
SECTIONS
{
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;
  .interp         : { *(.interp) }
  .hash           : { *(.hash) }
  .dynsym         : { *(.dynsym) }
  .dynstr         : { *(.dynstr) }
  .gnu.version    : { *(.gnu.version) }
  .gnu.version_d  : { *(.gnu.version_d) }
  .gnu.version_r  : { *(.gnu.version_r) }
&lt;/pre&gt;
&lt;p&gt;按照ld script的语法，我把它修改为(红色部分为新增行)：&lt;/p&gt;
&lt;pre&gt;
/* Script for -z combreloc: combine and sort reloc sections */
OUTPUT_FORMAT(&quot;elf32-i386&quot;, &quot;elf32-i386&quot;,
          &quot;elf32-i386&quot;)
OUTPUT_ARCH(i386)
ENTRY(_start)
SEARCH_DIR(&quot;/usr/i386-redhat-linux/lib&quot;); SEARCH_DIR(&quot;/usr/local/lib&quot;); SEARCH_DIR(&quot;/lib&quot;); SEARCH_DIR(&quot;/usr/lib&quot;);

SECTIONS
{
  /* Read-only sections, merged into text segment: */
  PROVIDE (__executable_start = 0x08048000); . = 0x08048000 + SIZEOF_HEADERS;
 &lt;span&gt; . = 0x00c00000;&lt;/span&gt;
  .interp         : { *(.interp) }
  .hash           : { *(.hash) }
  .dynsym         : { *(.dynsym) }
  .dynstr         : { *(.dynstr) }
  .gnu.version    : { *(.gnu.version) }
  .gnu.version_d  : { *(.gnu.version_d) }

  .gnu.version_r  : { *(.gnu.version_r) }
&lt;/pre&gt;
&lt;p&gt;用这个ld script再次测试，一切正常。又验证多个共享库的情况，也正常，下面是测试数据：&lt;/p&gt;
&lt;p&gt;test.c&lt;/p&gt;
&lt;pre&gt;
int test(int n)
{
    return n;
}
&lt;/pre&gt;
&lt;p&gt;test1.c&lt;/p&gt;
&lt;pre&gt;
int test1(int n)
{
    return n;
}
&lt;/pre&gt;
&lt;p&gt;main.c&lt;/p&gt;
&lt;pre&gt;
extern int test(int n);
extern int test1(int n);
#include 

int main(int argc, char* argv[])
{
    printf(&quot;Hello: %d %d\n&quot;, test(100), test1(200));

    getchar();

    return 0;
}
&lt;/pre&gt;
&lt;p&gt;Makefile&lt;/p&gt;
&lt;pre&gt;
all:
    gcc -shared -g -Xlinker --script -Xlinker ld.s test.c -o libtest.so
    gcc -shared -g -Xlinker --script -Xlinker ld1.s test1.c -o libtest1.so
    gcc -g main.c -L./ -ltest -ltest1 -o test.exe

clean:
    rm -f *.so *.exe
&lt;/pre&gt;
&lt;p&gt;libtest.so的加载地址为：0&amp;#215;00c00000&lt;br /&gt;
libtest1.so的加载地址为：0&amp;#215;00d00000&lt;/p&gt;
&lt;p&gt;ldd显示结果：&lt;/p&gt;
&lt;pre&gt;
        linux-gate.so.1 =&gt;  (0x00aa3000)
        libtest.so =&gt; ./libtest.so (0x00c00000)
        libtest1.so =&gt; ./libtest1.so (0x00d00000)
        libc.so.6 =&gt; /lib/libc.so.6 (0x007fa000)
        /lib/ld-linux.so.2 (0x007dd000)
&lt;/pre&gt;
&lt;p&gt;maps的内容为：&lt;/p&gt;
&lt;pre&gt;
007dd000-007f6000 r-xp 00000000 03:01 521466     /lib/ld-2.4.so
007f6000-007f7000 r-xp 00018000 03:01 521466     /lib/ld-2.4.so
007f7000-007f8000 rwxp 00019000 03:01 521466     /lib/ld-2.4.so
007fa000-00926000 r-xp 00000000 03:01 523579     /lib/libc-2.4.so
00926000-00929000 r-xp 0012b000 03:01 523579     /lib/libc-2.4.so
00929000-0092a000 rwxp 0012e000 03:01 523579     /lib/libc-2.4.so
0092a000-0092d000 rwxp 0092a000 00:00 0
00c00000-00c01000 r-xp 00001000 03:03 16370      /work/test/ldsex/libtest.so
00c01000-00c02000 rwxp 00001000 03:03 16370      /work/test/ldsex/libtest.so
00cf1000-00cf2000 r-xp 00cf1000 00:00 0          [vdso]
00d00000-00d01000 r-xp 00001000 03:03 16373      /work/test/ldsex/libtest1.so
00d01000-00d02000 rwxp 00001000 03:03 16373      /work/test/ldsex/libtest1.so
08048000-08049000 r-xp 00000000 03:03 16374      /work/test/ldsex/test.exe
08049000-0804a000 rw-p 00000000 03:03 16374      /work/test/ldsex/test.exe
b7fdf000-b7fe0000 rw-p b7fdf000 00:00 0
b7fed000-b7ff0000 rw-p b7fed000 00:00 0
bf8db000-bf8f0000 rw-p bf8db000 00:00 0          [stack]
&lt;/pre&gt;
&lt;p&gt;从以上测试结果可以看出，这种方法是可行的。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">KJava虚拟机hack笔记-编译</title>
		<link href="http://www.limodev.cn/blog/?p=347"/>
		<id>http://www.limodev.cn/blog/?p=347</id>
		<updated>2008-11-18T14:23:11+00:00</updated>
		<content type="html">&lt;p&gt;KJava虚拟机有两个版本，一个针对智能手机等设备的phoneme_advanced，另一个是针对传统手机(feature phone) 等设备的的phoneme_feature。去年我花过一点时间研究phoneme_advanced，在基于DirectFB的实现下，成功的运行了一个简单MIDlet程序。&lt;/p&gt;
&lt;p&gt;最近要移植KVM到broncho平台上，时隔一年，我发现phoneme_advanced的实现还是非常不完善，里面有很多低级错误，虽然改掉这些错误并不困难，但是我怀疑是否有人使用这个版本做过产品，所以这一次我决定移植phoneme_feature。&lt;/p&gt;
&lt;p&gt;编译phoneme_feature比phoneme_advanced要容易得多，基本上没有遇到什么问题。&lt;/p&gt;
&lt;p&gt;1.下载并安装j2sdk-1_4_2_15-linux-i586.bin。注意不要使用最新版本的JDK，否则出现语法上不兼容的问题。&lt;/p&gt;
&lt;p&gt;2.下载并解圧phoneme_feature-mr3-rel-src-b01-17_jul_2008.zip。经过验证这个版本还可以，比使用SVN中的最新代码更可靠。&lt;/p&gt;
&lt;p&gt;3.设置环境变量，我们把它放到脚本evn.sh中。&lt;/p&gt;
&lt;pre&gt;
#!/bin/bash
export PREFIX=${PREFIX_USR_LOCAL/--prefix=/}

if [ &quot;$1&quot; = &quot;&quot; ]
then
    echo &quot;Usage: . env.sh ARCH(i386|arm)&quot;
    return;
fi

export ARCH=&quot;$1&quot;
export CPU=$ARCH
export MEHOME=$PWD
export ENABLE_PCSL=true
export BUILD_OUTPUT_DIR=$MEHOME/output
export CLDC_DIR=$MEHOME/cldc
export JVMWorkSpace=$CLDC_DIR
export JVMBuildSpace=$BUILD_OUTPUT_DIR/cldc
export MIDP_OUTPUT_DIR=$BUILD_OUTPUT_DIR/midp
export MIDP_BUILD_DIR=$MEHOME/midp/build/linux_gtk_gcc

if [ &quot;$ARCH&quot; = &quot;arm&quot; ]
then
    export CPU_ALIAS=&quot;arm&quot;
    export TARGET_CPU=&quot;arm&quot;
    export CLDC_BUILD_DIR=${JVMWorkSpace}/build/linux_arm
        export CLDC_DIST_DIR=${JVMBuildSpace}/linux-$CPU_ALIAS/dist
else
    export CPU_ALIAS=&quot;x86&quot;
    export TARGET_CPU=&quot;i386&quot;
    export CLDC_BUILD_DIR=${JVMWorkSpace}/build/linux_i386
        export CLDC_DIST_DIR=${JVMBuildSpace}/linux_i386/dist
fi

export DIRECTFB_INSTALL_DIR=$PREFIX
export PCSL_PLATFORM=linux_&quot;$ARCH&quot;_gcc
export PCSL_OUTPUT_DIR=$BUILD_OUTPUT_DIR/pcsl
export TOOLS_DIR=$MEHOME/tools
export TOOLS_OUTPUT_DIR=$BUILD_OUTPUT_DIR/tools
&lt;/pre&gt;
&lt;p&gt;4.编译，我们把它放到脚本build.sh中。&lt;/p&gt;
&lt;pre&gt;
#!/bin/bash

cd $MEHOME/pcsl
make clean
make NETWORK_MODULE=bsd/generic
cd $MEHOME

cd $CLDC_BUILD_DIR
make clean
make USE_MIDP=true CVM_DEBUG=true USE_DIRECTFB=true USE_QT_FB=false ENABLE_PCSL=true

cd $MIDP_BUILD_DIR
make clean
make SUBSYSTEM_LCDUI_MODULES=platform_widget PLATFORM=linux_gtk SUBSYSTEM_EVENTS_MODULES=slave_mode INCLUDE_SHELL_SCRIPTS=true

cd  $MEHOME
&lt;/pre&gt;
&lt;p&gt;5.安装，我们把它放到脚本install.sh中。&lt;/p&gt;
&lt;pre&gt;
#!/bin/bash
if [ &quot;$PREFIX&quot; = &quot;&quot; ]
then
    echo &quot;PREFIX is not defined&quot;
else
    INSTALL_PATH=$PREFIX
    rm -rf $INSTALL_PATH/java
    mkdir -p $INSTALL_PATH/java
    echo &quot;cp -rf $CDC_DIST_DIR $INSTALL_PATH/java/cdc&quot;
    cp -rf $MIDP_OUTPUT_DIR $INSTALL_PATH/java/j2me
    cp -f $CLDC_DIST_DIR/bin/cldc_vm* $INSTALL_PATH/java/j2me/bin
    mv $INSTALL_PATH/java/j2me/classes.zip $INSTALL_PATH/java/j2me/classes.jar
    rm -rf $INSTALL_PATH/java/j2me/classes
    rm -rf $INSTALL_PATH/java/j2me/generated/
    rm -rf $INSTALL_PATH/java/j2me/obj
    rm -rf $INSTALL_PATH/java/j2me/ROM*
fi
&lt;/pre&gt;
&lt;p&gt;我这里编译的是基于GTK+实现的MIDP，编译其它版本的要做相应修改。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">关于知识积累与创新的思考</title>
		<link href="http://www.limodev.cn/blog/?p=344"/>
		<id>http://www.limodev.cn/blog/?p=344</id>
		<updated>2008-11-18T14:03:41+00:00</updated>
		<content type="html">&lt;p&gt;最近有网友提出这样的担心，现有知识太多了，会不会被这些知识所淹没，失去自己的创新。这种担心也是情有可原的，连大师李敖都不愿用电脑，他认为从网络中可获取的知识太多了，信息爆炸，让人分不清钻石和狗屎，一咕噜接收过去，会让人发疯。&lt;/p&gt;
&lt;p&gt;但我认为这只是一种逃避，一种不去了解新事物的借口。超市里商品种类繁多，没有人会全部买下，有人抱怨商品太多吗。同样的道理，知识多了，选择就多了，这是一种好事，你也不必全部学会，学习你需要的就行了。把爱学习的人贬为没有创新的人，那是懒人的清高，那种懒人绝不会比爱学习的人有更多创意。&lt;/p&gt;
&lt;p&gt;我们会被知识淹没吗？纵观历史，人类有过多次这种的主观上忧虑，最终证明这些担心都是多余的。人们曾担心工业革命带来生产力大幅度的提高，会造成大批工人的失业，导致社会的动乱。同样，人们对信息技术革命有类似的顾虑。然而两者都是大大提高各类工作的自动化程度，但并没有带来人们担心的失业和动乱，相反创造了更多的就业机会，社会更繁荣。Internet的兴起，也没有出现信息爆炸的恐怖场景，相反它已经成为我们不可以或缺的助手，我们乐于其中。&lt;/p&gt;
&lt;p&gt;当然，一部分人因为无法适应相应的变化而被淘汰，但更多的人是从中受益了。历史是向前发展的，青山扫不住，毕竟东流去。读书或许会让人变成书呆子，这并不是书本身的错误，也不能因为100个读书人中，有1个书呆子就劝告大家不要读书。就像我们不能因为有人吃海鲜过敏了，就让大家都不要吃海鲜一样的道理。&lt;/p&gt;
&lt;p&gt;知识积累在任何时候都必不可少，知识积累是非常重要的，它是创新的基础，而不是创新的绊脚石。没有思想的人不读书，仍然不会思想，不会有创新。有思想的人，读书越多就想得越多，想得越多就创新越多。&lt;/p&gt;
&lt;p&gt;爱因斯坦说过，想象力比知识更重要。我想他的全意是说知识重要，想象力会锦上添花。没有知识就没有想象力。没有黎曼几何，爱因斯坦能想象出相对论吗？没有殴氏几何，黎曼能想象出非欧几何吗？&lt;/p&gt;
&lt;p&gt;牛顿这样的天才也说，他是之所以能取这么大的成就，是因为他站在巨人的肩上。看看牛顿生活的背景，就会明白他说的大实话：如果没有哥白尼、伽利略等人前辈打下的基础，没有牛顿年少时的刻苦学习，让他闭门造车，我想，他的想象力再厉害，苹果把他脑瓜砸破了，他也想不出万有引力来。&lt;/p&gt;
&lt;p&gt;天才重要，培养天才的土壤更重要。知识就培养天才的土壤，意大利的佛罗伦萨，人才辈出。差不多历史上有名的艺术家，都到那里吸收过前人的经验。他们没有被知识所淹没，而是吸收这些知识的精华，推陈出新，创造新的奇迹。即使是天才，不去努力学习，不去吸引前人的经验，结果一定和《伤仲永》里的方仲永一样，天分慢慢枯竭，最终成为庸才。&lt;/p&gt;
&lt;p&gt;历史典故或许让人觉得太遥远，让我们来看看这几年IT技术的发展，也许能给我们更多的启发。&lt;/p&gt;
&lt;p&gt;面向对象技术可以说是IT 史最重要的创新之一。但面向对象绝不是横空出世的，没有面向过程，很难想象会出现面向对象这样的技术。从面向过程到面向对象的转变是痛苦的，甚至有人认为这是两种毫不相干的技术。事实上，面向对象技术中70%以上的内容是属于面向过程的，只不过它刻意强调它与面向过程的不同，而给一种错觉罢了。面向对象编程里，离得了函数吗？离得了数据结构吗？这些都是面向过程里的内容，不过是换了一种外观，而发生质的变化，这就是创新。那些是面向对象技术的开拓者，无一不曾是面向过程的高手。&lt;/p&gt;
&lt;p&gt;像其它一些热门技术，如契约式编程、AOP、 UML和XP等方法或技术，像C#和JAVA等新一代编程语言，无不一是在吸收前人的经验上，加以改进，加以创新而成的。这些实例中，没有丝毫迹象表明知识的积累阻碍了创新，也没有丝毫迹象表明前人的留下的知识太多，而让我们止步不前，不是吗？技术发展越来越快，而不是越来越慢。&lt;/p&gt;
&lt;p&gt;雄鹰不会嫌天空太广阔，鲸鱼不会怪大海太深远，程序员不会怪Google能查到太多答案。知识多，不可怕，学习到的知识多，更不可怕。学到的知识，只会激发你的创造力，而不会阻碍创造力。重要的是要有自己的目标，有自己的学习方向，才不会知识的海洋里迷失，才能在有限的时间里学到更多的东西。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">函数指针与软件设计</title>
		<link href="http://www.limodev.cn/blog/?p=340"/>
		<id>http://www.limodev.cn/blog/?p=340</id>
		<updated>2008-11-18T13:47:23+00:00</updated>
		<content type="html">&lt;p&gt;记得刚开始工作时，一位高手告诉我，说，longjmp和setjmp玩得不熟，就不要自称为C语言高手。当时我半信半疑，为了让自己向高手方向迈进，还是花了一点时间去学习longjmp和setjmp的用法。后来明白那不单是跳来跳去那样简单，而是一种高级的异常处理机制，在某些情况下确实很有用。&lt;/p&gt;
&lt;p&gt;为了显示自己的技巧，也在自己的程序中用过几次。渐渐发现这样的技巧带来的好处是有代价的，破坏了程序的结构化设计，程序变得很难读，尤其对新手来说。终于明白这种技巧不过是一种调味料，在少数情况使用几次，可以简化对问题的处理。如果把调味拿来当饭吃，一定会本末倒置，写出的程序会呈现营养不良之状。&lt;/p&gt;
&lt;p&gt;事实上，longjmp和setjmp玩得熟不熟与是不是C语言高手，不是因果关系。但是，如果可以套用那位高手的话，我倒想说如果函数指针玩得不熟，就不要自称为C语言高手。为什么这么说呢，函数指针有那么复杂吗？当然不是，任何一个稍有编程常识的人，不管他懂不懂C语言，在10分钟内，我想他一定可以明白C语言中的函数指针是怎么回事。&lt;/p&gt;
&lt;p&gt;原因在于，难的不是函数指针的概念和语法本身，而是在什么时候，什么地方该使用它。函数指针不仅是语法上的问题，更重要的是它是一个设计范畴。真正的高手当然不单应该懂得语法层面上的技巧，更应该懂得设计上的方法。不懂设计，能算高手吗？怀疑我在夸大其辞吗？那我们先看看函数指针与哪些设计方法有关：&lt;/p&gt;
&lt;p&gt;与分层设计有关。分层设计早就不是什么新的概念，分层的好处是众所周知的，比较明显好处就是简化复杂度、隔离变化。采用分层设计，每层都只需关心自己的东西，这减小了系统的复杂度，层与层之间的交互仅限于一个很窄的接口，只要接口不变，某一层的变化不会影响其它层，这隔离了变化。&lt;/p&gt;
&lt;p&gt;分层的一般原则是，上层可以直接调用下层的函数，下层则不能直接调用上层的函数。这句话说来简单，在现实中，下层常常要反过来调用上层的函数。比如你在拷贝文件时，在界面层调用一个拷贝文件函数。界面层是上层，拷贝文件函数是下层，上层调用下层，理所当然。但是如果你想在拷贝文件时还要更新进度条，问题就来了。一方面，只有拷贝文件函数才知道拷贝的进度，但它不能去更新界面的进度条。另外一方面，界面知道如何去更新进度条，但它又不知道拷贝的进度。怎么办？常见的做法，就是界面设置一个回调函数给拷贝文件函数，拷贝文件函数在适当的时候调用这个回调函数来通知界面更新状态。&lt;/p&gt;
&lt;p&gt;与抽象有关。抽象是面向对象中最重要的概念之一，也是面向对象威力强大之处。面向对象只是一种思想，大家都知道，用C语言一样可以实现面向对象的编程。这可不是为了赶时髦，而是一种实用的方法。如果你对此表示怀疑，可以去看看GTK+、linux kernel等开源代码。&lt;/p&gt;
&lt;p&gt;接口是最高级的抽象。在linux kernel里面，接口的概念无处不在，像虚拟文件系统(VFS)，它定义一个文件系统的接口，只要按照这种接口的规范，你可以自己开发一个文件系统挂上去。设备驱动程序更是如此，不同的设备驱动程序有自己一套不同的接口规范。在自己开发设备开发驱动程序时，只要遵循相应的接口规范就行了。接口在C语言中如何表示？很简单，就是一组函数指针。&lt;/p&gt;
&lt;p&gt;与接口与实现分开有关。针对接口编程，而不是针对实现编程，此为《设计模式》的第一条设计准则。分开接口与实现的目标是要隔离变化。软件是变化的，如果不能把变化的东西隔离开来，导致牵一发而动全身，代价是巨大的。这是大家所不愿看到的。&lt;/p&gt;
&lt;p&gt;C语言既然可以实现面向对象的编程，自然可以利用设计模式来分离接口与实现。像桥接模式、策略模式、状态模式、代理模式等等，在C语言中，无一不需要利用函数指针来实现。&lt;/p&gt;
&lt;p&gt;与松耦合原则有关。面向过程与面向对象相比，之所以显得苍白无力，原因之一就是它不像面向对象一样，可以直观的把现实模型映射到计算机中。面向过程讲的是层层控制，而面向对象更强调的对象间的分工合作。现实世界中的对象处于层次关系的较少，处于对等关系的居多。也就是说，对象间的交互往往是双向的。这会加强对象间的耦合性。&lt;/p&gt;
&lt;p&gt;耦合本身没有错，实际上耦合是必不可少的，没有耦合就没有协作，对象之间无法形成一个整体，什么事也做不了。关键在于耦合要恰当，在实现预定功能的前提下，耦合要尽可能的松散。这样，系统的一部分变化对其它部分的影响会很少。&lt;/p&gt;
&lt;p&gt;函数指针是解耦对象关系的最佳利器。Signal(如boost的signal和glib中的signal)机制是一个典型的例子，一个对象自身的状态可能是在变化的（或者会触发一些事件），而其它对象关心它的变化。一旦该对象有变化发生，其它对象要执行相应的操作。&lt;/p&gt;
&lt;p&gt;如果该对象直接去调用其它对象的函数，功能是完成了，但对象之间的耦合太紧了。如何把这种耦合降到最低呢，signal机制是很好的办法。它的原理大致如下：其它关注该对象变化的对象主动注册一个回调函数到该对象中。一旦该对象有变化发生，就调用这些回调函数通知其它对象。功能同样实现了，但它们之间的耦合度降低了。&lt;/p&gt;
&lt;p&gt;在C语言中，要解决以上这些问题，不采用函数指针，将是非常困难的。在编程中，如果你从没有想到用函数指针，很难想像你是一个C语言高手。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">GNOME.Asia Summit 2008 Report</title>
		<link href="http://blogs.sun.com/emily/entry/gnome_asia_summit_2008_report"/>
		<id>http://blogs.sun.com/emily/entry/gnome_asia_summit_2008_report</id>
		<updated>2008-11-17T15:49:11+00:00</updated>
		<content type="html">&lt;p&gt;
The &lt;b&gt;first ever GNOME.Asia Summit&lt;/b&gt; was held at the Beihang university, Beijing, China, from October 18th to 19th, 2008. The GNOME Foundation was the organizer of GNOME.Asia Summit in collaboration with Sun Microsystems, Beijing Linux User Group (BLUG)&amp;nbsp; and China OSS Promotion Union (COPU). This premier event was very well attended:&amp;nbsp;&lt;b&gt; 318&lt;/b&gt; people attended the first day, and &lt;b&gt;212&lt;/b&gt; people attended the second day. The majority of the attendees (2/3) were from universities, the remainder from companies.&amp;nbsp; Ninety percent of the participants were local (from China)&amp;nbsp; with the remainder from other countries. We had 46 volunteers from Beijing Linux Users Group, Beijing OpenSolaris Users Group, OpenParty, Beihang university, Beiyou university and many individual contributors,&amp;nbsp; they helped us in many ways including registration, guidance, emcees, photography and video.&lt;br /&gt;&lt;br /&gt;This year, there were total &lt;b&gt;42&lt;/b&gt; speakers, 70% were local speakers and 30% of them were from other countries,&amp;nbsp; including USA, Finland and Singapore etc. There were 46 talks over the two days of the summit. The talks covered several topics, including: &lt;b&gt;accessibility&lt;/b&gt;, &lt;b&gt;mobility&lt;/b&gt;, &lt;b&gt;i18n&lt;/b&gt;,&lt;b&gt; community&lt;/b&gt;,&lt;b&gt; development &lt;/b&gt;and&lt;b&gt; deployment&lt;/b&gt;. Each day started with a general session in the morning and was followed by 5 tracks in the afternoon. For more details, refer to the &lt;a href=&quot;http://www.gnome.asia/en/schedule/&quot;&gt;schedule&lt;/a&gt; on the summit website. Most of the slides have been uploaded to the website, as well as speakers' bios and photos.&lt;br /&gt;&lt;br /&gt;We had many sponsors for the first GNOME.Asia Summit. &lt;b&gt;Sun Microsystems&lt;/b&gt; sponsored the summit at gold level. We had three silver sponsors: &lt;b&gt;Nokia&lt;/b&gt;, &lt;b&gt;Motorola&lt;/b&gt; and &lt;b&gt;Mozilla&lt;/b&gt;. &lt;b&gt;Red Hat&lt;/b&gt; sponsored the Summit at bronze level. We also had one local sponsor, &lt;b&gt;Lemote&lt;/b&gt;, who sponsored the summit by providing three Lemote Laptops for the lucky-draw program. &lt;b&gt;Google&lt;/b&gt; sponsored the summit by providing gifts to participants. Finally, &lt;b&gt;CSDN&lt;/b&gt; and &lt;b&gt;Programmer&lt;/b&gt; Magazine were media partners. We are grateful for the great support we received from all of our sponsors.&lt;br /&gt;&lt;br /&gt;We had 7 booths at the venue: Sun, Motorola, Mozilla, Red Hat, CSDN &amp;amp; Programmer Magazine, Lemote, and the Beijing Linux Users Group. Each booth brought their own booth materials such as: laptops, PCs, lab equipment, gifts, posters and fliers. For example:&lt;br /&gt;http://www.gnome.asia/static/upload/photos/DSC_2131.JPG&lt;br /&gt;http://www.gnome.asia/static/upload/photos/DSC_2140.JPG&lt;br /&gt;&lt;br /&gt;We invited 5 media reporters to the Summit, they interviewed important speakers and core contributors to the GNOME community. On the 18th, they interviewed Stormy Peters and Brian Cameron from the GNOME Foundation, Robert O'Dea and Paul Mei from Sun Microsystems, Kate Alhola and Richard Sun from Nokia Finland. On the 19th, CSDN and Programmer magazine interviewed Rafael Camargo from Motorola, Jack Guo from Mozilla Online, Kevin Song from COPU (China OSS Promotion Union), Frederic Muller and Pockey Lam from BLUG (Beijing Linux Users Group). They also interviewed three Chinese input method authors: James Su, Yong Sun and Peng Huang ,&amp;nbsp; and Funda Wang from GNOME Chinese translation team. Below are the media reports:&lt;br /&gt;&lt;br /&gt;They are in Chinese.&lt;br /&gt;http://news.csdn.net/n/20081023/120205.html&lt;br /&gt;http://publish.it168.com/2008/1023/20081023045901.shtml&lt;br /&gt;http://soft.chinabyte.com/371/8517371.shtml&lt;br /&gt;http://it.hexun.com/20 08-10-21/110209130.html&lt;br /&gt;http://digi.it.sohu.com/20081022/n260181226.shtml&lt;br /&gt;http://paper.chinahightech.com.cn/html/2008-10/27/content_7247.htm&lt;br /&gt;http://www.lupaworld.com/viewnews-117800.html&lt;br /&gt;&lt;b&gt;&lt;br /&gt;Highlights of the Summit&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;One of the top three OSS conference in China&lt;/b&gt;&lt;br /&gt;The GNOME.Asia Summit&amp;nbsp; ranked as one of the top 3 open source conferences in Beijing this year.&amp;nbsp; The others were: the Linux Developer Symposium in February and the OpenOffice organization annual conference in November. All the open source communities think it is time to go to Asia!&lt;br /&gt;&lt;b&gt;&lt;br /&gt;Keynote about GNOME Community&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Stormy Peters' keynote &lt;/b&gt;&amp;quot;&lt;a title=&quot;Stormy's slides&quot; href=&quot;http://www.gnome.asia/static/upload/event_file/0810GNOMEAsiaCommunityBuiltSoftware-small.pdf&quot;&gt;Community built software is bringing change to the world&lt;/a&gt;&amp;quot; kicked off the summit on the first morning.During this speech, Stormy introduced the GNOME project and its strong community. She said that the GNOME community has developed core values like accessibility, internationalization and developer-friendliness that are shared amongst all the volunteers that work on GNOME. Over time, the GNOME project has developed strong foundations like time-based releases, universal access, and good communication with companies in the industry as well as the community itself. Building on the community's values and foundations, the GNOME community is now enabling their technologies for the future with initiatives like GNOME Mobile.&amp;nbsp; Finally, she encouraged everyone to join the GNOME community.&lt;b&gt;&lt;br /&gt;Brian Cameron's&lt;/b&gt; keynote about &amp;quot;Building Free Software Asia&amp;quot; was also very interesting. At the start of his talk, Brian played a &lt;a href=&quot;http://soaringbrain.com/GNOMEasiaSummit.swf&quot;&gt;cool video&lt;/a&gt;, made by a contributor in the GNOME community, which demonstrated GNOME using animations and cool music.Next, Brian introduced the concept of free software, open software, the GNOME community, how to get involved and be active with a free software project.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Accessibility&lt;/b&gt;&lt;br /&gt;Accessibility was one the main topics in this Summit. So we were honored to have Will Walker, lead of the GNOME accessibility project, join this summit as well as other accessibility developers, QA engineers, and teachers from the Beijing School for the Blind. On the first day, Will Walker gave an &lt;a href=&quot;http://www.gnome.asia/static/upload/event_file/2008-10-18-GNOME.Asia.odp&quot;&gt;overview of GNOME accessibility&lt;/a&gt;. &lt;br /&gt;Later, Li Yuan introduced the accessibility infrastructure from a developer point of view. During a lightning talk, Ray Wang from Novell China introduced Mono accessibility &amp;amp; UI Automation. On the second day, Will Walker gave a second talk, this time about Orca. Later, Tim Miao and Harry Fu shared their experience with accessibility testing. We also invited two teachers from the Beijing School for the Blind. They were interested in the screen reader, Orca, and they attended Will Walker's talk. After the talk, they went to Sun's accessibility booth to watch a demo about accessibility and share their expectations and user experiences with Will Walker and other accessibility developers. There were many accessibility discussions covering topics such as automation testing tools in GNOME community. Further discussion are going on after the summit.&lt;br /&gt;&lt;b&gt;&lt;br /&gt;GNOME Mobile&lt;/b&gt;&lt;br /&gt;GNOME technologies are used in many of the world's leading mobile phones. Nokia and Motorola, the leaders of the mobile industrial attended the first GNOME.Asia Summit. Nokia representatives from Finland participated in the summit by giving various technical talks which covered the Qt port to GTK+ on maemo, Tracker, GStreamer and memory management on mobile devices. Motorola's director Rafael Camargo talked about Motorola's commercial experience with Linux, how to improve the collaboration between open source communities and commercial enterprises. Finally, he announced that Motorola is joining the GNOME Foundation this year. Building on open source technologies enables them not only to get to market faster but also to offer cheaper and more open solutions.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Localization&lt;/b&gt;&lt;br /&gt;Localization is very important to non-English speaking GNOME users. This was also one of the main topics of this Summit. We invited four authors of the input method sub-system. They were: James Su, lead of the SCIM community (www.scim-im.org); Peng Huang, author of scim-python (code.google.com/p/scim-python) and ibus (code.google.com/p/ibus); Peng Wu, author of Novel Pinyin (http://sourceforge.net/projects/novel-pinyin); Yong Sun, maintainer of SunPinyin (www.opensolaris.org/os/project/input-method). They co-hosted a technical talk about the input method frameworks and introduced IIIMF and SCIM. Funda Wang,&amp;nbsp; leader of i18n-zh team, talked about the overall localization infrastructure of the GNOME project, the GTP infrastructure (administrator, team leader, translator, tester), and how to contribute to the GNOME Translation project.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;GNOME &amp;amp; Mozilla&lt;/b&gt;&lt;br /&gt;Mozilla is a sister community to GNOME. It was great to have Mozilla at this Summit. Jack Guo from Mozilla Online talked about &amp;quot;Mozilla in China&amp;quot;, and shared his experiences promoting Firefox in China. Mozilla Developers, Brian Lu and Alfred Peng from the OpenSolaris community, shared their experiences developing Firefox and Songbird on the OpenSolaris desktop.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Lightning Talks&lt;/b&gt;&lt;br /&gt;At the summit we introduced a new talk style to China: Lightning talks.&lt;br /&gt;A Lightning Talk is a short presentation given at a conference or similar forum. Unlike other presentations, lightning talks only last a few minutes and several will usually be delivered one after the other by different speakers.&lt;br /&gt;At the GNOME.Asia Summit, we had lightning talks on the afternoon of the 18th. The lightning talks session was one hour, with each lightning talk being only 5 minutes, with no Q&amp;amp;A session. We used a gong as timer. Here's the list of lightning talks:&lt;br /&gt;1. Richard Sun : Package management&lt;br /&gt;2. Simon Zheng : New generation of GNOME Display Manager&lt;br /&gt;3. Coly Li : Quick introduction to grub4ext4&lt;br /&gt;4. Ray Wang: Mono accessibility &amp;amp; UI Automation&lt;br /&gt;5. Anthony Fok : Attracting new GNOME contributors with Glade&lt;br /&gt;6. Jon Philips ：The Open Clip Art Library + China Lightning Talk&lt;br /&gt;7. Funda Wang: Experience Empathy&lt;br /&gt;This was one of the most entertaining parts of the Summit, see:http://www.gnome.asia/static/upload/photos/DSC_2334.JPG&lt;br /&gt;&lt;b&gt;&lt;br /&gt;Live Summit&lt;/b&gt;&lt;br /&gt;Check Live Summit from here: http://www.gnome.asia/en/live/&lt;br /&gt;Thanks to Alfred, Will and Joey's excellent work, we have successfully built the GNOME.Asia Live Summit.&lt;br /&gt;&lt;/p&gt; 
  &lt;p&gt;Online Summit is a real time aggregation tool for Flickr/Youtube/Twitter.&lt;br /&gt;To join in, you can use any of these services:&lt;br /&gt;1. Flickr&lt;br /&gt;&amp;nbsp;&amp;nbsp; - Have an account on Flickr(http://flickr.com/).&lt;br /&gt;&amp;nbsp;&amp;nbsp; - Upload your GNOME.Asia summit pictures and tag them with &amp;quot;gnomeasia&amp;quot;&lt;br /&gt;2. Youtube&lt;br /&gt;&amp;nbsp;&amp;nbsp; - Have an account on Youtube(http://www.youtube.com/).&lt;br /&gt;&amp;nbsp;&amp;nbsp; - Upload your GNOME.Asia summit videos and tag them with &amp;quot;gnomeasia&amp;quot;&lt;br /&gt;3. Twitter&lt;br /&gt;&amp;nbsp;&amp;nbsp; - Have an account on Twitter(http://twitter.com/).&lt;br /&gt;&amp;nbsp;&amp;nbsp; - Send message to the GNOME.Asia twitter by adding &amp;quot;@gnome_asia&amp;quot;. For example, if you want to say hello, just send this message &amp;quot;@gnome_asia hello&amp;quot;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Party and Tour trip&lt;/b&gt;&lt;br /&gt;We had a wonderful celebration party on the evening of the last day at the Laoshe Tea House. We invited organizers, sponsors, volunteers, speakers and media representatives. We had 120 people join this party. See: http://www.gnome.asia/static/upload/photos/DSCF7398.JPG&lt;br /&gt;On October 20th, the GNOME.Asia Summit arranged a one day tour trip for speakers to the Great Wall and Ming Tomb. See: http://www.flickr.com/photos/pockey/2967814109/&lt;br /&gt;&lt;b&gt;&lt;br /&gt;Future work after Summit&lt;/b&gt;&lt;br /&gt;One of the major goals after the GNOME.Asia Summit is building the Beijing GNOME Users Group. There are already some GNOME communities in Beijing, including: GNOME-CN and the GNOME learning panel at Tsinghua University. It would be better if we could gather together everyone who is interested in GNOME and host a GNOME Users Group regularly in Beijing. So far, we have recruited about 10 core members of the Beijing GNOME Users Group, notably: Pockey Lam from BLUG, Zhangshen and Da long from Beihang university, Fengyi from Beiyou University and Yanghong from GNOME-CN. We will use the following infrastructure for the Beijing GNOME Users Group:&lt;br /&gt;1. Website: www.gnome-cn.org (Need to add more modules into this website, like Wiki, BBS etc.)&lt;br /&gt;2. Mailing List: gnome-cn-list@gnome.org&lt;br /&gt;3. IRC: BeijingGUG&lt;br /&gt;We plan to host regular weekly meetings starting in November, 2008.&lt;br /&gt;&lt;br /&gt;Another big task after the Summit involves Pockey Lam from the Beijing LUG who is organizing a student study group on GNOME accessibility projects. Since accessibility generated a lot of interest in Beijing, this is a good to time to encourage more people to contribute with GNOME projects. The accessibility project is the first project for the student's study group. Some local engineers from Sun and Novell China will be mentors for the study group.&lt;br /&gt;&lt;br /&gt;GNOME.Asia Summit was a success, we see a lot of things happening during and after the summit! Let's ride on the momentum and continue to build a strong community in Beijing, in China and in Asia!&amp;nbsp;&lt;/p&gt;</content>
		<author>
			<name>emilychen</name>
			<uri>http://blogs.sun.com/emily/</uri>
		</author>
		<source>
			<title type="html">Emily</title>
			<subtitle type="html">Emily Chen's Blog</subtitle>
			<link rel="self" href="http://blogs.sun.com/emily/feed/entries/atom"/>
			<id>http://blogs.sun.com/emily/feed/entries/atom</id>
			<updated>2008-11-28T03:11:38+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-拥抱变化（下）</title>
		<link href="http://www.limodev.cn/blog/?p=333"/>
		<id>http://www.limodev.cn/blog/?p=333</id>
		<updated>2008-11-17T12:52:59+00:00</updated>
		<content type="html">&lt;p&gt;在专用双向链表中，dlist_printf的实现非常简单，如果里面存放的是整数，用”%d”打印，存放的字符串，用”%s”打印。现在的麻烦在于双向链表是通用的，我们无法预知其中存在的数据类型，也就是我们要面对数据类型的变化。怎么办呢？初学者常见的做法有：&lt;/p&gt;
&lt;p&gt;1.实现多个函数，需要哪个就用哪个。比如实现的有dlist_print_int用来打印存放整数的双向链表，dlist_print_string用来打印存放字符串的双向链表，如此等等，其它类型都有自己的打印函数。&lt;/p&gt;
&lt;p&gt;这种做法的缺点有：一是每个函数的实现方式类似，造成大量重复的代码。二是数据类型的种类不确定，每种数据类型都要写一个print函数，当要存放新的数据类型时，需要修改dlist的实现。&lt;/p&gt;
&lt;p&gt;2.传入一个附加参数来决定如何打印。比如传入1表示按整数方式打印，传入2表示按字符串方式打印，以此类推。&lt;/p&gt;
&lt;p&gt;这种做法比第一种好一点，至少不会造成大量重复的代码。但是同样存在增加新类型时要修改dlist_print函数的问题。&lt;/p&gt;
&lt;p&gt;3调用dlist的接口函数获取每一个位置的数据并打印出来。&lt;/p&gt;
&lt;p&gt;它可以避免前面两种方法的缺点，而且是一种很直观的方式。奇怪的是偏偏很少有人这样去做，原因可能有两个，其一是太拘泥于传统的实现方式而没有想到这一种。其二是担心性能问题，因为通过索引取值，每一次都从头开始定位，其性能开销为O(n*n)。&lt;/p&gt;
&lt;p&gt;其实这种方法是可以接受的，dlist_print是用于辅助测试，我们并不在乎它的性能开销，而且很少在链表中存放成千上万的数据，它带来的性能影响也没有想的那样严重。&lt;/p&gt;
&lt;p&gt;不过在这里我们要介绍一种新的方法:&lt;/p&gt;
&lt;p&gt;dlist_print的大体框架为：&lt;/p&gt;
&lt;pre&gt;
    DListNode* iter = thiz-&gt;first;

    while(iter != NULL)
    {
        print(iter-&gt;data);
        iter = iter-&gt;next;
    }
&lt;/pre&gt;
&lt;p&gt;在上面代码中，我们主要是不知道如何实现print(iter-&gt;data);这行代码。可是谁知道呢？很明显，调用者知道，因为调用者知道里面存放的数据类型。OK，那让调用者来做好了，调用者调用dlist_print时提供一个函数给dlist_print调用，这种回调调用者提供的函数的方法，我们可以称它为回调函数法。&lt;/p&gt;
&lt;p&gt;调用者如何提供函数给dlist_print呢？当然是通过函数指针了。变量指针指向的是一块数据，指针指向不同的变量，则取到的是不同的数据。函数指针指向的是一段代码（即函数），指针指向不同的函数，则具有不同的行为。函数指针是实现多态的手段，多态就是隔离变化的秘诀，这里只是一个开端，后面我们会逐步的深入学习。&lt;/p&gt;
&lt;p&gt;回到正题上，我们看如何实现dlist_print：&lt;br /&gt;
定义函数指针类型：&lt;br /&gt;
typedef DListRet (*DListDataPrintFunc)(void* data);&lt;/p&gt;
&lt;p&gt;声明dlist_print函数：&lt;br /&gt;
DListRet dlist_print(DList* thiz, DListDataPrintFunc print);&lt;/p&gt;
&lt;p&gt;实现dlist_print函数：&lt;/p&gt;
&lt;pre&gt;
DListRet dlist_print(DList* thiz, DListDataPrintFunc print)
{
    DListRet ret = DLIST_RET_OK;
    DListNode* iter = thiz-&gt;first;

    while(iter != NULL)
    {
        print(iter-&gt;data);

        iter = iter-&gt;next;
    }

    return ret;
}
&lt;/pre&gt;
&lt;p&gt;调用方法&lt;/p&gt;
&lt;pre&gt;
static DListRet print_int(void* data)
{
    printf(&quot;%d &quot;, (int)data);

    return DLIST_RET_OK;
}
…
dlist_print(dlist, print_int);
&lt;/pre&gt;
&lt;p&gt;所有问题都解决了，是不是很简单? 我以前写过一篇关于函数指针的BLOG，文中声称不懂函数指针就不要自称是C语言高手，现在我仍然坚持这个观点。函数指针的概念本身很简单，关键在于灵活应用，这里是一个最简单的应用，希望读者仔细体会一下，后面将会有大量篇幅介绍。&lt;/p&gt;
&lt;p&gt;我写了一个简单的示例，它的实现并不完善，不过用来演示我们到目前为止学到的内容已经够了。有兴趣的读者请到&lt;strong&gt;&lt;a href=&quot;http://www.limodev.cn/bbs/download/file.php?id=4&quot;&gt;这里&lt;/a&gt;&lt;/strong&gt;下载。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-拥抱变化（上）</title>
		<link href="http://www.limodev.cn/blog/?p=331"/>
		<id>http://www.limodev.cn/blog/?p=331</id>
		<updated>2008-11-15T07:48:38+00:00</updated>
		<content type="html">&lt;p&gt;需求简述&lt;/p&gt;
&lt;p&gt;大部分初学者在编写双向链表时，为了验证相关函数工作是否正常，都会编写一个dlist_print的函数，它的功能是在屏幕上打印出整个双向链表中的数据。从客观上讲，用dlist_print输出的信息来判断dlist的正确性不是最好的办法，不过脑袋里有质量概念总是值得表扬的。当把专用的双向链表演化成通用的双向链表时，编写一个dlist_print已经不那么简单了。这里我们请读者写一个dlist_printf函数，看看会遇到什么问题。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">创建线程时的几个陷阱</title>
		<link href="http://www.limodev.cn/blog/?p=328"/>
		<id>http://www.limodev.cn/blog/?p=328</id>
		<updated>2008-11-12T13:28:48+00:00</updated>
		<content type="html">&lt;p&gt;前几天帮同事查一个多线程的BUG，不到十秒钟我就找到了问题的根源。N年前我曾犯过类似的错误，呵，今天仍然有人在重复。这些问题都比较典型，把它们写出来，供新手参考吧。&lt;/p&gt;
&lt;p&gt;o 用临时变量作为线程参数的问题。&lt;/p&gt;
&lt;pre&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;assert.h&amp;gt;

void* start_routine(void* param)
{
    char* str = (char*)param;

    printf(&quot;%s:%s\n&quot;, __func__, str);

    return NULL;
}

pthread_t create_test_thread()
{
    pthread_t id = 0;
    char str[] = &quot;it is ok!&quot;;

    pthread_create(&amp;amp;id, NULL, start_routine, str);

    return id;
}

int main(int argc, char* argv[])
{
    void* ret = NULL;

    pthread_t id = create_test_thread();

    pthread_join(id, &amp;amp;ret);

    return 0;
}&lt;/pre&gt;
&lt;p&gt;分析：由于新线程和当前线程是并发的，谁先谁后是无法预测的。可 能create_test_thread 已经执行完成，str已经被释放了，新线程才拿到这参数，此时它的内容已经无法确定了，自然打印出的字符串是随机的。&lt;/p&gt;
&lt;p&gt;o 线程参数共享的问题。&lt;/p&gt;
&lt;pre&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;assert.h&amp;gt; 

void* start_routine(void* param)
{
    int index = *(int*)param;

    printf(&quot;%s:%d\n&quot;, __func__, index);

    return NULL;
}

#define THREADS_NR 10

void create_test_threads()
{
    int i = 0;

    void* ret = NULL;

    pthread_t ids[THREADS_NR] = {0};

    for(i = 0; i &amp;lt; THREADS_NR; i++)
    {
        pthread_create(ids + i, NULL, start_routine, &amp;amp;i);
    }

    for(i = 0; i &amp;lt; THREADS_NR; i++)
    {

        pthread_join(ids[i], &amp;amp;ret);
    }

    return ;
}

int main(int argc, char* argv[])
{
    create_test_threads();

    return 0;
}&lt;/pre&gt;
&lt;p&gt;分析：由于新线程和当前线程是并发的，谁先谁后是无法预测的。i在不断变化，所以新线程拿到的参数值是无法预知的，自然打印出的字符串也是随机的。&lt;/p&gt;
&lt;p&gt;o 虚假并发。&lt;/p&gt;
&lt;pre&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;pthread.h&amp;gt;
#include &amp;lt;assert.h&amp;gt;

void* start_routine(void* param)
{

    int index = *(int*)param;

    printf(&quot;%s:%d\n&quot;, __func__, index);

    return NULL;
}

#define THREADS_NR 10

void create_test_threads()
{
    int i = 0;
    void* ret = NULL;

    pthread_t ids[THREADS_NR] = {0};

    for(i = 0; i &amp;lt; THREADS_NR; i++)
    {
        pthread_create(ids + i, NULL, start_routine, &amp;amp;i);
        pthread_join(ids[i], &amp;amp;ret);
    }

    return ;
}

int main(int argc, char* argv[])
{
    create_test_threads();

    return 0;
}&lt;/pre&gt;
&lt;p&gt;分析：因为pthread_join会阻塞直到线程退出，所以这些线程实际上是串行执行的，一个退出了，才创建下一个。当年一个同事写了一个多线程的测试程序，就是这样写的，结果没有测试出一个潜伏的问题，直到产品运行时，这个问题才暴露出来。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">C++对象的拷贝与赋值操作</title>
		<link href="http://www.limodev.cn/blog/?p=324"/>
		<id>http://www.limodev.cn/blog/?p=324</id>
		<updated>2008-11-12T13:17:43+00:00</updated>
		<content type="html">&lt;p&gt;我发现一些同事在编写一个类时，知道什么时候需要实现拷贝构造函数和赋值操作，但不知道什么时候拷贝构造函数被调用，什么时候赋值操作被调用，甚至把二者混为一谈。&lt;/p&gt;
&lt;p&gt;要弄明白这个问题，最简单的做法莫过于写个测试程序试一下。不过那样做也未必是好办法，实验的结果往往导致以偏概全的结论。不如好好想一下，弄清楚其中的原理，再去写程序去验证也不迟。&lt;/p&gt;
&lt;p&gt;拷贝构造函数，顾名思义，等于拷贝 + 构造。它肩负着创建新对象的任务，同时还要负责把另外一个对象拷贝过来。比如下面的情况就调用拷贝构造函数：&lt;/p&gt;
&lt;p&gt;CString str = strOther;&lt;/p&gt;
&lt;p&gt;赋值操作则只含有拷贝的意思，也就是说对象必须已经存在。比如下面的情况会调用赋值操作。&lt;/p&gt;
&lt;p&gt;str = strOther;&lt;/p&gt;
&lt;p&gt;不过有的对象是隐式的，由编译器产生的代码创建，比如函数以传值的方式传递一个对象时。由于看不见相关代码，所以不太容易明白。不过我们稍微思考一下，就会想到，既然是根据一个存在的对象拷贝生成新的对象，自然是调用拷贝构造函数了。&lt;/p&gt;
&lt;p&gt;两者实现时有什么差别呢？我想有人会说，没有差别。呵，如果没有差别，那么只要实现其中一个就行了，何必要两者都实现呢？不绕圈子了，它们的差别是：&lt;/p&gt;
&lt;p&gt;拷贝构造函数对同一个对象来说只会调用一次，而且是在对象构造时调用。此时对象本身还没有构造，无需要去释放自己的一些资源。而赋值操作可能会调用多次，你在拷贝之前要释放自己的一些资源，否则会造成资源泄露。&lt;/p&gt;
&lt;p&gt;明白了这些道理之后，我们不防写个测试程序来验证一下我们的想法：&lt;/p&gt;
&lt;pre&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
class CString
{  

public:

    CString();
    CString(const char* pszBuffer);
    ~CString();

    CString(const CString&amp;amp; other);
    const CString&amp;amp; operator=(const CString&amp;amp; other);

private:
    char* m_pszBuffer;;

}; 

CString::CString()
{
    printf(&quot;CString::CString\n&quot;);
    m_pszBuffer = NULL;

    return;
}  

CString::CString(const char* pszBuffer)
{
    printf(&quot;CString::CString(const char* pszBuffer)\n&quot;);
    m_pszBuffer = pszBuffer != NULL ? strdup(pszBuffer) : NULL;

    return;
}

CString::~CString()
{
    printf(&quot;%s\n&quot;, __func__);

    if(m_pszBuffer != NULL)
    {
              free(m_pszBuffer);
              m_pszBuffer = NULL;
    }

    return;
}

CString::CString(const CString&amp;amp; other)
{
    if(this == &amp;amp;other)
    {
        return;
    }

    printf(&quot;CString::CString(const CString&amp;amp; other)\n&quot;);
    m_pszBuffer = other.m_pszBuffer != NULL ? strdup(other.m_pszBuffer) : NULL;
}

const CString&amp;amp; CString::operator=(const CString&amp;amp; other)
{
    printf(&quot;const CString&amp;amp; CString::operator=(const CString&amp;amp; other)\n&quot;);

    if(this == &amp;amp;other)
    {
        return *this;
    }

    if(m_pszBuffer != NULL)
    {
        free(m_pszBuffer);
        m_pszBuffer = NULL;
    }
    m_pszBuffer = other.m_pszBuffer != NULL ? strdup(other.m_pszBuffer) : NULL;

    return *this;
}

void test(CString str)
{
    CString str1 = str;

    return;
}

int main(int argc, char* argv[])
{
    CString str;
    CString str1 = &quot;test&quot;;
    CString str2 =  str1;

    str1 = str;

    CString str3 = str3;

    test(str);

    return 0;
}&lt;/pre&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-Write once, run anywhere(WORA)(上)</title>
		<link href="http://www.limodev.cn/blog/?p=308"/>
		<id>http://www.limodev.cn/blog/?p=308</id>
		<updated>2008-11-11T00:02:40+00:00</updated>
		<content type="html">&lt;p&gt;需求简述&lt;/p&gt;
&lt;p&gt;Write Once, Debug Everywhere。据说这是流传于JAVA程序员中间的一句笑话，Sun公司用来形容JAVA的跨平台性的原话是Write once, run anywhere(WORA) 。后者是理想的，前者才是现实。如果我们的双向链表可以到处运行，那就太好了。Write once, run anywhere(WORA)是我们的目标，不过我们先要面对现实，回到双向链表上，请读者思考下列问题：&lt;/p&gt;
&lt;p&gt;1.专用双向链表和通用双向链表各自的特点与适用范围。&lt;br /&gt;
2.如何编写一个通用的双向链表？&lt;/p&gt;
&lt;p&gt;花点时间思考一下，再尝试编写一个通用的双向链表，或许实现得不是那么完美，但是我们迈出了走向通用性的第一步。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">系统程序员成长计划-Write once, run anywhere(WORA)(下)</title>
		<link href="http://www.limodev.cn/blog/?p=319"/>
		<id>http://www.limodev.cn/blog/?p=319</id>
		<updated>2008-11-10T23:59:45+00:00</updated>
		<content type="html">&lt;p&gt;&lt;strong&gt;1.专用链表和通用链表各自的特点与适用范围。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;专用链表在这里是指它的实现和调用耦合在一起，只能被一个调用者使用，而不能单独在其它地方被重用。通用链表则相反，它具有通用性，可以在多处被重复使用。尽管通用链表相对专用链表来说有很多优越之处，不过简单的断定通用链表比专用链表好也是不公正的，因为它们都有自己的优点和适用范围：&lt;/p&gt;
&lt;p&gt;专用链表的优点：&lt;/p&gt;
&lt;p&gt;更高性能。专用链表的实现和调用在一起，可以直接访问数据成员，省去了包装函数带来的性能开销，可以提高时间性能。专用链表无需实现完整的接口，只要满足自己的需要就行了，生成的代码更小，因此可以提高空间性能。&lt;/p&gt;
&lt;p&gt;更少依赖。自己实现不用依赖于别人。有时候你要写一个规模不大的跨平台程序，比如想在展讯手机平台和MTK手机平台上运行，虽然有现存的库可用，但你又不想把整个库移植过去，那么实现一个专用链表是不错的选择。&lt;/p&gt;
&lt;p&gt;实现简单。实现专用链表时，不需要考虑在各种复杂应用情况下的特殊要求，也不需要提供完整的接口，所以实现起来比通用链表更为简单。&lt;/p&gt;
&lt;p&gt;通用链表的优点(从全局来看)：&lt;/p&gt;
&lt;p&gt;可靠性更高。通用链表的实现要复杂得多，复杂的东西意味着不可靠。但它是可以重复使用的，其存在的问题会随每一次重用而被发现和改正，慢慢的就行成一个可靠的函数库。&lt;/p&gt;
&lt;p&gt;开发效率更高。通用链表的实现要复杂得多，复杂的东西也意味着更高的开发成本。同样因为它是可以重复使用的，开发成本会随每一次重用而降低，从整个项目来看，会大大提高开发效率。&lt;/p&gt;
&lt;p&gt;考虑到链表是最常用的数据结构之一，很多地方都会用到它，实现通用的链表会更有价值。接下来我们要实现一个通用的链表，不过请大家记住，实现通用的链表并不是我们的目标，而是我们学习软件设计方法的手段。前面我许诺过要以简单的数据结构讲述复杂的软件设计方法，链表就是其中的载体之一。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.如何编写一个通用的链表？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;编写通用链表是一项复杂的任务，不可能在这一节中把它阐述清楚，这里我们先考虑三个问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;存值还是存指针&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通用链表首先是要做能够存放任何数据类型的数据，新手常见的做法是定义一个抽象数据类型，需要什么存放什么就定义成什么。如：&lt;/p&gt;
&lt;pre&gt;
typedef int Type;
typedef struct _DListNode
{
    struct _DListNode* prev;
    struct _DListNode* next;
    Type data;
}DListNode;
&lt;/pre&gt;
&lt;p&gt;这样的链表算不上是通用的，因为你存放整数时编译一次，存放字符串时，重义Type再编译一次，存放其它类型同样要重复这个过程。麻烦不说，关键是没有办法同时使用多个数据类型。我们要找到一种同时可以表示不同数据类型的类型才行，有人说可以用union，但是数据类型是无穷无尽的，不可能在union中表示它们的全部。&lt;/p&gt;
&lt;p&gt;可行的办法有两种：&lt;/p&gt;
&lt;p&gt;存值：&lt;/p&gt;
&lt;pre&gt;
typedef struct _DListNode
{
    struct _DListNode* prev;
    struct _DListNode* next;
    void*  data;
    size_t length;
}DListNode;
&lt;/pre&gt;
&lt;p&gt;存入时拷贝一份数据，保存数据的指针和长度。考虑到拷贝数据会带来性能开销，不合符C语言的风格，而且C语言中没有构造函数，实现深拷贝比较麻烦，所以在C语言中以这种方式实现的链表很少见。&lt;/p&gt;
&lt;p&gt;存指针：&lt;/p&gt;
&lt;pre&gt;
typedef struct _DListNode
{
    struct _DListNode* prev;
    struct _DListNode* next;
    void*  data;
}DListNode;
&lt;/pre&gt;
&lt;p&gt;只是保存指向对象的指针，存取效率高，是C语言中常见的做法。在存放整数时，可以把void*强制转换成整数使用，以避免内存分配(在现实中，90%以上的情况，链表都是存放结构的)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;让C++可以调用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这不是一个重要的话题，只是顺便提一下。C++中允许同名函数存在，所以编译器会对函数名重新编码。C++代码包含C语言的头文件时，重新编码名字与C语言库中的原函数名不一致，结果造成找不到函数的情况。为了让C语言实现的函数在C++中可以调用，需要在头文件中加点东西才行：&lt;/p&gt;
&lt;pre&gt;
#ifdef __cplusplus
extern &quot;C&quot; {
#endif
…
#ifdef __cplusplus
}
#endif
&lt;/pre&gt;
&lt;p&gt;它表示如果在C++中调用这里的函数，编译器不能对函数名进行重新编码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;完整的接口&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;作为一个通用的链表，接口要比较完整才行，否则无法满足各种情况的需要(提供完整的接口并不违背最小接口原则)。实现具有完整接口的链表不是件容易的事，读者先实现插入删除等基本操作就行了，后面我们会慢慢扩展它的功能。&lt;/p&gt;
&lt;p&gt;(为了避免读起来拗口，本文把双向链表简写成链表了，希望读者不要介意)&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">用NETLINK实现磁盘满通知</title>
		<link href="http://www.limodev.cn/blog/?p=300"/>
		<id>http://www.limodev.cn/blog/?p=300</id>
		<updated>2008-11-06T23:35:04+00:00</updated>
		<content type="html">&lt;p&gt;手机内置FLASH容量有限，在磁盘空间不足的情况下，应该提醒用户进行磁盘清理。这个处理在哪里做比较好呢？每次写入数据时由调用者检测显然是不合理的，因为处理的太多了，何况修改SQLITE等第三方程序也是不明智的，那样会给升级版本带来麻烦。比较好的办法是在文件系统中做处理，最近同事修改了yaffs2支持磁盘满通知功能。做法如下：&lt;/p&gt;
&lt;pre&gt;在yaffs_fs.c中：
&lt;span&gt;#include &lt;/span&gt;&lt;span&gt;&amp;lt;net/sock.h&amp;gt;&lt;/span&gt;
&lt;span&gt;#include &lt;/span&gt;&lt;span&gt;&amp;lt;linux/netlink.h&amp;gt;&lt;/span&gt;

&lt;span&gt;#define DISK_FULL_MSG_SIZE  &lt;/span&gt;&lt;span&gt;128&lt;/span&gt;
&lt;span&gt;#define NETLINK_DISK_FULL   &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;

&lt;span&gt;&lt;strong&gt;static&lt;/strong&gt;&lt;/span&gt; &lt;span&gt;&lt;strong&gt;struct&lt;/strong&gt;&lt;/span&gt; sock * yaffs_sock;

在init_yaffs_fs中：
    &lt;span&gt;&lt;strong&gt;if&lt;/strong&gt;&lt;/span&gt;((yaffs_sock = netlink_kernel_create(NETLINK_DISK_FULL, &lt;span&gt;1&lt;/span&gt;, &lt;span&gt;NULL&lt;/span&gt;, THIS_MODULE)) == &lt;span&gt;NULL&lt;/span&gt;)
    {
        printk(KERN_INFO&lt;span&gt;&quot;netlink_kernel_create fail.&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;);
    }

在exit_yaffs_fs中：
    &lt;span&gt;&lt;strong&gt;if&lt;/strong&gt;&lt;/span&gt;(yaffs_sock != &lt;span&gt;NULL&lt;/span&gt;)
    {
        sock_release(yaffs_sock);
        yaffs_sock = &lt;span&gt;NULL&lt;/span&gt;;
    }
增加两个函数：

&lt;span&gt;&lt;strong&gt;void&lt;/strong&gt;&lt;/span&gt; yaffs_notify_space_full(&lt;span&gt;&lt;strong&gt;const&lt;/strong&gt;&lt;/span&gt; &lt;span&gt;&lt;strong&gt;char&lt;/strong&gt;&lt;/span&gt;* partition, &lt;span&gt;&lt;strong&gt;const&lt;/strong&gt;&lt;/span&gt; &lt;span&gt;&lt;strong&gt;char&lt;/strong&gt;&lt;/span&gt;* type, &lt;span&gt;&lt;strong&gt;int&lt;/strong&gt;&lt;/span&gt; totalchunk, &lt;span&gt;&lt;strong&gt;int&lt;/strong&gt;&lt;/span&gt; freechunk)
{
    &lt;span&gt;&lt;strong&gt;size_t&lt;/strong&gt;&lt;/span&gt; len = &lt;span&gt;0&lt;/span&gt;;
    &lt;span&gt;&lt;strong&gt;char&lt;/strong&gt;&lt;/span&gt; *scratch = &lt;span&gt;NULL&lt;/span&gt;;
    &lt;span&gt;&lt;strong&gt;struct&lt;/strong&gt;&lt;/span&gt; sk_buff *skb = &lt;span&gt;NULL&lt;/span&gt;;

    totalchunk &amp;gt;&amp;gt;= &lt;span&gt;10&lt;/span&gt;;
    freechunk  &amp;gt;&amp;gt;= &lt;span&gt;10&lt;/span&gt;;

    len = DISK_FULL_MSG_SIZE;
    skb = alloc_skb(len, GFP_KERNEL);
    &lt;span&gt;&lt;strong&gt;if&lt;/strong&gt;&lt;/span&gt; (skb)
    {
        scratch = skb_put(skb, len);
        sprintf(scratch, &lt;span&gt;&quot;diskevent: type=&lt;/span&gt;&lt;span&gt;%s&lt;/span&gt;&lt;span&gt; total=&lt;/span&gt;&lt;span&gt;%d&lt;/span&gt;&lt;span&gt;KB free=&lt;/span&gt;&lt;span&gt;%d&lt;/span&gt;&lt;span&gt;KB partition=&lt;/span&gt;&lt;span&gt;%s&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;,
            type, totalchunk, freechunk, partition);

        NETLINK_CB(skb).dst_group = &lt;span&gt;1&lt;/span&gt;;
        netlink_broadcast(yaffs_sock, skb, &lt;span&gt;0&lt;/span&gt;, &lt;span&gt;1&lt;/span&gt;, GFP_KERNEL);
    }

    &lt;span&gt;&lt;strong&gt;return&lt;/strong&gt;&lt;/span&gt;;
}

&lt;span&gt;&lt;strong&gt;void&lt;/strong&gt;&lt;/span&gt; yaffs_notify_app_if_space_full(yaffs_Device * dev)
{
    &lt;span&gt;&lt;strong&gt;if&lt;/strong&gt;&lt;/span&gt; (yaffs_sock)
    {
        &lt;span&gt;&lt;strong&gt;const&lt;/strong&gt;&lt;/span&gt; &lt;span&gt;&lt;strong&gt;char&lt;/strong&gt;&lt;/span&gt;* type = &lt;span&gt;NULL&lt;/span&gt;;
        &lt;span&gt;&lt;strong&gt;int&lt;/strong&gt;&lt;/span&gt; totalchunk = (dev-&amp;gt;endBlock - dev-&amp;gt;startBlock + &lt;span&gt;1&lt;/span&gt;) * dev-&amp;gt;nChunksPerBlock * dev-&amp;gt;nDataBytesPerChunk;
        &lt;span&gt;&lt;strong&gt;int&lt;/strong&gt;&lt;/span&gt; freechunk =  yaffs_GetNumberOfFreeChunks(dev) * dev-&amp;gt;nDataBytesPerChunk;

        &lt;span&gt;&lt;strong&gt;if&lt;/strong&gt;&lt;/span&gt; (freechunk &amp;lt; totalchunk / &lt;span&gt;100&lt;/span&gt;)
        {
            type = &lt;span&gt;&quot;full&quot;&lt;/span&gt;;
        }
        &lt;span&gt;&lt;strong&gt;else&lt;/strong&gt;&lt;/span&gt; &lt;span&gt;&lt;strong&gt;if&lt;/strong&gt;&lt;/span&gt; (freechunk &amp;lt; totalchunk * &lt;span&gt;5&lt;/span&gt; / &lt;span&gt;100&lt;/span&gt;)
        {
            type = &lt;span&gt;&quot;low&quot;&lt;/span&gt;;
        }

        &lt;span&gt;&lt;strong&gt;if&lt;/strong&gt;&lt;/span&gt;(type != &lt;span&gt;NULL&lt;/span&gt;)
        {
            yaffs_notify_space_full(dev-&amp;gt;name, type, totalchunk, freechunk);
        }
    }

    &lt;span&gt;&lt;strong&gt;return&lt;/strong&gt;&lt;/span&gt;;
}&lt;/pre&gt;
&lt;p&gt;在yaffs_AllocateChunk中：&lt;br /&gt;
yaffs_notify_app_if_space_full(dev);&lt;/p&gt;
&lt;p&gt;NETLINK是Linux提供的一种用于内核与用户空间进程通信的方式，使用简单，传输效率高，hotplug事件也是通过这种方式通知udev的。&lt;/p&gt;
&lt;p&gt;用户空间监听磁盘满事件的实现很简单，我提供了一个示例，有兴趣的朋友可以到&lt;a href=&quot;http://www.limodev.cn/bbs/download/file.php?id=2&quot;&gt;&lt;strong&gt;这里&lt;/strong&gt;&lt;/a&gt;下载。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry xml:lang="en">
		<title type="html">企业应用架构模式读书笔记(2)</title>
		<link href="http://www.limodev.cn/blog/?p=297"/>
		<id>http://www.limodev.cn/blog/?p=297</id>
		<updated>2008-11-06T11:11:05+00:00</updated>
		<content type="html">&lt;p&gt;把SQL访问从领域逻辑中分离出来：行数据入口和表数据入口。行数据入口对象对应数据库中表的一行数据。表数据入口对象和记录集差不多，对应表中的多行数据。&lt;/p&gt;
&lt;p&gt;把相关领域逻辑移入行数据入口中，行数据入口不但拥有数据，而且拥有行为，这就行成了活动记录。&lt;/p&gt;
&lt;p&gt;活动记录适用于领域逻辑复杂度适中的情况，如果领域逻辑复杂度较大，需要按领域模型来组织系统结构，此时，数据对象与数据库中的表不再是一一对应的关系，这时一般采用数据映射器。&lt;/p&gt;
&lt;p&gt;视图相当于一张虚表，和真正表之间差别在于只能读取不能修改。&lt;/p&gt;
&lt;p&gt;管理内存中的数据对象和数据库中的数据，保持它们的一致性，是一件复杂的事。可以采用工作单元，让工作单元跟踪所有从数据库读取的对象以及所有以任何形式修改过的对象，并由它把数据对象提交到数据库中。&lt;/p&gt;
&lt;p&gt;把同一个数据对象加载两次，也即有两个数据对象对应于数据库中同一行数据，这可能产生非常糟糕的后果。可以采用标识映射来保持数据对象的唯一。标识映射一个附带的好处是能提高性能。&lt;/p&gt;
&lt;p&gt;如果使用领域模型，对象之间的关系可能比较复杂，产生裙带关系，加载一个对象会附带加载一大堆对象，这时可以采用延迟加载。这和代理模式差不多，先生成一个代理，真正用到该对象时才去加载。&lt;/p&gt;
&lt;p&gt;对象之间的关联可以通过外键映射来实现。&lt;/p&gt;
&lt;p&gt;一个人有多种技能，并且要知道多少人具备某一项技能，这是一种多对多的关系，关系数据库不能直接解决这类问题，而是通过关联表映射来实现。&lt;/p&gt;
&lt;p&gt;通过序列化LOB可以节省大量数据库开销。&lt;/p&gt;
&lt;p&gt;继承关系与数据库的映射方法有：&lt;/p&gt;
&lt;p&gt;1.单表继承：让所有类都映射到同一个表中，这张表拥有所有父类/子类全部的域。因为数据库会压缩没有用到的域，不会浪费多少空间，而且使用更简单。&lt;br /&gt;
2.具体表继承：每个子类对应一张独立的表。&lt;br /&gt;
3.类表继承：每个类都对应一张独立的表。&lt;/p&gt;
&lt;p&gt;同一个数据对象对应多个数据源时，可以建立多个映射层。&lt;/p&gt;
&lt;p&gt;元数据映射：用元数据文件来描述映射关系，通过代码产生或者反射编程来完成映射，这可以大大减少重复的代码。&lt;/p&gt;
&lt;p&gt;使用数据连接池，可以避免频繁建立/断开连接的性能开销。&lt;/p&gt;
&lt;p&gt;可以把连接的建立/断开与事务关联起来，以避免连接泄露。&lt;/p&gt;</content>
		<author>
			<name>admin</name>
			<uri>http://www.limodev.cn/blog</uri>
		</author>
		<source>
			<title type="html">The linux mobile development</title>
			<subtitle type="html">致力于基于linux的嵌入式系统的学习和研究，包括内核、驱动、GUI、MMI、软件设计方法和软件优化等方面。</subtitle>
			<link rel="self" href="http://www.limodev.cn/blog/?feed=atom"/>
			<id>http://www.limodev.cn/blog/?feed=atom</id>
			<updated>2008-12-19T02:48:34+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">GNOME 2.24 发布！</title>
		<link href="http://www.gnome-cn.org/newsitems/gnome-2.24-released"/>
		<id>http://www.gnome-cn.org/newsitems/gnome-2.24-released</id>
		<updated>2008-09-25T05:41:45+00:00</updated>
		<content type="html"></content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">Wine 1.0正式版下载</title>
		<link href="http://www.gnome-cn.org/newsitems/wine-1.0-released"/>
		<id>http://www.gnome-cn.org/newsitems/wine-1.0-released</id>
		<updated>2008-06-18T04:00:19+00:00</updated>
		<content type="html">Wine 1.0正式版下载</content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">Nautilus开始支持标签了</title>
		<link href="http://www.gnome-cn.org/Members/nautilus"/>
		<id>http://www.gnome-cn.org/Members/nautilus</id>
		<updated>2008-05-30T01:03:17+00:00</updated>
		<content type="html"></content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">Transmission 1.10 发布</title>
		<link href="http://www.gnome-cn.org/Members/xinliGG/Transmission"/>
		<id>http://www.gnome-cn.org/Members/xinliGG/Transmission</id>
		<updated>2008-03-29T17:31:43+00:00</updated>
		<content type="html"></content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">Banshee 1.0 Alpha 2：加入视频播放和管理功能</title>
		<link href="http://www.gnome-cn.org/Members/xinliGG/Banshee1.0Alpha2"/>
		<id>http://www.gnome-cn.org/Members/xinliGG/Banshee1.0Alpha2</id>
		<updated>2008-03-27T06:37:47+00:00</updated>
		<content type="html"></content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">Anjuta 2.4.0 发布</title>
		<link href="http://www.gnome-cn.org/Members/xinliGG/Anjuta2.4"/>
		<id>http://www.gnome-cn.org/Members/xinliGG/Anjuta2.4</id>
		<updated>2008-03-27T06:23:08+00:00</updated>
		<content type="html"></content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">gambas 2.4发布</title>
		<link href="http://www.gnome-cn.org/Members/xinliGG/gambas"/>
		<id>http://www.gnome-cn.org/Members/xinliGG/gambas</id>
		<updated>2008-03-21T03:15:01+00:00</updated>
		<content type="html">gambas2.4发布了</content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">GNOME-CN 寻找新的赞助商</title>
		<link href="http://www.gnome-cn.org/newsitems/gcn-look-for-new-sponsor"/>
		<id>http://www.gnome-cn.org/newsitems/gcn-look-for-new-sponsor</id>
		<updated>2008-03-17T01:58:01+00:00</updated>
		<content type="html">GNOME-CN 需要新的赞助商来提供服务器及宽带资源。</content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">GTK+ Hackfest 2008顺利闭幕</title>
		<link href="http://www.gnome-cn.org/Members/xinliGG/gtkhackfest2008"/>
		<id>http://www.gnome-cn.org/Members/xinliGG/gtkhackfest2008</id>
		<updated>2008-03-16T19:28:40+00:00</updated>
		<content type="html">在德国柏林举行的GTK+ Hackfest 2008已经顺利闭幕。</content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">MonoDevelop 1.0 发布</title>
		<link href="http://www.gnome-cn.org/Members/xinliGG/monodevelop1.0"/>
		<id>http://www.gnome-cn.org/Members/xinliGG/monodevelop1.0</id>
		<updated>2008-03-16T12:31:46+00:00</updated>
		<content type="html">MonoDevelop小组自豪的宣布了MonoDevelop 1.0 版本的发布。</content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">15年磨一剑，wine 1.0发布日期已定！</title>
		<link href="http://www.gnome-cn.org/Members/xinliGG/wine1.0"/>
		<id>http://www.gnome-cn.org/Members/xinliGG/wine1.0</id>
		<updated>2008-03-14T22:46:41+00:00</updated>
		<content type="html">本来和gnome没什么关系，但这也是一个重要的时刻了，毕竟15年做一件事不容易</content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

	<entry>
		<title type="html">GNOME2.22发布！</title>
		<link href="http://www.gnome-cn.org/Members/xinliGG/gome2.22released"/>
		<id>http://www.gnome-cn.org/Members/xinliGG/gome2.22released</id>
		<updated>2008-03-14T03:30:48+00:00</updated>
		<content type="html">GNOME2.22发布了</content>
		<author>
			<name>GNOME-CN-新闻</name>
			<uri>http://www.gnome-cn.org</uri>
		</author>
		<source>
			<title type="html">GNOME-CN 新闻</title>
			<link rel="self" href="http://www.gnome-cn.org/feed/news/RSS"/>
			<id>http://www.gnome-cn.org/feed/news/RSS</id>
			<updated>2008-12-19T02:49:07+00:00</updated>
		</source>
	</entry>

</feed>
