配置Tomcat 4使用SSL

来源:百度文库 编辑:神马文学网 时间:2024/04/27 03:13:43
arthas.kang的专栏
CSDN首页 | CSDN社区 | CSDN技术中心 | CSDNBlog | 我的首页 | 联系作者 | 聚合 | 搜索 | 登录  15篇原创:: 0篇翻译:: 5篇转载:: 3611次点击:: 1个评论:: 0个Trackbacks
文章
JAVA(RSS)
Linux(RSS)
VC++(RSS)
VxWorks(RSS)
XML(RSS)
技术(RSS)
嵌入式开发(RSS)
收藏
相册
程序图表
blog链接
博客园 - 八进制 (RSS)
朱 培 — 无 线 空 间(RSS)
Embed Linux
中国Linux公社论坛-嵌入式开发-精华(RSS)
实时操作系统 RTOS(RSS)
WebService
IBM developerWorks 中国 SOA and Web services(RSS)
存档
2006年06月(1)
2006年04月(1)
2006年03月(6)
2005年10月(3)
2005年08月(1)
2005年03月(1)
2004年07月(1)
2004年06月(1)
2003年10月(1)
2003年06月(1)
2002年12月(1)
2002年10月(1)
2002年05月(1)
2006年06月13日
 在OpenSSL中添加自定义加密算法
摘要:在OpenSSL中添加自定义加密算法    (全文共105577字)——点击此处阅读全文
发表于 @2006年06月13日 15:46:00 |评论 (0)
2006年04月11日
 LibXML2不支持中文补遗
