2011年1月20日 星期四

如何在linux内核启动时添加显示图片

/*
 * 如何在linux内核启动过程中贴图
 * 内核版本为2.6.30.4
 *
 * zhs
 * 2011年01月20日 星期四 16时58分26秒
 */

为了实现了在linux内核启动时不仅仅是只显示一个静态的全屏logo, 而是显示能够表示
内核正在启动的进度条,因此需要能够在启动过程中直接操纵framebuffer的功能。 而进
度条则可以很简单的使用多次贴图来实现。

既然在启动时能够显示logo,那么按照自己的要求来贴图也就是可能的。关于logo的显示
可以参考fbmem.c和logo.c文件。

在fbmem.c中fb_prepare_logo()调用logo.c中的fb_find_logo()获得struct linux_logo
指针,该结构体中包含了logo显示的数据和各种参数如高宽。

然后fbmem.c中fb_show_logo()函数中调用fb_show_logo_line()进行实际的显示操作。不
需要管fb_show_extra_logos(),一般这个函数不会有用处的。

在fb_show_logo_line()函数中,根据struct linux_logo中的数据,主要是clut信息,设
置好fb_info中的调色板;其次创建一个struct fb_image结构体,该结构体为实际用于显
示的变量。将fb_image的数据指向linux_logo中的数据,然后设置到fb_image的显示位置
和高宽,最后调用fb_do_show_logo()。

fb_do_show_logo()中实际上是调用fb_info中的fb_ops的fb_imageblit()函数将fb_image
表示的图像写入到framebuffer中,然后显示出来。

至此,一个logo的显示便完成了。由此看出,如果要显示自己的图像,需要准备和设置的
东西包括:图像数据、设置fb_image的各个参数以及设置调色板。

图像数据的获得和自制linux的logo的过程一样,在获得ppm文件之后,通过内核编译过程
生成.c文件。该文件中有表示实际数据的数据以及表示clut信息的数据,这两个数组都会
在后面使用到。 里面还有一个struct linux_logo结构体,该结构体的信息可以用来建立
后面需要显示的每个图像的struct linux_logo变量。 数据用来显示,而clut数据用来设
置调色板。这里在logo.c中定义如下:

/* 显示数据 */
static unsigned char own_image_data0[] __initdata = {
    ......//太多了,省略
};

/* clut信息 */
static unsigned char own_image_clut224_clut[] __initdata = {
    0xff, 0xff, 0xff, 0xed, 0xed, 0xed, 0x87, 0x87, 0x87, 0xdb, 0xdb, 0xdb,
    0x64, 0x64, 0x64, 0x77, 0xa4, 0xd1, 0x28, 0x70, 0xb6, 0x17, 0x64, 0xb0,
    0x21, 0x6b, 0xb3, 0x29, 0x70, 0xb6
};

然后在logo.c文件中建立struct linux_logo变量, 根据logo本身的格式将该变量的各个
参数设置好,如下:

/* linux_logo */
static struct linux_logo own_image_clut224 = {
    .type    = LINUX_LOGO_CLUT224,
    .width    = 182,
    .height    = 32,
    .clutsize    = 10,
    .clut    = own_image_clut224_clut,
    .data    = own_image_data0
};

在建立struct fb_image变量,同样设置好,如下:

/* fb_image */
static struct fb_image own_images[OWN_IMAGE_NUM] = {
    .dx = OWN_IMAGE_DX,  //左上角坐标
    .dy = OWN_IMAGE_DY,
    .width = OWN_IMAGE_WIDTH,
    .height = OWN_IMAGE_HEIGHT,
    .depth = OWN_IMAGE_DEPTH,
    .data = own_image_data0,
};

之后在logo.c中设置必要的变量和接口函数,如下:

static struct fb_info *own_fb_info;    //保存fb_info信息

/* 设置fb_info */
void own_set_fb_info(struct fb_info *info)
{
    own_fb_info = info;
}
EXPORT_SYMBOL_GPL(own_set_fb_info);

/* 获得保存的fb_info */
struct fb_info *own_get_fb_info()
{
    return own_fb_info;
}
EXPORT_SYMBOL_GPL(own_get_fb_info);

/* 获得linux_logo */
struct linux_logo *own_get_image_logo()
{
    return &own_image_clut224;
}
EXPORT_SYMBOL_GPL(own_get_image_logo);

/* 获得fb_image */
struct fb_image *own_get_image()
{
    return &own_images;
}
EXPORT_SYMBOL_GPL(own_get_image);

注意的是,需要把这些接口函数的声明放到linux_logo.h中,然后在需要调用这些接口函
数的文件文件中包含该头文件即可。


接下来,在fbmem.c添加一个执行显示图像的函数。 之所以放在该文件中,是因为可以方
便的调用一些本地函数。添加的函数如下:

/* 显示logo.c中指定的图像 */
void own_show_image()
{
    u32 *palette = NULL, *saved_pseudo_palette = NULL;
    struct fb_image *pimage;
    struct linux_logo *logo;
    struct fb_info *info;
  
    info = own_get_fb_info();
    logo = own_get_image_logo();
    pimage = own_get_image();
          
    palette = kmalloc(256 * 4, GFP_KERNEL);
    if (palette == NULL)
        return;
    fb_set_logo_truepalette(info, logo, palette);
    saved_pseudo_palette = info->pseudo_palette;
    info->pseudo_palette = palette;

    fb_do_show_logo(info, pimage, FB_ROTATE_UR, 1);

    kfree(palette);
    if (saved_pseudo_palette != NULL)
        info->pseudo_palette = saved_pseudo_palette;
}
EXPORT_SYMBOL(own_show_image);

要将该函数的声明放到include/linux/fb.h中,以便其他文件调用。

该函数其实是fb_show_logo_line()的简化版。调用了logo.c中的几个接口函数来显示。

另外在fbmem.c文件中的fb_show_logo()的最后添加logo.c中的own_set_fb_info()函数,
以便在确定framebuffer能使用之后获得有效的fb_info信息。

至此,对图像的显示便完成了,只需要在需要显示的代码处调用own_show_image()即可。

不过此方法存在一些缺点:
- 不够灵活,需要自己在各处添加显示函数,这会造成进度条的前进不均匀。这个可以通
  过添加定时器struct timer_list来实现定时的贴图。
- 对于uboot和应用程序的启动的时间没有办法控制。

以上。

2010年12月15日 星期三

Vim帮助文档格式的文档编写

/*
 * 如何编写Vim帮助文档那样的,具有:
 * 标签跳转、标签高亮、等格式的纯文本文档
 *
 * zhs
 * 2010年12月15日 星期三 13时28分07秒
 */

最近在写一个Vim的入门指南文档,在网友的建议下使用Vim帮助文档的文档格式改写了,
有一些心得,写在这里。

Vim中对其帮助文档有它自己的一套语法高亮,可以在

    /usr/local/shar/vim/vim73/syntax/help.vim

中查看。并且该有自己的一套所谓“帮助文档格式”的编写方法,可以很方便的实现文本级
别的标签跳转,使得文档的阅读十分方便,特别是针对技术文档。

下面就简要的说一下编写Vim帮助文档格式或风格文档的一些入门方法。

首先需要在文档的结尾处添加语句:

    vim:tw=78:fo=tcq2:isk=!-~,^*,^\|,^\":ts=8:ft=help:norl:

为一个模式行,其中重要的是ft=help命令,即将该文档的类型设置为help类型,使得该
文档能够使用Vim帮助的高亮和跳转方式。

然后开始编写。

如果要将输入的命令来高亮,则输入": >",即英文冒号、空格和右箭头,并立刻回
车。回车后在下一行输入命令。不过输入命令之前需要输入空白符,以便Vim正确识别该
格式以进行高亮。

对于制作和使用标签。使用|和|,'和'括住链接,使用*和*括住源地址,即可。最后需要
使用:

      :helptags .

该命令会扫描指定目录下的txt内的标签,并创建帮助文档类型使用的tags。这样Vim借助
该tags就可以使用CTRL+], CTRL+t来在标签之间来回跳转了。
 
不过貌似建立标签只能对英文字符会起到高亮效果.

2010年11月30日 星期二

pentadactyl中新建网页时的自动补齐设置

/*
 * 2010年12月01日 星期三 14时42分11秒
 * zhs
 */

使用vimperator的升级版: pentadactyl(名字好难记啊)已经有一段时间了,感觉在各方
面都和vimperator行为是一致的,但是速度上响应更加的快了,只是个别操作指令有些差
异,特别是保存配置的指令,vimperator中是mksession,和vim中一致,而pentadactyl
中是mkpentadactylrc,好长啊。不过还好有自动补齐。

自动补齐不仅体现在自身命令的补齐上,更加强大的一点是在打开一个网页时的自动补齐
。无论是使用o、O还是t命令打开一个新的网页,命令之后都要接着输入参数网址。 在以
前使用vimperator时,是需要自己记住网址的url的, 也许进行一些设置就可以帮助我们
快速的输入网址,但这里讲的是不经过太折腾的情况下。 pentadactyl就缺省的提供了这
中情况下的网址的自动补齐。help complete或者cpt可以查看更多信息。

之所以会去查看,是因为缺省情况下,每次使用tabnew的时候,都会出现google啊,百度
啊以及一些我不知道是什么的网址来作为自动补齐的候选。我当时想要清除这些候选,因
为嫌他太碍眼了。后来查看帮助之后发现,cpt是有多个选项的,如下:

s    Search engines and keyword URLs
f     Local files
l     Firefox location bar entries
      (bookmarks and history sorted in an intelligent way)
