嵌入式Linux 开发环境的建立
V1.0
【版本信息】
2010年4月28日 杨哲 编写初始版本V1.0
由于开发板采用的是CarmFS 文件系统,CramFS 文件系统为只读文件系统,不能添加和修改文件。为了以后可以随时修改、配置开发环境,所以我们按自己的要求制作好根文件系统,具体步骤如下:
本手册的一些约定:
1) 工作目录为:/up-Star2410。
2) 手册中的命令默认情况下,是在pc机的终端下输入的,如果在arm中输入的命令,文中会特别注明。
1、 安装交叉编译器
我们这里选用友善之臂提供的4.3.2版本,相比3.x.x版本,4.3.2的交叉编译器采用EABI技术(EABI的全称是:Embedded Application Binary Interface),它针对软件浮点数运行做了优化,使用它编译出来的应用程序效率更高,不过也有一个不好的地方:就是和以前版本编译的应用程序不兼容了,全部都需要程序编译
所以我们这里统一只使用4.3.2版本,在PC机上,具体安装步骤如下:
# cd /up-Star2410
# tar -zxvf arm-linux-gcc-4.3.2.tgz
解压后的目录为usr目录,将usr的子目录4.3.2拷贝到根目录/usr/local/arm下
然后
# vi /root/.bash_profile
将里边的PATH修改为
PATH=$PATH:HOME/bin:/usr/local/arm/4.3.2/bin/
保存退出,执行:
# source /root/.bash_profile
则以后的arm-linux-gcc-4.3.2会被自动搜索到
验证是否正确安装:
在终端上输入ar,然后双击tab键,会自动显示arm-linux-gcc-4.3.2,如果同时显示了以前的旧版本,则需重新启动虚拟机
2、 烧写内核镜像和根文件系统
由于内核镜像和根文件系统都存储在64MB的NAND Flah上,首先了解一下NAND Flah的分区情况:
名称
起始地址
结束地址
大小
作用
Boot Agent
0x00000000
0x00080000
0.5M
启动u-boot
S3C2410 kernel
0x00080000
0x00280000
2M
Linux内核镜像
S3C2410 rootfs
0x00280000
0x00680000
4M
根文件系统,只读,cramfs
user
0x00680000
0x04000000
57.5M
用户文件系统,可读,yaffs
注意:第四个分区user是yaffs2格式,可读,系统启动时会把user挂载到根文件系统的root目录下,所以root目录是可读的,并有57.5M的空间供用户使用,但是其它目录还是不可写的。
1) 制作根文件系统包
由于时间原因,本手册并未给出制作根文件系统的具体步骤(后续手册中会具体写到)
# cd /up-Star2410
# tar –zxvf rootfs.tar.gz
# ./mkfs.cramfs rootfs root.cramfs
这就生成了cramfs包,然后把它拷贝到tftp下载目录(本机为跟目录tftpboot),下一步要烧写到flash中
2) 制作内核镜像uImage
源码已经提供,直接把uImage复制到tftfboot目录下用就可以了,具体怎么裁剪和编译内核请看后续手册。
3) 烧写内核镜像和cramfs文件系统
注意:下载需用到tftp服务器,所以必须确保系统已经安装tftp服务器,下面验证你的系统是否已经安装了tftp服务器:
#service xinetd restart
#netstat –a | grep tftp
出现下图
明你已经安装了tftp服务器
①、配置bootloader
重启开发板,马上按下enter键,
在提示符下输入setenv serverip 192.168.1.137,此IP为我们虚拟机的IP,再输入setenv ipaddr 192.168.1.193,此IP为我们开发板的IP。最好输入saveenv,保存设置,之后就不需要改动了。
设置之后,我们可以通过printenv指令,查看u-boot的环境变量中已经添加了新的变量,如图(如果不是,请按开发板带的快速手册中的烧写部分烧写)
这时,我们的u-boot的配置就结束了。
②、在配置好u-boot之后,执行烧写内核的命令,输入run update_kernel指令,如图。
③、烧写根文件系统
执行烧写根文件系统的命令,输入run update_rootfs指令,如图
然后输入boot启动linux系统,至此开发板的环境已经建立好了。
特别注意:以后我们要不断的向根文件系统中添加相应的功能,需要随时更新系统,按照上述步骤执行就可以了。
这里有必要对开发板的部分启动信息进行一些解释
前三行表明开发板中的boa服务器已经启动,端口号是80。
(boa服务器的移植请看后续手册)
第四行表明user分区(即/dev/mtdblock3)已经挂载到root目录下,可以看到它是yaffs文件格式。
最后一行 [root@yangzhe]# 中的yangzhe是我自己设置的,有兴趣的话可以在根文件系统中的/etc/profile中修改PS1的值,修改为自己喜欢的格式,如下:
# cd /up-Star2410/rootfs/etc
# vi profile
修改完后更新根文件系统。
基于Boa的Web服务器设计
1、Boa服务器功能概述和实现
Boa是一款单任务的HTTP服务器,与其他传统的Web服务器不同的是当有连接请求到来时,它并不为每个连接单独创建进程,也不通过复制自身进程来处理多链接,而是通过建立HTTP请求列表来处理多路HTTP连接请求,同时它只为CGI程序创建新的进程,这样就在最大程度上节省了系统资源,这对嵌入式系统来说至关重要。同时它还具有自动生成目录、自动解压文件等功能,因此,Boa具有很高的HTTP请求处理速度和效率,在嵌入式系统中具有很高的应用价值。[14]
嵌入式Web服务器Boa和普通Web服务器一样,能够完成接收客户端请求、
请求、响应请求、向客户端返回请求结果等任务。它的工作过程主要包括:
1. 完成Web服务器的初始化工作,如创建环境变量、创建TCP套接字、绑定端口、开始侦听、进入循环结构,以及等待接收客户浏览器的连接请求;
2. 当有客户端连接请求时,Web服务器负责接收客户端请求,并保存相关请求信息;
3. 在接收到客户端的连接请求之后,分析客户端请求,解析出请求的方法、URL目标、可选的查询信息及表单信息,同时根据请求做出相应的处理;
4. Web服务器完成相应处理后,向客户端浏览器发送响应信息,关闭与客户机的TCP连接。嵌入式Web服务器Boa根据请求方法的不同,做出不同的响应。
Boa的功能实现也是通过建立连接、绑定端口、进行侦听、请求处理等来实现的。嵌入式Web服务器由于嵌入式系统资源非常有限,不能直接使用桌面或服务器上的Web服务器。Boa是一个单任务的HTTP服务器,它并不为每个新的连接启动新的进程,也不为多重连接而自我复制进程,而是内部并行处理所有的连接。Boa也遵循GNU协议,完全免费,完全开放源代码。
3、 移植Boa到开发平台
具体移植步骤请参看BOA服务器的移植文档。
这里我们使用的文件系统中已经移植好了boa服务器,只要知道怎么使用就可以了,这里需要说明几点:
①交叉编译好的boa可执行文件我们放在/sbin 目录下
②boa服务器的配置文件放在/etc/boa目录下,如果需要需该参数可以在这里修改
③boa服务器的主目录我们设置在/root/web下,这个目录很重要,它存放HTML文档,就是我们制作的网页。
④cgi脚本路径我们设置在/root/web/cgi-bin目录下,这里存放cgi程序。
4、 测试静态网页
boa移植好之后,我们就可以在windows下的浏览器访问开发板(/root/web)上的HTML页面了,下面我们复制已经准备好的页面到开发板中,进行测试。
在arm端挂载NFS共享目录:
# mount –t nfs –o nolock 192.168.1.137:/up-Star2410 /mnt/nfs
# cd /mnt/nfs
复制虚拟机下/up-Star2410下的web目录到开发板的root目录下
# cp –rf web /root
注意:有时可能不能通过nfs向开发板的root目录下复制文件,请使用其它方法传输文件。
接下来在主机的浏览器地址栏输入http://192.168.1.3/LXI-home.html,其中192.168.1.3是开发板的IP地址,该地址可能随时改变,观察在客户机的浏览器中的连接请求结果(如图)和在开发板上的服务器的打印信息。
5、 通用网关接口CGI
前面我们实现了在客户端请求开发板上的网页,但是我们的目标是通过在客户端上控制开发板上的LED灯,那么现在的问题是怎么把客户机上的信息传到开发板上,实现真正的双向交互,这里我们选用CGI程序,它是嵌入式Web服务器的首选,在桌面领域则还有很多其他形式的动态网页,比如ASP、JSP、PHP等
①CGI 的概念
CGI(Common Gateway Interface) 通用网关接口的简称。其主要的功能是在WWW 环境
下,从客户端传递一些信息给Web 服务器,再由Web 服务器去启动所指定的程序来完成特定的工作。所以简单点说,CGI 是一种通用的接口标准。
CGI 可以为我们提供许多HTML(HyperText Markup Language,超文本标记语言)无法
做到的功能。比如一个计算器、顾客表格的提交以及统计、搜索引擎、Web 数据库等等。
用HTML 是没有办法记住客户的任何信息的。要把顾客的信息记录在服务器的硬盘上,就
要用到CGI。
②CGI 的工作原理
CGI 是一种通用的接口标准。CGI 程序就是符合这种接口标准的,运行在Web 服务器
上的程序。它的工作就是控制信息要求,产生并传回所需的文件。CGI 由浏览器的输入触发这个程序。
先看看浏览器浏览网页是怎样实现的。作为一个用户首先在浏览器的地址栏中添加上要
访问的主页地址并回车触发这个申请。浏览器将申请发送到服务器上。Web 服务器接收这
些申请并根据.htm 或.html 的后缀并认识到这是HTML 文件。Web 服务器从当前硬盘或内存中读取正确的HTML 文件,然后将它送回浏览器。HTML 文件将被用户的浏览器解释并将结果显示在用户浏览器上。
CGI 程序可以用来在web 内加入动态的内容。通过接口,浏览器能够发送一个可执行
应用程序的HTTP 请求,而不仅仅只是静态的HTML 文件。服务器运行指定的应用程序,
这个应用程序读取与请求相关的信息,获得请求传过来的数值。例如使用者填写HTML 表
单提交了数据,浏览器将这些数据发送到Web 服务器上。Web 服务器接收这些数据并根据
客户机指定的CGI 程序把这些数据递交给指定的CGI 程序,并使CGI 在服务器上运行。CGI
程序运行结束,生成HTML 页面,Web 服务器把CGI 程序运行的结果送回用户浏览器。HTML文件将会被用户的浏览器解释并将结果显示在用户浏览器上。CGI 的基本工作情况如下图所示:
HTTP请求 执行 调用
URL
格式文档 运行结果 返回
③CGI的输入与输出
Web 服务器与CGI 程序之间通过四种途径进行通信: 环境变量、命令行、标准输入和
标准输出。其中负责输入的有环境变量、命令行和标准输入。命令行只用于ISINDEX 查询,
较少使用。环境变量存放服务器向CGI 程序传递的一些运行参数, 比REQUEST_METHOD 表示用户提出请求或提交数据的方法是GET 还是POST。方法(METHOD) 是HTTP 中对命令的称呼。GET 方法通过环境变量QUERY- STRING 传递用户提交的数据。经过编码的数据以问号打头追加在标识CGI 脚本地址的URL 后一起传给Web 服务器。服务器将其存于QUERYSTRING中, CGI 程序可以通过getenv( )函数来读取。编码数据除了表单数据, 还可以是直接调用CGI 脚本时追加在URL 地址后面的参数。POST 方法则通过标准输入( stdin) 传递提交数据。编码了的表单数据独立地传送给Web 服务器, CGI 程序从标准输入中获得, 可以用getchar( ), sscanf( ), fread( )等函数。要注意的是数据的长度是通过读取环境变量CONTENT_LENGTH 获得的, 而不是通过文件尾标识符来判断。
④CGI 编程语言的选择
CGI程序可以用任何程序设计语言编写,如shell脚本语言、Perl、Fortran、Pascal、C语言等。不过在嵌入式系统中,由于C语言编写的CGI程序具有执行速度快、内存开销小且安全性高(因为C语言程序是编译执行且不可被修改)等特点,应用更为广泛一些。
⑤CGI 程序的使用方法
一般而言,要使用CGI 程序就必须在Web 网页中迁入调用CGI 程序的代码。通常的做法有三种,一是通过表单调用,二是通过超链接调用,三是通过SSI 调用。这里着重阐述
使用表单调用CGI 程序的办法。
那么如何将数据通过表单提交给CGI 程序了,下面是一个简单的表单:
对应的源代码是
简单LED测试
网页中表单由字头
结束。其中action=“/cgi-bin/led.cgi”指明使用的CGI 程序名为led.cgi;method 属性指定提交数据的方法(POST 还是GET),这里使用的是GET 方法。
这个实例中,有三个复选框按钮,它们的名字是led1,led2,led3,对应的值是1,2,3。这里首先了解一下CGI编码的规则。规则如下:不同域(变量值对)之间用“&”分开;变量与值之间用“=”连接;空格符用“+”代替;任何特殊字符用“%”接相应的十六进制ASCII 码代替,最后形成的格式为:name1=value1&name2=value2%name3=value3 ...。假如用户选中了led1,然后点击提交按钮,那么表单中的数据就会被编码。最好形成的编码如下:led1=1;
如果选中led2,则形成的编码如下:led2=2;如果选中led2和led3,则最后提交给服务器的数据形式是:led2=2&led3=3,其他依次类推。
这样编码之后,客户端浏览器就会把如led2=2&led3=3传到action所指的服务器端程序中如/cgi-bin/led.cgi,开发板中的led.cgi程序将上面的编码解析后,就知道用户进行了哪些操作。
⑥ CGI 程序的编写
对于CGI 程序来讲,当采用GET 方式提交数据时,用户提交的数据放在环境变量QUERY_STRING 中。CGI 程序从环境变量QUERY_STRING 获得数据。为了解释和执行程序,CGI 必须要分析(处理)这个字符串。当你想从服务器获得数据并且不改变服务器上的数据时,应该选用GET。但是用GET 方式提交时,数据不经过CGI 编码,而且数据长度不能超过1K 字节。否则只能用POST 方式了。
CGI 程序的任务大概有两部分,输入任务和输出任务。输入任务就是指获取用户提交数据的过程。输入任务大概分以下几个步骤:(1)首先从CGI 环境变量REQUESR_METHOD中获取CGI 程序的提交方式;(2)根据提交方式的不同取出变量名和变量值。如果是Get方式,从环境变量QUERY_STRING 中取出编码数据;如果是POST 方式,从标准输入输出中读取相应的字符串,读取长度有环境变量CONTENT_LENGTH 决定。(3)根据CGI 编规则取出字符串中的数据(变量名和变量值对)。
在输出任务完成后,我们得到所有的变量名和变量值对,而后就是输出任务。输出任务主要有三个:(1)输出HTTP 响应的头标志。如printf(“Content-type:text/html\n\n”);它告诉Web 服务器随后的输出是以HTML 文本形式输出的。注意这个头信息中有两个换行符,这是因为Web 服务器需要再实际的文本信息开始之前先看见一个空行。(2)调用其它程序(如测量程序、设置参数的程序、或者查询数据库的程序)。(3)输出CGI 程序执行结果。
简单LED控制(CGI实现)
现在设计一个简单的LED控制页面。有三个复选框按钮,对应开发板3个LED灯,目标是可以任意控制开发板上的3个LED灯。
首先安装LED灯驱动,这里我们提供了编译好的驱动模块,通过NFS方式把mini2410-leds.ko和test_led复制到开发板的root目录下,然后在arm终端执行:
# insmod mini2410-leds.ko
查看新加设备对应的主设备号:
# cat /proc/devices
231 leds
手动创建设备节点:
#mknod /dev/leds c 231 0
下来测试led驱动是否装好
#./test_led 0 1
看第一个LED灯是否亮了,如果没亮就说明驱动没有加载上。
CGI程序:(此程序对应上面的网页)
cgi_led.c:(此源码放在web文件夹中的cgi-bin中)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_ARG 150
/*
此函数通过识别‘&’和‘=’,对客户端传来的数据进行解析,以下的解析过程有些复杂,还有待完善。
*/
unsigned int paraExtract(unsigned int pos, unsigned char *pIn, unsigned char *pOut, unsigned char *pValue)
{
unsigned int count = 0;
while(*(pIn + pos) != '&')
{
if(*(pIn + pos) == '\0')
{
*(pValue + 1) = '\0';
return 0xffff;
}
*(pOut + count) = *(pIn + pos);
if(*(pIn + pos) == '=')
{
*pValue = *(pIn + pos + 1);
*(pOut+count) = '\0';
}
pos++;
count++;
}
*(pValue + 1) = '\0';
return (pos + 1);
}
int main()
{
/*读取环境变量"QUERY_STRING"得到参数字符串*/
char *buf = getenv("QUERY_STRING");
//printf("%s\n", getenv("QUERY_STRING"));
char para1[MAX_ARG], para2[MAX_ARG],para3[MAX_ARG];
char value1[MAX_ARG], value2[MAX_ARG],value3[MAX_ARG];
/*对数组初始化,否则,会出现乱码*/
para1[0] = '\0';
para2[0] = '\0';
para3[0] = '\0';
value1[0] = '\0';
value2[0] = '\0';
value3[0] = '\0';
unsigned int extractEnd = 0;
unsigned int extractTemp = 0;
unsigned int n=0;
if (buf == NULL)
{
value1[0]=0;
value2[0]=0;
value3[0]=0;
}
else
{
while(1)
{
extractTemp = paraExtract(extractEnd, buf, para1, value1);
extractEnd = extractTemp;
if(extractEnd == 0xffff)
{
/*说明只传来了一对参数,如led1=1或led2=2或led3=3*/
switch(value1[0])
{
case '1':{
value1[0]=1;strcpy(para1,para1);
value2[0]=0;strcpy(para2,"0");
value3[0]=0;strcpy(para3,"0");
break;
}
case '2':
{
value2[0]=2;strcpy(para2,para1);
value1[0]=0;strcpy(para1,"0");
value3[0]=0;strcpy(para3,"0");
break;
}
case '3':
{
value3[0]=3;strcpy(para3,para1);
value1[0]=0;strcpy(para1,"0");
value2[0]=0;strcpy(para2,"0");
break;
}
}
break;
}
extractTemp = paraExtract(extractEnd, buf, para2, value2);
extractEnd = extractTemp;
if(extractEnd == 0xffff)
{
/*说明只传来了两对参数,如led1=1&led2=2或led2=2&led3=3或者led1=1&led3=3*/
switch(value1[0])
{
case '1':
{
switch(value2[0])
{
case '2':{value2[0]=2;strcpy(para2,para2);break;}
case '3':{
value3[0]=3;strcpy(para3,para2);
value2[0]=0;strcpy(para2,"0");
break;
}
}
break;
}
case '2':
{
value3[0]=3;strcpy(para3,para2);
value2[0]=2;strcpy(para2,para1);
value1[0]=0;strcpy(para1,"0");
break;
}
}
break;
}
extractTemp = paraExtract(extractEnd, buf, para3, value3);
extractEnd = extractTemp;
break;
} //end while
}//end if
/*下面这部分通过上面解析出来的信息,调用led驱动程序,控制led灯*/
int fd;
fd = open("/dev/leds", 0);//打开led设备
if (fd < 0) {
perror("open device /dev/leds");
exit(1);
}
if(value1[0]==0)
{
ioctl(fd, 0, 0);
}
else
{
ioctl(fd, 1, 0);
}
if(value2[0]==0)
{
ioctl(fd, 0, 1);
}
else
{
ioctl(fd, 1, 1);
}
if(value3[0]==0)
{
ioctl(fd, 0, 2);
}
else
{
ioctl(fd, 1, 2);
}
/*下面这部分发送HTTP相应到客户端*/
printf("Content-type: text/html\n\n");
printf("\n");
printf(" 简单LED测试(CGI实现)
\n");
printf("\n");
printf("LED设置已经提交\n");
printf("返回上一页\n");
//printf("led1=%s\n
",value1);
//printf("led2=%s\n
",value2);
//printf("led3=%s\n
",value3);
printf("\n");
printf("\n");
fflush(stdout);
return 0;
}
至此CGI程序已经编好了,然后对它进行交叉编译后,通过NFS下载到开发板的/root/web/cgi-bin目录下,下面就可以通过网页控制开发板的LED灯了。
效果如下:
点击提交按钮后,网页变为:
注意:如果网页提示出错,那需要修改CGI
程序的权限。
简单LED控制(JavaScript实现)
本实验用JavaScript技术实现了控制LED灯,注意这个实例和上个实例的区别,先看以下效果:
按下led1按钮,网页变为:
注意提交前后网页的变化。提交后网页还是以前的网页,这点和前面的实验不同,前面提交之后,显示的是服务器传过来的确认信息。
本手册提供了两种实现方法,可根据具体情况选取不同方法。
学习本实验之前,首先应该学习一下JavaScript语言。JavaScript是客户端的脚本语言,在Web编程中,它的主要应用是校验表单数据和创建动态的XHTML文档。
注意:做实验前应先修改网页代码中的IP地址为开发板的IP地址,黑色部分。
下面是控制LED灯的网页代码content-4.html:
LED测试
工作流程大概是:
⑴首先客户端浏览器检查是否有按钮按下,当有按钮按下时,触发事件onclick,调用相应的addPara()函数。
⑵在addPara()函数中调用addURLParamGet()函数,按照规则添加相应的参数到URL后面。
⑶接下来创建 XMLHttpRequest 对象,通过以下代码
xmlhttp.open("GET",url,false);
xmlhttp.send(null);
建立和服务器的连接,并把url传送到服务器端进行处理解析。
客户端浏览器
Web服务器
CGI程序
其它程序