使用 memleak 检查和调试内存泄漏

小型的嵌入式应用中经常会出现许多内存问题,很多情况下难以定位问题出现在哪里。
我在 sourceforge 上找了些检测 C 内存泄漏的工具,感觉比较易用的是 memleak,下面就来简要介绍一下它的使用。

下载得到的 memleak 压缩包大小不到 15 kB,解压后只有两个文件:memleak.c 和 memleak.h。在使用过程中只需要包含头文件 memleak.h 就可以使用 memleak 提供的几个易用而有效的内存检测函数了。

memleak 的原理是利用 C 语言的宏调用来替代原有的函数调用,比如我们在代码中调用 malloc(s),实际是调用了:dbg_malloc(s),这个宏定义在 memleak.h 中给出:
#define malloc(s) (FILE_LINE, dbg_malloc(s))

memleak 维护了一个链表,在这个链表中保存着程序中对内存函数调用的记录,这些函数包括:malloc、calloc、realloc、free。每次调用这些函数时,就会更新这个链表。
有了这个表,我们就可以在适当的位置调用 memleak 提供的函数,显示一些重要的信息,包括 malloc、calloc、realloc、free调用的次数,申请及分配的内存数,调用的文件和位置等等,信息非常详细。有了这些功能,我们就很容易定位内存使用的错误源。

由于 memleak 在某些交叉编译器下不能正常编译通过,这里我将 memleak.c 中的结构体 struct head 修改如下:

struct head
{
  struct head *addr;
  size_t size;
  char *file;
  unsigned line;
  /* two addresses took the same space as an address and an integer on many archs => usable */
  union lf {
    struct { struct head*prev, *next; } list;
    struct { char *file; unsigned line; } free;
  } u;
};

memleak.c 文件中其它调用到 head 中共用体 u 的地方也要做相应的修改。

修改后的文件可以点击这里下载

memleak 提供了以下几个函数接口:

extern void dbg_init(int history_length);
extern int dbg_check_addr(char *msg, void *ptr, int opt);
extern void dbg_mem_stat(void);
extern void dbg_zero_stat(void);
extern void dbg_abort(char *msg);
extern void dbg_heap_dump(char *keyword);
extern void dbg_history_dump(char *keyword);
extern void dbg_catch_sigsegv(void);

详细的介绍请查看 memleak.c 头部的注释或查看源代码理解。

下面举个简单的例子:

#include <stdio.h>
#include <stdlib.h>
#include "memleak.h"

int main(void)
{
    char * s, * t;

    dbg_init(10);
    s = (char *)malloc(100);    // 申请 100 bytes
    t = (char *)malloc(11);     // 再申请 11 bytes
    free(s);                    // 释放 100 bytes
    s = (char *)malloc(80);     // 重新申请 80 bytes
    dbg_heap_dump("");          // 显示调用栈
    dbg_mem_stat();             // 显示调用统计
    free(t);                    // 释放 11 bytes
    free(s);                    // 释放 80 bytes
    dbg_mem_stat();             // 再次显示调用统计

        return 0;
}

编辑后保存为 test.c,与 memleak.c 和 memleak.h 放于同一目录下。

然后编写一 Makefile:

CC = gcc

EXEC = test
CSRC = test.c memleak.c
OBJS = $(patsubst %.c,%.o, $(CSRC))

all: $(EXEC)

$(EXEC): $(OBJS)
        $(CC) $(OBJS) $(LDFLAGS) $(LDLIBS) -o $@

$(OBJS): %.o : %.c
        $(CC) $(CFLAGS) -c $< -o $@

clean:
        -rm -f $(EXEC) *.elf *.gdb *.o

也保存于同一目录,在该目录下 make 编译,执行 ./test 后输出如下:

***** test.c:14: heap dump start
(alloc: test.c:11 size: 11)
(alloc: test.c:13 size: 80)
***** test.c:14: heap dump end
test.c:15: m: 3, c: 0, r: 0, f: 1, mem: 91
test.c:18: m: 3, c: 0, r: 0, f: 3, mem: 0

怎么样,很简单吧?

memleak 中还有一个函数 dbg_catch_sigsegv(void),可以绑定系统出现 SIGSEGV 信号时的处理函数,我们可以通过修改 memleak.c 中的 sigsegv_handler,自定义这个 SIGSEGV 信号处理函数。不知道 uClinux 下的 SIGSEGV 信号是否也存在,有的话调试一些内存问题就更容易了。