b   Bookmarks
h   History
S   Search engine suggestions
t     Open tabs

而cpt的缺省值为slf,所以出现那些候选的网址也就不奇怪了。本来我想直接将该参数置
空就好了,这样天下就安静了,但是发现了histroy这个显示历史的, 想想:这个不是挺
好的吗,可以轻松的输入经常上的几个网站。但是悲剧的发现,如果不设置历史记录的个
数的话(没去查有没有这个参数,但想着这么强大的东西应该有吧),会载入太多的历史记
录来进行补齐的匹配,使得输入非常慢,因此就不设置h了。最后,还是将cpt的值设置为
b就可以了。 这样只使用书签作为补齐的候选,速度上是可以接受的,实用性和便利性也
非常不错。

2010年11月19日 星期五

CUnit文档的翻译

 因为需要使用单元测试,因此这几天便在网上找适合C语言的单元测试框架,话说java和
C#的很多啊... 发现了CUnit, CuTest 以及Cmockery(还是google出的呢)。大致看了一下,
貌似还是CUnit比较全面和简单一点。就首先从这个下手吧。在读文档的过程中,顺便花
时间将其翻译了一下,因为是边读边翻的,也没有事后去校正,可能有不对的地方,希望
大家指出。

/*
 * 大致翻译CUnit网站上的《Documentation》里的《CUnit Users Guide》文档
 * 文档位于:http://cunit.sourceforge.net/doc/index.html
 * 官网为:http://cunit.sourceforge.net
 * CUnit的版本为:CUnit-2.1-2-src.tar, Release Date: 2010-10-17
 *
 * 2010年11月18日 星期四 17时24分38秒
 * zhs
 */

Index:
- 1. Introduction
  - 1.1. Description
  - 1.2. Structure
  - 1.3. General Usage
  - 1.4. Changes to the CUnit API in Version 2
- 2. Writing CUnit Tests
  - 2.1. Test Functions
  - 2.2. CUnit Assertions
  - 2.3. Deprecated v1 Assertions
- 3. The Test Registry
  - 3.1. Synopsis
  - 3.2. Internal Structure
  - 3.3. Initialization
  - 3.4. Cleanup
  - 3.5. Other Registry Functions
  - 3.6. Deprecated v1 Data Tyeps & Functions
- 4. Managing Tests & Suites
  - 4.1. Synopsis
  - 4.2. Adding Suites to the Registry
  - 4.3. Adding Tests to Suites
  - 4.4. Shortcut Methds for Managing Tests
  - 4.5. Deprecated v1 Data Types & Functions
- 5. Running Tests
  - 5.1. Synopsis
  - 5.2. Running Tests in CUnit
  - 5.3. Automated Mode
  - 5.4. Basic Mode
  - 5.5. Interactive Console Mode
  - 5.6. Interactive Curses Mode
  - 5.7. Getting Test Results
  - 5.8. Deprecated v1 Data Types & Functions
- 6. Error Handing
  - 6.1. Synopsis
  - 6.2. CUnit Error Handing
  - 6.3. CUnit Behavior on Framework Errors
  - 6.4. Deprecated v1 Variables & Functions

------------------------------------------------------------------------------
- 1. Introduction to Unit Testing with CUnit
  - 1.1. Description
    CUnit是一个使用C来编写、管理以及运行单元测试的系统。它被构建成一个静态库,
    并被连接到使用者的测试代码。

    CUnit使用一个简单的框架来构建测试结构, 并且提供了一个丰富的断言集用以测试
    一般的数据类型。另外,还提供了一些不同的接口来运行测试和报告结果。这些接口
    包括用于由代码控制的测试和报告的所谓的自动式接口,以及允许使用者动态地运行
    测试和查看结果的交互式接口。

    下面列出的头文件声明了对典型使用者非常有用的数据类型和函数:
    --------------------------------------------------------------------------
    Header File                      Description
           用于测试用例(test case)ASSERT宏, 并包括其他的架构头
                          文件。
         用于处理错误的函数和数据类型。自动被CUnit.h包含。
          定义了用于test registry, suite和test的数据结构和操作
                          函数。自动被CUnit.h包含。
         定义了用于运行测试和获取测试结果的数据类型和函数。自
                          动被CUnit.h包含。
       输出xml格式结果的自动式接口(Automated interface)。
           无交互、输出为标准输出的基本接口(basic interface)。
         交互式的控制台接口。
        交互式的控制台接口(使用curses库,只用于*nix系统)。
             Windows下的接口(还没有实现)。

  - 1.2. Structure
    CUnit是一个拥有多个用户接口、平台无关的一个组合体。 其核心框架提供了对管理
    test registry, suite和test case的支持。 而用户接口则使得使用者能够和该框架
    进行交互以运行测试和查看测试结果。

    CUnit像一个通常的单元测试框架那样组织:

                      Test Registry
                            |
             ------------------------------
             |                            |
          Suite '1'      . . . .       Suite 'N'
             |                            |
       ---------------             ---------------
       |             |             |             |
    Test '11' ... Test '1M'     Test 'N1' ... Test 'NM'

    单个的test case打包成一个个suite,suite则被注册到当前活动的test registry。
    每个suite可以有setup函数和teardown函数, 在每个suite下的test执行前后自动被
    调用。一个registry中所有的suite/test可以通过单个函数调用被运行,也可以被选
    择地运行。

  - 1.3. General Usage
    一个典型的使用CUnit框架的步骤是:
    1. 编写用于测试的函数(如果有必要的话还包括suite的init/cleanup函数)。
    2. 初始化test registry - CU_initialize_registry()。
    3. 添加suite到test registry中 - CU_add_suite()。
    4. 添加test到suite中 - CU_add_test()
    5. 使用恰当的接口运行测试,例如CU_console_run_tests()。
    6. 清除test registry - CU_cleanup_registry()。

  - 1.4. Changes to the CUnit API in Version 2
    CUnit中所有的共用命名现在都加上了"CU_"前缀,有助于减少和使用者代码中的命名
    冲突。注意,早前的CUnit版本中使用了没有该前缀命名。旧的API命名不被推荐,但
    是仍然支持。为了使用旧的命名,用户代码必须使用USE_DEPRECATED_CUNIT_NAMES宏
    来进行编译。

    不被推荐的API函数在本文档中的特定(Deprecated)章节中说明。

