写在前面的话:初学Rtems,英语水平也有限,错误在所难免,请各位大牛不吝赐教,完善c_user的翻译。便于大家受益,在此谢过!
前言
这篇文档描述了rtems中带有的FreeBSD的TCP/IP协议栈。本文档的大部分由Eric Norum(eric@skatter.usask.ca)撰写。下面是一些资源的列表,对我们理解以太网是有帮助的。
Charles Spurgeon’s Ethernet Web Site
TCP/IP Illustrated, Volume 1 : The Protocols by by W. Richard Stevens (ISBN:0201633469)
TCP/IP Illustrated, Volume 2 : The Implementation by W. Richard Stevens andGary Wright (ISBN: 020163354X)
UNIX Network Programming, Volume 1 : 2nd Edition by W. Richard Stevens(ISBN: 0-13-490012-X)
1.网络任务结构和数据流
每个网络接口的发送任务常常会因为发送一个数据包到发送队列而阻塞。一旦发送过来一个数据包,发送任务将阻塞以等待发送中断事件的发生。发送中断处理函数将一个rtems事件发送给一个发送任务,表明发送发送的硬件资源已经可用。
每个网络接口的接收任务常常在等待一个来自接收中断的事件时会有阻塞。当事件到达时,接收任务会读到一个数据包。然后传输到网络协议栈,随后由网络任务处理。
网络任务将处理传来的数据包,要注意定时操作。比如TCP超时处理,超时、删除路由表项。网络代码包含了例程,可以在用户的应用程序的任务的上下文中跑,还可以在接口的接收任务和网络任务中跑。网络信号量确保了由网络代码操作的数据结构都可以保持一致。
2.网络驱动程序
2.1概述
本章将尝试去介绍写一个rtems网络驱动程序的步骤。示例代码来自通用68360网络驱动。源代码位于c/src/lib/libbsp/m68k/gen68360/。手头拿一份该驱动,将有对你有显著的帮助。
2.2了解网络设备
在写网络设备的驱动之前,要彻底熟悉一下需要编程的设备。下面列举了一些关于了解的设备细节,在写驱动前,必须弄清楚。
- 该设备是否使用DMA传送数据包到设备和内存,或者处理器必须复制数据包到设备和内存?
- 如果设备使用DMA是否能从多个分布在独立缓冲区内的数据帧形成一个单一的输出数据包?
- 如果设备使用DMA是否能够连续传出多个数据包,还是每个传出数据包都需要由驱动干预?
- 是否设备自动将短帧填充到最低的~64~个字节,或由驱动提供填充?
- 当检测到碰撞时设备是否会自动重新重传?
- 如果设备使用DMA它可以缓冲多个数据包到内存,或是接收任务必须在每一个包到达时重新启动传输?
- 数据包过短,过长,或收到的CRC错误怎么处理?设备自动接收或是驱动程序必须介入?
- 如何设置设备的以太网地址?如何对设备编程以接受或拒绝广播和多播数据包?
- 设备产生什么样的中断?它为每一个到达的数据包产生中断,或仅为没有错误的接收数据包产生中断?它是否为
每个发送数据包产生中断,或只是在发送队列是空的时候产生中断?
在发送时检测到错误会发生什么?
(红色字体部分来自《RTEMS 4.9.5:QEMU MINI2440 BSP 中的网络驱动开发(上)》作者:雪松--rtems中国推广的一位牛人)
另外,有些控制器还有一些特别的问题,涉及主板的特殊设置。比如,SONIC以太网控制器正好有一个可配置的数据总线接口。他甚至可以配置16或32位数据总线。如何配置,供应商给于相关信息。
2.3了解网络调度规则
当写驱动的发送和接收的任务代码时,需要了解下面的网络调度规则。所有与网络有关的共享结构体变量和资源,必须确保执行的结构体数据的一致性。只有获得了网络信号量的那个任务才能够执行(rtems_bsdnet_semaphore)。发送和接收任务必须遵守本协议。提供了许多例程,使得网络驱动代码遵循网络任务规则可以简单化。
void rtems_bsdnet_semaphore_release(void)
这个函数释放网络信号量。网络驱动任务必须立即调用该函数,在阻塞任何rtems请求之前。
void rtems_bsdnet_semaphore_obtain(void)
这个函数获取网络信号量。如果一个网络驱动任务要被阻塞,那需要释放网络信号量,然后去允许其他相关的网络任务。在从rtems阻塞结束后,需要再次获得信号量。
rtems_bsdnet_event_receive(rtems_event_set, rtems_option, rtems_interval, rtems_event_set *)
网络驱动任务必须调用这个函数,当他希望等待一个事件时。这个函数会释放网络信号量,调用rtems_event_receive函数去等待一个或几个特定的事件,再重新获得信号量。返回值为rtems_event_receive的返回值。
2.4网络驱动Makefile
网络驱动程序被认为是BSD网络包的一部分,因此编译时要使用了适当的选项。可以通过添加-D__INSIDE_RTEMS_BSD_TCPIP_STACK__到命令行实现。如果驱动程序在RTEMS源码树中或是使用RTEMS应用程序的Makefile进行编译,那么加入以下代码行实现:DEFINES += - D__INSIDE_RTEMS_BSD_TCPIP_STACK__
这是等同于以下列表中的定义。在RTEMS BSD网络协议栈的早期版本要求所有这些都必须被定义。
-D_COMPILING_BSD_KERNEL_ -DKERNEL -DINET -DNFS /
-DDIAGNOSTIC -DBOOTP_COMPAT
定义这些宏告诉网络头文件网络驱动程序以扩展协议栈可见性的方式编译。这与简单地使用网络协议栈的应用程序形成鲜明地对比。应用程序不需要这个水平的能见度,并应坚持应用水平API的可移植性。
网络协议栈是内部逻辑的直接结果(BSD 由自己的代码全部撰写),这意味着网络驱动程序使用BSD的内存分配例程,例如,有三个参数的malloc。例如超声设备驱动程序(c/src/lib/libchip/network/sonic.c)是一个典型的例子。正因为如此,网络驱动程序不应该包括<stdlib.h>。这样做将导致malloc()定义的冲突。(摘自雪松《网络资料的部分翻译》)
2.5写驱动的附加功能
驱动的附加功能负责配置驱动,连接网络协议栈和驱动。驱动的附加功能使用一个指针指向rtems_bsdnet_ifconfig结构体作为他们唯一的参数。设置基于该结构体中的值的与驱动相关的参数。如果一个在这个结构体中的入口为0,则附加函数将为参数选择了一个适当的默认值。驱动程序在ifnet结构体和与设备相关的数据结构中需要设置一下几个部分。
ifp->if_softc
ifp->if_name
ifp->if_unit
ifp->if_mtu
ifp->if_flags
ifp->if_snd.ifq_maxlen
ifp->if_init
ifp->if_start
ifp->if_ioctl
ifp->if_output
RTEMS提供了一个函数去分析配置结构体中设备的名字,然后将其转为设备名和设备号。
int rtems_bsdnet_parse_driver_name (
const struct rtems_bsdnet_ifconfig *config,
char **namep
);
该函数带有两个参数,一个为指向配置结构体的指针和一个指向字符的指针。该函数用于分析设备名,为设备分配内存,将设备驱动的名字装入内存。第二个参数用于指向某个设备,并返回设备号。如果有错误将返回-1。
一旦附加函数设置了上面的入口,它必须通过调用if_attach函数将驱动的数据连接到设备的目录中。以太网设备这时调用ether_ifattach函数。两个函数都使用设备的ifnet数据结构作为他们的参数。附加函数需要返回一个非0的值去表明驱动已经成功的配置和连接。
2.6 写驱动的开始函数
在网络协议栈每次要启动发送时应调用这个函数。每当网络协议栈添加一个数据包到一个设备的发送队列时,当IFF_OACTIVE位在设备的if_flags位未设置才调用启动。
对于许多设备这个函数只需要设置在if_flags中的IFF_OACTIVE位,并发送一个事件给发送任务,表明一个包在驱动程序的发送队列中。
2.7 写驱动的初始化函数
这个函数应初始化设备,连接中断处理程序,并启动驱动程序的发送和接收任务。
rtems_id
rtems_bsdnet_newproc (char *name,
int stacksize,
void(*entry)(void *),
void *arg);
函数rtems_bsdnet_newproc应用于启动驱动程序的任务。
请注意,网络协议栈可能会不止一次调用驱动程序的初始化函数。确认接收和发送任务的多个版本不会被意外启动。(摘自雪松《网络资料的部分翻译》)
2.8 写驱动的发送任务
发送任务负责从驱动程序中发出数据包至队列中,再将其发送到设备中。该任务将被阻塞等待一个来自驱动启动函数的事件,以示有数据包需要被发送。当发送任务发完驱动需发送的发送队列,任务需要清除if_flags中的IFF_OACTIVE位,直到另一个数据包的到达队列中。
2.9 写驱动的接收任务
在有新的数据包发送到设备之前该任务处于阻塞态。如果设备是以太网接口,必须调用ether_input函数将数据包送至网络栈。ether_input函数的参数是一个指向接口数据结构的指针。一个指针指向以太网的帧头,一个指针指向包含数据包自身的缓存。
2.10 写驱动的中断函数
一个典型的中断函数只是完成响应已产生的中断这一硬件操作,发送RTEMS事件去唤醒发送和接收任务。网络接口中断函数将屏蔽其他网络任务。
2.11 写驱动的控制函数
该函数处理来自设备的控制请求。IOCTL必须处理以下命令:
SIOCGIFADDR、SIOCSIFADDR:如果这个设备是一个以太网接口,这些命令需要传递到ether_ioctl中;
SIOCSIFFLAGS:这个命令通常用于启动或停止设备,依赖于接口if_flags中的IFF_UP和IFF_RUNNING位;
IFF_RUNING停止设备;
IFF_UP启动设备;
IFF_UPIFF_RUNNING'停止然后启动设备;
0不做任何事情。
2.12 写驱动的统计打印函数
这个函数是用来打印网络驱动可能用到的统计/诊断计数器的值。驱动程序的ioctl函数中,当ioctl命令为SIO_RTEMS_SHOW_STATS必须调用该统计打印函数。
3 在应用程序中使用网络
3.1 修改makefile
3.1.1 包含所需管理器
这个FreeBSD网络代码在应用程序中需要RTEMS的几个管理器:
MANAGERS = IO管理器 事件管理器 信号量管理器
3.1.2 增加堆的大小
网络任务会申请大量的内存。对于大多数应用程序,堆至少应为256 KB。设置堆的额外内存可以通过如下的方式调整CFLGAS_LD的定义:
CFLAGS_LD += -Wl,--defsym -Wl,HeapSize=0x80000
这为堆多设置了512 KB的内存。
3.2系统配置
网络任务申请一些RTEMS的对象。应用程序配置表必须统计这些对象。下面列出了要求。
TASK(任务):一个网络任务再加上每个设备的网络任务接收和发送任务;
SEMAPHORES(信号量):一个网络信号加上系统日志二值信号量,如果应用程序中使用了openlog/syslog;
EVENT(事件):网络协议栈使用了RTEMS_EVENT_24和RTEMS_EVENT_25。这对应用程序的配置没有影响,但调用网络函数的应用任务不应该为了其它目的使用这些事件。
3.3 初始化
3.3.1 添加包含文件
声明网络配置网络结构并调用初始化函数的源文件必须包含:
#include <rtems/rtems_bsdnet.h>
3.3.2 网络配置
指定网络的配置通过声明和初始化rtems_bsdnet_config结构实现。
struct rtems_bsdnet_config {
/*
* This entry points to the head of the ifconfig chain.
*/
struct rtems_bsdnet_ifconfig *ifconfig;
/*
* This entry should be rtems_bsdnet_do_bootp if BOOTP
* is being used to configure the network, and NULL
* if BOOTP is not being used.
*/
void (*bootp)(void);
/*
* The remaining items can be initialized to 0, in
* which case the default value will be used.
*/
rtems_task_priority network_task_priority; /* 100 */
unsigned long mbuf_bytecount; /* 64 kbytes */
unsigned long mbuf_cluster_bytecount; /* 128 kbytes */
char *hostname; /* BOOTP */
char *domainname; /* BOOTP */
char *gateway; /* BOOTP */
char *log_host; /* BOOTP */
char *name_server[3]; /* BOOTP */
char *ntp_server[3]; /* BOOTP */
unsigned long sb_efficiency; /* 2 */
/* UDP TX: 9216 bytes */
unsigned long udp_tx_buf_size;
/* UDP RX: 40 * (1024 + sizeof(struct sockaddr_in)) */
unsigned long udp_rx_buf_size;
/* TCP TX: 16 * 1024 bytes */
unsigned long tcp_tx_buf_size;
/* TCP TX: 16 * 1024 bytes */
unsigned long tcp_rx_buf_size;
};
下面会详细描述这个结构体中的每一个字段。如果应用程序中使用了BOOTP / DHCP来获得网络配置信息,并且如果你满意下列描述的默认值,你只需要提供这个结构体的前两个字段。
struct rtems_bsdnet_ifconfig *ifconfig:
指向第一个网络设备的配置结构的指针。这个结构体会在后续的部分进行介绍。必须为这个字段提供一个值,因为它没有默认值。
void (*BOOTP)(void):
此字段应设置为 rtems_bsdnet_do_bootp或rtems_bsdnet_do_dhcp,如果应用程序将使用 BOOTP/DHCP来获得网络配置信息。如果应用程序不使用 BOOTP/DHCP,它应该被设置为NULL。
int network_task_priority:
网络和网络设备的接收和发射任务将运行的优先级别。如果为这个字段指定一个0值,任务将运行在优先级100;
unsigned long buf_bytecount:
从堆中分配作为mbuf使用的字节数。如果为该字段指定0值,将申请64 KB;
unsigned long mbuf_cluster_bytecount:
从堆中分配作为 mbuf'的群集使用的字节数。如果为该字段指定0值,将申请128 KB;
char *hostname:
该系统的主机名。如果这样,或下列任何条目为NULL,它的值可以从一个 BOOTP/DHCP服务器获得;
char *domainname:
该系统隶属互联网域名的名称;
char *gateway:
网络网关机器的IP地址,用‘.十进制数’(129.128.4.1)的形式指定;
char *log_host:
syslog消息发送到的机器的IP地址;
char *name_server[3]:
多达三个主机的IP地址,作为因特网域名服务器;
char *ntp_server[3]:
多达三个主机的IP地址,作为网络时间协议(NTP)服务器;
unsigned long sb_efficiency:
这是五个涉及到每个socket的缓冲区内存消耗量的第一个配置参数。TCP/IP协议栈为每个打开的套接字(socket)保留一些缓冲区(例如mbuf)。TCP/IP协议栈对每个TCP和UDP套接字关联的发送和接收缓冲区有不同的限制。通过调节这些参数,应用程序开发人员可以平衡内存的消耗和性能。默认参数偏向性能多于内存消耗。见http://www.rtems.org/ml/rtems-users/2004/february/msg00200.html,可以得到更多的信息。但注意,在RTEMS 4.8以后的版本,sb_efficiency的缺省值由8变成了2。用户还应该了解 SO_SNDBUF和 SO_RCVBUF的IO控制操作。这些操作可以用来为特定的套接字指定发送和接收缓冲区的大小。没有一个标准的IO控制去改变 sb_efficiency参数。sb_efficiency参数是一个缓冲区因子用于实现TCP/IP协议栈。缺省值为2表示使用双倍的缓冲区。当为每个套接字申请内存时,这个数字会乘以每个套接字的缓冲区大小;
unsigned long udp_tx_buf_size:
此配置参数指定用于UDP套接字发送缓冲区的最大内存用量。默认大小为9216字节,对应的最大数据报文的大小;
unsigned long udp_rx_buf_size:
此配置参数指定用于UDP套接字接收缓冲区的最大内存用量。默认长度是(按字节计算):
40 * (1024 + sizeof(struct sockaddr_in));
unsigned long tcp_tx_buf_size:
此配置参数指定用于TCP套接字传输缓冲区的最大内存用量。默认大小为16 KB;
unsigned long tcp_rx_buf_size:
此配置参数指定用于TCP套接字接收缓冲区的最大内存用量。默认大小为16 KB。
此外,在下列领域rtems_bsdnet_ifconfig也值得注意。
int port:
外部以太网可以访问I/O端口号(如0x240);
int irno:
外部以太网控制器的中断号;
int bpar:
外部以太网控制器上共享内存的地址。(摘自雪松《网络资料的部分翻译》)
3.3.3 网络设备的配置
用户通过声明和初始化一个rtems_bsdnet_ifconfig结构体变量来指定每个网络设备。下面会详细介绍结构体中的每一个字段。一个使用单一网络接口的应用程序,会从一个 BOOTP/DHCP服务器获取网络配置信息,并且所有驱动程序的参数使用缺省值,只需要初始化结构体项的前两个字段。
char *name:
网络设备的全名。此名称由驱动的名称和单元号组成(例如“SCC1”)。 bsp.h文件中通常定义了RTEMS_BSP_NETWORK_DRIVER_NAME作为主(或唯一的)网络驱动程序的名称。
int (*attach)(struct rtems_bsdnet_ifconfig *conf):
该驱动程序的连接函数的地址。网络初始化函数调用这个函数来配置驱动程序并连接到网络协议栈。bsp.h文件中通常定义了 RTEMS_BSP_NETWORK_DRIVER_ATTACH作为主(或唯一的)网络驱动程序的函数的名称。
struct rtems_bsdnet_ifconfig *next:
指向下一个网络设备的配置结构的指针,如果本结构是最后一个网络接口的配置结构,该字段为NULL。
char* ip_address:
该设备的IP地址,用‘.十进制数’的形式指定(如129.128.4.2),如果该设备的配置信息正从BOOTP/DHCP服务器获取,该字段的值为NULL。
char* ip_netmask:
该设备的IP地址掩码,用‘.十进制数’的形式指定(如255.255.255.0),如果该设备的配置信息
正从BOOTP/DHCP服务器获取,该字段的值为 NULL。
void* hardware_address:
该设备的硬件地址,如果驱动程序使用一些其他方式获取硬件地址,该字段值为 NULL(通常是从设备或引导序列的ROM中读取)。
int ignore_broadcast:
如果该设备接收广播数据包,该字段的值为0;如果该设备是不接收广播数据包,该字段的值为非0。
int mtu:
该设备的最大传输单元(Maximum Transmission Unit, MTU),该字段为0时驱动会选择一个默认值(通常以太网设备为1500)。
int rbuf_count:
设备接收时使用的缓冲区数量,该字段为0时该驱动程序选择默认值。
xbuf_count:
设备发送时使用的缓冲区数量,该字段为0时该驱动程序选择默认值。请记住,某些网络设备发送一个单个的缓冲区时可能使用4个或更多的传输描述符。
一个完整的网络配置可以像下面的例子这样简单。这个配置使用一个网络接口,从 BOOTP/DHCP服务器获取网络配置信息,并使用所有驱动程序的默认值参数。
static struct rtems_bsdnet_ifconfig netdriver_config = {
RTEMS_BSP_NETWORK_DRIVER_NAME,
RTEMS_BSP_NETWORK_DRIVER_ATTACH
};
struct rtems_bsdnet_config rtems_bsdnet_config = {
&netdriver_config,
rtems_bsdnet_do_bootp,
};
3.3.4 网络的初始化
网络任务必须在任何网络I/O操作可以执行前先启动。这是通过调用函数 rtems_bsdnet_initialize_network()完成的。这个函数原型在文件 rtems/rtems_bsdnet.h中。函数返回0表示成功,-1表示错误并且在 errno中存储着错误代码。部分初始化的影响是不可能被撤销的,虽然如此,该函数可被调用一次,无论返回代码是什么。因此,如果失败的情况可得到纠正,系统必须复位以允许网络再次初始化的尝试。
3.4 应用程序接口
RTEMS的网络包提供了几乎全部的BSD网络服务。网络函数工作行为与BSD下对应的函数类似,除了以下:
同一时间,一个给定的套接字只可以被一个任务读取或写入;
select函数只用于与套接字相关的文件描述符;
您必须在调用任何syslog函数之前调用 openlog;
一些网络的函数不是线程安全的。例如,下面的函数返回一个指针指向一个静态的缓冲区,只保持到下一个调用有效:
gethostbyaddr
gethostbyname
inet_ntoa
RTEMS网络包收集统计信息;
新增的一个机制用于窃听每个接口并监控每一个数据包接收和传输;
新增的 SO_SNDWAKEUP'和 SO_RCVWAKEUP'套接字选项。
新的一些功能进行了更详细的在下面的章节。
3.4.1 网络统计
有一些函数可以打印网络协议栈收集的统计数据。这些函数在 rtems/rtems_bsdnet.h中定义。
rtems_bsdnet_show_if_stats:显示网络接口收集的统计数据;
rtems_bsdnet_show_ip_stats:显示IP数据包的统计数据;
rtems_bsdnet_show_icmp_stats:显示ICMP包的统计数据;
rtems_bsdnet_show_tcp_stats:显示TCP报文统计数据;
rtems_bsdnet_show_udp_stats:显示UDP数据包统计数据;
rtems_bsdnet_show_mbuf_stats:显示mbuf的统计数据;
rtems_bsdnet_show_inet_routes:显示路由表。
3.4.2 借助接入接口
RTEMS向BSD网络中增加两个新的 ioctls代码:SIOCSIFTAP和SIOCGIFTAP。这些用来设置和获取一个窃听函数。监听函数将在每个以太网接口收到数据包时调用。
这些功能调用起来像接口的其它 ioctls一样,如SIOCSIFADDR。当使用SIOCSIFTAP设置窃听函数,设置ifreq结构体中的ifr_tap字段指向窃听函数。当使用SIOCGIFTAP检索窃听函数时,当前的在ifr_tap字段中的窃听函数被返回。为了停止窃听数据包,调用SIOCSIFTAP将ifr_tap字段设置成0。
窃听函数的原型是这样的:
int tap (struct ifnet *, struct ether_header *, struct mbuf *)
如果包被完全处理,窃听函数应该返回1,这种情况下调用者简单的丢弃mbuf。如果包应该被传递到网络的更高层,窃听函数应该返回0。
窃听函数被调用时网络信号量应锁定。它不能调用与其自身处于相同级别的网络函数。它调用其他非网络的RTEMS函数是安全的。
3.4.3套接字选项
RTEMS为setsockopt和getsockopt增加了两个新的SOL_SOCKET级别的选项:
SO_SNDWAKEUP和SO_RCVWAKEUP。对于这个选项,它们的值应指向一个sockwakeup的结构。对于这个选项,它们的值应指向一个sockwakeup的结构。该sockwakeup结构具有以下字段:
void (*sw_pfn) (struct socket *, caddr_t);
caddr_t sw_arg;
这些选项用于设置一个回调函数,例如在有数据在套接字(SO_RCVWAKEUP)中可用时并且有空间可以接受数据写入到套接字中(SO_SNDWAKEUP)时被调用。
如果调用setsockopt并使用SO_RCVWAKEUP选项,并且sw_pfn字段不为零,这时当有数据可从套接字中读取,sw_pfn字段所指向的函数将被调用。一个指向套接字的结构的指针将作为第一个参数传递给函数。sw_arg字段为SO_RCVWAKEUP调用时的第二个传递到函数的参数所修改。
如果调用setsockopt并使用SO_SNDWAKEUP选项,并且sw_pfn字段不为零,这时当有空间可以接受的数据写入套接字时,sw_pfn字段所指向的函数将被调用。传递给该函数的参数将被视为带有SO_SNDWAKEUP。
当函数被调用时,网络信号量将被锁定,回调函数将运行在的网络任务的上下文中。该函数必须小心,不要调用任何网络函数。调用RTEMS函数是没有问题的,例如,发送一个RTEMS事件。
这些回调函数的目的是当大量的套接字需要处理时允许更有效的选择调用。
回调被调用的统一标准是select函数标记“就绪”的套接字。在Stevens 的 Unix Network Programming,第153-154页,“Under what Conditions Is a Descriptor Ready?”一节中,你将发现你会发现最后的条件的列表为可读可写,也决定着函数被调用。
当接收到的字节数等于或超过该套接字接收缓冲区的“低水位线”(缺省为1字节),你得到一个可读的回调。如果有100个字节在接收缓冲区内,你只读了1个字节,你不会立即得到另外一个回调。然而,当你读其余的99字节或至少有1到达后,你会得到另一个回调。使用非阻塞套接字,你应该读取数据直到它产生错误EWOULDBLOCK,然后让可读回调函数告诉你什么时候有更多的数据到达。(条件1.a.)
对于发送,当套接字连接后并且发送缓冲区的自由空间到达或高于“低水位线”(默认4096字节),你会收到一个写回调。如果没有写任何数据,你不会持续得到回调。使用非阻塞函数写套接字,你应该调用写操作直到它返回实际写的数据量比请求的数据量少,或它产生错误EWOULDBLOCK错误(表示缓冲区满,不能再被写入)。当发生这种情况时可以尝试再次写入,但往往最好去做其他事情,让写回调函数告诉你何时有空间可用来再次发送数据。
你只能得到一个可写回调,当发送缓冲区的自由空间高于“低水位线”时,而不是每次你写数据到一个还未写满的发送缓冲区内。(条件2.a.)
其余的条件Stevens也都列举的相关的事实,当连接、断开和错误发生,而不仅是接收或发送数据时套接字变为可读和/或可写的条件。例如,当一个服务器“监听”的套接字变为可读则表示一个客户端已连接,并且接受调用不会被阻塞,而不是接收网络数据。(条件1.c.)
3.4.4增加一个IP的别名
下面的代码为系统增加一个别名IP地址。
void addAlias(const char *pName, const char *pAddr, const char *pMask)
{
struct ifaliasreq aliasreq;
struct sockaddr_in *in;
/* initialize alias request */
memset(&aliasreq, 0, sizeof(aliasreq));
sprintf(aliasreq.ifra_name, pName);
/* initialize alias address */
in = (struct sockaddr_in *)&aliasreq.ifra_addr;
in->sin_family = AF_INET;
in->sin_len = sizeof(aliasreq.ifra_addr);
in->sin_addr.s_addr = inet_addr(pAddr);
/* initialize alias mask */
in = (struct sockaddr_in *)&aliasreq.ifra_mask;
in->sin_family = AF_INET;
in->sin_len = sizeof(aliasreq.ifra_mask);
in->sin_addr.s_addr = inet_addr(pMask);
/* call to setup the alias */
rtems_bsdnet_ifconfig(pName, SIOCAIFADDR, &aliasreq);
}
3.4.5添加一个默认路由
这个小节提供的函数等于在命令 route add default gw yyy.yyy.yyy.yyy。
3.4.6使用的NTP时间同步
int rtems_bsdnet_synchronize_ntp (int interval, rtems_task_priority priority);
如果参数interval为0,函数将~RTEMS的时间日期时钟与在rtems_bsdnet_ntpserve数组中的第一NTP服务器同步并返回。优先级参数priority被忽略。
如果间隔参数的值大于0,函数按指定的优先级别(priority)启动一个RTEMS任务,并且按照‘间隔’的秒数不断的轮询NTP服务器。注意:此模式的操作尚未实现。
一旦成功的同步了RTEMS的时间日期时钟,函数返回0。如果发生错误打,则印一条消息,函数返回-1并且在errno里存放错误代码。通讯没有超时,如果NTP服务器没有响应,函数将一直等下去。
4测试驱动程序
4.1初步设置
用于测试的驱动程序的网络至少应包括:
在具体的硬件上运行驱动程序。如果能带调试器来控制目标机器的运作,会使得测试更加容易;
以太网络分析仪或带有以太网抓包软件(如ethersnoop或tcpdump)的工作站;
工作站(一台PC机)。
在早期的调试中,应该考虑单独的对目标板,工作站和抓包软件组建一个小网络。它的优势在于:
减少了通讯的包数量,便于抓包程序处理和分析目标处理在驱动工作以后的动作;
任何严重的错误只会影响您的小型网络,而不是整个建筑物或校园网。可以避免任何不必要的问题;
测试数据包比较容易重复地产生;
性能测量不影响网络上的其他系统。
4.2调试输出
网络中有很多的调试输出可以启用,以帮助追查网络协议栈的行为。下面列出全部的调试输出:
mbuf的活动:
在网络协议栈代码中的sys/mbuf.h文件里已经注释掉了对printf的调用。取消这些注释,当mbuf分配和释放的时候会产生输出信息。这是非常有用的查找内存泄漏的工具;
发送和接收队列:
在网络协议栈代码中的net/if.h文件里已经注释掉了对printf的调用。取消这些注释,当从发送或接收数据包队列中插入或移除一个数据包时会产生输出。这些队列可以看作设备驱动程序和网络协议栈之间的边界。如果网络协议栈要发送而插入数据包到发送队列,设备驱动程序不出列,那么将指示这个问题在设备驱动程序的发送端。相反,如果设备驱动程序因为它接收数据包而让其包入队列(通过调用ether_input),但没有数据包被网络协议栈移出队列。
这种情况很可能是网络服务任务没有正确启动。
TCP状态转换:
万一有人会想查看TCP状态转换,可在opt_tcpdebug.h文件中定义TCPDEBUG宏。这会使tcp_trace()例程被网络协议栈调用,并且状态转换会记录到tcp_debug数据结构里。如果文件netinet/tcp_debug.c中的tcpconsdebug变量设置成1,则状态转换也将被打印到控制台。
4.3 终端命令
在shell和终端中有许多可用的命令来追踪网络栈的行为。下面就是他们的列表:
Inet
这个命令用于显示当前TCP/IP栈的当前路由信息。下面这个例子就是该命令的一组输出。Destination Gateway/Mask/Hw Flags Refs Use Expire Interface
10.0.0.0 255.0.0.0 U 0 0 17 smc1
127.0.0.1 127.0.0.1 UH 0 0 0 lo0
在这个例子中,只有一个IP地址为10.8.1.1。
显示了两条路由,一条是以太网接口的默认路由10.0.0.0,一条是回环接口127.0.0.1。
因为该栈来自BSD,因此命令十分类似netstat命令。要了解更多网络路由的细节,可查阅该链接
(http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/network-routing.html)
Mbuf
该命令用来显示当前对Mbuf的统计。该命令的一个例子如下:
************ MBUF STATISTICS ************
mbufs:4096 clusters: 256 free: 241
drops: 0 waits: 0 drains: 0
free:4080 data:16 header:0 socket:0
pcb:0 rtable:0 htable:0 atable:0
soname:0 soopts:0 ftable:0 rights:0
ifaddr:0 control:0 oobdata:0
If
该命令用于显示当前以太网设备一些统计
Ip
该命令用于显示当前配置接口的IP统计
Icmp
该命令用于显示当前配置接口的ICMP统计
Tcp
该命令用于显示当前配置接口的TCP统计
Udp
该命令用于显示当前配置接口的UDP统计
4.4 驱动的基本操作
用于作为网络演示的netdemo程序可以用来做如下测试。
编辑networkconfig.h来反应网络的配置的一些值;
用没有定义的RTEMS_USE_BOOTP启动;
编辑networkconfig.h,用确切的以太网和因特网地址来配置驱动,不接受广播的数据包,一旦驱动连接上,检查程序的运行;
发一个“u”命令用来发一个UDP的数据包,检查这个数据包在网络上的出现;
发一个“s”命令来打印驱动和网络的统计;
在一个工作站上,增加一个静态的路由到目标系统;
在相同的工作站上试着去ping目标系统。检查IMCP输出的请求和在网络上出现的回答的包;
移除目标板上的静态路由。修改networkconfig.h以修改驱动使其能够响应广播的数据包。试着再次去ping目标板。检查ARP的反应/回答,检查IMCP输出的请求和在网络上出现的回答的包;
发一个“t”命令用来发一个TCP的数据包,检查这个数据包在网络上的出现;
发一个“s”命令来打印驱动和网络的统计;
在你网络的工作站,检查你能否telnet目标系统的24742、24743端口。
4.5 BOOTP/DHCP操作
在你的网络上建立一个BOOTP/DHCP服务器。在networkconfig.h中设定RTEMS USE_BOOT。运行netdemo测试程序。检查目标系统来自自己的BOOTP/DHCP配置,以上的测试都能成功。
4.6 stress测试
一旦驱动通过上面章节所提到的测试。
4.6.1 获取包
给MAXIMUM_FRAME_SIZE设定相似的值如514,然后重新编译驱动;
从另一个工作站ping该驱动,检查大于514字节的帧是否正确的被接收;
给MAXIMUM_FRAME_SIZE设定相似的值如1518,然后重新编译驱动。
4.6.2资源消耗
编辑networkconfig.h来配置该驱动,使得其只有接收和发送两个描述符;
编译和运行netdemo程序;
检查程序是否正确的运行,你是否能够正确telnet这两个端口;
显示驱动的统计,并检查:
1.发送中断数是空的。这表明所有的发送描述在同时被使用了。
2.被丢失的数据包数是空的。这表明所有的接收描述在同时被使用了。
4.6.3 Cable错误
运行netdemo程序;
发送“u”控制台命令,让目标机发送一连串的UDP数据包;
当数据包被发送时,撤销或再次连接网络cable;
显示网络的统计,检查驱动的传输丢失;
检查你仍然能够telnent目标机的两个端口。
4.6.4 吞吐量
运行ttcp网络标准程序。从目标板发送大量数据数据。
从主机到一个安装了RTEMS操作系统的目标机测试吞吐量的流程是:
1.下载测试程序至目标板,并运行。
2.对ttcp请求的对应操作为,按键按下-s -r。这些标志的意义在ttcp.1手册中有所描述。
3.在主机上运行ttcp -s -t 《嵌入主机名和目标机的IP地址》。
从一个安装了RTEMS操作系统的目标机到主机测试吞吐量的流程是:
1.在主机上运行ttcp -s -t 。
2.下载并在目标机上运行ttcp程序。
3.对ttcp请求的对应操作为,按键按下-s -t。你需要输入主机的IP地址,除非你的目标机正在和域名服务器进行通信。
为了改变缓存的数目、大小等,你只要增加一些额外的ttcp.1手册中规定的标志如-t 机器,这些在ttcp_orig子目录下可查到。
5 网络服务器
5.1 RTEMS的FTP守护进程
RTEMS的FTP守护进程是一个完整的发送协议守护进程,他可以存储、检索、操作本地的系统文件。另外,RTEMS FTP提供了在接收数据可以活动的陷阱。陷阱在目标文件不是十分必要时显得十分有用,或一个常规的设备驱动没有正常工作。此服务器有Jack Janovetz执行和记录。
5.1.1参数配置
The configuration structure for FTPD is as follows:
struct rtems_ftpd_configuration
{
rtems_task_priority priority; /* FTPD task priority */
unsigned long max_hook_filesize; /* Maximum buffersize */
/* for hooks */
int port; /* Well-known port */
struct rtems_ftpd_hook *hooks; /* List of hooks */
};
FTPD任务优先级是由priority规定的。因为陷阱不是被保存成文件,接收的数据被放在分配的缓存中。Max_hook_filesize规定了缓存的大小。最后,陷阱是一个配置好的陷阱数据结构。
5.1.2 FTPD的初始化(启动守护进程)
启动FTPD就是完成调用rtems_initialize_ftpd()。数据结构的配置必需提供应用程序的源代码。陷阱结构的例子和配置结构如下:
struct rtems_ftpd_hook ftp_hooks[] =
{
{"untar", Untar_FromMemory},
{NULL, NULL}
};
struct rtems_ftpd_configuration rtems_ftpd_configuration =
{
40, /* FTPD task priority */
512*1024, /* Maximum hook ’file’ size */
0, /* Use default port */
ftp_hooks /* Local ftp hooks */
};
指定的0用于端口使得FTPD使用UNIX标准的FTPD端口(21)。
5.1.3使用陷阱
在上面的例子里,安装了一个陷阱。该陷阱使得FTPD当用户发送数据至解压文件时调用Untar_FromMemory。解压的陷阱的原型是:
int Untar_FromMemory(unsigned char *tar_buf, unsigned long size);
试着使用陷阱的FTP的结果:
220 RTEMS FTP server (Version 1.0-JWJ) ready.
Name (dcomm0:janovetz): John Galt
230 User logged in.
Remote system type is RTEMS.
ftp> bin
200 Type set to I.
ftp> dir
200 PORT command successful.
150 ASCII data connection for LIST.
drwxrwx--x 0 0 268 dev
drwxrwx--x 0 0 0 TFTP
226 Transfer complete.
ftp> put html.tar untar
local: html.tar remote: untar
200 PORT command successful.
150 BINARY data connection.
210 File transferred successfully.
471040 bytes sent in 0.48 secs (9.6e+02 Kbytes/sec)
ftp> dir
200 PORT command successful.
150 ASCII data connection for LIST.
drwxrwx--x 0 0 268 dev
drwxrwx--x 0 0 0 TFTP
drwxrwx--x 0 0 3484 public_html
226 Transfer complete.
ftp> quit
221 Goodbye.
6.1 DEC21240驱动简介
项目的一个目标是在PowerPC平台上移植RTEMS系统。为实现这个目标,我们选用了摩托罗拉MCP750的主板。该板包含一款基于DEC21140芯片的以太网控制器。因为RTEMS包含TCP/IP协议栈,我们将开发为安装在PowerPC平台上DEC21140网卡的RTEMS系统驱动。由于该控制器能够支持100M网络,且许多PCI卡使用DEC芯片,所以我们需要先开发出在PC386目标平台上的100M网卡的驱动,然后将开发出的驱动移植到PowerPC上。
该文档的目的是介绍一些PCI板卡的相关知识,然后解释RTEMS驱动的程序的软件架构。最后了解为了得到ChorusOs和网络启动环境需要做些什么。
6.2 文档版本历史
6.3 DEC21140 PCI主板概要
本章将快速讲述PCI接口的以太网控制器。该板我们为PC386平台选择的网卡是D-link的DFE-500TX。这是一种使用DEC21140AF芯片的10/100M自适应网卡。和其它PCI设备一样,该PCI网卡驱动的前部包含一些配置寄存器,这些寄存器在PCI寄存器表中有相关描述。通过读写这些寄存器,驱动程序中可以获得板卡类型的信息,其使用的中断,芯片特殊功能寄存器映射,...
在Intel目标机上,该芯片的特殊功能寄存器可以通过两种方式操作:I/O端口方式和PCI地址映射方式。
在RTEMS中,有PCI的API存在。我们需要使用它去对板子进行配置。同过pci_initialize()函数对PCI模块进行初始化后,我们将试着侦测DEC21140网卡。该板子有Vendor ID(0x1011)和设备ID(0x0009)。我们将这些参数传递给pcib_find_by_deviceid函数,如果设备存在,函数将返回一个生成的指向配置空间头部的指针。一旦操作完成,驱动程序中将输出它需要去配置板子上的外部寄存器的信息,如中断、基地址,...这里对该板子的外部寄存器就不详细介绍。
6.4 RTEMS驱动程序软件结构
在该章节里,我们将分析程序的初始化部分,控制器如何操作主存储器和在初始化时启动2个线程。
6.4.1 初始化部分
DEC21140网卡的驱动程序的软件架构和其它RTEMS以太网设备的软件架构相同。当板子被识别到之后,程序中可通过唯一的API函数是
rtems_dec21140_driver_attach (struct rtems_bsdnet_ifconfig *config) 函数,该函数的作用是侦测设备和初始化相关的数据结构(包括寄存器基地址、低级初始化函数入口地址)。
一旦相关函数被执行,驱动将初始化DEC芯片。然后驱动将连接中断处理函数和以太网的中断输出引脚,另外将启动2个线程:一个是接收的线程,一个是发送的线程。这时驱动将等待即将到来的帧然后送至协议栈,或输出帧至物理层的具体设备上。
6.4.2 内存缓冲区
DEC芯片使用主存储器去保存接收到的以太网帧和解释这些帧。我们需要根据缓冲区和封装的问题选择7个接收缓存和一个发送缓存去优化内存分配。
我们使用了一个缓冲环来介绍DEC芯片的缓存。这些描述的结构在缓存描述表中进行定义。每个描述能一个或两个缓存。我们选择一个1520字节的缓存。
接收缓存和发送缓存的区别是数据帧中的状态和控制位。在此不作过细的介绍,可以参考DEC21140的硬件手册。
6.4.3 接收线程
该线程由事件触发。每次DEC PCI板上中断产生是,中断处理函数检查是不是接收中断,并且以事件触发的形式启动接收线程。从整缓存看,其包含一个有效的要接收的帧。每个有效的要被接收的以太网帧被发往协议栈,缓存归还给DEC板。
6.4.4 发送线程
该线程也是由事件触发。每次一个以太网帧被放到发送队列,一个事件通知发送进程,该进程将要发送的数据发送后清空队列。由于我们只使用一个发送缓存,因此在发送下一帧数据前要先确定前一帧数据已发送成功。
6.5 遇到的问题
6.6 ChorusOs DEC驱动
因为ChorusOs用于几个Canon CRF项目,我们必须提供一个在这个操作系统上的驱动,确保在RTEMS和ChorusOs之间的兼容性。在ChorusOs上,一个DEC驱动源代码以存在但仅适用于PowerPC目标机。我们准备将这些代码移植到Inter的目标机器上。这两种开发工具是类似的。