最后从网上摘来一段 SIGSEGV 的介绍:

SIGSEGV --- Segment Fault. The possible cases of your encountering this error are:
1.buffer overflow --- usually caused by a pointer reference out of range.
2.stack overflow --- please keep in mind that the default stack size is 8192K.
3.illegal file access --- file operations are forbidden on our judge system.
-- EOF --

uClibc 中的 malloc 和 free

  uClibc 是一个小型的 C 库,应用于嵌入式 Linux 系统开发。它基本实现了 glibc 的功能,几乎所有 glibc 支持的应用程序都能在 uClibc 上运行,这使得应用程序的移植变得相当简单,只需要使用 uClibc 库重新编译源代码就可以了。目前 uClibc 主要运行在不带 MMU 的平台下,支持 alpha, ARM, i386, i960, h8300, m68k, mips/mipsel, PowerPC, SH, SPARC 等等。
  在国内,Linux 的小型应用一般采用 ARM7 作为处理器(见得较多的是Samsung S3C44B0X),加上开源的 uClinux 作为操作系统,构成一个价格上相对较低的嵌入式开发平台。
  在 uClinux 上最经常使用的 C 库,便是 uClibc,本篇主要是对 uClibc 中的 malloc 和 free 做简要分析,希望能起到一个抛砖引玉的作用。目前我使用的开发平台为 S3C44B0X + uClinux (Kernel Version 2.4.20),uClibc 的版本为 0.9.19,版本虽然旧了些,但不影响理解 malloc 的工作机制,而且可能更有利(通常版本越高,代码越复杂)。
  顺便在此附上该版本 malloc 部分的代码,它位于 uClibc/libc/stdlib/ 目录下。 点击下载文件

Hily Jiang
Email&Gtalk: hilyjiang at Gmail
Blog: http://hily.me/blog/

一、头文件中的声明
malloc 和 free 在头文件 stdlib.h 中声明:

/* Allocate SIZE bytes of memory.  */
extern void *malloc (size_t __size) __THROW __attribute_malloc__;
/* Free a block allocated by `malloc', `realloc' or `calloc'.  */
extern void free (void *__ptr) __THROW;

malloc 返回一个指定大小为 __size 的指针。
free 释放指针 __ptr 指向的内存空间。 

二、基本机制
stdlib 中的 malloc 模块维护了一个空闲区域的链表,这个链表可以说是这个模块最为核心的部分。
当调用 malloc 申请空间时,先检查该链表中是否有满足条件的空闲区域节点,如果没有,则向内核申请内存空间,放入这个链表中,然后再重新在链表中查找一次满足条件的空闲区域节点。
当调用 free 释放空间时,把释放的空间放入空闲区域中。和 malloc 相对应地,如果空闲的空间大于某个阈值时,调用 free 时会把不需要的空间再还给内核,以节约内存。

三、两个重要的结构
前面说到这个模块中最核心的空闲区域链表,它的节点结构为(heap.h中):

struct heap_free_area
{
	size_t size;
	struct heap_free_area *next, *prev;
};

size 表示该空闲区域的大小,这个空闲区域的实际地址并没有用指针详细地指明,因为它就位于当前 heap_free_area 节点的前面,如下图所示:

+-------------------------------+--------------------+
|                               |   heap_free_area   |
+-------------------------------+--------------------+
\___________ 空闲空间 ___________/\___ 空闲空间信息 ___/

从图上我们可以看出,实际可用的空闲空间大小为 size – sizeof(struct heap_free_area)。
指针 next, prev 分别指向下一个和上一个空间区域,所有的空闲区域就是通过许许多多这样的节点链起来的,很显然,这样组成的是一个双向链表。
双向链表的头节点由另外一个结构 heap 索引,heap 的定义为(heap.h中):

struct heap
{
  /* A list of memory in the heap available for allocation.  */
  struct heap_free_area *free_areas;

#ifdef HEAP_USE_LOCKING
  /* A lock that can be used by callers to control access to the heap.
     The heap code _does not_ use this lock, it's merely here for the
     convenience of users!  */
  pthread_mutex_t lock;
#endif
};

heap 结构中只有两个成员,free_areas 指向空间区域链表头节点,lock 在多线程环境中作线程锁使用。 

四、模块初始化
malloc 模块的初始化,其实就是对以上两个结构的初始化,在 malloc.c 中进行:

/* The malloc heap.  We provide a bit of initial static space so that
   programs can do a little mallocing without mmaping in more space.  */
HEAP_DECLARE_STATIC_FREE_AREA (initial_fa, 256);
struct heap __malloc_heap = HEAP_INIT_WITH_FA (initial_fa);

宏 HEAP_DECLARE_STATIC_FREE_AREA 在 heap.h 中定义:

#define HEAP_DECLARE_STATIC_FREE_AREA(name, size)			\
  static struct								\
  {									\
    char space[(size) - sizeof (struct heap_free_area)];		\
    struct heap_free_area _fa;						\
  } name = { "", { (size), 0, 0 } }

HEAP_DECLARE_STATIC_FREE_AREA 在这里初始化了一个大小为 256 字节的静态空闲区域。
接下来创建一个 heap,它的 free_areas 成员指向上面这个刚初始化的空间中的 _fa,同时初始化线程锁:

# define HEAP_INIT_WITH_FA(fa)	{ &fa._fa, PTHREAD_MUTEX_INITIALIZER }

至此初始化完毕,生成了一个 heap 结构,它的 free_areas 成员指向一个静态空间区域。 

五、malloc 解读
malloc 函数在 malloc.c 中定义,它实际上是调用 malloc_from_heap 从空闲区域中申请空间。
为减短篇幅,下面采用源代码注释的方法进行解读,同样删除了一些对于理解工作原理不太重要的代码:

static void *
malloc_from_heap (size_t size, struct heap *heap)
{
  void *mem;

  /* 一个 malloc 块的结构如下:

     +--------+---------+-------------------+
     | SIZE   |(unused) | allocation  ...   |
     +--------+---------+-------------------+
     ^ BASE             ^ ADDR
     ^ ADDR - MALLOC_ALIGN

     申请成功后返回的地址是 ADDR
     SIZE 表示块的大小,包括前面的那部分,也就是 MALLOC_HEADER_SIZE
  */
  size += MALLOC_HEADER_SIZE;

  /* 申请线程锁,避免其它线程操作 heap 引起冲突 */
  __heap_lock (heap);

  /* 从 heap 中申请大小为 size 的空间 */
  mem = __heap_alloc (heap, &size);

  /* 释放线程锁 */
  __heap_unlock (heap);

  if (! mem)
    /* 如果空间申请失败,则从系统中申请空间放入 heap 后再重新申请  */
    {
      /* 从系统申请空间时,最小的单元为 MALLOC_HEAP_EXTEND_SIZE,大于 MALLOC_HEAP_EXTEND_SIZE 时,
        则根据需要的空间大小向上申请 MALLOC_HEAP_EXTEND_SIZE 的整数倍大小的空间 */
      void *block;
      size_t block_size
	= (size < MALLOC_HEAP_EXTEND_SIZE
	   ? MALLOC_HEAP_EXTEND_SIZE
	   : MALLOC_ROUND_UP_TO_PAGE_SIZE (size));

      /* 向内核申请空间 */
      block = mmap (0, block_size, PROT_READ | PROT_WRITE,
		    MAP_SHARED | MAP_ANONYMOUS, 0, 0);

      /* 申请成功,将空间放入 heap 的空闲区域 */
      if (block != (void *)-1)
	{
	  /* 申请线程锁 */
	  __heap_lock (heap);

	  /* 将空间放入 heap 的空闲区域 */
	  __heap_free (heap, block, block_size);

	  /* 重新申请一次空间 */
	  mem = __heap_alloc (heap, &size);

	  /* 释放线程锁 */
	  __heap_unlock (heap);

	}
    }

  if (mem)
    {
    /* 申请成功,将 mem 指向 ADDR 处

     +--------+---------+-------------------+
     | SIZE   |(unused) | allocation  ...   |
     +--------+---------+-------------------+
     ^ BASE             ^ ADDR
                        ^ mem
    */
      mem = MALLOC_SETUP (mem, size);
    }
  else
    MALLOC_DEBUG (-1, "malloc: returning 0");

  return mem;
}

malloc_from_heap 中调用了两个重要的函数,以下分别作解读。
__heap_alloc函数,位于 heap_alloc.c,这个函数的作用就是从空闲区域链表中找到满足条件的节点,同时它会修改参数中的 size,返回实际分配的空间大小:

void *
__heap_alloc (struct heap *heap, size_t *size)
{
  struct heap_free_area *fa;
  size_t _size = *size;
  void *mem = 0;

  /* 根据 HEAP_GRANULARITY 大小向上取整,在 heap.h 中定义 */
  _size = HEAP_ADJUST_SIZE (_size);

  /* 在空闲区域链表中查找大小大于等于 _SIZE 的节点  */
  for (fa = heap->free_areas; fa; fa = fa->next)
    if (fa->size >= _size)
      {
	/* 找到满足条件的节点 */
	mem = HEAP_FREE_AREA_START (fa);
	*size = __heap_free_area_alloc (heap, fa, _size);
	break;
      }

  return mem;
}

在链表中如果找到满足条件的节点,则通过 __heap_free_area_alloc 分配空间(heap.h中):

extern inline size_t
__heap_free_area_alloc (struct heap *heap,
			struct heap_free_area *fa, size_t size)
{
  size_t fa_size = fa->size;

  if (fa_size < size + HEAP_MIN_FREE_AREA_SIZE)
    {
      /* 如果在这个空闲区域中剩余的空间不足以维持一个 heap_free_area 节点,
         则把这个区域从链表中移除
	 __heap_delete 只是个简单的链表操作,在 heap.h 中定义
      */
      __heap_delete (heap, fa);

      /* 修改大小,实际申请的空间要大于传入的 size */
      size = fa_size;
    }
  else
    /* 如果这个区域中还有空闲空间,就把 heap_free_area 节点中
       的 size 减小 size就可以了:

       分配前:
	 __________ 空闲空间 __________     __ 空闲空间信息 __
	/                              \ /                  \
	+-------------------------------+--------------------+
	|                               |   heap_free_area   |
	+-------------------------------+--------------------+
	\__________ fa->size __________/

       分配后:
       	 ___ 已分配 __     __ 空闲空间 __   __ 空闲空间信息 __
	/             \ /              \ /                  \
	+-------------------------------+--------------------+
	|              |                |   heap_free_area   |
	+-------------------------------+--------------------+
	\____ size ___/ \__ fa->size __/

    */
    fa->size = fa_size - size;

  return size;
}

如果第一次申请空间不成功,就会向系统申请空间,通过 __heap_free 加入到 heap 的空闲区域链表中。
__heap_free 在 heap_free.c 中被定义,它将指定的内存区域加入链表:

struct heap_free_area *
__heap_free (struct heap *heap, void *mem, size_t size)
{
  struct heap_free_area *fa, *prev_fa;
  void *end = (char *)mem + size;

  /* 空闲区域链表是按照地址从小到大排列的,这个循环是为了找到 mem 应该插入的位置 */
  for (prev_fa = 0, fa = heap->free_areas; fa; prev_fa = fa, fa = fa->next)
    if (HEAP_FREE_AREA_END (fa) >= mem)
      break;

  if (fa && HEAP_FREE_AREA_START (fa) <= end)
    {
      size_t fa_size = fa->size + size;

      if (HEAP_FREE_AREA_START (fa) == end)
        /* mem 在 fa 之前
	   如果 fa 和 mem 是连续的,那么将 mem 空间并入 fa 节点管理

	   +---------------+--------------+---------------+
	   |       |prev_fa|      mem     |       |   fa  |
	   +---------------+--------------+---------------+
                          ^______________________________^ 

           prev_fa 与 fa 的链接关系不变,只要更改 fa 中的 size 就可以了
	*/
	{
	  /* 如果 fa 前一个节点和 mem 是连续的,那么将 fa 前一个节点的空间
	     也并入 fa 节点管理

	   +---------------+---------------+--------------+---------------+
	   |       |pre2_fa|       |prev_fa|      mem     |       |   fa  |
	   +---------------+---------------+--------------+---------------+
                          ^______________________________________________^

	    将 prev_fa 从链表中移出,同时修改 fa 中的 size
          */
	  if (prev_fa && mem == HEAP_FREE_AREA_END (prev_fa))
	    {
	      fa_size += prev_fa->size;
	      __heap_link_free_area_after (heap, fa, prev_fa->prev);
	    }
	}
      else
	/* mem 在 fa 之后 */
	{
	  struct heap_free_area *next_fa = fa->next;

	  /* 如果 mem 与 next_fa 是连续的,将 mem 并入 next_fa 节点管理

 	   +---------------+--------------+--------------+---------------+
	   |       |prev_fa|      |   fa  |      mem     |       |next_fa|
	   +---------------+--------------+--------------+---------------+
                          ^_____________________________________________^ 

	   将 fa 从链表中移出,同时修改 next_fa 中的 size
	  */
	  if (next_fa && end == HEAP_FREE_AREA_START (next_fa))
	    {
	      fa_size += next_fa->size;
	      __heap_link_free_area_after (heap, next_fa, prev_fa);
	      fa = next_fa;
	    }
	  else
	    {
	    /* 如果 mem 与 next_fa 不连续,将 fa 结点移到 mem 尾部

 	   +---------------+--------------+--------------+---------------+
	   |       |prev_fa|      |   fa  | mem | unused |       |next_fa|
	   +---------------+--------------+--------------+---------------+
                          ^___________________^^________________________^

	       需要重新链接 fa 与 prev_fa 和 next_fa 的关系
            */
	      fa = (struct heap_free_area *)((char *)fa + size);
	      __heap_link_free_area (heap, fa, prev_fa, next_fa);
	    }
	}

      fa->size = fa_size;
    }
  else
    /* 如果找不到 fa,或 mem 在 fa 之前,那么可以简单地
       把 mem 插入 prev_fa 和 fa之间 */
    fa = __heap_add_free_area (heap, mem, size, prev_fa, fa);

  return fa;
}

 