------------------------------------------------------------------------------
- 2. Writing CUnit Tests
  - 2.1. Test Functions
    CUnit中的一个test是拥有如下形式(signature?)的C函数:

      void test_func(void)

    对于测试函数在内容上是没有什么限制的,除了它不能修改CUnit本身的框架(例如添
    加suite或test,修改test  registry,或者启动一次测试)。一个测试函数可以调用
    其他函数(那些不会修改框架的)。注册一个test意味着该test的函数会在这个test运
    行时被调用。

    对于返回两个整型数中较大者的例程的一个测试函数可能如下所示:
     
      int maxi(int i1, int i2)
      {
          return (i1 > i2) ? i1 : i2;
      }

      void test_maxi(void)
      {
          CU_ASSERT(maxi(0, 2) == 2);
          CU_ASSERT(maxi(0, -2) == 0);
          CU_ASSERT(maxi(2, 2) == 2);
      }

  - 2.2. CUnit Assertions
    CUnit提供了一套断言来测试逻辑条件。 这些断言的成功或者失败会被框架所跟踪,
    并且在一个test运行完成之后进行查看。

    每个断言测试一个逻辑条件,并且在条件等于FALSE时失败。 当失败时,除非使用者
    选择了断言的'xxx_FATAL'版本,测试函数会中断并立即返回, 否则测试函数会继续
    运行。注意,断言的FATAL版本应当被小心使用!一旦一个FATAL断言失败的话,测试
    函数是没有机会进行它自身的clean up工作。然后suite的cleanup函数则不会受到影
    响。

    存在着一些特殊的断言,用来在没有逻辑测试时给框架注册一个pass或者fail。这在
    测试控制流或者不需要逻辑测试的情况时很有用:

      void test_longjmp(void)
      {
          jmp_buf buf;
          int i;

          i = setjmp(buf);
          if(i == 0){
              run_other_func();
              CU_PASS("run_other_func() succeeded.");
          } else
              CU_FAIL("run_other_func() issued longjmp.");
      }

    被一个注册了的测试函数调用的其他函数也能够自由的使用断言。这些断言被算入到
    调用函数中。甚至也可以使用FATAL版的断言, 如果失败的话将会中断原来的测试函
    数以及它的整个调用链。

    有CUnit定义的断言如下:

    #include
    --------------------------------------------------------------------------
    CU_ASSERT(int expression)                      断言expression是TRUE(非0)
    CU_ASSERT_FATAL(int expression) 
    CU_TEST(int expression)
    CU_TEST_FATAL(int expression)
    --------------------------------------------------------------------------
    CU_ASSERT_TRUE(value)                          断言value是TRUE(非0)
    CU_ASSERT_TURE_FATAL(value)
    --------------------------------------------------------------------------
    CU_ASSERT_FALSE(value)                         断言value是FALSE(0)
    CU_ASSERT_FALSE_FATAL(value)
    --------------------------------------------------------------------------
    CU_ASSERT_EQUAL(actual, expected)              断言 actual == expected
    CU_ASSERT_EQUAL_FATAL(actual, expected)
    --------------------------------------------------------------------------
    CU_ASSERT_NOT_EQUAL(actual, expected)          断言 actual != expected
    CU_ASSERT_NOT_EQUAL_FATAL(actual, expected)
    --------------------------------------------------------------------------
    CU_ASSERT_PTR_EQUAL(actual, expected)          断言指针 acutal == expected
    CU_ASSERT_PTR_EQUAL_FATAL(actual, expected)
    --------------------------------------------------------------------------
    CU_ASSERT_PTR_NOT_EQUAL(actual, expected)      断言指针 acutal != expected
    CU_ASSERT_PTR_NOT_EQUAL_FATAL(actual, expected)
    --------------------------------------------------------------------------
    CU_ASSERT_PTR_NULL(value)                      断言指针 value == NULL
    CU_ASSERT_PTR_NULL_FATAL(value)
    --------------------------------------------------------------------------
    CU_ASSERT_PTR_NOT_NULL(value)                  断言指针 value != NULL
    CU_ASSERT_PTR_NOT_NULL_FATAL(value)
    --------------------------------------------------------------------------
    CU_ASSERT_STRING_EQUAL(actual, expected)       断言字符串 actual和expected
    CU_ASSERT_STRING_EQUAL_FATAL(actual, expected)                        相同
    --------------------------------------------------------------------------
    CU_ASSERT_STRING_NOT_EQUAL(actual, expected)   断言字符串 actual和expected
    CU_ASSERT_STRING_NOT_EQUAL_FATAL(actual, expected)                  不相同
    --------------------------------------------------------------------------
    CU_ASSERT_NSTRING_EQUAL(actual, expected, count)
    CU_ASSERT_NSTRING_EQUAL_FATAL(actual, expected, count)
    断言actual和expected的前count个字符相同。
    --------------------------------------------------------------------------
    CU_ASSERT_NSTRING_NOT_EQUAL(actual, expected, count)
    CU_ASSERT_NSTRING_NOT_EQUAL_FATAL(actual, expected, count)
    断言actual和expected的前count个字符是不同的。
    --------------------------------------------------------------------------
    CU_ASSERT_DOUBLE_EQUAL(actual, expected, granularity)
    CU_ASSERT_DOUBLE_EQUAL_FATAL(actual, expected, granularity)
    断言|actual-expeted| <= |granularity|,使用该断言必须链接math库(-lm)
    --------------------------------------------------------------------------
    CU_ASSERT_DOUBLE_NOT_EQUAL(actual, expected, granularity)
    CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL(actual, expected, granularity)
    断言|actual-expeted| > |granularity|,使用该断言必须链接math库(-lm)
    --------------------------------------------------------------------------
    CU_PASS(message)          用特定message注册一个passing断言,不需要逻辑测试
    --------------------------------------------------------------------------
    CU_FAIL(message)          用特定message注册一个failed断言,不需要逻辑测试
    CU_FAIL_FATAL(message)
    --------------------------------------------------------------------------

  - 2.3. Deprecated v1 Assertions
    如下断言是版本2不推荐的。要使用断言, 代码必须用USE_DEPRECATED_CUNIT_NAMES
    宏一起编译。注意,这些断言的行为和版本1里的一样(即当失败时执行'return')。

    #include
    --------------------------------------------------------------------------
    Deprecated Name                   Equivalent New Name
    ASSERT                            CU_ASSERT_FATAL
    ASSERT_TRUE                       CU_ASSERT_TRUE_FATAL
    ASSERT_FALSE                      CU_ASSERT_FALSE_FATAL
    ASSERT_EQUAL                      CU_ASSERT_EQUAL_FATAL
    ASSERT_NOT_EQUAL                  CU_ASSERT_NOT_EQUAL_FATAL
    ASSERT_PTR_EQUAL                  CU_ASSERT_PTR_EQUAL_FATAL
    ASSERT_PTR_NOT_EQUAL              CU_ASSERT_PTR_NOT_EQUAL_FATAL
    ASSERT_PTR_NULL                   CU_ASSERT_PTR_NULL_FATAL
    ASSERT_PTR_NOT_NULL               CU_ASSERT_PTR_NOT_NULL_FATAL
    ASSERT_STRING_EQUAL               CU_ASSERT_STRING_EQUAL_FATAL
    ASSERT_STRING_NOT_EQUAL           CU_ASSERT_STRING_NOT_EQAUL_FATAL
    ASSERT_NSTRING_EQUAL              CU_ASSERT_NSTRING_EQUAL_FATAL
    ASSERT_NSTRING_NOT_EQAUL          CU_ASSERT_NSTRING_NOT_EQUAL_FATAL
    ASSERT_DOUBLE_EQUAL               CU_ASSERT_DOUBLE_EQUAL_FATAL
    ASSERT_DOUBLE_NOT_EQUAL           CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL

------------------------------------------------------------------------------
- 3. The Test Registry
  - 3.1. Synopsis
    #include (自动被包含)
   
    typedef struct CU_TestRegistry
    typedef CU_TestRegistry* CU_pTestRegistry
   
    CU_ErrorCode     CU_initialize_registry(void);
    void             CU_cleanup_registry(void);
    CU_pTestRegistry CU_get_registry(void);
    CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry);
    CU_pTestRegistry CU_create_new_registry(void);
    void             CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry);

  - 3.2. Internal Structure
    test registry是suite以及对应的test的一个仓库。 CUnit维护一个活动(active)的
    test registry,当使用者添加一个suite或者test时,该registry则被更新。当使用
    者选择运行所有测试时,活动registry中的suite会被运行。

    CUnit中的registry是声明在中的一个数据结构CU_TestRegistry。
    它包含的成员包括存储在该registry中的 suite数目、test数目以及指向已经注册的
    suite链表头的指针。

      typedef struct CU_TestRegistry
      {
          unsigned int uiNumberOfSuites;
          unsigned int uiNumberOfTests;
          CU_pSuite    pSuite;
      } CU_TestRegistry;

      typedef CU_TestRegistry* CU_pTestRegistry;

    使用者通常只在使用前和清理之后初始化registry。但是仍然提供了其他函数来操作
    registry。

  - 3.3 Initialization
    - CU_ErrorCode CU_initialize_registry(void)

      CUnit中活动registry必须在使用前进行初始化。使用者调用任何其他CUnit函数之
      前必须使用CU_initialize_registry()。否则可能会造成程序崩溃。

      返回的错误状态代码为:
        CUE_SUCCESS    初始化成功
        CUE_NOMEMORY   内存分配失败

  - 3.4. Cleanup
    - void CU_cleanup_registry(void)

      当测试完成,使用者应当调用该函数来清理和释放掉框架所使用的内存。这个应当
      是最后被调用的CUnit函数(除了用于重置registry的CU_initialize_registry()或
      CU_set_registry())。

      如果调用CU_cleanup_registry()失败,会造成内存泄漏。 它可以被调用多次而不
      会产生错误。 注意,该函数会销毁调用registry中的所有suite以及对应的test。
      在清理了registry之后,指向注册suite和test的指针就不应该再被使用了。

      调用CU_cleanup_registry()只是影响到由CUnit框架维护的内部CU_TestRegisty。
      而析构其余的由使用者维护的registry则是使用者自己的责任。这个工作可以显式
      地调用 CU_destroy_existing_registry()或者隐式地调用CU_set_registry()激活
      registy然后再次调用CU_cleanup_registry()完成。

  - 3.5. Other Registry Functions
    CUnit还提供了一些主要用于内部或调试用的函数。 但是一般的使用者也可以使用,
    因此应该知道这些函数。包括:

    - CU_pTestRegistry CU_get_registry(void)
      返回当前活动registry的指针。registry是数据类型CU_TestRegistry的变量。 不
      建议对内部test registry进行直接操作,而应当使用API函数。测试框架内部维护
      着registry的所有,因此返回的指针对CU_cleanup_registry()或CU_initialize_-
      registry()是无效的(??)。

    - CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry)
      用指定的registry代替当前活动registry。指向先前的registry的指针被返回。使
      用者应该负责销毁掉旧的registry。可以通过显式地对返回的指针调用CU_destro-
      y_existing_registry()完成。或者通过使用CU_set_registry()激活registry,然
      后调用CU_cleanup_registry()隐式地销毁。 注意,不要去显式地销毁一个被设置
      为活动的registry。这会造成对同一内存的多次释放,并可能导致崩溃。

    - CU_pTestRegistry CU_create_new_registry(void)
      创建一个新的registry并返回它的指针。 这个新的registry不会包含任何的suite
      或test。使用者应该负责销毁这个新的registry,方法前面已经描述过了。

    - void CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry)
      销毁和释放指定test registry的所有内存,包括注册的suite和test。该函数不应
      该对被设置成活动的registry调用(例如,由CU_get_registry()返回的指针)。 这
      可能在CU_cleanup_registry()被调用时导致同一内存的多次释放。 传NULL给该函
      数不会产生任何效果。

  - 3.6. Deprecated v1 Data Types & Functions
    下面的数据类型和函数是版本2不推荐的。 必须使用USE_DEPRECATED_CUNIT_NAMES宏
    进行编译才能使用。

    #include (被CUnit/CUnit.h自动包含)
    --------------------------------------------------------------------------
    Deprecated Name                  Equivalent New Name
    _TestRegistry                    CU_TestRegistry
    _TestRegistry.uiNumberOfGroups   CU_TestRegistry.uiNumberOfSuites
    PTestRegistry->uiNumberOfGroups  CU_pTestRegistry->uiNumberOfSuites
    _TestRegistry.pGroup             CU_TestRegistry.pSuite
    PTestRegistry->pGroup            CU_pTestRegistry->pSuite
    PTestRegistry                    CU_pTestRegistry
    initialize_registry()            CU_initialize_registry()
    cleanup_registry()               CU_cleanup_registry()
    get_registry()                   CU_get_registry()
    set_registry()                   CU_set_registry()

------------------------------------------------------------------------------
- 4. Managing Tests & Suites
  为了利用CUnit运行一个test测试,必须将该test添加到一个被注册到test registry中
  的测试套件/测试包(suite)中。

  - 4.1. Synopsis
    #include   (自动被包含)

    typedef struct CU_Suite
    typedef CU_Suite* CU_pSuite

    typedef struct CU_Test
    typedef CU_Test* CU_pTest

    typedef void (*CU_TestFunc)(void)
    typedef int  (*CU_InitializeFunc)(void)
    typedef int  (*CU_CleanupFunc)(void)

    CU_pSuite CU_add_suite(const char* strName,
                           CU_InitializeFunc pInit,
                           CU_CleanupFunc pClean);

    CU_pTest  CU_add_test(CU_pSuite pSuite,
                          const char* strName,
                          CU_TestFunc pTestFunc);
 
    typedef struct CU_TestInfo
    typedef struct CU_SuiteInfo

    CU_ErrorCode CU_register_suites(CU_SuiteInfo suite_info[]);
    CU_ErrorCode CU_register_nsuites(int suite_count, ...);

  - 4.2. Adding Suites to the Registry
    - CU_pSuite CU_add_suite(const char* strName, CU_InitializeFunc pInit,
                             CU_CleanupFunc pClean)

      由指定的名字、初始化函数和清理函数创建一个新的测试集合(suite)。 这个新的
      suite被注册到当前活动的测试registry(即被registry所有), 因此在添加任何的
      suites之前,registry必须被初始化。 目前的实现不支持独立于test registry的
      的suite创建。

      该suite的名字必须在该registry中所有suite中唯一的。初始化函数和清理函数是
      可选项,作为函数指针传递, 并且在运行该suite内的测试之前和之后被调用。这
      使得suite能够建立和清除用于运行测试的临时结构。 这些函数没有参数,并且在
      调用成功后返回0,否则返回非0。 如果一个suite不需要这些函数,则传递NULL给
      函数CU_add_suite()。

      指向新的suite的指针被返回,该指针用以以后添加test到该suite。如果函数产生
      错误,则返回NULL,并且框架的错误代码被设置成如下中的一个:
       
        CUE_SUCCESS        suite创建成功
        CUE_NOREGISTRY     registry没有被初始化
        CUE_NO_SUITENAME   strName为NULL
        CUE_DUP_SUITE      suite的名字不是唯一的
        CUE_NOMEMORY       内存分配失败

  - 4.3. Adding Tests to Suites
    - CU_pTest CU_add_test(CU_pSuite pSuite, const char* strName,
                           CU_TestFunc pTestFunc)

      由指定的名字和测试函数创建一个新的test,并注册添加到指定的suite中。suite
      必须为使用CU_add_suite()已经创建了的。目前的实现不支持独立于一个已注册的
      suite而创建一个test。

      test的名字在被添加到的单个suite中所有test的名字中必须唯一。 测试函数不能
      为NULL,该参数指向的函数在该test被运行时调用。测试函数没有参数也没有返回
      值。

      新test的指针被返回。如果创建过程中出现错误,则返回NULL,并且框架的错误代
      码被设置为如下中的一个:

        CUE_SUCCESS        test创建成功
        CUE_NOSUITE        指定的suite为空或者是非法的
        CUE_NO_TESTNAME    strName为NULL
        CUE_NO_TEST        pTestFunc为NULL或非法
        CUE_DUP_TEST       test的名字不是唯一的
        CUE_NOMEMORY       内存分配失败

  - 4.4. Shortcut Methods for Managing Tests
    - #define CU_ADD_TEST(suite, test) (CU_add_test(suite, #test, (CU_TestFUnc)test))

      该宏基于测试函数函数名自动地产生一个唯一的test名字,并且将test添加到指定
      的suite。使用者应当检查返回值以确定是否添加成功

    - CU_ErrorCode CU_register_suites(CU_SuiteInfo suite_info[])
      CU_ErrorCode CU_register_nsuites(int suite_count, ...)

      对于拥有众多test和suite的大型测试结构, 管理test/suite的连接和注册是枯燥
      和容易出错的。CUnit提供了一个特殊的注册系统来帮助管理suite和test。它主要
      的好处就是集中管理了suite以及对应的test的注册, 以及减少了使用者需要编写
      的检查错误代码的数量。

      首先test case被打包到一个CU_TestInfo类型数组中(定义在):

        CU_TestInfo test_arrya1[] = {
            {"testname1", test_func1},
            {"testname2", test_func2},
            {"testname3", test_func3},
            CU_TEST_INFO_NULL,
        };

      每个数组元素都包含了一个test case里(唯一的)名字以及测试函数。 数组必须以
      一个NULL值元素结尾,通常是使用宏CU_TEST_INFO_NULL。包含在这个CU_TestInfo
      数组的所有test case组成了一个test集合,该集合将会被注册到一个suite里。

      suite的信息在一个或多个CU_SuieInfo类型数组中定义():

        CU_SuiteInfo suites[] = {
            {"suitename1", suite1_init_func, suite1_cleanup_func, test_array1},
            {"suitename2", suite2_init_func, suite2_cleanup_func, test_array2},
            CU_SUITE_INFO_NULL,
        };

      该数组的每个元素包含一个(唯一的)名字、suite初始化函数、 清理函数以及一个
      该suite的CU_TestInfo数组。 同样的,如果该suite不需要初始化函数和清理函数
      的话,那么相应的设置为NULL即可。该数组必须以一个全NULL的元素结尾,这里使
      用CU_SUITE_INFO_NULL宏即可。

      接下来,所有定义成CU_SuiteInfo数组的suite可以通过一个语句进行注册:

        CU_ErrorCode error = CU_register_suites(suites);

      如果在任何一个suite或者test注册过程中出现错误,则返回一个错误代码。 这些
      错误代码和通常的suite注册或test添加操作返回的错误代码一致。 如果要将多个
      CU_SuiteInfo数组在一个语句里进行注册,则使用函数CU_register_nsuites():

         CU_ErrorCode error = CU_register_nsuites(2, suites1, suites2);

      该函数接受可变数目的CU_SuiteInfo数组作为参数。第一个参数表示了实际被传入
      的数组个数。

  - 4.5. Deprecated v1 Data Types & Functions
    下面的数据类型和函数是版本2不推荐的。 必须使用USE_DEPRECATED_CUNIT_NAMES宏
    进行编译才能使用。

    #include (自动被包含)
    --------------------------------------------------------------------------
    Deprecated Name                  Equivalent New Name
    TestFunc                         CU_TestFunc
    InitializeFunc                   CU_InitializeFunc
    CleanupFunc                      CU_CleanupFunc
    _TestCase                        CU_Test
    PTestCase                        CU_pTest
    _TestGroup                       CU_Suite
    PTestGroup                       CU_pSuite
    add_test_group()                 CU_add_suite()
    add_test_case()                  CU_add_test()
    ADD_TEST_TO_GROUP()              CU_ADD_TEST()
    test_case_t                      CU_TestInfo
    test_group_t                     CU_SuiteInfo
    test_suite_t                     没有等价用法,使用CU_SuiteInfo
    TEST_CASE_NULL                   CU_TEST_INFO_NULL
    TEST_GROUP_NULL                  CU_SUITE_INFO_NULL
    test_group_register              CU_register_suites()
    test_suite_register              没有等价用法,使用CU_register_suites()

