我的博客列表

2012年6月10日星期日

【c++】指针图解

http://hi.csdn.net/attachment/201105/13/0_1305265216BaSQ.gif

【c++】内存栈分配和堆分配图解





内存栈分配如上图  内存堆分配如下图  堆无序的可用内存







深入分析C++中char * 和char []的区别 [转]

转自:http://www.yuanma.org/data/2007/0305/article_2375.htm

问题引入:在实习过程中发现了一个以前一直默认的错误,同样char *c = "abc"和char c[]="abc",前者改变其内
容程序是会崩溃的,而后者完全正确。
程序演示:
测试环境Devc++
代码
运行结果
2293628 4199056 abc
2293624 2293624 abc
2293620 4199056 abc

#include <iostream>
using namespace std;

main()
{
   char *c1 = "abc";
   char c2[] = "abc";
   char *c3 = ( char* )malloc(3);
   c3 = "abc";
   printf("%d %d %s/n",&c1,c1,c1);
   printf("%d %d %s/n",&c2,c2,c2);
   printf("%d %d %s/n",&c3,c3,c3);
   getchar();
}  


参考资料:
首先要搞清楚编译程序占用的内存的分区形式:
一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)—由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于

数据结构中的栈。
2、堆区(heap)—一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据

结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态

变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统
释放。
4、文字常量区—常量字符串就是放在这里的。程序结束后由系统释放。
5、程序代码区
这是一个前辈写的,非常详细
//main.cpp
  int a=0;    //全局初始化区
  char *p1;   //全局未初始化区
  main()
  {
   int b;栈
   char s[]="abc";   //栈
   char *p2;         //栈
   char *p3="123456";   //123456/0在常量区,p3在栈上。
   static int c=0;   //全局(静态)初始化区
   p1 = (char*)malloc(10);
   p2 = (char*)malloc(20);   //分配得来得10和20字节的区域就在堆区。
   strcpy(p1,"123456");   //123456/0放在常量区,编译器可能会将它与p3所向"123456"优化成一个
地方。
}
二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1=(char*)malloc(10);
在C++中用new运算符
如p2=(char*)malloc(10);
但是注意p1、p2本身是在栈中的。
2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将

该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大
小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正
好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地

址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译
时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间
较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地

址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的
虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4申请效率的比较:
栈:由系统自动分配,速度较快。但程序员是无法控制的。
堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用Virtual Alloc分配内存,他不是在堆,也不是在栈,而是直接在进

程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
2.5堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的

地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变
量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主

