/*
* 如何在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和应用程序的启动的时间没有办法控制。
以上。
鸟之家
一些琐碎的事,一些烦心的事,一些关注的事,that's all。
2011年1月20日 星期四
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来在标签之间来回跳转了。
不过貌似建立标签只能对英文字符会起到高亮效果.
* 如何编写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年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()
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
文件。
函数。自动被CUnit.h包含。
动被CUnit.h包含。
- 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是声明在
它包含的成员包括存储在该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
--------------------------------------------------------------------------
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年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();
* 这里是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();
订阅:
帖子 (Atom)