------------------------------------------------------------------------------
- 5. Running Tests
  - 5.1. Synopsis
    #include

      void          CU_automated_run_tests(void);
      CU_ErrorCode  CU_list_tests_to_file(void);
      void          CU_set_output_filename(const char* szFilenameRoot);

    #include

      typedef enum    CU_BasicRunMode
      CU_ErrorCode    CU_basic_run_tests(void);
      CU_ErrorCode    CU_basic_run_suite(CU_pSuite pSuite);
      CU_ErrorCode    CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest);
      void            CU_basic_set_mode(CU_BasicRunMode mode);
      CU_BasicRunMode CU_basic_get_mode(void);
      void            CU_basic_show_failures(CU_pFailureRecord pFailure);

    #include

      void CU_console_run_tests(void);

    #include

      void CU_curses_run_tests(void);

    #include    (自动被包含)

      unsigned int CU_get_number_of_suites_run(void);
      unsigned int CU_get_number_of_suites_failed(void);
      unsigned int CU_get_number_of_tests_run(void);
      unsigned int CU_get_number_of_tests_failed(void);
      unsigned int CU_get_number_of_asserts(void);
      unsigned int CU_get_number_of_successes(void);
      unsigned int CU_get_number_of_failures(void);

      typedef struct CU_RunSummary
      typedef CU_Runsummary* CU_pRunSummary
      const CU_pRumSummary CU_get_run_summary(void);

      typedef struct CU_FailureRecord
      typedef CU_FailureRecord* CU_pFailureRecord
      const CU_pFailureRecord CU_get_failure_list(void);
      unsigned int CU_get_number_of_failure_records(void);

  - 5.2. Running Tests in CUnit
    CUnit支持运行在所有已注册suite中的所有test,但是也可以运行单独的test或者单
    独的suite。在每次运行中,框架跟踪suite数目、test数目以及断言运行、通过、失
    败的数目。注意,在每次测试运行初始化时会清空上次的结果。

    虽然CUnit提供了许多原始函数用于运行suite和test,大多数使用者希望使用一个简
    化的用户接口。这些用户接口负责处理和框架的交互细节,并且将测试细节和结果的
    输出提供给使用者。

    CUnit库包含的接口如下所示:

    --------------------------------------------------------------------------
      接口        平台           描述
      Automatd    所有           非交互式,输出到xml文件
      Basic       所有           非交互式,可选输出到标准输出
      Console     所有           控制台交互模式
      Curses      Linux/Unix     curses的交互模式

    如果这些接口还不够的话, 客户端可以使用定义在中的框架提供
    的原始API。可以查看源码,了解如何直接和原始API进行交互。

  - 5.3 Automated Mode
    automated接口是非交互式的。客户端启动一次测试,然后结果被输出到一个XML文件
    里。已注册的test和suite列表也可在XML文件中看到。

    如下所示的函数构成了automated接口API:

    - void CU_automated_run_tests(void)

      运行所有已注册suite中的test。 测试结果被输出到一个名为ROOT-Results.xml的
      文件中。文件名ROOT可以使用CU_set_output_filename()进行设置,否则将使用默
      认的文件名CUnitAutomated-Results.xml。注意,如果每次运行前没有设置不同的
      ROOT文件名的话,结果文件会被覆盖掉。

      结果文件可以被DTD文件(CUnit-Run.dtd)和XSL文件(CUnit-Run.xsl)支持。它们可
      以在源码和安装树下的Share子目录中找到。

    - CU_ErrorCode CU_list_tests_to_file(void)

      列出已注册的suite以及对应的test,并输出到文件中。 结果输出到的列表文件名
      字为ROOT-Listing.xml。文件名中的ROOT可以使用CU_set_output_filename()进行
      设置,否则使用默认值CUnitAutomated。注意,每次运行前没有设置不同的ROOT文
      件名的话,列表文件会被覆盖掉。

      列表文件可以被DTD文件(CUnit-List.dtd)和XSL文件(CUnit-List.xsl)支持。它们
      可以在源码和安装树下的Share子目录中找到。

      还需要注意的是,列表文件不会由CU_automated_run_tests()自动生成。客户端需
      要显式地进行请求调用。

    - void CU_set_output_filename(const char* szFilenameRoot)

      设置结果文件和列表文件的输出文件名。szFilenameRoot被用于替换掉-Results.x
      ml和-Listing.xml里的相应位置。

  - 5.4. Basic Mode
    basic接口同样是一个非交互式接口,结果输出到标准输出stdout。 这个接口支持运
    行单个的suite或test,并允许客户端代码在每次运行时控制显示输出的类型。 该接
    口为想要简化CUnit API调用的客户端提供了最大的灵活度。

    提供了如下所示的公共函数:

    - CU_ErrorCode CU_basic_run_tests(void)

      运行所有已注册的suite中的所有test。 返回值为该次测试运行中出现的第一个错
      误代码。输出类型由当前运行模式控制,可通过CU_basic_set_mode()进行设置。

    - CU_ErrorCode CU_basic_run_suite(CU_pSuite pSuite)

      运行指定的单个suite中的所有test。 返回值为该次测试运行中出现的第一个错误
      代码。输出类型由当前运行模式控制,可通过CU_basic_set_mode()进行设置。

    - CU_ErrorCode CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest)
   
      运行指定的suite中的指定的单个test。 返回值为该次测试运行中出现的第一个错
      误代码。输出类型由当前运行模式控制,可通过CU_basic_set_mode()进行设置。

    - void CU_basic_set_mode(CU_BasicRunMode mode)

      设置basic运行的模式,以控制测试运行时的输出。可选项为:
       
        CU_BRM_NORMAL     打印失败和运行总结
        CU_BRM_SILENT     只打印错误信息
        CU_BRM_VERBOSE    打印运行的所有细节信息

    - CU_BasicRunMode CU_basic_get_mode(void)

      获得当前basic运行模式的代码。

    - void CU_basic_show_failures(CU_pFailureRecord pFailure)
     
      打印所有失败的一个总结到stdout。和运行的模式无关。

  - 5.5. Interactive Console Mode
    console接口是交互式的。客户端需要做的是启动console会话,然后由使用者交互地
    控制测试的运行。这包括选择和运行已注册的suite和test,以及查看测试结果。 要
    启动一个console会话,使用:
     
      void CU_console_run_tests(void)

  - 5.6. Interactive Curses Mode
    curses接口是交互式的。客户端需要做的是启动curses会话,然后由用户交互地控制
    测试的运行。这包括选择和运行已注册的suite和test,以及查看测试结果。 使用该
    接口需要应用程序链接ncurses库。要启动一个curses会话,使用:
     
      void CU_curses_run_tests(void)

  - 5.7. Getting Test Results
    接口直接呈现了测试运行的结果,但是客户端代码可能有时需要直接处理这些结果。
    这些结果包括不同的统计,以及一个失败记录链表,每个记录存有失败的细节信息。
    注意,当一个新的测试运行开始时,或者registry初始化或清理掉时,测试结果都会
    被覆盖掉。

    用于处理测试结果的函数如下:

    - unsigned int CU_get_number_of_suites_run(void)
    - unsigned int CU_get_number_of_suites_failed(void)
    - unsigned int CU_get_number_of_tests_run(void)
    - unsigned int CU_get_number_of_tests_failed(void)
    - unsigned int CU_get_number_of_asserts(void)
    - unsigned int CU_get_number_of_successes(void)
    - unsigned int CU_get_number_of_failures(void)
   
      这些函数能获得在上次运行中运行的或失败了的suite、test和断言的数目。 如果
      suite的初始化函数或者清理函数返回非NULL值,则该suite被认为是失败的。而一
      个test的任一个断言失败,则该test被认为失败。最后三个函数指向不同类型的断
      言。

      为了获得已注册的suite和test的总数,可以调用CU_get_registry()->uiNumberO-
      fSuites和CU_get_registry()->uiNumberOfTests分别获得。

    - const CU_pRunSummary CU_get_run_summary(void)

      一次性获得所有测试结果统计。返回值是指向存有统计信息的结构体的指针。该数
      据类型定义在(自动被包含):

        typedef struct CU_RunSummary
        {
            unsigned int nSuitesRun;
            unsigned int nSUitesFailed;
            unsigned int nTestRun;
            unsigned int nTestsFailed;
            unsigned int nAsserts;
            unsigned int nAssertsFailed;
            unsigned int nFailureRecords;
        } CU_RunSummary;

        typedef CU_Runsummary* CU_pRunSummary;

      返回的指针指向的结构体变量是由框架所拥有,因此使用者不应该去释放或者修改
      它。注意,该指针可能在一次新测试开始之后无效。

    - const CU_pFailureRecord CU_get_failure_list(void)
   
      获得一个链表,记录了上次测试中出现的错误(如果返回NULL表示没有错误发生)。
      返回值的数据类型在(自动被包含)声明。每个
      错误记录包含了错误的位置和性质信息:

        typedef struct CU_FailureRecord
        {
            unsigned int uiLineNumber;
            char*        strFileName;
            char*        strCondition;
            CU_pTest     pTest;
            CU_pSuite    pSuite;

            struct CU_FailureRecord* pNext;
            struct CU_FailureRecord* pPrev;
        } CU_FailureRecord;

        typedef CU_FailureRercord* CU_pFialureRecord;

      返回的指针指向的结构体变量由框架维护,因此使用者不应该释放或者修改这些变
      量。注意,该指针可能在一次新测试开始之后无效。

    - unsigned int CU_get_number_of_failure_records(void)

      获得由CU_get_failure_list()取得的错误记录链表中CU_FailureRecords结构体的
      数目。 注意,该数目可能大于错误断言的数目,因为suite的初始化和清理错误也
      包括在内。

  - 5.8. Deprecated v1 Data Types & Functions
    下面的数据类型和函数是版本2不推荐的。 必须使用USE_DEPRECATED_CUNIT_NAMES宏
    进行编译才能使用。

    --------------------------------------------------------------------------
    Deprecated Name            Equivalent New Name
    automated_run_tests()      CU_automated_run_tests() 和
                               CU_list_tests_to_file()
    set_output_filename()      CU_set_output_filename()
    console_run_tests()        CU_console_run_tests()
    curses_run_tests()         CU_curses_run_tests()

------------------------------------------------------------------------------
- 6. Error Handing
  - 6.1. Synopsis
    #include  (自动被包含)

      typedef enum CU_ErrorCode
      CU_ErrorCode CU_get_error(void);
      const char*  CU_get_error_msg(void);

      typedef enum   CU_ErrorAction
      void           CU_set_error_action(CU_ErrorAction action);
      CU_ErrorAction CU_get_error_action(void);

  - 6.2. CUnit Error Handing
    大多数CUnit函数会设置错误代码来指示框架的错误状态。 一些函数返回该代码,而
    其他会设置该代码却返回其他值。提供了两个函数来检查框架的错误状态:

      CU_ErrorCode CU_get_error(void)
      const char* CU_get_error_msg(void)

    第一个函数返回错误代码本身,然后第二个则返回描述该错误状态的信息字符串。错
    误代码(error code)是CU_ErrorCode类型,为enum类型, 定义在
    中。下面是错误代码的值:

      ------------------------------------------------------------------------
      Error Value            Description
      CUE_SUCCESS            没有错误
      CUE_NOMEMORY           内存分配失败
      CUE_NOREGISTRY         registry没有初始化
      CUE_REGISTRY_EXISTS    没有CU_cleanup_registry()就直接CU_set_registry()
      CUE_NOSUITE            需要的CU_pSuite指针为NULL
      CUE_NO_SUITENAME       需要的CU_Suite的名字没有提供
      CUE_SINIT_FAILED       Suite初始化失败
      CUE_SCLEAN_FAILED      Suite清理失败
      CUE_DUP_SUITE          suite的名字有重复
      CUE_NOTEST             需要的CU_pTest指针为NULL
      CUE_NO_TESTNAME        需要的CU_Test名字为空
      CUE_DUP_TEST           test的名字有重复
      CUE_TEST_NOT_IN_SUITE  test没有被注册到指定的suite中
      CUE_FOPEN_FAILED       打开文件时出现错误
      CUE_FCLOSE_FAILED      关闭文件时出现错误
      CUE_BAD_FILENAME       请求的是一个坏的文件名(例如NULL, 空,不存在等)
      CUE_WRITE_ERROR        在写文件时出现错误

  - 6.3. Behavior Upon Framework Errors
    当错误条件出现时,默认的行为是设置好错误代码,然后继续执行。有时客户端可能
    想要在框架的错误发生时停止本次测试的运行,或者退出测试程序。这是可以由使用
    者进行设置的,通过以下所提供的函数:

      void CU_set_error_action(CU_ErrorAction action)
      CU_ErrorAction CU_get_error_action(void)

    错误动作代码(error action code)是定义在中的枚举类型。有如
    下所示的值:

      ------------------------------------------------------------------------
      Error Value       Description
      CUEA_IGNORE       当错误条件产生时继续运行(默认值)
      CUEA_FAIL         当错误条件产生时停止运行
      CUEA_ABORT        当错误条件产生时程序调用exit()退出

  - 6.4. Deprecated v1 Variables & Functions
    下面的数据类型和函数是版本2不推荐的。 必须使用USE_DEPRECATED_CUNIT_NAMES宏
    进行编译才能使用。

    --------------------------------------------------------------------------
    Deprecated Name            Equivalent New Name
    get_error()                CU_get_error_msg()
    error_code                 没有,使用CU_get_error()

2010年11月17日 星期三

二叉树的一些操作

/*
 * 2010年11月17日 星期三 19时56分53秒
 * zhs
 */
是的,我还活着,很久没有更新博客了(吹吹灰...)。前一阵子一直忙着找工作。 其实
工作定得也听挺早的,大概在10月20号左右的样子就定了下来了。以后要去深圳了。某人
还说定的太早,为什么不再去找找其他更好的呢。其实每个人想法不一样的吧,我自己是
觉得如果遇到合适的就抓住吧,对于自己的决定以后不要去患得患失。而且在找工作时,
每个人或多或少都会有着一种莫名的虚荣心,言必称外企, 仿佛找到500强的公司自己就
牛了,但那时公司牛,不是你牛。找工作有很多变数的,可能真正的牛人时运不济,找的
工作并不算太好,但是人家技术在,终归是会发光的。而我找工作,目标城市很明确,就
是深圳了,而且到学校开宣讲会的深圳公司其实并不多,因此可选项也较少。我甚至有在
校内没找到就去深圳找的后备招了。正是因为这样,所以找工作的过程对我来说还是相对
轻松的,真正用心找的公司大概也就2、3家吧,最大的一笔开销就是打的去长江科技园面
试诚致,10块钱(结果被鄙视了-_-)。 最后定的那家公司,第一天笔试,第二天技术面
试,第三天技术二面,第四天群面....也算是很折腾了,每天晚上等第二天的通知时是比
较情绪焦躁的。不过还好最后结果是好的,就此找工作就搞一段落了。

找玩工作之后在实验室打了一阵子酱油,每天无所是事,倒是每周会去锻炼两次,跑步或
者踢球,意识到了身体还是不行啊~~。后来在周末去了一趟上海玩,找了几个本科的同
学叙叙旧,有工作了的,有在读目前找工作中的。感觉大家都不容易啊,谈及上海就没什
么好词出来。此次去上海有几个收获,一就是去陆家嘴的苹果店朝圣了,第一次去的时候
是同学陪着的,因为后面还有其他安排,所以待的时间比较短,后来周日我一个人逛的时
候再去,就足足待了两个多小时。不得不说苹果的产品真的很赞啊,不过也真的贵啊。我
很想要那个无线键盘啊~~二个收获就是和做市场的同学聊了聊人生(- -||), 通过他的
经历知道了很多为人处世的道理,这个对于我这个宅男来说的非常重要的,当时也对自己
重新做了一次审视。三呢,就是在华师的宿舍阳台上很悠闲地晒了一上午太阳,看着电子
书,周围环境也很好,偶尔ppmm经过,非常惬意。看来我就是宅男的命啊~~
老实说,我对上海的印象不是很好,特别是混乱的交通灯。可能也是我只是走马观花的缘
故吧。

扯远了,这个有一个函数想晒出来,给各位讨论、批评一下。缘由是在AMD的笔试中, 有
这么一题:要求将一个二叉树按照深度打印出来。这个解法我当时是想着用队列来解决。
事实上也是可行的,代码如下:

/*
 * proot 为二叉树的根节点
 * phead 为队列的头结点
 */
void print_node(struct queue_head *phead, BinNode *proot)
{
    BinNode *p;
    BinNode *pnode;
    int ret;

    PushQ(phead, proot);

    while( (p = PopQ(phead, &ret)) != NULL){
        list_node(p);
        if( (pnode = GetLeft(p)) != NULL)
            PushQ(phead, pnode);
        if( (pnode = GetRight(p)) != NULL)
            PushQ(phead, pnode);
    }

    printf("\n");
}

其中,PushQ()以及PopQ()为对队列加入和取出数据结点的函数,GetLeft()和GetRight()
则是分别获得二叉树结点的左子树后右子树的函数。 list_node()则是将参数结点打印出
来的函数。

基本思想是获得一个结点时,先打印出该结点,同时将它的子树加入队列中,这样就使得
该深度的被打印出来,并且下一深度的被加入到队列中。利用队列的FIFO性质,可以很自
然地获得这种按深度排序的次序。

在这个问题的编码过程中,突然有了个想法,这个想法在我当初学习编程时就有了,就是
能不能将树的各个结点按照书上的那样子形象的打印出来,以便自己查看。当时能力是能
力有限,现在虽然不知道能力够不够,不过可以一试。结果一番折腾之后,就写出了以下
的稍微恶心的代码...:

/*
 * 打印出一个二叉树,需要利用队列,struct queue_head为队列结构的头结点
 * phead为二叉树的头结点,并非根节点;w为每个结点打印出来占的宽度
 */
#define SPACE ' '
#define NULL_NUM '*'
void PrintT(BinHead *phead, int w)
{
    int d, dd, depth = 0, n = 0;
    int j, m, count, ret, is_first;
    struct queue_head *pq = NULL;
    BinNode *p = NULL;

    if(phead == NULL){
        my_error(stderr, "phead is NULL\n");
        return;
    }
    if(phead->pnode == NULL){
        printf("*\n");
        return;
    }

    /* 初始化队列 */
    pq = InitQueue(NULL);
    if(pq == NULL){
        fprintf(stderr, "InitQueue failed\n");
        return;
    }
   
    depth = GetTreeDepth(phead);
    count = 0;    //该深度下总的结点个数
    for(n = 0; n <= depth; n++)
        count += (1 << n);
    d = 0;    //当前深度
    dd = depth;
    n = 1 << d;    //当前深度下的结点个数
    is_first = 1;    //是否是该深度下第一个结点
    j = ((1 << dd) - 1);    //首步进值
    m = (j + j + 1) * w;    //次步进值
    j = j * w;

    PushQ(pq, phead->pnode);
    while(1){
        p = PopQ(pq, &ret);
        count--;
        if(count < 0)    //所有节点打印完
            break;
        if(n != 0){    //该层没有打印完
            if(!is_first){
                print_n(SPACE, m);
            } else {
                print_n(SPACE, j);
                is_first = 0;
            }
            p != NULL ? printf("%*d", w, (int)p->data):
                printf("%*c", w, NULL_NUM);
            n--;
            if(n == 0){ //该层已经打印完,设置新一层参数
                printf("\n");
                dd--;
                d++;
                n = 1 << d;
                is_first = 1;
                j = ((1 << dd) - 1);
                m = (j + j + 1) * w;
                j = j * w;
            }
        }

        if(p != NULL){
            PushQ(pq, p->left);
            PushQ(pq, p->right);
        } else {
            PushQ(pq, NULL);
            PushQ(pq, NULL);
        }
    }

    EndQueue(pq);
}

InitQueue()创建一个空的队列。EndQueue()释放掉一个队列。
PushQ()和PopQ()作用如上所说。
GetTreeDepth()获得一棵树的深度。
print_n()重复打印同一个字符。

逻辑上稍微有点混乱和恶心,但是还是能工作的,呵呵。同样要利用队列,以便能够按照
树的深度来遍历每个结点,这个也是受了上个问题的启发。每个空结点也被考虑进去了,
这样就能够对二叉树的深度进行推断,从而能够退出在每个结点之前打印多少空格,结点
与结点之间打印多少空格。而传入每个结点的宽度作为参数,则是为了能够应对每个结点
的数据长度在每种情况下不同。

其实这些编程对我来说是十分新鲜的,如果不是为了找工作的笔试,我是绝没有兴致去做
这些比较“花哨”的东西的,因为平时都是以应用为导向。但是,这种想法毕竟还是不对的
吧,呵呵,但是道理其实说不清。想想高德纳老爷爷写的计算机编程艺术,前面都是数学
的知识,读者也不能够直接从中获益而写出程序出来。