函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
2.6存取效率的比较
char s1[]="aaaaaaaaaaaaaaa";
char *s2="bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
voidmain()
{
char a=1;
char c[]="1234567890";
char *p="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10:a=c[1];
004010678A4DF1movcl,byteptr[ebp-0Fh]
0040106A884DFCmovbyteptr[ebp-4],cl
11:a=p[1];
0040106D8B55ECmovedx,dwordptr[ebp-14h]
004010708A4201moval,byteptr[edx+1]
004010738845FCmovbyteptr[ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据

edx读取字符,显然慢了。
2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会

切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

自我总结:
char *c1 = "abc";实际上先是在文字常量区分配了一块内存放"abc",然后在栈上分配一地址给c1并指向

这块地址,然后改变常量"abc"自然会崩溃
然而char c2[] = "abc",实际上abc分配内存的地方和上者并不一样,可以从
4199056
2293624 看出,完全是两块地方,推断4199056处于常量区,而2293624处于栈区

2293628
2293624
2293620 这段输出看出三个指针分配的区域为栈区,而且是从高地址到低地址

2293620 4199056 abc 看出编译器将c3优化指向常量区的"abc"

继续思考:
代码:

输出:
2293628 4199056 abc
2293624 2293624 abc
2293620 4012976 gbc
写成注释那样,后面改动就会崩溃
可见strcpy(c3,"abc");abc是另一块地方分配的,而且可以改变,和上面的参考文档说法有些不一定, 

#include <iostream>
using namespace std;

main()
{
   char *c1 = "abc";
   char c2[] = "abc";
   char *c3 = ( char* )malloc(3);
   //  *c3 = "abc" //error
   strcpy(c3,"abc");
   c3[0] = 'g';
   printf("%d %d %s/n",&c1,c1,c1);
   printf("%d %d %s/n",&c2,c2,c2);
   printf("%d %d %s/n",&c3,c3,c3);
   getchar();
}  

2012年6月9日星期六

有关TinyXML使用的简单总结

这次使用了TinyXML后,觉得这个东西真是不错,于是将使用方法坐下总结来和大家分享。
     该解析库在开源网站(http://sourceforge.net )上有下载,在本Blog也提供下载(下载TinyXML
    TinyXML是一个开源的解析XML的解析库,能够用于C++,能够在WindowsLinux中编译。这个解析库的模型通过解析XML文件,然后在内存中生成DOM模型,从而让我们很方便的遍历这课XML树。
    注:DOM模型即文档对象模型,是将整个文档分成多个元素(如书、章、节、段等),并利用树型结构表示这些元素之间的顺序关系以及嵌套包含关系(理解html语言的读者会很容易理解这种树状模型)。              
     如下是一个XML片段:
    <Persons>
         <Person ID="1">
             <name>周星星</name>
             <age>20</age>
         </Person>
         <Person ID="2">
             <name>白晶晶</name>
             <age>18</age>
         </Person>
     </Persons>
     在TinyXML中,根据XML的各种元素来定义了一些类:
        TiXmlBase:整个TinyXML模型的基类。
               TiXmlAttribute:对应于XML中的元素的属性。
                TiXmlNode:对应于DOM结构中的节点。
                        TiXmlComment:对应于XML中的注释。
                        TiXmlDeclaration:对应于XML中的申明部分,即<?versiong="1.0" ?>。
                        TiXmlDocument:对应于XML的整个文档。
                        TiXmlElement:对应于XML的元素。
                        TiXmlText:对应于XML的文字部分。
                        TiXmlUnknown:对应于XML的未知部分。
        TiXmlHandler:定义了针对XML的一些操作。
    那我们如何使用这些类以及他们的方法来操纵我们的XML呢?请看下面。
    一、读取XML(假设我们的Xml文档中的内容与上面的Xml内容一样)
    //创建一个XML的文档对象
    TiXmlDocument *myDocument = new TiXmlDocument("填上你的Xml文件名");
    myDocument->LoadFile();
    //获得根元素,即Persons。
    TiXmlElement *RootElement = myDocument.RootElement();
    //输出根元素名称,即输出Persons。
    cout << RootElement->Value() << endl;
    //获得第一个Person节点。
    TiXmlElement *FirstPerson = RootElement->FirstChildElement();
    //获得第一个Person的name节点和age节点和ID属性。
    TiXmlElement *NameElement = FirstPerson->FirstChildElement();
    TiXmlElement *AgeElement = NameElement->NextSiblingElement();
    TiXmlAttribute *IDAttribute = FirstPerson->FirstAttribute();
    //输出第一个Person的name内容,即周星星;age内容,即20;ID属性,即1。
    cout << NameElement->FirstChild()->Value << endl;
    cout << AgeElement->FirstChild()->Value << endl;
    cout << IDAttribute->Value() << endl;

   
    看,读取XML是不是很简单阿,和Java的XML解析库非常的相似,就是名字改了一下而已。
    二、生成XML内容
    //创建一个XML的文档对象。
    TiXmlDocument *myDocument = new TiXmlDocument();
    //创建一个根元素并连接。
    TiXmlElement *RootElement = new TiXmlElement("Persons");
    myDocument->LinkEndChild(RootElement);
    //创建一个Person元素并连接。
    TiXmlElement *PersonElement = new TiXmlElement("Person");
    RootElement->LinkEndChild(PersonElement);
    //设置Person元素的属性。
    PersonElement->SetAttribute("ID", "1");
    //创建name元素、age元素并连接。
    TiXmlElement *NameElement = new TiXmlElement("name");
    TiXmlElement *AgeElement = new TiXmlElement("age");
    PersonElement->LinkEndChild(NameElement);
    PersonElement->LinkEndChild(AgeElement);
    //设置name元素和age元素的内容并连接。
    TiXmlText *NameContent = new TiXmlText("周星星");
    TiXmlText *AgeContent = new TiXmlText("20");
    NameElement->LinkEndChild(NameContent);
    AgeElement->LinkEndChild(AgeContent);
    //保存到文件
     myDocument->SaveFile("要保存的xml文件名");
     这样,便创建了一个如下的xml文件:
    <Persons>
         <Person ID="1">
             <name>周星星</name>
             <age>20</age>
         </Person>
    </Persons>

TinyXML中文文档(转)


2009-08-25 9:25
译注:本文是TinyXML 2.5.2版本Document的中文文档,经原作者Lee Thomason同意由hansen翻译,如有误译或者错漏,欢迎指正。
版权:版权归原作者所有,翻译文档版权归本人hansen所有,转载请注明出处。
原文:http://www.grinninglizard.com/tinyxmldocs/index.html

TinyXml 文档

2.5.2

TinyXML

TinyXML是一个简单小巧,可以很容易集成到其它程序中的C++ XML解析器。

它能做些什么

简单地说,TinyXML解析一个XML文档并由此生成一个可读可修改可保存的文档对象模型(DOM)。

XML的意思是“可扩展标记语言“(eXtensible Markup Language)。它允许你创建你自己的文档标记。在为浏览器标记文档方面HTML做得很好,然而XML允许你定义任何文档标记,比如可以为一个组织者 应用程序定义一个描述“to do”列表的文档。 XML拥有一个结构化并且方便的格式,所有为存储应用程序数据而创建的随机文件格式都可以用XML代替,而这一切只需要一个解析器。

最全面正确的说明可以在http://www.w3.org/TR/2004/REC-xml-20040204/找到,但坦白地说,它很晦涩难懂。事实上我喜欢http://skew.org/xml/tutorial上关于XML的介绍。

有不同的方法可以访问和与XML数据进行交互。TinyXML使用文档对象模型(DOM),这意味着XML数据被解析成一个可被浏览和操作的C++对象, 然后它可以被写到磁盘或者另一个输出流中。你也可以把C++对象构造成一个XML文档然后把它写到磁盘或者另一个输出流中。

TinyXML被设计得容易快速上手。它只有两个头文件和四个cpp文件。只需要把它们简单地加到你的项目中就行了。有一个例子文件——xmltest.cpp来引导你该怎么做。

TinyXML以Zlib许可来发布,所以你可以在开源或者商业软件中使用它。许可证更具体的描述在每个源代码文件的顶部可以找到。

TinyXML在保证正确和恰当的XML输出的基础上尝试成为一个灵活的解析器。TinyXML可以在任何合理的C++适用系统上编译。它不依赖于异常或 者运行时类型信息,有没有STL支持都可以编译。TinyXML完全支持UTF-8编码和前64k个字符实体(<i>译注:如果你不明白这句 译文,可能你需要了解一下Unicode编码</i>)。

它无法做些什么

TinyXML不解析不使用DTDs(文档类型定义)或者XSLs(可扩展样式表语言)。有其它解析器(到www.sourceforge.org搜索一 下XML)具有更加全面的特性,但它们也就更大,需要花更长的时间来建立你的项目,有更陡的学习曲线,而且经常有一个更严格的许可协议。如果你是用于浏览 器或者有更复杂的XML需要,那么TinyXML不适合你。

下面的DTD语法在TinyXML里是不做解析的:


<!DOCTYPE Archiv [
<!ELEMENT Comment (#PCDATA)>
]>

因为TinyXML把它看成是一个带着非法嵌入!ELEMENT结点的!DOCTYPE结点。或许这在将来会得到支持。

指南

有耐性些,这是一份能很好地指导你怎么开始的指南,它(非常短小精悍)值得你花时间完整地读上一遍。
TinyXML指南

代码状况

TinyXML是成熟且经过测试的代码,非常健壮。如果你发现了漏洞,请提交漏洞报告到sourcefore网站上 (www.sourceforge.net/projects/tinyxml)。 我们会尽快修正。

有些地方可以让你得到提高,如果你对TinyXML的工作感兴趣的话可以上sourceforge查找一下。

相关项目

你也许会觉得TinyXML很有用!(简介由项目提供)
TinyXPath (http://tinyxpath.sourceforge.net). TinyXPath是一个小巧的XPath语法译码器脚本,用C++写成。
TinyXML++ (http://code.google.com/p/ticpp/). TinyXML++是一个全新的TinyXML接口,使用了许多诸如模板,异常处理和更好的错误处理这些C++强项技术。

特性

使用STL

TinyXML可以被编译成使用或不使用STL。如果使用STL,TinyXML会使用std::string类,而且完全支持 std::istream,std::ostream,operator<<和operator>>。许多API方法都有 ‘const char*’和’const std::string&’两个版本。

如果被编译成不使用STL,则任何STL都不会被包含。所有string类都由TinyXML它自己实现。所有API方法都只提供’const char*’传入参数。

使用运行时定义:

TIXML_USE_STL

来编译成不同的版本。这可以作为参数传给编译器或者在“tinyxml.h”文件的第一行进行设置。

注意:如果在Linux上编译测试代码,设置环境变量TINYXML_USE_STL=YES/NO可以控制STL的编译。而在Windows上,项目文 件提供了STL和非STL两种目标文件。在你的项目中,在tinyxml.h的第一行添加"#define TIXML_USE_STL"应该是最简单的。

UTF-8

TinyXML支持UTF-8,所以可以处理任何语言的XML文件,而且TinyXML也支持“legacy模式”——一种在支持UTF-8之前使用的编码方式,可能最好的解释是“扩展的ascii”。

正常情况下,TinyXML会检测出正确的编码并使用它,然而,通过设置头文件中的TIXML_DEFAULT_ENCODING值,TinyXML可以被强制成总是使用某一种编码。

除非以下情况发生,否则TinyXML会默认使用Legacy模式:
如果文件或者数据流以非标准但普遍的"UTF-8引导字节" (0xef 0xbb 0xbf)开始,TinyXML会以UTF-8的方式来读取它。
如果包含有encoding="UTF-8"的声明被读取,那么TinyXML会以UTF-8的方式来读取它。
如果读取到没有指定编码方式的声明,那么TinyXML会以UTF-8的方式来读取它。
如果包含有encoding=“其它编码”的声明被读取,那么TinyXML会以Legacy模式来读取它。在Legacy模式下,TinyXML会像以前那样工作,虽然已经不是很清楚这种模式是如何工作的了,但旧的内容还得保持能够运行。
除了上面提到的情况,TinyXML会默认运行在Legacy模式下。

如果编码设置错误或者检测到错误会发生什么事呢?TinyXML会尝试跳过这些看似不正确的编码,你可能会得到一些奇怪的结果或者乱码,你可以强制TinyXML使用正确的编码模式。

通过使用LoadFile( TIXML_ENCODING_LEGACY )或者LoadFile( filename, TIXML_ENCODING_LEGACY ), 你可以强制TinyXML使用Legacy模式。你也可以通过设置TIXML_DEFAULT_ENCODING = TIXML_ENCODING_LEGACY来强制一直使用Legacy模式。同样的,你也可以通过相同的方法来强制设置成 TIXML_ENCODING_UTF8。

对于使用英文XML的英语用户来说,UTF-8跟low-ASCII是一样的。你不需要知道UTF-8或者一点也不需要修改你的代码。你可以把UTF-8当作是ASCII的超集。

UTF-8并不是一种双字节格式,但它是一种标准的Unicode编码!TinyXML当前不使用或者直接支持wchar,TCHAR,或者微软的 _UNICODE。"Unicode"这个术语被普遍地认为指的是UTF-16(一种unicode的宽字节编码)是不适当的,这是混淆的来源。

对于“high-ascii”语言来说——几乎所有非英语语言,只要XML被编码成UTF-8, TinyXML就能够处理。说起来可能有点微妙,比较旧的程序和操作系统趋向于使用“默认”或者“传统”的编码方式。许多应用程序(和几乎所有现在的应用 程序)都能够输出UTF-8,但是那些比较旧或者难处理的(或者干脆不能使用的)系统还是只能以默认编码来输出文本。

比如说,日本的系统传统上使用SHIFT-JIS编码,这种情况下TinyXML就无法读取了。但是一个好的文本编辑器可以导入SHIFT-JIS的文本然后保存成UTF-8编码格式的。

Skew.org link上关于转换编码的话题做得很好。

测试文件“utf8test.xml”包含了英文、西班牙文、俄文和简体中文(希望它们都能够被正确地转化)。“utf8test.gif”文件是从IE 上截取的XML文件快照。请注意如果你的系统上没有正确的字体(简体中文或者俄文),那么即使你正确地解析了也看不到与GIF文件上一样的输出。同时要注 意在一个西方编码的控制台上(至少我的Windows机器是这样),Print()或者printf()也无法正确地显示这个文件,这不关TinyXML 的事——这只是操作系统的问题。TinyXML没有丢掉或者损坏数据,只是控制台无法显示UTF-8而已。

实体

TinyXML认得预定义的特殊“字符实体”,即:


&amp; &
&lt; <
&gt; >
&quot; "
&apos; ‘

这些在XML文档读取时都会被辨认出来,并会被转化成等价的UTF-8字符。比如下面的XML文本:


Far &amp; Away

从TiXmlText 对象查询出来时会变成"Far & Away"这样的值,而写回XML流/文件时会以“&amp;”的方式写回。老版本的TinyXML“保留”了字符实体,而在新版本中它们会被转化成字符串。

另外,所有字符都可以用它的Unicode编码数字来指定, "&#xA0;"和"&#160;"都表示不可分的空格字符。

打印

TinyXML有几种不同的方式来打印输出,当然它们各有各的优缺点。
Print( FILE* ):输出到一个标准C流中,包括所有的C文件和标准输出。
"相当漂亮的打印", 但你没法控制打印选项。
输出数据直接写到FILE对象中,所以TinyXML代码没有内存负担。
被Print()和SaveFile()调用。

operator<<:输出到一个c++流中。
与C++ iostreams集成在一起。
在"network printing"模式下输出没有换行符,这对于网络传输和C++对象之间的XML交换有好处,但人很难阅读。
TiXmlPrinter:输出到一个std::string或者内存缓冲区中。
API还不是很简练。
将来会增加打印选项。
在将来的版本中可能有些细微的变化,因为它会被改进和扩展。



设置了TIXML_USE_STL,TinyXML就能支持C++流(operator <<,>>)和C(FILE*)流。但它们之间有些差异你需要知道:

C风格输出:
基于FILE*
用Print()和SaveFile()方法

生成具有很多空格的格式化过的输出,这是为了尽可能让人看得明白。它们非常快,而且能够容忍XML文档中的格式错误。例如一个XML文档包含两个根元素和两个声明仍然能被打印出来。

C风格输入:
基于FILE*
用Parse()和LoadFile()方法

速度快,容错性好。当你不需要C++流时就可以使用它。

C++风格输出:
基于std::ostream
operator<<

生成压缩过的输出,目的是为了便于网络传输而不是为了可读性。它可能有些慢(可能不会),这主要跟你系统上ostream类的实现有关。无法容忍格式错误的XML:此文档只能包含一个根元素。另外根级别的元素无法以流形式输出。

C++风格输入:
基于std::istream
operator>>

从流中读取XML使其可用于网络传输。通过些小技巧,它知道当XML文档读取完毕时,流后面的就一定是其它数据了。TinyXML总假定当它读取到根结点 后XML数据就结束了。换句话说,那些具有不止一个根元素的文档是无法被正确读取的。另外还要注意由于STL的实现和TinyXML的限 制,operator>>会比Parse慢一些。

空格

对是保留还是压缩空格这一问题人们还没达成共识。举个例子,假设‘_’代表一个空格,对于"Hello____world",HTML和某些XML解析器 会解释成"Hello_world",它们压缩掉了一些空格。而有些XML解析器却不会这样,它们会保留空格,于是就是“Hello____world” (记住_表示一个空格)。其它的还建议__Hello___world__应该变成Hello___world 。

这是一个解决得不能让我满意的问题。TinyXML一开始就两种方式都支持。调用TiXmlBase::SetCondenseWhiteSpace( bool )来设置你想要的结果,默认是压缩掉多余的空格。

如果想要改变默认行为,你应该在解析任何XML数据之前调用TiXmlBase::SetCondenseWhiteSpace( bool ) ,而且我不建议设置之后再去改动它。

句柄

想要健壮地读取一个XML文档,检查方法调用后的返回值是否为null是很重要的。一种安全的检错实现可能会产生像这样的代码:


TiXmlElement* root = document.FirstChildElement( "Document" );
if ( root )
{
     TiXmlElement* element = root->FirstChildElement( "Element" );
     if ( element )
     {
         TiXmlElement* child = element->FirstChildElement( "Child" );
         if ( child )
         {
             TiXmlElement* child2 = child->NextSiblingElement( "Child" );
             if ( child2 )
             {
                 // Finally do something useful.

用句柄的话就不会这么冗长了,使用TiXmlHandle类,前面的代码就会变成这样:


TiXmlHandle docHandle( &document );
TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement();
if ( child2 )
{
     // do something useful

这处理起来容易多了。 查阅TiXmlHandle可以得到更多的信息。

行列追踪

对于某些应用程序来说,能够追踪节点和属性在它们源文件中的原始位置是很重要的。另外,知道解析错误在源文件中的发生位置可以节省大量时间。

TinyXML能够追踪所有结点和属性在文本文件中的行列原始位置。TiXmlBase::Row() 和 TiXmlBase::Column() 方法返回结点在源文件中的原始位置。正确的制表符号可以经由TiXmlDocument::SetTabSize() 来配置。

使用与安装

编译与运行xmltest:

提供了一个Linux Makefile和一个Windows Visual C++ .dsw 文件。只需要简单地编译和运行,它就会在你的磁盘上生成demotest.xml文件并在屏幕上输出。它还尝试用不同的方法遍历DOM并打印出结点数。

那个Linux makefile很通用,可以运行在很多系统上——它目前已经在mingw和MacOSX上测试过。你不需要运行 ‘make depend’,因为那些依赖关系已经硬编码在文件里了。

用于VC6的Windows项目文件
tinyxml: tinyxml 库,非STL
tinyxmlSTL: tinyxml 库,STL
tinyXmlTest: 用于测试的应用程序,非STL
tinyXmlTestSTL: 用于测试的应用程序,STL

Makefile

在makefile的顶部你可以设置:

PROFILE,DEBUG,和TINYXML_USE_STL。makefile里有具体描述。

在tinyxml目录输入“make clean”然后“make”,就可以生成可执行的“xmltest”文件。

在某一应用程序中使用:

把tinyxml.cpp,tinyxml.h, tinyxmlerror.cpp, tinyxmlparser.cpp, tinystr.cpp, 和 tinystr.h 添加到你的项目和makefile中。就这么简单,它可以在任何合理的C++适用系统上编译。不需要为TinyXML打开异常或者运行时类型信息支持。

TinyXML怎么工作

举个例子可能是最好的办法,理解一下:


<?xml version="1.0" standalone=no>
<!– Our to do list data –>
<ToDo>
<Item priority="1"> Go to the <bold>Toy store!</bold></Item>
<Item priority="2"> Do bills</Item>
</ToDo>

它称不上是一个To Do列表,但它已经足够了。像下面这样读取并解析这个文件(叫“demo.xml”)你就能创建一个文档:

TiXmlDocument doc( "demo.xml" );
doc.LoadFile();

现在它准备好了,让我们看看其中的某些行和它们怎么与DOM联系起来。


<?xml version="1.0" standalone=no>

第一行是一个声明,它会转化成TiXmlDeclaration 类,同时也是文档结点的第一个子结点。

这是TinyXML唯一能够解析的指令/特殊标签。一般来说指令标签会保存在TiXmlUnknown 以保证在它保存回磁盘时不会丢失这些命令。


<!– Our to do list data –>

这是一个注释,会成为一个TiXmlComment对象。


<ToDo>

"ToDo"标签定义了一个TiXmlElement 对象。它没有任何属性,但包含另外的两个元素。


<Item priority="1">

生成另一个TiXmlElement对象,它是“ToDo”元素的子结点。此元素有一个名为“priority”和值为“1”的属性。


Go to the

TiXmlText ,这是一个叶子结点,它不能再包含其它结点,是"Item" TiXmlElement的子结点。


<bold>

另一个TiXmlElement, 这也是“Item”元素的子结点。

等等

最后,看看整个对象树:


TiXmlDocument "demo.xml"
TiXmlDeclaration "version=’1.0′" "standalone=no"
TiXmlComment " Our to do list data"
TiXmlElement "ToDo"
TiXmlElement "Item" Attribtutes: priority = 1
TiXmlText "Go to the "
TiXmlElement "bold"
TiXmlText "Toy store!"
TiXmlElement "Item" Attributes: priority=2
TiXmlText "Do bills"


文档

本文档由Doxygen使用‘dox’配置文件生成。

许可证

TinyXML基于zlib许可证来发布:

本软件按“现状”提供(即现在你看到的样子),不做任何明确或隐晦的保证。由使用此软件所引起的任何损失都决不可能由作者承担。

只要遵循下面的限制,就允许任何人把这软件用于任何目的,包括商业软件,也允许修改它并自由地重新发布:

1. 决不能虚报软件的来源;你决不能声称是你是软件的第一作者。如果你在某个产品中使用了这个软件,那么在产品文档中加入一个致谢辞我们会很感激,但这并非必要。

2. 修改了源版本就应该清楚地标记出来,决不能虚报说这是原始软件。

3. 本通告不能从源发布版本中移除或做修改。

参考书目

万维网联盟是定制XML的权威标准机构,它的网页上有大量的信息。

权威指南:http://www.w3.org/TR/2004/REC-xml-20040204/

我还要推荐由OReilly出版由Robert Eckstein撰写的"XML Pocket Reference"……这本书囊括了入门所需要的一切。

捐助者,联系人,还有简史

非常感谢给我们建议,漏洞报告,意见和鼓励的所有人。它们很有用,并且使得这个项目变得有趣。特别感谢那些捐助者,是他们让这个网站页面生机勃勃。

有很多人发来漏洞报告和意见,与其在这里一一列出来不如我们试着把它们写到“changes.txt”文件中加以赞扬。

TinyXML的原作者是Lee Thomason

xml dom结构概念图解


                                       实例XML文档内容:
xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="">
  
<book id="bk101">
     
<author>Matthewauthor>
     
<title>XML Developertitle>
     
<genre>Computergenre>
     
<price>44.95price>
     
<publish_date>2000-10-01publish_date>
     
<description>After an inadvertant description>
  
book>
  
<book id="bk109">
     
<author>Kress, Peterauthor>
     
<title>Paradox Losttitle>
     
<genre>Science Fictiongenre>
     
<price>6.95price>
     
<publish_date>2000-11-02publish_date>
     
<description>James Salway discoversdescription>
  
book>
catalog>

对应的概念术语:
 

对应的API接口(暂且这么称呼):
<book id="bk109"> book> 就是 IXMLDOMNode
所有的<book> 就是  IXMLDOMNodeList
authortitleprice 这些则是 IXMLDOMElement 
authortitleprice 的值则是 IXMLDOMText

2012年6月5日星期二

国家公务员-部级-厅级-处级-科级-干部等级排列


国家公务员的级别分为十五级。职务与级别的对应关系是:
(一)国务院总理:一级;
(二)国务院副总理,国务委员:二至三级;
(三)部级正职,省级正职:三至四级;
(四)部级副职,省级副职:四至五级;
(五)司级正职,厅级正职,巡视员:五至七级;
(六)司级副职,厅级副职,助理巡视员:六至八级;
(七)处级正职,县级正职,调研员:七至十级;
(八)处级副职,县级副职,助理调研员:八至十一级;
(九)科级正职,乡级正职,主任科员:九至十二级;
(十)科级副职,乡级副职,副主任科员:九至十三级;
(十一)科员:九至十四级;
(十二)办事员:十至十五级。 股级不属于公务员级别。最低就是办事员,相当于大专学历。本科毕业则是科员待遇,硕士为副科,博士为正科。 国干部级别划分(以政府和军队为例,不含党委、人大、政协)
  国家级:
  国务院总理(一级)
  国务院副总理(二级)
  国务院常委(三级)
  正省级干部(正部级干部):
  国务院各部委正职干部(如教育部部长、国家发改委主任)
  各省、自治区、直辖市政府 正职干部(如江苏省省长、天津市市长)
  部队正军职干部(如江苏省军区司令员、12军军长)
  副省级干部(副部级干部):
  国务院各部委副职干部(如公安部副部长、国家体育总局副局长)
  国务院部委管理的国家局正职干部(如国家文物总局局长)
  各省、自治区、直辖市政府副职干部(如安徽省副省长、重庆市副市长)
  各副省级市政府正职干部(如南京市市长)
  部队副军职干部(如浙江省警备司令部副司令、31军副军长)
  正厅级干部(地市级干部):
  国务院部委各司正职干部(如教育部社会科学研究与思想政治工作司司长)
  各省、自治区、直辖市厅局正职干部(如河北省交通厅厅长,北京市财政局局长)
  各副省级市政府副职干部(如宁波市副市长)
  各地级市政府正职干部(如无锡市市长)
  部队正师职干部(如1军后勤部部长、34师政委)
  注:以上为高级干部副厅级干部:
  国务院部委各司副职干部(如人事部人才流动开发司副司长)
  各省、自治区、直辖市厅局副职干部(如黑龙江省建设厅副厅长、上海市文化局副局长)
  副省级市所属各局及各区县政府正职干部(如南京市教育局局长、江宁区区长)
  各地级市政府副职干部(如苏州市副市长)
  部队副师职干部(如35师副政委、179旅旅长)
  正处级干部(县团级干部):
  国务院部委各司所属处室正职干部(如农业部种植业管理司经济作物处处长)
  各省、自治区、直辖市厅局所属处室正职干部(如江苏省科技厅农村科技处处长)
  副省级市所属各局及各区县政府副职干部(如沈阳市卫生局副局长、浦口区副区长)
  地级市所属各局及各区县政府正职干部(如扬州市劳动局局长、滨海县县长)
  部队正团职干部(如105团政委)
  副处级干部:
  国务院部委各司所属处室副职干部(如农业部兽医局防疫处副处长)
  各省、自治区、直辖市厅局所属处室副职干部(如江苏省教育厅人事处副处长)
  副省级市所属各局处室及区县各局正职干部(如南京市科技局科技成果处处长、玄武区卫生局局长)
  地级市所属
  各局及各区县政府副职干部(如镇江市民政局副局长、张家港市副市长)
  部队副团职干部(如105团参谋长)
  正科级、副科级干部略注:各高等院校、科研院所、医院等事业单位,其各级干部参照政府各级干部待遇,但不具有行政级别,不属于公务员编制政府各级干部如为上一级党委常委,则其行政级别升一级。如张家港市市长为正处级干部
,若为苏州市市委常委,则为副厅级干部按例,高级干部在60或65岁以后往往调往相应级别的人大、政协担任领导职务,俗称“退居二线”正省级干部约略相当于清代之巡抚与布政使,为从二品副省级干部约略相当于清代之按察使,为正
三品正厅级干部约略相当于清代之知府,为从四品副厅级干部约略相当于清代之同知,为正五品正处级干部约略相当于清代之知县,为正七品副处级干部约略相当于清代之县丞,为正八品副处级干部约略相当于清代之县丞,为正八品 。
《中华人民共和国公务员法》第十六条 :
公务员职务分为领导职务和非领导职务。
领导职务层次分为:国家级正职、国家级副职、省部级正职、省部级副职、厅局级正职、厅局级副职、县处级正职、县处级副职、乡科级正职、乡科级副职。
非领导职务层次在厅局级以下设置。
1.国家级正职:
(包括国家主席、中央军委主席、中共中央政治局常委、国务院总理、全国政协主席、全国人大常委会委员长等国家头号人物)
2.国家级副职:
(包括国家副主席、中央军委副主席、中共中央政治局委员、国务委员、全国政协副主席、全国人大常委会副委员长、全国人大常委等)
3.省部级正职:
(包括各个省级行政区的省委书记、省长、省政协主席、省人大常委会主席等、特别行政区行政长官、直辖市市委书记等。以及国家各部部长等)
国务院各部委正职干部(如教育部部长、国家发改委主任)
各省、自治区、直辖市政府正职干部(如江苏省省长、天津市市长)
部队正军职干部(如江苏省军区司令员、12军军长)
4.省部级副职:
(包括各个省级行政区的省委副书记、副省长、省政协副主席、省人大常委会副主席、省人大常委等(行、直辖市市委副书记等。以及国家各部副部长等,还有一些行政级别是正厅但是必须要副布来兼任的,一般认为那个单位是副部单位
如省宣传部、组织部、纪委等必须由省委常委或者省委副书记兼任。
国务院各部委副职干部(如公安部副部长、国家体育总局副局长)
国务院部委管理的国家局正职干部(如国家文物总局局长)
各省、自治区、直辖市政府副职干部(如安徽省副省长、重庆市副市长)
各副省级市政府正职干部(如南京市市长)
部队副军职干部(如浙江省警备司令部副司令、31军副军长)
5.厅局级正职:
以前叫地厅级,主要是地级市(各自治州)市委书记、市长、市人大主席、市政协主席、省级下属单位,如省公安厅厅长、国家各部下属单位,如外交部新闻司司长
国务院部委各司正职干部(如教育部社会科学研究与思想政治工作司司长)
各省、自治区、直辖市厅局正职干部(如河北省交通厅厅长,北京市财政局局长)
各副省级市政府副职干部(如宁波市副市长)
各地级市政府正职干部(如无锡市市长)
部队正师职干部(如1军后勤部部长、34师政委)
注:以上为高级干部
6.厅局级副职:
地级市市委副书记、市委常委、副市长、市人大副主席、市政副协主席、,省级下属单位,如省委宣传部副部长、国家各部下属单位,如外交部新闻司副司长等,还有一些行政级别是正处但是必须要副厅来兼任的,一般认为那个单位是副
厅单位,如市政法委,市宣传部、组织部、纪委等必须由市委常委或者市委副书记兼任。
国务院部委各司副职干部(如人事部人才流动开发司副司长)
各省、自治区、直辖市厅局副职干部(如黑龙江省建设厅副厅长、上海市文化局副局长)
副省级市所属各局及各区县政府正职干部(如南
部队副师职干部(如35师副政委、179旅旅长)
7.县处级正职:
包括县(县级市、区、旗等)委书记、(区)县长、(区)县人大、(区)县政协主席、市各单位局长,如市公安局、司法局等,还有厅级各单位下属,如省公安厅X处,省委办公厅主任等
国务院部委各司所属处室正职干部(如农业部种植业管理司经济作物处处长)
各省、自治区、直辖市厅局所属处室正职干部(如江苏省科技厅农村科技处处长)
副省级市所属各局及各区县政府副职干部(如沈阳市卫生局副局长、浦口区副区长)
地级市所属各局及各区县政府正职干部(如扬州市劳动局局长、滨海县县长)
部队正团职干部(如105团政委)
8.县处级副职:
包括县(县级市、区、旗等)委副书记、县(区)委常委、(区)副县长、(区)县人大、(区)县政协主席、市各单位局长,如市公安局、司法局等,还有厅级各单位下属,如省公安厅X处,省委办公厅主任等。
国务院部委各司所属处室副职干部(如农业部兽医局防疫处副处长)
各省、自治区、直辖市厅局所属处室副职干部(如江苏省教育厅人事处副处长)
副省级市所属各局处室及区县各局正职干部(如南京市科技局科技成果处处长、玄武区卫生局局长)
地级市所属各局及各区县政府副职干部(如镇江市民政局副局长、张家港市副市长)
部队副团职干部(如105团参谋长)
9.乡科级正职:
包括各乡、镇、办事处党委书记、乡长、镇长、办事处主任、以及镇级人大等,还有处级下属,如县公安局长等
10.乡科级副职:
包括各乡、镇、办事处党委副书记、党委委员、副乡长、副镇长、办事处副主任、以及镇级人大副主任等,还有处级下属,如县公安副局长等
乡科级在往下,就是所谓股级,是中国行政当中最小的,一般是乡里边财政所、派出所之类的

转几个比较有哲理的小故事


2012年05月04日 星期五 17:31
一、 广东一伙劫匪在抢劫银行时说了一句至理名言:"通通不许动,钱是国家的,命是自己的!大家都一声不吭躺倒。劫匪望了一眼躺在桌上四肢朝天的出纳小姐, 说:"请你躺文明些!这是抢劫,又不是强奸!劫匪回去后,其中一个新来的硕士劫匪说,老大,我们赶快数一下抢了多少,那老劫匪(小学文化)说,你傻啊?这 么多,你要数到什么时候啊?今天晚上看新闻不就知道了吗。这就叫工作经验,这年头工作经验比学历更重要
呵呵,这个故事还有续集的:
劫匪走后,行长说,赶紧报案!主任刚要走,行长急忙说:“
等等!把我们上次私自挪用的那五百万也加上去!”主任说:“要是劫匪每个月都来抢一回就好了”。
----这就叫工作经验,这年头工作经验比学历更重要
结果,第二天新闻联播报道银行被抢了一亿,劫匪数来数去只有两千万。
老大骂道:“妈的,老子拼了一条命才抢了两千万,银行行长动动手指头就赚了八千万,看来这年头还是要读书啊!
2
四、3名男子去提亲,女方家长:请自我介绍。
A说:我有一千万;
B说:我有一栋豪宅,价值两千万;
家长很满意。就问C,你家有什么?
C答:我什么都没有,只有一个孩子,在你女儿肚子里。
AB无语,走了。
【此事的重要启示:核心竞争力不是钱和房子,是在关键的岗位有自已的人。】
三、某日,女秘书神色凝重地说:王总,我怀孕了。 王继续低头看文件,然后淡淡一笑:我早结扎了。 女秘书楞了一会媚笑道:我和您开玩笑呢! 王抬起头看了她一眼,喝了口茶说:我也是。
【此事的重要启示:在江湖上混的人,遇事不要慌,先让子弹飞一会。】
4、女浴室起火,里面人乱作一团,赤身往外跑,只见大街上白花花一大群,一老者大喊“快捂住”,众裸女突然醒悟,但身上要紧部位有三处,手忙脚乱捂不过来,不知所措。这时老者又大喊:“捂脸就行,下面都一样!”
【此事的重要启示:在特殊情况下抓工作不可能面面俱到,要抓住重点。】
五、7年前他为了出国抛弃了未婚妻,现在事业有成,他开始愧疚,听说她过的不好,他想去弥补。
他去到了她的鱼摊,她在刮鳞,边上蹲着一个男娃,模样有几分像他,他心里一震。
她突然停下,指着隔壁摊的男人:你咋还不给孩子做饭呢!他松了口气,转身走了。
她递根烟给隔壁摊男人:刚才不好意思
【 不愿意做你的累赘 不愿意让你带着愧疚过一身 】
六、父亲在洗车, 儿子拿起小石头在车门上划起来.
父亲见此大怒, 拿起扳手就打了下去,
后来儿子被送到医院, 证实手指骨折.
面对父亲, 儿子轻声说道: 爸爸, 手指会好的, 不要担心了.
父亲内心无比自责. 一怒之下,
冲回去要把自己的汽车给砸了. 他看见儿子划的痕迹:
爸爸, 我爱你.
【 有些事是否在看清之后再做决定更好呢 】
七、草原上有对狮子母子。小狮子问母狮子:“妈,幸福在哪里?”母狮子说:“幸福就在你的尾巴上。”
于是小狮子不断追着尾巴跑,但始终咬不到。母狮子笑道:“傻瓜!幸福不是这样得到的!只要你昂首向前走,幸福就会一直跟随着你!”。
【 刻意的追寻 不如勇敢的往前走 成功又有多远呢 】
八、有一个盲了的女孩,她一无所有,只剩下她男朋友,男朋友问她:“如果你眼睛好了,能和我结婚吗?”女孩答应了。
很快,女孩可以移植新视角膜,也很快回复视力,但她发现她男朋友也是盲的。男朋友向她求婚,女孩拒绝了,最后男孩只说了一句话:“take care of my eyes.”
【 照顾好我的眼睛 】
九、他向她求婚时,只说了三个字:相信我;
她为他生下第一个女儿的时候,他对她说:辛苦了;
女儿出嫁那天,他搂着她的肩说:还有我;
他收到她病危的那天,重复的对她说:我在这;
她要走的那一刻,他亲吻她的额头轻声说:你等我。
这一生,他没有对他说过一次“我爱你”,但爱,从未离开过。
【 这个世界有不会表白的人 但谁说他们一定缺少爱 】
十、儿子养不起年迈的母亲,决定把她背上山丢下去。傍晚,儿子说要背母亲上山走走,母亲吃力地爬上他的背。
他一路都在想爬高点再丢下她,当看到母亲在他背上偷偷往路上洒豆子,他很生气地问:“你洒豆子干什么?”结果母亲的回答让他泪流满面:“傻儿子,我怕你等会儿一个人下山会迷路。”
【 父母的爱伴随着你一生 哪怕他们已经年迈 】
这十个故事是我转载的,拜读完以后我感触良多,转来跟朋友们共享。故事也许不只是故事,内中蕴含着大道理,只要我们善于感悟,就一定会受益无穷的

2012年6月4日星期一

va_list、va_start、va_arg、va_end的原理与使用


  1. 概述
    由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦;即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,有些人采用指针参数来解决问题.下面就c语言中处理不定参数数目的问题进行讨论.
  2. 定义
    大家先看几宏.
    在VC++6.0的include有一个stdarg.h头文件,有如下几个宏定义:
    #define _INTSIZEOF(n)   ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
    #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )           //第一个可选参数地址
    #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址
    #define va_end(ap)    ( ap = (va_list)0 )                            // 将指针置为无效
    如果对以上几个宏定义不理解,可以略过,接这看后面的内容.
  3. 参数在堆栈中分布,位置
    在进程中,堆栈地址是从高到低分配的.当执行一个函数的时候,将参数列表入栈,压入堆栈的高地 址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减,一些黑客就是在堆栈中修改函数返回地址,执行自己的代码来达 到执行自己插入的代码段的目的.
    总之,函数在堆栈中的分布情况是:地址从高到低,依次是:函数参数列表,函数返回地址,函数执行代码段.
    堆栈中,各个函数的分布情况是倒序的.即最后一个参数在列表中地址最高部分,第一个参数在列表地址的最低部分.参数在堆栈中的分布情况如下:
    最后一个参数
    倒数第二个参数
    ...
    第一个参数
    函数返回地址
    函数代码段
  4. 示例代码
    void arg_test(int i, ...);
    int main(int argc,char *argv[])
    {
     int int_size = _INTSIZEOF(int);
     printf("int_size=%d\n", int_size);
     arg_test(0, 4);

     arg_cnt(4,1,2,3,4);
     return 0;
    }
    void arg_test(int i, ...)
    {
     int j=0;
     va_list arg_ptr;

     va_start(arg_ptr, i);
     printf("&i = %p\n", &i);//打印参数i在堆栈中的地址
     printf("arg_ptr = %p\n", arg_ptr);
     //打印va_start之后arg_ptr地址,
     //应该比参数i的地址高sizeof(int)个字节
     //这时arg_ptr指向下一个参数的地址

     j=*((int *)arg_ptr);
     printf("%d %d\n", i, j);
     j=va_arg(arg_ptr, int);
     printf("arg_ptr = %p\n", arg_ptr);
     //打印va_arg后arg_ptr的地址
     //应该比调用va_arg前高sizeof(int)个字节
     //这时arg_ptr指向下一个参数的地址
     va_end(arg_ptr);
     printf("%d %d\n", i, j);
    }

  5. 代码说明:
    int int_size = _INTSIZEOF(int);得到int类型所占字节数
     va_start(arg_ptr, i); 得到第一个可变参数地址, 根据定义(va_list)&v得到起始参数的地址, 再加上_INTSIZEOF(v) ,就是其实参数下一个参数的地址,即第一个可变参数地址.
    j=va_arg(arg_ptr, int); 得到第一个参参数的值,并且arg_ptr指针上移一个_INTSIZEOF(int),即指向下一个可变参数的地址.
    va_end(arg_ptr);置空arg_ptr,即arg_ptr=0;
    总结:读取可变参数的过程其实就是堆栈中,使用指针,遍历堆栈段中的参数列表,从低地址到高地址一个一个地把参数内容读出来的过程.
  6. 在编程中应该注意的问题和解决办法
    虽然可以通过在堆栈中遍历参数列表来读出所有的可变参数,但是由于不知道可变参数有多少个,什么时候应该结束遍历,如果在堆栈中遍历太多,那么很可能读取一些无效的数据.
    解决办法:a.可以在第一个起始参数中指定参数个数,那么就可以在循环还中读取所有的可变参数;b.定义一个结束标记,在调用函数的时候,在最后一个参数中传递这个标记,这样在遍历可变参数的时候,可以根据这个标记结束可变参数的遍历;
    下面是一段示例代码:
    //第一个参数定义可选参数个数,用于循环取初参数内容
    void arg_cnt(int cnt, ...);
    int main(int argc,char *argv[])
    {
     int int_size = _INTSIZEOF(int);
     printf("int_size=%d\n", int_size);
     arg_cnt(4,1,2,3,4);
     return 0;
    }
    void arg_cnt(int cnt, ...)
    {
     int value=0;
     int i=0;
     int arg_cnt=cnt;
     va_list arg_ptr;
     va_start(arg_ptr, cnt);
     for(i = 0; i < cnt; i++)
     {
      value = va_arg(arg_ptr,int);
      printf("value%d=%d\n", i+1, value);
     }
    }

    虽 然可以根据上面两个办法解决读取参数个数的问题,但是如果参数类型都是不定的,该怎么办,如果不知道参数的类型,即使读到了参数也没有办法进行处理.解决 办法:可以自定义一些可能出现的参数类型,这样在可变参数列表中,可以可变参数列表中的那类型,然后根据类型,读取可变参数值,并进行准确地转换.传递参 数的时候可以这样传递:参数数目,可变参数类型1,可变参数值1,可变参数类型2,可变参数值2,....
    这里给出一个完整的例子:
    #include <stdio.h>
    #include <stdarg.h>
    const int INT_TYPE  = 100000;
    const int STR_TYPE  = 100001;
    const int CHAR_TYPE  = 100002;
    const int LONG_TYPE  = 100003;
    const int FLOAT_TYPE = 100004;
    const int DOUBLE_TYPE = 100005;
    //第一个参数定义可选参数个数,用于循环取初参数内容
    //可变参数采用arg_type,arg_value...的形式传递,以处理不同的可变参数类型
    void arg_type(int cnt, ...);
    //第一个参数定义可选参数个数,用于循环取初参数内容
    void arg_cnt(int cnt, ...);
    //测试va_start,va_arg的使用方法,函数参数在堆栈中的地址分布情况
    void arg_test(int i, ...);
    int main(int argc,char *argv[])
    {
     int int_size = _INTSIZEOF(int);
     printf("int_size=%d\n", int_size);
     arg_test(0, 4);

     arg_cnt(4,1,2,3,4);
     arg_type(2, INT_TYPE, 222, STR_TYPE, "ok,hello world!");
     return 0;
    }
void arg_test(int i, ...)
{
 int j=0;
 va_list arg_ptr;

 va_start(arg_ptr, i);
 printf("&i = %p\n", &i);//打印参数i在堆栈中的地址
 printf("arg_ptr = %p\n", arg_ptr);
 //打印va_start之后arg_ptr地址,
 //应该比参数i的地址高sizeof(int)个字节
 //这时arg_ptr指向下一个参数的地址

 j=*((int *)arg_ptr);
 printf("%d %d\n", i, j);
 j=va_arg(arg_ptr, int);
 printf("arg_ptr = %p\n", arg_ptr);
 //打印va_arg后arg_ptr的地址
 //应该比调用va_arg前高sizeof(int)个字节
 //这时arg_ptr指向下一个参数的地址
 va_end(arg_ptr);
 printf("%d %d\n", i, j);
}
void arg_cnt(int cnt, ...)
{
 int value=0;
 int i=0;
 int arg_cnt=cnt;
 va_list arg_ptr;
 va_start(arg_ptr, cnt);
 for(i = 0; i < cnt; i++)
 {
  value = va_arg(arg_ptr,int);
  printf("value%d=%d\n", i+1, value);
 }
}
void arg_type(int cnt, ...)
{
 int arg_type = 0;
 int int_value=0;
 int i=0;
 int arg_cnt=cnt;
 char *str_value = NULL;
 va_list arg_ptr;
 va_start(arg_ptr, cnt);
 for(i = 0; i < cnt; i++)
 {
  arg_type = va_arg(arg_ptr,int);
  switch(arg_type)
  {
  case INT_TYPE:
   int_value = va_arg(arg_ptr,int);
   printf("value%d=%d\n", i+1, int_value);
   break;
  case STR_TYPE:
   str_value = va_arg(arg_ptr,char*);
   printf("value%d=%d\n", i+1, str_value);
   break;
  default:
   break;
  }
 }
}

2012年6月3日星期日

职业生涯中12个最致命的想法 (转)

1、总觉得自己不够好
  这种人虽然聪明、有历练,但是一旦被提拔,反而毫无自信,觉得自己不胜任。此外,他没有往上爬的野心,总觉得自己的职位已经太高,或许低一两级可能还比较适合。
  这种自我破坏与自我限制的行为,有时候是无意识的。但是,身为企业中、高级主管,这种无意识的行为却会让企业付出很大的代价。
  2、非黑即白看世界
  这种人眼中的世界非黑即白。他们相信,一切事物都应该像有标准答案的考试一样,客观地评定优劣。他们总是觉得自己在捍卫信念、坚持原则。但是,这些原则,别人可能完全不以为意。结果,这种人总是孤军奋战,常打败仗。
  3、无止境地追求卓越
  这种人要求自己是英雄,也严格要求别人达到他的水准。在工作上,他们要求自己与部属“更多、更快、更好”。结果,部属被拖得精疲力竭,纷纷“跳船求生”,留下来的人则更累。结果离职率节节升高,造成企业的负担。
  这种人适合独立工作,如果当主管,必须雇用一位专门人员,当他对部属要求太多时,大胆不讳地提醒他。
  4、无条件地回避冲突
  这种人一般会不惜一切代价,避免冲突。其实,不同意见与冲突,反而可以激发活力与创造力。一位本来应当为部属据理力争的主管,为了回避冲 突,可能被部属或其他部门看扁。为了维持和平,他们压抑感情,结果,他们严重缺乏面对冲突、解决冲突的能力。到最后,这种解决冲突的无能,蔓延到婚姻、亲 子、手足与友谊关系。
  5、强横压制反对者
  他们言行强硬,毫不留情,就像一部推土机,凡阻挡去路者,一律铲平,因为横冲直撞,攻击性过强,不懂得绕道的技巧,结果可能伤害到自己的事业生涯。
  6、天生喜欢引人侧目
  这种人为了某种理想,奋斗不懈。在稳定的社会或企业中,他们总是很快表明立场,觉得妥协就是屈辱,如果没有人注意他,他们会变本加厉,直到有人注意为止。
  7、过度自信,急于成功
  这种人过度自信,急于成功。他们不切实际,找工作时,不是龙头企业则免谈,否则就自立门户。进入大企业工作,他们大多自告奋勇,要求负责超过自己能力的工作。结果任务未达成,仍不会停止挥棒,反而想用更高的功绩来弥补之前的承诺,结果成了常败将军。
  这种人大多是心理上缺乏肯定,必须找出心理根源,才能停止不断想挥棒的行为。除此之外,也必须强制自己“不作为,不行动”。
  8、被困难绳捆索绑
  他们是典型的悲观论者,喜欢杞人忧天。采取行动之前,他会想像一切负面的结果,感到焦虑不安。这种人担任主管,会遇事拖延,按兵不动。因为太在意羞愧感,甚至担心部属会出状况,让他难堪。
  这种人必须训练自己,在考虑任何事情时,必须控制心中的恐惧,让自己变得更有行动力。职场中最有效地生存法!
  9、疏于换位思考
  这种人完全不了解人性,很难了解恐惧、爱、愤怒、贪婪及怜悯等情绪。他们在通电话时,通常连招呼都不打,直接切入正题,缺乏将心比心的能力,他们想把情绪因素排除在决策过程之外。
  这种人必须为自己做一次“情绪稽查”,了解自己对哪些感觉较敏感;问朋友或同事,是否发现你忽略别人的感受,搜集自己行为模式的实际案例,重新演练整个情境,改变行为。
  10、不懂装懂
  工作中那种不懂装懂的人,喜欢说:“这些工作真无聊。”但他们内心的真正感觉是:“我做不好任何工作。”他们希望年纪轻轻就功成名就,但是 他们又不喜欢学习、求助或征询意见,因为这样会被人以为他们“不胜任”,所以他们只好装懂。而且,他们要求完美却又严重拖延,导致工作严重瘫痪。
  11、管不住嘴巴
  有的人往往不知道,有些话题可以公开交谈,而有些内容是只能私下说。这些人通常都是好人,没有心机,但在讲究组织层级的企业,这种管不住嘴巴的人,只会断送事业生涯。
  他们必须随时为自己竖立警告标示,提醒自己什么可以说,什么不能说。什么样的MM最好找工作……
  12、我的路到底对不对?
  这种人总是觉得自己失去了职业生涯的方向。“我走的路到底对不对?”他们总是这样怀疑。他们觉得自己的角色可有可无,跟不上别人,也没有归属感。

u-boot 详细介绍(转)

Bootloader
对于计算机系统来说,从开机上电到操作系统启动需要一个引导过程。嵌入式Linux系统同样离不开引导程序,这个引导程序就叫作Bootloader。
6.1.1  Bootloader介绍
Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射表,从而建立适当的系统软硬件环境,为最终调用操作系统内核做好准备。
对于嵌入式系统,Bootloader是基于特定硬件平台来实现的。因此,几乎不可能为所有的嵌入式系统建立一个通用的Bootloader, 不同的处理器架构都有不同的Bootloader。Bootloader不但依赖于CPU的体系结构,而且依赖于嵌入式系统板级设备的配置。对于2块不同 的嵌入式板而言,即使它们使用同一种处理器,要想让运行在一块板子上的Bootloader程序也能运行在另一块板子上,一般也都需要修改 Bootloader的源程序。
反过来,大部分Bootloader仍然具有很多共性,某些Bootloader也能够支持多种体系结构的嵌入式系统。例如,U-Boot就同 时支持PowerPC、ARM、MIPS和X86等体系结构,支持的板子有上百种。通常,它们都能够自动从存储介质上启动,都能够引导操作系统启动,并且 大部分都可以支持串口和以太网接口。
本章将对各种Bootloader总结分类,分析它们的共同特点。以U-Boot为例,详细讨论Bootloader的设计与实现。
6.1.2  Bootloader的启动
Linux系统是通过Bootloader引导启动的。一上电,就要执行Bootloader来初始化系统。可以通过第4章的Linux启动过程框图回顾一下。
系统加电或复位后,所有CPU都会从某个地址开始执行,这是由处理器设计决定的。比如,X86的复位向量在高地址端,ARM处理器在复位时从地 址0x00000000取第一条指令。嵌入式系统的开发板都要把板上ROM或Flash映射到这个地址。因此,必须把Bootloader程序存储在相应 的Flash位置。系统加电后,CPU将首先执行它。
主机和目标机之间一般有串口可以连接,Bootloader软件通常会通过串口来输入输出。例如:输出出错或者执行结果信息到串口终端,从串口终端读取用户控制命令等。
Bootloader启动过程通常是多阶段的,这样既能提供复杂的功能,又有很好的可移植性。例如:从Flash启动的Bootloader多数是两阶段的启动过程。从后面U-Boot的内容可以详细分析这个特性。
大多数Bootloader都包含2种不同的操作模式:本地加载模式和远程下载模式。这2种操作模式的区别仅对于开发人员才有意义,也就是不同启动方式的使用。从最终用户的角度看,Bootloader的作用就是用来加载操作系统,而并不存在所谓的本地加载模式与远程下载模式的区别。
因为Bootloader的主要功能是引导操作系统启动,所以我们详细讨论一下各种启动方式的特点。
1.网络启动方式
这种方式开发板不需要配置较大的存储介质,跟无盘工作站有点类似。但是使用这种启动方式之前,需要把Bootloader安装到板上的 EPROM或者Flash中。Bootloader通过以太网接口远程下载Linux内核映像或者文件系统。第4章介绍的交叉开发环境就是以网络启动方式 建立的。这种方式对于嵌入式系统开发来说非常重要。
使用这种方式也有前提条件,就是目标板有串口、以太网接口或者其他连接方式。串口一般可以作为控制台,同时可以用来下载内核影像和 RAMDISK文件系统。串口通信传输速率过低,不适合用来挂接NFS文件系统。所以以太网接口成为通用的互连设备,一般的开发板都可以配置10M以太网 接口。
对于PDA等手持设备来说,以太网的RJ-45接口显得大了些,而USB接口,特别是USB的迷你接口,尺寸非常小。对于开发的嵌入式系统,可以把USB接口虚拟成以太网接口来通讯。这种方式在开发主机和开发板两端都需要驱动程序。
另外,还要在服务器上配置启动相关网络服务。Bootloader下载文件一般都使用TFTP网络协议,还可以通过DHCP的方式动态配置IP地址。
DHCP/BOOTP服务为Bootloader分配IP地址,配置网络参数,然后才能够支持网络传输功能。如果Bootloader可以直接设置网络参数,就可以不使用DHCP。
TFTP服务为Bootloader客户端提供文件下载功能,把内核映像和其他文件放在/tftpboot目录下。这样Bootloader可以通过简单的TFTP协议远程下载内核映像到内存。如图6.1所示。
0 _fcksavedurl=">
图6.1  网络启动示意图
大部分引导程序都能够支持网络启动方式。例如:BIOS的PXE(Preboot Execution Environment)功能就是网络启动方式;U-Boot也支持网络启动功能。
2.磁盘启动方式
传统的Linux系统运行在台式机或者服务器上,这些计算机一般都使用BIOS引导,并且使用磁盘作为存储介质。如果进入BIOS设置菜单,可 以探测处理器、内存、硬盘等设备,可以设置BIOS从软盘、光盘或者某块硬盘启动。也就是说,BIOS并不直接引导操作系统。那么在硬盘的主引导区,还需 要一个Bootloader。这个Bootloader可以从磁盘文件系统中把操作系统引导起来。
Linux传统上是通过LILO(LInux LOader)引导的,后来又出现了GNU的软件GRUB(GRand Unified Bootloader)。这2种Bootloader广泛应用在X86的Linux系统上。你的开发主机可能就使用了其中一种,熟悉它们有助于配置多种系 统引导功能。
LILO软件工程是由Werner Almesberger创建,专门为引导Linux开发的。现在LILO的维护者是John Coffman,最新版本下载站点:http://lilo.go.dyndns.org。LILO有详细的文档,例如LILO套件中附带使用手册和参考手册。此外,还可以在LDP的“LILO mini-HOWTO”中找到LILO的使用指南。
GRUB是GNU计划的主要bootloader。GRUB最初是由Erich Boleyn为GNU Mach操作系统撰写的引导程序。后来有Gordon Matzigkeit和Okuji Yoshinori接替Erich的工作,继续维护和开发GRUB。GRUB的网站http://www.gnu.org/software/grub /上有对套件使用的说明文件,叫作《GRUB manual》。GRUB能够使用TFTP和BOOTP或者DHCP通过网络启动,这种功能对于系统开发过程很有用。
除了传统的Linux系统上的引导程序以外,还有其他一些引导程序,也可以支持磁盘引导启动。例如:LoadLin可以从DOS下启动Linux;还有ROLO、LinuxBIOS,U-Boot也支持这种功能。
3.Flash启动方式
大多数嵌入式系统上都使用Flash存储介质。Flash有很多类型,包括NOR Flash、NAND Flash和其他半导体盘。其中,NOR Flash(也就是线性Flash)使用最为普遍。
NOR Flash可以支持随机访问,所以代码是可以直接在Flash上执行的。Bootloader一般是存储在Flash芯片上的。另外,Linux内核映像 和RAMDISK也可以存储在Flash上。通常需要把Flash分区使用,每个区的大小应该是Flash擦除块大小的整数倍。图6.2是 Bootloader和内核映像以及文件系统的分区表。
0 _fcksavedurl=" border=0>
图6.2  Flash存储示意图
Bootloader一般放在Flash的底端或者顶端,这要根据处理器的复位向量设置。要使Bootloader的入口位于处理器上电执行第一条指令的位置。
接下来分配参数区,这里可以作为Bootloader的参数保存区域。
再下来内核映像区。Bootloader引导Linux内核,就是要从这个地方把内核映像解压到RAM中去,然后跳转到内核映像入口执行。
然后是文件系统区。如果使用Ramdisk文件系统,则需要Bootloader把它解压到RAM中。如果使用JFFS2文件系统,将直接挂接为根文件系统。这两种文件系统将在第12章详细讲解。
最后还可以分出一些数据区,这要根据实际需要和Flash大小来考虑了。
这些分区是开发者定义的,Bootloader一般直接读写对应的偏移地址。到了Linux内核空间,可以配置成MTD设备来访问Flash分 区。但是,有的Bootloader也支持分区的功能,例如:Redboot可以创建Flash分区表,并且内核MTD驱动可以解析出redboot的分 区表。
除了NOR Flash,还有NAND Flash、Compact Flash、DiskOnChip等。这些Flash具有芯片价格低,存储容量大的特点。但是这些芯片一般通过专用控制器的I/O方式来访问,不能随机访 问,因此引导方式跟NOR Flash也不同。在这些芯片上,需要配置专用的引导程序。通常,这种引导程序起始的一段代码就把整个引导程序复制到RAM中运行,从而实现自举启动,这 跟从磁盘上启动有些相似。
6.1.3  Bootloader的种类
嵌入式系统世界已经有各种各样的Bootloader,种类划分也有多种方式。除了按照处理器体系结构不同划分以外,还有功能复杂程度的不同。
首先区分一下“Bootloader”和“Monitor”的概念。严格来说,“Bootloader”只是引导设备并且执行主程序的固件;而 “Monitor”还提供了更多的命令行接口,可以进行调试、读写内存、烧写Flash、配置环境变量等。“Monitor”在嵌入式系统开发过程中可以 提供很好的调试功能,开发完成以后,就完全设置成了一个“Bootloader”。所以,习惯上大家把它们统称为Bootloader。
表6.1列出了Linux的开放源码引导程序及其支持的体系结构。表中给出了X86 ARM PowerPC体系结构的常用引导程序,并且注明了每一种引导程序是不是“Monitor”。
表6.1                                                   开放源码的Linux 引导程序
 
Bootloader
Monitor
描    述
x86
ARM
PowerPC
LILO
Linux磁盘引导程序
GRUB
GNU的LILO替代程序
Loadlin
从DOS引导Linux
ROLO
从ROM引导Linux而不需要BIOS
Etherboot
通过以太网卡启动Linux系统的固件
LinuxBIOS
完全替代BUIS的Linux引导程序
BLOB
LART等硬件平台的引导程序
U-boot
通用引导程序
RedBoot
基于eCos的引导程序
 
对于每种体系结构,都有一系列开放源码Bootloader可以选用。
(1)X86
X86的工作站和服务器上一般使用LILO和GRUB。LILO是Linux发行版主流的Bootloader。不过Redhat Linux发行版已经使用了GRUB,GRUB比LILO有更有好的显示界面,使用配置也更加灵活方便。
在某些X86嵌入式单板机或者特殊设备上,会采用其他Bootloader,例如:ROLO。这些Bootloader可以取代BIOS的功 能,能够从FLASH中直接引导Linux启动。现在ROLO支持的开发板已经并入U-Boot,所以U-Boot也可以支持X86平台。
(2)ARM
ARM处理器的芯片商很多,所以每种芯片的开发板都有自己的Bootloader。结果ARM bootloader也变得多种多样。最早有为ARM720处理器的开发板的固件,又有了armbootStrongARM平台的blob,还有S3C2410处理器开发板上的vivi等。现在armboot已经并入了U-Boot,所以U-Boot也支持ARM/XSCALE平台。U-Boot已经成为ARM平台事实上的标准Bootloader
(3)PowerPC
PowerPC平台的处理器有标准的Bootloader,就是ppcboot。PPCBOOT在合并armboot等之后,创建了U-Boot,成为各种体系结构开发板的通用引导程序。U-Boot仍然是PowerPC平台的主要Bootloader。
(4)MIPS
MIPS公司开发的YAMON是标准的Bootloader,也有许多MIPS芯片商为自己的开发板写了Bootloader。现在,U-Boot也已经支持MIPS平台。
(5)SH
SH平台的标准Bootloader是sh-boot。Redboot在这种平台上也很好用。
(6)M68K
M68K平台没有标准的Bootloader。Redboot能够支持m68k系列的系统。
值得说明的是Redboot,它几乎能够支持所有的体系结构,包括MIPS、SH、M68K等体系结构。Redboot是以eCos为基础,采用GPL许可的开源软件工程。现在由core eCos的开发人员维护,源码下载网站是http://www.ecoscentric.com/snapshots。Redboot的文档也相当完善,有详细的使用手册《RedBoot User’s Guide》。
6.2.1  U-Boot工程简介
最早,DENX软件工程中心的Wolfgang Denk基于8xxrom的源码创建了PPCBOOT工程,并且不断添加处理器的支持。后来,Sysgo Gmbh把ppcboot移植到ARM平台上,创建了ARMboot工程。然后以ppcboot工程和armboot工程为基础,创建了U-Boot工 程。
现在U-Boot已经能够支持PowerPC、ARM、X86、MIPS体系结构的上百种开发板,已经成为功能最多、灵活性最强并且开发最积极的开放源码Bootloader。目前仍然由DENX的Wolfgang Denk维护。
U-Boot的源码包可以从sourceforge网站下载,还可以订阅该网站活跃的U-Boot Users邮件论坛,这个邮件论坛对于U-Boot的开发和使用都很有帮助。
U-Boot软件包下载网站:http://sourceforge.net/project/u-boot。
U-Boot邮件列表网站:http://lists.sourceforge.net/lists/listinfo/u-boot-users/。
DENX相关的网站:http://www.denx.de/re/DPLG.html
6.2.2  U-Boot源码结构
从网站上下载得到U-Boot源码包,例如:U-Boot-1.1.2.tar.bz2
解压就可以得到全部U-Boot源程序。在顶层目录下有18个子目录,分别存放和管理不同的源程序。这些目录中所要存放的文件有其规则,可以分为3类。
· 第1类目录与处理器体系结构或者开发板硬件直接相关;
· 第2类目录是一些通用的函数或者驱动程序;
· 第3类目录是U-Boot的应用程序、工具或者文档。
表6.2列出了U-Boot顶层目录下各级目录存放原则。
表6.2                                                  U-Boot的源码顶层目录说明
 
目    录
特    性
解 释 说 明
board
平台依赖
存放电路板相关的目录文件,例如:RPXlite(mpc8xx)、smdk2410(arm920t)、sc520_cdp(x86) 等目录
cpu
平台依赖
存放CPU相关的目录文件,例如:mpc8xx、ppc4xx、arm720t、arm920t、 xscale、i386等目录
lib_ppc
平台依赖
存放对PowerPC体系结构通用的文件,主要用于实现PowerPC平台通用的函数
目    录
特    性
解 释 说 明
lib_arm
平台依赖
存放对ARM体系结构通用的文件,主要用于实现ARM平台通用的函数
lib_i386
平台依赖
存放对X86体系结构通用的文件,主要用于实现X86平台通用的函数
include
通用
头文件和开发板配置文件,所有开发板的配置文件都在configs目录下
common
通用
通用的多功能函数实现
lib_generic
通用
通用库函数的实现
Net
通用
存放网络的程序
Fs
通用
存放文件系统的程序
Post
通用
存放上电自检程序
drivers
通用
通用的设备驱动程序,主要有以太网接口的驱动
Disk
通用
硬盘接口程序
Rtc
通用
RTC的驱动程序
Dtt
通用
数字温度测量器或者传感器的驱动
examples
应用例程
一些独立运行的应用程序的例子,例如helloworld
tools
工具
存放制作S-Record 或者 U-Boot格式的映像等工具,例如mkimage
Doc
文档
开发使用文档
 
U-Boot的源代码包含对几十种处理器、数百种开发板的支持。可是对于特定的开发板,配置编译过程只需要其中部分程序。这里具体以S3C2410 arm920t处理器为例,具体分析S3C2410处理器和开发板所依赖的程序,以及U-Boot的通用函数和工具。
6.2.3  U-Boot的编译
U-Boot的源码是通过GCC和Makefile组织编译的。顶层目录下的Makefile首先可以设置开发板的定义,然后递归地调用各级子目录下的Makefile,最后把编译过的程序链接成U-Boot映像。
1.顶层目录下的Makefile
它负责U-Boot整体配置编译。按照配置的顺序阅读其中关键的几行。
每一种开发板在Makefile都需要有板子配置的定义。例如smdk2410开发板的定义如下。
 
smdk2410_config :   unconfig
     @./mkconfig $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
 
执行配置U-Boot的命令make smdk2410_config,通过./mkconfig脚本生成include/config.
mk的配置文件。文件内容正是根据Makefile对开发板的配置生成的。
 
ARCH   = arm
CPU    = arm920t
BOARD  = smdk2410
SOC    = s3c24x0
 
上面的include/config.mk文件定义了ARCH、CPU、BOARD、SOC这些变量。这样硬件平台依赖的目录文件可以根据这些定义来确定。SMDK2410平台相关目录如下。
board/smdk2410/
cpu/arm920t/
cpu/arm920t/s3c24x0/
lib_arm/
include/asm-arm/
include/configs/smdk2410.h
再回到顶层目录的Makefile文件开始的部分,其中下列几行包含了这些变量的定义。
 
# load ARCH, BOARD, and CPU configuration
include include/config.mk
export       ARCH CPU BOARD VENDOR SOC
 
Makefile的编译选项和规则在顶层目录的config.mk文件中定义。各种体系结构通用的规则直接在这个文件中定义。通过ARCH、CPU、BOARD、SOC等变量为不同硬件平台定义不同选项。不同体系结构的规则分别包含在ppc_config.mk、arm_config.mk、mips_config.mk等文件中。
顶层目录的Makefile中还要定义交叉编译器,以及编译U-Boot所依赖的目标文件。
 
ifeq ($(ARCH),arm)
CROSS_COMPILE = arm-linux-          //交叉编译器的前缀
#endif
export  CROSS_COMPILE
# U-Boot objects....order is important (i.e. start must be first)
OBJS  = cpu/$(CPU)/start.o                  //处理器相关的目标文件
LIBS  = lib_generic/libgeneric.a            //定义依赖的目录,每个目录下先把目标文件连接成*.a文件。
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
 
然后还有U-Boot映像编译的依赖关系。
 
ALL = u-boot.srec u-boot.bin System.map
all:        $(ALL)
u-boot.srec:    u-boot
            $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
u-boot.bin: u-boot
            $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
……
u-boot:         depend $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
            UNDEF_SYM='$(OBJDUMP) -x $(LIBS) /
            |sed  -n -e 's/.*/(__u_boot_cmd_.*/)/-u/1/p'|sort|uniq`;/
            $(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) /
                 --start-group $(LIBS) $(PLATFORM_LIBS) --end-group /
                 -Map u-boot.map -o u-boot
 
Makefile缺省的编译目标为all,包括u-boot.srec、u-boot.bin、System.map。u-boot.srecu-boot.bin又依赖于U-BootU-Boot就是通过ld命令按照u-boot.map地址表把目标文件组装成u-boot。
其他Makefile内容就不再详细分析了,上述代码分析应该可以为阅读代码提供了一个线索。
2.开发板配置头文件
除了编译过程Makefile以外,还要在程序中为开发板定义配置选项或者参数。这个头文件是include/configs/<board_name>.h。<board_name>用相应的BOARD定义代替。
这个头文件中主要定义了两类变量。
一类是选项,前缀是CONFIG_,用来选择处理器、设备接口、命令、属性等。例如:
 
#define   CONFIG_ARM920T         1
#define   CONFIG_DRIVER_CS8900  1
 
另一类是参数,前缀是CFG_,用来定义总线频率、串口波特率、Flash地址等参数。例如:
 
#define     CFG_FLASH_BASE      0x00000000
#define CFG_PROMPT          "=>"
3.编译结果
根据对Makefile的分析,编译分为2步。第1步配置,例如:make smdk2410_config;第2步编译,执行make就可以了。
编译完成后,可以得到U-Boot各种格式的映像文件和符号表,如表6.3所示。
表6.3                                                  U-Boot编译生成的映像文件
 
文 件 名 称
说    明
文 件 名 称
说    明
System.map
U-Boot映像的符号表
u-boot.bin
U-Boot映像原始的二进制格式
u-boot
U-Boot映像的ELF格式
u-boot.srec
U-Boot映像的S-Record格式
 
U-Boot的3种映像格式都可以烧写到Flash中,但需要看加载器能否识别这些格式。一般u-boot.bin最为常用,直接按照二进制格式下载,并且按照绝对地址烧写到Flash中就可以了。U-Boot和u-boot.srec格式映像都自带定位信息。
4.U-Boot工具
在tools目录下还有些U-Boot的工具。这些工具有的也经常用到。表6.4说明了几种工具的用途。
表6.4                                                              U-Boot的工具
 
工 具 名 称
说    明
工 具 名 称
说    明
bmp_logo
制作标记的位图结构体
img2srec
转换SREC格式映像
envcrc
校验u-boot内部嵌入的环境变量
mkimage
转换U-Boot格式映像
gen_eth_addr
生成以太网接口MAC地址
updater
U-Boot自动更新升级工具
 
这些工具都有源代码,可以参考改写其他工具。其中mkimage是很常用的一个工具,Linux内核映像和ramdisk文件系统映像都可以转换成U-Boot的格式。
6.2.4  U-Boot的移植
U-Boot能够支持多种体系结构的处理器,支持的开发板也越来越多。因为Bootloader是完全依赖硬件平台的,所以在新电路板上需要移植U-Boot程序。
开始移植U-Boot之前,先要熟悉硬件电路板和处理器。确认U-Boot是否已经支持新开发板的处理器和I/O设备。假如U-Boot已经支持一块非常相似的电路板,那么移植的过程将非常简单。
移植U-Boot工作就是添加开发板硬件相关的文件、配置选项,然后配置编译。
开始移植之前,需要先分析一下U-Boot已经支持的开发板,比较出硬件配置最接近的开发板。选择的原则是,首先处理器相同,其次处理器体系结构相同,然后是以太网接口等外围接口。还要验证一下这个参考开发板的U-Boot,至少能够配置编译通过。
以S3C2410处理器的开发板为例,U-Boot-1.1.2版本已经支持SMDK2410开发板。我们可以基于SMDK2410移植,那么先把SMDK2410编译通过。
我们以S3C2410开发板fs2410为例说明。移植的过程参考SMDK2410开发板,SMDK2410在U-Boot-1.1.2中已经支持。
移植U-Boot的基本步骤如下。
(1)在顶层Makefile中为开发板添加新的配置选项,使用已有的配置项目为例。
 
smdk2410_config   :       unconfig
         @./mkconfig $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
 
参考上面2行,添加下面2行。
 
fs2410_config   :       unconfig
     @./mkconfig $(@:_config=) arm arm920t fs2410 NULL s3c24x0
 
(2)创建一个新目录存放开发板相关的代码,并且添加文件。
board/fs2410/config.mk
board/fs2410/flash.c
board/fs2410/fs2410.c
board/fs2410/Makefile
board/fs2410/memsetup.S
board/fs2410/u-boot.lds
(3)为开发板添加新的配置文件
可以先复制参考开发板的配置文件,再修改。例如:
$cp include/configs/smdk2410.h  include/configs/fs2410.h
如果是为一颗新的CPU移植,还要创建一个新的目录存放CPU相关的代码。
(4)配置开发板
$ make fs2410_config
(5)编译U-Boot
执行make命令,编译成功可以得到U-Boot映像。有些错误是跟配置选项是有关系的,通常打开某些功能选项会带来一些错误,一开始可以尽量跟参考板配置相同。
(6)添加驱动或者功能选项
在能够编译通过的基础上,还要实现U-Boot的以太网接口、Flash擦写等功能。
对于FS2410开发板的以太网驱动和smdk2410完全相同,所以可以直接使用。CS8900驱动程序文件如下。
drivers/cs8900.c
drivers/cs8900.h
对于Flash的选择就麻烦多了,Flash芯片价格或者采购方面的因素都有影响。多数开发板大小、型号不都相同。所以还需要移植Flash的驱动。每种开发板目录下一般都有flash.c这个文件,需要根据具体的Flash类型修改。例如:
board/fs2410/flash.c
(7)调试U-Boot源代码,直到U-Boot在开发板上能够正常启动。
调试的过程可能是很艰难的,需要借助工具,并且有些问题可能困扰很长时间。
6.2.5 添加U-Boot命令
U-Boot的命令为用户提供了交互功能,并且已经实现了几十个常用的命令。如果开发板需要很特殊的操作,可以添加新的U-Boot命令。
U-Boot的每一个命令都是通过U_Boot_CMD宏定义的。这个宏在include/command.h头文件中定义,每一个命令定义一个cmd_tbl_t结构体。
 
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) /
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
 
这样每一个U-Boot命令有一个结构体来描述。结构体包含的成员变量:命令名称、最大参数个数、重复数、命令执行函数、用法、帮助。
从控制台输入的命令是由common/command.c中的程序解释执行的。find_cmd()负责匹配输入的命令,从列表中找出对应的命令结构体。
基于U-Boot命令的基本框架,来分析一下简单的icache操作命令,就可以知道添加新命令的方法。
(1)定义CACHE命令。在include/cmd_confdefs.h中定义了所有U-Boot命令的标志位。
 
#define CFG_CMD_CACHE       0x00000010ULL   /* icache, dcache       */
 
如果有更多的命令,也要在这里添加定义。
(2)实现CACHE命令的操作函数。下面是common/cmd_cache.c文件中icache命令部分的代码。
 
#if (CONFIG_COMMANDS & CFG_CMD_CACHE)
static int on_off (const char *s)
{       //这个函数解析参数,判断是打开cache,还是关闭cache
        if (strcmp(s, "on") == 0) {  //参数为“on”
               return (1);
        } else if (strcmp(s, "off") == 0) {  //参数为“off”
               return (0);
    }
    return (-1);
}
 
int do_icache ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{     //对指令cache的操作函数
      switch (argc) {
      case 2:               /* 参数个数为1,则执行打开或者关闭指令cache操作 */
             switch (on_off(argv[1])) {
             case 0:     icache_disable();        //打开指令cache
                   break;
             case 1:     icache_enable ();        //关闭指令cache
                   break;
             }
            /* FALL TROUGH */
      case 1:           /* 参数个数为0,则获取指令cache状态*/ 
            printf ("Instruction Cache is %s/n",
                    icache_status() ? "ON" : "OFF");
            return 0;
      default:  //其他缺省情况下,打印命令使用说明
            printf ("Usage:/n%s/n", cmdtp->usage);
            return 1;
      }
      return 0;
}
……
U_Boot_CMD( //通过宏定义命令
    icache,   2,   1,     do_icache,  //命令为icache,命令执行函数为do_icache()
    "icache  - enable or disable instruction cache/n",   //帮助信息
    "[on, off]/n"
    "    - enable or disable instruction cache/n"
);
……
#endif
 
U-Boot的命令都是通过结构体__U_Boot_cmd_##name来描述的。根据U_Boot_CMD在include/command.h中的两行定义可以明白。
 
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) /
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
 
还有,不要忘了在common/Makefile中添加编译的目标文件。
(3)打开CONFIG_COMMANDS选项的命令标志位。这个程序文件开头有#if语句需要预处理是否包含这个命令函数。 CONFIG_COMMANDS选项在开发板的配置文件中定义。例如:SMDK2410平台在include/configs/smdk2410.h中有 如下定义。
 
/***********************************************************
 * Command definition
 ***********************************************************/
#define CONFIG_COMMANDS /
                 (CONFIG_CMD_DFL  | /
                 CFG_CMD_CACHE     | /
                 CFG_CMD_REGINFO    | /
                 CFG_CMD_DATE      | /
                 CFG_CMD_ELF)
 
按照这3步,就可以添加新的U-Boot命令。
6.3  U-Boot的调试
新移植的U-Boot不能正常工作,这时就需要调试了。调试U-Boot离不开工具,只有理解U-Boot启动过程,才能正确地调试U-Boot源码。
6.3.1 硬件调试器
硬件电路板制作完成以后,这时上面还没有任何程序,就叫作裸板。首要的工作是把程序或者固件加载到裸板上,这就要通过硬件工具来完成。习惯上,这种硬件工具叫作仿真器。
仿真器可以通过处理器的JTAG等接口控制板子,直接把程序下载到目标板内存,或者进行Flash编程。如果板上的Flash是可以拔插的,就可以通过专用的Flash烧写器来完成。在第4章介绍过目标板跟主机之间的连接,其中JTAG等接口就是专门用来连接仿真器的。
仿真器还有一个重要的功能就是在线调试程序,这对于调试Bootloader和硬件测试程序很有用。
从最简单的JTAG电缆,到ICE仿真器,再到可以调试Linux内核的仿真器。
复杂的仿真器可以支持与计算机间的以太网或者USB接口通信。
对于U-Boot的调试,可以采用BDI2000。BDI2000完全可以反汇编地跟踪Flash中的程序,也可以进行源码级的调试。
使用BDI2000调试U-boot的方法如下。
(1)配置BDI2000和目标板初始化程序,连接目标板。
(2)添加U-Boot的调试编译选项,重新编译。
U-Boot的程序代码是位置相关的,调试的时候尽量在内存中调试,可以修改连接定位地址TEXT_BASE。TEXT_BASE在board/<board_name>/config.mk中定义。
另外,如果有复位向量也需要先从链接脚本中去掉。链接脚本是board/<board_name>/
u-boot.lds。
添加调试选项,在config.mk文件中查找,DBGFLAGS,加上-g选项。然后重新编译U-Boot
(3)下载U-Boot到目标板内存。
通过BDI2000的下载命令LOAD,把程序加载到目标板内存中。然后跳转到U-Boot入口。
(4)启动GDB调试。
启动GDB调试,这里是交叉调试的GDB。GDB与BDI2000建立链接,然后就可以设置断点执行了。
 
$ arm-linux-gdb u-boot
(gdb)target remote 192.168.1.100:2001
(gdb)stepi
(gdb)b start_armboot
(gdb)c
6.3.2 软件跟踪
假如U-Boot没有任何串口打印信息,手头又没有硬件调试工具,那样怎么知道U-Boot执行到什么地方了呢?可以通过开发板上的LED指示灯判断。
开发板上最好设计安装八段数码管等LED,可以用来显示数字或者数字位。
U-Boot可以定义函数show_boot_progress (int status),用来指示当前启动进度。在include/common.h头文件中声明这个函数。
 
#ifdef CONFIG_SHOW_BOOT_PROGRESS
void    show_boot_progress (int status);
#endif
 
CONFIG_SHOW_BOOT_PROGRESS是需要定义的。这个在板子配置的头文件中定义。CSB226开发板对这项功能有完整实现,可以参考。在头文件include/configs/csb226.h中,有下列一行。
 
#define CONFIG_SHOW_BOOT_PROGRESS       1
 
函数show_boot_progress (int status)的实现跟开发板关系密切,所以一般在board目录下的文件中实现。看一下CSB226在board/csb226/csb226.c中的实现函数。
 
/** 设置CSB226板的0、1、2三个指示灯的开关状态
 * csb226_set_led: - switch LEDs on or off
 * @param led:   LED to switch (0,1,2)
 * @param state: switch on (1) or off (0)
 */
void csb226_set_led(int led, int state)
{
      switch(led) {
             case 0: if (state==1) {
                              GPCR0 |= CSB226_USER_LED0;
                    } else if (state==0) {
                            GPSR0 |= CSB226_USER_LED0;
                    }
                    break;
             case 1: if (state==1) {
                              GPCR0 |= CSB226_USER_LED1;
                    } else if (state==0) {
                              GPSR0 |= CSB226_USER_LED1;
                    }
                    break;
             case 2: if (state==1) {
                              GPCR0 |= CSB226_USER_LED2;
                  } else if (state==0) {
                          GPSR0 |= CSB226_USER_LED2;
                  }
                  break;
      }
      return;
}
/** 显示启动进度函数,在比较重要的阶段,设置三个灯为亮的状态(1, 5, 15)*/
void show_boot_progress (int status)
{
      switch(status) {
            case  1: csb226_set_led(0,1); break;
            case  5: csb226_set_led(1,1); break;
            case 15: csb226_set_led(2,1); break;
      }
      return;
}
 
这样,在U-Boot启动过程中就可以通过show_boot_progresss指示执行进度。比如hang()函数是系统出错时调用的函数,这里需要根据特定的开发板给定显示的参数值。
 
void hang (void)
{
      puts ("### ERROR ### Please RESET the board ###/n");
#ifdef CONFIG_SHOW_BOOT_PROGRESS
      show_boot_progress(-30);
#endif
      for (;;);
6.3.3  U-Boot启动过程
尽管有了调试跟踪手段,甚至也可以通过串口打印信息了,但是不一定能够判断出错原因。如果能够充分理解代码的启动流程,那么对准确地解决和分析问题很有帮助。
开发板上电后,执行U-Boot的第一条指令,然后顺序执行U-Boot启动函数。函数调用顺序如图6.3所示。
看一下board/smsk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。第一个要链接的是cpu /arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。下面详细分析一下程序跳转和函数的调用关系以及函数实现。
1.cpu/arm920t/start.S
这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。
0 _fcksavedurl=" border=0>
图6.3  U-Boot启动代码流程图
 
_start: b       reset        //复位向量
       ldr   pc, _undefined_instruction
       ldr   pc, _software_interrupt
       ldr   pc, _prefetch_abort
       ldr   pc, _data_abort
       ldr   pc, _not_used
       ldr   pc, _irq      //中断向量
       ldr   pc, _fiq      //中断向量
 /* the actual reset code  */
reset:          //复位启动子程序
       /* 设置CPU为SVC32模式 */
       mrs   r0,cpsr
       bic   r0,r0,#0x1f
       orr   r0,r0,#0xd3
       msr   cpsr,r0
/* 关闭看门狗 */
 
/* 这些初始化代码在系统重起的时候执行,运行时热复位从RAM中启动不执行 */
#ifdef CONFIG_INIT_CRITICAL
       bl    cpu_init_crit
#endif
 
relocate:                       /* 把U-Boot重新定位到RAM */
       adr   r0, _start          /* r0是代码的当前位置 */
       ldr   r1, _TEXT_BASE      /* 测试判断是从Flash启动,还是RAM */
       cmp     r0, r1          /* 比较r0和r1,调试的时候不要执行重定位 */
       beq     stack_setup    /* 如果r0等于r1,跳过重定位代码 */
       /* 准备重新定位代码 */
       ldr   r2, _armboot_start
       ldr   r3, _bss_start
       sub   r2, r3, r2          /* r2 得到armboot的大小   */
       add   r2, r0, r2          /* r2 得到要复制代码的末尾地址 */
copy_loop: /* 重新定位代码 */
       ldmia r0!, {r3-r10}   /*从源地址[r0]复制 */
       stmia r1!, {r3-r10}   /* 复制到目的地址[r1] */
       cmp   r0, r2          /* 复制数据块直到源数据末尾地址[r2] */
       ble   copy_loop
 
       /* 初始化堆栈等    */
stack_setup:
       ldr   r0, _TEXT_BASE              /* 上面是128 KiB重定位的u-boot */
       sub   r0, r0, #CFG_MALLOC_LEN     /* 向下是内存分配空间 */
       sub   r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo结构体地址空间  */
#ifdef CONFIG_USE_IRQ
       sub   r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
       sub   sp, r0, #12     /* 为abort-stack预留3个字 */
clear_bss:
       ldr   r0, _bss_start      /* 找到bss段起始地址 */
       ldr   r1, _bss_end        /*  bss段末尾地址   */
       mov   r2, #0x00000000     /* 清零 */
clbss_l:str r2, [r0]        /* bss段地址空间清零循环...  */
       add   r0, r0, #4
       cmp   r0, r1
       bne   clbss_l
       /* 跳转到start_armboot函数入口,_start_armboot字保存函数入口指针 */
       ldr   pc, _start_armboot
_start_armboot: .word start_armboot     //start_armboot函数在lib_arm/board.c中实现
/* 关键的初始化子程序 */
cpu_init_crit:
……  //初始化CACHE,关闭MMU等操作指令
       /* 初始化RAM时钟。
       * 因为内存时钟是依赖开发板硬件的,所以在board的相应目录下可以找到memsetup.S文件。
       */
       mov   ip, lr
       bl    memsetup        //memsetup子程序在board/smdk2410/memsetup.S中实现
       mov   lr, ip
       mov   pc, lr
 
2.lib_arm/board.c
start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。
 
 
void start_armboot (void)
{
       DECLARE_GLOBAL_DATA_PTR;
       ulong size;
       init_fnc_t **init_fnc_ptr;
       char *s;
       /* Pointer is writable since we allocated a register for it */
       gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
       /* compiler optimization barrier needed for GCC >= 3.4 */
       __asm__ __volatile__("": : :"memory");
       memset ((void*)gd, 0, sizeof (gd_t));
       gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
       memset (gd->bd, 0, sizeof (bd_t));
       monitor_flash_len = _bss_start - _armboot_start;
       /* 顺序执行init_sequence数组中的初始化函数 */
       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
              if ((*init_fnc_ptr)() != 0) {
                      hang ();
              }
       }
       /*配置可用的Flash */
       size = flash_init ();
       display_flash_config (size);
       /* _armboot_start 在u-boot.lds链接脚本中定义 */
       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
       /* 配置环境变量,重新定位 */
       env_relocate ();
       /* 从环境变量中获取IP地址 */
       gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
       /* 以太网接口MAC 地址 */
       ……
       devices_init ();      /* 获取列表中的设备 */
       jumptable_init ();
       console_init_r ();    /* 完整地初始化控制台设备 */
       enable_interrupts (); /* 使能例外处理 */
       /* 通过环境变量初始化 */
       if ((s = getenv ("loadaddr")) != NULL) {
               load_addr = simple_strtoul (s, NULL, 16);
       }
       /* main_loop()总是试图自动启动,循环不断执行 */
       for (;;) {
               main_loop ();      /* 主循环函数处理执行用户命令 -- common/main.c */
       }
       /* NOTREACHED - no way out of command loop except booting */
}
 
3.init_sequence[]
init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。
 
init_fnc_t *init_sequence[] = {
       cpu_init,             /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */
       board_init,           /* 基本的板级相关配置 -- board/smdk2410/smdk2410.c */
       interrupt_init,       /* 初始化例外处理 -- cpu/arm920t/s3c24x0/interrupt.c */
       env_init,             /* 初始化环境变量 -- common/cmd_flash.c */
       init_baudrate,        /* 初始化波特率设置 -- lib_arm/board.c */
       serial_init,          /* 串口通讯设置 -- cpu/arm920t/s3c24x0/serial.c */
       console_init_f,       /* 控制台初始化阶段1 -- common/console.c */
       display_banner,       /* 打印u-boot信息 -- lib_arm/board.c */
       dram_init,            /* 配置可用的RAM -- board/smdk2410/smdk2410.c */
       display_dram_config,  /* 显示RAM的配置大小 -- lib_arm/board.c */
       NULL,
};
6.3.4  U-Boot与内核的关系
U-Boot作为Bootloader,具备多种引导内核启动的方式。常用的go和bootm命令可以直接引导内核映像启动。U-Boot与内核的关系主要是内核启动过程中参数的传递。
1.go命令的实现
 
/* common/cmd_boot.c  */
int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
       ulong addr, rc;
       int     rcode = 0;
       if (argc < 2) {
              printf ("Usage:/n%s/n", cmdtp->usage);
              return 1;
       }
       addr = simple_strtoul(argv[1], NULL, 16);
       printf ("## Starting application at 0x%08lX .../n", addr);
       /*
        * pass address parameter as argv[0] (aka command name),
        * and all remaining args
        */
       rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);
       if (rc != 0) rcode = 1;
 
       printf ("## Application terminated, rc = 0x%lX/n", rc);
       return rcode;
}
 
go命令调用do_go()函数,跳转到某个地址执行的。如果在这个地址准备好了自引导的内核映像,就可以启动了。尽管go命令可以带变参,实际使用时一般不用来传递参数。
2.bootm命令的实现
 
/* common/cmd_bootm.c */
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
       ulong iflag;
       ulong addr;
       ulong data, len, checksum;
       ulong  *len_ptr;
       uint  unc_len = 0x400000;
       int   i, verify;
       char  *name, *s;
       int   (*appl)(int, char *[]);
       image_header_t *hdr = &header;
 
       s = getenv ("verify");
       verify = (s && (*s == 'n')) ? 0 : 1;
       if (argc < 2) {
              addr = load_addr;
       } else {
              addr = simple_strtoul(argv[1], NULL, 16);
       }
       SHOW_BOOT_PROGRESS (1);
       printf ("## Booting image at %08lx .../n", addr);
       /* Copy header so we can blank CRC field for re-calculation */
       memmove (&header, (char *)addr, sizeof(image_header_t));
       if (ntohl(hdr->ih_magic) != IH_MAGIC)
       {
              puts ("Bad Magic Number/n");
              SHOW_BOOT_PROGRESS (-1);
              return 1;
       }
       SHOW_BOOT_PROGRESS (2);
       data = (ulong)&header;
       len  = sizeof(image_header_t);
 
       checksum = ntohl(hdr->ih_hcrc);
       hdr->ih_hcrc = 0;
 
       if(crc32 (0, (char *)data, len) != checksum) {
              puts ("Bad Header Checksum/n");
              SHOW_BOOT_PROGRESS (-2);
              return 1;
       }
       SHOW_BOOT_PROGRESS (3);
       /* for multi-file images we need the data part, too */
       print_image_hdr ((image_header_t *)addr);
       data = addr + sizeof(image_header_t);
       len  = ntohl(hdr->ih_size);
       if(verify) {
              puts ("   Verifying Checksum ... ");
              if(crc32 (0, (char *)data, len) != ntohl(hdr->ih_dcrc)) {
                     printf ("Bad Data CRC/n");
                     SHOW_BOOT_PROGRESS (-3);
                     return 1;
              }
              puts ("OK/n");
       }
       SHOW_BOOT_PROGRESS (4);
       len_ptr = (ulong *)data;
……
       switch (hdr->ih_os) {
       default:                /* handled by (original) Linux case */
       case IH_OS_LINUX:
             do_bootm_linux  (cmdtp, flag, argc, argv,
                         addr, len_ptr, verify);
             break;
       ……
}
 
bootm命令调用do_bootm函数。这个函数专门用来引导各种操作系统映像,可以支持引导Linux、vxWorks、QNX等操作系统。引导Linux的时候,调用do_bootm_linux()函数。
3.do_bootm_linux函数的实现
 
/* lib_arm/armlinux.c */
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
                   ulong addr, ulong *len_ptr, int verify)
{
       DECLARE_GLOBAL_DATA_PTR;
       ulong len = 0, checksum;
       ulong initrd_start, initrd_end;
       ulong data;
       void (*theKernel)(int zero, int arch, uint params);
       image_header_t *hdr = &header;
       bd_t *bd = gd->bd;
#ifdef CONFIG_CMDLINE_TAG
       char *commandline = getenv ("bootargs");
#endif
       theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
       /* Check if there is an initrd image */
       if(argc >= 3) {
              SHOW_BOOT_PROGRESS (9);
              addr = simple_strtoul (argv[2], NULL, 16);
              printf ("## Loading Ramdisk Image at %08lx .../n", addr);
              /* Copy header so we can blank CRC field for re-calculation */
              memcpy (&header, (char *) addr, sizeof (image_header_t));
              if (ntohl (hdr->ih_magic) != IH_MAGIC) {
                      printf ("Bad Magic Number/n");
                      SHOW_BOOT_PROGRESS (-10);
                      do_reset (cmdtp, flag, argc, argv);
              }
              data = (ulong) & header;
              len = sizeof (image_header_t);
              checksum = ntohl (hdr->ih_hcrc);
              hdr->ih_hcrc = 0;
              if(crc32 (0, (char *) data, len) != checksum) {
                     printf ("Bad Header Checksum/n");
                     SHOW_BOOT_PROGRESS (-11);
                     do_reset (cmdtp, flag, argc, argv);
              }
              SHOW_BOOT_PROGRESS (10);
              print_image_hdr (hdr);
              data = addr + sizeof (image_header_t);
              len = ntohl (hdr->ih_size);
              if(verify) {
                     ulong csum = 0;
                     printf ("   Verifying Checksum ... ");
                     csum = crc32 (0, (char *) data, len);
                     if (csum != ntohl (hdr->ih_dcrc)) {
                            printf ("Bad Data CRC/n");
                            SHOW_BOOT_PROGRESS (-12);
                            do_reset (cmdtp, flag, argc, argv);
                     }
                     printf ("OK/n");
              }
              SHOW_BOOT_PROGRESS (11);
              if ((hdr->ih_os != IH_OS_LINUX) ||
                     (hdr->ih_arch != IH_CPU_ARM) ||
                     (hdr->ih_type != IH_TYPE_RAMDISK)) {
                     printf ("No Linux ARM Ramdisk Image/n");
                     SHOW_BOOT_PROGRESS (-13);
                     do_reset (cmdtp, flag, argc, argv);
              }
              /* Now check if we have a multifile image */
       } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
               ulong tail = ntohl (len_ptr[0]) % 4;
               int i;
               SHOW_BOOT_PROGRESS (13);
               /* skip kernel length and terminator */
               data = (ulong) (&len_ptr[2]);
               /* skip any additional image length fields */
               for (i = 1; len_ptr[i]; ++i)
                       data += 4;
              /* add kernel length, and align */
              data += ntohl (len_ptr[0]);
              if (tail) {
                       data += 4 - tail;
              }
              len = ntohl (len_ptr[1]);
       } else {
               /* no initrd image */
              SHOW_BOOT_PROGRESS (14);
              len = data = 0;
       }
       if (data) {
               initrd_start = data;
               initrd_end = initrd_start + len;
       } else {
               initrd_start = 0;
               initrd_end = 0;
       }
       SHOW_BOOT_PROGRESS (15);
       debug ("## Transferring control to Linux (at address %08lx) .../n",
               (ulong) theKernel);
#if defined (CONFIG_SETUP_MEMORY_TAGS) || /
      defined (CONFIG_CMDLINE_TAG) || /
      defined (CONFIG_INITRD_TAG) || /
      defined (CONFIG_SERIAL_TAG) || /
      defined (CONFIG_REVISION_TAG) || /
      defined (CONFIG_LCD) || /
      defined (CONFIG_VFD)
      setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
      setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
      setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
      setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
      setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
      if (initrd_start && initrd_end)
               setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
      setup_end_tag (bd);
#endif
      /* we assume that the kernel is in place */
      printf ("/nStarting kernel .../n/n");
      cleanup_before_linux ();
 
      theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}
 
do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。这里引导的内核映像和 ramdisk映像,必须是U-Boot格式的。U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。
6.4  使用U-Boot
U-Boot是“Monitor”。除了Bootloader的系统引导功能,它还有用户命令接口,提供了一些复杂的调试、读写内存、烧写Flash、配置环境变量等功能。掌握U-Boot的使用,将极大地方便嵌入式系统的开发。
6.4.1 烧写U-Boot到Flash
新开发的电路板没有任何程序可以执行,也就不能启动,需要先将U-Boot烧写到Flash中。
如果主板上的EPROM或者Flash能够取下来,就可以通过编程器烧写。例如:计算机BIOS就存储在一块256KB的Flash上,通过插座与主板连接。
但是多数嵌入式单板使用贴片的Flash,不能取下来烧写。这种情况可以通过处理器的调试接口,直接对板上的Flash编程。
处理器调试接口是为处理器芯片设计的标准调试接口,包含BDM、JTAG和EJTAG 3种接口标准。JTAG接口在第4章已经介绍过;BDM(Background Debug Mode)主要应用在PowerPC8xx系列处理器上;EJTAG主要应用在MIPS处理器上。这3种硬件接口标准定义有所不同,但是功能基本相同,下 面都统称为JTAG接口。
JTAG接口需要专用的硬件工具来连接。无论从功能、性能角度,还是从价格角度,这些工具都有很大差异。关于这些工具的选择,将在第6.4.1节详细介绍。
最简单方式就是通过JTAG电缆,转接到计算机并口连接。这需要在主机端开发烧写程序,还需要有并口设备驱动程序。开发板上电或者复位的时候, 烧写程序探测到处理器并且开始通信,然后把Bootloader下载并烧写到Flash中。这种方式速率很慢,可是价格非常便宜。一般来说,平均每秒钟可 以烧写100~200个字节。
烧写完成后,复位实验板,串口终端应该显示U-Boot的启动信息。
6.4.2  U-Boot的常用命令
U-Boot上电启动后,敲任意键可以退出自动启动状态,进入命令行。
 
U-Boot 1.1.2 (Apr 26 2005 - 12:27:13)
U-Boot code: 11080000 -> 1109614C  BSS: -> 1109A91C
RAM Configuration:
Bank #0: 10000000 32 MB
Micron StrataFlash MT28F128J3 device initialized
Flash: 32 MB
In:    serial
Out:   serial
Err:   serial
Hit any key to stop autoboot:  0
U-Boot>
 
在命令行提示符下,可以输入U-Boot的命令并执行。U-Boot可以支持几十个常用命令,通过这些命令,可以对开发板进行调试,可以引导Linux内核,还可以擦写Flash完成系统部署等功能。掌握这些命令的使用,才能够顺利地进行嵌入式系统的开发。
输入help命令,可以得到当前U-Boot的所有命令列表。每一条命令后面是简单的命令说明。
 
=> help
?       - alias for 'help'
autoscr - run script from memory
base    - print or set address offset
bdinfo  - print Board Info structure
boot    - boot default, i.e., run 'bootcmd'
bootd   - boot default, i.e., run 'bootcmd'
bootm   - boot application image from memory
bootp   - boot image via network using BootP/TFTP protocol
cmp     - memory compare
coninfo  - print console devices and information
cp      - memory copy
crc32   - checksum calculation
dhcp    - invoke DHCP client to obtain IP/boot params
echo    - echo args to console
erase   - erase FLASH memory
flinfo  - print FLASH memory information
go      - start application at address 'addr'
help    - print online help
iminfo  - print header information for application image
imls    - list all images found in flash
itest    - return true/false on integer compare
loadb   - load binary file over serial line (kermit mode)
loads   - load S-Record file over serial line
loop   - infinite loop on address range
md    - memory display
mm    - memory modify (auto-incrementing)
mtest   - simple RAM test
mw      - memory write (fill)
nfs     - boot image via network using NFS protocol
nm      - memory modify (constant address)
printenv - print environment variables
protect - enable or disable FLASH write protection
rarpboot - boot image via network using RARP/TFTP protocol
reset   - Perform RESET of the CPU
run     - run commands in an environment variable
saveenv - save environment variables to persistent storage
setenv  - set environment variables
sleep   - delay execution for some time
tftpboot - boot image via network using TFTP protocol
version - print monitor version
=>
 
U-Boot还提供了更加详细的命令帮助,通过help命令还可以查看每个命令的参数说明。由于开发过程的需要,有必要先把U-Boot命令的用法弄清楚。接下来,根据每一条命令的帮助信息,解释一下这些命令的功能和参数。
 
=> help bootm
bootm [addr [arg ...]]
    - boot application image stored in memory
          passing arguments 'arg ...'; when booting a Linux kernel,
          'arg' can be the address of an initrd image
 
bootm命令可以引导启动存储在内存中的程序映像。这些内存包括RAM和可以永久保存的Flash。
第1个参数addr是程序映像的地址,这个程序映像必须转换成U-Boot的格式。
第2个参数对于引导Linux内核有用,通常作为U-Boot格式的RAMDISK映像存储地址;也可以是传递给Linux内核的参数(缺省情况下传递bootargs环境变量给内核)。
 
=> help bootp
bootp [loadAddress] [bootfilename]
bootp命令通过bootp请求,要求DHCP服务器分配IP地址,然后通过TFTP协议下载指定的文件到内存。
第1个参数是下载文件存放的内存地址。
第2个参数是要下载的文件名称,这个文件应该在开发主机上准备好。
 
=> help cmp
cmp [.b, .w, .l] addr1 addr2 count
     - compare memory
 
cmp命令可以比较2块内存中的内容。.b以字节为单位;.w以字为单位;.l以长字为单位。注意:cmp.b中间不能保留空格,需要连续敲入命令。
第1个参数addr1是第一块内存的起始地址。
第2个参数addr2是第二块内存的起始地址。
第3个参数count是要比较的数目,单位按照字节、字或者长字。
 
=> help cp
cp [.b, .w, .l] source target count
       - copy memory
 
cp命令可以在内存中复制数据块,包括对Flash的读写操作。
第1个参数source是要复制的数据块起始地址。
第2个参数target是数据块要复制到的地址。这个地址如果在Flash中,那么会直接调用写Flash的函数操作。所以U-Boot写Flash就使用这个命令,当然需要先把对应Flash区域擦干净。
第3个参数count是要复制的数目,根据cp.b cp.w cp.l分别以字节、字、长字为单位。
 
=> help crc32
crc32 address count [addr]
     - compute CRC32 checksum [save at addr]  
 
crc32命令可以计算存储数据的校验和。
第1个参数address是需要校验的数据起始地址。
第2个参数count是要校验的数据字节数。
第3个参数addr用来指定保存结果的地址。
 
=> help echo
echo [args..]
      - echo args to console; /c suppresses newline
 
echo命令回显参数。
 
=> help erase
erase start end
      - erase FLASH from addr 'start' to addr 'end'
erase N:SF[-SL]
      - erase sectors SF-SL in FLASH bank # N
erase bank N
      - erase FLASH bank # N
erase all
      - erase all FLASH banks
 
erase命令可以擦Flash。
参数必须指定Flash擦除的范围。
按照起始地址和结束地址,start必须是擦除块的起始地址;end必须是擦除末尾块的结束地址。这种方式最常用。举例说明:擦除0x20000 – 0x3ffff区域命令为erase 20000 3ffff。
按照组和扇区,N表示Flash的组号,SF表示擦除起始扇区号,SL表示擦除结束扇区号。另外,还可以擦除整个组,擦除组号为N的整个Flash组。擦除全部Flash只要给出一个all的参数即可。
 
=> help flinfo
flinfo
       - print information for all FLASH memory banks
flinfo N
       - print information for FLASH memory bank # N
 
flinfo命令打印全部Flash组的信息,也可以只打印其中某个组。一般嵌入式系统的Flash只有一个组。
 
=> help go
go addr [arg ...]
      - start application at address 'addr'
        passing 'arg' as arguments
 
go命令可以执行应用程序。
第1个参数是要执行程序的入口地址。
第2个可选参数是传递给程序的参数,可以不用。
 
=> help iminfo
iminfo addr [addr ...]
      - print header information for application image starting at
         address 'addr' in memory; this includes verification of the
         image contents (magic number, header and payload checksums)
 
iminfo可以打印程序映像的开头信息,包含了映像内容的校验(序列号、头和校验和)。
第1个参数指定映像的起始地址。
可选的参数是指定更多的映像地址。
 
=> help loadb
loadb [ off ] [ baud ]
     - load binary file over serial line with offset 'off' and baudrate 'baud'
 
loadb命令可以通过串口线下载二进制格式文件。
 
=> help loads
loads [ off ]
    - load S-Record file over serial line with offset 'off'
 
loads命令可以通过串口线下载S-Record格式文件。
 
=> help mw
mw [.b, .w, .l] address value [count]
     - write memory
 
mw命令可以按照字节、字、长字写内存,.b .w .l的用法与cp命令相同。
第1个参数address是要写的内存地址。
第2个参数value是要写的值。
第3个可选参数count是要写单位值的数目。
 
=> help nfs
nfs [loadAddress] [host ip addr:bootfilename]
 
nfs命令可以使用NFS网络协议通过网络启动映像。
 
=> help nm
nm [.b, .w, .l] address
     - memory modify, read and keep address
 
nm命令可以修改内存,可以按照字节、字、长字操作。
参数address是要读出并且修改的内存地址。
 
=> help printenv
printenv
      - print values of all environment variables
printenv name ...
      - print value of environment variable 'name'
 
printenv命令打印环境变量。
可以打印全部环境变量,也可以只打印参数中列出的环境变量。
 
=> help protect
protect on  start end
      - protect Flash from addr 'start' to addr 'end'
protect on  N:SF[-SL]
      - protect sectors SF-SL in Flash bank # N
protect on  bank N
      - protect Flash bank # N
protect on  all
      - protect all Flash banks
protect off start end
      - make Flash from addr 'start' to addr 'end' writable
protect off N:SF[-SL]
     - make sectors SF-SL writable in Flash bank # N
protect off bank N
     - make Flash bank # N writable
protect off all
     - make all Flash banks writable
 
protect命令是对Flash写保护的操作,可以使能和解除写保护。
第1个参数on代表使能写保护;off代表解除写保护。
第2、3参数是指定Flash写保护操作范围,跟擦除的方式相同。
 
=> help rarpboot
rarpboot [loadAddress] [bootfilename]
 
rarboot命令可以使用TFTP协议通过网络启动映像。也就是把指定的文件下载到指定地址,然后执行。
第1个参数是映像文件下载到的内存地址。
第2个参数是要下载执行的映像文件。
 
=> help run
run var [...]
      - run the commands in the environment variable(s) 'var'
 
run命令可以执行环境变量中的命令,后面参数可以跟几个环境变量名。
 
=> help setenv
setenv name value ...
      - set environment variable 'name' to 'value ...'
setenv name
      - delete environment variable 'name'
 
setenv命令可以设置环境变量。
第1个参数是环境变量的名称。
第2个参数是要设置的值,如果没有第2个参数,表示删除这个环境变量。
 
 
=> help sleep
sleep N
      - delay execution for N seconds (N is _decimal_ !!!)
 
sleep命令可以延迟N秒钟执行,N为十进制数。
 
=> help tftpboot
tftpboot [loadAddress] [bootfilename]
 
tftpboot命令可以使用TFTP协议通过网络下载文件。按照二进制文件格式下载。另外使用这个命令,必须配置好相关的环境变量。例如serverip和ipaddr。
第1个参数loadAddress是下载到的内存地址。
第2个参数是要下载的文件名称,必须放在TFTP服务器相应的目录下。
这些U-Boot命令为嵌入式系统提供了丰富的开发和调试功能。在Linux内核启动和调试过程中,都可以用到U-Boot的命令。但是一般情 况下,不需要使用全部命令。比如已经支持以太网接口,可以通过tftpboot命令来下载文件,那么还有必要使用串口下载的loadb吗?反过来,如果开 发板需要特殊的调试功能,也可以添加新的命令。
在建立交叉开发环境和调试Linux内核等章节时,在ARM平台上移植了U-Boot,并且提供了具体U-Boot的操作步骤。
6.4.3  U-Boot的环境变量
有点类似ShellU-Boot也使用环境变量。可以通过printenv命令查看环境变量的设置。
 
U-Boot> printenv
bootdelay=3
baudrate=115200
netmask=255.255.0.0
ethaddr=12:34:56:78:90:ab
bootfile=uImage
bootargs=console=ttyS0,115200 root=/dev/ram rw initrd=0x30800000,8M
bootcmd=tftp 0x30008000 zImage;go 0x30008000
serverip=192.168.1.1
ipaddr=192.168.1.100
stdin=serial
stdout=serial
stderr=serial
 
Environment size: 337/131068 bytes
U-Boot>
 
表6.5是常用环境变量的含义解释。通过printenv命令可以打印出这些变量的值。
表6.5                                                  U-Boot环境变量的解释说明
 
环 境 变 量
解 释 说 明
bootdelay
定义执行自动启动的等候秒数
baudrate
定义串口控制台的波特率
netmask
定义以太网接口的掩码
ethaddr
定义以太网接口的MAC地址
bootfile
定义缺省的下载文件
bootargs
定义传递给Linux内核的命令行参数
bootcmd
定义自动启动时执行的几条命令
serverip
定义tftp服务器端的IP地址
ipaddr
定义本地的IP地址
stdin
定义标准输入设备,一般是串口
stdout
定义标准输出设备,一般是串口
stderr
定义标准出错信息输出设备,一般是串口
 
U-Boot的环境变量都可以有缺省值,也可以修改并且保存在参数区。U-Boot的参数区一般有EEPROM和Flash两种设备。
环境变量的设置命令为setenv,在6.2.2节有命令的解释。
举例说明环境变量的使用。
 
=>setenv serverip  192.168.1.1
=>setenv ipaddr  192.168.1.100
=>setenv rootpath  "/usr/local/arm/3.3.2/rootfs"
=>setenv bootargs  "root=/dev/nfs rw nfsroot=/$(serverip):/$(rootpath) ip=
/$(ipaddr) "
=>setenv kernel_addr 30000000
=>setenv nfscmd  "tftp /$(kernel_addr) uImage; bootm /$(kernel_addr) "
=>run nfscmd
 
上面定义的环境变量有serverip ipaddr rootpath bootargs kernel_addr。环境变量bootargs中还使用了环境变量,bootargs定义命令行参数,通过bootm命令传递给内核。环境变量 nfscmd中也使用了环境变量,功能是把uImage下载到指定的地址并且引导起来。可以通过run命令执行nfscmd脚本。