由于对libxml2的不熟悉,发表了“LibXML2不支持中文”一文。经过不断的接触,发现LibXML2自身已经支持了中文编码.只是他的所有api处理的数据都是UTF-8类型的,所以只要在读入和写入数据时进行相应转换即可!而且libxml2已融合了iconv,以下是代码!flags标示是读入(0)还是写入(1)!已测试通过
uint8_t *convert(uint8_t *in, char *encoding, uint8_t flags)
{
uint8_t *out;
int ret, size, out_size, temp;
xmlCharEncodingHandlerPtr handler;
size = (int) strlen( (char*)in ) + 1;
out_size = size * 2 - 1;
out = (uint8_t *)malloc((size_t) out_size);
if (out) {
handler = xmlFindCharEncodingHandler(encoding);
if (!handler) {
free(out);
out = NULL;
}
}
if (out) {
temp = size - 1;
if ( flags ) {
ret = handler->input(out, &out_size, in, &temp);
}else {
ret = handler->output(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 =(uint8_t *) 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)
{
uint8_t *content, *out, *in;
xmlDocPtr doc;
xmlNodePtr rootnode;
char *encoding = "ISO-8859-1";
//char *encoding = "utf-8";
if (argc <= 1) {
printf("Usage: %s content\n", argv[0]);
return (0);
}
content = (uint8_t *)argv[1];
out = convert(content, encoding, 1);
in = convert( out, encoding, 0 );
doc = xmlNewDoc( (xmlChar*)"1.0" );
printf( "%s:%s\n", encoding, out  );
printf( "%s:%s\n", encoding, in  );
rootnode = xmlNewDocNode(doc, NULL, (const xmlChar *) "root", out);
xmlDocSetRootElement(doc, rootnode);
xmlSaveFormatFileEnc("-", doc, encoding, 1);
free( out );
free( in );
return (1);
}
发表于 @2006年04月11日 11:35:00 |评论 (0)
2006年03月24日
 配置Tomcat 4使用SSL
目前介绍配置Tomcat 4使用单向SSL认证(只验证服务器证书)的资料很多,过程也比较简单。但是由于配置其使用双向SSL认证(还需要验证客户端个人证书)除了需要CA对证书签名外,还要从CA获得个人证书。有关这一问题,目前结合具体web服务器来讲解如何操作的资料很少。作者通过摸索借助一些SSL工具在本地实现了简单的CA功能,并在此基础上配置成功了Tomcat的双向认证,希望能把其中的一些经验与大家共享。不过受本人水平所限,文中难免会有错误与不当之处,敬请大家谅解。
Tomcat是Apache Jakarta的子项目之一,作为一个优秀的开源web应用服务器,全面支持jsp1.2以及servlet2.3规范。因其技术先进、性能稳定,而且免费,因而深受Java爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的web应用服务器。
在网络上信息在源-宿的传递过程中会经过其它的计算机。一般情况下,中间的计算机不会监听路过的信息。但在使用网上银行或者进行信用卡交易的时候有可能被监视,从而导致个人隐私的泄露。由于Internet和Intranet体系结构的原因,总有某些人能够读取并替换用户发出的信息。随着网上支付的不断发展,人们对信息安全的要求越来越高。因此Netscape公司提出了SSL协议,旨在达到在开放网络(Internet)上安全保密地传输信息的目的,这种协议在WEB上获得了广泛的应用。 之后IETF(www.ietf.org)对SSL作了标准化,即RFC2246,并将其称为TLS(Transport Layer Security),从技术上讲,TLS1.0与SSL3.0的差别非常微小。
SSL协议使用不对称加密技术实现会话双方之间信息的安全传递。可以实现信息传递的保密性、完整性,并且会话双方能鉴别对方身份。不同于常用的http协议,我们在与网站建立SSL安全连接时使用https协议,即采用https://ip:port/的方式来访问。
当我们与一个网站建立https连接时,我们的浏览器与Web Server之间要经过一个握手的过程来完成身份鉴定与密钥交换,从而建立安全连接。具体过程如下:
用户浏览器将其SSL版本号、加密设置参数、与session有关的数据以及其它一些必要信息发送到服务器。
服务器将其SSL版本号、加密设置参数、与session有关的数据以及其它一些必要信息发送给浏览器,同时发给浏览器的还有服务器的证书。如果配置服务器的SSL需要验证用户身份,还要发出请求要求浏览器提供用户证书。
客户端检查服务器证书,如果检查失败,提示不能建立SSL连接。如果成功,那么继续。
客户端浏览器为本次会话生成pre-master secret,并将其用服务器公钥加密后发送给服务器。
如果服务器要求鉴别客户身份,客户端还要再对另外一些数据签名后并将其与客户端证书一起发送给服务器。
如果服务器要求鉴别客户身份,则检查签署客户证书的CA是否可信。如果不在信任列表中,结束本次会话。如果检查通过,服务器用自己的私钥解密收到的pre-master secret,并用它通过某些算法生成本次会话的master secret。
客户端与服务器均使用此master secret生成本次会话的会话密钥(对称密钥)。在双方SSL握手结束后传递任何消息均使用此会话密钥。这样做的主要原因是对称加密比非对称加密的运算量低一个数量级以上,能够显著提高双方会话时的运算速度。
客户端通知服务器此后发送的消息都使用这个会话密钥进行加密。并通知服务器客户端已经完成本次SSL握手。
服务器通知客户端此后发送的消息都使用这个会话密钥进行加密。并通知客户端服务器已经完成本次SSL握手。
本次握手过程结束,会话已经建立。双方使用同一个会话密钥分别对发送以及接受的信息进行加、解密。
Tomcat 4.0.2
用途:Web Server。
下载:http://jakarta.apache.org/builds/jakarta-tomcat-4.0/release/v4.0.3/bin/
JSSE 1.0,2
用途:用来产生Tocmcat使用的秘钥对(keystore)。
下载:http://java.sun.com/products/jsse/
Openssl 0.9.9.6
用途:用来产生CA证书、签名并生成IE可导入的PKCS#12格式私钥。
下载:http://www.openssl.org/
以上工具的安装过程可以参考自带的帮助,本文就不再详细描述了。
4.2.1 建立工作目录
mkdir ca
4.2.2 生成CA私钥以及自签名根证书
4.2.2.1 生成CA私钥
openssl genrsa -out ca\ca-key.pem 1024
4.2.2.2 生成待签名证书
openssl req -new -out ca\ca-req.csr -key ca\ca-key.pem
4.2.2.3 用CA私钥进行自签名
openssl x509 -req -in ca\ca-req.csr -out ca\ca-cert.pem -signkey ca\ca-key.pem -days 365
在本文中用符号"%JDK_HOME%"来表示JDK的安装位置,用符号"%TCAT_HOME%" 表示Tomcat的安装位置。
4.3.1建立工作目录
mkdir server
4.3.2 生成server端证书
4.3.2.1 生成KeyPair
%JDK_HOME%\bin\keytool -genkey -alias tomcat_server -validity 365 -keyalg RSA -keysize 1024 -keypass changeit -storepass changeit -dname "cn=localhost, ou=department, o=company, l=Beijing, st=Beijing, c=CN" -keystore server\server_keystore
4.3.2.2 生成待签名证书
%JDK_HOME%\bin\keytool -certreq -alias tomcat_server -sigalg MD5withRSA -file server\server.csr -keypass changeit -keystore server\server_keystore -storepass changeit
4.3.2.3 用CA私钥进行签名
openssl x509 -req -in server\server.csr -out server\server-cert.pem -CA ca\ca-cert.pem -CAkey ca\ca-key.pem -days 365
4.3.2.4 导入信任的CA根证书到JSSE的默认位置(%JDK_ROOT %/jre/security/cacerts)
%JDK_HOME%\bin\keytool -import -v -trustcacerts -storepass changeit -alias my_ca_root -file ca\ca-cert.pem -keystore %JDK_HOME%\jre\lib\security\cacerts
4.3.2.5 把CA签名后的server端证书导入keystore
%JDK_HOME%\bin\keytool -import -v -trustcacerts -storepass changeit -alias tomcat_server -file server\server-cert.pem -keystore server\server_keystore
4.3.2.6 查看server端证书
keytool -list -keystore %JDK_HOME%\jre\lib\security\cacerts keytool -list -keystore server\server_keystore
4.3.3 修改server.xml使Tomcat支持SSL
首先找到以下内容,去掉对其的注释。然后参照红色部分修改。如果配置Tomcat不验证客户身份,可以设置clientAuth="false"。

然后把文件server\server_keystore复制到目录%TCAT_HOME%\conf\下。
4.4.1 建立工作目录
mkdir client
4.4.2 生成client私钥并用CA私钥签名
4.4.2.1 生成client私钥
openssl genrsa -out client\client-key.pem 1024
4.4.2.2 生成待签名证书
openssl req -new -out client\client-req.csr -key client\client-key.pem
4.4.2.3 用CA私钥进行签名
openssl x509 -req -in client\client-req.csr -out client\client.crt -signkey client\client-key.pem -CA ca\ca-cert.pem -CAkey ca\ca-key.pem -CAcreateserial -days 365
4.4.2.4 生成client端的个人证书
因为JSSE1.0.2没有完全实现了对PKCS#12格式文件的操作(只能读取,不能输出),所以在这里需要用openssl制作client端的个人证书(包含私钥)。
openssl pkcs12 -export -clcerts -in client\client.crt -inkey client\client-key.pem -out client\client.p12
4.4.2.5 安装信任的根证书
把ca\ca-key.pem改名为ca\ca-key.cer,在client端的IE中使用"工具 ‘ Internet选项 ‘ 内容 ‘ 证书 ‘ 导入"把我们生成的CA根证书导入,使其成为用户信任的CA。
4.4.3 安装个人证书
把client.p12导入到client端的IE中作为个人证书,导入过程同4.4.2.5。
4.5.1 启动Tomcat 4.x
执行%TCAT_HOME%\bin\startup.bat启动Tomcat 4.x
4.5.2 用IE访问Tomcat 4.x
在IE浏览器的地址栏中输入https://localhost:8443,如果前面的操作都正确的话,应该可以看到Tomcat的欢迎页面。同时状态栏上的小锁处于闭合状态,表示您已经成功地与服务器建立了要求客户端验证的SSL安全连接。
以上我们实现了为Tomcat 4.x配置要求客户端验证的SSL的全过程。对于其它类型的服务器,例如Apache,Netscape Enterprise Server, Websphere,Weblogic等,一般只是在服务器端保存证书的方式略有不同,但它们的原理都是类似的,配置时可以在本文中办法的基础上做出相应的调整。




回页首
Tomcat SSL Configuration HOW-TO
SSL3.0规范
Description of the Secure Sockets Layer (SSL) Handshake (Q257591)
keytool - Key and Certificate Management Tool
Openssl使用手册
赵梁,1995年毕业于北京航空航天大学。对J2EE, J2ME以及CORBA, Web Service等技术比较感兴趣。目前的主要工作是采用J2EE技术开发供分销企业使用的供应链/销售链ERP。可以通过电子邮件b-i-d@163.com与作者联系。
发表于 @2006年03月24日 15:03:00 |评论 (0)
2006年03月15日
 基于arm+uClinux的嵌入式系统的开发
前些日子基于arm+uClinux开发了一个网络监控系统,眼看项目马上要做完了,终于松了一口气,于是整理了一些笔记和心得想和大家针对这种开发模式进行一些探讨,希望对各位有所帮助。
按照我的开发过程想分以下几部分逐一介绍。
1.开发平台的选择和论证
2.开发环境的建立
3.一般程序的开发
4.Linux程序向ARM+uClinux平台的移植
5.剩下的问题
希望诸位多多补充自己的想法,以利于大家共同提高。
1. 开发平台的选择和论证
一个项目拿到手,如何选择开发平台(主要是指CPU和操作系统以及开发环境和工具)应该说至关重要,有时这不光影响进度,产品质量,可维护性等一般问题,甚至涉及到方案的可实现性。本人结合自己的网络监控系统简单归纳了一些对平台的考虑,还请各位补充。
从系统功能实现考虑:
(1) 是否有片上外设,专用指令或配套的软件模块直接实现系统功能要求。 感 觉这一条对很多人的决策影响很大
(2) 价格
这一点应通过CPU提供的资源综合考虑,它提供了多少有用的资源,多少没用的资源(那可都是银子呀!),还是那三个字,性价比,另一方面,是要抓主要矛盾,是不是有些特性是必须的,什么特性是用户需求里的亮点(就靠这些亮点往上抬价),这时该花的就得花了。
(3) 功耗
本系统对CPU功耗要求不高,但对移动设备,这一点可是致命,而且这一点不是仅针对CPU,所有几乎器件都要勒紧裤腰带运行。
(4) 处理速度
这项不用多说,大家都明白重要性,但具体算起来可是一门学问,一方面是自己需要多快的速度,如果加上非实时操作系统这事就不好控制,余量还是大点稳妥,另一方面,CPU指令周期多少,有没有流水,有没有并行,什么体系结构,有没有专用指令(看人家DSP多牛,干这事一绝),对外部存储器和外设的存取速度等等,哪一个慢了都叫瓶颈。
(5) 需要的硬件支持(如外部存储器,双电源等)
这算是杂项,但会增加额外的价格,系统体积等,不容忽视。
从开发者的角度考虑:
(1) 是否有足够的技术支持包括demo版及原理图,demo程序,操作系统和BSP,测试开发工具等。
(2) 自身条件;包括对项目开发周期的要求,开发人员对器件和开发模式的熟悉程度以及掌握的难易程度。
(3) 可用资源是否丰富(书籍,网络等)
以上三点主要考虑迅速开发出稳定的系统。
(4) 系统的可继承性,可移植性和可扩展性。
(5) 是否有现货。
(6) 方案提供商的素质。(包括技术水平和服务意识)。
根据以上考虑选择了s3c4510b(ARM7TDMI)+uClinux开发模式
(1) 以下是该平台对我的系统的满足情况:(和上面几点对应)
本监控系统硬件部分主要要求以下部分:
a.以太网接口 (s3c4510b自带网络控制器)
b.串口 (自带)
c.与数据采集芯片的接口(8位数据线,小于8位地址总线)。(自带)
本系统软件部分主要要求以下部分:
a. 硬件接口驱动程序 (uClinux提供串口和网络控制器驱动)
b. 网络协议栈支持(uClinux提供TCPIP,UDP等的协议栈)
c. 应用层程序(如果算上可以从linux移植的程序来看,那就太多了,我就用到了一个现成的)
(2) 本应用系统不是那种批量的东西,对价格要求不苛刻,而且这款CPU最便宜可以到55左右,可以接受。
(3) 本应用系统有固定电源,功耗要求不高。当然,据说ARM在节省功耗上很有特点。
(4) 本应用系统速度方面要满足两方面:1。串口:115200bps 2。网络速度 能到10Mbps就行,所以对系统速度要求也不高。这款ARM内部可以到50M。
(5) 系统对体积要求也不高,加片flash和RAM还是没问题(到目前为止感觉我的系统真是无欲无求!)
从开发者的角度考虑:
(1) 因为时间很紧(一个半月),所以支持越多越好。目前从开发商那里拿到了开发板,原理图,uClinux,相应驱动,bootloader,拿来就可以用了。软件硬件并行开发。(bootloader和网络控制器驱动没提供原码,比较可惜:-(
(2) 当时我对嵌入式系统的开发模式和ARM都是只有耳闻,linux接触过一个月左右。现在想起来有些后怕。
(3) 网上的资源,非常多。
提供一些我常用的。
::URL::http://www.uClinux.org/uClinux的大本营。
::URL::http://www.ucdot.org/ 里面有些技术文章非常不错。
::URL::http://www.linuxdevices.com/
::URL::http://www.linuxeden.com/ 这是国产的linux站点。
uClinux-dev@uClinux.org 这是uClinux的邮件列表,回答问题的都是大牛,非常有帮助,记住把你的邮件设置成纯文本格式。 申请是在:
::URL::http://mailman.uClinux.org/mailman/listinfo/uClinux-dev web方式。
(4) 采用以上开发模式,软件的可维护性,可移植性和可扩展性都不错。
(5) 目前该CPU使用还是比较普遍,现货没问题。
(6) 方案提供商的素质吗……..还算可以吧:-)
根据以上考虑和目前的开发情况,这套方案还是比较令人满意。
今天先回家了,下回介绍具体开发步鄹吧。
2.开发环境的建立。
先说两句废话为和我以前一样对操作系统(尤其是嵌入式操作系统)迷惑的弟兄解释些概念。因为总是有人在问是不是一定要用操作系统,我的CPU能不能移植操作系统,可以移植什么操作系统,有了操作系统可不可以运行某些程序。
从我的个人经历来讲,这其实就是许多硬件出身的弟兄对操作系统这个东西有神秘感(和我一年前一样)。说白了,操作系统就是一段设计非常巧妙的程序,和你自己的程序从本质讲没有区别,于是,以上问题转为,我是不是一定要用这段程序,我的CPU能不能运行这段程序,可以跑什么样的程序。这个程序可以跑,调用这个程序接口的另一个程序能不能跑!
答案也就变得简单,操作系统对任何一个CPU都不是必须的(对嵌入式系统更是如此),你可以自己编些程序在没有操作系统的PC裸机上跑(BIOS就是这样的),像玩C51一样,(虽然奢侈的让人有些心痛),或者移植UCOS到上面。另一方面,现代操作系统大多需要一些硬件的支持,(像保护模式的实现),反过来说,高端CPU中专门有针对支持操作系统的体系结构,这样,许多操作系统的实现是挑剔硬件平台的。其实其它程序也一样,你编的程序使用的片上外设另一CPU上没有,那这段程序就无法移植了。这就是话粗理不粗。书归正传,还是聊聊ARM+uClinux开发模式下开发环境的建立(其实下面说到的东西不仅限于这种硬件平台和操作系统)
很久以前就在介绍嵌入式系统开发的书上见过“交叉编译环境”这词,当时觉得很玄,用了以后才知道,其实就是解决在谁的地盘上用谁的工具编谁的代码问题。
编译的最主要的工作就在将你的程序转化成运行该程序的CPU所能识别的机器代码,不同的CPU有相应的编译器,另一方面。编译器本身也是程序,当然也要在某一个CPU平台上运行。于是交叉编译的交叉点就在那个编译器本身是CPU1上的一个程序,却在为CPU2编译代码(整个一个吃里扒外!)。这么一想,以前用51和dsp的开发软件(大部分都是IDE-集成开发环境)开发程序时,都算是交叉编译啦。当然,假如在你的ARM系统上,操作系统已经正常运行,并且你的资源足够多,你可以把PC机上运行的ARM编译工具移植到ARM上,然后所有该系统的应用程序都直接在ARM系统上编译,这就不算交叉编译,但如果有条件这么作,程序的开发或者移植就方便多了,因为整个开发过程又回到在自己PC机上编应用程序的那种模式了,那就是在自己的地盘上用自己的编译器编自己的应用程序。
与不使用操作系统的开发模式不同(此处的操作系统尤其指提供了专门的接口函数库的操作系统,目前的UCOS就不算),在目标板(就是实现系统的板子)使用操作系统的开发模式下,交叉编译环境中还需要该对应该操作系统的库。比如uClinux提供的uClibc。此时,开发用的主机上不光要有目标板CPU所需的编译工具,还要有对应操作系统的库,又因为一般库文件还要在开发机上拿目标CPU的编译器重新编译一下,所以还要把操作系统的原码也放到开发机上。(唉,跟目标板没什么关系,却要帮它背这么多东西,真是上辈子欠它的!!)。
虽然操作系统的接口库至关重要,但大家似乎已经淡忘了它的存在。这些多是因为大家已经远离了刀耕火种的年代(需要告诉编译器需要的include路径,lib路径,以及lib的名称),集成的编译环境让我们编译链接的所有繁琐工作化作对BUILD按钮的潇洒一击。而且不论是windows环境,还是linux环境,都有环境变量去记录这些参数。。但尝试将/usr/lib目录改一个名字,你就会知道你不能无视他们的存在,因为操作系统的功能都是通过这些库来交给应用层程序使用的。当然如果你的系统不依靠任何操作系统,像最原始的那种完全自己实现所有代码,就只需要一个编译工具,少了这些罗嗦事。
以上的东西一般时候是没有必要仔细研究,但交叉环境下开发或移植比较大的程序时,你可能就需要了解编译器,链接器等开发工具的几乎所有重要参数。
我在开发时,主机完全使用的是linux,如果有条件,建议大家这样作,linux的使用没有想象的复杂(虽然我现在身边还要放一本关于linux使用的书籍),而且开发程序可以先在主机上调通,然后用交叉编译工具为目标系统重新编译一遍,可以这样做是因为主机是linux,目标系统跑uClinux,两个操作系统提供的应用程序接口几乎是一样的,所以程序几乎不用修改。
在我的系统上,建立基本的开发环境过程如下。
(1) 安装gnu开发工具链(是GNU开发的针对ARM CPU的一组编译开发程序(是linux程序)。包括arm-elf-gcc,arm-elf-ld等
(2) 将uClinux源代码源代码解压到相应路径下,按照编译内核的步鄹编译一遍(此时使用的编译工具已经是上面提到的ARM编译工具了,因为它要在ARM CPU上运行,另外,和编译linux内核一样,此时可以通过menuconfig来对内核提供的功能进行裁减
(3) 将库(uClibc)解压到相应路径下,用以上工具编译一遍。
这样最基本的环境就算搭建好了。
以上工作对于做过的人来说比较简单,这里介绍一下帮助没有使用或刚开始使用这种开发模式的弟兄们理清一下思路。
3.应用程序的开发
因为目标板上用uClinux,它提供的程序接口和linux下的基本一致,不一致的部分主要在于uClinux不支持MMU(应该说是uClinux是为不带MMU的cpu定制的),最明显的就是fork函数要用vfork函数替代,这也是编程时,感觉最不爽的一点(没办法,谁让咱们的CPU有生理缺陷)。另一个不易觉察的差异在于uClinux提供的库uClibc是经过裁减的。更适合于资源紧张的嵌入式系统(上回分解已经说了,应用程序很大一部分是在和库函数打交道,而且大家最终是链在一起,所以库函数大了,你的程序也小不了)。
于是基于这种开发模式的应用程序开发变成了linux下的程序开发。而且在实际中一般是编好了程序先在主机上拿主机平台上的编译器编译并且调试一下(linux下的编译器就是gcc了),当然前提是被调试的程序中需要的硬件条件主机具备,例如我的程序中有一段是针对串口的,于是先在主机编一个串口程序,调通以后拿目标板的编译器重新编译一下(如果看了上一章“交叉编译环境”,这里就不会晕了),下载到目标板上运行,一般来说就可以直接用了。
以上也是为什么我认为开发嵌入式linux程序主机应该选用 linux环境。对于以前没用过linux的人来说(比如我),开发程序前应该花3,4天时间熟悉linux环境,尤其是它的编辑器,用惯集成编译环境的人有时连编译器和编辑器的概念都模糊了,所以一般是直接进入集成编译环境,连写带编一气呵成,殊不知有些集成编译器提供的编辑器弱智的一塌胡涂,如果用熟了linux下的emacs,你就会发现他们之间的差距大概……要像我和盖茨那么大吧。所以编程序时应该选一款优秀的编辑器,linux下,我当然选 emacs,虽然刚看见它的感觉是外表丑陋,使用复杂。但只要多用多练,对提高效率很有帮助。(将你的程序用两个编辑器完成,一半是用emacs的,一半是不用emacs的,看看效果:-)
对具体的linux编程我就不板门弄斧了,需要提个醒的是咱硬件出身的人作软件应该养成良好的编程习惯,别让作软件的笑话咱。因为作了些网络应用,所以介绍一些网络编程时要用到的网站和书籍;
<>w.Richard.Stevens. 这可是linux网络编程的圣经级的书籍
::URL::http://www.fanqiang.com/a4/b7/ 适合于网络编程的入门。
还有IBM中国上关于linux的教程和文章,都是翻译过来的,有很多写非常不错。
其实类似的资源不计其数,遇到问题时应该先到google上狂搜一圈。
重点想说些关于编译器的东西,不了解它,在交叉编译环境下编译程序就寸步难行了,这无非是因为交叉编译环境下目标板编译器所处的寄人篱下的悲惨环境。想想在linux下将myprogram.c编译链接成应用程序myprogram,最简单的一句 gcc –o myprogram myprogram.c 就可以了。(其实在诸如VC下你也可以找到类似的命令,集成开发环境只不过替你来调用它了)。一切看起来天经地义。
但试着把/usr/include路径改一个名字(比如改成stupid_include),再这样编译一下,会发现程序中被< >引用的头文件(比如#include)都找不到了。因为编译器看见这样的头文件会到系统指定的路径下寻找,而这个路径是由环境变量保存的(linux和windows下都是这样的)。针对以上情况,不将路径名字改回去,但是给编译器加一个参数如下:
gcc –I/usr/stupid_include –o myprogram myprogram.c 会发现错误信息没了,一切又恢复了往日的宁静,顿时明白,不用环境变量,通过参数,同样可以将这些信息告诉编译器。 返回来说说你的目标编译器,虽然占用了人家的地盘,编译器,头文件,库文件,一个都不少,但你要编一个程序编译器照样发晕,因为没有环境变量告诉它自己需要的头文件和库文件在哪里。看来只有两种办法,一个是抢占了主机的环境变量改成自己的(整个儿一个土匪),或者在编译时加上必要参数(还是这样绅士一些),告诉编译器需要的文件的位置。(除此之外,还有其他一些参数也是如此)。
从源程序到可执行文件根据情况不同可能分好几步,一般每一步可能都会有一个应用程序实现,像gnu提供的arm开发工具链其实就是这么一组程序。提供从编译到链接到格式转化的全套服务。你可以用arm-elf-gcc命令一步到底直接产生可执行文件(其实也是在自己的任务完成后调用下一个程序),也可以每一步加上自己的参数,只作自己的事。
编译器的主要参数的使用下次将程序的移植时再讲。这里想说一下编译器产生应用程序的几个主要步鄹,讲这个问题的原因还是很多人无法区分诸如编译和链接,不用问,这一切还是IDE集成开发环境惹的祸。有人会说,IDE招你惹你了,你老贬它。其实不然,首先以上说的东西一般在IDE的project菜单下的option或build option中找到,只是一般不用管罢了。另一个方面,IDE就像是傻瓜照相机,很多工作他都帮你完成了,使用简单。但如果要做摄影师的话,你就少不了要对每一个细节都了解。其实编译程序也是一样。(你可以对优化,警告级,宏定义等诸多选项进行自己的选择)。以下是几个主要步鄹:(以下有些我也不确认,如发现问题,请及时纠正。
(1) 预编译。 主要工作就是处理所有#开头的,包括头文件。以前搞不清头文件和可执行文件有没有什么联系(因为总看见两个文件名字取一样的),现在知道,他们之间没有任何联系。在预编译结束后,头文件的使命就结束了。在下一次介绍不同平台程序移植时可以看到,预编译有时非常有用。
(2) 编译。编译应该是最主要的一步,就是将源文件生成CPU能识别的语言,一般是 后缀为.o的目标文件,应该说,此时的文件就已经可以执行了。当然这个时候外部函数等外部符号都没有引入,对于被编译程序来说,这些外部符号还只是留一个倩影,压根儿不知它在不在。你可以在你的程序里调用一个不存在的函数,甚至都不用声明,在编译阶段,很多编译器只是给个警告。只有在链接时才会报错。(呵呵,够弱智!)
(3) 链接:链接才是清帐的时候,以前在程序里用到的外部符号都要把真正的东西交出来。你可以指定需要连接在一起的目标文件,也可以告诉编译器库文件的名字和路径(指定方法下次讲)。编译器会去找,需要注意的是,库的指定需要注意顺序。首先,如果不同的库里有同名函数,并且该函数被调用,那么在前面的就被链接进去了,这一点对于头文件路径的指定也适用,如果你自己的头文件和系统头文件相同,并且你的头文件路径在系统头文件路径前面,你的头文件就会代替头文件。库文件是由相应的程序(linux下是ar命令)将需要被添加到库里的目标文件(该文件是编译阶段生成的)组织起来生成档案文件,同时可以建立一个检索,检索内容为所包含的目标文件中定义的符号。也就是说,库文件并不是必须的,但它为经常使用的目标文件中的函数提供了快速的检索机制。
以上就是主要的步鄹,当然除此之外,还有一些用于格式转换的工具等。不一一介绍。知道编译器的细节对于程序的开发和移植都是很有好处的。
程序开发过程中调试也是至关重要,因为可以先在主机上调试,所以可以使用linux下的gdb,(有点像dos 下的debug)。但是只是用到了皮毛,还有一个专用于宿主机模式的调试工具gdbserver,一直没时间研究,希望用过的大侠多发些文章铺路。
另外还会遇到如何作ramdisk,如何让系统启动自己的程序,这些都太linux了,没接触过linux的人会晕,为了大家的健康,就不讲了,遇到问题可以给我email,大家一起讨论。
4.不同平台间程序的移植--简单程序的移植
研究程序移植的那两周是最痛苦的两周,没有太多可以借鉴的东西,只能摸黑向前走,于是更加坚定决心要整理些东西给后来的弟兄。不过话说回来,各位弟兄别被我前面说的吓倒,只要搞清你要作什么,程序移植其实是比较简单的事情。
首先列出一些问题:
(1) X86上运行的程序能不能在51单片机上运行,为什么,有没有可能,如果可以,应该做哪些工作才可以实现。
(2) 相同CPU平台,DOS的程序为什么可以在windows下运行,能不能在linux下运行,为什么,作什么工作可能实现。
为什么可以移植程序,为什么要移植程序?
程序可以移植首先要感谢开发出高级语言的大牛们,记住,无论多么漂亮的代码经过编译以后都要变成CPU可以识别的机器语言,而几乎一千种CPU说着一千种语言。为保证大家有共同语言,规定一种高级语言――高级语言。每一个CPU派出自己的翻译――编译器。这个翻译精通两国语言,高级语言和自己的语言。(由此已经可以看出编译工具在程序移植中的重要性)。只要程序没有硬件上的约束,可以说这种沟通是无极限的,甚至于不同操作系统平台。(操作系统也是程序,也可以移植喽)举例:在x86的windows下用VC(或TC,BC)编一个c程序实现i=i+1,丝毫不改就可以用51的C编译器重新编译并在51单片机上运行。一次小型的移植就结束了。
可以移植已有的程序还要感谢开放源代码的弟兄,没有这些C文件和H文件让你重新编译一下,怎么在你的CPU上跑?其实不止这些,后面还会看见开源组织的牛人专为程序可移植性所作的专门的工作。
那么为什么要移植程序?
问这问题就像问地上有个钱包为什么要捡一样,答案不言而喻。现成的东西为什么不要。当然,移植程序可没有捡钱包那么简单,尤其是第一次,后面会说一些移植之前应该考虑的问题。(就像现在地上有个钱包也千万别上去就揣自己兜里,说不定就是套)。另一方面,你给我你写好的程序让我拿去用,我还要考虑一下,或许里头问题多的还不如自己写一个。我这里所说的可移植的程序应该是维护比较好,比较成熟的源代码(像我后面的所说的UCD-SNMP),目前的开放源代码运动决不仅仅是把自己的程序公开就行了,而是有了一套成熟完整的版本控制,BUG报告和PATCH提交流程。
这样的代码才有更大的使用价值。
什么时候可以考虑移植程序? 在基于嵌入式操作系统进行开发时,具有一定规模的程序都可以到网上查一查都没有成熟的源代码可用。前面已经说到,程序的移植最终只针对CPU,其实和操作系统没什么关系,但另一方面,因为代码中可能会使用一些库函数,这些库会包括C语言标准库和操作系统提供的API(应用程序接口)库。假设源代码中只包括C标准库,那么该程序就可以跨操作系统去移植。例如hello world程序中使用了printf,因为该函数是C标准函数,所以在X86上使用TC(BC或VC)可以直接编译运行,在ARM+uClinux平台下也一样,但如果程序中调用了vfork函数,那么只有 linux一脉相承的操作系统支持这种特殊服务了,在window或dos操作系统下无法直接编译该程序了。只能找该操作系统支持的类似的功能来实现。再进一步,硬件上的生理缺陷也会为移植带来麻烦,S3C4510B不支持MMU,在其上运行的uClinux也不提供和MMU有关的服务(其实uClinux本身可以支持MMU),于是在移植前相关的函数(比如FORK)都要被替代掉(使用VFORK)。好在uClinux和linux提供的应用接口大部分还是相同的。所以这样的工作还可以承受。
由上可知,如果是在各种嵌入式linux(除了uClinux以外,还有好几种)平台上开发,那么针对该平台以及linux平台上的源代码都可以使用,但是要牢记他们之间的差异。在我的系统中需要实现网络监控,所以想使用snmp协议,该协议和http,ftp一样属于应用层的成熟协议,专用于网络管理。目前已经有一些针对该协议成熟的代码,最有名的是ucd-snmp,不光软件本身功能强大,可移植性也比较好,在linux,unix等平台上都可以移植,于是决定将它移植到ARM+uClinux平台上(别看现在说的这么轻松,当时接这活时都有点哆嗦)。
简单总结一下,移植应用程序的前提是有源代码,移植的关键工具是编译器,源代码中和硬件平台相关的东西越少越好(这里主要指使用了汇编,或做了针对自己平台的事,比如将指针指向特定地址然后操作),另一方面,如果该程序是基于某个操作系统(利用了操作系统提供的特殊服务,即API),要看自己的操作系统是否提供了相关服务。
下面简单列出一些我认为移植时需要考虑的问题:
(1) 自己的操作系统的特点以及在当前版本下支持的特性。
例如:uClinux不支持MMU,同样就无法支持相应的特性。
(2) 硬件资源。
因为嵌入式系统资源比较紧张,硬件资源考虑必须要周全:
(1) 软件存储空间的大小
这一般要等用目标编译器重新编译完以后可能才会知道,所以只能大概估算,但千万不要看这个程序在linux下只有几十k,就认为程序很小,这是因为linux下程序多时使用动态库,而在嵌入式系统中,很有可能是把用到的库都链接在一起,所以程序的尺寸会大大增加。
(2) 程序运行空间。.
(3) 硬件以及相应的驱动是否完备
以上工作应该尽量做,但有时事先无法把握,只能听天由命了(有没有搞错!!)
可能有人已经要晕菜了,振奋一下大家,如果找到了好的源代码(可移植性好),那么剩下的如要工作就是玩转你的编译器,只要你能顺利的把源代码用你的编译器重新编译一下。90%的工作就完成了(不是吗)
上回已经介绍了一些编译器方面的东西,下面针对我的ARM编译器的具体参数来讲解一些编译器主要参数的设置。
加入我已经有了hello.c,在x86的linux平台下编译链接一下。
gcc –c hello.c 产生.o
gcc –o hello hello.o 产生可执行文件,上回说过,主机编译器参数都有环境变量保存,所以看起来很简单。这里我故意分两个步鄹。
下面看一下用我的编译器编这个程序(心脏不好的先吃药)。
arm-elf-gcc -Iroot/uClibc/include -msoft-float -mcpu=arm7tdmi -fomit-frame-pointer -fsigned-char -mcpu=arm7tdmi -Os –Wall -DEMBED -D_uClinux_ -c hello.c
这只是编译,将参数逐一讲解。
Arm-elf-gcc 是gnu的arm编译工具
1)Include地址:参数:-I 值:root/uClibc/include(这是在主机上我的uClinux的头文件路径) 用法:-I root/uClibc/include
-I参数保证后面的头文件路径在搜索系统头文件路径前被搜索从而有可能替代系统的头文件,如果有多个这样的参数,则搜索的顺序是从左到右,然后是系统的头文件。
2)-m 是针对CPU的选项。
-mcpu=arm7tdmi 说明CPU类型
-msoft-float 产生包含浮点库的输出
-fsigned-char 让char类型有符号
-fomit-frame-pointer 对所有不需要帧指针的函数都不将其保存在寄存器中。
3) -Os –Wall
-Wall:所有警告都显示
Os:优化尺寸,该选项使能所有所有不增加尺寸的O2优化,并且进一步根据尺寸优化
4) = -DEMBED -D_uClinux_
-D: 将-Dmacro 后的macro定义为字符串1。
以下是链接:
arm-elf-ld -L/root/uClibc/lib -L/usr/local/gnu/arm-elf/lib -L/usr/local/gnu/lib/gcc-lib/arm-elf/3.0.1 -elf2flt –o hello /root/uClibc/lib/crt0.o /usr/local/gnu/lib/gcc-lib/arm-elf/3.0.1/crtbegin.o hello.o
/usr/local/gnu/lib/gcc-lib/arm-elf/3.0.1/crtend.o -lc -lgcc –lc
其中
1) 链接工具: arm-elf-ld
2) -L指明需要链接的库的路径,用法和-I一样,自己的库的路径也可以在这里加入。 -L/root/uClibc/lib -L/usr/local/gnu/arm-elf/lib
-L/usr/local/gnu/lib/gcc-lib/arm-elf/3.0.1
3) –o 后面紧跟生成的最终的文件名
4)/root/uClibc/lib/crt0.o /usr/local/gnu/lib/gcc-lib/arm-elf/3.0.1/crtbegin.o OBJECTS.o
/usr/local/gnu/lib/gcc-lib/arm-elf/3.0.1/crtend.o
这是需要链接在一起的.o文件
5) -lc -lgcc –lc -l 后面紧跟的是需要链接的库的名字,一般库的名字是libxxx.a,使用时为-lxxx即可,不加lib和.a。还要注意位置,自己的库文件应该加在他的库前面。
编译通过后,移植就算完成了,对于比较小的源代码都可以这样,即先分析他的编译选项(用到了那些头文件,库文件等),然后用自己的编译器对照相应参数重新编译一下就行了。
当然这只是简单程序的移植,复杂案例在下一次讲吧。
发表于 @2006年03月15日 22:06:00 |评论 (0)
 ArmLinux BOOTLOADER全程详解
