IPv6的嵌入式设计与实现
时间:03-31
来源:互联网
点击:
引言
随着嵌入式系统应用的快速发展,为实现对其他设备的控制、监视或管理等功能,需要将嵌入式设备连入互联网,以便于远程控制和监测。为将嵌入式设备连入互联网,需要有通信模块实现通信协议。目前嵌入式设备能够通过主流的TCP/IP 协议可直接与外网连接。然而随着网络规模的日益扩大、上网设备和人数的激增,目前采用的IPv4逐渐接近其自身发展的极限,其中比较显著的问题是IP 地址资源的不足。IPv6[1] 采用长度为128 位的地址,地址空间几乎可以视为无限,IPv6不仅完全地解决了地址短缺问题,还对在IPv4 中解决不好的其他问题(如,端到端IP连接、服务质量、安全性、移动性等方面)进行了改进。IPv6成为嵌入式设备进行网络互连的首选。
1 嵌入式系统中TCP/IPv6 协议栈的特点与设计
1.1 嵌入式系统中TCP/IPv6 协议栈的特点
嵌入式系统不同于普通系统,它在硬件资源的占有量以及处理器的处理能力等方面受到限制,因此TCP/IP 协议栈在嵌入式系统与其在通用操作系统中的实现有很大不同。对于嵌入式系统而言,考虑到TCP/IP协议的复杂性以及嵌入式系统自身资源的有限,对TCP /IP 的实现并不是一件容易的事情[2]。因此就需要对原本复杂的TCP/IPv6 协议栈的实现进行精简。嵌入式IPv6 协议栈独立于嵌入式设备的操作,代码空间小、移植性好,可以在无法承载操作系统的小存储空间的嵌入式设备上实施,以实现这些设备间的基本网络功能。
1.2 嵌入式系统中TCP/IPv6 协议栈的设计
芯片采用挪威Chipcon 公司推出的符合2.4GHz IEEE802.15.4 标准的射频收发器CC2420[3] 。该协议栈要实现TCP/IPv6 协议栈的基本功能,运行于无线网环境下。底层协议采用IEEE 802.15 工作组制定的802.15.4 协议,802.15.4 协议是一个短距离的无线通信标准。网络层支持TCP、ICMPv6 协议,不实现UDP 协议。
1.2.1 网络接口层
网络接口层是TCP/IPv6 协议栈与以太网设备的驱动程序之间的接口。一方面,它将从网卡接收到的数据提交给上层网络层协议软件处理;另一方面,它将从网络层接收来的数据传送给网卡驱动。接口函数介绍如下:
(1)初始化函数
初始化函数InitNi()负责实现网络接口层以及下层的物理设备驱动程序的初始化。主要包括以太网控制器的上电复位、MAC 地址的配置、收发缓冲环首尾地址的配置以及DMA 的初始化和收发数据格式的定义等。
(2)发送函数
在发送数据时,网络接口层发送函数Send_Ethernet_Frame()负责接收IP协议产生的数据,将其封装成以太网数据帧后,调用下层的 Send_ Packet()函数实现发送。Send_Packet()函数实现的是把以太网数据帧通过远程DMA 通道送到RTL8019AS 中的发送缓存区,然后再启动本地DMA,将数据发送网上。
(3)接收函数
在接收数据时,网络接口层接收函数Rec_Ethernet_Packed( )被下层以太网驱动程序的数据接收函数Rec_Packet()调用。Rec_Ethernet_Packed()的作用根据以太类型值,调用不同的函数,同时去除以太帧的头部,将正确的IPv6 数据从NIC 的数据缓冲区内发送到ARM 的接收缓冲区内。Rec_Packet()函数通过读取RTL8019AS的当前寄存器CURR(写寄存器)和边界寄存器BNRY(读寄存器)的值来确定是否有新数据的到来,若有新数据到来,则设置数据地址和数据长度,然后启动远程DMA 将接收缓冲环中的以太网帧送交给上层。
2 嵌入式TCP/IPv6 协议栈的实现
2.1 嵌入式TCP/IPv6 协议栈处理流程
如图1 所示,嵌入式TCP/IPv6 协议接收数据包的过程就是解析数据包的过程。首先由底层处理函数解析数据包,根据类型,将去掉帧首部的数据包分配到缓冲区BUF 中,接着由IP 协议处理程序继续解析。IP 协议处理程序对数据包解析后,将数据交给TCP 或ICMPv6 协议处理程序。嵌入式TCP/IPv6 协议栈发送数据包的过程是封装数据包的过程,数据经过某层协议的处理,就会在数据包首部增加某种格式的首部。
图1 嵌入式TCP/IPv6协议解析数据包的过程
2.2 软件实现
首先做如下类型定义:
#define PROTO_ICMP 58
#define PROTO_TCP 6
#define ICMP_ECHO_REPLY 129
#define ICMP_ECHO 128
芯片接收到数据包后,放入缓冲区BUF 中交由上层协议处理。然后对数据包进行判断。过程下:
for(c=0;c<8;c++)
if(BUF->destipaddr[c] != hostaddr[c])
{ STAT(++stat.ip.drop);
goto drop; }
接收数据包后,检查下一个报头中的协议类型,如果是TCP 或ICMP 协议,则分别转向其处理程序,否则丢弃。
if(BUF->proto == PROTO_TCP) /* Check for TCP packet.If so,jump to the tcp_input label.*/
goto tcp_input;
if(BUF->proto = PROTO_ICMP) /*Check for ICMP packet.If so,jump to the icmp_input label.*/
goto icmp_input;
goto drop;
3 IPv6 在ARM 中的移植
IPv6协议栈在设计时就考虑到了移植问题,已把所有与硬件、OS、编译器相关的部分独立出来[4]。因此,IPv6 在本文研究的系统中的移植就是针对LPC2210 硬件平台、uC/OS-II 操作系统和ADS1.2 的编译器对其进行相应的修改。
1 数据类型定义
IPv6 的数据定义应该与uC/OS-II 定义的数据长度类型是一致的。
typedef unsigned char uint8;/* 无符号8 位整型变量*/
typedef signed char int8;/* 有符号8 位整型变量*/
typedef unsigned short uintl6;/* 无符号16 位整型变量*/
typedef signed short int16;/* 有符号16 位整型变量*/
typedef unsigned int uint32;/* 无符号32 位整型变量*/
typedef signed int int32;/*有符号32位整型变量*/
typedef float fp32;/* 单精度浮点数(32 位长度)*/
typedef double fp64;/* 双精度浮点数(64 位长度)*/
2 操作系统相关部分
(1)信号量
IPv6 中需要使用信号量进行同步。信号量实际上是一种约定机制,在多任务内核中普遍使用。信号像是一把钥匙,任务要运行下去,得先拿到这把钥匙。如果信号已被别的任务占用,该任务被挂起,直到信号被当前使用者释放。一般地说,对信号量只能实施三种操作:初始化(也可称作建立)、等信号(也可称作挂起)、给信号或发信号。信号量初始化时要给信号量赋初值,等待信号量的任务表应清为空。想要得到信号量的任务执行等待操作。如果该信号量有效(即信号量值大于0),则信号量值减1,任务得以继续运行。如果信号量的值为0,等待信号量的任务就被列入等待信号量任务表。多数内核允许用户定义等待超时,如果等待时间超过了某一设定值时,该信号量还是无效,则等待信号量的任务进入就绪态准备运行,并返回出错代码(指出发生了等待超时错误)。任务以发信号操作释放信号量。如果没有任务在等待信号量,信号量的值仅仅是简单地加1。如果有任务在等待该信号量,那么就会有一个任务进入就绪态,信号量的值也就不加1。于是,钥匙给了等待信号量的诸任务中的等待信号量任务中优先级最高的任务、信号量处理函数:
OSSemCreate / * 创建一个信号量* /
OSSemDel()/* 删除信号量*/
OSSemPend()/* 等待信号量*/
OSSemPost()/* 发送信号量*/
(2) 消息队列
消息队列用于给任务发消息。通过内核提供的服务、任务或中断服务子程序可以将一条消息(该消息的指针)放入消息队列。同样,一个或多个任务可以通过内核服务从消息队列中得到消息。发送和接收消息的任务约定,传递的消息实际上是传递的指针指向的内容。通常,先进入消息队列的消息先传给任务[5],也就是说,任务先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。然而,uC/OS-II也允许使用后进先出方式(LIFO)。当一个以上的任务要从消息队列接收消息时,每个消息队列有一张等待消息任务的等待列表。如果消息队列中没有消息,即消息队列是空,等待消息的任务就被挂起并放入等待消息任务列表中,直到有消息到来。通常,内核允许等待消息的任务定义等待超时的时间。如果限定时间内,任务没有收到消息,该任务就进入就绪态并开始运行,同时返回出错代码,指出出现等待超时错误。一旦一则消息放入消息队列,该消息将传给等待消息的任务中优先级最高的那个任务,或是最先进入等待消息任务列表的任务。
2.3 库函数的实现
IPv6 协议栈中用到了6 个外部函数,这些函数通常与用户使用的系统或编译器有关。返回字符串长度、字符串比较、内存数据块之间的互相拷贝和内存中指定长度的数据块清零,4 个函数已由ADS1.2 中的运行时库提供,不需要再编写。因为网络数据采用的是大端数据存储[6] , 而LPC2210 是工作在小端,所以,在存取网络数据时要进行字节的交换。
下面两个简单的函数需要实现:
uintl6 swapw( uintl6 n); //16位数据高低字节交换
{ return(((n<<8)&0xff00)}((n>>8)&0x00ff)); }
uint32 swapl(uint32 n);//32 位数据大小头对调
{ return(((n << 24 & 0xff000000L) ) ((n +8) & 0x00ff0000L) ( ( n >> 8 ) & 0x0000ff00L) ( ( n >> 2 4 )&0x000000ffL)); }
随着嵌入式系统应用的快速发展,为实现对其他设备的控制、监视或管理等功能,需要将嵌入式设备连入互联网,以便于远程控制和监测。为将嵌入式设备连入互联网,需要有通信模块实现通信协议。目前嵌入式设备能够通过主流的TCP/IP 协议可直接与外网连接。然而随着网络规模的日益扩大、上网设备和人数的激增,目前采用的IPv4逐渐接近其自身发展的极限,其中比较显著的问题是IP 地址资源的不足。IPv6[1] 采用长度为128 位的地址,地址空间几乎可以视为无限,IPv6不仅完全地解决了地址短缺问题,还对在IPv4 中解决不好的其他问题(如,端到端IP连接、服务质量、安全性、移动性等方面)进行了改进。IPv6成为嵌入式设备进行网络互连的首选。
1 嵌入式系统中TCP/IPv6 协议栈的特点与设计
1.1 嵌入式系统中TCP/IPv6 协议栈的特点
嵌入式系统不同于普通系统,它在硬件资源的占有量以及处理器的处理能力等方面受到限制,因此TCP/IP 协议栈在嵌入式系统与其在通用操作系统中的实现有很大不同。对于嵌入式系统而言,考虑到TCP/IP协议的复杂性以及嵌入式系统自身资源的有限,对TCP /IP 的实现并不是一件容易的事情[2]。因此就需要对原本复杂的TCP/IPv6 协议栈的实现进行精简。嵌入式IPv6 协议栈独立于嵌入式设备的操作,代码空间小、移植性好,可以在无法承载操作系统的小存储空间的嵌入式设备上实施,以实现这些设备间的基本网络功能。
1.2 嵌入式系统中TCP/IPv6 协议栈的设计
芯片采用挪威Chipcon 公司推出的符合2.4GHz IEEE802.15.4 标准的射频收发器CC2420[3] 。该协议栈要实现TCP/IPv6 协议栈的基本功能,运行于无线网环境下。底层协议采用IEEE 802.15 工作组制定的802.15.4 协议,802.15.4 协议是一个短距离的无线通信标准。网络层支持TCP、ICMPv6 协议,不实现UDP 协议。
1.2.1 网络接口层
网络接口层是TCP/IPv6 协议栈与以太网设备的驱动程序之间的接口。一方面,它将从网卡接收到的数据提交给上层网络层协议软件处理;另一方面,它将从网络层接收来的数据传送给网卡驱动。接口函数介绍如下:
(1)初始化函数
初始化函数InitNi()负责实现网络接口层以及下层的物理设备驱动程序的初始化。主要包括以太网控制器的上电复位、MAC 地址的配置、收发缓冲环首尾地址的配置以及DMA 的初始化和收发数据格式的定义等。
(2)发送函数
在发送数据时,网络接口层发送函数Send_Ethernet_Frame()负责接收IP协议产生的数据,将其封装成以太网数据帧后,调用下层的 Send_ Packet()函数实现发送。Send_Packet()函数实现的是把以太网数据帧通过远程DMA 通道送到RTL8019AS 中的发送缓存区,然后再启动本地DMA,将数据发送网上。
(3)接收函数
在接收数据时,网络接口层接收函数Rec_Ethernet_Packed( )被下层以太网驱动程序的数据接收函数Rec_Packet()调用。Rec_Ethernet_Packed()的作用根据以太类型值,调用不同的函数,同时去除以太帧的头部,将正确的IPv6 数据从NIC 的数据缓冲区内发送到ARM 的接收缓冲区内。Rec_Packet()函数通过读取RTL8019AS的当前寄存器CURR(写寄存器)和边界寄存器BNRY(读寄存器)的值来确定是否有新数据的到来,若有新数据到来,则设置数据地址和数据长度,然后启动远程DMA 将接收缓冲环中的以太网帧送交给上层。
2 嵌入式TCP/IPv6 协议栈的实现
2.1 嵌入式TCP/IPv6 协议栈处理流程
如图1 所示,嵌入式TCP/IPv6 协议接收数据包的过程就是解析数据包的过程。首先由底层处理函数解析数据包,根据类型,将去掉帧首部的数据包分配到缓冲区BUF 中,接着由IP 协议处理程序继续解析。IP 协议处理程序对数据包解析后,将数据交给TCP 或ICMPv6 协议处理程序。嵌入式TCP/IPv6 协议栈发送数据包的过程是封装数据包的过程,数据经过某层协议的处理,就会在数据包首部增加某种格式的首部。
图1 嵌入式TCP/IPv6协议解析数据包的过程
2.2 软件实现
首先做如下类型定义:
#define PROTO_ICMP 58
#define PROTO_TCP 6
#define ICMP_ECHO_REPLY 129
#define ICMP_ECHO 128
芯片接收到数据包后,放入缓冲区BUF 中交由上层协议处理。然后对数据包进行判断。过程下:
for(c=0;c<8;c++)
if(BUF->destipaddr[c] != hostaddr[c])
{ STAT(++stat.ip.drop);
goto drop; }
接收数据包后,检查下一个报头中的协议类型,如果是TCP 或ICMP 协议,则分别转向其处理程序,否则丢弃。
if(BUF->proto == PROTO_TCP) /* Check for TCP packet.If so,jump to the tcp_input label.*/
goto tcp_input;
if(BUF->proto = PROTO_ICMP) /*Check for ICMP packet.If so,jump to the icmp_input label.*/
goto icmp_input;
goto drop;
3 IPv6 在ARM 中的移植
IPv6协议栈在设计时就考虑到了移植问题,已把所有与硬件、OS、编译器相关的部分独立出来[4]。因此,IPv6 在本文研究的系统中的移植就是针对LPC2210 硬件平台、uC/OS-II 操作系统和ADS1.2 的编译器对其进行相应的修改。
1 数据类型定义
IPv6 的数据定义应该与uC/OS-II 定义的数据长度类型是一致的。
typedef unsigned char uint8;/* 无符号8 位整型变量*/
typedef signed char int8;/* 有符号8 位整型变量*/
typedef unsigned short uintl6;/* 无符号16 位整型变量*/
typedef signed short int16;/* 有符号16 位整型变量*/
typedef unsigned int uint32;/* 无符号32 位整型变量*/
typedef signed int int32;/*有符号32位整型变量*/
typedef float fp32;/* 单精度浮点数(32 位长度)*/
typedef double fp64;/* 双精度浮点数(64 位长度)*/
2 操作系统相关部分
(1)信号量
IPv6 中需要使用信号量进行同步。信号量实际上是一种约定机制,在多任务内核中普遍使用。信号像是一把钥匙,任务要运行下去,得先拿到这把钥匙。如果信号已被别的任务占用,该任务被挂起,直到信号被当前使用者释放。一般地说,对信号量只能实施三种操作:初始化(也可称作建立)、等信号(也可称作挂起)、给信号或发信号。信号量初始化时要给信号量赋初值,等待信号量的任务表应清为空。想要得到信号量的任务执行等待操作。如果该信号量有效(即信号量值大于0),则信号量值减1,任务得以继续运行。如果信号量的值为0,等待信号量的任务就被列入等待信号量任务表。多数内核允许用户定义等待超时,如果等待时间超过了某一设定值时,该信号量还是无效,则等待信号量的任务进入就绪态准备运行,并返回出错代码(指出发生了等待超时错误)。任务以发信号操作释放信号量。如果没有任务在等待信号量,信号量的值仅仅是简单地加1。如果有任务在等待该信号量,那么就会有一个任务进入就绪态,信号量的值也就不加1。于是,钥匙给了等待信号量的诸任务中的等待信号量任务中优先级最高的任务、信号量处理函数:
OSSemCreate / * 创建一个信号量* /
OSSemDel()/* 删除信号量*/
OSSemPend()/* 等待信号量*/
OSSemPost()/* 发送信号量*/
(2) 消息队列
消息队列用于给任务发消息。通过内核提供的服务、任务或中断服务子程序可以将一条消息(该消息的指针)放入消息队列。同样,一个或多个任务可以通过内核服务从消息队列中得到消息。发送和接收消息的任务约定,传递的消息实际上是传递的指针指向的内容。通常,先进入消息队列的消息先传给任务[5],也就是说,任务先得到的是最先进入消息队列的消息,即先进先出原则(FIFO)。然而,uC/OS-II也允许使用后进先出方式(LIFO)。当一个以上的任务要从消息队列接收消息时,每个消息队列有一张等待消息任务的等待列表。如果消息队列中没有消息,即消息队列是空,等待消息的任务就被挂起并放入等待消息任务列表中,直到有消息到来。通常,内核允许等待消息的任务定义等待超时的时间。如果限定时间内,任务没有收到消息,该任务就进入就绪态并开始运行,同时返回出错代码,指出出现等待超时错误。一旦一则消息放入消息队列,该消息将传给等待消息的任务中优先级最高的那个任务,或是最先进入等待消息任务列表的任务。
2.3 库函数的实现
IPv6 协议栈中用到了6 个外部函数,这些函数通常与用户使用的系统或编译器有关。返回字符串长度、字符串比较、内存数据块之间的互相拷贝和内存中指定长度的数据块清零,4 个函数已由ADS1.2 中的运行时库提供,不需要再编写。因为网络数据采用的是大端数据存储[6] , 而LPC2210 是工作在小端,所以,在存取网络数据时要进行字节的交换。
下面两个简单的函数需要实现:
uintl6 swapw( uintl6 n); //16位数据高低字节交换
{ return(((n<<8)&0xff00)}((n>>8)&0x00ff)); }
uint32 swapl(uint32 n);//32 位数据大小头对调
{ return(((n << 24 & 0xff000000L) ) ((n +8) & 0x00ff0000L) ( ( n >> 8 ) & 0x0000ff00L) ( ( n >> 2 4 )&0x000000ffL)); }
- 蓝牙无线电调制解调器Siw1701原理与应用(02-19)
- 嵌入式移动数据库的关键技术(03-20)
- 在嵌入式SQL中怎样使用游标(08-12)
- 嵌入式Linux系统软件开发学习思路详细介绍 (08-20)
- 基于AVR单片机的嵌入式“瘦服务器”系统设计思想(03-11)
- 嵌入式系统设计中的存储碎片收集策略(05-04)