介绍在Windows平台上使用Visual C++ 6.0开发环境进行嵌入式TCP/IP协议栈的开发和仿真调试手段。详细讲解了如何使用Winpcap接收和发送IP数据报文以及在Windows上配置双协议栈的一些问题。
【关键词】 嵌入式系统;TCP/IP协议栈;Windows平台;仿真调试;WinPcap;
1 引言
随着Internet的广泛的应用,在嵌入式设备中支持TCP/IP协议以连接到Internet网络并与外界通信的需求更加强烈,这就需要在嵌入式系统中支持TCP/IP协议栈。虽然目前的商用嵌入式操作系统,如VxWorks、QNX、pSOS、VRTX等,均提供基于TCP/IP的网络组件,但为了满足各个方面的应用需要,其实现过于复杂,需要占用大量的系统资源。而嵌入式系统的本身资源有限,并且其应用和功能比较单一,具有较强的针对性,因此也并不需要一个完整的TCP/IP网络协议组件,只需要实现与需求相关的部分协议,不使用的协议则不需要支持。在另一方面,对于某些特定的嵌入式系统,甚至需要优化TCP/IP协议栈或者在TCP/IP协议栈中编写自己需要的网络协议。那些不能提供开放源码的商用嵌入式系统的TCP/IP协议栈都很难满足用户的配置需求,需要用户自行开发和定制适合自己系统需求的嵌入式TCP/IP协议栈。
2 嵌入式协议栈的开发和调试问题
一般的嵌入式系统的开发和调试都是使用其相应的开发调试工具连接计算机和目标机进行交叉开发和调试。例如被广泛使用的VxWorks嵌入式实时操作系统的开发工具Tornado,它就是一套强有力的交叉开发工具,用户可以在计算机上使用图形界面对目标机上的应用程序进行调试。
但即便是使用像Tornado这样优秀的嵌入式开发环境,在交叉调试协议栈此类比较大型的程序时,还是显得力不从心,其开发调试是件费时费力的工作,大大增加了系统的开发难度和开发调试的周期。在目前的嵌入式系统的调试工具还不尽如人意的现状下,对嵌入式TCP/IP协议栈的开发如果能前期在Windows平台上进行开发和仿真调试,将是一件很有意义的工作。
嵌入式TCP/IP协议栈虽然是基于嵌入式操作系统,但除了接收和发送数据包以外,几乎并不直接与底层硬件打交道。因此在Windows平台上仿真调试和运行TCP/IP协议栈是完全可行的,可以完成绝大部分功能的开发与调试,后期再移植到嵌入式系统上,只需进一步稍加调试和测试便能实现整个嵌入式软件系统的功能和性能。这样的开发流程能够极大的提高开发的效率,减少开发的周期。
3 在Windows平台上运行嵌入式协议栈
在Windows平台上仿真调试和运行TCP/IP协议栈,首先需要在Visual C++ 6.0开发环境中创建一个Win32应用程序的项目工程用于模拟嵌入式系统,嵌入式TCP/IP协议栈就是在这个Win32的应用程序中运行。这样,我们使用Windows平台下的一个进程模拟了一个多任务的嵌入式操作系统。
一个多任务嵌入式操作系统需要具有任务管理、内存管理以及任务间通信机制如信号量、消息队列等功能。因此,如想在Windows平台上运行嵌入式TCP/IP协议栈,也必须提供上述多任务嵌入式操作系统的基本功能。
在多任务嵌入式操作系统中,任务是系统进行调度的最基本的单元,参与资源竞争和CPU资源在任务间的分配,系统通过循环的方式为每个任务安排一定的 CPU时间片,而在宏观上看仿佛是若干任务并发处理,形成多任务操作系统。而在Winodows这样的通用操作系统平台上,则是由线程作为参与CPU时间 片资源竞争最小实体,因此,我们使用线程模拟嵌入式操作系统中的一个任务。任务的创建、删除和控制等操作通过调用Windows平台中提供的线程的创建、 删除和控制的Win32 API函数来实现。
在多任务操作系统中,任务与任务之间需要协调动作,相互配合,这就需要提供任务间相互通信的机制以进行同步和互斥。嵌入式系统中一般提供信号量、事件和消 息机制这三种主要的任务间通信手段。同样,在Windows平台上的Win32 API也提供相应的用于进程/线程间通信的信号量、事件和消息机制的函数。
由于Windows是一个强大的通用分时操作系统,能够提供完善的操 作系统接口。因此,多任务嵌入式操作系统完全可以在Windows平台上模拟。不过,这种模拟也只是近似的,毕竟对于多数嵌入式系统都是实时系统,而 Windows却是分时系统,无法保证其系统的实时性。
至于嵌入式TCP/IP协议栈的开发,考虑TCP/IP协议族的复杂性以及其协议栈庞大的代码量,试图完全从头到尾彻底的重新编写一套TCP/IP协议栈的代码是极为艰难的,需要大量的人力、物力和时间的投入。其实目前TCP/IP技术已经十分成熟,几乎所有的通用操作系统都提供TCP/IP协议栈用于网络支持,包括那些公开源码的操作系统。因此考虑移植源码公开的TCP/IP协议栈,同时根据需求对其进行适量的精简和改进。
目前,比较常见的源码公开的TCP/IP协议栈软件有:
1). BSD Net网络协议栈软件。这是由加利福利亚大学伯克利分校计算机系统研究小组发布的,世界上第一个被广泛应用TCP/IP软件版本就是伯克利于1983年发布的4.2BSD,有许多系统的TCP/IP协议栈实现都是以它的源代码为基础而开发的。目前其最新版本是1994年发布的4.4BSD-Lite2,又称为Net/3。
2). Linux的TCP/IP协议栈软件。作为一个遵循GUN公共许可协议,源码全部公开的自由操作系统软件,其TCP/IP协议栈部分源码是以BSD的网络协议栈为模型,支持BSD的Socket接口,但其内部代码是重新写的,与4.4BSD-Lite2并不雷同。
3). lwIP是一个比较小型的源码开放的TCP/IP协议栈软件,是由瑞典计算机科学研究院的Adam Dunkels教授编写。它只需要10K的RAM空间和40K的ROM存储空间,因此非常适合嵌入式系统里使用。
4). uIP则是一个超小型的TCP/IP协议栈,仅能提供ARP、SLIP、TCP、ICMP和IP这几种基本的协议。其所需资源非常的少,所以非常适合在8位和16位单片机上运行。
对于TCP/IP协议栈的选择主要根据用户本身的需求和所能提供的软硬件资源来确定。比如像BSD和Linux的TCP/IP协议栈属于通用的协议栈,支持协议比较齐全,但也需要耗用大量的ROM和RAM存储空间,对CPU的要求也比较高;而lwIP和uIP这类的协议栈是专门为嵌入式操作系统开发出来的,软件结构比较紧凑,对CPU和存储器需求不高,但其所支持的协议种类及功能也非常有限。
4 WinPcap工具包介绍
在Windows平台上仿真调试和运行TCP/IP协议栈,还需要考虑协议栈如何接收和发送数据报文的问题,这就需要使用WinPcap来实现。
WinPcap(Windows Packet Capture)是Windows平台下一个公共的、免费的网络访问系统,能为Win32应用程序提供网络访问的能力。它提供以下四项主要功能:
1) 捕获原始数据报文,包括共享网络上各主机发送/接收和相互交换的数据包;
2) 在数据报文发送往应用程序之前,按照自定义的规则将某些特定的数据包过滤掉;
3) 在网络上发送原始的数据报文;
4) 收集网络通信过程中的统计信息;
WinPcap的主要功能在于独立于主机协议而发送和接收原始数据报文,能够监听共享网络上传送的数据包。因此,通过调用它提供的各种函数,可以实现在 Windows平台下将各类数据报文通过网络适配器发送到共享网络上去,同样也可以接收网络适配器上收到的各种原始的数据包。
要使用WinPcap,首先需要在Windows平台上安装WinPcap驱动软件,然后便可以在Win32的应用程序中通过包含packet32.h这 个头文件来使用由WinPcap的动态链接库packet32.dll或者静态链接库packet32.lib所提供的库函数来对网络适配器进行打开、设 置、关闭操作和通过网络适配器进行接收或者发送数据报文。
下面简要介绍一下其主要函数的功能:
1) BOOLEAN PacketGetAdapterNames(LPSTR pStr,PULONG BufferSize) 返回可以得到的网络适配器列表及描述。
2) LPADAPTER PacketOpetAdapter(LPTSTR AdapterName) 打开一个网络适配器。
3) BOOLEAN PacketSetBuff(LPADAPTER AdapterObject,int dim) 设置捕获数据报的内核级缓冲区大小。
4) BOOLEAN PacketSetHwFilter(LPADAPTER AdapterObject,ULONG Filter) 为接收到的数据报设置硬件过滤规则。一般而言,需要将其设置为 NDIS_PACKET_TYPE_PROMISCUOUS(混杂模式),即接收所有流过的数据报文。
5) LPPACKET PacketAllocatePacket(void) 如果运行成功,返回一个_PACKET结构的指针,否则返回NULL。成功返回的结果将会传送到PacketReceivePacket()函数,接收来自驱动的网络数据报。
6) VOID PacketInitPacket(LPPACKET lpPacket, PVOID Buffer, UINT Length) 初始化一个_PACKET结构。
7) BOOLEAN PacketSendPacket(LPADAPTER AdapterObject,LPPACKET lpPacket, BOOLEAN Sync) 发送一个或多个数据报的副本。
8) BOOLEAN PacketReceivePacket(LPADAPTER AdapterObject,LPPACKET lpPacket,BOOLEAN Sync) 从NPF驱动程序读取网络数据报及统计信息。
9) VOID PacketFreePacket(LPPACKET lpPacket) 释放参数提供的_PACKET结构。
10) VOID PacketCloseAdapter(LPADAPTER lpAdapter) 关闭参数中提供的网络适配器,释放相关的ADAPTER结构。
5 在Windows平台下协议栈发送和接收函数的设计
在Windows平台下通过WinPcap可以直接对网络适配器进行操作,从而实现发送和接收数据报文的目的。在使用WinpCap前首先需要进行初始化 操作,其步骤如下:调用PacketGetAdapterNames()获取当前网络适配器的名称,再调用PacketOpenAdapter()函数打 开一个网络适配器,然后还需要调用PacketSetHwFilter()函数设置网络适配器的过滤规则和调用PacketSetBuff()设置捕获数 据报的内核级缓冲区大小。
当协议栈需要发送数据报文时,首先需要调用PacketAllocatePacket()函数为发送数据报文创建一个网络数据报结构,然后调用 PacketInitPacket()函数对该结构进行初始化,将存储待发送数据报文的缓冲区指针和长度填入网络数据报结构,再接下来便是调用 PacketSendPacket()函数将数据报文从指定的网络适配器中发送出去,之后需要调用PacketFreePacket()函数释放掉刚才申 请的网络数据报文结构的内存空间。这样,一个TCP/IP协议栈就能够完成将一个数据报文通过WinPcap操作网络适配器发送到网络上的工作。
使用WinPcap接收网络适配器上收集到的网络上的数据报文,首先需要创建一个高优先级别的任务或者线程,一般在TCP/IP协 议栈的网络接口层初始化时即需要创建一个这样的任务或者线程,然后在该任务或者线程函数里调用PacketAllocatePacket()为接收数据报 文创建网络数据报结构,再调用PacketInitPacket()函数为接收的数据报文分配内存缓冲区,这个缓冲区需要尽量大一点儿,否则一旦网络上数 据报文比较多,而协议栈上层来不及处理,则会造成数据报文的丢失。接下来便是循环的调用PacketReceivePacket()函数从指定的网络适配 器读取数据报文,并将数据报文拷贝到协议栈的内存空间,再通过操作系统提供的任务或线程间通信的机制将该数据报文发送到TCP/IP协议栈的接收任务或者线程进行报文的分析和处理。这个数据接收的任务和线程将永远不会返回,始终循环调用PacketReceivePacket()函数读取网络适配器上接收到的网络上传输过来的数据报文并将其交付给TCP/IP协议栈进行处理。
6 在Windows平台上配置双协议栈的问题
由于Windows平台本身自带有TCP/IP协议栈,而我们又需要在Windows平台上运行一个嵌入式TCP/IP协议栈,实际上是在Windows这一个操作系统上配置了双TCP/IP协议栈。
要使这两个TCP/IP协议栈相互之间互不影响并能各自良好的运行,首先需要为两个协议栈各自分配不同的IP地址;其次在嵌入式TCP/IP协议栈中的数据链路层的Mac地址,一定不能使用Windows平台的TCP/IP协议栈使用的网络适配器的地址,除非计算机装有两块网络适配器,Windows平台的TCP/IP协议栈使用一个网络适配器进行数据的收发,而嵌入式TCP/IP协议栈使用另一个网网络适配器收发数据报文。其实在嵌入式TCP/IP协 议栈中,其链路层的Mac地址可以通过修改程序代码设置任意的虚拟Mac地址,但这必须首先调用WinPcap的PacketSetHwFilter() 函数设置网络适配器的接收模式为NDIS_PACKET_TYPE_PROMISCUOUS(混杂模式)用于指定网罗适配器接收所有流过的数据报文,否 则,网络适配器会根据本身的Mac地址对网络上接收到的数据报文进行Mac过滤,丢弃掉不属于该网络适配器接收的数据报文。
7 小结
作者在工作中按照上述方法,成功的在Windows平台上运行并调试了Linux的TCP/IP协议栈,并最终将其移植到VxWorks操作系统中运行。在调试过程中,作者明显感觉Windows的VC6开发平台下调试确实比直接使用Tornado调试要方便和快捷许多。由于前期在Windows平台上调试时解决了大部分移植和修改TCP/IP协议栈的问题,后期在Tornado下调试时基本上没有花费太多的时间,大大提高了工作的效率,减小了开发的周期。