第7章 嵌入式uClinux及其应用开发(2)

更新时间:2024-05-08 19:05:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第7章 嵌入式uClinux及其应用开发(2)

7.3.2 uClinux针对硬件的改动

目前,uClinux已被成功移植到S3C4510B及其他多款ARM芯片上,但由于嵌入式操作系统的运行是与嵌入式系统的硬件密切相关的,而硬件的设计则会因为使用场合的不同而千差万别,因此,在uClinux内核源代码中和硬件紧密相关的部分就应该针对特定的硬件作出适当的修改,由于uClinux内核源代码包含很大一部分的硬件驱动程序,不可能一一列举,在此,就基于S3C4510B的最小系统的设计与运行相关的部分作简单的介绍,希望对读者有所启发。

uClinux内核源代码中对S3C4510B片内特殊功能寄存器以及其他相关硬件信息的定义位于uClinux-Samsung\\Linux-2.4.x\\include\\asm-armnommu\\arch-samsung\\hardware.h文件中,其中有几个地方值得注意: /*

* define S3C4510b CPU master clock */

#define MHz

1000000

#define fMCLK_MHz (50 * MHz) #define fMCLK #define MCLK2

(fMCLK_MHz / MHz) (fMCLK_MHz / 2)

以上定义了系统工作的主时钟频率为50MHz,若用户系统的工作频率不同,应在此处修改,若串行口采用内部时钟信号用于波特率生成,该频率同时还与串行通信波特率有关。 /**********************************/ /* System Memory Control Register */ /**********************************/ #define DSR0 #define DSR1 #define DSR2 #define DSR3

(2<<0) (0<<2) (0<<4) (0<<6)

/* ROM Bank0 */

/* 0: Disable, 1: Byte, 2: Half-Word, 3: Word */

#define DSR4 #define DSR5 #define DSD0 #define DSD1 #define DSD2 #define DSD3 #define DSX0 #define DSX1 #define DSX2 #define DSX3

(0<<8) (0<<10)

(2<<12) /* RAM Bank0 */ (0<<14) (0<<16) (0<<18) (0<<20) (0<<22) (0<<24) (0<<26)

/* EXTIO0 */

#define rEXTDBWTH (DSR0|DSR1|DSR2|DSR3|DSR4|DSR5 | DSD0|DSD1|DSD2|DSD3 | DSX0|DSX1|DSX2|DSX3)

以上定义了系统存储器控制寄存器,按照以上定义,ROM/SRAM/FLASH Bank0定义为16位数据宽度(事实上,ROM/SRAM/FLASH Bank0的数据宽度由B0SIZE[1:0]的状态决定),而

ROM/SRAM/FLASH Bank1~ROM/SRAM/FLASH Bank5禁用;DRAM/SDRAM Bank0定义为16位数据宽度,DRAM/SDRAM Bank1~DRAM/SDRAM Bank3禁用;外部I/O组全部禁用;若用户系统的存储器系统配置不同,应在此处修改。

之后还做了其他一些改动,包括对ROM/SRAM/FLASH Bank0控制寄存器的设置,Flash容量的设置,DRAM/SDRAM Bank0控制寄存器的设置,SDRAM容量的设置等,这些设置均应该与用户系统对应。

7.3.3 编译uClinux内核

作为操作系统的核心,uClinux内核负责管理系统的进程、内存、设备驱动程序、文件系统和网络系统,决定着系统的各种性能。uClinux内核的源代码是完全公开的,任何人只要遵循GPL,就可以对内核加以修改并发布给他人使用,因此,在广大编程人员的支持下,uClinux的内核版本不断更新,新的内核修改了旧的内核的缺陷,并增加了许多新的特性,用户如果想在自己的系统中使用这些新的特性,或想根据自己的系统量身定制更高效、更稳定可靠的内核,就需要重新编译内核。一般说来,更新的内核版本会支持更多的硬件,具有更好的进程管理能力,运行速度会更快、更稳定,并且一般都会修复旧版本中已发现的缺陷等,因此,经常选择升级更新的系统内核是必要的。

uClinux内核采用模块化的组织结构,通过增减内核模块的方式来增减系统

的功能,因此,正确合理的设置内核的功能模块,从而只编译系统所需功能的代码,会对系统的运行进行如下几个方面的优化:

— 用户根据自身硬件系统的实际情况定制编译的内核因为具有

更少的代码,一般会获得更高的运行速度。 — 由于内核代码在系统运行时会常驻内存,因此,更短小的内核

会获得更多的用户内存空间。 — 减少内核中不必要的功能模块,可以减少系统的漏洞,从而增

加系统的稳定性和安全性。 uClinux的内核源代码可以从许多网站上免费下载,内核的发布一般有两种形式,一种是完整的内核版本,完整的内核版本一般是.tar.gz文件,使用时需要解压。另一种是通过对旧的版本发布补丁(patch),达到升级的效果。

本例所采用的在Linux下使用的交叉编译器和uClinux-Samsung-20020318.tar.gz源码均来自网站http://mac.os.nctu.edu.tw/。

在准备好uClinux的内核源代码后,利用交叉编译器就可以编译生成运行在硬件目标板上的uClinux内核。

从http://mac.os.nctu.edu.tw/上下载uClinux内核源代码

uClinux-Samsung-20020318.tar.gz,保存到宿主机的用户目录。运行解压命令:

tar xzvf uClinux-Samsung-250020318.tar.gz

解压完毕后,就会在用户目录下生成uClinux-Samsung目录,以下命令进入到该目录中:

$ cd uClinux-Samsung 1. 键入命令: make menuconfig

内核配置。该命令执行完毕后生成文件.config,它保存这个配置信息。下一次再做make menuconfig的时候将产生新的.config文件,原来的.config被改名为.config.old。

此时会出现菜单配置对话框,要求进行目标平台的选择,如图7.6所示,输入回车后,出现供选择的具体的供应商和产品列表,在这里我们选择:Samsung/4510B,如图7.7所示,在库的选择上,我们选择uC-libc,其他

选项暂时不用修改,保存好设置后,存盘退出。

图7.6 目标平台配置

图7.7 选择合适的产品类型

2.键入命令:make dep

该命令用于寻找依存关系。 3. 键入命令:make clean

该命令清除以前构造内核时生成的所有目标文件,模块文件和一些临时文件。

1. 键入命令:make lib_only 该命令编译库文件。

2. 键入命令:make user_only 该命令编译用户应用程序文件。 3. 键入命令:make romfs 该命令生成romfs文件系统。 4. 键入命令:make image

注意做到这一步的时候可能会出现错误的信息提示,类似于: arm-elf-objcopy: /home/nie/uClinux-Samsung/linux-2.4.x/linux: No such file or directory make[1]: *** [image] Error 1

make[1]: Leaving directory

`/home/nie/uClinux-Samsung/vendors/Samsung/4510B' make: *** [image] Error 2

这是因为第一次编译时还没有romfs.o,所以出错,等romfs.o编译好了以后,如果再进行内核的编译,就不会出现这个错误信息了。它完全不影响内核的编译,可以完全不必理会这个错误信息。继续进行编译工作。

5. 键入命令:make

通过各个目录的Makefile文件进行,会在各目录下生成一大堆目标文件。

上述步骤完成后,就完成了对uClinux源码的编译工作。整个编译过程视计算机运行速度而定,大约需要十几分钟左右。

在编译内核的时,建议在Linux平台下进行。 7.3.4 内核的加载运行

当内核的编译工作完成之后,会在/ uClinux-Samsung/images目录下看到两个内核文件:image.ram和image.rom,其中,可将image.rom烧写入ROM/SRAM/FLASH Bank0对应的Flash存储器中,当系统复位或上电时,内核自解压到SDRAM,并开始运行。

image.ram可直接在系统的SDRAM中运行,使用ADS(或SDT)集成开发环境将系统的SDRAM映射到起始地址为0x0处,并将image.ram载入从0x8000开始的SDRAM中,加载完毕后,修改PC指针寄存器的值为0x8000并执行。 注意该内核默认串行口COM1为输入输出控制台,波特率为19200,8个数据位,1个停止位,无校验。

7.4 在uClinux下开发应用程序

当完成了上述所有工作后,一个嵌入式应用开发平台就已经搭建好了,在这个平台之上,就可以根据不同需要开发嵌入式应用了。图7.8所示为一个基于uClinux 的嵌入式系统典型框架结构,下面将向读者介绍如何将自己开发的应用程序添加到目标板上运行。

图7.8 基于uClinux嵌入式系统框图

基于uClinux系统的应用程序的开发通常是在标准Linux平台上(本书已经介绍了适用于Windows环境的交叉编译器,所以也可以在Windows平台)用交叉编译工具来完成。由于uClinux是为没有内存管理单元(MMU)的处理器和控制器而设计的,并做了较大幅度的精简,所以可能出现这样的情况:在标准Linux下可以使用的某些函数在uClinux下却用不了,这个时候,就需要用户编写相应的库函数了。当然绝大多数的函数它们都还是通用的。除

此以外,在x86版本的gcc编译器下编译通过的软件,通常不需要做太大的改动就可以用刚才我们建立的交叉编译工具编译成可以在uClinux上运行的文件格式。因此开发在uClinux 下运行的程序,基本上就和开发在Linux下运行的程序是一样的,关于Linux下的编程,读者可以参考其他更详细的资料,以下就一个简单的例子,描述其基本开发过程。

考虑一个定时中断的例子,文件名为lednxy.c,其源代码如下:

/******************************************************* * Institute of Automation, Chinese Academy of Sciences * File Name: lednxy.c

* Description: timing interrupt * Author: * Date:

Xueyuan Nie

*******************************************************/ #include #include

#define IOPMOD (*(volatile unsigned *)0x3ff5000) #define IOPDATA (*(volatile unsigned *)0x3ff5008) int i=0;

static void sig_alarm(int signumber) {

if(i==3) i=0; IOPDATA=i++; alarm(2); }

int main(void) {

IOPMOD=0xff;

if(signal(SIGALRM,sig_alarm)==SIG_ERR) {

printf(“some error occurs\\n”);

return 1; }

} alarm(2); while(1); return 0;

在代码中,SIGALRM为系统定义的信号的名字,在头文件里被定义为一个正整数,用户自定义函数sig_alarm()为信号处理函数, 系统函数

alarm()用来设定一个2秒的定时器,当定时器时间片终止的时候,进程将会产生SIGALRM信号,在程序中用函数signal()实现了信号SIGALRM和信号处理函数sig_alarm()的连接,这样,当用alarm()函数设置时钟的时间段终止时,就会有SIGALRM信号产生,程序就会转而执行函数sig_alarm(),从而实现每隔2秒钟,I/O口数据寄存器的值发生一次变化,达到控制LED等的目的。有关signal()函数和alarm()函数的使用,读者可以查阅有关在Linux上的C编程方面的内容,本书在此不作详述。

该程序达到的效果就是,让目标硬件上的P0和P1口的两个LED显示器按照P0亮,P1亮,P0、P1全亮的顺序,每隔2秒实现其中的一种状态。 在装有标准Linux的宿主机(或装有Cygwin的windows的PC机)上,用前面已经建立好的交叉编译工具编译源文件,在该程序所在的目录下键入如下命令:

arm-elf-gcc –Wall –O2 –Wl,-elf2flt –o lednxy lednxy.c 仍然在该目录下,键入命令:

ls

可以查看到在该目录下生成了文件名为lednxy的文件。

在键入的编译命令中,选项 : -Wall 指定产生全部的警告信息;

-O2 是一个二级优化选项,它表示告诉编译器产生尽可能小和尽可能快的代码;

-Wl 的一般用法是’’-Wl,option’’ 就是把它后面的选项传递给链接器,在本命令中就是把’’- elf2flt ‘’传给链接器;

-elf2flt 指定自动调用elf转换flat格式的工具;之所以要使用该选项是因为,由于GNU工具本身并不支持flat格式的二进制文件,然而,uClinux目前只支持flat格式的可执行文件,因此必须使用相应的二进制工具进行格式转换。flat格式是对elf格式的很大的文件头和一些段信息做了简化的文件格式。

编译成功后得到的lednxy就可以在uClinux环境上运行了。关于如何将生成的可执行代码加入到uClinux,将在后面的章节讲述。 除了以命令行的形式进行代码编译外,我们还可以利用前面提到的makefile的知识,用makefile文件实现代码编译的功能。

下面给出本例相应的makefile文件(该文件名为makefile)。

CFLAGS = -Wall –Os –Dlinux –D__linux__ -Dunix –D__uClinux__ -DEMBED LDFLAGS = -Wl,-elf2flt CC = arm-elf-gcc LD = arm-elf-gcc TARGT = lednxy OBJ = $(TARGT).o SRC = $(TARGT).c all: $(TARGT) %.o : %.c

$(CC) $(CFLAGS) –c $< -o $@ $(TARGT): $(OBJ)

$(CC) $(CFLAGS) $(LDFLAGS) –o $@ $(OBJ)

整个编译过程如下:

[nie@uClinux usr]$ make

arm-elf-gcc –Wall –Os –Dlinux –D__linux__ -Dunix –D__uClinux__ -DEMBED -c lednxy.c –o lednxy.o arm-elf-gcc –Wall –Os –Dlinux –D__linux__ -Dunix –D__uClinux__ -DEMBED -Wl,-elf2flt –o lednxy lednxy.o

可以用工具arm-elf-flthdr查看生成的lednxy的格式,它是一个能够操作和显示flat格式文件的头信息的可执行程序。在生成lednxy的当前路径下键入命令:

arm-elf-flthdr lednxy

后,可以看到以下对该文件头描述的信息,

lednxy

Magic: bFLT Rev: 4

Build Date: Thu Jun 19 10:31:14 2003 Entry: 0x50 Data Start: 0x1c80 Data End: 0x2010 BSS End: 0x22a0 Stack Size: 0x1000 Reloc Start: 0x2010 Reloc Count: 0x4f

Flags: 0x1 ( Load-to-Ram )

从显示的信息,可以看出文件lednxy的确是一个flat格式的文件,是可以在uClinux环境下运行的。 7.4.1 串行通信

所谓串行通信就是在传输数据的时候每次只传输一位,其传输的速率通常用“位/秒”来表示,即通常所说的“波特率”。

Linux对所有各类设备文件的输入输出操作,看上去就像对普通文件的输入输出一样,所以Linux对串口的操作,也是通过设备文件访问的。为了访问串口,只需要打开相应的设备文件即可。设备文件/dev/ttyS*是用于挂起Linux终端的文件。默认地,在Linux下,串行口COM1和COM2对应的设备分别为/dev/ttyS0和/dev/ttyS1。

在程序中,很容易配置串口的属性,这些属性定义在结构体struct termios中。为在程序中使用该结构体,需要包含文件,该头文件定义了结构体struct termios。

#define NCCS 19 struct termios {

tcflag_t c_iflag; /* input mode flags */ tcflag_t c_oflag; /* output mode flags */ tcflag_t c_cflag; /* control mode flags */ tcflag_t c_lflag; /* local mode flags */ cc_t c_line; /* line discipline */ cc_t c_cc[NCCS]; /* control characters */ };

下面对结构体中的各个成员做一个简单介绍。

在c_iflag中的输入模式标志符控制所有的输入处理过程,就是说,从设备发送的字符在被read函数读取之前要经过处理。类似的,成员c_oflag控制输出处理过程,c_cflag包含对端口的设置,如,波特率,字符位数,停止位等。存储在成员c_lflag的本地模式标志符决定是否显示字符,是否发送信号到应用程序等。数组c_cc包含了控制字符的定义和超时参数。成员c_line在POSIX(Portable Operating System Interface for UNIX)系

统中不使用。

下面结合一个简单的实例,说明如何对串口进行读写操作。

/******************************************************* * Institute of Automation, Chinese Academy of Sciences * File Name: serialcomm.c

* Description:communication with serial * Author: * Date:

Xueyuan Nie

*******************************************************/ #include #include #include #include #include

#define BAUDRATE B19200 #define SERIALDEVICE \int main() {

int fd,ncount;

struct termios oldtio,newtio;

char buf[]=\

fd = open(SERIALDEVICE, O_RDWR | O_NOCTTY );

if (fd <0)

{

perror(SERIALDEVICE); exit(-1);

}

tcgetattr(fd,&oldtio);

bzero(&newtio, sizeof(newtio));

newtio.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD; newtio.c_iflag = IGNPAR | ICRNL; newtio.c_oflag = 0; newtio.c_lflag = ICANON; tcflush(fd, TCIFLUSH);

fcntl(fd,F_SETFL,0);

tcsetattr(fd,TCSANOW,&newtio); ncount=write(fd,buf,sizeof(buf));

printf(\

printf(\

perror(\

tcsetattr(fd,TCSANOW,&oldtio);

close (fd); }

return 0;

程序首先为波特率常数定义了宏值,为设备文件定义了设备名常数。有关波特率常数的定义可参见(该头文件包含在termios.h中)。 对于普通用户而言,是不允许访问设备文件的,如果要访问,要么以root

账号登录,要么需要改变文件的访问属性。假定设备文件是可以访问的,用open函数打开设备文件,返回一个文件描述符(file descriptors,fd),通过文件描述符来访问文件。O_RDWR标志表示对该文件可读可写,O_NOCTTY表示该程序不会成为控制终端,这样就避免了当在键盘输入类似ctrl+c的命令后,终止程序的运行。

然后用tcgetattr保存串口的当前设置,给端口设置新的属性,通过对c_cflag的赋值,设置波特率,字符大小(CS8表示8位数据位,1位停止位,没有奇偶校验位),使能本地连接,使能串行口驱动读取输入数据。 通过设置c_iflag ,控制端口对字符的输入处理过程,IGNPAR符号常量表示忽略奇偶性错误的字节,并不对输入数据进行任何校验,ICRNL 将回车符映射为换行符。

设置原始数据输出,使能规范输入。

在对struct termios结构体的各个成员赋值完毕后,调用tcsetattr函数选择新的设置,常数TCSANOW表示新设置立即生效。

调用write函数往串口发送数据,此时如果打开超级终端应该可以看到写入的字符串。对串口操作结束后,恢复原有的端口设置,关闭打开的设备文件。 以上是一个简单的对串口进行写操作的程序,因为通过超级终端来显示,所以没有调用read函数,如果接收数据的一端是其他设备的话,有可能需要读者再编写一个接收数据的程序,运行发送和接收程序的两台设备通过串行口进行连接。也可以将接收和发送的程序在同一台设备上运行,通过一根交叉线(就是将 TXD-数据传输信号和另一个端口的 RXD -接收数据信号相连起来)将两个串口接在一起。

下面就针对上述提到的情况,再举一个有关接收和发送数据的程序,通过串口交叉线的连接运行在不同设备(也可以是同一台设备)上的例子。 假设接收程序readtest.c运行在装有标准Linux的PC机上,发送程序writetest.c运行在目标板S3C4510B上,两台设备的串口通过交叉线连接在一起。

接收程序readtest.c的源码如下:

/******************************************************* * Institute of Automation, Chinese Academy of Sciences * File Name: readtest.c

* Description: receive data from the serial

* Author: * Date:

Xueyuan Nie

*******************************************************/ #include #include #include #include #include #include #include #include #include \ int spfd; int main() {

char fname[16],hd[16],*rbuf; int retv,i,ncount=0; struct termios oldtio; int realdata=0;

spfd=open(\ perror(\

if(spfd<0) return -1;

tcgetattr(spfd,&oldtio); cfmakeraw(&oldtio); cfsetispeed(&oldtio,B19200); cfsetospeed(&oldtio,B19200); tcsetattr(spfd,TCSANOW,&oldtio); rbuf=hd;

printf(\

retv=read(spfd,rbuf,1); if(retv==-1) perror(\ while(*rbuf!='\\0') { }

for(i=0;i

ncount+=1; rbuf++;

retv=read(spfd,rbuf,1);

printf(\ if(retv==-1) perror(\

}

realdata+=(hd[i]-48)*pow(10,ncount-i-1);

printf(\

close(spfd); return 0; }

发送程序writetest.c的源码如下:

/******************************************************* * Institute of Automation, Chinese Academy of Sciences * File Name: writetest.c

* Description: send data to serial * Author: * Date:

Xueyuan Nie

*******************************************************/ #include #include #include #include #include #include #include #include

int spfd;

int main(int argc, char *argv[]) {

char fname[16],*sbuf; int sfd,retv,i; struct termios oldtio;

spfd=open(\if(spfd<0) {

perror(\return -1;

}

printf(\ tcgetattr(spfd,&oldtio); cfmakeraw(&oldtio); cfsetispeed(&oldtio,B19200); cfsetospeed(&oldtio,B19200); tcsetattr(spfd,TCSANOW,&oldtio);

fname[0]='1'; fname[1]='2';

fname[2]='3'; fname[3]='\\0';

sbuf=(char *)malloc(4);

strncpy(sbuf,fname,4); retv=write(spfd,sbuf,4); if(retv==-1) perror(\

printf(\

close(spfd);

return 0; }

本例程实现:在发送端发送数字123,在接收端接收并显示接收到的数据。 这里请读者注意的是,发送方按字符发送数据,接收方将接收的字符相应的ascii值与字符0所对应的ascii值相减,最终得到实际的十进制数值。 按照前面介绍的方法编译程序,有关如何将可执行文件添加到目标板的方法将在下一小节介绍。

开始运行程序。先在装有Linux的 PC上运行接收程序,然后在S3C4510B上运行发送程序,整个运行的过程如下所示: 在Linux的PC上:

root@uClinux nie]# ./recvtest & [1] 2171

[root@uClinux nie]# open /dev/ttyS1: Success

ready for receiving data...

[root@uClinux nie]# the number received is 1 the number received is 1 the number received is 1 complete receiving the data 123

[1]+ Done ./recvtest

在目标板上:

/var/tmp> ./writetest ready for sending data... the number of char sent is 4

这里所举的例子比较简单,旨在为读者介绍最基本的串行通信的步骤,读者可以此为基础,开发出满足自己需求的应用程序来。

7.4.2 socket编程

uClinux本身就是一个网络的产物,它可以从网上供人们自由免费的下载,正是通过很多爱好者利用网络修改,改善Linux,才得到我们现在的uClinux,所以没有网络可以说就看不到今天的uClinux。因此,在学习uClinux的时候,就不能不涉及到网络,而要掌握在uClinux下设计用户应用程序,就必须要学习有关uClinux下的网络编程。本节主要讲述当前在网络编程中被广泛使用的socket。

socket一般被翻译为“套接字”,简而言之就是网络进程中的ID。 其实网络通信,本质就是进程间的通信,在网络中,每个节点都有唯一一个网络地址,即通常说的IP地址,两个进程在通信的时候,必须首先要确定通信双方的网络地址。但是网络地址只能确定进程所在的PC机,然而同一台PC可能有好几个网络进程,只有网络地址是不能够确定到底是哪个进程,所以套接字还需要提供其他信息,那就是端口号,同一台PC机,一个端口号只能分配给一个进程。所以,网络地址和端口号结合在一起,才可以共同确定整个Internet中的一个网络进程。

套接字最常用的有两种:流式套接字(Stream Socket)和数据报套接字(Datagram Socket)。在Linux中,分别称为”SOCK_STREAM”

和”SOCK_DGRAM”。

这两种套接字的区别在于它们使用不同的协议。流式套接字使用TCP协议,数据报套接字使用的是UDP协议。

TCP(Transmission Control Protocol)传输控制协议,是TCP/IP体系中的运输层协议,是面向连接的,因而可提供可靠的,按序传送数据流,它的可靠是因为它使用三段握手协议来传输数据,并且采用“重发机制”确保数据的正确发送,接收端收到数据后要发出一个肯定确认,而发送端必须接收到接收端的确认信息后,否则发送端会重发数据。同时TCP是无错误传递的,有自己的检错和纠错机制,使用TCP协议的套接字是属于流式套接字。大家熟知的telnet就是使用的流式套接字。

UDP(User Datagram Protocol)用户数据报协议提供无连接的不可靠的服务,在传送数据之前不需要建立连接。远地主机在接收接收到UDP数据报后,不需要给出任何应答,这样的话,如果发送一个数据报,可能到达也可能丢失。如果发送多个包,到达接收端的次序可能是颠倒的。数据报套接字有时候也称为“无连接套接字”,大家熟悉的TFTP和NFS使用的就是该协议。 大多数情况下,如果只是将数据包发送给给定地址的机器,是不能够确定到底把数据包发送给机器哪一个进程的,端口号的指定才能够更明确的指明。适用于通信的用户应用程序可以使用从1到65535的任何一个端口号,并将它分配给端口。这些号通常分成以下几个范围段:

端口0,不使用。如果传递的端口号是0,就会为进程分配一个1024到5000之间的一个没有使用的端口。

端口1~255,保留给特定的服务,如FTP,远程网,FINGER等。 端口256~1023,保留给别的一般服务如Routing function(路由函数)。 端口1024~4999,可以被任意的客户机端口所使用,客户机套接字通常会使用这个范围段的端口。

端口5000~65535,为用户定义的服务器端口所使用。如果一个客户机需要事先知道服务器的端口,那么服务器套接字就应该使用这个范围的端口值。 下面结合一个具体的服务器端的例子,使读者熟悉socket编程的方法。

/******************************************************* * Institute of Automation, Chinese Academy of Sciences * File Name: comsamp.c

* Description:communication with socket * Author: * Date:

Xueyuan Nie

*******************************************************/

#include #include #include #include #include #include #include #include #include #include #include #include

/*=========* * Defines * *=========*/ #ifndef TRUE #define FALSE 0

#define TRUE 1 #endif

#ifndef EXIT_FAILURE #define EXIT_FAILURE 1 #endif

#ifndef EXIT_SUCCESS #define EXIT_SUCCESS 0 #endif

#ifndef EXT_NO_ERROR #define EXT_NO_ERROR 0 #endif

#ifndef EXT_ERROR #define EXT_ERROR 1 #endif

#ifndef INVALID_SOCKET #define INVALID_SOCKET -1 #endif

#ifndef SOCK_ERR #define SOCK_ERR -1

#endif

/*==================================* * Global data local to this module *

*==================================*/ typedef int SOCKET;

typedef struct ConnectData_tag { int port; int waitForStart;

SOCKET sFd; /* socket to listen/accept on SOCKET msgFd; /* socket to send/receive messages } ConnectData;

ConnectData *CD; int i=0;

int connectionMade = 0;

/*=================* * Local functions * *=================*/ void prompt_info(int signumber) {

char src[]=\ int nBytesToSet=strlen(src);

*/ */ send(CD->msgFd, src, nBytesToSet, 0); }

void init_sigaction(void) {

struct sigaction act; act.sa_handler=prompt_info; act.sa_flags=0;

sigemptyset(&act.sa_mask); sigaction(SIGPROF,&act,NULL); }

void init_time(double t_usec) {

struct itimerval value; int int_usec;

int_usec=(int)(t_usec*1000000); value.it_value.tv_sec=0;

value.it_value.tv_usec=int_usec; value.it_interval=value.it_value; setitimer(ITIMER_PROF,&value,NULL); }

int ModeInit(void)

{

int error = EXT_NO_ERROR;

error = ExtInit(CD);

if (error != EXT_NO_ERROR) goto EXIT_POINT; printf(\EXIT_POINT: return(error); } /* end ModeInit */

/* Function: ExtInit * Abstract:

* Called once at program startup to do any initialization. * A socket is created to listen for

* connection requests from the client. EXT_NO_ERROR is returned * on success, EXT_ERROR on failure. * NOTES:

* This function should not block. */

int ExtInit(ConnectData *UD) {

int sockStatus; struct sockaddr_in serverAddr;

int sFdAddSize = sizeof(struct sockaddr_in); int option = 1; int port = 17725;

int error = EXT_NO_ERROR; SOCKET sFd = INVALID_SOCKET; #ifdef WIN32 WSADATA data;

if (WSAStartup((MAKEWORD(1,1)),&data)) { fprintf(stderr,\ error = EXT_ERROR; goto EXIT_POINT; } #endif /*

* Create a TCP-based socket. */

memset((char *) &serverAddr,0,sFdAddSize); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(port);

serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

sFd = socket(AF_INET, SOCK_STREAM, 0); if (sFd == INVALID_SOCKET) { fprintf(stderr,\ error = EXT_ERROR; goto EXIT_POINT; } /*

* Listening socket should always use the SO_REUSEADDR option * (\ * Volume 1, 2nd edition, by W. Richard Stevens). */

sockStatus =

setsockopt(sFd,SOL_SOCKET,SO_REUSEADDR,(char*)&option,sizeof(option));

if (sockStatus == SOCK_ERR) {

fprintf(stderr,\ error = EXT_ERROR; goto EXIT_POINT; }

sockStatus =

bind(sFd, (struct sockaddr *) &serverAddr, sFdAddSize); if (sockStatus == SOCK_ERR) {

fprintf(stderr,\ error = EXT_ERROR; goto EXIT_POINT; }

sockStatus = listen(sFd, 1); if (sockStatus == SOCK_ERR) { fprintf(stderr,\ error = EXT_ERROR; goto EXIT_POINT; }

EXIT_POINT:

UD->msgFd = INVALID_SOCKET; UD->port=17725;

if (error == EXT_ERROR) {

if (sFd != INVALID_SOCKET) { close(sFd); }

UD->sFd = INVALID_SOCKET; } else {

UD->sFd = sFd; }

return(error); } /* end ExtInit */

int OpenConnection(ConnectData *UD) {

struct sockaddr_in clientAddr;

int sFdAddSize = sizeof(struct sockaddr_in);

int error = EXT_NO_ERROR; SOCKET msgFd = INVALID_SOCKET; const SOCKET sFd = UD->sFd; /*

* Wait to accept a connection on the message socket. */

msgFd = accept(sFd, (struct sockaddr *)&clientAddr, &sFdAddSize); if (msgFd == INVALID_SOCKET) {

fprintf(stderr,\ error = EXT_ERROR; goto EXIT_POINT; }

connectionMade = 1;

EXIT_POINT:

if (error != EXT_NO_ERROR) { if (msgFd != INVALID_SOCKET) { close(msgFd); }

UD->msgFd = INVALID_SOCKET; } else {

UD->msgFd = msgFd;

if(msgFd !=INVALID_SOCKET)

printf(\ socket!\\n\

}

return(error); }

/* Function: main * * Abstract: * */

int main(int argc, const char *argv[]) {

int error;

const char *option=argv[1];

CD=(ConnectData *)malloc(sizeof(ConnectData)); memset(CD,0,sizeof(ConnectData)); if (strcmp(option, \ else

CD->waitForStart=0; ModeInit();

while((CD->waitForStart)&&(connectionMade==0)) {

error=OpenConnection(CD); }

init_sigaction(); init_time(2.0); while (1);

if(error) exit(EXIT_FAILURE); CD->waitForStart=1;

return(EXIT_SUCCESS);

} /* end main */

下面就结合本例,介绍如何在linux(uClinux)下建立通信双方中服务器端的程序。

本例是一个服务器程序,采用流式套接字,因为流式套接字提供了一种可靠的面向连接的数据传输方法。正如它的名字所指的那样,不管是对单个的数据报,还是对于数据包,流式套接字都提供一种流式数据传输。流式套接字由socket()函数调用来创建,而且调用时必须用bind()函数为它分配一个地址。

在创建好一个套接字,并赋给它一个地址之后,需要用一种方法来建立和客户机的连接,为了做到这一点,要使用listen()函数。该函数告诉套接字开始侦听客户机的连接请求。一旦将套接字设置成侦听连接后,实际的连接就可以由accept()函数来完成。如果连接成功的接受,accept()函数将返回一个新套接字的描述符,正是由accept()函数所创建的这个新套接字会被用作以后处理新的连接。在该例程中,ConnectData结构体中的msgFd套接字就是用来真正和客户端进行通信的socket。

原来的侦听套接字将会继续侦听新的连接请求,而新的请求可能会通过accept()函数的再一次调用而获得接受。

到目前为止,读者已经看到有两类套接字了,一个是由socket()函数创建的,称之为“侦听套接字”(listening socket),另一类是由accept()函数创建的,称之为“连接套接字”(connected socket),它们的区别如表7.1所示。

表7.1 两种套接字比较

创建 应用 侦听套接字 socket() bind(),listen(),accept() 连接套接字 accept() 文件读写调用read(),write() 网络文件专用函数send(),recv() 作用 监听来自客户端的连接请求,并建立连接 生存周期 一个服务器进程与一个监听套接字相对应,与服务器进程同时存在或消灭 与某一个客户进程连接,完成具体的数据传输工作 一次连接对应一个连接套接字,建立连接时创建套接字,连接结束时关闭

在主程序中,利用信号处理函数,进行每隔2秒定时的往客户端发送字符串(有关信号处理函数的知识,在本节开始已有介绍)。

网络应用程序包括两个部分:一部分是服务器端的应用程序,主要是用于接受客户端的连接请求,接收客户端的信息,处理客户端的计算请求,向客户端发送计算结果和应答信息等。另一部分就是客户端应用程序,主要用于申请连接到服务器,向服务器发送计算请求,处理服务器发回的计算结果和其他信息。

本书所给的例子只是服务器端的应用程序,对于客户端程序,在此只为读者做一个简单的介绍。

在客户端的应用程序为了让服务器接收一个连接请求,必须首先也要建立一个socket,一般也是使用流式套接字,接着发起一个请求,通过调用函数connect()来实现。

一旦客户机套接字和服务器套接字建立了连接,双方就可以通过send()和recv()函数的调用来发送和接收数据了。 流式套接字基本使用方法如图7.9所示。

图7.9 流式套接字用法

如果想断开连接,调用函数close()真正释放和套接字相关的系统资源。 7.4 .3 添加用户应用程序到uClinux

以下通过一个具体实例向读者介绍将程序添加到uClinux的标准方法。 例如要把前面提到的源程序lednxy.c添加到运行于目标板上的uClinux操作系统中,则该文件应在目录/home/nie/uClinux-Samsung/user下,进入uClinux-Samsung/user目录并建立一个自己的子目录,比如键入: mkdir myapp,

这样在user目录下就建立了一个新的子目录myapp,把lednxy.c拷贝到myapp目录下,并将该源文件相应的makefile文件也拷贝到该目录下。注意,为了使用标准方法,我们应该修改一下刚才的makefile文件,这个文件名应为Makefile,写成这样的形式:

EXEC = lednxy OBJS = lednxy.o all: $(EXEC) $(EXEC): $(OBJS)

$(CC) $(LDFLAGS) –o $@ $(OBJS) $(LDLIBS) romfs:

$(ROMFSINST) /bin/$(EXEC) clean:

rm –f $(EXEC) *.elf *.gdb *.o

进入user目录,增加一行语句到该目录下的Makefile文件中,

dir_$(CONFIG_USER_MYAPP_LEDNXY) += myapp

该语句的作用是让编译器可以访问到我们所创建的myapp目录下的makefile文件,保存后退出。

切换到目录/home/nie/uClinux-Samsung/config下,编辑Configure.help文件,即输入一下命令

cd ../config vi Configure.help

这是一个包含了在配置的时候出现的所有文本信息的文件。在这个文件

中加入类似下面的语句块:

CONFIG_USER_ MYAPP_LEDNXY This program is an example.

注意第二行文本信息必须要空两格开始。每行的字符要小于70个。添加完毕后,保存退出。

不过,用户也可不必修改该文件,因为它仅仅是提供一个在线文本信息显示的功能,对于添加用户程序到uCllinux影响不大。 接下来需要修改uClinux系统中对编译器来讲比较重要的一个文件config.in。

仍然是在config目录下,打开该文件,在最后增加类似下面的语句:

#################################################################

mainmenu_option next_comment comment ? My Application ?

bool ?lednxy? CONFIG_USER_MYAPP_LEDNXY

comment ? My Application? endmenu

#################################################################

现在我们已经把要做的修改的相关工作完成了,接下来需要进行内核的编译工作,按照在7.3.3 中谈到的编译uClinux内核的步骤进行就可以了。 值得注意的一点是在第一步make menuconfig 进行内核配置的时候,在Target Platform Selection ,要选中Customize Vendor/User Settings (NEW) 如图7.10所示,选中了该选项后,与最初我们配置内核过程不同的是,它还会在 make menuconfig的最后出现如图7.11所示对话框,让你进

行用户应用程序的配置,在对话框里出现的文字是在config.in文件中添加的文字,选中要编译的应用程序所在路径,就会出现如图7.12所示的对话框,显示所选中目录下的,在config.in中所设定的应用程序文件名,选中要编译的文件名,保存好内核配置后退出。用这种方法生成的可执行文件在romfs/bin下。

图7.10 添加用户应用程序配置

图7.11 选择要配置的用户应用程序

图7.12 选择要编译的源文件

当用户应用程序做了修改后,需要重新编译内核,但是此时只要进行内核编译的后四步,即从make user_only开始即可,不必再从内核配置开始了。

以上介绍的是一种基本的添加用户应用程序的方法,如果读者觉得比较麻烦,还可以使用下面一种较为简单的方法,这种方式是将用户的应用程序作为uClinux自身的应用程序对待,在内核编译时一起完成。

在uClinux-Samsung/romfs/usr下面编写用户应用程序源代码以及它对应的makefile文件,就在该目录下编译这个makefile文件,将生成的可执行文件拷贝到uClinux-Samsung/romfs/bin下。

进行内核的部分编译工作,用这种方法只需要做编译内核的最后三步工作,即:

make romfs make iamge make

最后都把在uClinux-Samsung/images下生成的image.rom文件烧写到系统的FLASH存储器中,uClinux启动后,用户的应用程序在/bin目录下,此时可运行用户程序。

在Windows环境中,可以使用超级终端建立串口与目标硬件连接。超级终端的一些端口属性需要设置,该内核默认的端口设置为:COM1,波特率为19200,数据位为8,无校验,停止位为1,无流控。通过超级终端可以看到整个uClinux的启动过程。

对于本例,在uClinux启动后,从超级终端中键入cd bin,进入到bin目录下,运行lednxy程序,可以看到该程序对两个LED显示器的控制效果。 上面介绍的方法中,在将用户应用程序添加到uClinux内核运行时,都需要对内核进行部分或全部的编译,每次对内核编译完成后,都要先将FLASH存储器中的内容擦除,然后重新烧写新编译好的内核到FLASH存储器中去,这对于程序开发来说,是非常不方便的。下面介绍一种通过网络来传输可执行文件,避免每次测试程序运行效果时都要编译一次内核。 7.4.4 通过网络添加应用程序到目标系统

作为一款优秀的网路控制器,基于S3C4510B的系统一般都提供以太网接口,通过以太网接口从网络添加用户程序到目标系统运行,显然比前面所介绍的方法方便得多,特别是在用户应用程序的调试过程中,若每做一点修改都要求重新编译内核并烧写入FLASH存储器运行,其工作量是可想而知的。

事实上,鉴于uClinux操作系统本身强大的网络功能,同时基于

S3C4510B的系统提供以太网接口,通过局域网可方便的在运行uClinux目标系统和运行Linux宿主机上进行文件传输。运行目标系统的uClinux内核在编译的过程中,已默认选择了FTP和其他一些网络服务,同时,宿主机上的Linux在默认时,也会安装运行FTP 服务,因此,当目标系统的uClinux启动运行以后,可将目标系统作为FTP客户端,而运行Linux宿主机作为FTP服务器,进行双向的文件传输。

但由于目前所使用的uClinux操作系统内核采用ROMFS作为其根文件系统,当目标系统的uClinux启动运行以后,其目录大多数是建在FLASH存储器中,因而是不可写的,只有/var、/tmp等少数几个目录是建立在SDRAM,是可读写的,但若目标系统掉电,内容就丢失了,因此只能作为应用程序调试之用,当应用程序调试完成后,还应将其写入FLASH存储器。当然,若能在目标系统中使用JFFS/JFFS2,用以代替ROMFS作为其根文件系统,则整个目标系统就像有磁盘一样方便,用户应用程序的加载再也不用像前面介绍的方式进行了。关于JFFS/JFFS2文件系统的建立,请读者参考相关技术资料,在此仅描述如何将用户程序通过局域网,从FTP服务器(运行Linux宿主机)上,传输到运行uClinux的目标系统(FTP客户机)并执行的过程: 将目标系统与Linux宿主机连接在同一网段中,在宿主机的任意目录下编写应用程序,并用交叉编译工具生成flat格式的文件。

启动目标系统的uClinux,通过超级终端,输入下面的命令: ifconfig eth0 192.168.100.50

ifconfig命令用于显示及设置目标系统的网卡配置,例如,IP地址,子网掩码,IRQ及IO Port等。在上述命令中,参数eth0代表目标系统的网络设备,IP地址192.168.100.50为目标系统的IP地址,注意应与宿主机在同一网段内(此时宿主机的IP地址为:192.1681.100.21)。

执行命令: ifconfig –all

可以看到目标系统的IP地址已被正确配置,显示信息如下:

/var/tmp> ifconfig -all

eth0 Link encap:Ethernet HWaddr 00:40:95:36:35:34

inet addr:192.168.100.52 Bcast:192.168.100.255 Mask:255.255.255.0 UP BROADCAST RUNNING MTU:1500 Metric:1 RX packets:30533 errors:10 dropped:0 overruns:0 frame:0 TX packets:21090 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:100 Interrupt:17

lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0

UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:19 errors:0 dropped:0 overruns:0 frame:0 TX packets:19 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0

本文来源:https://www.bwwdw.com/article/x0bg.html

Top