赞
踩
这里我们讨论了所有接口要用到的数据结构以及对这个数据结构的初始化。本文中我们将说明以太网设备驱动程序在初始化后是如何接收和传输帧的
如果读者对一个驱动程序的源代码感兴趣,Net/3版本中包括很多不同接口的源代码。要想研究接口的技术规范,就要求理解设备专用的命令。下图所示的是Net/3提供的各种驱动程序
网络设备驱动程序通过ifnet结构中的7个函数指针来访问。下图列出了指向我们的三个例子驱动程序的入口点
只有函数if_output和if_ioctl被经常的调用。而if_init、if_done和if_reset从来不被调用或者仅从设备专用代码调用。函数if_start仅被函数ether_output调用
以太网设备驱动程序和通用接口ioctl的代码包含在两个头文件和三个C文件中。如下所示
下图显示的全局变量包括协议输入队列LANCE接口结构和以太网广播地址
le_softc是一个数组,因为这里可以有多个以太网接口
结构ifnet中为每个接口收集的统计量如下图所示:
下图所示的是SNMP接口表(ifTable)中的一个接口项对象(ifEntry),它包含在每个接口的ifnet结构中
SNMP变量 | ifnet成员 | 说明 |
---|---|---|
ifIndex | if_index | 唯一地标识接口 |
ifDescr | if_name | 接口的文本名称 |
ifType | if_type | 接口的类型(例如以太网、S L I P等等) |
ifMtu | if_mtu | 接口的MTU(字节) |
ifSpeed | (看正文) | 接口的正常速率(每秒比特) |
ifPhyAddress | ac_enaddr | 媒体地址(来自结构arpcom) |
ifAdminStatus | (看正文) | 接口的期望状态(IFF_UP标志) |
ifOperStatus | if_flags | 接口的操作状态(IFF_UP标志) |
ifLastChange | (看正文) 上一次统计改变时间 | |
ifInOctets | if_ibytes | 输入的字节总数 |
ifInUcastPkts | if_ipackets -if_imcasts | 输入的单播分组数 |
ifInNUcastPkts | if_imcasts | |
ifInDiscards | if_iqdrops | 因为实现的限制而丢弃的分组数 |
ifInErrors | if_ierrors | 差错的分组数 |
ifInUnknownProtos | if_noproto | 指定为未知协议的分组数 |
ifOutOctets | if_obytes | 输出字节数 |
ifOutUcastPkts | if_opackets-if_omcasts | 输出的单播分组数 |
ifOutNUcastPkts | if_omcasts | 输出的广播或多播分组数 |
ifOutDiscards | if_snd.ifq_drops | 因为实现的限制而丢失的输出分组数 |
ifOutErrors | if_oerrors | 因为差错而丢失的输出分组数 |
ifOutQLen | if_snd.ifq_len | 输出队列长度 |
ifSpecific | n/a | 媒体专用信息的SNMP对象ID(未实现) |
Net/3以太网设备驱动程序都遵循同样的设计。对于大多数Unix设备驱动程序来说,都是这样,因为写一个新接口卡的驱动程序总是在一个已有的驱动程序的基础上修改而来的。在本节,我们简要的概述一下以太网的标准和一个以太网驱动程序的设计。
下图说明了一个IP分组的以太网封装
以太网帧包括48bit的目标地址和源地址,接下来是一个16bit的类型字段,它标识这个帧所携带的数据的格式。对于IP分组,类型是0x0800(2048)。帧的最后是一个32bit的CRC,用来检测帧中的差错
我们用48bit的以太网地址作为硬件地址。IP地址到硬件地址之间的转换用ARP协议,硬件地址到IP地址的转换用RARP协议。
以太网地址有两种类型,单播和多播。一个单播地址描述一个单一的以太网接口,而一个多播地址描述一组以太网接口。一个以太网广播地址是一个所有接口都接收的多播。以太网单播地址由设备的厂商分配,也有一些设备的地址允许用软件改变。
下图列举了以太网接口的数据结构和函数:
上图左上角显示的是OSI无连接网络层(clnl)协议、IP和ARP输入队列===>也就是说,ether_input函数要将以太网帧分用到多个协议队列中。
我们从以太网帧的接收开始。现在,假设硬件已初始化并且系统已完成配置,当接口产生一个中断时,leintr被调用。在正常操作中,一个以太网接口接收发送到它的单播地址和以太网广播地址的帧。当一个完整的帧可用时,接口就产生一个中断,并且内核调用leintr。
leintr检测硬件,并且如果有一个帧到达,就调用leread把这个帧从接口转移到另一个mbuf中(用m_devget)。如果硬件报告一个帧已完成传输或者发现一个差错,则leintr更新相应的接口统计,复位这个硬件,并调用restart来传输另一个帧
所有以太网设备驱动程序将它们接收到的帧传递给ether_input做进一步的处理。设备驱动程序构造的mbuf链不包括以太网首部,以太网首部ether_header作为一个独立的参数传递给ether_input。ether_header如下:
//4.4BSD-Lite\usr\src\sys\netinet\if_ether.h
/*
* Structure of a 10Mb/s Ethernet header.
*/
struct ether_header {
u_char ether_dhost[6];
u_char ether_shost[6];
u_short ether_type;
};
函数leread的开始是由leintr传给它的一个连续的内存缓冲区,并且构造了一个ether_header结构和一个mbuf链。这个链表存储来自以太网帧的数据。leread还将输入帧传给BPF。
// 4.4BSD-Lite\usr\src\sys\hp300\dev\if_le.c leread(int unit, char *buf, int len) { register struct le_softc *le = &le_softc[unit]; register struct ether_header *et; struct mbuf *m; int off, resid, flags; le->sc_if.if_ipackets++; et = (struct ether_header *)buf; et->ether_type = ntohs((u_short)et->ether_type); /* adjust input length to account for header and CRC */ len = len - sizeof(struct ether_header) - 4; if (len <= 0) { if (ledebug) log(LOG_WARNING, "le%d: ierror(runt packet): from %s: len=%d\n", unit, ether_sprintf(et->ether_shost), len); le->sc_runt++; le->sc_if.if_ierrors++; return; } flags = 0; if (bcmp((caddr_t)etherbroadcastaddr, (caddr_t)et->ether_dhost, sizeof(etherbroadcastaddr)) == 0) flags |= M_BCAST; if (et->ether_dhost[0] & 1) flags |= M_MCAST; #if NBPFILTER > 0 /* * Check if there's a bpf filter listening on this interface. * If so, hand off the raw packet to enet. */ if (le->sc_if.if_bpf) { bpf_tap(le->sc_if.if_bpf, buf, len + sizeof(struct ether_header)); /* * Keep the packet if it's a broadcast or has our * physical ethernet address (or if we support * multicast and it's one). */ if ( #ifdef MULTICAST (flags & (M_BCAST | M_MCAST)) == 0 && #else (flags & M_BCAST) == 0 && #endif bcmp(et->ether_dhost, le->sc_addr, sizeof(et->ether_dhost)) != 0) return; } #endif /* * Pull packet off interface. Off is nonzero if packet * has trailing header; m_devget will then force this header * information to be at the front, but we still have to drop * the type and length which are at the front of any trailer data. */ m = m_devget((char *)(et + 1), len, off, &le->sc_if, 0); if (m == 0) return; m->m_flags |= flags; ether_input(&le->sc_if, et, m); }
leread(int unit, char *buf, int len)
{
struct le_softc *le = &le_softc[unit];
struct ether_header *et;
struct mbuf *m;
int off, resid, flags;
le->sc_if.if_ipackets++;
et = (struct ether_header *)buf;
et->ether_type = ntohs((u_short)et->ether_type);
/* adjust input length to account for header and CRC */
len = len - sizeof(struct ether_header) - 4;
if (len <= 0) {
if (ledebug)
log(LOG_WARNING,
"le%d: ierror(runt packet): from %s: len=%d\n",
unit, ether_sprintf(et->ether_shost), len);
le->sc_runt++;
le->sc_if.if_ierrors++;
return;
}
flags = 0;
if (bcmp((caddr_t)etherbroadcastaddr,
(caddr_t)et->ether_dhost, sizeof(etherbroadcastaddr)) == 0)
flags |= M_BCAST;
if (et->ether_dhost[0] & 1)
flags |= M_MCAST;
etherbroadcastaddr是一个数组,定义如下:
u_char etherbroadcastaddr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
这是C语言中定义一个48bit值的简便方法。这项技术仅在我们假设字符是8bit值时才起作用-----ANSI C并不保证这一点。
bcmp比较etherbroadcastaddr和ether_dhost,如果相同,则设置标志M_BCAST。
一个以太网多播地址由这个地址的首字节的低位比特来标识,如下图所示:
并不是所有以太网多播帧都是IP多播数据报,并且IP必须进一步检测这个分组。
如果这个地址的多播比特被置位,在mbuf首部中设置M_MCAST。检测的顺序是重要的:首先ether_input将这个48bit地址和以太网广播地址相比较,如果不同,则检测标识以太网多播地址的首字节的低位比特。
当一个接口带有BPF时,它可以配置为运行在混淆模式,并且接口网络上出现的所有以太网帧,而不是通常由硬件接收的帧的子集。如果分组发送给一个不与此接口地址匹配的单播地址,则被leread丢弃。
/* * 检查是否有一个bpf滤波器在这个接口上监听。如果是这样,就把原始数据包交给enet。 */ if (le->sc_if.if_bpf) { bpf_tap(le->sc_if.if_bpf, buf, len + sizeof(struct ether_header)); /* * 如果数据包是广播或具有我们的物理以太网地址(或者如果我们支持多播且它是一个),则保留该数据包。 */ if ( #ifdef MULTICAST (flags & (M_BCAST | M_MCAST)) == 0 && #else (flags & M_BCAST) == 0 && #endif bcmp(et->ether_dhost, le->sc_addr, sizeof(et->ether_dhost)) != 0) return; }
m = m_devget((char *)(et + 1), len, off, &le->sc_if, 0);
if (m == 0)
return;
m->m_flags |= flags;
ether_input(&le->sc_if, et, m);
如下,函数ether_input的参数有:
任何到达不工作接口的分组将被丢弃。可能没有为此接口配置一个协议地址,或者接口可能被ifconfig显示的禁用了
// F:\source\4.4BSD-Lite\usr\src\sys\net\if_ethersubr.c
void
ether_input(struct ifnet *ifp, struct ether_header *eh, struct mbuf *m)
{
register struct ifqueue *inq;
register struct llc *l;
struct arpcom *ac = (struct arpcom *)ifp;
int s;
if ((ifp->if_flags & IFF_UP) == 0) {
m_freem(m);
return;
}
ifp->if_lastchange = time;
ifp->if_ibytes += m->m_pkthdr.len + sizeof (*eh);
if (bcmp((caddr_t)etherbroadcastaddr, (caddr_t)eh->ether_dhost,
sizeof(etherbroadcastaddr)) == 0)
m->m_flags |= M_BCAST;
else if (eh->ether_dhost[0] & 1)
m->m_flags |= M_MCAST;
if (m->m_flags & (M_BCAST|M_MCAST))
ifp->if_imcasts++;
链路层分用
switch (eh->ether_type) {
#ifdef INET
case ETHERTYPE_IP:
schednetisr(NETISR_IP);
inq = &ipintrq;
break;
case ETHERTYPE_ARP:
schednetisr(NETISR_ARP);
inq = &arpintrq;
break;
#endif
分组排队
s = splimp();
if (IF_QFULL(inq)) {
IF_DROP(inq);
m_freem(m);
} else
IF_ENQUEUE(inq, m);
splx(s);
我们现在查看以太网帧的输出,当一个网络层协议,比如IP,调用此接口ifnet结构中指定的函数if_output时,开始处理输出。所有以太网设备的if_output是ether_output。ether_output用14字节以太网首部封装一个以太网帧的数据部分,并将它放置到接口的发送队列中。这个函数比较大,我们分四个部分来说明:
int ether_output(struct ifnet *ifp, struct mbuf *m0, struct sockaddr *dst, struct rtentry *rt0)
{
short type;
int s, error = 0;
u_char edst[6];
register struct mbuf *m = m0;
register struct rtentry *rt;
struct mbuf *mcopy = (struct mbuf *)0;
register struct ether_header *eh;
int off, len = m->m_pkthdr.len;
struct arpcom *ac = (struct arpcom *)ifp;
#define senderr(e) { error = (e); goto bad;}
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING))
senderr(ENETDOWN);
ifp->if_lastchange = time;
主机路由:
if (rt = rt0) {
if ((rt->rt_flags & RTF_UP) == 0) {
if (rt0 = rt = rtalloc1(dst, 1))
rt->rt_refcnt--;
else
senderr(EHOSTUNREACH);
}
网关路由:
if (rt->rt_flags & RTF_GATEWAY) {
if (rt->rt_gwroute == 0)
goto lookup;
if (((rt = rt->rt_gwroute)->rt_flags & RTF_UP) == 0) {
rtfree(rt); rt = rt0;
lookup: rt->rt_gwroute = rtalloc1(rt->rt_gateway, 1);
if ((rt = rt->rt_gwroute) == 0)
senderr(EHOSTUNREACH);
}
}
避免ARP泛洪:
if (rt->rt_flags & RTF_REJECT)
if (rt->rt_rmx.rmx_expire == 0 ||
time.tv_sec < rt->rt_rmx.rmx_expire)
senderr(rt == rt0 ? EHOSTDOWN : EHOSTUNREACH);
IP输出:
switch (dst->sa_family) { #ifdef INET case AF_INET: if (!arpresolve(ac, rt, m, dst, edst)) return (0); /* if not yet resolved */ /* If broadcasting on a simplex interface, loopback a copy */ if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX)) mcopy = m_copy(m, 0, (int)M_COPYALL); off = m->m_pkthdr.len - m->m_len; type = ETHERTYPE_IP; break; #endif #ifdef NS case AF_NS: type = ETHERTYPE_NS; bcopy((caddr_t)&(((struct sockaddr_ns *)dst)->sns_addr.x_host), (caddr_t)edst, sizeof (edst)); if (!bcmp((caddr_t)edst, (caddr_t)&ns_thishost, sizeof(edst))) return (looutput(ifp, m, dst, rt)); /* If broadcasting on a simplex interface, loopback a copy */ if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX)) mcopy = m_copy(m, 0, (int)M_COPYALL); break;
显示以太网输出:
case AF_UNSPEC:
eh = (struct ether_header *)dst->sa_data;
bcopy((caddr_t)eh->ether_dhost, (caddr_t)edst, sizeof (edst));
type = eh->ether_type;
break;
未识别的地址族类:
default:
printf("%s%d: can't handle af%d\n", ifp->if_name, ifp->if_unit,
dst->sa_family);
senderr(EAFNOSUPPORT);
以太网首部
if (mcopy) (void) looutput(ifp, mcopy, dst, rt); /* * Add local net header. If no space in first mbuf, * allocate another. */ M_PREPEND(m, sizeof (struct ether_header), M_DONTWAIT); if (m == 0) senderr(ENOBUFS); eh = mtod(m, struct ether_header *); type = htons((u_short)type); bcopy((caddr_t)&type,(caddr_t)&eh->ether_type, sizeof(eh->ether_type)); bcopy((caddr_t)edst, (caddr_t)eh->ether_dhost, sizeof (edst)); bcopy((caddr_t)ac->ac_enaddr, (caddr_t)eh->ether_shost, sizeof(eh->ether_shost));
这时,mbuf包含一个除32bit CRC以外的完整以太网帧,CRC由以太网硬件在传输时计算。这时,需要先用如下代码对设备要传输的帧进行排队
s = splimp(); /* * Queue message on interface, and start output if interface * not yet active. */ if (IF_QFULL(&ifp->if_snd)) { IF_DROP(&ifp->if_snd); splx(s); senderr(ENOBUFS); } IF_ENQUEUE(&ifp->if_snd, m); if ((ifp->if_flags & IFF_OACTIVE) == 0) (*ifp->if_start)(ifp); splx(s); ifp->if_obytes += len + sizeof (struct ether_header); if (m->m_flags & M_MCAST) ifp->if_omcasts++; return (error); bad: if (m) m_freem(m); return (error);
函数lestart从接口输出队列中取出排队的帧,并交给LANCE以太网卡发送。如果设备空闲,调用此函数开始发送帧。
如果设备忙,当它完成了当前帧的传输时产生一个中断。设备调用lestart来退队并传输下一帧。一旦开始,协议层不再用调用lestart来排队帧,因为驱动程序不断退队并传输帧,直到队列为空为止。
下面分组中假设已经调用splimp来阻塞所有设备中断。
接口必须初始化
lestart(struct ifnet *ifp)
{
register struct le_softc *le = &le_softc[ifp->if_unit];
register struct letmd *tmd;
register struct mbuf *m;
int len;
if ((le->sc_if.if_flags & IFF_RUNNING) == 0)
return (0);
将帧从输出队列中退队:
tmd = &le->sc_r2->ler2_tmd[le->sc_tmd];
do {
if (tmd->tmd1 & LE_OWN) {
le->sc_xown2++;
return (0);
}
IF_DEQUEUE(&le->sc_if.if_snd, m);
if (m == 0)
return (0);
传输帧并传递给BPF:
len = leput(le->sc_r2->ler2_tbuf[le->sc_tmd], m);
#if NBPFILTER > 0
/*
* If bpf is listening on this interface, let it
* see the packet before we commit it to the wire.
*/
if (ifp->if_bpf)
bpf_tap(ifp->if_bpf, le->sc_r2->ler2_tbuf[le->sc_tmd],
len);
#endif
如果设备准备好,重复发送多帧:
} while (++le->sc_txcnt < LETBUF);
将设备标记为忙:
le->sc_if.if_flags |= IFF_OACTIVE;
return (0);
ioctl系统调用提供一个通用命令接口,一个进程用它来访问一个设备的标准系统调用所不支持的特性。其原型为:
int ioctl(int fd, unsigned long com, ...);
第一列显示的符号常量标识 ioctl命令(第二个参数, com)。第二列显示传递给第一列所显示的命令的系统调用的第三个参数的类型。第三列是实现这个命令的函数的名称。
系统调用ioctl将上上图所列的5种命令传递给下面所示的ifioctl函数。
int ifioctl(so, cmd, data, p) struct socket *so; int cmd; caddr_t data; struct proc *p; { register struct ifnet *ifp; register struct ifreq *ifr; int error; switch (cmd) { case SIOCGIFCONF: case OSIOCGIFCONF: return (ifconf(cmd, data)); }
ifr = (struct ifreq *)data;
ifp = ifunit(ifr->ifr_name);
if (ifp == 0)
return (ENXIO);
switch (cmd) {
default:
if (so->so_proto == 0)
return (EOPNOTSUPP);
#ifndef COMPAT_43
return ((*so->so_proto->pr_usrreq)(so, PRU_CONTROL,
cmd, data, ifp));
ifconf为进程提供一个标准的方法来发现一个系统中的接口和配置的地址。
// F:\source\4.4BSD-Lite\usr\src\sys\net\if.h struct ifreq { #define IFNAMSIZ 16 char ifr_name[IFNAMSIZ]; /* if name, e.g. "en0" */ union { struct sockaddr ifru_addr; struct sockaddr ifru_dstaddr; struct sockaddr ifru_broadaddr; short ifru_flags; int ifru_metric; caddr_t ifru_data; } ifr_ifru; #define ifr_addr ifr_ifru.ifru_addr /* address */ #define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-to-p link */ #define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ #define ifr_flags ifr_ifru.ifru_flags /* flags */ #define ifr_metric ifr_ifru.ifru_metric /* metric */ #define ifr_data ifr_ifru.ifru_data /* for use by interface */ };
struct ifconf {
int ifc_len; /* size of associated buffer */
union {
caddr_t ifcu_buf;
struct ifreq *ifcu_req;
} ifc_ifcu;
#define ifc_buf ifc_ifcu.ifcu_buf /* buffer address */
#define ifc_req ifc_ifcu.ifcu_req /* array of structures returned */
};
下图中,左边的数据在内核中,而右边的数据在一个进程中,我们用这个图来讨论ifconf函数:
int ifconf(int cmd, caddr_t data)
{
register struct ifconf *ifc = (struct ifconf *)data;
register struct ifnet *ifp = ifnet;
register struct ifaddr *ifa;
register char *cp, *ep;
struct ifreq ifr, *ifrp;
int space = ifc->ifc_len, error = 0;
ifrp = ifc->ifc_req;
ep = ifr.ifr_name + sizeof (ifr.ifr_name) - 2;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。