六、free 解读
解读完 malloc 的代码,再解读 free 应该是件容易的事了,因为 free 中也用到了上面解读过程中的一些函数。
来看代码吧,free 在 free.c 中定义,它实际调用的是 free_to_heap:

static void
free_to_heap (void *mem, struct heap *heap)
{
  size_t size;
  struct heap_free_area *fa;

  /* 检查 mem 是否合法 */
  if (! mem)
    return;

  /* 获取 mem 指向的 malloc 块的的实际大小和起始地址 */
  size = MALLOC_SIZE (mem);
  mem = MALLOC_BASE (mem);

  /* 申请线程锁 */
  __heap_lock (heap);

  /* 把 mem 指向的空间放到 heap 中,__heap_free 在 malloc 中已解读过了  */
  fa = __heap_free (heap, mem, size);

  /* 检查空闲区域大小是否超过了阈值 MALLOC_UNMAP_THRESHOLD */
  if (HEAP_FREE_AREA_SIZE (fa) < MALLOC_UNMAP_THRESHOLD)
    /* 没有超过,只需要释放线程锁就可以了 */
    __heap_unlock (heap);
  else
    /* 超过了,则把多余的空间释放,交还给系统内核管理  */
    {
      unsigned long start = (unsigned long)HEAP_FREE_AREA_START (fa);
      unsigned long end = (unsigned long)HEAP_FREE_AREA_END (fa);

      /* 从空闲链表中删除该空闲区域 */
      __heap_delete (heap, fa);

      if (__heap_is_empty (heap))
	/* 如果空闲链表为空 */
	{
	  /* 保留 MALLOC_MIN_SIZE 大小的区域给 heap,作为空闲区域 */
	  __heap_free (heap, (void *)start, MALLOC_MIN_SIZE);
	  start += MALLOC_MIN_SIZE;
	}

      /* 使要释放的空间的起止地址按页对齐,开始地址向上对齐,结束地址向下对齐 */
      unmap_start = MALLOC_ROUND_UP_TO_PAGE_SIZE (start);
      unmap_end = MALLOC_ROUND_DOWN_TO_PAGE_SIZE (end);

      /* 将对齐后余下的空间再放回 heap 中 */
      if (unmap_start > start)
	{
	  if (unmap_start - start < HEAP_MIN_FREE_AREA_SIZE)
	    unmap_start += MALLOC_PAGE_SIZE;
	  __heap_free (heap, (void *)start, unmap_start - start);
	}
      if (end > unmap_end)
	{
	  if (end - unmap_end < HEAP_MIN_FREE_AREA_SIZE)
	    unmap_end -= MALLOC_PAGE_SIZE;
	  __heap_free (heap, (void *)unmap_end, end - unmap_end);
	}

      /* 系统调用前需要先释放锁 */
      __heap_unlock (heap);

      if (unmap_end > unmap_start)
	/* 最后,调用 munmap 释放内存 */
	munmap ((void *)unmap_start, unmap_end - unmap_start);

    }
}

在这个过程中值得提到的有两点,似乎是 uClibc 不完善的地方。
1. 在检查到空闲链表为空时,为何不把模块初始化时产生的静态空闲域 initial_fa._fa 赋给 heap 中的 free_areas?而偏偏要到将要释放的空间里割出一块呢?
2. 源代码在将对齐后余下的空间放回 heap 中前有一段注释:

      /* We have to be careful that any left-over bits are large enough to
	 return.  Note that we _don't check_ to make sure there's room to
	 grow/shrink the start/end by another page, we just assume that
	 the unmap threshold is high enough so that this is always safe
	 (i.e., it should probably be at least 3 pages).  */