网上关于Linux的BOOTLOADER文章不少了,但是大都是vivi,blob等比较庞大的程序,读起来不太方便,编译出的文件也比较大,而且更多的是面向开发用的引导代码,做成产品时还要裁减,这一定程度影响了开发速度,对初学者学习开销也比较大,在此分析一种简单的BOOTLOADER,是在三星公司提供的2410 BOOTLOADER上稍微修改后的结果,编译出来的文件大小不超过4k,希望对大家有所帮助.
1.几个重要的概念
COMPRESSED KERNEL and DECOMPRESSED KERNEL
压缩后的KERNEL,按照文档资料,现在不提倡使用DECOMPRESSED KERNEL,而要使用COMPRESSED KERNEL,它包括了解压器.因此要在ram分配时给压缩和解压的KERNEL提供足够空间,这样它们不会相互覆盖.当执行指令跳转到 COMPRESSED KERNEL后,解压器就开始工作,如果解压器探测到解压的代码会覆盖掉COMPRESSED KERNEL,那它会直接跳到COMPRESSED KERNEL后存放数据,并且重新定位KERNEL,所以如果没有足够空间,就会出错.
Jffs2 File System
可以使armlinux应用中产生的数据保存在FLASH上,我的板子还没用到这个.
RAMDISK
使用RAMDISK可以使ROOT FILE SYSTEM在没有其他设备的情况下启动.一般有两种加载方式,我就介绍最常用的吧,把COMPRESSED RAMDISK IMAGE放到指定地址,然后由BOOTLOADER把这个地址通过启动参数的方式ATAG_INITRD2传递给KERNEL.具体看代码分析.
启动参数(摘自IBM developer)
在调用内核之前,应该作一步准备工作,即:设置 Linux 内核的启动参数。Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中.
在嵌入式 Linux 系统中,通常需要由 BOOTLOADER 设置的常见启动参数有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
(注)参数也可以用COMMANDLINE来设定,在我的BOOTLOADER里,我两种都用了.
2.开发环境和开发板配置:
CPU:S3C2410,BANK6上有64M的SDRAM(两块),BANK0上有32M NOR FLASH,串口当然是逃不掉的.这样,按照数据手册,地址分配如下:
0x4000_0000开始是4k的片内DRAM.
0x0000_0000开始是32M FLASH 16bit宽度
0x3000_0000开始是64M SDRAM 32bit宽度
注意:控制寄存器中的BANK6和BANK7部分必须相同.
0x4000_0000(片内DRAM)存放4k以内的BOOTLOADER IMAGE
0x3000_0100开始存放启动参数
0x3120_0000 存放COMPRESSED KERNEL IMAGE
0x3200_0000 存放COMPRESSED RAMDISK
0x3000_8000 指定为DECOMPRESSED KERNEL IMAGE ADDRESS
0x3040_0000 指定为DECOMPRESSED RAMDISK IMAGE ADDRESS
开发环境:Redhat Linux,armgcc toolchain, armlinux KERNEL
如何建立armgcc的编译环境:建议使用toolchain,而不要自己去编译armgcc,偶试过好多次,都以失败告终.
先下载arm-gcc 3.3.2 toolchain
将arm-linux-gcc-3.3.2.tar.bz2 解压到 /toolchain
# tar jxvf arm-linux-gcc-3.3.2.tar.bz2
# mv /usr/local/arm/3.3.2 /toolchain
在makefile 中在把arch=arm CROSS_COMPILE设置成toolchain的路径还有就是INCLUDE = -I ../include -I /root/my/usr/local/arm/3.3.2/include.,否则库函数就不能用了
3.启动方式:
可以放在FLASH里启动,或者用Jtag仿真器.由于使用NOR FLASH,根据2410的手册,片内的4K DRAM在不需要设置便可以直接使用,而其他存储器必须先初始化,比如告诉memory controller,BANK6里有两块SDRAM,数据宽度是32bit,= =.否则memory control会按照复位后的默认值来处理存储器.这样读写就会产生错误.
所以第一步,通过仿真器把执行代码放到0x4000_0000,(在编译的时候,设定TEXT_BASE=0x40000000)
第二步,通过 AxD把linux KERNEL IMAGE放到目标地址(SDRAM)中,等待调用
第三步,执行BOOTLOADER代码,从串口得到调试数据,引导armlinux
4.代码分析
讲了那么多执行的步骤,是想让大家对启动有个大概印象,接着就是BOOTLOADER内部的代码分析了,BOOTLOADER文章内容网上很多,我这里精简了下,删除了不必要的功能.
BOOTLOADER一般分为2部分,汇编部分和c语言部分,汇编部分执行简单的硬件初始化,C部分负责复制数据,设置启动参数,串口通信等功能.
BOOTLOADER的生命周期:
1. 初始化硬件,比如设置UART(至少设置一个),检测存储器= =.
2. 设置启动参数,这是为了告诉内核硬件的信息,比如用哪个启动界面,波特率 = =.
3. 跳转到Linux KERNEL的首地址.
4. 消亡
当然,在引导阶段,象vivi等,都用虚地址,如果你嫌烦的话,就用实地址,都一样.
我们来看代码:
2410init.s
.global _start//开始执行处
_start:
//下面是中断向量
b reset @ Supervisor Mode//重新启动后的跳转
reset:
ldr r0,=WTCON /WTCON地址为53000000,watchdog的控制寄存器 */
ldr r1,=0x0 /*关watchdog*/
str r1,[r0]
ldr r0,=INTMSK
ldr r1,=0xffffffff /*屏蔽所有中断*/
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x3ff /*子中断也一样*/
str r1,[r0]
/*Initialize Ports...for display LED.*/
ldr r0, =GPFCON
ldr r1, =0x55aa
str r1, [r0]
ldr r0, =GPFUP
ldr r1, =0xff
str r1, [r0]
ldr r0,=GPFDAT
ldr r1,=POWEROFFLED1
str r1,[r0]
/* Setup clock Divider control register
* you must configure CLKDIVN before LOCKTIME or MPLL UPLL
* because default CLKDIVN 1,1,1 set the SDMRAM Timing Conflictnop
* FCLK:HCLK:PCLK = 1:2:4 in this case
*/
ldr r0,=CLKDIVN
ldr r1,=0x3
str r1,[r0]
/*To reduce PLL lock time, adjust the LOCKTIME register. */
ldr r0,=LOCKTIME
ldr r1,=0xffffff
str r1,[r0]
/*Configure MPLL */
ldr r0,=MPLLCON
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) //Fin=12MHz,Fout=203MHz
str r1,[r0]
ldr r1,=GSTATUS2
ldr r10,[r1]
tst r10,#OFFRST
bne 1000f
//以上这段,我没动,就用三星写的了,下面是主要要改的地方
/* MEMORY C0NTROLLER(MC)设置*/
add r0,pc,#MCDATA - (.+8)// r0指向MCDATA地址,那里存放着MC初始化要用到的数据
ldr r1,=BWSCON // r1指向MC控制器寄存器的首地址
add r2,r0,#52 // 复制次数,偏移52字
1: //按照偏移量进行循环复制
ldr r3,[r0],#4
str r3,[r1],#4
cmp r2,r0
bne 1b
.align 2
MCDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
上面这行就是BWSCON的数据,具体参数意义如下:
需要更改设置DW6 和DW7都设置成10,即32bit,DW0 设置成01,即16bit
下面都是每个BANK的控制器数据,大都是时钟相关,可以用默认值,设置完MC后,就跳到调用main函数的部分
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0xB2 /* REFRESH Control Register */
.word 0x30 /* BANKSIZE Register : Burst Mode */
.word 0x30 /* SDRAM Mode Register */
.align 2
.global call_main //调用main函数,函数参数都为0
call_main:
ldr sp,STACK_START
mov fp,#0 /* no previous frame, so fp=0*/
mov a1, #0 /* set argc to 0*/
mov a2, #0 /* set argv to NUL*/
bl main /* call main*/
STACK_START:
.word STACK_BASE
undefined_instruction:
software_interrupt:
prefetch_abort:
data_abort:
not_used:
irq:
fiq:
/*以上是主要的汇编部分,实现了时钟设置,串口设置watchdog关闭,中断关闭功能(如果有需要还可以降频使用),然后转入main*/
2410init.c file
int main(int argc,char **argv)
{
u32 test = 0;
void (*theKERNEL)(int zero, int arch, unsigned long params_addr) = (void (*)(int, int, unsigned long))RAM_COMPRESSED_KERNEL
_BASE; //压缩后的IMAGE地址
int i,k=0;
// downPt=(RAM_COMPRESSED_KERNEL_BASE);
chkBs=(_RAM_STARTADDRESS);//SDRAM开始的地方
// fromPt=(FLASH_LINUXKERNEL);
MMU_EnableICache();
ChangeClockDivider(1,1); // 1:2:4
ChangeMPllValue(M_MDIV,M_PDIV,M_SDIV); //Fin=12MHz FCLK=200MHz
Port_Init();//设置I/O端口,在使用com口前,必须调用这个函数,否则通信芯片根本得不到数据
Uart_Init(PCLK, 115200);//PCLK使用默认的200000,拨特率115200
/*******************(检查ram空间)*******************/
Uart_SendString("ntLinux S3C2410 Nor BOOTLOADERn");
Uart_SendString("ntChecking SDRAM 2410loader.c...n");
for(;chkBs<0x33FA0140;chkBs=chkBs+0x4,test++)//
//根据我的经验,最好以一个字节为递增,我们的板子,在256byte递增检测的时候是没问题的,但是
//以1byte递增就出错了,第13跟数据线随几的会冒”1”,检测出来是硬件问题,现象如下
//用仿真器下代码测试SDRAM,开始没贴28F128A3J FLASH片子,测试结果很好,但在上了FLASH片子//之后,测试数据(data)为0x00000400
连续成批写入读出时,操作大约1k左右内存空间就会出错,//而且随机。那个出错数据总是变为0x00002400,数据总线10位和13位又没短路
发生。用其他数据//测试比如0x00000200;0x00000800没这问题。dx帮忙。
//至今没有解决,所以我用不了Flash.
{
chkPt1 = chkBs;
*(u32 *)chkPt1 = test;//写数据
if(*(u32 *)chkPt1==1024))//读数据和写入的是否一样?
{
chkPt1 += 4;
Led_Display(1);
Led_Display(2);
Led_Display(3);
Led_Display(4);
}
else
goto error;
}
Uart_SendString("ntSDRAM Check Successful!ntMemory Maping...");
get_memory_map();
//获得可用memory 信息,做成列表,后面会作为启动参数传给KERNEL
//所谓内存映射就是指在4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。
Uart_SendString("ntMemory Map Successful!n");
//我用仿真器把KERNEL,RAMDISK直接放在SDRAM上,所以下面这段是不需要的,但是如果KERNEL,RAMDISK在FLASH里,那就需要.
/*******************(copy linux KERNEL)*******************/
Uart_SendString("tLoading KERNEL IMAGE from FLASH... n ");
Uart_SendString("tand copy KERNEL IMAGE to SDRAM at 0x31000000n");
Uart_SendString("ttby LEIJUN DONG dongleijun4000@hotmail.com n");
for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M
* (u32 *)downPt = * (u32 *)fromPt;
/*******************(load RAMDISK)*******************/
Uart_SendString("ttloading COMPRESSED RAMDISK...n");
downPt=(RAM_COMPRESSED_RAMDISK_BASE);
fromPt=(FLASH_RAMDISK_BASE);
for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M
* (u32 *)downPt = * (u32 *)fromPt;
/******jffs2文件系统,在开发中如果用不到FLASH,这段也可以不要********/
Uart_SendString("ttloading jffs2...n");
downPt=(RAM_JFFS2);
fromPt=(FLASH_JFFS2);
for(k = 0;k < (1024*1024/32);k++,downPt += 1,fromPt += 1)
* (u32 *)downPt = * (u32 *)fromPt;
Uart_SendString( "Load Success...Run...n ");
/*******************(setup param)*******************/
setup_start_tag();//开始设置启动参数
setup_memory_tags();//内存印象
setup_commandline_tag("console=ttyS0,115200n8");//启动命令行
setup_initrd2_tag();//root device
setup_RAMDISK_tag();//ramdisk image
setup_end_tag();
/*关I-cache */
asm ("mrc p15, 0, %0, c1, c0, 0": "=r" (i));
i &= ~0x1000;
asm ("mcr p15, 0, %0, c1, c0, 0": : "r" (i));
/* flush I-cache */
asm ("mcr p15, 0, %0, c7, c5, 0": : "r" (i));
//下面这行就跳到了COMPRESSED KERNEL的首地址
theKERNEL(0, ARCH_NUMBER, (unsigned long *)(RAM_BOOT_PARAMS));
//启动kernel时候,I-cache可以开也可以关,r0必须是0,r1必须是CPU型号
(可以从linux/arch/arm/tools/mach-types中找到),r2必须是参数的物理开始地址
/*******************END*******************/
error:
Uart_SendString("nnPanic SDRAM check error!n");
return 0;
}
static void setup_start_tag(void)
{
params = (struct tag *)RAM_BOOT_PARAMS;//启动参数开始的地址
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next(params);
}
static void setup_memory_tags(void)
{
int i;
for(i = 0; i < NUM_MEM_AREAS; i++) {
if(memory_map[i].used) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size(tag_mem32);
params->u.mem.start = memory_map[i].start;
params->u.mem.size = memory_map[i].len;
params = tag_next(params);
}
}
}
static void setup_commandline_tag(char *commandline)
{
int i = 0;
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = 8;
//console=ttyS0,115200n8
strcpy(params->u.cmdline.cmdline, p);
params = tag_next(params);
}
static void setup_initrd2_tag(void)
{
/* an ATAG_INITRD node tells the kernel where the compressed
* ramdisk can be found. ATAG_RDIMG is a better name, actually.
*/
params->hdr.tag = ATAG_INITRD2;
params->hdr.size = tag_size(tag_initrd);
params->u.initrd.start = RAM_COMPRESSED_RAMDISK_BASE;
params->u.initrd.size = 2047;//k byte
params = tag_next(params);
}
static void setup_ramdisk_tag(void)
{
/* an ATAG_RAMDISK node tells the kernel how large the
* decompressed ramdisk will become.
*/
params->hdr.tag = ATAG_RAMDISK;
params->hdr.size = tag_size(tag_ramdisk);
params->u.ramdisk.start = RAM_DECOMPRESSED_RAMDISK_BASE;
params->u.ramdisk.size = 7.8*1024; //k byte
params->u.ramdisk.flags = 1; // automatically load ramdisk
params = tag_next(params);
}
static void setup_end_tag(void)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
} void Uart_Init(int pclk,int baud)//串口是很重要的
{
int i;
if(pclk == 0)
pclk = PCLK;
rUFCON0 = 0x0; //UART channel 0 FIFO control register, FIFO dISAble
rUMCON0 = 0x0; //UART chaneel 0 MODEM control register, AFC dISAble
//UART0
rULCON0 = 0x3; //Line control register : Normal,No parity,1 stop,8 bits
下面这段samsung好象写的不太对,但是我按照Normal,No parity,1 stop,8 bits算出来的确是0x245
// [10] [9] [8] [7] [6] [5] [4] [3:2] [1:0]
// Clock Sel, Tx Int, Rx Int, Rx Time Out, Rx err, Loop-back, Send break, Transmit Mode, Receive Mode
// 0 1 0 , 0 1 0 0 , 01 01
// PCLK Level Pulse DISAble Generate Normal Normal Interrupt or Polling
rUCON0 = 0x245; // Control register
rUBRDIV0=( (int)(PCLK/16./ baud) -1 ); //Baud rate divisior register 0
delay(10);
}
经过以上的折腾,接下来就是kernel的活了.能不能启动kernel,得看你编译kernel的水平了.
Copyright ? arthas.kang