2010年7月27日 星期二

at91rm9200的Linux启动流程分析

/*
 * 这里是uboot-1.1.2版本
 */
- 链接脚本为board/at91rm9200dk/u-boot.lds

- 但是,但是,使用命令arm-linux-objdump -d u-boot出来后的结果看的话,_start
  的地址是为0x21f0,0000的,而不是0x0000,0000,也就是说是在sdram中的。这个又
  是在哪里定下的呢?并且0x21f0,0000和TEXT_BASE的值是一样的。

- cpu/at91rm9200/start.s貌似是第一个执行的程序


- start.s中做的工作依序为:
  - 设置异常向量表。
  - 开始reset部分处理。
  - 进入到svc模式下。
  - 定义了CONFIG_BOOTBINFUNC宏。执行宏条件内的代码。
    - 设置主振荡器使能。并加个循环等待稳定。
    - 设置svc模式下的堆栈sp指针为0x0020,4000。
    - 跳转到lowlevelinit例程中执行存储器初始化。
      - 这个例程中就是将一些控制寄存器进行赋值。
      - 主要是初始化和使能sdram接口。
    - 使能指令cache。
    - 配置cp15,使能小端和异步总线。
  - 拷贝重定位异常表。(有疑问)
  - 执行cpu_init_crit,不过貌似这个是个空函数。
  - 因为定义了CONFIG_BOOTBINFUNC宏,因此执行uboot本身的代码转移拷贝。
    将uboot搬运到TEXT_BASE指定的地址上,即在sdram中了。但是此时并没有进行跳转
    吧,uboot还是运行在flash上。
  - 留出足够的空间给堆栈。
  - 清空bss全局变量。
  - 跳转到_start_armboot,这个时候应该是跳到内存中运行了吧。

- 跳转到start_armboot里之后,经过一系列的初始化之后,进入到main_loop()中,接收
  各种命令行输入的命令,直到输入命令bootm,uboot调用有效内核的执行代码,将控制
  权交由linux内核。

对于u-boot.lds里面_start标记为0x0000,0000,而在编译出来的u-boot文件使用
arm-linux-objdump -d出来的汇编文件之后,_start的地址为0x21f0,0000,究竟这是
怎么回事,以及和TEXT_BASE有什么关系呢?现在还弄不清除装载地址和运行地址有什么
关系,在u-boot.lds中怎么体现出来.


/*
 * uboot-1.3.4版本
 */
cpu/arm920t/start.S为第一执行的程序


最后在start.S跳转到start_armboot。start_armboot在lib_arm/board.c中定义。


/*
 * 顺便也将Linux启动的代码在这边记录讲述一下吧
 * 内核版本为linux-2.6.20
 */

/* from Internet */
linux内核有两种映像,一种是非压缩内核,叫Image,一种是它的压缩版本,为zImage。
根据内核映像的不同,linux内核的启动在开始阶段也有所不同。zImage是Image经过压缩
形成的,所以它的大小比Image小。但为了能使用zImage,必须在它的开头加上解压缩的
代码,将zImage解压缩之后才能执行,因此它的执行速度比Image慢。
对于arm处理器来说,zImage的入口程序为arch/arm/boot/compressed/head.S,依次完成
以下工作:开启MMU和cache,调用decompress_kernel()解压内核,最后通过调用
call_kernel()进入非压缩内核Image的启动。

cpu寄存器中r0必须是0,r1必须是arm linux machine type,r2为kernel parameter list
的物理地址,由bootloader传递给kernel的,这里为u-boot传递的。

arm linux的启动顺序,按照zIamge的顺序依序经过的文件为:
- #/arch/arm/boot/compressed/head.S
- #/arch/arm/kernel/head.S
- #/arch/arm/kernel/head-common.S
- #/init/main.c: start_kernel()。

内核从现在开始就进入了c语言部分,内核启动第二阶段从init/main.c的start_kernel()
函数开始到函数结束。这一阶段对整个系统内存、cache、信号、设备等进行初始化,最
后产生新的内核线程init后,调用cpu_idle()完成内核第二阶段。

at91rm9200dk的宏配置文件位于#/arch/arm/configs/at91rm9200dk_dfconfig。

下面开始漫长的start_kernel()的分析了~~~

- smp_setup_processor_id();
  在本文件中定义,为空函数。
- unwind_init();
  有两个文件有定义,比较可能的那个里是空函数。
- lockdep_init();
  定义在#/kernel/lockdep.c中,关于内核中锁的初始化。(还不太清楚)
- local_irq_disable();
  禁用irq中断。
- early_boot_irqs_off();
  early_init_irq_lock_class();
  初始化irq中断锁相关。
- lock_kernel();
  有宏CONFIG_PREEMPT_BKL定义与否有两个版本,用来取得内核全局锁。
- boot_cpu_init();
  设置启动cpu为active等,用于SMP系统,和现在的at91rm9200无关。
- page_address_init();
  这个在#/mm/higmem.c中定义,另外还有两处为空宏定义,貌似是用于x86的64位高端内
  存,这里应该没有关系,为空。
- printk(KERN_NOTICE);
  printk(linux_banner);
  将系统信息输出到log_buf中。
- setup_arch(&command_line);
  这里转到定义在#/arch/arm/kernel/setup.c里面定义的函数中。
  |
  \_- setup_processor();
      获得处理器相关信息。返回的结构体processor是通过.proc.info.init来找寻的,
      其实体可以在#/arch/arm/mm/proc-arm920.s里面的arm920_processor_functions
      的结构体里获得。结构体中的和at920相关的函数也在该文件里实现了。可以发现
      ,cpu_arm920_proc_init没有做什么事情直接放回,因此函数cpu_proc_init()也
      没有做什么事情。
    - setup_machine(machine_arch_type);
      传入的参数为全局变量,在#/include/asm/mach-types.h中赋给了值为
      MACH_TYPE_AT91RM9200EK或者是setup.c中定义的变量__machine_arch_type。
      函数中通过传入的参数作为机器编号,调用lookup_machine_type查找并返回一个
      struct machine_desc结构体指针。
      这里的machine_desc结构的所搜寻到的实体定义在#/arch/arm/mach-at91rm9200/
      board-dk.c中最后。可以通过了解MACHINE_START宏定义以及#/arch/arm/kernel/
      vmlinux.lds.S中的.arch.info.init段可以了解到。
      这段lookup_machine_type和上面的lookup_processor_type的行为和模式是非常相
      似的。
      struct machine_desc结构体的定义在#/include/asm-arm/mach/arch.h中。其中有
      几个成员变量是在board-dk.c中显示赋值的:
      - unsigned int nr,机器编号,这里为MACH_TYPE_AT91RM9200DK。
      - char *name,为"Atmel AT91RM9200-DK"。
      - unsigned int phys_io, 表示9200系统寄存器的开始物理地址,即物理io的开始
        的物理地址。
      - unsigned int io_pg_offset,byte offset for io page table entry,io页表
        入口项的偏移量。
      - unsigned long boot_params,tagged list,貌似是启动参数。
      - struct sys_timer *timer, 系统滴答定时器结构体指针,实体结构体定义在文
        件#/arch/arm/mach-at91rm9200/at91rm9200_time.c中。
      - void (*map_io)(void),函数指针,说是用于io map的,但是实际的代码实现的
        是初始化处理器时钟、设置LED和初始化串口和控制台。
      - void (*init_irq)(void),初始化中断的函数指针。
      - void (*init_machine)(void),初始化函数,9200代码在这个函数里实现了所有
        外设驱动的加载和相应设备的初始化。
    - 获得启动参数struct tag *tags,否则使用默认值init_tags。这里因为使能了mmu
      因此对于启动参数的物理地址进行了转化为虚拟地址。并将tags转化为tag list。
      然后解析tag list。 初始化init_mm结构体,拷贝存储从bootloader传递的命令行
      参数,并解析命令行参数。
    - paging_init(&meminfo, mdesc);
      建立页表,初始化其他数据结构,比如zero page, bad page and bad page tables.
      里面对存储器进行了初始化。 里面调用了devicemaps_init()函数,该函数建立了
      设备映射。
    - request_standard_resources();
      申请相关资源,获得虚拟地址空间,为内核代码段、数据段等区域申请虚拟地址空
      间。
    - cpu_init();
      打印cpu信息,建立每个cpu的堆栈。
    - 设置中断初始化函数指针、系统滴答时钟结构体和机器初始化函数指针。
      分别为init_arch_irq(),system_timer和init_machine。
    - 设置console,这里配置宏定义了为CONFIG_DUMMPY_CONSOLE,因此使用dummy_con。
   _/
  |
- unwind_setup();
  空函数。
- setup_per_cpu_areas();
  这里为空函数。
- smp_prepare_boot_cpu();
  对于单核的也没有什么影响。
- sched_init();
  在中断之前建立调度器。为每个cpu初始化可运行进程队列,这里是单核。
  初始化0进程。
- preempt_disable();
  禁用preemption,不知道。
- build_all_zonelists();
  建立系统内存页区(zone)链表。
- page_alloc_init();
  这个里面调用的是hotcpu_notifier,热插拔cpu?
- 打印出启动命令行。
- parse_early_param();
  parse_args();
  解析内核参数。
- 可能的话禁中断。
- sort_main_extable();
  可能是排序异常向量表,但是不知道具体怎么用。
- trap_init();
  将异常向量表,stubs和kuser代码拷贝到向量页上,这里为0xffff,0000,异常向量表
  以及其他都定义在#/arch/arm/kernel/entry-armv.S里。
  然后拷贝其他的一些signal return handlers之类的到vector page里。
  然后调整指令cache和mmu的相关域。