意思是余下的空间应该要足够大,也就是就最小也要有 HEAP_MIN_FREE_AREA_SIZE 这么大,否则上面的对齐就没有意义了。程序中没有去处理这种情况,是因为它假设当阈值设置得足够大时,这种情况发生的机率很小,作者认为它基本上是安全的。
检查了一下官方最新的代码,这部分仍然没有做修改:
http://www.uclibc.org/cgi-bin/viewcvs.cgi/trunk/uClibc/libc/stdlib/malloc/free.c?rev=18427&view=markup 

七、结语
之前从来没读过 C 库的代码,这次对 malloc 和 free 代码的走读,原因是开发过程中碰上了 Bug,跟踪到这里面来的。看完后发现 C 库函数远不如想像中的恐怖,希望读到此文的朋友们够循此继续解读下去能 :-)

本篇完。

— EOF —

arm-elf-tools-20030314.sh 和 arm-elf-tools-20040427.sh

这两个版本的工具链都有些问题 :(

arm-elf-tools-20030314.sh的sprintf函数存在缺陷。
如下语句:

     sprintf(buf, "一共 %d 个!", ch);

最终buf中的内容为“Invalid multibye”。 

arm-elf-tools-20040427.sh的pthread库有问题。
链接使用pthread库的程序时出错:

pthread_test.elf2flt(.text+0x5ffc): In function `__pthread_perform_cleanup':
: undefined reference to `__rpc_thread_destroy'
collect2: ld returned 1 exit status
make[2]: *** [pthread_test] 错误 1
make[2]: Leaving directory `/home/uClinux/user/pthread_test'
make[1]: *** [all] 错误 2
make[1]: Leaving directory `/home/uClinux/user'
make: *** [subdirs] 错误 1

比较了一下pthread库中__pthread_perform_cleanup部分:
20030314版本:

00000608 g     F .text  000000a8 __pthread_perform_cleanup
00000000         *UND*  00000000 __pthread_provide_wrappers

20040427版本:

00000608 g     F .text  000000bc __pthread_perform_cleanup
00000000         *UND*  00000000 __rpc_thread_destroy
00000000         *UND*  00000000 __pthread_provide_wrappers

而符号__rpc_thread_destroy在库中无法找到,所以编译时出错。 

解决方法:
运行 ./arm-elf-tools-20040427.sh 安装20040427版本的工具链,然后将20030314版本的libpthread.a替换/usr/local/arm-elf/lib/libpthread.a。

— EOF —

MiniGUI源码走读

注:这里是针对以下配置的MiniGUI v1.3.3进行解读:
使用thread模式
资源内置(Resource Incore)
使用旧版本GAL
使用native gal引擎
支持RBF字体
不支持鼠标

Author: Hily Jiang
Email&Gtalk: hilyjiang at Gmail
Blog: http://hily.me/blog/

0. 主函数main()
主函数main()在src/main/main.c中定义,主要运行过程如下:
InitGUI():用于初始化GUI;
MiniGUIMain():进入用户应用程序主体;
TerminateGUI():用户应用程序退出后执行它释放资源,最后退出minigui。

1 初始化GUI:InitGUI()
位于src/kernel/init.c中,主要过程:
InitMisc():初始化配置;
InitGAL():初始化图形抽象层;
InstallSEGVHandler():指定信号处理函数;
InitGDI():初始化图形设备接口;
InitWindowElementColors():初始化窗体颜色;
InitLWEvent():初始化底层事件;
InitFixStr():初始化固定字符串;
InitMenu():初始化菜单;
InitControlClass():初始化控制类;
InitAccel():初始化加速器;
SystemThreads():开辟系统线程;
SetKeyboardLayout():设置键盘布局;
SetCursor():设置鼠标;
SetCursorPos():设置鼠标位置;
TerminateMgEtc():INCORE_RES时该函数为空。

1.1 初始化配置:InitMisc()
位于src/misc/misc.c中,函数主体就是调用这个文件头文件misc.h中的InitMgEtc()。
InitMgEtc()的作用就是获取src/sysres/mgetc.c中定义的MiniGUI运行时的配置,并分配一个句柄hMgEtc指向配置对象。
配置信息保存在一个ETC_S类型的结构MGETC中,它在src/sysres/mgetc.c中定义。

1.2 初始化图形抽象层:InitGAL()
位于src/gal/gal.c中,运行过程如下:
检查可用的图形引擎数NR_GFXES,即该文件中gfxes[]数组的元素个数,若无可用引擎信息,则返回错误;
GetMgEtcValue()从配置中读取配置的GAL引擎名称;
接着查找gfxes[]数组中是否有匹配的引擎,找到后用cur_gfx指向该引擎信息,如果没有找到,则返回错误;
接着用GAL_InitGfx()检测当前引擎的初始化函数是否返回成功。

1.2.1 初始化GAL引擎:GAL_InitGfx()
GAL_InitGfx是一个宏定义,位于include/oldgal.h中,它调用src/gal/native/native.c中对当前引擎的图像函数进行初始化。

1.3 指定信号处理函数:InstallSEGVHandler()
位于src/kernel/init.c中,它的作用就是指定一些信号和信号处理函数。
指定的信号有:SIGSEGV(段错误)、SIGTERM(终止信号)和SIGINT(中断信号)。
指定的信号处理函数为:segvsig_handler,也在这个文件中定义。
在程序接收到指定的三个信号时,segvsig_handler会释放资源并退出程序执行。

1.4 初始化图形设备接口:InitGDI()
位于src/gdi/gdi.c中,运行过程如下:
InitTextBitmapBuffer():这个函数始终返回true,似乎没什么用了;
InitIncoreRBFonts():初始化内嵌的RBF字体;
InitSysFont():初始化系统字体;
InitFreeClipRectList():初始化空闲方形区域块列表;
初始化互斥锁__mg_gdilock(GDI锁)和dcslot(DCSlot锁);
dc_InitClipRgnInfo():初始化Device Context(设备环境)的范围;
dc_Init_ScreenDC():初始化屏幕的DC。

1.4.1 初始化RBF字体:InitIncoreRBFonts()
位于src/font/rawbitmap.c中,运行过程如下:
可用字体数为NR_RBFONTS,即该文件中incore_rbfonts[]数组的元素个数;
检查当前所有可用的字体的信息是否正确,包括:
字符集:fontGetCharsetFromName();
是否支持该字符集:GetCharsetOps();
字体宽度:fontGetWidthFromName();
字体高度:fontGetHeightFromName()。
接着初始化设备字体信息,包括字体信息,操作函数等;
最后把设备字体增加到字体列表中。

1.4.2 初始化系统字体:InitSysFont()
位于src/font/sysfont.c中,运行过程如下:
利用GetMgEtcIntValue()从配置中读取系统字体的数量,若字体数小于1则返回,若字体数大于6,则只使用前6种字体;
接着检测这些字体信息是否正确,并创建逻辑字体;
最后将合法的字体根据配置一一填入对应的逻辑字体数组中。

1.4.3 初始化空闲方形区域块列表:InitFreeClipRectList()
该宏定义位于include/gdi.h,它调用include/gdi.h中的InitBlockDataHeap()初始化空闲方形区域块的信息(包括大小和个数)。

1.4.4 初始化设备环境的范围:dc_InitClipRgnInfo()
位于src/gdi/gdi.c中,
InitClipRgn()

1.4.5 初始化屏幕的DC:dc_Init_ScreenDC()
位于src/gdi/gdi.c中,运行过程如下:
初始化DC设置:
调用SetClipRgn()设置该DC的区域范围。

1.4.5.1 设置DC的区域范围:SetClipRgn()
位于src/gdi/cliprect.c,运行过程如下:
IsRectEmpty():位于src/gdi/rect.c中,检测区域是否为空;
NormalizeRect():位于src/gdi/rect.c中,纠正错误的区域信息,即使区域left ClipRectAlloc():该宏定义位于include/gdi.h,它调用src/kernel/blockheap.c中的BlockDataAlloc()从分配该区域的堆空间。

1.5 初始化窗体颜色:InitWindowElementColors()
位于src/sysres/syscfg.c中,通过调用src/gdi/draw.c中的RGB2Pixel()对颜色进行转换。

1.6 初始化底层事件:InitLWEvent()
位于src/kernel/event.c中,运行过程如下:
GetDblclickTime():获取双击间隔时间;
GetTimeout():获取超时时间;
InitIAL():初始化输入引擎;
ResetMouseEvent():重置鼠标事件;
ResetKeyEvent():重置键盘事件。

1.6.1 获取双击间隔时间:GetDblclickTime()
位于src/kernel/event.c中,因为不支持鼠标,配置文件中没有mouse相关选项,该函数直接返回。

1.6.2 获取超时时间:GetTimeout()
位于src/kernel/event.c中,因为不支持鼠标,配置文件中没有mouse相关选项,该函数直接返回。

1.6.3 初始化输入引擎:InitIAL()
位于src/ial/ial.c中,运行过程如下:
获取配置中ial_engine、mdev和mtype的配置信息;
查找配置的IAL引擎是否在该文件的inputs[]数组中,存在的话就把该引擎做为当前的IAL引擎;
IAL_InitInput():调用inputs数组中的IAL引擎初始化函数初始化IAL引擎。

1.7 初始化固定字符串:InitFixStr()
位于src/kernel/fixstr.c中,初始化一个结构体FixStrHeap,暂时不能理解它的用处。

1.8 初始化菜单:InitMenu()
位于src/gui/menu.c中。

1.9 初始化控制类:InitControlClass()
位于src/gui/ctrlclass.c中,注册配置中支持的控件类。

1.10 初始化加速器:InitAccel()
位于src/gui/accelkey.c中。

1.11 开辟系统线程:SystemThreads()
位于src/kernel/init.c中,运行过程如下:
InitDesktop():初始化桌面;
InitFreeQMSGList():初始化空闲消息队列;
InitMsgQueue():初始化消息队列;
DesktopMain:创建桌面线程;
TimerEntry:创建定时器线程;
EventLoop:创建事件循环线程。

1.11.1 初始化桌面:InitDesktop()
位于src/kernel/desktop.c中,运行过程如下:
InitZOrderInfo():初始化桌面叠放次序;
InitFreeClipRectList():初始化空闲方形区域块列表;
InitSystemRes():初始化系统资源;
InitWndManagementInfo():初始化窗体管理信息。

1.12 设置键盘布局:SetKeyboardLayout()
位于src/gui/keyboard.c中,在数组layouts[]中查找与指定键盘布局名称相同的键盘设置,找到后调用相应的键盘初始化函数对按键图和按键函数。

— EOF —

uClinux开发环境介绍

以下是我对uClinux 开发环境的一些想法,欢迎拍砖。

Author: Hily Jiang
Email: hilyjiang at Gmail
Blog: http://hily.me/blog/

目前开发基于 uClinux 平台应用的开发环境主要有三种:

1. 装Linux系统
    这是目前我的项目组中开发时使用的主要方式。
    环境配置:
        操作系统:  各种 linux 发行版
        开发工具链:arm-elf-tools
        终端调试器:minicom
        代码编辑工具:gedit/vim/Emacs/Kate/KScope等
        CVS工具:cvs命令行,也可以使用图形界面的cvs客户端,如gcvs、TkCVS。
    Linux发行版在这里推荐使用 Ubuntu(当前最新版本为7.10),软件维护和更新都很快,在开发之余可以充分体验Linux的乐趣。
    代码编辑工具,强烈推荐KScope,经过一段时间试用,感觉很不错,支持基于变量/函数名的声明、定义、引用的查找,还有自动完成功能。

2. Windows + 虚拟机
    虚拟机中装Linux进行编译,代码放在虚拟机中,共享到Windows下进行编辑,产生的映像在Windows下进行烧写。
    环境配置:
        操作系统:  Windows 系列
        终端调试器:超级终端等
        代码编辑工具:UltraEdit、EditPlus、SourceInsight等
        CVS工具:TortoiseCVS、WinCVS等
        虚拟机:  VMWare
           – 操作系统:  各种 linux 发行版
           – 开发工具链:arm-elf-tools
           – 与 PC 主机文件交互:Samba或VMWare-tools。

3. Windows + 远程编译
        与上一方案类似,只是代码是放在服务器上的,通过ssh或telnet登录到服务器上进行编译。生成的映像也是在Windows下进行烧写。
    环境配置:
        操作系统:  Windows 系列
        终端调试器:超级终端等
        代码编辑工具:UltraEdit、EditPlus、SourceInsight等
        CVS工具:TortoiseCVS、WinCVS等
        远程连接工具:  支持SSH或Telnet的连接工具,如Putty、SecureCRT等。
           – 服务器操作系统:  各种linux发行版
           – 开发工具链:arm-elf-tools
           – 与客户端的文件交互:NFS或Samba。

— EOF —