所谓接口,就是指在一个特定网络上硬件与设备驱动器之间的接口。BSD设计将网络协议和连接到一个系统的网络设备的驱动器间提供一个与硬件无关的编程接口。
1、ifnet结构
结构ifnet中包含所有接口的通用信息。在系统初始化期间,分别为每个网络设备分配一个独立的ifnet结构。每个ifnet结构有一个列表,它包含这个设备的一个或多个协议地址。函数if_attach在系统初始化期间构造这个链表。if_addrlist指向这个接口的ifaddr结构列表。每个ifaddr结构存储一个要用这个接口通信的协议的地址信息。
结构ifnet比较大,我们分为部分来说明:
Ø 实现信息
Ø 硬件信息
Ø 接口统计
Ø 函数指针
Ø 输出队列
struct ifnet { /*实现信息*/ char *if_name; /* name, e.g. ``en'' or ``lo'' *//* 字符串,标识接口的类型 */ struct ifnet *if_next; /* all struct ifnets are chained *//* 把所有接口的ifnet结构/*链接成一个链表,if_attach在系统初始化期间构造这个链表 */ Struct ifaddr *if_addrlist;/* linked list of addresses per if *//* 指向这个接口的ifaddr结构列表 */ int if_pcount; /* number of promiscuous listeners *//* 监听者的数目 */ caddr_t if_bpf; /* packet filter structure */ /* 分组过滤器结构 */ u_short if_index; /* numeric abbreviation for this if *//* 在内核中唯一地标识这个接口 */ short if_unit; /* sub-unit for lower level driver */ /* 标识多个相同类型的实例 */ short if_timer; /* time 'til if_watchdog called */ /* 以秒为单位记录时间,直到内核为此接口调用函数if_watchdog为止 */ Short if_flags; /* up/down, broadcast, etc. */ /* 接口的操作状态和属性,譬如接口正在工作和用于广播 */ struct if_data { /*硬件信息*/ u_char ifi_type; /* ethernet, tokenring, etc *//* 指明接口支持的硬件地址类型,如以太网,令牌环 */ u_char ifi_addrlen; /* media address length *//* 数据链路地址的长度 */ u_char ifi_hdrlen; /* media header length */ /* 由硬件附加给任何分组的首部的长度 */ u_long ifi_mtu; /* maximum transmission unit *//* 接口在一次输出操作中能传输的最大数据单元的字节数,如以太网是1500 */ u_long ifi_metric; /* routing metric (external only) *//* routing metric,通常为0 */ u_long ifi_baudrate; /* linespeed *//* 指定接口的传输速率 */只有SLIP接口才设置它 对于ifi_addrlen和ifi_hdrlen,例如,以太网有一个长度为6字节的地址和一个长度为14字节的首部。 /* 接口统计 */ u_long ifi_ipackets; /* packets received on interface */ u_long ifi_ierrors; /* input errors on interface */ u_long ifi_opackets; /* packets sent on interface */ u_long ifi_oerrors; /* output errors on interface */ u_long ifi_collisions; /* collisions on csma interfaces */ u_long ifi_ibytes; /* total number of octets received */ u_long ifi_obytes; /* total number of octets sent */ u_long ifi_imcasts; /* packets received via multicast */ u_long ifi_omcasts; /* packets sent via multicast */ u_long ifi_iqdrops; /* dropped on input, this interface */仅被SLIP设备驱动访问 u_long ifi_noproto; /* destined for unsupported protocol */ struct timeval ifi_lastchange;/* last updated */记录任何统计改变的最近时间 } if_data; /* 函数指针,在系统初始化时,每个设备驱动程序初始化它自己的ifnet结构 */ int (*if_init) /* init routine */初始化接口 __P((int)); int (*if_output) /* output routine (enqueue) */对要传输的输出分组进行排队 __P((struct ifnet *, struct mbuf *, struct sockaddr *, struct rtentry *)); int (*if_start) /* initiate output routine */启动分组的传输 __P((struct ifnet *)); int (*if_done) /* output complete routine */传输完成后的清除(未用) __P((struct ifnet *)); /* (XXX not used; fake prototype) */ int (*if_ioctl) /* ioctl routine */处理I/O控制命令 __P((struct ifnet *, u_long, caddr_t)); int (*if_reset) /*复位接口设备*/ __P((int)); /* new autoconfig will permit removal */ int (*if_watchdog) /* timer routine */周期性接口例程 __P((int)); /* 输出队列 */ struct ifqueue { struct mbuf *ifq_head;/*指向队列的第一个分组(下一个要输出的分组)*/ struct mbuf *ifq_tail;/*指向队列最后一个分组*/ int ifq_len;/*当前队列中分组的数目*/ int ifq_maxlen;/*队列中允许的缓存最大个数*/ int ifq_drops;/*统计因为队列满而丢弃的分组数*/ } if_snd; /* output queue */ };
2、ifaddr结构
下面我们要看的下一个结构式接口地址结构,ifaddr,每个接口维护一个ifaddr结构的链表,因为一些数据链路,如以太网,支持多于一个的协议。一个单独的ifaddr结构描述每个分配给接口的地址,通常每个协议一个地址。支持多地址的另一个原因是很多协议,包括TCP/IP,支持为单个物理接口指派多个地址。虽然Net/3支持这个特性,但很多TCP/IP实现并不支持。
struct ifaddr {
struct sockaddr *ifa_addr; /* address of interface */指向接口的一个协议地址
struct sockaddr *ifa_dstaddr; /* other end of p-to-p link */指向一个点对点链路上的另一端的接口协议地址或指向一个广播网中分配给接口的广播地址(如以太网)
#define ifa_broadaddr ifa_dstaddr /* broadcast address interface */
struct sockaddr *ifa_netmask; /* used to determine subnet */指向一个位掩码,地址中表示网络部分的比特在掩码中被设置为1,地址中表示主机的部分被设置为0
struct ifnet *ifa_ifp; /* back-pointer to interface */指回接口的ifnet结构的指针
struct ifaddr *ifa_next; /* next address for interface */接口的下一个地址
void (*ifa_rtrequest)(); /* check or clean routes (+ or -)'d */支持接口的路由查找
u_short ifa_flags; /* mostly rt_flags for cloning */支持接口的路由查找
short ifa_refcnt; /* extra to malloc for link info */统计对结构ifaddr的引用,宏IFAFREE仅在引用计数将到0时才释放这个结构,结构ifaddr使用引用计数是因为接口和路由数据结构共享这个结构
int ifa_metric; /* cost of going out this interface */支持接口的路由查找
#ifdef notdef
struct rtentry *ifa_rt; /* XXXX for ROUTETOIF ????? */
#endif
};
3、sockaddr结构
一个接口的编码信息不仅仅只包括一个主机地址。Net/3在通用的sockaddr结构中维护主机地址、广播地址和网络掩码。通过使用一个通用的结构,将硬件与协议专用的地址细节相对于接口层隐藏起来。
如下,早期BSD版的定义——结构osockaddr
struct osockaddr {
u_short sa_family; /* address family *//* 地址族,如AF_INET */
char sa_data[14]; /* up to 14 bytes of direct address */ /* 具体地址数组 */
};
struct sockaddr {
u_char sa_len; /* sockaddr的长度 */
u_char sa_family; /* 地址族,如AF_INET */
char sa_data[14]; /* 具体地址数组 */
};
sa_family |
协议 |
AF_INET |
Internet |
AF_ISO,AF_OSI |
OSI |
AF_UNIX |
Unix |
AF_ROUTE |
路由表 |
AF_LINK |
数据链路 |
AF_UNSPEC |
|
SA_FAMILY 常量
4、ifnet和ifaddr的专用化
结构ifnet和ifaddr包含适用于所有网络接口和协议地址的通用信息。为了容纳其他设备和协议专用信息,每个设备定义了并且每个协议分配了一个专用化版本的ifnet和ifaddr结构。这些专用化的结构总是包含一个ifnet或ifaddr结构作为它们的第一个成员,这样无须考虑其他专用信息就能访问这些公共信息。
多数设备驱动程序通过分配一个专用化的ifnet结构的数组来处理同一类型的多个接口,但其他设备(例如环回设备)仅处理一个接口。如下图是我们的例子接口的专用化ifnet结构的组织。
内核通过分配一个ifaddr结构和两个sockaddr_dl结构(一个是链路层地址本身,一个是地址掩码)来构造一个链路层地址。上图显示的是一个带有一个链路层地址、一个internet地址和一个OSI地址的以太网接口。
5、网络初始化概述
网络初始函数从main开始显示:
70-96 cpu_startup 查找并初始化所有连接到系统的硬件设备,包括任何网络接口
97-174 在内核初始化硬件设备后,它调用包含在pdevinit数组中的每个pdev_attach函数
175-234 ifinit和domaininit完成网络接口和协议的初始化,并且scheduler开始内核进程调度。
有些设备,例如SLIP和环回接口,完全用软件来实现。这些伪设备用存储在全局pdevinit数组中的一个pdevinit结构来表示。在内核配置期间构造这个数组。如上所示:
120-123 对于SLIP和环回接口,在结构pdevinit中,pdev_attach分别被设置为slattach和loopattach。当调用这个attach函数时,pedev_count作为传递的唯一参数,它指定创建的设备个数。只有一个环回设备被创建,但如果管理员适当配置SLIP项可能有多个SLIP设备被创建。
6、以太网、SLIP、环回初始化
以太网初始化:
作为cpu_startup的一部分,内核查找任何连接的网络设备。一旦一个设备被识别,一个设备专用的初始化函数就被调用。如下图是要讨论的3个例子接口的初始化函数。
设备 |
初始化函数 |
LANCE以太网 |
Leattach |
SLIP |
Slattach |
环回 |
Loopattach |
每个设备驱动程序为一个网络接口初始化一个专用化的ifnet结构,并调用if_attach把这个结构插入到接口链表中。如下图的结构le_softc是我们的例子以太网驱动程序的专用化ifnet结构
(1)le_softc结构:
69-95 在if_le.c中声明了一个le_softc结构(有NLE成员)的数组。每个结构的第一个成员是sc_ac,一个arpcom结构,它对于所有以太网接口都是通用的,接下来是设备专用成员。宏sc_if和sc_addr简化了对结构ifnet及存储在结构arpcom(sc_ac)中的以太网地址的访问,如下图所示。
(2)arpcom 结构:
95-101 结构arpcom的第一个成员ac_if是一个ifnet结构,如上图所示。ac_enaddr是以太网硬件地址,它是在cpu_startup期间内核检测设备时由LANCE设备驱动程序从硬件上复制的。对于我们的例子驱动程序,这发生在函数leattach中(如下图)。ac_ipaddr是上一个分配给此设备的IP地址。ac_multiaddrs是一个用结构ether_multi表示的以太网多播地址的列表。ac_multicnt统计这个列表的项数。
下图所示的是LANCE以太网驱动程序的初始化代码。
106-115 内核在系统中每发现一个LANCE卡都调用一次leattach。
Le指向此卡的专用化ifnet结构(4、ifnet和ifaddr专用化的第一个图),ifp指向这个结构的第一个 sc_if,一个通用的ifnet结构。
(3)从设备复制硬件地址
126-137 对于LANCE设备,由厂商指派的以太网地址在这个循环中以每次半个字节(4bit)从设备复制到sc_addr.
(4)初始化ifnet结构
150-157 leattach从hp_device结构把设备单元号复制到if_unit来标识同类型的多个接口。这个设备的if_name是“le”:if_mtu为1500字节(ETHERMTU),以太网的最大传输单元;if_inti、if_reset、if_ioctol、if_output和it_start都指向控制网络接口的通用函数的设备专用实现。
158 所有的以太网设备都支持IFF_BROADCAST。LANCE设备不接收它自己发送的数据,因此被设置为IFF_SIMPLEX。支持多播的设备和硬件还要设置IFF_MULTICAST。
159-162 bpfattach登记有BPF的接口。函数if_attach把初始化了的ifnet结构插入到接口的链表中。