- rcu_init();
  初始化读时更新机制,Read-Copy Update RCU。
- init_IRQ();
  初始化中断,这里调用了set_arch()中设置的平台相关的init_arch_irq()。
- pidhash_init();
  进程ID的hash表的初始化。
- init_timers();
  初始化系统定时器相关的全局变量。
- hrtimers_init();
  高分辨率定时器(high resolution kernel timers)的初始化。
- softirq_init();
  初始化软中断环境。
- timekeeping_init();
  初始化时钟源以及通用的时钟相关变量。
- time_init();
  初始化时间相关的环境,里面用到了set_arch()中设置的平台相关的system_timer变量。
- profile_init();
  剖析机制的初始化,就是分配用于profile的空间。
- early_boot_irqs_on();
  置一个标志变量。
- local_irq_enalble();
  使能中断。
- console_init();
  初始化控制台,并将后台的log_buf里面的信息输出。
  这里只是做一些简单的初始化,更复杂的设置在后面做。
- lockdep_info();
  这里貌似没有定义CONFIG_LOCKDEP宏,因此这个为空函数。
- locking_selftest();
  因为没有定义CONFIG_DEGUG_LOCKING_API_SELFTESTS宏,因此该函数为空。
- 定义了CONFIG_BLK_DEV_INITRD
  执行对initrd_start的判断,如果有则置为0。
- vfs_caches_init_early();
  - dcache_init_early();
  - inode_init_early();
  早期的vfs的caches初始化。
- cpuset_init_early();
  没有定义CONFIG_CPUSETS宏,该函数为空。
- mem_init();
  mem_init() marks the free areas in the mem_map and tells us how much memory
  is free. This is done after various parts of the system have claimed their
  memory after the kernel image.
  执行之后不能使用alloc_bootmem()之类的函数了。
- kmem_cache_init();
  执行高速缓存内存管理即slab分配器相关初始化。
- setup_per_cpu_pageset();
  numa_pllicy_init();
  没有定义CONFIG_NUMA宏,因此函数为空。
- late_time_init();
  默认为空,这里at91rm9200也没有对其进行重载。
- calibrate_delay();
  延迟的校准。
- pidmap_init();
  pdimap的初始化。
- pgtable_cache_init();
  空函数。
- prio_tree_init();
  优先级搜索树的初始化。
- anon_vma_init();
  匿名pages的初始化。
- fork_init();
  对fork()系统调用的一些初始化。
- proc_caches_init();
  进程相关的缓冲的初始化,包括signal, file, mm等。
- buffer_init();
  因为没有定义宏CONFIG_BLOCK,因此为空函数。
- unamed_dev_init();
  调用idr_init();
  idr用于small id to pointer translation service。
- key_init();
  没有定义宏CONFIG_KEYS,因此为空函数。
- security_init();
  没有编译啊!!
- vfs_caches_init();
  初始化vfs系统的caches。打印出一条语句。Mount-cache...
- radix_tree_init();
  radix tree的初始化,用于文件系统的caches快的搜索。
- signals_init();
  信号的初始化,就是调用kmem_cache_create给signal部分分配cache。
- page_writeback_init();
  Called early on to tune the page writeback dirty limits.
- proc_root_init();
  对proc文件系统(貌似还有其他的比如sys, net之类的)的初始化。
- cpuset_init();
  也是为空函数。
- taskstats_init_early();
  没有定义CONFIG_TASKSTATS宏,函数为空。
- delayacct_init();
  delayacct is for per-task delay accounting。
  这里初始化相关环境。
- check_bugs();
  这里将其定义为宏,值为函数check_writebuffer_bugs(),检查写缓冲错误。
- acpi_early_init();
  没有定义CONFIG_ACPI宏,函数为空。
- rest_init();
  Do the rest non-__init'ed, we're now alive。
  不再调用__init修饰的函数了。是start_kernel()里的最后一次的函数调用了。
  |
  \_- kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
      调用init函数指针,创建一个内核进程。kernel_thread定义在#/arch/arm/kernel
      /process.c中。而启动的进程函数init定义在#/init/main.c中。
      - init();
      |
      \_- lock_kernel();
          取得全局内核锁。
    - set_cpus_allowed();
      smp_prepare_cpus();
      do_pre_smp_initcalls();
      smp_init();
      sched_init_smp();
      cpuset_init_smp();
      对于单核的系统,没什么大的意义。
    - do_basic_setup();
      在调用这个函数之前,cpu部分的系统初始化已经设置完成了,存储器系统以
      及进程管理部分也ok了。接下来就是设置系统上的各个设备。
      - init_workqueues();
        初始化工作队列。
      - usermodehelper_init();
        初始化用户模式的驱动模块加载器。
      - driver_init();
        初始化驱动模块的各个子系统。
      - sysctl_init();
        初始化linux generic system control interface.
      - do_initcalls();
        这个的解释来自网上:
        /*
        说说subsys_initcall
        在linux内核代码里, 到处充满了subsys_initcall,这个调用是用来干吗的呢?
        有人回答是系统启动时候用来初始化某些系统的,具体怎么初始化的呢,说起来还是有点复杂。
        在linux/init.h里,有这样一段代码:
        #define pure_initcall(fn)  __define_initcall("0",fn,1)
        #define core_initcall(fn)  __define_initcall("1",fn,1)
        #define core_initcall_sync(fn)  __define_initcall("1s",fn,1s)
        #define postcore_initcall(fn)  __define_initcall("2",fn,2)
        #define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
        #define arch_initcall(fn)  __define_initcall("3",fn,3)
        #define arch_initcall_sync(fn)  __define_initcall("3s",fn,3s)
        #define subsys_initcall(fn)  __define_initcall("4",fn,4)
        #define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
        #define fs_initcall(fn)   __define_initcall("5",fn,5)
        #define fs_initcall_sync(fn)  __define_initcall("5s",fn,5s)
        #define rootfs_initcall(fn)  __define_initcall("rootfs",fn,rootfs)
        #define device_initcall(fn)  __define_initcall("6",fn,6)
        #define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
        #define late_initcall(fn)  __define_initcall("7",fn,7)
        #define late_initcall_sync(fn)  __define_initcall("7s",fn,7s)

        而__define_initcall又被定义为
        #define __define_initcall(level,fn,id) \
         static initcall_t __initcall_##fn##id __attribute_used__ \
         __attribute__((__section__(".initcall" level ".init"))) = fn

        so subsys_initcall == __initcall_fn4 它将被链接器放于section  .initcall4.init. 中

        在启动过程中,do_basic_setup--->do_initcalls里有以下代码:
        for (call = __initcall_start; call < __initcall_end; call++) {
        .    .....
          result = (*call)();
        .    ........
         }

        这个__initcall_start是在文件arch/xxx/kernel/vmlinux.lds.S定义的:
        __initcall_start = .;
           INITCALLS
          __initcall_end = .;

        INITCALLS被定义于asm-generic/vmlinux.lds.h:

        #define INITCALLS       \
           *(.initcall0.init)      \
           *(.initcall0s.init)      \
           *(.initcall1.init)      \
           *(.initcall1s.init)      \
           *(.initcall2.init)      \
           *(.initcall2s.init)      \
           *(.initcall3.init)      \
           *(.initcall3s.init)      \
           *(.initcall4.init)      \
           *(.initcall4s.init)      \
           *(.initcall5.init)      \
           *(.initcall5s.init)      \
           *(.initcallrootfs.init)      \
           *(.initcall6.init)      \
           *(.initcall6s.init)      \
           *(.initcall7.init)      \
           *(.initcall7s.init)

        好了,subsys_callinit应该讲清楚来龙去脉了,顺便说一句,在linux/init.h里,还有这样一段代码:
        #define core_initcall(fn)  module_init(fn)
        #define postcore_initcall(fn)  module_init(fn)
        #define arch_initcall(fn)  module_init(fn)
        #define subsys_initcall(fn)  module_init(fn)
        #define fs_initcall(fn)   module_init(fn)
        #define device_initcall(fn)  module_init(fn)
        #define late_initcall(fn)  module_init(fn)
        这是在定义MODULE变量的情况下对subsys_initcall的定义,就是说对于驱动模块,使用subsys_initcall等价于使用module_init
        */
        可知,do_initcalls()调用了大部分设备子系统的初始化函数。包括网络初始化、平台初始化等。
        其中arch_initcall()修饰的初始化函数还包括了和at91rm9200平台初始化相关的一些函数。
        late_initcall()修饰了at91_clock_reset()函数。
        并且通过module_init()添加的外围设备驱动的初始化函数,都在这里被调用了。在#/include/linux/init.h中有定义。
    - check is there is an early userspace init. If yes, let it do all
      the work.
      检查ramdisk_execute_command是否不为空。
    - free_initmem();
      释放初始化过程中使用的内存。
    - unlock_kernel();
      释放内核锁。
    - mark_rodata_ro();
      空函数。
    - numa_default_policy();
      没有定义宏CONFIG_NUMA,为空函数。
    - 执行各种初始化脚本,最后启动/bin/sh,即shell。
       _/
      |
      //以下为kernel_thread(init)之后的代码,应该是并行执行的。
    - numa_default_policy();
      空函数。
    - unlock_kernel();
      释放内核锁,以便init进程获得。
    - preempt_enable_no_resched();
      schedule();
      preempt_disable();
      调度一下,让各个进程能够执行。
    - cpu_idle();
      进入无限内核无限循环。
   _/
  |
- end of start_kernel();