一,TCP/IP Lean简介
有一次听邵贝贝老师的讲座,他除了讲著名的uC/OS之外,介绍了一本嵌入式TCP/IP协议的书,后来我买到了一个译本,《嵌入式系统Web服务器 TCP/IP Lean 》Jeremy Bentham著,陈向群译,机械工业出版社2003年5月出版。看了三次,自认为入门了,于是开始移植。
这书附带了两个TCP/IP协议堆栈,一个就叫TCP/IP Lean 是在DOS上实现的,另一个是ChipWeb 是在PIC单片机上实现的一个Web服务器。
TCP/IP Lean 支持以下协议:
ARP:没有ARP缓存,没有超时机制;
IP:V4,支持IP分片;
ICMP:只实现ping;
UDP:
TCP:支持超时重传,不支持平滑往返时间,窗口大小固定。
Socket接口用回调函数实现,还实现了Telnet客户端和Web服务器,有一个简单的CGI,作者称之为EGI(Embedded CGI).
TCP/IP Lean并没有明显的层次结构。所有的功能都是用一个大循环实现的,这是单任务操作系统下的合理选择,也便于程序的理解和维护,下面以WebServe为例说明,主程序main()结构如下:
定义了一个叫GENFRAME的通用数据包,
typedef struct {
GENHDR g; /* General-pupose frame header */
BYTE buff[MAXGEN];/* Frame itself (2 frames if fragmented) */
} GENFRAME;
可以放下3040字节的数据,据书上解释可以放下两条以太网数据,在IP数据包分片处理时需要那么大。这个数据结构是静态分配的,和LwIP的pbuf结构有很大不同。也许pbuf结构的效率更高一些,但太复杂,不适合象我这样的初学者。几乎所有的处理都基于这个通用包进行的, 先看一段程序:
if ((rxlen=get_frame(gfp)) > 0) /* Any incoming frames? */
{
ip = getframe_datap(gfp);
if (is_arp(gfp, rxlen))
{ /* ARP response? */
arp = getframe_datap(gfp);
if (arp->op==ARPREQ && arp->dip==locnode.ip)
{ /* ARP request? */
node.ip = arp->sip; /* Make ARP response */
memcpy(node.mac, arp->smac, MACLEN);
txlen = make_arp(gfp, &locnode, &node, ARPRESP);
put_frame(gfp, txlen); /* Send packet */
}
if (arp->op==ARPRESP && arp->dip==locnode.ip)
{ /* ARP response? */
arp_receive(tsocks, NSOCKS, gfp);
}
}
else if ((rxlen=is_ip(gfp, rxlen))!=0 && /* IP datagram? */
ip->i.dip==locnode.ip || ip->i.dip==BCASTIP)
{
getip_srce(gfp, &node);
if ((len=is_icmp(ip, rxlen))!=0) /* ICMP? */
{
icmp = (ICMPKT *)ip;
if (icmp->c.type==ICREQ) /* Echo request? */
{
len = (WORD)maxi(len, 0); /* Make response */
txlen = make_icmp(gfp, &locnode, &node, ICREP,
icmp->c.code, (WORD)len);
put_frame(gfp, txlen); /* Send packet */
}
}
else if ((len=is_tcp(ip, rxlen))!=0) /* TCP? */
{
tcp_receive(tsocks, NSOCKS, gfp, len);
}
}
}
}
这段程序用来处理输入包,gfp是指向通用包的指针,有好几层的if-else,首先是用get_frame()函数判断有没有数据输入,然后用几个"is"开头的函数判断是什么类型的数据包,比如is_arp(),判断是不是ARP数据包,is_ip()就判断是不是ip协议。为了生成一个数据包则用"make"开头的函数,比如make_icmp(),就生成icmp应答数据包。
TCP协议的状态机,是在一个tsock_rx()函数里面进行处理的,当接收到一个TCP数据包的时候会调用这个函数,在TCP超时处理的时候也调用这个函数,这个函数名有点令人迷惑,需要特别注意。
这个协议堆栈的Socket没有采用标准的方式,而是依赖回调函数实现的,在适当的时候调用一个upcall(),而应用程序也可以通过upcall()的返回值影响下一层的程序。
共3条
1/1 1 跳转至页
这段程序用来处理输入包,gfp是指向通用包的指针,有好几层的if-else,首先是用get_frame()函数判断有没有数据输入,然后用几个"is"开头的函数判断是什么类型的数据包,比如is_arp(),判断是不是ARP数据包,is_ip()就判断是不是ip协议。为了生成一个数据包则用"make"开头的函数,比如make_icmp(),就生成icmp应答数据包。
TCP协议的状态机,是在一个tsock_rx()函数里面进行处理的,当接收到一个TCP数据包的时候会调用这个函数,在TCP超时处理的时候也调用这个函数,这个函数名有点令人迷惑,需要特别注意。
这个协议堆栈的Socket没有采用标准的方式,而是依赖回调函数实现的,在适当的时候调用一个upcall(),而应用程序也可以通过upcall()的返回值影响下一层的程序。
二,开发环境
最初是在一个叫NetStart的开发板上调试程序的,后来又自己做了一块线路板,核心部分基本上是一样的。MCU是SamSung 的S3C4510B,板上有16M的DRAM和2M的Flash,S3C4510B已经集成了一个10M/100M的以太网接口,再加上一块PHY就可以工作了,我用的PHY是DM9161,现在最常见的是RTL8201。线路板上有JTAG口和串口,程序的调试主要就依靠它们。
仿真器的软硬件都是EMBEST公司的,功能和STD差不多,基于JTAG口,速度是25KB/s。后来这家公司推出了新的仿真器,速度为120 KB/s,于是又买了一个。这个速度,只要不是调很大的程序,已经足够了。
用JTAG口调程序比较方便,拿到新板子,在第一次上电前先查一查有没有短路现象,然后通电。可以先读写一些寄存器,如果能读写,则JTAG口、电源、Reset电路和晶振都没问题,然后就可以读写DRAM和Flash了,可以调一下点亮LED的程序。第三步是调试串口,只要串口调通了,基本的开发环境就建立起来了。
三,移植的工作
需要修改的主要以下部分:
1, 以太网的驱动程序;
2, 实现一个软时钟,能支持timeout()函数;
3, 改造文件操作有关的程序段;
4, 一些数据结构需要改为紧凑格式;
以太网的驱动程序,就借用了Samsung开发板SNDS100的测试代码,输入是DMA方式的,输出是MAC方式,也就是直接输出,不用中断和DMA了。对于输入的数据包设置了一个环形缓冲区,在DMA结束后,产生一个中断,就在中断服务程序里将数据拷贝到环形缓冲区。前面已经介绍的get_frame()再从缓冲区拷贝数据,这样就有两次拷贝过程。我对段程序不太满意,觉得至少可以减少一次拷贝,到什么时候有空再改写一次吧。
因为TCP/IP Lean 本身就是一个单进程结构,自然不能用阻塞进程的手段来实现延时了。于是就用了查询系统时钟的方式,在书上有比较详细的介绍的。要实现这个timeout(),还稍微有点麻烦,得用一个定时器产生定时中断,还得维护一个时间片变量tickes。幸好Samsung的测试代码也有测试定时器的,就拿来用了。
还有一个问题就是文件,原来的程序里面,网络配置信息是放在文件里的,网页也是以文件形式保存的。相关的程序在编译时出来大量的错误信息。只好耐心地一句句修改。将配置信息直接放在源程序中,还写了一个很简单的文件系统来存放网页。
所有数据包都是用结构来定义的,包括前面提到的通用数据包,这些数据包都有一个存储格式问题的,即是否加上一个"__attribute__ ((packed))",不知什么原因,原来的程序没考虑这个问题。紧凑格式的结构,处理效率会下降,最坏的情况是一个32位的数据被拆成4个8位数据,32位CPU的优势荡然无存。要不要把结构定义为紧凑格式,还得仔细考虑。
我先调试以太网的驱动程序,和PC机组成对等网,用Ethereal抓包,比较收发的数据包是否一样,然后调ARP,这是第一个协议,而且对工具和程序都不熟悉,走了不少弯路。调了ARP以后就发现IP协议和ICMP协议基本上是同样的思路。TCP协议多了一个状态机,而且还需要和socket一起调试,稍微麻烦一些。
四,总结
TCP/IP Lean 有详细的注释,还有参考书,而且程序突出了可读性。和Internet上的几个协议堆栈相比较,是最容易入门的。
TCP协议的状态机,是在一个tsock_rx()函数里面进行处理的,当接收到一个TCP数据包的时候会调用这个函数,在TCP超时处理的时候也调用这个函数,这个函数名有点令人迷惑,需要特别注意。
这个协议堆栈的Socket没有采用标准的方式,而是依赖回调函数实现的,在适当的时候调用一个upcall(),而应用程序也可以通过upcall()的返回值影响下一层的程序。
二,开发环境
最初是在一个叫NetStart的开发板上调试程序的,后来又自己做了一块线路板,核心部分基本上是一样的。MCU是SamSung 的S3C4510B,板上有16M的DRAM和2M的Flash,S3C4510B已经集成了一个10M/100M的以太网接口,再加上一块PHY就可以工作了,我用的PHY是DM9161,现在最常见的是RTL8201。线路板上有JTAG口和串口,程序的调试主要就依靠它们。
仿真器的软硬件都是EMBEST公司的,功能和STD差不多,基于JTAG口,速度是25KB/s。后来这家公司推出了新的仿真器,速度为120 KB/s,于是又买了一个。这个速度,只要不是调很大的程序,已经足够了。
用JTAG口调程序比较方便,拿到新板子,在第一次上电前先查一查有没有短路现象,然后通电。可以先读写一些寄存器,如果能读写,则JTAG口、电源、Reset电路和晶振都没问题,然后就可以读写DRAM和Flash了,可以调一下点亮LED的程序。第三步是调试串口,只要串口调通了,基本的开发环境就建立起来了。
三,移植的工作
需要修改的主要以下部分:
1, 以太网的驱动程序;
2, 实现一个软时钟,能支持timeout()函数;
3, 改造文件操作有关的程序段;
4, 一些数据结构需要改为紧凑格式;
以太网的驱动程序,就借用了Samsung开发板SNDS100的测试代码,输入是DMA方式的,输出是MAC方式,也就是直接输出,不用中断和DMA了。对于输入的数据包设置了一个环形缓冲区,在DMA结束后,产生一个中断,就在中断服务程序里将数据拷贝到环形缓冲区。前面已经介绍的get_frame()再从缓冲区拷贝数据,这样就有两次拷贝过程。我对段程序不太满意,觉得至少可以减少一次拷贝,到什么时候有空再改写一次吧。
因为TCP/IP Lean 本身就是一个单进程结构,自然不能用阻塞进程的手段来实现延时了。于是就用了查询系统时钟的方式,在书上有比较详细的介绍的。要实现这个timeout(),还稍微有点麻烦,得用一个定时器产生定时中断,还得维护一个时间片变量tickes。幸好Samsung的测试代码也有测试定时器的,就拿来用了。
还有一个问题就是文件,原来的程序里面,网络配置信息是放在文件里的,网页也是以文件形式保存的。相关的程序在编译时出来大量的错误信息。只好耐心地一句句修改。将配置信息直接放在源程序中,还写了一个很简单的文件系统来存放网页。
所有数据包都是用结构来定义的,包括前面提到的通用数据包,这些数据包都有一个存储格式问题的,即是否加上一个"__attribute__ ((packed))",不知什么原因,原来的程序没考虑这个问题。紧凑格式的结构,处理效率会下降,最坏的情况是一个32位的数据被拆成4个8位数据,32位CPU的优势荡然无存。要不要把结构定义为紧凑格式,还得仔细考虑。
我先调试以太网的驱动程序,和PC机组成对等网,用Ethereal抓包,比较收发的数据包是否一样,然后调ARP,这是第一个协议,而且对工具和程序都不熟悉,走了不少弯路。调了ARP以后就发现IP协议和ICMP协议基本上是同样的思路。TCP协议多了一个状态机,而且还需要和socket一起调试,稍微麻烦一些。
四,总结
TCP/IP Lean 有详细的注释,还有参考书,而且程序突出了可读性。和Internet上的几个协议堆栈相比较,是最容易入门的。
共3条
1/1 1 跳转至页
回复
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |