|
|
7月27日 http://wiki.ubuntu.org.cn/index.php?title=Qref/Hardy&variant=zh-cn
7月20日 1.CGI 脚本结构 当脚本被服务器引发时,服务器常常以两种途径之一向脚本传递信息:GET或POST。这两种方法被称为请求方法。所使用的请求方法是通过环境变量传给脚本,该环境变量叫作REQUEST_METHOD(还定义了另外两种请求方法一HEAD和PUT,但它们不是特别应用于CGI,并且不鼓励使用它们)。 1)GET是对数据的一个请求——同样的方法被用于获得静态文档。GET方法以附加在URL后面的参数发送请求信息。这些参数将放在环境变量QUERY_STRING中传给CGI程序。例如,有一个叫作Myprog.exe的脚本,从如下的链接启动它: <a href="cgi-bin/myprog.exe?lname=blow&fname=joe"> REQUEST_METHOD是GET,QUERY_STRING包含lname=b1ow&fname=joe。在“URL一编码”中将讨论QUERY_STRING的格式。 问号从QUERY_STRING的起始处分隔开脚本名字。在一些服务器上,问号是强制性的,即使后面没有跟着QUERY_STRING。另一些服务器则允许用一个正斜杠代替问号或与之附加在一起。如果使用斜杠,服务器则用PATH_INFO而不是QUERY_STRING变量将信息传给脚本。(URL解码) 2)当浏览器将数据从一个填写表单传给服务器时,发生POST操作。对于POST,QUERY一STRING可能为空或不空,这有赖于服务器。如果有信息,则其如GET的情况一样被格式化和传递。 来自POST查询的数据使用STDIN从服务器传到脚本。由于STDIN是一个源,脚本需要知道有多少有效数据。于是服务器还提供了另一个变量,CONTENT_LENGTH,以指出到来数据的字节数。而POST的数据格式为: variable1=value1&variable2=value2&etc 你的程序必须检查REQUEST_METHOD环境变量以知道是否要读取STDIN。CONTENT_LENGTH变量一般只在REOUEST_METHOD为POST时有用。 CGI应用的基本结构既简单又直接明了:初始化、处理、输出和终止。由于讨论的是概念、数据源、编程规则,所以在例子中将使用伪码而不是使用某种特定语言。 理想情况下,一个脚本具有如下形式(do-initialize,do-process和do-output代表恰当的子例程): 程序开始 调用 do-initialize 调用 do-proces 调用 do一output 程序结束。 实际情况并非这么简单。 1.1 初始化 脚本启动后必须做的第一件事是确定其输入、环境和状态。基本操作系统环境信息能以通常方式得到:在Windows NT或windows95中从系统注册区得到,在Unix系统中从标准环境变量得到,在别的Windows版本中从INI文件得到,等等。 状态信息来自于输入,而不是操作环境或静态变量。记住:每当CGI脚本被引发时,它都好象此前从未被引发过。脚本不在调用之间持续运行,所有的东西都必须从头初始化,如下: 1.确定脚本是如何被引发的 典型情况下,这涉及读取REQUEST_METHOOD环境变量并分析其中的单词GET或POST。 注意 尽管当前定义应用于coi的操作只有GET和posT,你或许会时不时地遇到PUT或HEAD,假王口你的服务器支持它并且用户的剜览器或一个机器人使用它就可能发生这种情况。 PUl7k作为PosT的另选提供,但从未得多(认可的RFC资格,一般不被使用。HEAD被一些剜览器fotL器人(自动剜览器账用,仅用于提取HTML文在的头部,不适用于C6路程。此外还有一些古怪的请求方法。你的代码应该检查是否为GET和PosT,拒绝任何其他方法,不要假设请求方法如果不是GET便是PosT,或者相反。 2.提取输入数据 如果方法是GET,必须获得、分析、解码QUERY_STRING环境变量。如果方法是POST,必须检查QUERY_STRING并还要分析STDIN。如果CONTENT_TYPE环境变量是设为application/x-www-form-urlencoded,来自STDIN的源也需要解码。 1.2处理 脚本通过读取和分析其输入从而对环境初始化之后,便准备进入工作。在此阶段发生的事情则远没有初始化阶段那样确定。在初始化时,参数是知道的(或是可以被发现),所要做的任务对于各个脚本都多多少少地相同。然而,处理阶段是脚本的核心,在此时要做的事情几乎完全依赖于脚本的目标。 1.处理输入数据 此时做什么取决于脚本。例如,你可以忽略全部输入而仅仅输出数据,可能以有条理格式化的HTML将输入在吐出去,或许会在一个数据库中猎取信息在将其显示出来,或者是从前没有想到的任何事情。处理数据一般意味着,以某种方式对其进行转换。在传统的数据处理术语中,这叫做转换步骤,因为,在面向批作业的处理中,程序读取一个记录并对其施加一些规则(转换它),然后将其写回。CGI 程序很少被看作传统的数据处理,但思想是一样的。程序的处理数据阶段不同的CGI 程序,——在数据处理阶段,你拿到输入,并从其中做出一些新的东西来。 2.输出结果 在一个简单的CGI脚本中,输出常常只是一个头部和一些HTML。更复杂些的脚本可能;输出图形、图形与文本的混和,或者为了用一些附加信息再次调用脚本而必要的全部信息。一个常用并且更精巧的技术是使用GET调用脚本一次,这可以用一个标准的<A HREF>标记做到。脚本可以感知它是用GET调用的,并动态地创建HTML表单一一包括隐藏变量和再次用POST调用脚本所需的代码。 兼容性问题 在UNIX世界中,字符流是一种特殊的文件。默认地,STDIN和STDOUT是字符流。操作系统很有帮助地为你分析流,确保所通过的全是正确的7-bitASCII码,或者是认可的控制码。 7-bit?是的。对于HTML,这没有问题。然而,如果你的脚本发送图形数据,使用面向字符的流则意味着立即失败。解决方法是将流切换到二进制模式。在C语言中,可以使用setmode函数:setmode(fileno(stdout),O_BINARY)。通过setmode(fi1eno(stdout),O_TEXT)在流当中进行切换。一个典型的图形脚本以字符模式输出头部,而后切换到二进制模式用于图形数据。 在windows NT世界中,为了兼容性,流有着同样方式的行为。输出中的一个简单\n,当写到STDOUT时,被变换为\r\n。一般的windows NT调用,如write Fi1e(),不发生上述变换,如果同时想要一个回车和一个换行,则必须显式地指出\r\n。 字符模式和二进制模式的另一种说法是cooked和raw,知道这两个名词的人或许会使用它们,而不是更常见的说法。不管使用什么词,在什么平台上,关于流存在着另一问题:默认情况下,它们是有缓冲区的,意思是操作系统挂起数据,直至看见一个行结束符、缓冲区满或者流被关闭。这意味着,你如果将有缓冲区的prinif()语句同无缓冲区的fwriie()或fprintf()语句混合在一起,事情可能就变得混乱了,尽管它们都会是写到STDOUT。printf()有缓冲区地将数据写到流,面向文件的例程则无缓冲区地输出数据。结果是乱序的一团糟。 你可能将此归咎于后向兼容性。除了许多老程序之外,流实在没理由将默认定为有缓冲区和cooked。这应当是在需要时可以打开的选项,而不是在不要时关闭。幸运的是,你能够用setvbuf(stdout,NULL,_IONBF,0)解决这一困难,这个函数关闭UTDOUT流的全部缓冲区。 另一个解决是避免混和不同类型的输出语句,即使这样,也不能使cooked输出变成raw。所以最好是关闭所有缓冲区。许多服务器和浏览器不喜欢接收单调乏味的输入。 注意 那些常把UNIX挂在嘴边的人可能会对名词CRLF(回车与换行)皱眉,而那些在其他平台上编程的人也许不认识\n或\r\n。CRLF等于\r\n。C编程者用\r表示一个回车(CR)符号,用\n表示一个换行(LF)符。(对于Basic编程,LF是Chr$(10,CR是Chr$(13)。) 1.3 终止 终止就是清理和退出。你如果对任何文件加了锁,则必须在程序结束前释放它们。你如果分配了内存、信号量或其他对象,也必须进行释放。不正确完成这些会导致脚本“昙花只能一现”。即脚本在第一次调用时能工作,而在以后的调用中就会崩溃。更有甚者,脚本由于没有正确释放资源和锁,将会妨碍甚至破坏其他脚本或服务器本身。 在一些平台上一Windows NT最显著, UNIX次之——文件句柄和内存对象在进程终止时会被关闭和收回。即使这样,依赖操作系统为你清理垃圾也非明智之举。例如,在Windows NT上,如果一个程序对一个文件全部或部分加锁,而后不释放锁便终止,则文件系统的行为将是不确定的。 必须确保你的出错一退出例程——如果有(也应该有)——了解脚本的资源并能象主退出例程一样彻底地对它们进行清理。 2.计划脚本 现在读者已经看到了一个脚本的基本结构,下面将要学习如何从头计划一个脚本。按照如下基本步骤进行: 用一些时间定义程序的任务。整体、周到地考虑一下,把它写下来并描绘程序逻辑。当你已经很好理解了输入、输出和必须做的转换处理之后,才可以往下继续。 预订好食物和饮料,把自己关在屋里一晚上,第二天便可以带着完成的程序出来了。如果前面第1步做得好,那么实际编写程序是算不了什么的(编写代码时不要忘记为它做文档)。 测试、测试、测试。试一试各种知道的浏览器和各种能想到的输入。尤其检测一下诸如用户在一个10字节字段中输入32KB数据,或者在期望为简单文字的地方输入控制代码等等这些情况。 将整个程序文档作为一个整体——不仅仅针对其中的单个步骤——以便让其他人维护或改编代码时能够理解你的意图。 当然,本节的话题是上面的第一步,因此下面让我们更深入地看一看此过程: 如果你的脚本处理表单变量,计划出每个变量的名字、预期长度、数据类型。 当你从QUERY_STRING或STDIN拷贝变量时,检查类型和长度是否正确。UNIX破坏者的一个惯用技俩是蓄意让缓冲区溢出,鉴于一些脚本语言(显著的是sh和bash)为变量分配内存的方式,使得破坏者能够访问本应受到保护的内存,他们能在你脚本的堆或栈空间放置可执行指令。 使用有意义的变量名字。一个指向环境变量QUERY_STRING的指针应该叫作类似PQueryString的名字,而不是P2。这不仅在一开始有助于调试,也能简化维护和修改工作。不管代码有多漂亮,也免不了在一年后想不起来P1是指向CONTENT_TYPE而P2指向QUERY_STRING。 区分系统级参数和用户级参数。前者影响程序如何操作而后者提供实例特定的信息,例如,在一个发送电子邮件的脚本中,不要让用户指定SMTP主机的IP号。这个信息甚至不应该出现在隐藏变量里的表单上。它是实例无关的,因而应当是一个系统级参数。在Windows NT中,将该信息存在注册区里,在UNIX中,将它放入一个配置文件或系统环境变量。 如果你的脚本退出外壳(shell out)而到系统去加载另一程序或脚本,不要传递未经检查的用户给出的变量——尤其在UNIX系统中,那里system()调用可包含管道或重定向符使不经检查的变量可引起灾难。聪明的用户和恶意的窃入者会用这种方式拷贝敏感信息或破坏数据。你如果不能完全避免system()调用,则要小心地计划。确切定义什么能作为一个参数传递,并且知道哪些bit来自用户。在程序中包含一个分析可疑字符串并将其排斥掉的算法。 如果你的脚本存取外部文件,则要对如何处理并发做出计划。你可能会加锁部分或全部数据文件,建立一个信号量,或者使用一个文件作为一个信号量,决不要假想你的脚本是存取某一给定文件的唯一程序而毫不顾虑并发问题。你的脚本的5个拷贝可能会同时运行用以满足来自5个不同用户的请求。 注意 编程者使用信号量来同步多个程序、同一程序的多个实例,甚至是单个程序内的多个例程。一些操作系统具有对信号量的内置支持,另一些则要求编程者建立信号量策略。 以最简单的含义,信号量象一个开关,它的状态可以被检测:开关是打开的吗?如果是,则这样做;否则那样做,文件经常被用作信号量(文件存在否?存在则这样做,否则那样做)。一个更复杂的方法是向文件加锁以实现互斥存取(如果能得到锁,这样做,否则,等待一会儿并重试)。 在CGI编程中,信号量经常被用于同步同一CGI脚本的多个实例。举例来说,如果你的脚本必须更新一个文件,它不能假设文件随时可以得到。如果恰好该脚本的另一实例正在更新文件之中呢?第二个进程则必须等待,直至前一个完成,否则文件会被致命地破坏掉。解决的办法是使用一个信号量。要检测你的脚本以确保信号量被清掉。如果没有,它进入一个短循环,间隔地检测信号量。当信号量已被清掉,应设置信号量以避免其他程序介入,而后,便执行其临界区一一在这种情况下,写入文件一一然后再次清除信号量,使其他实例又能得到机会。信号量就是这样提供了一种管理并发安全性的方式。 如果需要加锁文件,则应使用最小限定。当仅仅读取一个数据文件时,对写加锁,并在读完之后立即释放锁。当更新一条记录时,只对记录(或一定范围的字节加锁。理想情况下,锁逻辑应该紧紧围绕实际I/O调用。不要在程序一开头便打开一个文件并锁住它直至终止。如果必要,可以立刻打开文件但不要加锁,直到真正要用它时,这样能让其他应用或者你脚本的其他实例能工作平滑和快速。 为意外事件准备良好的退出。举例来说,如果你的程序要求互斥地存取一个特定资源,准备好等待一段合理时间而后优雅地退出。决不要编写一个永久等待的调用。当你的程序从一个致命错误消亡时,要确保它在临终前对错误进行报告。错误报告应该使用简单明白的语言。如果可能,还应将错误写进一个日志文件,使得系统管理员能够知道它。 你如果在使用一种GUI语言来编写CGI脚本,不要把捕获的错误表现为一个屏幕上的消息盒。这是一个服务器应用,错误将很少被人注意到并且清除,你的程序会挡在那里直至系统管理员偶尔路过。埋藏所有的错误,在能够存活的地方工作,把其他的都看成天灾吧。 在启动代码编辑器之前,为你的例程写伪码,至少到一般逻辑结构一级。这常常有助于建立存根例程,使你能在开发中在程序时使用实际调用。存根例程(stub routine)是一个权宜之计,它并不实际做任何处理,仅仅接收最终例程期待的输入,返回结果一致的代码。 对于复杂的项目,一个数据流图将大有稗益。数据流应该有别于逻辑源。数据在程序中按照某条路径流动,为各个程序片段所拥有,不管它是如何被子例程变换的。 尽量封装私有数据和处理。你的例程应该有一个确定的输入和输出——一个门进,一个门出,并且要知道通过大门的是什么。你的例程如何完成其任务,这不是调用例程的事。这叫作“黑匣子方法”。从外面不应该看到箱子里面发生了什么,也不应对它产生影响。例如,一个正确封装的使用平面文件表的查找例程可以被置换为一个与后端数据库打交道的例程,而不用对程序的其余部分做何改变。 在进行中为程序做文档。自组织文档的代码是最好的方法,带有一般的注释和用于分隔代码的额外空行,如果使用了含义明确,很有说明性的变量和函数名,则事情已经做了一半。但好的文档不仅仅指出一般代码是做什么的,还要说明为什么这样做。例如:“给REQUEST-METHOD赋值pRequestMethod”指出代码是做什么的,“确定是否由GET或POST引发”则说明为何编写该段代码,并且,更理想的是引出下一段代码和文档:“如果由 GET启动,做这个”或“如果由POST启动,做这个。” 象计划输入那样仔细地定义输出。你给用户的消息应该是标准化的。例如,不要象这样报告一个文件加锁问题:“Couldn't obtain lock,Please try again later”,而报告一个栈溢出错误为“ERR4332”,成功消息也应该具有一致性,不要一次返回: you are the first visitor to this site since l/1/96 而下次回返: you are visitor number 2 since 01-01-96 如果按逻辑分类数据流和分组函数,则每种类型的消息应由相应于那种类型的例程产生。如果你把带有错误消息和ear1y-out成功消息的代码加入到程序的逻辑流中,那么终端用户来说,你的程序看上去不太一致,而对任何维护你的代码的人来说它是一团糟。 注意 early-out算法是一种用预先定义好的答案来检测异常和无意义情况并退出的算法,它不是以执行算法来决定答案的。例如,除法算法通常以两个操作检测一个除,并做一个移位而非除。 3 标准CGI环境变量 这里对常遇到的标准环境变量作一简要总结。各个服务器一致地实现了其中大部分, 但也有变化、例外和附加的情况。一般地,你更可能找到一个新的、没有归档的变量而非一个省略的归档变量。那么,唯一用来确认的办法就是检查你的服务器文献。本节内容来自于NCSA规范 ,是你所能找到的最接近“标准”的规范。NCSA CGI规范的URL如下: http://www.w3.org/hypertext/WWW/CGI/ 每当服务器加载脚本的一个实例时下述环境变量被设置,并且是私有和特定于该实例的: AUTH_TYPE 如果服务器支持基本的认证并且如果脚本被保护,此变量提供认证类型,此信息是特定于协议和服务器的。AUTH_TYPE的一个例子是BASIC。 CONTENT_LENGTH 如果请求通过POST方法包括数据,此变量被设置为提供通过STDIN的字节的合法数据的长度——如,72。 CONTENT_TYPE 如果请求包括数据,此变量指定数据类型为一个MIME头一一例如,application/x-www-form-urlencoded GATEWAY_INTERFACE 它提供被服务器支持的CGI接口的版本数,其格式为CGI/版本数:如CGI/1.1。 HTTP_ACCEPT 提供由逗号分开并被客户服务器可接受的MIME类型的列表,如image/gif,image/x-xbitmap,image/jpeg,image/pjpeg和*/*。此列表实际上来自浏览器本身,服务器只是将它传到CGI脚本。 HTTP_USER_AGENT 提供可能包含版本数或其他专有数据的客户沏览器名,如 Mozilla/2.0b3(Win NT;I)。 PATH_INFO 显示由客户提供并附在虚拟路径尾的任何附加的路径信息。它通常被用作脚本的参数 。例如,在URL http://www.yourcompany.com/cgi-bin/myscript.pl/dir1/dir2中,脚本为myscript.pl,PATH_INFO为/dirl/dlr2。 PATH_TRANSLATED 仅由部分服务器支持,此变量包含由虚拟路径到被执行脚本的转换(即虚拟路径到物理路径的映射)。例如,如果到你的Web服务器根的绝对路径为/usr/local/etc/httpd/htdocs,并且你的cgi-bin文件夹在Web服务器的根水平上(即,http://www.mycorp.com/cgi- bin),一个具有URL http://www.mycorp.com/cgi-bin/search.cgi的脚本将变量PATH_TRANSLATED 设置为/usr/local/etc/httpd/htdocs/cgi-bin/search.cgi。 QUERY_STRING 显示由客户提供的附在URL尾并用一个问号与脚本名分开的任何附加信息。例如, htt p://www.yourcompany.com/hello.html?name=joe&id=id=45中的name=joe&id=45为QUERY_STRING。 10)REMOTE_ADDR 它提供发请求客户的IP地址——如,199.1.166.171。此信息一直可用。 11)REMOTE_HO8T 它提供已分解的发请求客户的主机名。如dial-up102.abc.def.com。此信息通常不可用,这是由于两种原因:调用者的IP没能通过DNS正确映射到一个主机名,或是你的站点的Web管理员屏蔽了IP查找,Web管理员通常关闭查找是因为它们意味着在每次连接之后服务器要进行额外的步骤,这将降低服务器的运行效率。 EMOTE_IDENT 如果服务器和客户支持RFC931,此变量将包含由远程用户的计算机提供的识别信息。很少有服务器和客户还支持这种协议。这种信息也没什么价值,因为用户可把此信息设置为他们想要的任何东西。即使你的服务器支持也不要用这个变量。 REMOTE_USER 如果AUTH_TYPE被设置,此变量将包含用户提供并由服务器确认的用户名。 注意 AUTH_TYPE和REMOTE_USER仅在用户成功地使其标识在服务器上得到认证后(通常通过用户名和口令)才被设置,因此,这些变量仅在建立限定区域时并且仅在此区域中有用。 REQUEST_METHOD 它提供脚本被调用的方法。对于使用HTTP/1.0协议的脚本,仅GET和POST有意义。 SCRIPT_NAME 这是被调用脚本文件的名字,它对于自引用脚本很有用。例如,可用此变量产生一个通过GET方法被调用脚本的URL来产生并输出一个表单,这个表单被提交时通过POST法调用同样的脚本。通过使用此变量而非硬编码脚本名或位置将更容易做到维护——如,/cgi-bin/myscript.exe。当移动或更名脚本,当重新配置服务器而改变cgi-bin目录,或是在另外一台机器上安装脚本时,你不必改变代码。 SERVER_NAME 这是你的Web服务器的主机名、别名或IP地址。它对于在运行时产生指向服务器的URL是可靠的——如,www.ourcompany.com。 SERVER_PORT 这是本连接的端口号——如,80。 SERVER_PROTOCOL 这是本请求所用协议的名字/版本。如, HTTP/1.0。 SERVER一S0FTWARE 这是运行脚本的HTTP服务器的名字/版本。如,HTTPS/1.1。 4 CGI脚本可移植性 CGI程序员面临两种可移植性问题:平台独立性和服务器独立性。平台独立性是指代码不必修改就可以在不同于为其而写的硬件平台或操作系统上运行的能力。服务器独立性是指代码不必修改就可以在使用相同操作系统的另一台服务器上运行。 4.1 平台独立性 保持脚本可移植的最好办法就是要使用通用的语言,并且要避免使用平台特有的代码。听上去很简单,是吗?实际上,这就意味着要么用C语言要么用Perl语言,并且不不能做超出格式文本的事,也不能输出图形。 这是否就意味着不必考虑使用VisualBasic,AppleScript和Unix shell等语言呢?是的,我认为目前是这样的。然而,平台独立性并非是选择一个CGI平台时所考虑的唯一准则,还要考虑如代码速度、维护的简易性和执行所选择任务的能力等因素。 某此类型的操作是不可移植的。例如,如果你开发16位Windows程序,将很难在其他平台上找到所用函数VBX和DLL等效函数。如果开发的是32位windows,NT程序,你将会发现在UNIX环境下,所有异步Winsock调用都毫无意义。如果你的shell脚本调用一个System()来运行grep、并以管道形式将输出回送到你的程序,你将会发现,在windows NT或Windows 95环境下没有类似的东西。 如果你的指令之一是以最少的修改在平台之间移动代码的能力,你可能会发现用C语言将会取得最大的成功。用ANSI C库中的标准函数写代码并且要避免其他的操作系统调用。不幸的是,遵循这样的规则将会限制脚本的功能。然而,如果你将依赖于例程自带的代码包含平台,你就使需要从一个平台移至到另一平台的工作最小化了。如果在前面部分“计划脚本”中所看到的一样,当谈及封装性时,一个设计良好的程序在其整体中的任何模块被替换后不影响到程序的其他部分。运用这些原则,你可能不得不替换一两个好程序,而且当然也得重新编译,但是,你的程序将是可移植的。 Perl 脚本当然要比C程序更易维护,主要是因为没有编译这一步。在知道什么该修改时,可迅速修改程序。而事情难就难在这里:Perl今人恼火地迟钝,并且它的库比C语言的库更不一致——即使是在同平台上的不同版本之间也是如此。另外,windows NT下的perl相当新并且仍很奇特(似乎任何与Perl相关的东西都比其他部分更为奇特)。这个问题正在解决,但是不要在不理解Perl时就去使用它,几乎不可能直接从书上或联机资源上复制一个脚本而不需做任何修改就可在你的系统上运行。 一旦识别出依赖于平台的部分并且找到(或写出)能得到标准函数的库,在平台之间 移动代码就不会有太大的麻烦了。 4.2 服务器独立性 比平台独立性更为重要的是服务器独立性(除非你只是因爱好而写脚本)。服务器独立性相当容易实现,但是因为某些原因,它对初写脚本的人来说也有点难缠。要做到服务器独立性,你的脚本必须不做任何修改就可在使用相同操作系统的任何服务器上运行。只有独立于服务器的程序作为共享软件或免费软件才真正有用,并且毫无疑问,服务器独立性对于商业软件是必须的。 大多数编程人员考虑到的都是一些明显的问题,如不假定服务器有静态IP地址。接下来是服务器独立性的其他一些规则,尽管一旦指出来也很明显,但它还是一次次被忽略了: 不要假定你的环境 例如,不要因为你的开发环境上temp的目录为C:\TEMP就假定在脚本运行的其他地方目录也一样。不要将目录和文件名写死了,这将使Perl脚本的可读性更差,而这里对正确编程的曲解也时常发生。如果你的Perl脚本排除一定范围内IP地址的全部或部访问,就不要将此地址硬编码到你的程序中,也不要在注解中写上“改变此行”这样的话,应该用一个配置文件。 不要假定特权 在UNIX上,服务器(及你的脚本)可能是以用户nobody或root运行,或是以它们之间任何的特权水平运行的。在装有windows NT的机器上,CGI程序也继承了服务器的安全属性。检测访问权限并仔细检查返回代码,以便万一因不能访问其资源而使脚本执行失败是能提供给用户明了的错误信息。 不要假定CGI变量的一致性 一些服务器传递一些规定的CGI环境变量(如PATH和LIB变量),然而,它们传递的变量要依赖于运行时的环境。服务器配置也可影响CGI变量的数目和格式。如果有依赖于环境的输入,那么你的程序采取有相应的措施。 不要假定特定版本的信息 检测工作环境或检测告诉用户更改什么和为什么要更改的错误信息,服务器和操作系统版本都可影响脚本的环境。 不要假定LAN或WAN配置 在Windows NT环境下,服务器可能会是一个Windows NT工作站或是Windows NT服务器;它可能是独立的、工作组的一部分或是域的一部分。DNS(域名服务)可能或不能使用;查找可能会被限制在静态窗主机文件上。在UNIX环境下,不要假定任何关于如inetd、sendmail等守护程序的配置或系统环境,也不要假定目录名。对不能用系统调用找到的各项用一个配置文件,并给脚本维护人员指令以编辑脚本。 不要假定系统目标的可用性 用相应的特权,检测诸如数据库、消息队列、硬驱等目标的存在,并在找不到或配置错误时输出显式消息。没有什么比下载一个新脚本、安装而最后只得到“Run time error#203”的输出更令人恼火的了。 5 CGI库 通常谈及的CGI库有两种可能:一种是用户自己开发,并希望在其他项目中使用的代码库,另一种是公用的程序、例程和消息库。 5.1 个人库 如果你采纳了“计划脚本”中有关用黑匣子方式写代码的建议,你就会发现你正在创建一个将要反复使用的例程库。例如,在解决了如何解码URL编码数据这个问题后,就不必再去做这项工作。当你写好一个基本的main()函数后,该函数将可能为你所写的每一个CGI程序服务。这对一般的例程也一样,如查询数据库、解码输出、报告运行的错误。 如何管理个人库取决于所用的编程语言。用C语言和汇编语言可以将代码预编译进实际的1ib文件,然后可用它来链接程序。尽管也有可能,但这种方法对CGI来说是不必要的,而且它对于解释性语言(如Perl和VisualBasic)是无效的(尽管Perl和VB可以调用已编译好的库,但是不能用同使用C语言一样的静态方式来链接它们)。使用已编译好的库的好处是当改变库中的代码时不必重新编译所有的程序。如果库是在运行时(一个DLL)装入,就不必修改任何东西。如果库是被静态链接,所需做的只是重新链接。 另一种解决办法是保留独立的源文件,并在每个项目中包括这件文件。你可以将最为常用的例程放人一个非常大的文件中,而把其他不太常用的例程放到各自独立的文件中。以源文件格式保留文件会增加编译时间,但不必担心——尤其是同节省写代码的时间相比时。这种方法的不利之处是当修改库代码时,必须重新编译你的所有程序才能利用修改后的好处。 没有什么可以阻止你把公共域例程并入你的个人库中。一旦确定了版权和特许允许使用和修改源代码而不必付费或没有其他条件,你就可以将感兴趣的例程筛选进你的库中。 设计的归档良好的程序为新的程序提供了基础。如果仔细地将特定的程序组成部分分离成为例程,就没有什么理由不把整个程序的结构拆用到其他项目中。 也可以开发某些例程特定于平台的版本,并且,如果编译程序允许的话,可自动为建立的每种类型包括进正确的例程。最坏的情况下就得手工指定需要的例程。 使代码可重用的关键是尽量使代码通用,但也不是绝对通用。例如,美元纸币打印例程不需要通用到可同时处理美元和日元,但至少任何打印美元总量的程序都可调用它。在升级、增加功能甚至修改例程内容时,要保持每个函数的输入和输出不变。这就是实际上的黑匣子方法。通过保持调用约定和参数不变,就可自由升级任何代码段而不必担心破坏调用你的函数的程序。 另外一种要考虑的技术是使用函数框架。假定你最终决定打印日元和美元两者的单个例程实际上是最有效的方法。但是你已经有了分开的例程,并且旧的程序不知道如何将增加的参数传递给新的例程。你不必回头去修改调用旧例程的每个程序,你只需使用库中例程的框架,这样程序唯一要做的只是用正确的参数调用新的组合例程。在一些语言中,可通过重新定义例程声明部分而实现这一点;在其他一些语言中,就需要编码一个调用并要付出一些额外系统开销的代价。但既使这样,这个代价也远小于所有的旧程序遭到破坏的代价。 5.2公共库 Internet具有丰富的公共域范例代码、库和预编译程序。尽管你所能找到的这些大部分是面向UNIX的(因为它出现得时间较长),然而并不缺乏面向Windows NT的例程。 下面的序列是Internet上的一些最好的站点及对在每个站点上能找到什么的简要描述。这个序列中包含一部分站点。成百上千的站点致力于或是包含有关CGI编程的信息。打开你的web浏览器和最喜欢的搜索引擎并告诉它搜来“CGI”或“CGI Libraries”,你就会看到我说的那些东西了。为了使你免去那些乏味的击点,我已为你把它们都找了出来。下面是一些很有用的站点: http://www.ics.uci.edu/pub/websoft/libwww-perl 这是加州大学的公用库libwww一per1,它基于perl4.036版,包含许多很有用的例程。如果你打算用Perl编程,并想学习一些想法和技巧,这个库还是值得下载的。 http://www.bio.cam.ac.uk/web/form.html 这是由Steven E.Brenner以Perl 4为CGI编的库, Cgi-1ib.pl现在被认为是经典。它 也可以从许多其他站点上取得。 http://www-genome.wi.mit.edu/www/tools/scripting Cgi-utils.pl是Cgi-1ib.pl的扩展,它是由Whitehead研究所的Lincoln D.Stein完成的。 http://www-genome.wi.mit.edu/ftp/pub/software/WWW/cgi-docs.html Cgi.pm是用Perl 5编的用来创建表单和解码CGI输出的库。 http://www-genome.wi.mit.edu/www/tools/scripting/CGIperl/ 这是一个有关perl链接和实用程序的列表。 ftp://ftp.w3.org/pub/www/src/WWWDaemon-3.0.tar.Z Cgiparse是一个shell脚本工具,它是CERN服务器发布的一部分。也可以用Perl和c语言使用Cgiparse。 http://siva.cshl.org/gd/gd.html 它是一个用来动态生成GIF图像的c语言库,加使用户程序能用直线、弧线、文本和种种颜色创建图像,也能使程序从其它写成文件的图像和填充图中剪贴。用户程序可以吸收这些数据并把它包括到程序的输出中去。尽管这些库难以管理,但还是值得的。一些与图像有关的站点用这些例程产生指向图像位置的指针。 http://www-genome.wi.mit.edu/ftp/pub/software/WWW/gd.html GD.pm是gd的一个PERL扩展和包装程序,是由Cold Spring Harbor实验室的 Thomas Boutell写成。 http://www.iserver.com/cgi/library.html 这是internet服务器的非常好的一个小型次CGI库。在这个库中,可以找到图像映像样本,创建web 子目录、服务器上运行的动画例子,以及用户手册的例子。 http://raps.eit.com/wsk/dist/doc/libcgi/libcgi.html 这是一个极为有用的几乎能够完成任何常见CGI任务的C例程的集合。 http://www.charm.net/~web/Vlib/Providers/CGI.html 这是链接和实用程序的集合,它能帮你创建一个编辑器及用预定义类使用C++加入一个 CGI程序员的邮件列表,并且,最重要的是它能帮你浏览一个可单击——即插即用的CGI脚本。 http://www.greyware.com/greyware/software/ Greyware Automation Products提供了丰富的Windows NT共享软件和免费软件 程序的清单。对我们具有特别好处的是免费SSI实用程序、 CGI-wrapper程序以及允许你 用免费软件EMWAC HTTP服务器使用Visual Basic,Dephi或其他GUI编程环境。 http://canon.bhs.com/cgi-shl/dbm1.exe?action=query&template=/NTwebNet/appctr&udir=NEW 尽管不是特别适合于CGI,NI应用程序中心(由Bever1y Hills Software赞助)还是提 供了许多非常好的应用程序,其中一些与CGI有关。特别地,能够找到EMWAC的软件、Windows NT的Perl 和Perl库及SMTP邮件程序。 http://mfginfo.com/htm/website.htm 生产商的Information Net提供了一套丰富的Windows NT实用程序的链接,其中一些 与CGI相关。对我们特别有用的是到后端数据库接口和到许多Internet服务器组成部分的链接。 http://cervantes.comptons.com/software/software.htm 这是kevin Athey的Windows NT软件的清单,包括一个了不起的小的页面计数器(它越来越流行了)。你还可以享受Athey的其他软件。 http://website.ora.com/software/ Website的作者Bob Denny在Windows NT平台上普及HTTP服务器所做的工作大 概要比任何其他人做的都多。在这个站点上可以找到一个工具集合,包括在windows NT 下开发的Per1、为利用Website服务器而开发的VB例程和其他一些我们感兴趣的项目。 http://www.applets.com 这个站点具有所有最新的Java applet,通常包括源代码和小型指导。如果你打算写 Java程序,这个站点是获取灵感和教育所要首先访问的地方。 http://www.earthweb.com/Java/ 这是另一个演示EarthWeb成就的一流的Java站点,它包括了许多applet的源代码。 http://www.gamelan.com/ 这是EarthWeb的Gamelan页面:“Java资源的目录和档案。”这个站点是与Sun Microsystems(Java的发明者)联合开发和维护的,它列出了成百上千的Java app1et。 http://www.Javasoft.com/applets/applets.html 这是Sun Microsystem自己Java applet页面。尽管此站点由于很繁忙而没有任何实际用途,但它毕竟是取得Java信息的权威性的源泉,因此为了能够接通此站点,等待还是值得的。要得到Java说明书和白皮书可以看一下http://www.Javasoft.com/本身。 这个序列是可以没有止境的,但是这些对于你刚刚开始学习已经是足够了。 6 CGI的局限 CGI的最大局限是它的“无状态性”。一个HTTP服务器是不会记住两个请求组成——这些请求要么全是到同一服务器的,要么是到一些不同的服务器。每种情况下,服务器完成请求后,就挂起并忘记曾顺便访问过的用户。 能够记住一个呼叫者上次接通时做了什么的能力叫做“记住用户的状态”。HTTP以及CGI都没有自动保留状态信息,在Web事务中与状态信息最相近的东西是用户的浏览器高速缓冲区和CGI程序的智能。例如,如果一个用户在填写一个表单时漏填了一个必须的字段,CGI程序不能弹出一个警告框也不能拒绝接收输入。那么,这个程序唯一的选择是要么输出一条警告信息,告诉用户单击浏览器的back按钮,要么再次输出整个表单,填入提供的字段值并让用户重试,或者修改错误或者提供遗漏的信息。 有几种解决这个问题的办法,但都不是很令人满意。有一种想法是保留一个包含来自所有用户最新信息的文件,当一个新的请求传来时,在文件中找到这个用户并假定基于用户上一次所做事情的正确的程序状态。这种想法的问题是很难识别一个Web用户,即一个用户可能在今天没有完成操作而第二天又因其他目的再次访问这个站点。这种方法大量的精力都花费到了保留状态的算法上了,而这只是为了节省有限的一点时间。所以,这种解决问题的方法效率很低,并且它们忽视了其他的问题:即首先要识别用户。 你不能依靠用户来提供他或她的标识。不仅那些想匿名的用户,而且即使那些想让你知道他们名字的用户都可能一次又一次地将名字拼写错。那么,用IP地址作为用户标识又如何呢?也不好。每个通过同一代理的用户都使用相同的IP地址。在某一时刻,是公司内哪个雇员在呼叫呢?你说不出来的。不仅如此,现在许多人在每一次拨号时都使IP地址动态分配。你当然不会因为这一次John Joe和Jane Joe的IP地址相同,就给John Joe访问Jane Joe的数据的特权。 标识映射唯一可靠的形式是由服务器提供的,它运用名字和口令模式。即使这样,用户还是不能忍受每次请求时都需输入名字和口令,所以,服务器缓存数据并用前面提到的算法判断缓冲区何时变得非法。 假定你们单位的CEO没有使用其名字或其他可猜得到的东西作为口令,并且也没有人洗劫他秘书的抽屉,也没看过他监视器上的黄色便笺,那么在服务器告诉你他是CEO时你就有理由肯定他就是CEO。那么接下来做什么呢?你的CGI程序仍必须通过一些环节来防止CEO在查你的数据库时重复回答相同的问题。你的CGI程序的每个响应都必须包含从那点开始程序向前或向后进行的所有信息。这很麻烦但却很有必要。 第二个继承到CGI程序中的主要局限性是与围绕发送文档的设计HTTP规范的方式有关。HTTP不倾向于长交换和长交互性。这意味着当你的CGI程序要做一些像生成一个服务器推送的图形时,它必须保持连接打开。它是通过把各种图像都真正当成同一图像的组成部分而实现这一点的。 可怜的用户例览器还坚持显示“连接活动”的信号,以为它正在检索单个的文档。从浏览器的观点来看,文档只是偶尔有点过长。但从脚本的观点来看,文档实际上是由几十个(也许是成百个)独立的图像组成的,每个图像都是按顺序通过管道传输的,并且它被做为一个巨大文件的部分被标记,而这个文件实际上并不存在。 也许当下一代HTTP规范出现,并且例览器和服务器被更新以能利用保持有效的协议时,我们将会看到一些真正的革新。同时, CGI就会成为真正的CGI。尽管CGI偶尔不那么优雅,但它还是非常有用——并且很有意思。 7月15日 D. XPath例程代码 #include <libxml/parser.h> #include <libxml/xpath.h> xmlDocPtr getdoc (char *docname) { xmlDocPtr doc; doc = xmlParseFile(docname); if (doc == NULL ) { fprintf(stderr,"Document not parsed successfully. \n"); return NULL; } return doc; } xmlXPathObjectPtr getnodeset (xmlDocPtr doc, xmlChar *xpath){ xmlXPathContextPtr context; xmlXPathObjectPtr result; context = xmlXPathNewContext(doc); result = xmlXPathEvalExpression(xpath, context); if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ printf("No result\n"); return NULL; } xmlXPathFreeContext(context); return result; } int main(int argc, char **argv) { char *docname; xmlDocPtr doc; xmlChar *xpath = ("//keyword"); xmlNodeSetPtr nodeset; xmlXPathObjectPtr result; int i; xmlChar *keyword; if (argc <= 1) { printf("Usage: %s docname\n", argv[0]); return(0); } docname = argv[1]; doc = getdoc(docname); result = getnodeset (doc, xpath); if (result) { nodeset = result->nodesetval; for (i=0; i < nodeset->nodeNr; i++) { keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->printf ("keyword: %s\n", keyword); xmlFree(keyword); } xmlXPathFreeObject (result); } xmlFreeDoc(doc); xmlCleanupParser(); return (1); } E. 添加keyword例程代码 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <libxml/xmlmemory.h> #include <libxml/parser.h> void parseStory (xmlDocPtr doc, xmlNodePtr cur, char *keyword) { xmlNewTextChild (cur, NULL, "keyword", keyword); return; } xmlDocPtr parseDoc(char *docname, char *keyword) { xmlDocPtr doc; xmlNodePtr cur; doc = xmlParseFile(docname); if (doc == NULL ) { fprintf(stderr,"Document not parsed successfully. \n"); return (NULL); } cur = xmlDocGetRootElement(doc); if (cur == NULL) { fprintf(stderr,"empty document\n"); xmlFreeDoc(doc); return (NULL); } if (xmlStrcmp(cur->name, (const xmlChar *) "story")) { fprintf(stderr,"document of the wrong type, root node != story"); xmlFreeDoc(doc); return (NULL); } cur = cur->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))){ parseStory (doc, cur, keyword); } cur = cur->next; } return(doc); } int main(int argc, char **argv) { char *docname; char *keyword; xmlDocPtr doc; if (argc <= 2) { printf("Usage: %s docname, keyword\n", argv[0]); return(0); } docname = argv[1]; keyword = argv[2]; doc = parseDoc (docname, keyword); if (doc != NULL) { xmlSaveFormatFile (docname, doc, 0); xmlFreeDoc(doc); } return (1); } F. 添加属性例程代码 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <libxml/xmlmemory.h> #include <libxml/parser.h> xmlDocPtr parseDoc(char *docname, char *uri) { xmlDocPtr doc; xmlNodePtr cur; xmlNodePtr newnode; xmlAttrPtr newattr; doc = xmlParseFile(docname); if (doc == NULL ) { fprintf(stderr,"Document not parsed successfully. \n"); return (NULL); } cur = xmlDocGetRootElement(doc); if (cur == NULL) { fprintf(stderr,"empty document\n"); xmlFreeDoc(doc); return (NULL); } if (xmlStrcmp(cur->name, (const xmlChar *) "story")) { fprintf(stderr,"document of the wrong type, root node != story"); xmlFreeDoc(doc); return (NULL); } newnode = xmlNewTextChild (cur, NULL, "reference", NULL); newattr = xmlNewProp (newnode, "uri", uri); return(doc); } int main(int argc, char **argv) { char *docname; char *uri; xmlDocPtr doc; if (argc <= 2) { printf("Usage: %s docname, uri\n", argv[0]); return(0); } docname = argv[1]; uri = argv[2]; doc = parseDoc (docname, uri); if (doc != NULL) { xmlSaveFormatFile (docname, doc, 1); xmlFreeDoc(doc); } return (1); } G. 取得属性值例程代码 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <libxml/xmlmemory.h> #include <libxml/parser.h> void getReference (xmlDocPtr doc, xmlNodePtr cur) { xmlChar *uri; cur = cur->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"reference"))) { uri = xmlGetProp(cur, "uri"); printf("uri: %s\n", uri); xmlFree(uri); } cur = cur->next; } return; } void parseDoc(char *docname) { xmlDocPtr doc; xmlNodePtr cur; doc = xmlParseFile(docname); if (doc == NULL ) { fprintf(stderr,"Document not parsed successfully. \n"); return; } cur = xmlDocGetRootElement(doc); if (cur == NULL) { fprintf(stderr,"empty document\n"); xmlFreeDoc(doc); return; } if (xmlStrcmp(cur->name, (const xmlChar *) "story")) { fprintf(stderr,"document of the wrong type, root node != story"); xmlFreeDoc(doc); return; } getReference (doc, cur); xmlFreeDoc(doc); return; } int main(int argc, char **argv) { char *docname; if (argc <= 1) { printf("Usage: %s docname\n", argv[0]); return(0); } docname = argv[1]; parseDoc (docname); return (1); } H. 编码转换例程代码 #include <string.h> #include <libxml/parser.h> unsigned char* convert (unsigned char *in, char *encoding) { unsigned char *out; int ret,size,out_size,temp; xmlCharEncodingHandlerPtr handler; size = (int)strlen(in)+1; out_size = size*2-1; out = malloc((size_t)out_size); if (out) { handler = xmlFindCharEncodingHandler(encoding); if (!handler) { free(out); out = NULL; } } if (out) { temp=size-1; ret = handler->input(out, &out_size, in, &temp); if (ret || temp-size+1) { if (ret) { printf("conversion wasn't successful.\n"); } else { printf("conversion wasn't successful. converted:"); } free(out); out = NULL; } else { out = realloc(out,out_size+1); out[out_size]=0; /*null terminating out*/ } } else { printf("no mem\n"); } return (out); } int main(int argc, char **argv) { unsigned char *content, *out; xmlDocPtr doc; xmlNodePtr rootnode; char *encoding = "ISO-8859-1"; if (argc <= 1) { printf("Usage: %s content\n", argv[0]); return(0); } content = argv[1]; out = convert(content, encoding); doc = xmlNewDoc ("1.0"); rootnode = xmlNewDocNode(doc, NULL, (const xmlChar*)"root", out); xmlDocSetRootElement(doc, rootnode); xmlSaveFormatFileEnc("-", doc, encoding, 1); return (1); } char *convert(char *instr,char *encoding) { xmlCharEncodingHandlerPtr handler; xmlBufferPtr in,out; handler = xmlFindCharEncodingHandler(encoding); if(NULL != handler) { in = xmlBufferCreate(); xmlBufferWriteChar(in,instr); out = xmlBufferCreate(); if(xmlCharEncInFunc(handler, out, in) 〈 0) { xmlBufferFree(in); xmlBufferFree(out); return NULL; } else { xmlBufferFree(in); return (char *)out-〉content; } } } 写属性 写属性类似于给一个新元素写文本。在这个例子中,我们将添加一个reference结点URI属性到我们的文档中。完整代码:附录F,添加属性例程代码。 reference是story元素的一个子结点,所以找到并插入新元素及其属性是简单的。一旦我们在parseDoc进行了错误检查,我们将在正确的位置加放我们的新元素。但进行之前我们需要定义一个此前我们不见过的数据类型。 xmlAttrPtr newattr; 我们也需要xmlNodePtr: xmlNodePtr newnode; 剩下的parseDoc则和前面一样,检查根结点是否为story。如果是的,那我们知道我们将在指定的位置添加我们的元素。 ① newnode = xmlNewTextChild (cur, NULL, "reference", NULL); ②newattr = xmlNewProp (newnode, "uri", uri); ①使用xmlNewTextChild函数添国一个新结点到当前结点位置。 一旦结点被添加,文件应像前面的例子将我们添加的元素及文本内容写入磁盘。 取得属性 取得属性值类似于前面我们取得一个结点的文本内容。在这个例子中,我们将取出我们在前一部分添加的URI的值。完整代码:附录G,取得属性值例程代码。 这个例子的初始步骤和前面是类似的:解析文档,查找你感兴趣的元素,然后进入一个函数完成指定的请求任务。在这个例子中,我们调用getReference。 void getReference (xmlDocPtr doc, xmlNodePtr cur) { xmlChar *uri; cur = cur->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"reference"))) { ① uri = xmlGetProp(cur, "uri"); printf("uri: %s\n", uri); xmlFree(uri); } cur = cur->next; } return; } ① 关键函数是xmlGetProp,它返回一个包含属性值的xmlChar。在本例中,我们仅仅打印它。 注释 如果你使用DTD定义属性的固定值或缺省值,这个函数也将取得它。 编码转换 数据编码兼容问题是程序员新建普通的XML或特定XML时最常见的困难。按照这里 稍后的讨论来思考设计你的应用程序将帮助你避免这个困难。实际上,libxml能以UTF-8格式保存和操纵多种数据 你的程序使用其它的数据格式,比如常见的ISO-8859-1编码,必须使用libxml函数转换到UTF-8。如果你想你的程序以除UTF-8外的其它编码方式输出也必须做转换。 如果能有效地转换数据Libxml将使用转换器。无转换器时,仅仅UTF-8、UTF-16和ISO-8859-1能够被作为外部格式使用。有转换器时,它能将从其它格式与UTF-8互换的任何格式均可使用。当前转换器支持大约150种不同的编码格式之间的相互转换。实际支持的格式数量正在被实现。每一个实现在的转换器尽可能的支持每一种格式。 警告 一个常见错误是在内部数据不同的部分使用不同的编码格式。最常见的是情况是一个应用以ISO-8859-1作为内部数据格式,结合libxml部分使用UTF-8格式。结果是一个应用程序要面对不同地内部数据格式。一部分代码执行后,它或其它部分代码将使用曲解的数据。 这个例子构造一个简单的文档,然后添加在命令行提供的内容到根元素并使用适当的编码将结果输出到标准输出设备上。在这个例子中,我们使用ISO-8859 -1编码。在命令输入的内容将被从ISO-8859-1转换到UTF-8。完整代码:附件H,编码转换例程代码。 包含在例子中的转换函数使用libxml的xmlFindCharEncodingHandler函数。 ①xmlCharEncodingHandlerPtr handler; ②size = (int)strlen(in)+1; out_size = size*2-1; out = malloc((size_t)out_size); … ③handler = xmlFindCharEncodingHandler(encoding); … ④handler->input(out, &out_size, in, &temp); … ⑤xmlSaveFormatFileEnc("-", doc, encoding, 1); ①定义一个xmlCharEncodingHandler函数指针。 ②XmlCharEncodingHandler函数需要给出输入和输出字符串的大小,这里计算输入输出字符串。 ③XmlFindCharEncodingHandler使用数据初始编码作为参数搜索libxml已经完成的转换器句柄并将找到的函数指针返回,如果没有找到则返回NULL。 ④The conversion function identified by handler requires as its arguments pointers to the input and output strings, along with the length of each. The lengths must be determined separately by the application. 由句柄指定的转换函数请求输入、输出字符中及它们的长度作为参数。这个长度必须由应用程序分别指定。 ⑤用指定编码而不是UTF-8输出,我们使用xmlSaveFormatFileEnc指不定期编码方式。 A. 编译 Libxml包含一个脚本xml2-config,它一般用于编译和链接程序到库时产生标志。 为了取得预处理和编译标志,使用xml2-config –cflags,为了取得链接标志,使用xml2-config –libs。其它有效的参数请使用xml2-config –help查阅。 B. 示例文档 <?xml version="1.0"?> <story> <storyinfo> <author>John Fleck</author> <datewritten>June 2, 2002</datewritten> <keyword>example keyword</keyword> </storyinfo> <body> <headline>This is the headline</headline> <para>This is the body text.</para> </body> </story> C. Keyword例程代码 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <libxml/xmlmemory.h> #include <libxml/parser.h> void parseStory (xmlDocPtr doc, xmlNodePtr cur) { xmlChar *key; cur = cur->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) { key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); printf("keyword: %s\n", key); xmlFree(key); } cur = cur->next; } return; } static void parseDoc(char *docname) { xmlDocPtr doc; xmlNodePtr cur; doc = xmlParseFile(docname); if (doc == NULL ) { fprintf(stderr,"Document not parsed successfully. \n"); return; } cur = xmlDocGetRootElement(doc); if (cur == NULL) { fprintf(stderr,"empty document\n"); xmlFreeDoc(doc); return; } if (xmlStrcmp(cur->name, (const xmlChar *) "story")) { fprintf(stderr,"document of the wrong type, root node != story"); xmlFreeDoc(doc); return; } cur = cur->xmlChildrenNode; while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))){ parseStory (doc, cur); } cur = cur->next; } xmlFreeDoc(doc); return; } int main(int argc, char **argv) { char *docname; if (argc <= 1) { printf("Usage: %s docname\n", argv[0]); return(0); } docname = argv[1]; parseDoc (docname); return (1); } Libxml 是一个实现读、创建及操纵XML数据功能的C语言库。这个指南提供例子代码并给出它基本功能的解释。在这个项目的主页上有Libxml及更多关于它可用的资料。包含有完整的API文档。这个指南并不能替代这些完整的文档,但是阐明功能需要使用库来完成基本操作。 本指南中的例子代码示范如何做到: • 解析文档 • 取得指定元素的文本 • 添加一个元素及它的内容 • 添加一个属性 • 取得一个属性的值 例子的完整代码包含在附录中 数据类型 Libxml 定义了许多数据类型,我们将反复碰到它们,它隐藏了杂乱的来源以致你不必处理它除非你有特定的需要。xmlChar 替代char,使用UTF-8编码的一字节字符串。如果你的数据使用其它编码,它必须被转换到UTF-8才能使用libxml的函数。在libxml编码支持WEB页面有更多关于编码的有用信息。 XmlDoc 包含由解析文档建立的树结构,xmlDocPtr是指向这个结构的指针。 xmlNodePtr and xmlNode 包含单一结点的结构xmlNodePtr是指向这个结构的指针,它被用于遍历文档树。 解析文档 解析文档时仅仅需要文件名并只调用一个函数,并有错误检查。完整代码:附录C, Keyword例程代码 ①xmlDocPtr doc; ②xmlNodePtr cur; ③doc = xmlParseFile(docname); ④if (doc == NULL ) { fprintf(stderr,"Document not parsed successfully. \n"); return; } ⑤cur = xmlDocGetRootElement(doc); ⑥if (cur == NULL) { fprintf(stderr,"empty document\n"); xmlFreeDoc(doc); return; } ⑦if (xmlStrcmp(cur->name, (const xmlChar *) "story")) { fprintf(stderr,"document of the wrong type, root node != story"); xmlFreeDoc(doc); return; } ①定义解析文档指针。 ②定义结点指针(你需要它为了在各个结点间移动)。 ④检查解析文档是否成功,如果不成功,libxml将指一个注册的错误并停止。 注释 一个常见错误是不适当的编码。XML标准文档除了用UTF-8或UTF-16外还可用其它编码保存。如果文档是这样,libxml将自动地为你转换到UTF-8。更多关于XML编码信息包含在XML标准中。 ⑤取得文档根元素 ⑥检查确认当前文档中包含内容。 ⑦在这个例子中,我们需要确认文档是正确的类型。“Story”是在这个指南中使用文档的根类型。 取得元素内容 你找到在文档树中你要查找的元素后可以取得它的内容。在这个例子中我们查找“story”元素。进程将在冗长的树中查找我们感兴趣的元素。我们假定期你已经有了一个名为doc的xmlDocPtr和一个名为cur的xmlNodPtr。 ①cur = cur->xmlChildrenNode; ②while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"storyinfo"))){ parseStory (doc, cur); } cur = cur->next; } ①取得cur的第一个子结点,cur指向文档的根,即“story”元素。 ②这个循环迭代通过“story”的子元素查找“storyinfo”。这是一个包含有我们将查找的“keywords”的元素。它使用了libxml字符串比较函数xmlStrcmp。如果相符,它调用函数parseStory。 void parseStory (xmlDocPtr doc, xmlNodePtr cur) { xmlChar *key; ① cur = cur->xmlChildrenNode; ② while (cur != NULL) { if ((!xmlStrcmp(cur->name, (const xmlChar *)"keyword"))) { ③ key = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1); printf("keyword: %s\n", key); xmlFree(key); } cur = cur->next; } return; } ① 再次取得第一个子结点。 ② 像上面那个循环一样,我们能过迭代,查找我们感兴趣的叫做“keyword”的元素。 ③ 当我们找到元素“keyword”时,我们需要打印它包含在XML中的记录的内容,文本被包含于元素的子结点中,因此我们借助了cur-> xmlChildrenNode,为了取得文本,我们使用函数xmlNodeListGetString,它有一个文档指针参数,在这个例子中,我们仅仅打印它。 注释 因为xmlNodeListGetString为它返回的字符串分配内存,你必须使用xmlFree释放它。 使用XPath取得元素内容 除了一步步遍历文档树查找元素外,Libxml2包含支持使用Xpath表达式取得指定结点集。完整的Xpath API文档在这里。Xpath允许通过路径文档搜索匹配指定条件的结点。在下面的例子中,我们搜索文档中所有的“keyword”元素。 注释 下面是Xpath完整的讨论。它详细的使用资料,请查阅Xpath规范。 这个例子完整的代码参见附录D,XPath例程代码。 Using XPath requires setting up an xmlXPathContext and then supplying the XPath expression and the context to the xmlXPathEvalExpression function. The function returns an xmlXPathObjectPtr, which includes the set of nodes satisfying the XPath expression. 使用XPath需要安装xmlXPathContext才支持XPath表达式及xmlXPathEvalExpression函数,这个函数返回一个xmlXPathObjectPtr,它包含有 XPath表达式的结点集。 xmlXPathObjectPtr getnodeset (xmlDocPtr doc, xmlChar *xpath){ ①xmlXPathContextPtr context; xmlXPathObjectPtr result; ②context = xmlXPathNewContext(doc); ③result = xmlXPathEvalExpression(xpath, context); ④if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ printf("No result\n"); return NULL; } xmlXPathFreeContext(context); return result; } ①首先定义变量 ②初始化变量context ③应用XPath表达式 ④检查结果 由函数返回的xmlPathObjectPtr包含一个结点集和其它需要被迭代及操作的信息。在这个例子中我们的函数返回 xmlXPathObjectPtr,我们使用它打印我们文档中keyword结点的内容。这个结点集对象包含在集合(nodeNr)中的元素数目及一个结点(nodeTab)数组。 ①for (i=0; i < nodeset->nodeNr; i++) { ②keyword = xmlNodeListGetString(doc, nodeset->nodeTab[i]->xmlChildrenNode, printf("keyword: %s\n", keyword); xmlFree(keyword); } ①变量nodeset->Nr持有结点集中元素的数量。我们使用它遍历数组。 ②打印每个结点包含的内容。 注释 Note that we are printing the child node of the node that is returned, because the contents of the keyword element are a child text node.注意我们打印的是结点的子结点的返回值,因为keyword元素的内容是一个子文本结点。 写元素 写元素内容使用上面许多一样的步骤—解析文档并遍历树。我们先解析文档然后遍历树查找我们想插入元素的位置。在这个例子中,我们再一次查找“storyinfo ”元素并插入一个keyword。然后我们装文件写入磁盘。完整代码:附录E,添加keyword例程 本例中主要的不同在于parseStory void parseStory (xmlDocPtr doc, xmlNodePtr cur, char *keyword) { ①xmlNewTextChild (cur, NULL, "keyword", keyword); return; } ①XmlNewTextChild函数添加一个当前结点的新的子元素到树中 一旦结点被添加,我们应当写文档到文件中。你是否想给元素指定一个命名空间?你能添加它,在我们的例子中,命名空间是NULL。 xmlSaveFormatFile (docname, doc, 1); 第一个参数是写入文件的名,你注意到和我们刚刚读入的文件名是一样的。在这个例子中,我们仅仅覆盖原来的文件。第二个参数是一个xmlDoc结构指针,第三个参数设定为1,保证在输出上写入。 我从网上下载的libxml2-2.6.30,解压后用 ./configure --host=arm-linux-gnu --target=arm-linux CC=arm-linux-gcc AR=arm-linux-ar LD=arm-linux-ld RANLIB=arm-linux-ranlib --prefix=/usr/local/arm/3.4.1/arm-linux/ 进行配置,然后 make make install 将/usr/local/arm/3.4.1/arm-linux/lib/libxml2.a和/usr/local/arm/3.4.1/arm-linux/include/libxml2下的包含头文件的文件夹libxml一起拷贝到我的程序文件夹下,用 arm-linux-gcc -Wall -lm -L ./ -I ./ xmlfile.c -o testxml -lxml2 交叉编译,结果出现错误如下: .//libxml2.a(xmlIO.o)(.text 0xa68): In function `xmlGzfileOpen_real': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1108: undefined reference to `gzdopen' .//libxml2.a(xmlIO.o)(.text 0xaa8):/home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1132: undefined reference to `gzopen' .//libxml2.a(xmlIO.o)(.text 0xb7c): In function `xmlGzfileOpenW': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1178: undefined reference to `gzdopen' .//libxml2.a(xmlIO.o)(.text 0xbb0):/home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1200: undefined reference to `gzopen' .//libxml2.a(xmlIO.o)(.text 0xbe8): In function `xmlGzfileRead': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1219: undefined reference to `gzread' .//libxml2.a(xmlIO.o)(.text 0xc18): In function `xmlGzfileWrite': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1239: undefined reference to `gzwrite' .//libxml2.a(xmlIO.o)(.text 0xc48): In function `xmlGzfileClose': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1255: undefined reference to `gzclose' .//libxml2.a(xmlIO.o)(.text 0xccc): In function `xmlFreeZMemBuff': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1361: undefined reference to `deflateEnd' .//libxml2.a(xmlIO.o)(.text 0x141c): In function `__xmlParserInputBufferCreateFilename': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:2411: undefined reference to `gzread' .//libxml2.a(xmlIO.o)(.text 0x144c):/home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:2416: undefined reference to `gzrewind' .//libxml2.a(xmlIO.o)(.text 0x1ea0): In function `xmlIOHTTPCloseWrite': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1563: undefined reference to `deflate' .//libxml2.a(xmlIO.o)(.text 0x2184): In function `xmlIOHTTPOpenW': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1405: undefined reference to `deflateInit2_' .//libxml2.a(xmlIO.o)(.text 0x2230):/home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1420: undefined reference to `crc32' .//libxml2.a(xmlIO.o)(.text 0x288c): In function `xmlIOHTTPWrite': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1522: undefined reference to `deflate' .//libxml2.a(xmlIO.o)(.text 0x28b0):/home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/xmlIO.c:1534: undefined reference to `crc32' .//libxml2.a(nanohttp.o)(.text 0x4dc): In function `xmlNanoHTTPFreeCtxt': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/nanohttp.c:422: undefined reference to `inflateEnd' .//libxml2.a(nanohttp.o)(.text 0x1068): In function `xmlNanoHTTPRead': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/nanohttp.c:1214: undefined reference to `inflate' .//libxml2.a(nanohttp.o)(.text 0x1a98): In function `xmlNanoHTTPMethodRedir': /home/lixin/WORK/XML/CreateXmlStaticLib/libxml2-2.6.30/nanohttp.c:782: undefined reference to `inflateInit2_' collect2: ld returned 1 exit status 是什么原因呢??该如何解决呢??各位帮忙看看。 我用动态库链接时没有问题。 网友回复:仔细查看源码后发现所有这些未定义函数都包含在zlib.h头文件中,在程序里这些函数都是根据如下宏定义去执行的: #ifdef HAVE_ZLIB_H #include <zlib.h> #endif ............ #ifdef HAVE_ZLIB_H .......执行函数........ #endif 但我找了好久也没找到在其他文件的什么位置有#define HAVE_ZLIB_H出现。即根本就没有包含头文件。 不知道是不是这个原因,仅当参考吧,各位再帮忙看看该怎么解决,谢了!! 网友回复:HAVE_ZLIB_H 定义一般在config.h用./configure生成的 configure 时加上 --without-zlib参数 当然也可以安装zlib然后链接程序时加上 网友回复:谢谢楼上,问题已解决了!! 结贴..... 最近在做linux c xml 编程测试。 在网络上似乎找不到太多的资料。 我觉得 http://www.xmlsoft.org/tutorial/index.html 的教程不错。给大家介绍一下。。是英文,不过很简单。相信做编程的人不会看不懂。。 给出国内的一资料,写得也很不错。(程序 我测试过了) 使用简介 数据类型: xmlChar 替代char,使用UTF-8编码的一字节字符串。如果你的数据使用其它编码,它必须被转换到UTF-8才能使用libxml的函数。 XmlDoc 包含由解析文档建立的树结构,xmlDocPtr是指向这个结构的指针。 xmlNodePtr and xmlNode 包含单一结点的结构 xmlNodePtr是指向这个结构的指针,它被用于遍历文档树。 优点:1. 安装、使用比较简单,容易入门;2. 支持的编码格式较多,能很好的解决中文问题(使用一个很简单的编码转换函数);3. 支持Xpath解析(这点对于任意定位xml文档中的节点还是很有用的哦);4.支持Well-formed 和valid验证,具体而言支持DTD验证,Schema验证功能正在完善中(目前多数解析器都还不完全支持shema验证功能);5. 支持目前通用的Dom、Sax方式解析等等。 不足:1. 指针太多,使用不当时就会出现错误,在Linux系统中表现为常见的段错误,同样管理不当易造成内存泄漏;2.个人认为内面有些函数的功能设计的不是很好(比如获取Xpath函数,它不获取节点属性,这样子有些情况会定位不准)。 在学习libxml2中,最好的学习手册就是由官方开发者提供的开发手册就是libxml2-devel-2.6.19,rpm –q –d libxml2获得文档路径,就是它了。 关于xml 开始研究 LibXML2 库之前,让我们先来巩固一下 XML 的相关基础。XML 是一种基于文本的格式,它可用来创建能够通过各种语言和平台访问的结构化数据。它包括一系列类似 HTML 的标记,并以树型结构来对这些标记进行排列。 例如,可参见清单 1 中介绍的简单文档。这是配置文件部分中研究的配置文件示例的简化版本。为了更清楚地显示 XML 的一般概念,所以对其进行了简化。 清单 1. 一个简单的 XML 文件 <?xml version="1.0" encoding="UTF-8"?> <files> <owner>root</owner> <action>delete</action> <age units="days">10</age> </files> 清单 1 中的第一行是 XML 声明,它告诉负责处理 XML 的应用程序,即解析器,将要处理的 XML 的版本。大部分的文件使用版本 1.0 编写,但也有少量的版本 1.1 的文件。它还定义了所使用的编码。大部分文件使用 UTF-8,但是,XML 设计用来集成各种语言中的数据,包括那些不使用英语字母的语言。 接下来出现的是元素。一个元素以开始标记开始(如 <files>),并以结束标记结束(如 </files>),其中使用斜线 (/) 来区别于开始标记。 元素是Node的一种类型。XML 文档对象模型 (DOM) 定义了几种不同的Nodes类型,包括Elements(如files或者age)、Attributes(如units)和 Text(如root或者10)。元素可以具有子节点。例如,age 元素有一个子元素,即文本节点10。而 files 元素有七个子元素。其中三个很明显。它们分别是三个子元素:owner、action和age。其他四个分别是元素前后的空白文本符号。 XML 解析器可以利用这种父子结构来遍历文档,甚至修改文档的结构或内容。LibXML2 是这样的解析器中的其中一种,并且文中的示例应用程序正是使用这种结构来实现该目的。对于各种不同的环境,有许多不同的解析器和库。LibXML2 是用于 UNIX 环境的解析器和库中最好的一种,并且经过扩展,它提供了对几种脚本语言的支持,如 Perl 和 Python。 1 tree /******************************************* * compile: gcc -I/usr/include/libxml2/ -lxml2 tree1.c * usage: create a xml tree * *******************************************/ #include <stdio.h> #include <libxml/parser.h> #include <libxml/tree.h> int main(int argc, char **argv) { xmlDocPtr doc = NULL; /* document pointer */ xmlNodePtr root_node = NULL, node = NULL, node1 = NULL; /* node pointers */ //Creates a new document, a node and set it as a root node doc = xmlNewDoc(BAD_CAST "1.0"); root_node = xmlNewNode(NULL, BAD_CAST "root"); xmlDocSetRootElement(doc, root_node); //creates a new node, which is "attached" as child node of root_node node. xmlNewChild(root_node, NULL, BAD_CAST "node1",BAD_CAST "content of node1"); // xmlNewProp() creates attributes, which is "attached" to an node. node=xmlNewChild(root_node, NULL, BAD_CAST "node3", BAD_CAST"node has attributes"); xmlNewProp(node, BAD_CAST "attribute", BAD_CAST "yes"); //Here goes another way to create nodes. node = xmlNewNode(NULL, BAD_CAST "node4"); node1 = xmlNewText(BAD_CAST"other way to create content"); xmlAddChild(node, node1); xmlAddChild(root_node, node); //Dumping document to stdio or file xmlSaveFormatFileEnc(argc > 1 ? argv[1] : "-", doc, "UTF-8", 1); /*free the document */ xmlFreeDoc(doc); xmlCleanupParser(); xmlMemoryDump(); //debug memory for regression tests return(0); } 生成的xml: [denny@localhost xml]$ gcc -I/usr/include/libxml2/ -lxml2 tree1.c [denny@localhost xml]$ ./a.out <?xml version="1.0" encoding="UTF-8"?> <root> <node1>content of node1</node1> <node3 attribute="yes">node has attributes</node3> <node4>other way to create content</node4> </root> 执行序列: 1 声明指针:文档指针(xmlDocPtr),结点指针(xmlNodePtr); 2 生成文档doc:xmlNewDoc 3 生成根结点root_node: xmlNewDocNode ,xmlNewNode 4 文档与根结点捆绑: xmlDocSetRootElement 5 结点操作 1)创建子结点:xmlNewChild或xmlNewNode 2)设置结点属性:xmlNewProp 3)设置结点值:xmlNewText,xmlNewChild, xmlAddChild 6 释放内存:xmlFreeDoc,xmlMemoryDump 7 lib的载入退出: LIBXML_TEST_VERSION , xmlCleanupParser 2 parse 对于应用程序来说,读取 XML 文件的第一步是加载该数据并将其解析为一个Document对象。在此基础上,可以对 DOM 树进行遍历以获取特定的节点。 /******************************************* * compile: gcc -I/usr/include/libxml2/ -lxml2 tree1.c * usage: tree2 filename_or_URL * *******************************************/ #include <stdio.h> #include <libxml/parser.h> #include <libxml/tree.h> #ifdef LIBXML_TREE_ENABLED static void print_element_names(xmlNode * a_node) { xmlNode *cur_node = NULL; for (cur_node = a_node; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE) { printf("node type: Element, name: %s\n", cur_node->name); } print_element_names(cur_node->children); } } /** * Simple example to parse a file called "file.xml", * walk down the DOM, and print the name of the * xml elements nodes. */ int main(int argc, char **argv) { xmlDoc *doc = NULL; xmlNode *root_element = NULL; if (argc != 2) return(1); //LIBXML_TEST_VERSION /*parse the file and get the DOM */ doc = xmlReadFile(argv[1], NULL, 0); if (doc == NULL) { printf("error: could not parse file %s\n", argv[1]); } /*Get the root element node */ root_element = xmlDocGetRootElement(doc); print_element_names(root_element); /*free the document */ xmlFreeDoc(doc); //xmlCleanupParser(); return 0; } #else int main(void) { fprintf(stderr, "Tree support not compiled in\n"); exit(1); } #endif 执行序列: 1 声明指针:文档指针(xmlDocPtr),结点指针(xmlNodePtr); 2 得到文档doc: xmlReadFile 3 得到根结点root_node:xmlDocGetRootElement 4 结点操作: 1)获得到结点值:xmlNodeGetContent(对应于xmlFree) 2)遍历: 指向下一个结点:xmlNodePtr ->children 结点值:xmlNodePtr->name, 结点内遍历:xmlNodePtr->next 5 释放内存:xmlFreeDoc,xmlFree 在基于ARM的嵌入式系统开发中,常常用到交叉编译的GCC工具链有两种:arm-linux-*和 arm-elf-*,两者区别主要在于使用不同的C库文件。arm-linux-*使用GNU的Glibc,而arm-elf-*一般使用 uClibc/uC-libc或者使用REDHAT专门为嵌入式系统的开发的C库newlib.Glibc。uClibc/uC-libc以及 newlib都是C语言库文件,只是所应用的领域不同而已,Glibc是针对PC开发的,uClibc/uC-libc是与Glibc API兼容的小型化C语言库,实现了Glibc部分功能。 关于uClibc/uC-libc的说明,详见如下: There are two libc libraries commonly used with uClinux. uC-libc and uClibc. They are quite different despite their similar names. Here is a quick overview of how they are different. uC-libc is the original library for uClinux. It was based on sources from the Linux-8086 C library which was part of the ELKs project with m68000 support added by Jeff Dionne and Kenneth Albanowski. It is a fairly complete libc implementation, however, some of the API's are a little non-standard and quite a few common libc routines are not present. Currently it has stable support for m68000, ColdFire and ARM (Non-MMU) architectures. It was primary design goal is to be small and light weight. It does try to conform to any standards, although its API tries to be compatible with most libcs, it is not always exactly the same. The uClinux distribution provides an environment that can compile using either uC-libc or uClibc depending on your needs. For m68000 and Coldfire platforms it is generally better to chose uC-libc as it supports shared libraries and is the most commonly used libc for these CPUs. uClibc also works quite well with almost all platforms supported by the distribution. Which libc you choose to use will be decided by your requirements uClinux有两个经常使用的libc库:uC-libc和uClibc。虽然两者名字很相似,其实有差别,下面就简单的介绍一下二者的不同之处。uC -libc是最早为uClinux开发的库,是Jeff Dionne和Kenneth Albanowski为在EKLs项目中支持m68000在Linux-8086 C库源码上移植的。uC-libc是一个完全的libc实现,但其中有一些api是非标准的,有些libc的标准也没有实现。uC-libc稳定地支持 m68000,ColdFire和没有MMU的ARM。其主要设计目标是“小”、"轻",并尽量与标准一致,虽然它的API和很多libc兼容,但是似乎并不像它期望的那样和所有标准一致。 uClibc就是为了解决这个问题从uC-libc中发展出来的。它的所有API都是标准的(正确的返回类型,参数等等),它弥补了uC-libc中没有实现的libc标准,现在已经被移植到多种架构中。一般来讲,它尽量兼容glibc以便使应用程序用uClibc改写变的容易。uClibc能够在标准的 VM linux和uClinux上面使用。为了应用程序的简洁,它甚至可以在许多支持MMU的平台上被编译成共享库。Erik Anderson在uClibc背后做了很多的工作。uClibc支持许多系列的处理器:m68000,Coldfire,ARM,MIPS,v850, x86,i960,Sparc,SuperH,Alpha,PowerPC和Hitachi 8。不断增加的平台支持显示uClibc能够很容易的适应新的架构。uClinux发行版提供了环境能够让你选择使用uC-libc或是uClibc编译。对于m68000和Coldfire平台来说,选择uC-libc还是稍微好一点,因为它支持共享库,而共享库是这些cpu经常使用的 libc.uClibc也几乎和所有的平台都能很好的工作。选择哪种libc取决于你的需求。 newlib 是一个用于嵌入式系统的开放源代码的C语言程序库,由libc和libm两个库组成,特点是轻量级,速度快,可移植到很多CPU结构上。newlib实现了许多复杂的功能,包括字符串支持,浮点运算,内存分配(如malloc)和I/O流函数(printf,fprinf()等等)。其中libc提供了c 语言库的实现,而libm提供了浮点运算支持。 在为ARM交叉编译gcc编译器时,对gcc指定不同的配置选项时,使用的C语言库就不同,gcc编译器默认使用Glibc,也可以使用 uClibc/uC-libc(基本兼容Glibc API),当使用--with-newlib时,gcc编译器不使用Glibc。当没有交叉编译Glibc时,可以使用--with-newlib禁止连接Glibc而编译bootstrap gcc编译器。从gcc源目录下的config/arm中的t-linux和t-arm-elf中可以看出,不同的--target也影响gcc连接C语言库,t-linux(--target=arm-linux)默认使用Glibc,-arm-elf(--target=arm-elf)使用- Dinhibit_libc禁止连接Glibc,这时我们就可以使用newlib等其他C语言库编译GCC工具链。 虽然GCC工具链配置了不同的的C语言库,但由于这些C语言库都可以用来支持GCC,它们对核心数据的处理上不存在较大出入。因而arm-linux-* 和 arm-elf-*区别主要表现在C语言库的实现上,例如不同系统调用,不同的函数集实现,不同的ABI\启动代码以及不同系统特性等微小的差别。 arm-linux-*和 arm-elf-*的使用没有一个绝对的标准,排除不同库实现的差异,gcc可以编译任何系统。arm-linux-*和 arm-elf-*都可以用来编译裸机程序和操作系统,只是在遵循下面的描述时系统程序显得更加协调: arm-linux-*针对运行linux的ARM机器,其依赖于指定的C语言库Glibc,因为同样使用Glibc的linux而使得arm-linux-*在运行linux的ARM机器上编译显得更加和谐。 arm-elf-*则是一个独立的编译体系,不依赖于指定的C语言库Glibc,可以使用newlib等其他C语言库,不要求操作系统支持,当其使用为嵌入式系统而设计的一些轻巧的C语言库时编译裸机程序(没有linux等大型操作系统的程序),如监控程序,bootloader等能使得系统程序更加小巧快捷。 7月14日 NOR和NAND是现在市场上两种主要的非易失闪存技术。Intel于1988年首先开发出NOR flash技术,彻底改变了原先由EPROM和EEPROM一统天下的局面。紧接着,1989年,东芝公司发表了NAND flash结构,强调降低每比特的成本,更高的性能,并且象磁盘一样可以通过接口轻松升级。但是经过了十多年之后,仍然有相当多的硬件工程师分不清NOR和NAND闪存。 相“flash存储器”经常可以与相“NOR存储器”互换使用。许多业内人士也搞不清楚NAND闪存技术相对于NOR技术的优越之处,因为大多数情况下闪存只是用来存储少量的代码,这时NOR闪存更适合一些。而NAND则是高数据存储密度的理想解决方案。 NOR的特点是芯片内执行(XIP, eXecute In Place),这样应用程序可以直接在flash闪存内运行,不必再把代码读到系统RAM中。 NOR的传输效率很高,在1~4MB的小容量时具有很高的成本效益,但是很低的写入和擦除速度大大影响了它的性能。 NAND结构能提供极高的单元密度,可以达到高存储密度,并且写入和擦除的速度也很快。应用NAND的困难在于flash的管理和需要特殊的系统接口。 性能比较 flash闪存是非易失存储器,可以对称为块的存储器单元块进行擦写和再编程。任何flash器件的写入操作只能在空或已擦除的单元内进行,所以大多数情况下,在进行写入操作之前必须先执行擦除。NAND器件执行擦除操作是十分简单的,而NOR则要求在进行擦除前先要将目标块内所有的位都写为0。 由于擦除NOR器件时是以64~128KB的块进行的,执行一个写入/擦除操作的时间为5s,与此相反,擦除NAND器件是以8~32KB的块进行的,执行相同的操作最多只需要4ms。 执行擦除时块尺寸的不同进一步拉大了NOR和NADN之间的性能差距,统计表明,对于给定的一套写入操作(尤其是更新小文件时),更多的擦除操作必须在基于NOR的单元中进行。这样,当选择存储解决方案时,设计师必须权衡以下的各项因素。 ● NOR的读速度比NAND稍快一些。 ● NAND的写入速度比NOR快很多。 ● NAND的4ms擦除速度远比NOR的5s快。 ● 大多数写入操作需要先进行擦除操作。 ● NAND的擦除单元更小,相应的擦除电路更少。 接口差别 NOR flash带有SRAM接口,有足够的地址引脚来寻址,可以很容易地存取其内部的每一个字节。 NAND器件使用复杂的I/O口来串行地存取数据,各个产品或厂商的方法可能各不相同。8个引脚用来传送控制、地址和数据信息。 NAND读和写操作采用512字节的块,这一点有点像硬盘管理此类操作,很自然地,基于NAND的存储器就可以取代硬盘或其他块设备。 容量和成本 NAND flash的单元尺寸几乎是NOR器件的一半,由于生产过程更为简单,NAND结构可以在给定的模具尺寸内提供更高的容量,也就相应地降低了价格。 NOR flash占据了容量为1~16MB闪存市场的大部分,而NAND flash只是用在8~128MB的产品当中,这也说明NOR主要应用在代码存储介质中,NAND适合于数据存储,NAND在CompactFlash、Secure Digital、PC Cards和MMC存储卡市场上所占份额最大。 可靠性和耐用性 采用flahs介质时一个需要重点考虑的问题是可靠性。对于需要扩展MTBF的系统来说,Flash是非常合适的存储方案。可以从寿命(耐用性)、位交换和坏块处理三个方面来比较NOR和NAND的可靠性。 寿命(耐用性) 在NAND闪存中每个块的最大擦写次数是一百万次,而NOR的擦写次数是十万次。NAND存储器除了具有10比1的块擦除周期优势,典型的NAND块尺寸要比NOR器件小8倍,每个NAND存储器块在给定的时间内的删除次数要少一些。 位交换 所有flash器件都受位交换现象的困扰。在某些情况下(很少见,NAND发生的次数要比NOR多),一个比特位会发生反转或被报告反转了。 一位的变化可能不很明显,但是如果发生在一个关键文件上,这个小小的故障可能导致系统停机。如果只是报告有问题,多读几次就可能解决了。 当然,如果这个位真的改变了,就必须采用错误探测/错误更正(EDC/ECC)算法。位反转的问题更多见于NAND闪存,NAND的供应商建议使用NAND闪存的时候,同时使用EDC/ECC算法。 这个问题对于用NAND存储多媒体信息时倒不是致命的。当然,如果用本地存储设备来存储操作系统、配置文件或其他敏感信息时,必须使用EDC/ECC系统以确保可靠性。 坏块处理 NAND器件中的坏块是随机分布的。以前也曾有过消除坏块的努力,但发现成品率太低,代价太高,根本不划算。 NAND器件需要对介质进行初始化扫描以发现坏块,并将坏块标记为不可用。在已制成的器件中,如果通过可靠的方法不能进行这项处理,将导致高故障率。
易于使用 可以非常直接地使用基于NOR的闪存,可以像其他存储器那样连接,并可以在上面直接运行代码。 由于需要I/O接口,NAND要复杂得多。各种NAND器件的存取方法因厂家而异。 在使用NAND器件时,必须先写入驱动程序,才能继续执行其他操作。向NAND器件写入信息需要相当的技巧,因为设计师绝不能向坏块写入,这就意味着在NAND器件上自始至终都必须进行虚拟映射。
软件支持 当讨论软件支持的时候,应该区别基本的读/写/擦操作和高一级的用于磁盘仿真和 闪存管理算法的软件,包括性能优化。 在NOR器件上运行代码不需要任何的软件支持,在NAND器件上进行同样操作时,通常需要驱动程序,也就是内存技术驱动程序(MTD),NAND和NOR器件在进行写入和擦除操作时都需要MTD。 使用NOR器件时所需要的MTD要相对少一些,许多厂商都提供用于NOR器件的更高级软件,这其中包括M-System的TrueFFS驱动,该驱动被Wind River System、Microsoft、X Software System、Symbian和Intel等厂商所采用。 驱动还用于对DiskOnChip产品进行仿真和NAND闪存的管理,包括纠错、坏块处理和损耗平衡。 在 Unix 上写过程序的人一般都遇到过 Makefile,尤其是用 C 来开发程序的人。用 make 来开发和编译程序的确很方便,可是要写出一个MakeFile就不那么简单了。偏偏介紹 Makefile 的文件不多,GNU Make 那份印出来要几百页的文件,光看完 Overview 自己就快要先Over了,难怪许多人闻 Unix色变。本文将介绍如何利用 GNU Autoconf 及 Automake 这两套软件来帮助『自动』产生 Makefile 文件,并且让开发出来的的软件可以象 Apache, MySQL 和常見的 GNU 软件一样,只要会 ``./configure'', ``make'', ``make install'' 就可以把程序安裝到系统中。如果您有心开发 Open Source 的软件,或只是想在 Unix 系統下写写程序。希望这份介绍文件能帮助您轻松的进入 Unix Programming 的殿堂。 1. 简介 Makefile 基本上就是『目标』(target), 『关联』(dependencies) 和『动作』三者所组成的一系列规则。而 make 就会根据 Makefile 的规则来決定如何编译 (compile) 和连接 (link) 程式。实际上,make 可做的不只是编译和连接程序,例如 FreeBSD 的 port collection 中,Makefile还可以做到自动下载远程程序,解压缩 (extract) , 打补丁 (patch),设定,然后编译,安装到系统中。 Makefile 基本结构虽然很简单,但是妥善运用这些规则就可以变换出许多不同的花样。却也因为这样,许多刚刚开始学习写Makefile 时会觉得没有规范可以遵循,每个人写出来的Makefile都不大一样,不知道从哪里下手,而且常常会受到自己的开发环境的限制,只要环境参数不同或者路径更改,可能 Makefile 就得跟着修改修改。虽然有 GNU Makefile Conventions (GNU Makefile惯例例)订出一些使用 GNU 程式设计时撰写 Makefile 的一些标准和规范,但是内容很长而且很复杂,并且经常作一些调整,为了减轻程序开发人员维护Makefile 的负担,因此出现了Automake。 程序设计者只需要写一些预先定义好的宏 (macro),提交给Automake处理后会产生一个可以供 Autoconf 使用的 Makefile.in文件。再配合利用 Autoconf产生的自动培植设置文件 configure 即可产生一份符合符合 GNU Makefile 惯例的 Makeifle 了。 2. 上路之前 在开始使用 Automake 之前,首先确认你的系统安装有如下软件: 1. GNU Automake 2. GNU Autoconf 3. GNU m4 4. perl 5. GNU Libtool (如果你需要产生 shared library) 建议最好也使用 GNU C/C++ 编译器 、GNU Make 以及其它 GNU 的工具程序来作为开发的环境,这些工具都是属于 Open Source Software 不但免费而且功能强大。如果你是使用 Red Hat Linux 可以找到所有上述软件的 rpm 文件,FreeBSD 也有现成的 package 可以直接安装,或也可以自行下载这些软件的源代码回来安装。下面的示例是在Red Hat Linux 5.2 + CLE2 的环境下所完成的。 3. 一个简单的例子 Automake 所产生的 Makefile 除了可以做到程式的编译和连接,也已经把如何产生程序文件 (如 manual page, info 文件及 dvi 文件) 的动作,还有把源码文件包装起来以供发布都考虑进去了,所以程序源代码所存放的目录结构最好符合GNU 的标准惯例,接下来就用一个hello.c 來做为例子。 在工作目录下建立一个新的子目录"devel"',再在 devel 下建立一个"hello"' 的子目录,这个目录将作为存放 hello这个程序及其相关文件的地方: % mkdir devel % cd devel % mkdir hello % cd hello 用编辑器写一个hello.c文件, #include int main(int argc, char** argv) { printf(``Hello, GNU!n''); return 0; } 接下来就要用 Autoconf 及 Automake 來产生 Makefile 文件了, 1. 用 autoscan 产生一个 configure.in 的原型,执行autoscan 后会产生一个configure.scan 的文件,可以用它作为 configure.in文件的蓝本。 % autoscan % ls configure.scan hello.c 2. 编辑 configure.scan文件,如下所示,並且改名为configure.in dnl Process this file with autoconf to produce a configure script. AC_INIT(hello.c) AM_INIT_AUTOMAKE(hello, 1.0) dnl Checks for programs. AC_PROG_CC dnl Checks for libraries. dnl Checks for header files. dnl Checks for typedefs, structures, and compiler characteristics. dnl Checks for library functions. AC_OUTPUT(Makefile) 3. 执行 aclocal 和 autoconf ,分別会产生 aclocal.m4 及 configure 两个文件 % aclocal % autoconf % ls aclocal.m4 configure configure.in hello.c 4. 编辑 Makefile.am 文件,內容如下 AUTOMAKE_OPTIONS= foreign bin_PROGRAMS= hello hello_SOURCES= hello.c 5. 执行 automake --add-missing ,Automake 会根据Makefile.am 文件产生一些文件,包含最重要的 Makefile.in % automake --add-missing automake: configure.in: installing `./install-sh' automake: configure.in: installing `./mkinstalldirs' automake: configure.in: installing `./missing' 6. 最后执行 ./configure , % ./configure creating cache ./config.cache checking for a BSD compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking whether make sets ${MAKE}... yes checking for working aclocal... found checking for working autoconf... found checking for working automake... found checking for working autoheader... found checking for working makeinfo... found checking for gcc... gcc checking whether the C compiler (gcc ) works... yes checking whether the C compiler (gcc ) is a cross-compiler... no checking whether we are using GNU C... yes checking whether gcc accepts -g... yes updating cache ./config.cache creating ./config.status creating Makefile 現在你的目录下已经产生了一个 Makefile 檔,下個 ``make'' 指令就可以開始編譯 hello.c 成執行檔,執行 ./hello 和 GNU 打聲招呼吧! % make gcc -DPACKAGE="hello" -DVERSION="1.0" -I. -I. -g -O2 -c hello.c gcc -g -O2 -o hello hello.o % ./hello Hello! GNU! 你還可以試試 ``make clean'',''make install'',''make dist'' 看看會有什麼結果。你也可以把產生出來的 Makefile 秀給你的老闆,讓他從此對你刮目相看 :-) 4. 追根问底 上述产生Makefile 的过程和以往自行编写的方式非常不一樣,舍弃传统自定义make 的规则,使用 Automake 只需用到一些已经定义好的宏就可以了。我们把宏及目标 (target)写在Makefile.am 文件内,Automake 读入 Makefile.am 文件后会把这一串已经定义好的宏展开并产生相对应的 Makefile.in 文件,然后再由 configure这个 shell script 根据 Makefile.in 产生合适的Makefile。 [Figure 1:利用 autoconf 及 automake产生Makefile 的流程] 上图表示在上一范例中要使用的文件档案及产生出来的文件,有星号 (*) 者代表可执行文件。在此示例中可由 Autoconf 及 Automake 工具所产生的额外文件有 configure.scan、aclocal.m4、configure、Makefile.in,需要自行加入设置的有configure.in 及 Makefile.am。 4.1 编辑 configure.in 文件 Autoconf 是用来产生 'configure'文件的工具。'configure' 是一个 shell script,它可以自动设定原始程序以符合各种不同平台上Unix 系统的特性,并且根据系统参数及环境产生合适的Makefile文件或C 的头文件(header file),让原始程式可以很方便地在不同的平台上进行编译。Autoconf会读取 configure.in 文件然后产生'configure' 这个 shell script。 configure.in 文件内容是一系列GNU m4 的宏,这些宏经autoconf处理后会变成检查系统特性的shell scripts。 configure.in 内宏的顺序并没有特别的规定,但是每一个configure.in 文件必須在所有宏前加入 AC_INIT 宏,然后在所有宏的最后加上 AC_OUTPUT宏。可先用 autoscan 扫描原始文件以产生一个 configure.scan 文件,再对 configure.scan 做些修改成 configure.in 文件。在范例中所用到的宏如下: dnl 这个宏后面的字不会被处理,可以视为注释 AC_INIT(FILE) 该宏用来检查源代码所在路径,autoscan 会自动产生,一般无须修改它。 AM_INIT_AUTOMAKE(PACKAGE,VERSION) 这个是使用 Automake 所必备的宏,PACKAGE 是所要产生软件套件的名称,VERSION 是版本编号。 AC_PROG_CC 检查系统可用的C编译器,若源代码是用C写的就需要这个宏。 AC_OUTPUT(FILE) 设置 configure 所要产生的文件,若是Makefile ,configure 便会把它检查出来的结果带入 Makefile.in 文件后产生合适的 Makefile。 实际上,这里使用 Automake 时,还需要一些其他的宏,这些额外的宏我们用 aclocal来帮助产生。執行 aclocal会产生aclocal.m4 文件,如果无特别的用途,可以不需要修改它,用 aclocal 所产生的宏会告诉 Automake如何动作。 有了 configure.in 及 aclocal.m4两个文件以后,便可以执行 autoconf来产生 configure 文件了。 4.2 编辑Makefile.am 文件 接下来要编辑Makefile.am 文件,Automake 会根据 configure.in 中的宏把Makefile.am 转成 Makefile.in 文件。 Makefile.am 文件定义所要产生的目标: AUTOMAKE_OPTIONS 设置 automake 的选项。Automake 主要是帮助开发 GNU 软件的人员来维护软件,所以在执行 automake 时,会检查目录下是否存在标准 GNU 软件中应具备的文件,例如 'NEWS'、'AUTHOR'、'ChangeLog' 等文件。设置 foreign 时,automake 会改用一般软件的标准来检查。 bin_PROGRAMS 定义要产生的执行文件名。如果要产生多个执行文件,每个文件名用空白符隔开。 hello_SOURCES 定义 'hello' 这个执行程序所需要的原始文件。如果 'hello'这个程序是由多个原始文件所产生,必須把它所用到的所有原始文件都列出来,以空白符隔开。假设 'hello' 还需要 'hello.c'、'main.c'、'hello.h' 三个文件的话,则定义 hello_SOURCES= hello.c main.c hello.h 如果定义多个执行文件,则对每个执行程序都要定义相对的filename_SOURCES。 编辑好 Makefile.am 文件,就可以用 automake --add-missing来产生 Makefile.in。加上 --add-missing 选项来告诉 automake顺便假如包装一个软件所必须的文件。Automake产生生出來的 Makefile.in 文件是完全符合 GNU Makefile 的惯例,只要执行 configure这个shell script 便可以产生合适的 Makefile 文件了。 4.3 使用 Makefile 利用 configure 所产生的 Makefile文件有几个预先设定的目标可供使用,这里只用几个简述如下: make all 产生设定的目标,既次范例中的执行文件。只敲入make 也可以,此时会开始编译源代码,然后连接并产生执行文件。 make clean 清除之前所编译的执行文件及目标文件(object file, *.o)。 make distclean 除了清除执行文件和目的文件以外,也把 configure 所产生的 Makefile 清除掉。 make install 將程序安装到系统中,若源码编译成功,且执行结果正确,便可以把程序安装到系统预先设定的执行文件存放路径中,若用 bin_PROGRAMS 宏的话,程序会被安装到 /usr/local/bin下。 make dist 將程序和相关的文档包装为一个压缩文档以供发布 (distribution) 。执行完在目录下会产生一个以PACKAGE-VERSION.tar.gz 为名称的文件。PACKAGE 和 VERSION 这两个参数是根据 configure.in 文件中 AM_INIT_AUTOMAKE(PACKAGE, VERSION) 的定义。在此范例中会产生 'hello-1.0.tar.gz' 的文件。 make distcheck 和 make dist 类似,但是加入检查包装以后的压缩文件是否正常,这个目标除了把程序和相关文档包装成 tar.gz 文件外,还会自动把这个压缩文件解开,执行 configure,并执行 make all ,确认编译无错误以后,户显示这个 tar.gz 文件已经准备好可以发布了。这个检查非常有用,检查过关的套件,基本上可以给任何具备 GNU 开发环境的人去重新编译成功。就 hello-1.tar.gz 这个范例而言,除了在Red Hat Linux 上,在 FreeBSD 2.2.x 也可以正确编译。 要注意的是,利用 Autoconf 及 Automake 所产生出來的软件套件是可以在没有安装 Autoconf 及 Automake 的环境使用的,因为 configure 是一个 shell script,它己被设计为可以在一般 Unix 的 sh 这个 shell 下执行。但是如果要修改 configure.in 及 Makefile.am 文件再产生新的 configure 及 Makefile.in 文件时就一定要有 Autoconf 及 Automake 了。 5. 相关资料 Autoconf 和 Automake 功能十分强大,可以从它们附带的 info 稳当4中找到详细的使用方法说明。你也可以从许多现有的GNU 软件或 Open Source 软件中找到相关的 configure.in 或 Makefile.am 文件,他们是学习 Autoconf 及 Automake 更多技巧的最佳范例。 这个简介只用到了 Autoconf 及 Automake 的皮毛罢了,如果你有心加入 Open Source 软件开发的行列,希望这篇文章可以帮助你对产生 Makefile 有个简单的了解。其它有关开发 GNU 程式或 C 程序设计及 Makefile 的详细运用及技巧,建议从 GNU Coding Standards (GNU 编码规定) 读起,里面包含了 GNU Makefile 惯例,及开发 GNU 软件的标准程序和惯例。这些 GNU 软件的在线说明文件可以在 http://www.gnu.org/ 上找到。 6. 结束语 利用 Autoconf 及 Automake,产生一个 Makefile 似乎不再象以前那么困难了,而使用 Autoconf 也使得我们在不同平台上或各家 Unix 之间发布及便宜程序变的简单,这对于在Unix 系统上程序开发员来说减轻了许多负担。妥善运用这些 GNU 的工具软件,可以帮助我们更容易的去开发程序,而且更容易维护源代码。 7月10日 MSP430学习笔记(3)--程序模块化设计 作者:happytang 整体的程序设计结构,包括了所有外围模块及内部时钟,中断,定时的初始化。具体情况大家可以根据自己的需要添加或者减少,记住,模块化设计时最有力的武器。 这可是个人总结的经典阿,谢谢支持。因为经常使用149,所以这是149的结构,其他的再更改,根据个人需要。 /*****************************************************************************\ 文件名:main.c 描述:MSP430框架程序。适用于MSP430F149,其他型号需要适当改变。 不使用的中断函数保留或者删除都可以,但保留时应确保不要打开不需要的中断。 \*****************************************************************************/ //头文件 #include <MSP430x14x.h> //函数声明 void InitSys(); int main( void ) { WDTCTL = WDTPW + WDTHOLD; //关闭看门狗 InitSys(); //初始化 start: //以下填充用户代码 LPM3; //进入低功耗模式n,n:0~4。若不希望进入低功耗模式,屏蔽本句 goto start; } /***************************************************************************** 系统初始化 ******************************************************************************/ void InitSys() { unsigned int iq0; //使用XT2振荡器 BCSCTL1&=~XT2OFF; //打开XT2振荡器 do { IFG1 &= ~OFIFG; // 清除振荡器失效标志 for (iq0 = 0xFF; iq0 > 0; iq0--); // 延时,等待XT2起振 } while ((IFG1 & OFIFG) != 0); // 判断XT2是否起振 BCSCTL2 =SELM_2+SELS; //选择MCLK、SMCLK为XT2 //以下填充用户代码,对各种模块、中断、外围设备等进行初始化 _EINT(); //打开全局中断控制,若不需要打开,可以屏蔽本句 } /***************************************************************************** 端口2中断函数 ******************************************************************************/ #pragma vector="PORT2"_VECTOR __interrupt void Port2() { //以下为参考处理程序,不使用的端口应当删除其对于中断源的判断。 if((P2IFG&BIT0) == BIT0) { //处理P2IN.0中断 P2IFG &= ~BIT0; //清除中断标志 //以下填充用户代码 } else if((P2IFG&BIT1) ==BIT1) { //处理P2IN.1中断 P2IFG &= ~BIT1; //清除中断标志 //以下填充用户代码 } else if((P2IFG&BIT2) ==BIT2) { //处理P2IN.2中断 P2IFG &= ~BIT2; //清除中断标志 //以下填充用户代码 } else if((P2IFG&BIT3) ==BIT3) { //处理P2IN.3中断 P2IFG &= ~BIT3; //清除中断标志 //以下填充用户代码 } else if((P2IFG&BIT4) ==BIT4) { //处理P2IN.4中断 P2IFG &= ~BIT4; //清除中断标志 //以下填充用户代码 } else if((P2IFG&BIT5) ==BIT5) { //处理P2IN.5中断 P2IFG &= ~BIT5; //清除中断标志 //以下填充用户代码 } else if((P2IFG&BIT6) ==BIT6) { //处理P2IN.6中断 P2IFG &= ~BIT6; //清除中断标志 //以下填充用户代码 } else { //处理P2IN.7中断 P2IFG &= ~BIT7; //清除中断标志 //以下填充用户代码 } LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** USART1发送中断函数 ******************************************************************************/ #pragma vector="USART1TX"_VECTOR __interrupt void Usart1Tx() { //以下填充用户代码 LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** USART1接收中断函数 ******************************************************************************/ #pragma vector="USART1RX"_VECTOR __interrupt void Ustra1Rx() { //以下填充用户代码 LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** 端口1中断函数 多中断中断源:P1IFG.0~P1IFG7 进入中断后应首先判断中断源,退出中断前应清除中断标志,否则将再次引发中断 ******************************************************************************/ #pragma vector="PORT1"_VECTOR __interrupt void Port1() { //以下为参考处理程序,不使用的端口应当删除其对于中断源的判断。 if((P1IFG&BIT0) == BIT0) { //处理P1IN.0中断 P1IFG &= ~BIT0; //清除中断标志 //以下填充用户代码 } else if((P1IFG&BIT1) ==BIT1) { //处理P1IN.1中断 P1IFG &= ~BIT1; //清除中断标志 //以下填充用户代码 } else if((P1IFG&BIT2) ==BIT2) { //处理P1IN.2中断 P1IFG &= ~BIT2; //清除中断标志 //以下填充用户代码 } else if((P1IFG&BIT3) ==BIT3) { //处理P1IN.3中断 P1IFG &= ~BIT3; //清除中断标志 //以下填充用户代码 } else if((P1IFG&BIT4) ==BIT4) { //处理P1IN.4中断 P1IFG &= ~BIT4; //清除中断标志 //以下填充用户代码 } else if((P1IFG&BIT5) ==BIT5) { //处理P1IN.5中断 P1IFG &= ~BIT5; //清除中断标志 //以下填充用户代码 } else if((P1IFG&BIT6) ==BIT6) { //处理P1IN.6中断 P1IFG &= ~BIT6; //清除中断标志 //以下填充用户代码 } else { //处理P1IN.7中断 P1IFG &= ~BIT7; //清除中断标志 //以下填充用户代码 } LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** 定时器A中断函数 多中断中断源:CC1~2 TA ******************************************************************************/ #pragma vector="TIMERA1"_VECTOR __interrupt void TimerA1() { //以下为参考处理程序,不使用的中断源应当删除 switch (__even_in_range(TAIV, 10)) { case 2: //捕获/比较1中断 //以下填充用户代码 break; case 4: //捕获/比较2中断 //以下填充用户代码 break; case 10: //TAIFG定时器溢出中断 //以下填充用户代码 break; } LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** 定时器A中断函数 中断源:CC0 ******************************************************************************/ #pragma vector="TIMERA0"_VECTOR __interrupt void TimerA0() { //以下填充用户代码 LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** AD转换器中断函数 多中断源:摸拟0~7、VeREF+、VREF-/VeREF-、(AVcc-AVss)/2 没有处理ADC12TOV和ADC12OV中断标志 ******************************************************************************/ #pragma vector="ADC"_VECTOR __interrupt void Adc() { //以下为参考处理程序,不使用的中断源应当删除 if((ADC12IFG&BIT0)==BIT0) { //通道0 //以下填充用户代码 } else if((ADC12IFG&BIT1)==BIT1) { //通道1 //以下填充用户代码 } else if((ADC12IFG&BIT2)==BIT2) { //通道2 //以下填充用户代码 } else if((ADC12IFG&BIT3)==BIT3) { //通道3 //以下填充用户代码 } else if((ADC12IFG&BIT4)==BIT4) { //通道4 //以下填充用户代码 } else if((ADC12IFG&BIT5)==BIT5) { //通道5 //以下填充用户代码 } else if((ADC12IFG&BIT6)==BIT6) { //通道6 //以下填充用户代码 } else if((ADC12IFG&BIT7)==BIT7) { //通道7 //以下填充用户代码 } else if((ADC12IFG&BIT8)==BIT8) { //VeREF+ //以下填充用户代码 } else if((ADC12IFG&BIT9)==BIT9) { //VREF-/VeREF- //以下填充用户代码 } else if((ADC12IFG&BITA)==BITA) { //温度 //以下填充用户代码 } else if((ADC12IFG&BITB)==BITB) { //(AVcc-AVss)/2 //以下填充用户代码 } LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** USART0发送中断函数 ******************************************************************************/ #pragma vector="USART0TX"_VECTOR __interrupt void Usart0Tx() { //以下填充用户代码 LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** USART0接收中断函数 ******************************************************************************/ #pragma vector="USART0RX"_VECTOR __interrupt void Usart0Rx() { //以下填充用户代码 LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** 看门狗定时器中断函数 ******************************************************************************/ #pragma vector="WDT"_VECTOR __interrupt void WatchDog() { //以下填充用户代码 LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** 比较器A中断函数 ******************************************************************************/ #pragma vector="COMPARATORA"_VECTOR __interrupt void ComparatorA() { //以下填充用户代码 LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** 定时器B中断函数 多中断源:CC1~6 TB ******************************************************************************/ #pragma vector="TIMERB1"_VECTOR __interrupt void TimerB1() { //以下为参考处理程序,不使用的中断源应当删除 switch (__even_in_range(TBIV, 14)) { case 2: //捕获/比较1中断 //以下填充用户代码 break; case 4: //捕获/比较2中断 //以下填充用户代码 break; case 6: //捕获/比较3中断 //以下填充用户代码 break; case 8: //捕获/比较4中断 //以下填充用户代码 break; case 10: //捕获/比较5中断 //以下填充用户代码 break; case 12: //捕获/比较6中断 //以下填充用户代码 break; case 14: //TBIFG定时器溢出中断 //以下填充用户代码 break; } LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** 定时器B中断函数 中断源:CC0 ******************************************************************************/ #pragma vector="TIMERB0"_VECTOR __interrupt void TimerB0() { //以下填充用户代码 LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** 不可屏蔽中断函数 ******************************************************************************/ #pragma vector="NMI"_VECTOR __interrupt void Nmi() { //以下为参考处理程序,不使用的中断源应当删除 if((IFG1&OFIFG)==OFIFG) { //振荡器失效 IFG1 &= ~OFIFG; //以下填充用户代码 } else if((IFG1&NMIIFG)==NMIIFG) { //RST/NMI不可屏蔽中断 IFG1 &= ~NMIIFG; //以下填充用户代码 } else //if((FCTL3&ACCVIFG)==ACCVIFG) { //存储器非法访问 FCTL3 &= ~ACCVIFG; //以下填充用户代码 } LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } /***************************************************************************** 基本定时器中断函数 ******************************************************************************/ #pragma vector="BASICTIMER"_VECTOR __interrupt void BasTimer() { //以下填充用户代码 LPM3_EXIT; //退出中断后退出低功耗模式。若退出中断后要保留低功耗模式,将本句屏蔽 } 7月8日 读《史记》,看黄帝家谱 0代:少典(没找到其他的说法,就说他是黄帝的爹) 1代:黄帝(五帝第一位) 2代:玄嚣,昌意。 3代:蟜极-他爹是玄嚣;高阳-他爹是昌意(高阳即颛顼帝,五帝第二位) 4代:穷蝉-他爹是高阳;鲧-他爹是高阳;高辛-他爹是蟜极(高辛即帝喾,五帝第三位) 5代:敬康-他爹是穷蝉;挚-他爹是高辛;放勋-他爹是高辛(放勋即尧,五帝第四位),文命-他爹是鲧(文命即禹,五帝第5位舜的接班人,年龄不大,辈可真TM大) 6代:丹硃-他爹是尧;句望-他爹是敬康; 7代:桥牛-他爹是句望 8代:瞽叟-他爹是桥牛 9代:重华-他爹是瞽叟(重华即舜,五帝第五位,辈太TM小了) 10代:商均-他爹是舜。 夏朝:禹的儿子是启,启的儿子太康,太康的弟弟中康,中康的儿子相,相的儿子少康,少康的儿子予,予的儿子槐,槐的儿子芒,芒的儿子泄,泄的儿子不降,不降的弟弟扃,扃的儿子廑,不降的儿子孔甲,孔甲的儿子皋,皋的儿子发,发的儿子履癸,履癸就是夏桀。 夏朝以“姒”为姓,实际上是“颛顼”帝的后人。即黄帝二儿子“昌意”的后人。 商朝:殷契是帝喾第二个妃子的儿子,契的儿子是昭明,昭明的儿子是相土,相土的儿子是昌若,昌若的儿子是曹圉,曹圉的儿子是冥,冥的儿子是振,振的儿子是微,微的儿子是报丁,报丁的儿子是报乙,报乙的儿子是报丙,报丙的儿子是主壬,主壬的儿子是主癸,主癸的儿子是天乙(天乙就是成汤,商朝开国领导人) 商朝以“子”为姓,实际上是“帝喾”的后人。即皇帝大儿子“玄嚣”的后人 周朝:弃是帝喾大妃子的儿子,弃的儿子是不窋,不窋的儿子是鞠,鞠的儿子是公刘,公刘的儿子是庆节,庆节的儿子是皇仆,皇仆的儿子是差弗,差弗的儿子是毁隃,毁隃的儿子是公非,公非的儿子高圉,高圉的儿子是亚圉,亚圉的儿子是公叔祖类,公叔祖类的儿子是古公亶父,古公亶父的儿子是季,季的儿子是昌(昌就是周文王) 周朝以“姬”为姓,实际上也是“帝喾”的后人。即皇帝大儿子“玄嚣”的后人 黄帝真是牛啊,统治中国五帝和夏商周时期的都是他们家人。虽然有禅让,我开始以为尧舜禹互相没关系呢,现在看来都是一大家子,而且,禹比舜大四辈。夏朝是黄帝二儿子的后人,商周则是皇帝大儿子的后人。真实冥冥自有天意啊。 开放分类: 历史人物、名人、历史学家、古希腊、将军 目录 修昔底德(希腊文Θουκυδίδης 英文Thucydides,B.C.460或455~400或395年) 人物简介 [编辑本段] 古希腊历史学家,是在高度成熟了的希腊文化的熏陶下成长起来的。根据《伯罗奔尼撒战争史》中偶尔提及的有关他自己的文字内容判断:他约出生于公元前460年,其父奥罗路斯是雅典的贵族,其家族在色雷斯沿海地区拥有金矿开采权。他在雅典长大,自幼受到良好的教育。 他生活的时代正值雅典的极盛时期,也是古希腊文化的全盛时期。伯里克利等人的政治演说,爱斯契里斯、幼里匹德斯、索福克利斯等人的戏剧,诡辩派(又称“智者派”)的哲学,希罗多德等人的历史著作,以及“医学之父”希波格拉底所代表的“实验的”“科学”精神,都对他产生了极大的影响。成年以后,他也像大多数贵族子弟一样,凭借家族的门第和个人的才干而步入仕途。 伯罗奔尼撒战争爆发时,他已经30岁左右,并可能已投身军旅。军旅生涯使他积累了一定的军事经验,因而于公元前424年被推选为雅典的“十将军”之一,率领一支由7艘战舰组成的舰队,驻扎在色雷斯附近的塔索斯岛。当斯巴达的军队围攻安菲波里斯的时候,他接到该城守将攸克利的求援后立刻率军增援,但在他到达之前城池已被攻破。当局认为他贻误战机、且有通敌之嫌,就将他革职并放逐到色雷斯。 此后的20年间,他虽然居住在色雷斯,但始终关注着伯罗奔尼撒战争的进展情况,随时记下具体过程。据说他经常到各地战场去进行实地考察,甚至还去过伯罗奔尼撒同盟军队的阵地和西西里岛。公元前404年,战争结束以后,他才获得特赦,得以重返故乡雅典。 创作《伯罗奔尼撒战争史》 [编辑本段] ▲《伯罗奔尼撒战争史》是修昔底德在自己亲身感受的基础上、依靠敏锐的观察力、发挥了卓越的写作才能之后才完成的。这部著作体大思精、前后一贯,是预先订好写作计划之后一气呵成的。因此各个部分上下衔接、首尾相连,其间有严密的逻辑性。它原先也没有分卷,后来的校注家们把它分成8卷,每卷又分为若干章,但是各家在分章分段时却有很大的不同。 修昔底德的写作冲动,来自他对伯罗奔尼撒战争的深刻认识。正如他在书中所说的那样:“在这次战争刚刚爆发的时候,我就开始写这部历史著作,相信这次战争是一个伟大的战争,比过去曾经发生过的任何一次战争都更有记载的价值。我的这种信念是依据下列事实得来的:双方都竭尽全力来备战;同时我看到希腊世界中其余的国家,不是参加了这一边,就是参加了那一边;即使那些目前还没有参战的国家,也正在准备参战。这是希腊人历史上最严重的一次大动乱,同时也波及到大部分非希腊人的地区,可以说,几乎全人类都将蒙受其影响……。” 正因为修昔底德从战争之初就有这样的认识,所以他从一开始就十分用心地关注着战局的变化,注意收集和整理资料,并拟订了写作计划。等到战争结束、回到雅典、重新过上安定的生活之后,他就开始实施自己的写作计划。从这部著作的结构安排来看,修昔底德是想把那场延续了27年之久的伯罗奔尼撒战争当作一个完整的过程、严格地按照年代顺序加以叙述的。 第1卷:是导论;其中第1章是自序,阐明了写作的目的和方法,追溯了战争的远因和近因。 第2卷:叙述战争头3年的战况;其中第4章就是那篇著名的“伯里克利在阵亡将士国葬典礼上的演说”。 第3卷:叙述第4~第6年的战况。 第4卷:叙述第7~第9年的战况;其中有雅典人在派勒斯的胜利、以及斯巴达人求和被雅典人拒绝的经过。 第5卷:叙述第11~第16年的战况;其中有克莱昂阵亡和《尼西斯和约》的签订经过。 第6~7卷:叙述第17年的战况;包括雅典海军远征西西里岛上的叙拉古以及全军覆没的经过,是全书中描写得最精彩的篇章。 第8卷:叙述第18~第19年的战况;其中包括开俄斯等地的暴动和雅典内部的党争状况。 从以上的内容可以看出,修昔底德并没有最终完成自己的全部写作计划。他的叙述止于公元前411年,而且他叙述的最后一个句子是不完整的。人们由此判断:修昔底德可能是在著述的过程中猝然而逝的。有关伯罗奔尼撒战争最后7年(B.C.411~404年)的史事,修昔底德虽然没有来得及叙述,但他完成叙述的部分已经占了战争全过程的5分之4。 ※关于对《伯罗奔尼撒战争史》的非议 ⑴主题过于狭隘。许多西方史学家在评价《伯罗奔尼撒战争史》时,都为它的主题狭隘而感到遗憾,并认为这是它最主要的缺陷。因为修昔底德在书中只叙述了伯罗奔尼撒战争的经过、以及与其有关的人和事,基本上没有涉及战争以外的事情,对雅典的文化发展状况也只字未提。在这一方面,修昔底德明显地比希罗多德倒退了一大步。然而近年来也有人指出:修昔底德的这种做法恰恰反映了古希腊史学的成熟。 ⑵用抽象的和永恒的“人性”来解释和理解历史发展。修昔底德认为:“古往今来,人就是人,有不变的人性。因此,过去发生过的事情,在未来会以十分相似的方式重复出现。”这样一来,就容易陷入历史循环论。 ⑶纪年方法不精确。修昔底德在叙述历史事件时,只使用“冬季”和“夏季”来纪时,不写具体日期。这样就不能给人以明确的时间概念,容易混淆史实。 当然,修昔底德仍不失为古希腊杰出的历史学家,他的《伯罗奔尼撒战争史》仍然是西方史学史上的重要里程碑。有人曾评论道:“尽管修昔底德比希罗多德只晚生了25年,但他们两人对历史的理解却大不相同;从希罗多德到修昔底德,史学几乎要进步一个世纪。”这是对修昔底德史学成就的高度评价。 求实的精神,理智的批判态度 [编辑本段] 修昔底德把当时希腊哲学(主要是“诡辩派”哲学)中追求真理的精神和逻辑方法应用到了历史研究之中,强调历史研究必须坚持求实的原则、研究者必须坚持理智的和批判的态度,为后世的历史学家树立了光辉的榜样。 修昔底德用怀疑批判的眼光去看待他的前辈们,对纪事家们的作品和希罗多德的著作都进行过批评。他主张历史学不应该取悦流俗,而应该以叙述历史的真实为最高目标。他认为:要使自己的叙述与历史事实相符,要使一部历史著作成为不朽的传世之作,历史学家首先必须完全抛弃历史著作之中的神话和传说的因素,这是关键所在;其次,历史学家必须尽可能地去亲身经历自己所记述的历史事件。正如他在第5卷中所说:“我亲身经历了战争的整个过程,幸而我已成年,能充分了解这次战争的意义。为了明确地弄清这一系列重大的事件,我乃细心观察。事有凑巧,自从在安菲波里斯城下兵败之后,我便度过了20年流放的生活,得以密切地观察到交战双方的情况。而且因为自己闲着,便可以专心致志地对这些事进行深入的研究。” 在这个基础上,修昔底德提出了“历史就是当代史”、“历史的内容是刚刚发生过的政治事件”的著史原则,以他的《伯罗奔尼撒战争史》奠定了西方史学中政治叙事史传统的基础和基本模式,对此后西方史学2千多年的发展产生了极大的影响。同时他还根据“历史就是当代史”的原则,将较遥远的历史划归考古学。他认为“要准确认识有关遥远的过去、甚至我们这个时代以前的历史都是不可能的,因为它们在时间上距离我们太远了”,所以“应该由考古学家去研究。” 为了达到追求真实的目标,修昔底德还提醒历史学家们:不要轻信传闻、要对资料进行批判和考证,还要尽量避免先入为主的偏见。他自己就进行过大量的实地考察和文字考证工作,并且明确地写道:“在叙事方面,我决不是先入为主、一拿到什么材料就写;我甚至不敢相信我自己的观察就一定可靠。我所记载的,一部分是根据我亲身的经历,一部分是根据其他目击其事的人向我提供的材料。这些材料的确凿性,总是尽可能用最严格、最仔细的考证方法检验过的。然而即使费了心力,真情实况也还是不容易获得的:不同的目击者,对于同一件事情会有许多不同的说法,因为他们或者偏袒这一边,或者偏袒那一边,而记忆也不一定完全可靠。我这部没有轶闻奇事的史著,恐难引人入胜。但是,如果学者们想得到关于过去的正确知识、借以预见未来(因为在人类历史的进程中,未来虽然不一定就是过去的重演,但同过去总是很相似的),从而判明这部书是有用的,那么我就心满意足了。我的著作不是为了迎合人们一时的兴趣,而是要作为千秋万世的瑰宝。”他还写道:“我的责任是不相信任何一个偶然的消息提供者的话,也不相信在我看来很有可能是真实的事。我列举的事件,无论是我亲自参与的,还是我从其他与此有关的人那里得到的消息,都经过了对每一细微末节精心备至的审核。” 修昔底德在书中提出来的这些关于“史料怀疑”和“史料批判”的原则,几乎已经达到了现代专业化的水平;与此同时,他在自己的写作过程中,也几乎完美地实践了这个原则。 从人事活动的角度出发,总结历史的经验教训 [编辑本段] 修昔底德认为:叙述历史,就是要寻找历史事件之间的因果关系。在他看来,一切历史事件之间都有内在的联系;它们之所以会在某时某地发生,都有远因和近因,也有表面原因和根本原因;历史学家应当对它们进行区别分析,从而对纷纭复杂的历史现象作出合理的解释。 修昔底德试图站在世俗的立场上、从人事活动的角度总结伯罗奔尼撒战争的原因及其教训,用以垂训后世。他借伯里克利之口,说出了他的基本观点:“人是第一重要的,其他一切都是人的劳动成果。”他虽然也提到命运和神意,但是对宗教迷信式的解释是持否定态度的。在他的著作中,没有给超自然的力量留下任何位置;凡是涉及到“神”的地方,都是在批评人对神的迷信行为;他始终坚持从社会本身寻找社会现象的因果关系。另外,他笔下的“命运”或“神意”等概念的内涵,与其他古代史学家笔下的类似概念有着本质的不同:在他那里,命运或神意只是偶然现象的代名词,并没有神灵事先和事后对人事的任何干预。他还曾经借伯里克利之口阐述过自己的“命运”观:“事件的进程可能与人们的计划相反是正常的,而这正是我们通常把某种出乎我们预料之外的事归咎于命运的原因。”他还坚决拒绝把各种灾变当作神灵对各种即将到来的人间祸福的预示,他指出:日食、月蚀、地震、风暴等都是自然现象,不能把它们看作是吉凶祸福德征兆。在这里我们可以看到:修昔底德身上表现出来的朴素的唯物主义历史观,达到了他那个时代的最高水平。 在修昔底德看来,希波战争以后,雅典势力的不断扩张引起了斯巴达的妒忌和科林斯等城邦的不满,这就是伯罗奔尼撒战争爆发的基本原因;而雅典的最终失败,则是由于国内各党派之间的相互倾轧、奴隶的大批逃亡、以及盟邦的叛离等。他认识到:伯罗奔尼撒战争是一场空前的大悲剧,因为参战各方都有一批不负责任的政客,他们为了实现个人的野心和自私自利的目的,不顾国家的安危和人民的死活,使得整个民族都卷入了这场非正义的、愚蠢的战争;与此同时,一般的民众又没有力量来阻止战争的爆发和发展、无法主宰自己的命运,只好无可奈何地随波逐流,结果导致同归于尽。 在叙述和分析伯罗奔尼撒战争的进程和变化时,修昔底德还看到了某些个人(如伯里克利和克莱昂)的性格差异在历史进程中产生的不同影响,有时甚至是起关键性作用的。同时他也注意到了民众心理对历史事件的变化和结局的影响作用,如他在叙述雅典的局势时,对雅典出现的社会动乱、战局变化和大瘟疫在民众心理上造成的影响及其后果作了详细的分析和交代。 认识到经济因素的重要性 [编辑本段] 修昔底德在叙述战争过程和分析历史事件的因果关系时,很注意把经济因素放在比较重要的位置上。他考察了当时希腊的一些经济情况,记述了雅典与埃及等外邦之间的贸易情况,并特别指出了粮食贸易对雅典的重要性。在论述雅典的战时财政状况对战局的影响时,他曾强调指出:“如果没有充裕的财库,要想支撑一个长期的战争是不可能的”。这些都说明修昔底德已经认识到了经济在历史发展以及社会兴衰治乱中的作用。 开创“范例历史学”先河,追求史学垂训功能 [编辑本段] 修昔底德写作《伯罗奔尼撒战争史》的目的,是想通过叙述这场战争给希腊世界造成的影响、以及雅典等城邦在战争前后的成败兴衰的变化过程,来垂训后世。“范例历史学”这个概念,是伊索克拉底在读了修昔底德的《伯罗奔尼撒战争史》之后提出来的,是他对这部著作的概括性评价。 修昔底德不仅力求真实地记载历史,而且力图站在哲学的高度上去理解和概括历史、并把这种概括之后的历史事实传达给后人。他自幼生活在雅典,因此对雅典的民主政制和法治精神有深切的体会。他在书中通过伯里克利那篇千古传颂的《在阵亡将士国葬典礼上的演说》,对雅典民主政治的精神和原则进行了高度的概括和赞扬:“……我们的政体之所以称为民主政体,就是因为我们这个政府是为了多数人,而不是为了少数人。我们的法律,在解决私人争执的时候,保证人人在法律面前一律平等、无所偏私;尽管人们的社会地位有高低不同,但在选拔某人担任公职的时候,所考虑的不是他的阶级出身,而是看他有没有真才实学。任何人,只要他对国家有所贡献,决不会因为贫穷而在政治上湮没无闻。我们在政治上享有的这种民主自由,也广泛地体现于我们的日常生活之中。”由此可见,修昔底德对雅典民主政治的理解比希罗多德更加深刻。 另外,修昔底德与希罗多德在描述雅典民主政治时的情绪和角度也是不同的。希罗多德把雅典的民主政治视为雅典之所以能在希波战争中取胜的基本原因;他对雅典民主政治的颂扬,是与他对专制统治和独裁政治的批判联系在一起的;他在描述雅典的民主政治时,充满了一种自豪、昂扬的情绪。修昔底德对雅典民主政治的颂扬,恰恰反衬出那几个为了自己的私利而使整个希腊民族卷入战火灾难的政客们的可恶,反衬出使这种制度遭受破坏的伯罗奔尼撒战争的可悲,反衬出人们对这种美好制度遭受破坏之后的痛惜。 ▲修昔底德的这种情绪,使得他在写作手法上也独具特色。他用简练的文笔、精确的词句,通过冷峻、朴实的具体叙述,抒发了自己那种悲天悯人的真切情感,使得整部《伯罗奔尼撒战争史》充满着浓厚的悲剧气氛,加强了作品的内在感染力量。这是他吸取了希腊悲剧发展成果的具体体现。他是想通过这种悲剧效果来强化著作内容在读者头脑中的印象,从而加强对后世的垂训作用。 在写作的过程中,修昔底德还大量借鉴了诡辩派哲学家的演说和修辞手法,以致于整部《伯罗奔尼撒战争史》约有4分之1的篇幅是演说辞。这些演说辞优美感人、与内容情节交相辉映,不仅体现了当时古希腊人高超的演说技巧和修辞水平、强化了著作的感染力,而且还包含了丰富的历史信息。因为这些演说辞都是经过修昔底德的加工而成的。他坦诚地向读者说明了自己在加工演说辞时所奉行的原则:尽量争取忠实于原演讲的基本意思、并根据当时的情况(时间、场合)推想演讲者最可能说出的话语。修昔底德在“推想”当时情况的过程中,实际上就包含着他所理解的历史过程。因此,书中的演说辞不仅是他强化著作效果的手段,而且也是他记录历史内容的方法。 为了使自己的著作更有效地发挥“垂训”作用,修昔底德在整个叙述过程中十分注意克制个人的情感,尽量做到“客观”和“公正”。书中处处充满了冷静、理智的精神,处处以平实、白描的笔法描述人事的过程及其前因后果,尽量避免做过多的个人评价和文辞性渲染。在叙述敌我双方对同一件事情的解释时,他往往能够做到以相同的篇幅分配给双方,既不曲笔讳言,也不随波逐流;无论是敌方对雅典的种种指责,还是雅典对敌方的指控,他都照录不误。 修昔底德不仅在叙述史事时比较成功地克服了个人的情感,做到了“冷漠无情”,而且在评价史事时也能够遵守“客观”原则,按照一般的社会道德标准来判断人事的是非善恶。无论是雅典人还是外邦人,无论是雅典的朋友还是雅典的敌人,无论是对雅典有利还是对雅典不利,修昔底德评判过的人物和事件基本上都是比较公平的。 正是因为修昔底德公平地、充分地叙述了史实,又以一般原则为标准进行了评判,因而使得他的著作赢得了很高的信誉,几乎没有人对他的记载表示过疑义。 无线传感网络技术逐鹿中原 中国”龙” 大战 美国”弓” 原创 作者: 无线粉丝 发布:2008年六月六日 一/引言: 话说这就到了公元2000多年, 看这高科技领域日新月异,推陈出新, 这激烈竞争,好不热闹非凡今天本位高科技”粉丝” 在这里 不表INTEL 大战AMD ,也不表 WIMAX 对阵LTE(4G 无线) ,专门向列位看官表一表这无线传感网络技术的一场精彩好戏, 包管各位看官, 拍案叫绝, 拍手叫好, 这位看官举手提问: 啥子东西叫无线传感器网络? 答曰: 无线传感器网络由部署在检测区域内的大量、廉价、微型、节能传感器节点组成,通过无线通信方式自我形成网络系统,其主要目的是协同地感知、采集和处理网络覆盖区域中感知对象的信息,接收命令并与控制中心交换有关现实世界的信息。 这无线传感器网络可是了不起的技术, 如果说互联网构成了逻辑上的信息世界,改变了人与人之间的沟通方式,那么,无线传感器网络就是将逻辑上的信息世界与客观上的物理世界融合在一起,改变人类与自然界的交互方式。无线传感器网络被美国商业周刊列为21世纪最有影响的改变世界的十大技术之一,还被麻省理工学院(MIT)技术评论列为全球未来的三大高科技产业; 无线传感器网络涉及传感器技术、网络通讯技术、无线传输技术、嵌入式计算技术、分布式信息处理技术、微电子制造技术、软件编程技术等研究领域,具有鲜明的跨学科特点。微型传感器技术和节点间的无线通信能力为传感器网络赋予了广阔的应用前景,主要表现在军事、民防、环境、生态、农业、健康、家庭和其他商业领域。在空间探索和灾难拯救等特殊的领域,传感器网络也有其得天独厚的技术优势. 二/看中原大好市场, 美国”十字弓”扬帆东征 如今是市场经济, 这样光明的大好市场,岂有无人唾延之理? 在美国著名的硅谷,诞生了CROSSBOW THCH(中文意思,十字弓) 技术, 国内也叫 , 美国克尔斯博, 这把弓可是厉害,几年中间,在这无线传感器领域,独领风骚, 推出一系列产品, 以MICA2和MICAz为代表的无线传感器节点模块, 以MTS310CA为代表的传感器模块功能比较完善,包括了声音、温度、湿度、光、磁、加速度计等多种微型传感器, 基于传感器网络的新操作系统TinyOS和开发语言NesC等,刮起一阵传感器网络风暴, 好不风光无限; 这美国十字弓,在美国几年间赚到大把黄金白银,也聚积了包括多家”疯投”的美圆欧元, 看亚洲和中国,经济发展, 市场升温,就合计东征,推出新产品,思谋在中国赚个大满贯; 这个十字弓这些几年生意兴隆,在中国办讲座,听说一位难求,要价每位数千元,仍然场场客满, 为学校提供的无线传感器网络教学系统,要价每套4-8万”大洋” 依然供不应求,许多大学建无线传感器实验室,发出标书, 非十字弓”不娶”…. 三/ 中国腾起无线龙,短兵相接争雌雄 列位看官,这光阴似箭, 话说就到了公元2005年,中国的西部,出了一家名不见经传高科技企业—成都无线龙通讯, 号称”通往无线的桥梁,无线世界的先锋”,专攻无线传感器网络技术和教学系统, 几年成长,几经风雨, 让人刮目相看,推出了全套无线传感器微功耗节点, ZIGBEE协议栈, 无线传感器开发系统,更和北京航空航天大学出版社合作,出版了合计300多万字8本无线和无线传感器网络入门的教材,在中国市场刮起了一阵无线传感器网络的”龙卷风” 2008年,成都无线龙更上一层楼, 推出了和美国十字弓完全直接竞争的高级无线传感器网络实验教学平台C51RF-WSN,直接叫板美国克尔斯博公司同类教学和实验室产品; 四/ 双方都有杀手锏, 看看什么门道在其中? 列位看官,俗话说,”外行看热闹,内行看门道”,这无线龙对阵十字弓,这一场捉对厮杀,好不热闹, 直杀的昏天黑地, 但是这内在地命门在哪里?本”粉丝”多年在这个领域拼搏,透过滚滚烟雾,看得真切,让我慢慢道来: 第一场大战, 采用什么ZIGBEE协议栈 ?这是无线传感器网络的核心,几万行C语言代码,是无线传感器网络的核心,十字弓无线传感器的平台,使用的是老掉牙的ATMEL+CC2420 ,目前仍然使用ZIGBEE2004协议,(因为硬件限制,协议栈不能教学升级, 最近两年新起的ZIGBEE2006和今后的ZIGBEEPRO,都不能兼容,而无线龙的平台,是建立在TI CC2430最新无线SOC基础上,完全兼容ZIGBEE2006和今后的ZIGBEE2007, 这场大战, 十字弓先输一阵. 第二场大战,开发工具, 美国克尔斯博使用TinyOS和开发语言NesC, 这些东西都是东西志愿者开发的东西,和目前大学对单片机开发普遍使用的,学生非常熟悉的商业开发平台完全不同,学习入门都非常困难,而无线龙的无线传感网络系统,使用国内强大的IAR,KEIL平台,标准8051单片机,JATG仿真器, 学生容易学,老师容易教,开发效率大大提高,学生和老师可以集中在无线传感器核心技术的学习和研究;从长远而言,无线龙再次领先; 第三场大战,PC监控,教学软件,作为无线传感器网络节点,要求非常低的功耗,无线节点上,不能有太多的显示器件,那么,对无线传感器网络的远程监控和对远端传感器数据的监视,就非常重要, 美国克尔斯博有一套自己的PC教学监视系统,也是这个教学系统的核心,无线龙通讯自己也开发了自己知识产权的图形化高级的PC教学监控软件,可以在电脑屏幕上,让学生和研究人员,对ZIGBEE无线网络教学远程控制,显示各种传感器曲线,观察实时网络拓扑和通讯质量等,由于采用最新的.NET平台开发,具有响应速度快,图形质量好,可靠性高等特点,这场大战,双方打成平手,无线龙通讯稍微有优势; 其他方面,价格:无线龙同类产品只有美国克尔斯博同类产品价格的几分之一,质量方面, 无线龙也和十字弓产品,不相上下, 都是高质量的产品,而且具有强大技术支持; 另外,两家公司都在尽量向高端发展, 美国克尔斯博推出了X-SCALE 32 位无线传感器高档平台,无线龙通讯最近也推出了ARMRF-2 采用ARM9低功耗的无线传感器产品线,这场龙争虎斗,看来还有持续下去! 本粉丝在这领域摸爬滚打多年,使用过两家的产品,感到无线龙的产品无论从质量,设计,发展,都有更多的灵活性和前瞻性, 而且价格合算,中文教材丰富.也更加适合国内大学和研究单位的需要,同时,非常感谢无线龙通讯为国内无线传感器贡献了300万字的公开教材和实验代码. 我为无线龙通讯对中国的高科技发展做出的贡献,表示敬意; 本粉丝也推荐中国的大学,研究单位,尽量采用无线龙通讯的无线传感教学产品; 五/不是结束 列位看官,我打字到手酸,为大家来这段”龙’”弓”斗,不知大家是否认为精彩?这高科技市场,真是好戏连台, 如果要知这场争斗,还有那些精彩续章,且听我下回再慢慢道来: 这正是: 美国硅谷十字弓 , 来会中国无线龙 科技甘露洒神州 ,无线网络正当红. (作者保留版权,欢迎转载,但是必须保证全文完整) **************************** 成都无线龙通讯科技有限公司 __________________________ 事业部 周柏宏 __________________________ TEL:15928067854 EMAIL:zigbee_rfmcu@163.com QQ:892508706 *************************** 在学习boost的过程中,我们发现在boost的文档中,多处提及了complete type和incomplete type。到底什么是complete type呢?经过我查找msdn,终于发现了,原来complete type是编译器能够确定类型大小的类型,incomplete type是编译器不能确定类型大小的类型。incomplete type有: - 没有确定成员的struct
- 没有确定成员的union
- 没有确定大小的数组
看看msdn吧: C Language Reference Incomplete Types An incomplete type is a type that describes an identifier but lacks information needed to determine the size of the identifier. An "incomplete type" can be: - A structure type whose members you have not yet specified.
- A union type whose members you have not yet specified.
- An array type whose dimension you have not yet specified.
The void type is an incomplete type that cannot be completed. To complete an incomplete type, specify the missing information. The following examples show how to create and complete the incomplete types. - To create an incomplete structure type, declare a structure type without specifying its members. In this example, the
ps pointer points to an incomplete structure type called student. struct student *ps;
- To complete an incomplete structure type, declare the same structure type later in the same scope with its members specified, as in
struct student
{
int num;
} /* student structure now completed */
- To create an incomplete array type, declare an array type without specifying its repetition count. For example:
char a[]; /* a has incomplete type */
- To complete an incomplete array type, declare the same name later in the same scope with its repetition count specified, as in
char a[25]; /* a now has complete type */ 中新网8月29日电 世界著名数学家、菲尔兹奖得主丘成桐日前接受香港《亚洲周刊》专访时,再次痛批中国学界的霸权垄断现象扼杀后辈人才,并且直言中国学术腐败,“不是做学术,是玩权术”。 丘成桐曾连续三届被推选为世界华人数学家大会主席,其数学成就在华人学界颇负盛名。而同样广为人知的,是丘成桐一贯率直发言的作风,和他数次对中国学术界不良风气的批评。 “中国人看不起中国人” 中国数学家朱熹平和曹怀东有关“庞加莱猜想”的证明公布之后,网络上曾很快出现质疑声。 对此,丘成桐表示,这些人的姿态根本不是学术讨论,因为整篇论文长三百多页,一般的数学教授也需要至少半年的时间才能理解当中的内容。 他说,“网络上如此短的时间已对论文质疑,我肯定他们根本没有阅读这论文,这就是典型的‘中国人看不起中国人’的学术作风。而且这些评论没有留下个人名字,我怀疑有人恶意中伤。” 丘成桐认为,中国内地学术界对一个重大的学术议题一致反应冷淡,甚至排斥朱熹平,说穿了是中国学术界中的一些既得利益者担心这个成果动摇他们的地位,刻意把朱教授他们的研究成果压下去。因此,不排除是数学界的同行在网上如此不负责任地留言。 人际关系作用大 同时也是中国科学院外籍院士的丘成桐表示,这些人控制了科研经费的发放、奖项的评审、新的院士的提拔。他们的一句话就可以决定一个数学工作者的命运。所以,想要做一点研究的中国数学家,就要给他们面子。 丘成桐举例说,曾经有个数学家申请院士资格,其学术成就非常优秀,足够资格,但评审团却拒绝了他,理由竟是“人品不好”。 “数学研究与人品有关系?再说,这些人,他们有什么资格说别人品格不好?”丘成桐忿忿不平地指出,“这正是中国学术界最大的问题,不是做学术,都在搞权术。” 他还举例,2002年,国际数学联盟在北京召开年会,中国数学会出面,负责安排中国的数学家在会上发表演讲。国际数学家年会的演讲人是对学术成就的很高肯定,但结果八个演讲人里,七个都是北京的。其他成就远远高于演讲者的数学家都没有机会发言。“朱熹平教授是中国国内最好的数学家之一,后来被哈佛大学用最礼遇的方式邀请做访问学者,但当时,竟然连年会的邀请也没有收到。” 学术风气很浮躁 丘成桐说,这些人势力很大,大学和博士生也要得到他们肯定,才能获得研究资金或学位。不少人为拿取博士学位,愿意付出一百万元,这利益可不少呢!他们早已把学术研究看成一门生意。 他表示,权力垄断对中国的学术研究影响很大,学术风气更加浮躁。很多教授的心思都不在学问上,都变成怎么搞关系、怎么不被排挤、怎么拿经费。有些省份比较富裕,比如广东省政府,愿意给学术研究拨款,大学教授的基本薪金也都不错。但是在北方地区,教授的基本薪金较低,国家的经费资助对他们来说很重要,他们也必须更努力地与有关方面及个人搞好关系。 “这些朋党关系真的埋没了不少人才,年轻的学者为了不得罪人,就离开中国到美国念博士。学术环境不好,学生毕业也都出国留学。这对未来中国的学术发展影响很坏。”丘成桐痛心疾首。(张洁平、谢智衡) 7月5日
引用
由Equalize想到的-论足球与通信的关系
昨天晚上重温Proakis的Digital Communication。说来惭愧,作为通信科班出身,又一直在做wireless communication的我,居然一直没仔细读过这本书,即使是里面的几个章节。其实做接收机的核心之一Equalizer这块,最后的算法和实现可以说基本都是基于这本书中的理论总结之上的,虽然结构已经或许有些区别;只是作为一个engineer,我经常偷懒,看了paper后把结论直接用上去。
言归正传,我昨晚又在看Equalizer的章节,看到半夜一时兴奋,居然联想到足球去了。经常看英文解说球赛(拜可恶的天盛和歌华之功,我已经看了一赛季模糊不清的英超网上的国外直播)的同学可能会经常听到“Equalize”这个词,指的是落后的一方进球扳平比分,双方重新回到同一水平线上。我因为是搞通信的,对这个词特别敏感。我们这行把这词翻译成“均衡”。说到这要介绍一下这么个东西是干嘛使得。在无线通信中,有两个对接收性能影响很大的因素:一个是多径(multi-path),一个是加性噪声(noise)。多径指的是接收到的信号来自四面八方,有着不同的延迟和强度。这个是容易理解的,因为无线电波在空气中是到处乱跑的,可能通过这几栋楼的折射,也可能通过那几颗树或者啥的折射,最后到达接收机,每条路径“跑”的时间不一样,功率衰减的程度也不一样,由此带来的一个很直观的问题是在接收端,路径A和路径B的前后符号会叠加在一起造成混淆(俺们叫Inter-Symbol Interference,ISI),严重的情况可能会有上百个符号最后混在一块。这是从我们直观的时域(Time-Domain)角度的影响分析,从频域(Frequency-Domain)来看,多径干扰会带来频域选择性衰落(selective-fading),就是有些频率上没事,有些个频率上就影响特别大(衰落非常深),成了一个很深的null。北邮出身的要是都忘了这些东西,应该打回去重新学习通信原理和信号与系统。 另一个因素,噪声,更专业一点,指的是加性白高斯噪声(Additive White Gaussian Noise,AWGN),就是这个噪声是简单附加在信号上,频域是平坦的,均匀加在各频率上。这个相对来说比较简单容易处理。好了,均衡器Equalizer目的就是为了对抗第一个因素,是wireless receiver的一个重要模块。但是为了整体性能,我们设计均衡器也必须考虑第二个因素的影响。嘿嘿,这句话可是有深意的。
足球如何和这个东西联系起来呢?在漫长的联赛中其实我们也可以注意到,经常有前后几场比赛是互相关联的,比如一周双赛,间隔太短会有影响,或者说为了下一场大战,这一轮要适当用一些替补队员而雪藏主力,这可以对应于上文中的multi-path;另外,每一场中队员的体力和状态是有随机欺负的,这可以对应于另一个因素additive noise。这里出于严格起见,我们设计equalizer不再局限于某一场比分的追平,而是扩展到整个联赛如何取得理想的最终成绩。
说到这一时兴起要容许我再罗嗦一下。在通信领域中,传统的单载波(Single-Carrier)世界里,均衡器的设计是很复杂的,前面说过,最多可能要有上百个点混在一起,需要上百阶的滤波器去做均衡,消除前后干扰。据某人说过,地面国标单载波系统的均衡器要占去70%的接收机芯片面积,有做过的同志请举手指点一下对不对。而最近十年来已经越来越兴起的是以OFDM为代表的多载波技术,通过循环前缀(Cyclic Prefix)把多径信道的线性卷积(linear convolution)转为圆周卷积(circular convolution),于是在时域上那个“几百阶卷积影响”在频域上成为一个相当简单而优美的简单乘法的影响,所以均衡器也可以做一个相当简单的one-tap equalizer就完事了(当然代价就是要做傅立叶变换来完成时-频转换,但随着FFT的发明,这个已经很容易做到)。Sigh,容许我再次赞美一下傅立叶变换吧,真是多么优美的东西。如果有同志耐心看到这里说明你有兴趣,但如果你连这个都没看懂还是打回去重修信号与系统。那么既然这玩意这么好,为什么不通通改成OFDM技术呢?事实上近十年来OFDM已经在广播领域成为dominant的技术了,比如DAB,DVB,CMMB等等,但在个人通信中还暂时没有广泛使用。这个原因是OFDM有个比较大的缺陷是在发射端,峰均功率比(PAPR,Peak-to-Average Power Ratio)比单载波要大,对Power Amplifier要求比较高,一句话概括就是比较费电。在广播中通信是单向的(这玩意就不能叫通信),少数几个发射塔完成发射任务就完了,所以费电没啥了不起;但个人通信就不一样了,通信是个人和基站之间双向完成的,没人愿意一天给手机充几次电(嘿嘿,咱也算深入浅出吧)。为了解决这个问题,据说新一代(4G)的标准中,LTE的方案就是下行是OFDM,上行称为“DFT-spread OFDM”,本质还是一种单载波技术。所以,又回到了单载波的均衡问题。虽然是老问题,但现在流行的解决方案已经不一样了,受到OFDM的影响,更多人愿意考虑频域均衡(SC-FDE, Frequency-Domain Equalizer)而不是传统的时域一大堆tap的filter的架构。
罗嗦了半天还没完。以上说了很多各种equalizer的实现思想,包括不同的架构,但是前人,至少Proakis早已经指出:甭管你怎么实现,设计均衡器有两种准则。一个翻译为峰值失真准则,或称为“迫零”(ZF,Zero-Forcing)准则,另一个是最小均方误差(MMSE,Minimum Mean Square Error)准则。前者的目的是要完全消除多径干扰,而后者的目的是要达到所有接收符号的误差的平方的平均最小,就是整体上使均衡后的所有符号,距离正确的发射符号最接近。初看这二者好像并不矛盾啊,把多径干扰都消除了,接收符号不就自然离正确的是最接近了么?当年在学校时愚钝的我也没有啥实际的设计经验,读这本书时觉得这人怎么这么扯淡啊,整了几十页的乱七八糟不知所云的数学公式扯这些东西,丫是不是故弄玄虚啊?说到底俺最恨的就是灌水的paper,自己制造出一些莫须有的问题来折腾自己,当时以为Proakis写这么多名堂就是显摆自己有学问吧。反正我当时没看懂,就把书扔一边了。工作后去了深圳又回北京,好几次想把这厚砖头扔了,幸亏多少有点不舍得,如今越来越从VLSI engineer转回algorithm engineer,捡起来重温才真正明白老人家多么用心良苦啊。当然,三年前还在VIA的时候,alin和xiaoli就给我指出了二者的区别,当时是第一次在做implementation时有切身体会,虽然知道实际是怎么做更好,但是alin跟我说这个指导理论时,多少还是有点转不过这根筋来的。感谢伟大的alin,哈哈。现在我的理解是,首先声明,这只是设计的目的,您出发时是这么想的,但不代表最后真能走到那一步。实际上,前面说过的两大因素,多径和加性噪声是混在一块的,ZF的设计准则把二者割裂来考虑,就想着去补偿多径的影响,把后者丢给了后续的模块去解决。这会导致什么问题呢?前面已经说过,多径带来频域上的selective fading,会出现一些或深或浅的null,为了消除多径干扰,频域上应该把它们拉平,所以,对出现了fading的null,要尝试把它们放大来拉平,但是问题是对这种null,这样的设计准则在放大信号的同时把噪声也放大了,类似“拆东墙补西墙”,而且最后整体上是有害的,并不难达到最优目的。另一方面,MMSE准则从一开始就不指望完全消除符号间干扰,最后是为了达到最接近正确信号去的,二者的均衡或者抑制是统一考虑的,所以,MMSE是最优的设计准则。如果完全没有噪声,二者是一致的,但是只要有一点噪声,后者的优势马上就会有明显体现。
回到足球上来,有类似的相通之处。ZF准则可以理解为完全消除前后场次的影响,不顾队员的状态起伏,尽力在每一场,都排出理论上的最佳阵容,追求最佳表现。而MMSE准则可以理解为追求整体上的“最小均方”,即全局上的最佳效果。显然,主力队员是有伤病和状态的,如果出现很深的“fading null”,还派他们上场并要求他们全力以赴,无疑这些伤病,“噪声”也会随之放大,最后得到的并不是想要的。好的教练会统筹安排,安排替补队员上场;或者另一方面,有阅读比赛能力的队员会在领先而对手反扑时控制比赛节奏,保持体力而不是依旧死磕。我们经常看到的现象是,曼联在客场对布莱克本,米堡这些准强队会略微回收而不像主场那样压着打,请不要责怪我们的教练和队员“缺乏勇气”,为了大局,我们追求的是MMSE,而不是看似热闹的ZF。说到这,mm最近很迷法布雷加斯,前些年有人这么评价法布:当他拿球时两类人要打起十分精神,一类是对方的后卫,因为F15(当时还是15号)随时会塞出手术刀搬的直传撕裂整个后防线;另一类人是本方的后卫,图雷森德罗斯们也要小心了,自己的中前场队友们正四处寻找空档插上,而法布的直传一旦被拦截就会面临对手的反击。这其实已经反映了一个现实,法布已经展现出了足够犀利的一面,但也要多为那些跑上跑下气喘如牛的边后卫们多想想,保持体力控制全队节奏是中场队员的另一门功课。
最后总结陈词:Sir Alex Ferguson 太伟大了,他不需要学习Digital Communication就明白MMSE的道理,所以,虽然我们客场打巴萨很难看,虽然我们收官阶段客场输给了车子,但是,我们还是伟大的双冠王!
新快报讯 六方会谈相关人士4日透露,朝鲜的核申报清单中写道,朝鲜总共提取了38.5公斤的钚,其中25.5公斤可用于制成核武器。据计算,25.5公斤钚至少可制造5枚核弹。而美国45天内将朝鲜从支恐国名单中除名的承诺,也可能“无限期推迟”。 25.5公斤钚造核武器 据六方会谈相关人士透露,朝鲜根据六方会谈协议向主席国中国提交的所有核计划申报清单之中,有关用钚进行核开发的部分写道,提取的钚总量约为38.5公斤,其中为制成武器而使用的量为25.5公斤。 目前已查明的是,朝鲜在2006年10月实施的核试验中使用了约2公斤的钚,但这一部分则与上述用于制成武器的量分开列出。 上述申报内容将在10日重开的六方会谈团长会议上由与会国进行讨论,同时各方还将就申报内容的验证顺序展开协调。根据计算,上述25.5公斤钚若确实被用于制造核武器,至少可制造5枚核弹。 美为朝“摘帽”存分歧 朝鲜6月26日向朝核问题六方会谈主席国中国提交核计划申报书后,美国当天即启动在45天内将朝鲜从支持恐怖主义国家名单上除名的程序。不过,华盛顿内部在为朝鲜“摘帽”问题上出现了分歧,一些议员提交了附加相关条件的议案,其中提出在朝鲜提交包括核武器相关内容在内的完整、可验证的核申报文件之前,不能将其从支恐国名单中除名。如果该议案获得通过,那么朝鲜从支恐名单中除名可能被“无限期推迟”。 美国媒体报道称,美朝双方在申报内容的完整性和可验证性方面存在不少分歧。朝鲜在清单中根本没有提到铀浓缩活动,只是承认拥有38.5公斤钚。清单中虽然列出了一系列核设施,但没有证实任何美国所怀疑的生产核武的设施。美国官员透露,如果朝鲜在清单验证的过程中采取非合作姿态,美国可能会停止其支恐除名程序。 相关链接 美助朝弃核预算1.35亿元 新快报讯美国国务院日前编列了1950万美元(约合1.35亿元人民币)的预算,作为对朝鲜核去功能化的援助。朝鲜当局已炸毁宁边地区核反应炉冷却塔,已迈出核去功能化的第一步。 据韩国媒体报道,美国国务院公布,朝鲜在宁边核设施去功能化的十二项措施中,已完成了其中的九项。 最近,美国政府已提供朝鲜250万美元,作为宁边核设施冷却塔爆破、拆除等相关费用。(来源:新快报) 7月3日 1. 马克思社会主义思想的产生和发展,实现了社会主义学说从空想到科学的历史性变革,使社会主义思想产生了质的飞跃。有了马克思社会主义思想,才有了社会主义运动的蓬勃发展,才有了社会主义革命和建设的实践。历史的发展已经证明,马克思社会主义思想的基本原理和基本原则是科学的、正确的,是我们认识和把握人类社会发展规律的指导思想和理论武器。但是,任何思想理论都要受到时代条件的制约,马克思的思想也不例外。马克思虽然以毕生精力倾注于对资本主义社会的研究,探索实现社会主义的道路和途径,但他毕竟没有看到社会主义革命的成功,更没有建设社会主义的实践经历。因此,马克思对社会主义的个别论断难免带有历史的局限性,有的想法还带有理想化的色彩。因此,他给我们提供的是研究新情况、解决新问题的思想方法,而不是一套有关如何建设社会主义的现成方案。马克思的社会主义思想是具有科学性和生命力的理论体系,它的生命力就在于它是随着时代和实践的发展而发展的。我们要以科学的态度对待马克思主义,要以与时俱进的精神认识和发展马克思社会主义思想。 一、要对社会主义代替资本主义的实现途径有新的认识,进一步坚定社会主义必胜的信念 马克思恩格斯从分析19世纪资本主义发展的现状出发,认为社会主义革命将在西欧的几个主要资本主义发达国家同时发动并同时取得胜利。但社会的发展并没有应马克思所料,社会主义革命却在生产力相对落后的俄国和中国首先取得了胜利。当代发达的资本主义国家,现在看来,也好像并不需要像马克思所预料的那样通过无产阶级革命来实现社会主义乃至共产主义,很有可能通过和平的改良手段实现社会的渐进变革。那么,马克思社会主义理论是不是失灵呢不是。马克思关于资本主义必然灭亡,社会主义必然胜利的“两个必然”的基本思想并没有过时。而是社会主义代替资本主义的实现途径和条件有了新的变化,对此,我们要有新的认识。列宁在这方面为我们作出了榜样,他不拘泥于马克思主义的某些个别论断和具体行动纲领而忠实于马克思主义科学原理和科学精神,通过对俄国国情的深入考察和对帝国主义的深入研究,从资本主义经济政治发展不平衡规律出发,提出了社会主义将在一国或数国首先取得胜利的英明论断,并且在实践中成功地领导十月革命取得了社会主义革命的新胜利。那么,当代发达的资本主义国家到底通过什么途径实现社会主义,也需要我们用科学的态度通过对资本主义国家发展状况以及时代特征的考察去寻找答案。当代资本主义国家随着生产力的高度发展,生产关系也在不断调整优化。按照马克思关于生产关系一定要适合生产力发展的理论,生产关系越是适应生产社会化的客观要求,社会主义因素就发展得越充分。从西方发达资本主义国家来看,随着股份制和股份合作制经济的发展,生产资料社会化程度越来越高,生产社会化与生产资料私人占有之间的矛盾在逐渐缓和;由于物质财富的充分涌流,社会福利政策的配套完善,工人阶级与资产阶级之间的矛盾也在逐步缓解;随着资本主义国有化步伐的加快,经济宏观调控的加强,资本主义基本矛盾在生产上表现出来的个别工厂生产的有组织性和整个社会生产的无政府状态之间的对立也趋于缓和。这些矛盾之所以得到如此有效的缓解,正是资本主义国家自觉不自觉地按照马克思社会主义思想发展社会主义因素的结果。这些社会主义因素不断增长的过程,实际上就是资本主义逐步走向灭亡、被社会主义取代的过程。 二、要对社会主义代替资本主义的历史过程有新的认识,进一步明确社会主义代替资本主义的长期性 马克思恩格斯在《共产党宣言》中,通过对资本主义社会生产力和生产关系矛盾的产生及其表现的分析,并从历史发展的趋势上得出了社会主义必然取代资本主义的结论。同时,他们又根据资本主义基本矛盾激化的情况,尤其是经济危机周期性爆发的现象,认为当时的社会生产力已经发展到了资本主义生产关系不能容纳的地步,无产阶级革命很快就会在欧洲各国发生,资本主义的灭亡为期不远了。可是,出乎马克思所料,现实的资本主义不仅垂而未死,而且还发生了深刻的变化。因此,我们一定要以科学的态度对待马克思主义,以与时俱进的精神认真研究当代资本主义的新发展。那么当代资本主义国家为什么能够获得生产力的高度发展和政治经济的相对稳定我们要从发达资本主义国家生产力与生产关系矛盾运动的规律中去寻找答案。资本主义生产方式最大的特点就是具有追逐利润无限扩张其生产力的内在倾向,资本主义市场竞争又对生产力的发展构成强大的外在动力,资本主义的垄断不仅没有消除竞争,而且加剧了竞争。与此同时,随着生产力的高度发展,资本主义国家的生产关系也有了新的调整,国家与垄断资本得到了进一步结合,国家在经济生活中日益起到重要的作用。国家不仅提供现代化基础设施,推动科技、教育的迅速发展,而且也调整了生产关系,加强了宏观经济调控,为确保国民经济的顺利发展和社会政治的稳定创造了政治、经济和社会条件。这就说明,资本主义生产关系经过调整不仅能够容纳而且还能够在一定程度上促进生产力的进一步发展。后来,马克思对自己原来的估计也作了适当调整,明确地提出了“两个决不会”的思想。马克思指出:“无论哪一个社会形态,在它所能容纳的全部生产力发挥出来以前,是决不会灭亡的;而新的更高的生产关系,在它的物质存在条件在旧社会的胎胞里成熟以前,是决不会出现的”。因此,我们一定要充分认识社会主义代替资本主义的长期性和曲折性,增强建设社会主义的紧迫感,切实加大改革开放步伐,大胆吸收和借鉴人类社会创造的一切文明成果,吸收和借鉴当今世界各国包括资本主义发达国家的一切反映现代社会化生产规律的先进经营方式、管理方法,创造出比资本主义更高的劳动生产率,更高的物质文明和精神文明。 三、要对我国社会主义的实践形式有新的认识,进一步增强建设有中国特色的社会主义的信心 马克思恩格斯对社会主义社会形态的基本特征的主要构想,一是要实现生产力的迅速发展,二是要实现生产资料的社会占有,三是要有计划地组织社会生产,四是要实行按劳分配的原则,五是要消灭阶级和阶级差别。而我们正在进行的有中国特色的社会主义建设并没有完全按照马克思的构想实施,而是在社会主义条件下发展市场经济,坚持和完善社会主义市场经济体制;坚持和完善社会主义公有制为主体、多种所有制经济共同发展的基本经济制度;坚持和完善按劳分配为主体的多种分配方式。那么我国社会主义建设的实践是不是偏离了马克思社会主义的方向呢没有。马克思的设想是共产党人奋斗的目标,但实现这一目标,必须从我国的基本国情和时代特征出发。我国社会主义与马克思所设想的社会主义产生的条件不一样。马克思所设想的社会主义是在生产力高度发达的资本主义国家产生的,而我国社会主义是在生产力相当落后的半封建、半殖民地国家建立起来的。因此,虽然我国已经进入社会主义社会,但正如邓小平所说,我们正处于社会主义初级阶段。这个阶段的最大实际就是生产力不发达。因此,我们要想尽一切办法,包括借鉴资本主义有利于生产力发展的一切手段,大力发展生产力。列宁在俄国建立社会主义以后,积极推行新经济政策,明确提出要利用资本主义某些形式以及在资本主义条件下创造的文明成果来建设社会主义。列宁指出:“同社会主义比较,资本主义是祸害。但同中世纪制度、同小生产、同小生产者涣散性引起的官僚主义比较,资本主义则是幸福。既然我们还不能实现从小生产到社会主义的直接过渡,所以作为小生产和交换的自发产物的资本主义,在一定程度上是不可避免的,所以我们应该利用资本主义作为小生产和社会主义之间的中间环节,作为提高生产力的手段、途径、方法和方式。”列宁推行新经济政策的核心就是尝试将共产党领导的政治体制与市场经济相结合,其基本思想就是要把大力发展生产力和提高劳动生产率摆在首位,允许多种经济成分存在,大力发展商品货币和商品市场,发展国家资本主义。我们现在所进行的有中国特色的社会主义实际上就是对列宁新经济政策在当代中国条件下的继承和发展。因此,我们一定要增强建设有中国特色的社会主义的信心,正确处理最低纲领和最高纲领的辩证关系,既要胸怀实现人类美好社会的崇高理想,又要脚踏实地地把眼前的事情办好,为实现共产主义全面推进有中国特色社会主义的伟大事业。(作者系州委常委、州委秘书长)
|