Unix/Linux编程:Xinu的接口层(链路层)

引用

接口层包括在本地网上发送和接收分组的硬件与软件。

我们用术语设备驱动程序来表示与硬件以及接口(包括网络接口)通信的软件,网络接口是指在一个特定网络上硬件与设备驱动器之间的接口

Net/3接口层试图在网络协议和连接到一个系统的网络设备的驱动器间提供一个与硬件无关的编程接口。这个接口层为所有的设备提供以下支持

  • 一套精心设备的接口函数
  • 一套标准的统计与控制表示
  • 一个与设备无关的存储协议地址的方法
  • 一个标准的输出分组的排队方法

这里不要求接口层提供可靠的分组传输,仅要求提供最大努力的服务。更高层的协议必须弥补这种可靠性缺陷

在这里插入图片描述

代码介绍

通用接口结构和初始化代码可在三个头文件和两个 C文件中找到。在本章说明的设备专用初始化代码可在另外三个C文件中找到。所有的8个文件都列于图3-1中。
在这里插入图片描述

全局变量

在这里插入图片描述

SNMP变量

Net/3内核收集了大量的各种联网统计。在大多数章节中,我们都要总结这些统计并说明它们与定义在简单网络管理信息库SNMP中的标准TCP/IP信息与统计之间的关系。SNMP被组织成如下图所示的10个不同的信息组
在这里插入图片描述
Net/3并不包括一个SNMP代理。一个针对Net/3的SNMP代理是作为一个进程来实现的

内核在一个全局变量中保持对某些统计信息的跟踪。当内核在运行时,一个进程 可以检查这些统计。

ifnet层

结构ifbnet中饭包含所有接口的通用信息。在系统初始化期间,分别为每个网络设备分配一个独立的ifnet结构。每个ifnet结构有一个列表,它包含这个设备的一个或者多个协议地址。下图说明了一个接口与它的地址之间的关系
在这里插入图片描述
上图显示了三个存放在ifaddr结构中的协议地址。虽然一些物理接口,比如SLIP,仅支持一个协议;而其他接口,比如以太网,支持多个协议并需要多个地址。比如,一个系统可能使用一个以太网接口同时用于Internet和OSI两个协议。一个类型字段标识每个以太网帧的内容,并且因为Internet和OSI协议使用不同的编制方式,以太网接口必须有一个Internet地址和一个OSI地址。所有的地址使用一个链表连接起来(上图右侧),并且每个结构包含一个回值指针指向相关的ifnet结构(上图左侧)

结构ifnet比较大,我们分五个部分来说明

  • 实现信息
  • 硬件信息
  • 接口统计
  • 函数指针
  • 输出队列

如下所示的是包含在结构ifnet中的实现信息
在这里插入图片描述

  • 如下代码,if_next把所有接口的ifnet结构连接成一个链表。函数if_attach在系统初始化期间构造这个链表。if_addrlist指向这个接口ifaddr结构列表(图3-5):每个ifaddr结构存储一个要用到这个接口通信的协议的地址信息
struct ifnet {
	struct	ifnet *if_next;		/* all struct ifnets are chained */
	struct	ifaddr *if_addrlist;	/* linked list of addresses per if */
  • if_name是一个短字符串,用于标识接口的类型;而if_unit标识多个相同类型的实例。比如,一个系统有两个SLIP接口,每个接口都有一个if_name,包含两字节的"s1"和一个if_unit。对于第一个接口,if_unit为0;对于第二个接口,if_unit为1。if_index在内核中唯一的标识这个接口,这在sysctl系统调用以及路由域中要用到
	char	*if_name;		/* name, e.g. ``en'' or ``lo'' */
	short	if_unit;		/* sub-unit for lower level driver */
	u_short	if_index;		/* numeric abbreviation for this if  */
  • if_flags表明接口的操作系统和属性。一个进程能够检测所有的标识,但不能改变在图3-7中“内核专用”列中作了记号的标志
    • IFF_BROADCAST(广播网)和IFF_POINTOPOINT(点对点)标志是互斥的。
    • 设备专用标志 (IFF_LINKx)对于一个依赖这个设备的进程可能是可修改的,也
      可能是不可修改的

在这里插入图片描述

  • 接口时钟字段if_timer以秒为单位记录时间,直到内核为此接口调用函数if_watchdog为止。这个函数用于设备驱动程序定时收集接口统计,或者用于复位运行不正确的硬件
short	if_timer;		/* time 'til if_watchdog called */
  • **BSD分组过滤器(BPF)**字段if_pcountif_bpf,通过BPF,一个进程能接收由此接口传输或者接收的分组的备份
    int	if_pcount;		/* number of promiscuous listeners */
	caddr_t	if_bpf;			/* packet filter structure */

ifnet结构的下一个部分显示如下代码中,它用来描述接口的硬件特性

	struct	if_data {
/* generic interface information */
		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 */
		u_long	ifi_metric;	/* routing metric (external only) */
		u_long	ifi_baudrate;	/* linespeed */


#define	if_mtu		if_data.ifi_mtu
#define	if_type		if_data.ifi_type
#define	if_addrlen	if_data.ifi_addrlen
#define	if_hdrlen	if_data.ifi_hdrlen
#define	if_metric	if_data.ifi_metric
#define	if_baudrate	if_data.ifi_baudrate



  • 字段if_type指明接口支持的硬件地址。下图列出来net/if_types.h中几个公共的if_type值
    在这里插入图片描述
  • if_addrlen是数据链路地址的长度;if_hdrlen是由硬件附加给任何分组的首部长度。
  • if_mtu是接口传输单元的最大值:接口在一次输出操作中能传输的最大数据单元的字节数。这是控制网络和传输协议创建分组大小的重要参数。对于以太网来说,这个值是1500
  • if_metric 通常是0;其他更大的值不利于路由通过此接口。
  • if_baudrate指定接口的传输速率,只有SLIP接口才设置它

接口统计由ifnet接口中如下成员来收集

/* volatile statistics */
		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 */
		u_long	ifi_noproto;	/* destined for unsupported protocol */
		struct	timeval ifi_lastchange;/* last updated */
	}	if_data;

#define	if_ipackets	if_data.ifi_ipackets
#define	if_ierrors	if_data.ifi_ierrors
#define	if_opackets	if_data.ifi_opackets
#define	if_oerrors	if_data.ifi_oerrors
#define	if_collisions	if_data.ifi_collisions
#define	if_ibytes	if_data.ifi_ibytes
#define	if_obytes	if_data.ifi_obytes
#define	if_imcasts	if_data.ifi_imcasts
#define	if_omcasts	if_data.ifi_omcasts
#define	if_iqdrops	if_data.ifi_iqdrops
#define	if_noproto	if_data.ifi_noproto
#define	if_lastchange	if_data.ifi_lastchange
  • 当分钟组传输被共享媒体上的其他传输中断时,ifi_collisions加1。
  • if_noproto 统计由于协议不被系统或接口支持而不能处理的分组数。比如如果一个非IP分组到达一个SLIP接口的输出队列,if_noproto 加1

这些统计在NET/1中不是ifnet接口的一部分。它们被加入来支持接口的标准SNMP MIB-II变量

  • if_iqdrops 仅被SLIP设备驱动程序访问。当IF_DROP被调用时,SLIP和其他网络驱动程序把if_snd.ifq_drops加1
#define	IF_DROP(ifq)		((ifq)->ifq_drops++)
  • if_lastchange 记录任何统计改变的最近时间

结构ifnet的下一部分,如下所示,把包含指向标准接口层函数的指针,它们把设备专用的细节从网络层分离出来。每个网络接口实现这些适用于特定设备的函数

/* procedure handles */
	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 */
		__P((struct ifnet *, int, caddr_t));
	int	(*if_reset)	
		__P((int));		/* new autoconfig will permit removal */
	int	(*if_watchdog)		/* timer routine */
		__P((int));
  • 在系统初始化时,每个设备驱动程序初始化它字节的ifnet结构,包含7个函数指针。下图说明了这些通用函数(if_done不再Net/3中使用)
    在这里插入图片描述
  • 内核通过ifnet结构中的这些指针调用接口设备专用函数。比如,一个ifp执行一个ifnet结构
(*ifp->if_start)(ifp)

结构ifnet中剩下的最后一个成员是接口的输出队列,如图所示:

  • if_snd是接口输出分组队列,每个接口都有它自己的ifnet结构,即它自己的输出队列。
  • ifq_head指向队列的第一个分组(下一个要输出的分组)
  • ifq_tail指向队列最后一个分组
  • ifq_len是当前队列中的分组的数目
  • ifq_maxlen是队列中允许的缓冲的最大个数
struct	ifqueue {
		struct	mbuf *ifq_head;
		struct	mbuf *ifq_tail;
		int	ifq_len;
		int	ifq_maxlen;
		int	ifq_drops;
	} if_snd;			/* output queue */

队列作为一个mbuf链的链表来实现。ifq_drops统计因为队列满而被丢弃的分组数。下图列出了那些访问队列的宏和函数
在这里插入图片描述

// net/if.h 
// ifq是否满
// int IF_QFULL(struct ifqueue *ifq);
#define	IF_QFULL(ifq)		((ifq)->ifq_len >= (ifq)->ifq_maxlen)

// IF_DROP仅将与ifq关联的ifq_drops加1。这个名字会引起误导:调用者丢弃这个分组
// void IF_DROP(struct ifqueue *ifq);
#define	IF_DROP(ifq)		((ifq)->ifq_drops++)

// 把分组m追加到ifq队列的后面。分组通过mbuf首部中的m_nextpkt链接在一起
// void IF_ENQUEUE(struct ifqueue *ifq, struct mbuf *m);
#define	IF_ENQUEUE(ifq, m) { \
	(m)->m_nextpkt = 0; \
	if ((ifq)->ifq_tail == 0) \
		(ifq)->ifq_head = m; \
	else \
		(ifq)->ifq_tail->m_nextpkt = m; \
	(ifq)->ifq_tail = m; \
	(ifq)->ifq_len++; \
}


// 把分组m插入到ifq队列的前面
// void IF_PREPEND(struct ifqueue *ifq, struct mbuf *m);
#define	IF_PREPEND(ifq, m) { \
	(m)->m_nextpkt = (ifq)->ifq_head; \
	if ((ifq)->ifq_tail == 0) \
		(ifq)->ifq_tail = (m); \
	(ifq)->ifq_head = (m); \
	(ifq)->ifq_len++; \
}

// 从ifq队列中取走第一个分组。m指向取走的分组,若队列为空,则 m为空值
// void IF_DEQUEUE(struct ifqueue *ifq, struct mbuf *m);
#define	IF_DEQUEUE(ifq, m) { \
	(m) = (ifq)->ifq_head; \
	if (m) { \
		if (((ifq)->ifq_head = (m)->m_nextpkt) == 0) \
			(ifq)->ifq_tail = 0; \
		(m)->m_nextpkt = 0; \
		(ifq)->ifq_len--; \
	} \
}
//4.4BSD-Lite\usr\src\sbin\XNSrouted\if.c
/*
 * 丢弃队列ifq中的所有分组,例如,当一个接口被关闭了
 */
void if_qflush(struct ifqueue *ifq)
{
	struct mbuf *m, *n;

	n = ifq->ifq_head;
	while (m = n) {
		n = m->m_act;
		m_freem(m);
	}
	ifq->ifq_head = 0;
	ifq->ifq_tail = 0;
	ifq->ifq_len = 0;
}

上面这些宏经常出现在下面这样的程序语句中:

s = splimp();
if(IS_QFULL(inq)){
	IF_DROP(inq); //queue if full, drop new packet
	m_freem(m);
}else{
	IF_ENQUEUE(inq, m); // add to end of queue
splx();
}

这段代码试图把一个分组加到队列中。如果队列满,IF_DROP把ifq_drop加1,并且分组被丢弃。可靠协议如TCP会重传丢弃的分组。不可靠协议比如UDP的应用程序必须自己检测和重传数据

访问队列的语句被splimp和splx括起来,阻止网络中断,并且防止在不确定状态时网络中断服务例程访问此队列。

ifaddr结构

我们要看的下一个结构是ifaddr,如下。每个接口维护一个ifaddr结构的链表,因为一些数据链路,比如以太网,支持多于一个的协议。虽然 N e t / 3支持这个特性,但很多TCP/IP实现并不支持。

一个单独的ifaddr结构描述每个分配给接口的地址,通常每个协议一个地址。

//4.4BSD-Lite\usr\src\sbin\XNSrouted\if.h
/*
 * The ifaddr structure contains information about one address
 * of an interface.  They are maintained by the different address families,
 * are allocated and attached when an address is set, and are linked
 * together so all addresses for an interface can be located.
 */
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 */
	struct	ifnet *ifa_ifp;		/* back-pointer to interface */
	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 */
	int	ifa_metric;		/* cost of going out this interface */
#ifdef notdef
	struct	rtentry *ifa_rt;	/* XXXX for ROUTETOIF ????? */
#endif
};
  • 结构ifaddr通过ifa_next把分配给一个接口的所有地址链接起来,它还包括一个指回接口的ifnet结构的ifa_ifp。下图显示了结构ifnet与ifaddr的关系
	struct	ifnet *ifa_ifp;		/* back-pointer to interface */
	struct	ifaddr *ifa_next;	/* next address for interface */

在这里插入图片描述

  • ifa_addr指向接口的一个协议地址,而ifa_netmask指向一个位掩码,它用于选择ifa_addr中的网络部分。地址中表示网络部分的比特在掩码中被设置为1,地址中表示主机的部分被设置为0。两个地址都存放在sockadddr结构中。
struct	sockaddr *ifa_addr;	/* address of interface */
	struct	sockaddr *ifa_netmask;	/* used to determine subnet */
  • ifa_dstaddr指向一个点对点链路上的另一端的接口协议地或者执行一个广播网中分配给接口的广播地址。接口的ifnet结构中互斥的两个标志IFF_BROADCAST和IFF_POINTOPOINT
    (图3 - 7 )指示接口的类型。
struct	sockaddr *ifa_dstaddr;	/* other end of p-to-p link */
  • ifa_rtrequest、ifa_flags、ifa_refcnt、ifa_metric支持接口的路由查找
    • ifa_refcnt统计对结构ifaddr的引用。宏IFAFREE仅在引用计数降到0时才释放这个结构
	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 */
	int	ifa_metric;		/* cost of going out this interface */

sockaddr结构

一个接口的编码信息不仅仅包括一个主机地址。Net/3在通用的sockaddr结构中维护主机地址、广播地址、网络掩码。通过使用一个通用的结构,将硬件与协议专用的地址细节相对于接口层隐藏起来

//4.4BSD-Lite\usr\src\sys\sys\socket.h
/*
 * Structure used by kernel to store most
 * addresses.(当前的定义)
 */
struct sockaddr {
	u_char	sa_len;			/* total length */
	u_char	sa_family;		/* address family */
	char	sa_data[14];		/* actually longer; address value */
};


/*
 * 4.3 compat sockaddr, move to compat file later  (早期的定义,之后移除了)
 */
struct osockaddr {
	u_short	sa_family;		/* address family */
	char	sa_data[14];		/* up to 14 bytes of direct address */
};

在这里插入图片描述

  • 每个协议都有自己的地址格式,Net/3在一个sockaddr结构中处理通用的地址。sa_len指示地址的长度,sa_family指示地址的类型。下图列出了地址族常量
    在这里插入图片描述

当指明为AF_UNSPEC时,一个sockaddr的内容要根据情况而定。大多数情况下,它包含一个以太网硬件地址

  • 成员sa_data,包含一个协议相关格式的地址。sa_data定义为一个14字节的数组,但当sockaddr结构覆盖更大的内存空间时,sa_data可能会扩展到253字节。(这是C语言的一种通用技术,它允许程序员把一个结构中的最后一个成员看成是可变长的。)

  • 每个协议定义一个专用的sockaddr结构,该结构复制成员sa_len和sa_family,但按照那个协议的要求来定义sa_data。存储在sa_data中的地址是一个传输地址,它包含足够的信息来标识同一主机上的多个通信端点

  • 结构osockaddr是4.3BSD Reno版本以前的so ckaddr定义。因为在这个定义中一个地址的长度不是显式地可用,所以它不能用来写处理可变长地址的协议无关代码

ifnet和ifaddr的专用话

结构ifnet和ifaddr包含适用于所有网络接口和协议地址的通用信息。为了容纳其他设备和协议专用信息,每个设备定义了并且每个协议分配了一个专用化版本的ifnet和ifaddr结构。这些专用化的结构总是包含一个 ifnet或ifaddr结构作为它们的第一个成员,这样无须考虑其他专用信息就能访问这些公共信息

多数设备驱动程序通过分配一个专用化的ifnet结构的数组来处理同一类型的多个接口,如下图所示
在这里插入图片描述
注意,每个设备的结构以一个ifnet开始,接下来全是设备相关的数据。环回接口只声明一个ifnet结构,因为它不要求任何设备相关的数据。以太网和SLIP驱动程序的结构softc带有数组下标,因为两个设备都支持多个接口。任何给定类型的接口的最大个数均由内核建立时的配置参数来限制

结构arpcom对于所有以太网设备是通用的,并且包含地址解析协议ARP和以太网多播信息。结构le_softc包含专用于LANCE以太网驱动器的其他信息

网络初始化

有些设备,比如SLIP和环回结构,完全用软件实现。这些伪设备用存储在全局pdevinit数组中的一个pdevinit结构来表示。在内核配置期间构造这个数组。比如:

struct pdevinit pdevinit [] = {
	{slattach, 1},
	{loopattach, 1},
	{0, 0}
}

// 4.4BSD-Lite\usr\src\sys\sys\device.h
/*
 * Pseudo-device attach information (function + number of pseudo-devs).
 */
struct pdevinit {
	void	(*pdev_attach) __P((int));
	int	pdev_count;
};

对于SLIP和环回接口,在结构pdevinit中,pdev_attach分别被置为slattach和loopattach。当调用这个attach函数时,pdev_count作为传递的唯一参数,它指定创建的设备个数

网络初始化函数从main开始

//4.4BSD-Lite\usr\src\sys\kern\init_main.c
main(framep)
	void *framep;
{	
	cpu_startup();

	/* Attach pseudo-devices. */
	for (pdev = pdevinit; pdev->pdev_attach != NULL; pdev++)
		(*pdev->pdev_attach)(pdev->pdev_count);

	/*
	 * Initialize protocols.  Block reception of incoming packets
	 * until everything is ready.
	 */
	s = splimp();
	ifinit();
	domaininit();
	splx(s);


	/* The scheduler is an infinite loop. */
	scheduler();
	/* NOTREACHED */
}



  • cpu_startup查找并初始化所有连接到系统的硬件设备,包括任何网络接口
  • 在内核初始化硬件设备之后,它调用包含在pdevinit数组中的每个pdev_attach函数
  • ifinit和domaininit完成网络接口和协议的初始化,并且scheduler开始内核进程调用

以太网初始化

作为cpu_startup的一部分,内核查找任何连接的网络设备。一旦一个设备被识别,一个设备专用的初始化函数就被调用。下图显示的是我们的三个例子的初始化函数
在这里插入图片描述
每个设备驱动程序为一个网络接口初始化一个专用的ifnet结构,并调用if_attach把这个结构插入到接口链表中。

下面结构lesoftc是以太网驱动程序的专用化ifnet结构:每个结构的第一个成员是sc_ac,一个arpcom 结构,它对于所有以太网接口都是通用的,接下来是设备专用成员。宏sc_if和sc_addr简化了对结构ifnet以及存储在结构arpcom 中的以太网地址的访问

//4.4BSD-Lite\usr\src\sys\hp300\dev\if_le.c
struct	le_softc {
	struct	arpcom sc_ac;	/* common Ethernet structures */
#define	sc_if	sc_ac.ac_if	/* network-visible interface */
#define	sc_addr	sc_ac.ac_enaddr	/* hardware Ethernet address */
	...	
} le_softc[NLE];

arpcom结构:

  • arpcom 的第一个成员ac_if是一个ifnet结构。
  • ac_enaddr是以太网硬件地址,它在cpu_startup期间检测设备时由LANCE设备驱动程序从硬件上复制的。对于我们的例子驱动程序,这发生在函数 leattach中
  • ac_ipaddr是上一个分配给此设备的IP地址。一个接口可以由多个IP地址
  • ac_multiaddrs是一个用结构ether_multi表示的以太网多播地址的列表
  • ac_multicnt统计这个列表的项
//4.4BSD-Lite\usr\src\sys\netinet\if_ether.h
/*
 * Structure shared between the ethernet driver modules and
 * the address resolution code.  For example, each ec_softc or il_softc
 * begins with this structure.
 */
struct	arpcom {
	struct 	ifnet ac_if;		/* network-visible interface */
	u_char	ac_enaddr[6];		/* ethernet hardware address */
	struct	in_addr ac_ipaddr;	/* copy of ip address- XXX */
	struct	ether_multi *ac_multiaddrs; /* list of ether multicast addrs */
	int	ac_multicnt;		/* length of ac_multiaddrs list */	
};

在这里插入图片描述
下面显示了LANCE以太网驱动程序的初始化代码:

  • 内核在系统中每发现一个LANCE卡都调用一次leattach,其参数是一个指向hp_device 结构的指针,它包含了HP专用信息,因为它是专为HP工作站写的驱动程序
  • le指向专用化的ifnet结构(如上图),ifp指向这个结构的第一个成员sc_if,一个通用的ifnet结构
//4.4BSD-Lite\usr\src\sys\hp300\dev\if_le.c
leattach(struct hp_device *hd)
{
	register struct lereg0 *ler0;
	register struct lereg2 *ler2;
	struct lereg2 *lemem = 0;
	struct le_softc *le = &le_softc[hd->hp_unit];
	struct ifnet *ifp = &le->sc_if;
	char *cp;
	int i;



	/*
	 * 从设备复制地址
	 */
	cp = (char *)(lestd[3] + (int)hd->hp_addr);
	for (i = 0; i < sizeof(le->sc_addr); i++) {
		le->sc_addr[i] = (*++cp & 0xF) << 4;
		cp++;
		le->sc_addr[i] |= *++cp & 0xF;
		cp++;
	}
	printf("le%d: hardware address %s\n", hd->hp_unit,
		ether_sprintf(le->sc_addr));

	/*
	 * 初始化ifnet结构
	 */

	ifp->if_unit = hd->hp_unit;
	ifp->if_name = "le";
	ifp->if_mtu = ETHERMTU;
	ifp->if_init = leinit;
	ifp->if_reset = lereset;
	ifp->if_ioctl = leioctl;
	ifp->if_output = ether_output;
	ifp->if_start = lestart;
#ifdef MULTICAST
	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
#else
	ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX;
#endif
#if NBPFILTER > 0
	bpfattach(&ifp->if_bpf, ifp, DLT_EN10MB, sizeof(struct ether_header));
#endif
	if_attach(ifp);
	return (1);
}

  • . 从设备复制硬件地址:对于LANCE设备,以太网地址在这个循环中以每次半个字节(4bit)从设备复制到sc_addr
  • 初始化ifnet结构:
    • leattach从hp_device结构把设备单元号复制到if_unit来标识同类型的多个接口。
    • 这个设备的if_name是"le";
    • if_mut为1 5 0 0字节(ETHERMTU),以太网的最大传输单元
    • if_init 、if_reset 、if_ioctl 、if_output 、if_start 都指向控制网络接口的通用函数的设备专用实现
    • 对于if_flags :所有的以太网设备都支持IFF_BROADCAST。LANCE设备不接受它自己发送的数据,因此被设置为IFF_SIMPLEX。支持多播的设备和硬件还要设置IFF_MULTICAST
  • bpfattach登记有BPF的接口。
  • if_attach把初始化了的ifnet插入到接口的链表中

SLIP初始化

SLIP协议是一种在串行线上传送IP包的简单方法。它不提供成帧或错误控制,因此在今天使用不广泛。

依赖标准异步串行设备的SLIP接口在调用cpu_startup时初始化。当main直接通过SLIP的pdevinit结构中的指针pdev_attach调用slattach时,SLIIP伪设备被初始化。

每个SLIP接口由一个sl_softc结构来描述

在这里插入图片描述

  • 与所有的接口结构一样,sl_softc有一个ifnet结构并且后面跟着设备专用信息
  • 除了在ifnet结构中的输出队列外,一个SLIP设备还维护另一个队列,它用于低延时服务的分组
  • sc_ttyp指向关联的终端设备。
  • 指针sc_mp和sc_ep分别指向一个接受SLIP分组的缓存的第一个字节和最后一个指针。sc_mp指向下一个接收字节的地址,并在另一个字节到达时向前移动
// 4.4BSD-Lite\usr\src\sys\net\if_slvar.h
struct sl_softc {
	struct	ifnet sc_if;		/* network-visible interface */
	struct	ifqueue sc_fastq;	/* interactive output queue */
	struct	tty *sc_ttyp;		/* pointer to tty structure */
	u_char	*sc_mp;			/* pointer to next available buf char */
	u_char	*sc_ep;			/* pointer to last available buf char */
	u_char	*sc_buf;		/* input buffer */
	u_int	sc_flags;		/* see below */
	u_int	sc_escape;	/* =1 if last char input was FRAME_ESCAPE */
	long	sc_lasttime;		/* last time a char arrived */
	long	sc_abortcount;		/* number of abort esacpe chars */
	long	sc_starttime;		/* time of first abort in window */
#ifdef INET				/* XXX */
	struct	slcompress sc_comp;	/* tcp compression data */
#endif
	caddr_t	sc_bpf;			/* BPF data */
};
  • SLIP定义的4个标志显示在图3-29中
    在这里插入图片描述
  • sc_escape用于串行线的IP封装机制,而TCP首部压缩信息保留在sc_comp中
  • 指针sc_bpf指向设备SLIP设备的BPF信息

指针sl_softc 由slattach初始化,如下:

// 4.4BSD-Lite\usr\src\sys\net\if_sl.c
void slattach()
{
	register struct sl_softc *sc;
	register int i = 0;

	for (sc = sl_softc; i < NSL; sc++) {
		sc->sc_if.if_name = "sl";
		sc->sc_if.if_next = NULL;
		sc->sc_if.if_unit = i++;
		sc->sc_if.if_mtu = SLMTU;
		sc->sc_if.if_flags =
		    IFF_POINTOPOINT | SC_AUTOCOMP | IFF_MULTICAST;
		sc->sc_if.if_type = IFT_SLIP;
		sc->sc_if.if_ioctl = slioctl;
		sc->sc_if.if_output = sloutput;
		sc->sc_if.if_snd.ifq_maxlen = 50;
		sc->sc_fastq.ifq_maxlen = 32;
		if_attach(&sc->sc_if);
#if NBPFILTER > 0
		bpfattach(&sc->sc_bpf, &sc->sc_if, DLT_SLIP, SLIP_HDRLEN);
#endif
	}
}

不像leattach一次仅初始化一个接口,内核只调用一次slattach,并且初始化所有的SLIP接口。硬件设备在内核指向cpu_startup被发现时初始化,而伪设备设置main为这个pdev_atach函数时被初始化的。一个SLIP设备的if_mtu为296字节(S L M T U)。这

环回初始化

环回接口把输出分组放回到响应的输入队列中。接口没有相关联的硬件设备。环回伪设备在main通过环回接口的pdevinit结构中的pdev_attach指针直接调用loopattach时初始化。

// 4.4BSD-Lite\usr\src\sys\net\if_loop.c
void loopattach(int  n)
{
	register struct ifnet *ifp = &loif;

#ifdef lint
	n = n;			/* Highlander: there can only be one... */
#endif
	ifp->if_name = "lo";
	ifp->if_mtu = LOMTU;
	ifp->if_flags = IFF_LOOPBACK | IFF_MULTICAST;
	ifp->if_ioctl = loioctl;
	ifp->if_output = looutput;
	ifp->if_type = IFT_LOOP;
	ifp->if_hdrlen = 0;
	ifp->if_addrlen = 0;
	if_attach(ifp);
#if NBPFILTER > 0
	bpfattach(&ifp->if_bpf, ifp, DLT_NULL, sizeof(u_int));
#endif
}

环回接口没有链路首部和硬件地址,因此if_hdrlen和if_addrlen被设置为0。if_attach完成ifnet结构的初始化并且bpfattach登记有BPF的环回接口

if_attach函数

前面显示的三个接口初始化函数都调用if_attach来完成接口的ifnet结构初始化,并把这个结构插入到先前配置的接口的列表中。在if_attach中,内核也为每个接口初始化并分配一个链路层地址。下图说明了由if_attach构造的数据结构
在这里插入图片描述
上图中,if_attach被调用了三次:

  • 以一个le_softc结构为参数从leattach调用
  • 以一个sl_softc结构为参数从slattach调用
  • 以一个通用ifnet结构为参数从loopattach调用。

每次调用时,它向ifnet列表添加一个新的ifnet结构,为这个接口创建一个链路层ifaddr结构(包括两个sockaddr_dl结构),并且初始化ifnet_addrs数组中的一项

if_attach更新两个全局变量。

  • 第一个是if_index,它存放系统中的最后一个接口的索引;
  • 第二个是ifnet_addrs,它指向一个ifaddr指针的数组。这个数组的每一项都指向一个接口的链路层地址。这个数组提供对系统中每个接口的链路层地址的快速访问

初始化以后,接口仅仅配置了链路层地址。

链路层地址包含接口的一个逻辑地址和一个硬件地址。在ARP和OSI协议中要用到这个硬件地址,而一个sockadd_dl中的逻辑地址包含一个名称和这个接口在内核中的索引数值,它支持用于在接口索引和关联ifaddr结构间相互转换的表查找。

//4.4BSD-Lite\usr\src\sys\net\if_dl.h
/*
 * Structure of a Link-Level sockaddr:
 */
struct sockaddr_dl {
	u_char	sdl_len;	/* Total length of sockaddr */
	u_char	sdl_family;	/* AF_DLI */
	u_short	sdl_index;	/* if != 0, system given index for interface */
	u_char	sdl_type;	/* interface type */
	u_char	sdl_nlen;	/* interface name length, no trailing 0 reqd. */
	u_char	sdl_alen;	/* link level address length */
	u_char	sdl_slen;	/* link layer selector length */
	char	sdl_data[12];	/* minimum work area, can be larger;
				   contains both if name and ll address */
};

#define LLADDR(s) ((caddr_t)((s)->sdl_data + (s)->sdl_nlen))
  • sdl_len指明了整个地址的长度
  • sdl_family指明了地址族类,此例中为AF_LINK
  • sdl_index在内核中标识接口。上图(3-32)中的以太网接口会有一个为1的索引,SLIP接口的索引值为2,而环回接口的索引值为3.全局整数变量if_index包含的是内核最近分配的一个索引值
  • sdl_type根据这个数据链路地址的ifnet结构的成员if_type进行初始化
  • 除了一个数字索引,每个接口有一个由结构ifnet的成员if_name和if_unit组成的文本名称。比如,第一个SLIP接口叫做"s10",而第二个叫做"s11"。文本名称存储在数组sdl_data的前面,并且sdl_nlen为这个名称的字节长度(在我们的SLIP例子中为3 )。
  • Net/3不使用sdl_slen
  • 数据链路也存储在这个结构中。宏LLADDR将一个指向sockaddr_dl 结构的指针转换成一个指向这个文本字节名字的第一个字节的指针。sdl_alen是硬件地址的长度。对于一个以太网设备,48bit硬件地址位于结构sockaddr_dl 这个文本名称的前面。下图显示了一个初始化了的sockaddr_dl结构
    在这里插入图片描述

函数if_attach较长,并且由几个奇怪的赋值语句,下面分5个部分来分析这个函数

函数if_attach:分配接口索引

  • if_attach由一个参数:ifp,这是一个指向ifnet结构的指针,由网络设备驱动程序初始化。Net/3在一个链表中维护所有这些ifnet结构,全局指针ifnet指向这个链表的首部。while循环查找链表的尾部,并将链表尾部的空指针的地址存储到P中。在循环后,新ifnet结构被接到这个链表的尾部,if_index加1,并且将新索引值赋值给ifp->index。如下
//4.4BSD-Lite\usr\src\sys\net\if.c
void if_attach(struct ifnet * ifp)
{
	unsigned socksize, ifasize;
	int namelen, unitlen, masklen, ether_output();
	char workbuf[12], *unitname;
	register struct ifnet **p = &ifnet;  // head of interface list
	register struct sockaddr_dl *sdl;
	register struct ifaddr *ifa;
	static int if_indexlim = 8;  // size of idnet_addrs array
	extern void link_rtrequest();

	while (*p)   .// find end of interface list
		p = &((*p)->if_next);
	*p = ifp;
	ifp->if_index = ++if_index; // assign next index
  • 必要时调整ifnet_addrs数组的大小,如下代码:
    • 第一次调用if_attach时,数组ifnet_addrs不存在,因此要分配16(16=8<<1)项的空间。
    • 当数组满时,一个两倍大的新数组被分配,并且老数组中的项被复制到新数组中
    • if_indexlim是if_attach私有的一个静态变量。if_indexlim通过<<=操作符来更新
    • 下面的malloc和free不是同名的标准C库函数。内核版的第二个参数指明一个类型,内核中可选的诊断代码用它来检测程序错误。如果malloc的第三个参数为M_WAITOK,而且函数需要等待释放的可用内存,则阻塞调用进程。如果第三个参数为M_DONTWAIT,则当内存不可用时,函数不阻塞并返回一个空指针。
if (ifnet_addrs == 0 || if_index >= if_indexlim) {
		unsigned n = (if_indexlim <<= 1) * sizeof(ifa);
		struct ifaddr **q = (struct ifaddr **)
					malloc(n, M_IFADDR, M_WAITOK);
		if (ifnet_addrs) {
			bcopy((caddr_t)ifnet_addrs, (caddr_t)q, n/2);
			free((caddr_t)ifnet_addrs, M_IFADDR);
		}
		ifnet_addrs = q;
	}

函数if_attach:计算链路层地址大小,为接口准备一个文本名称并计算链路层地址的长度

  • 如下代码,if_attach用if_unit和if_name组装接口的名称。函数sprint_d将if_unit的数值转换成一个串并存储到workbuf中。
  • masklen是sockaddr_dl数组中sdl_data前面的信息所占用的字节数加上这个接口的文本名称的大小( unitlen + namelen)。
  • socksize 是masklen加上硬件地址长度(if_addrlen),向上舍入(ROUNDUP)为一个长整型。
  • 如果socksize小于一个sockaddr_dl结构的长度,就使用标准的sockaddr_dl结构。
  • ifasize 是一个ifaddr结构的大小加上两倍的socksize
unitname = sprint_d((u_int)ifp->if_unit, workbuf, sizeof(workbuf));
	namelen = strlen(ifp->if_name);
	unitlen = strlen(unitname);
#define _offsetof(t, m) ((int)((caddr_t)&((t *)0)->m))
	masklen = _offsetof(struct sockaddr_dl, sdl_data[0]) +
			       unitlen + namelen;
	socksize = masklen + ifp->if_addrlen;
#define ROUNDUP(a) (1 + (((a) - 1) | (sizeof(long) - 1)))
	socksize = ROUNDUP(socksize);
	if (socksize < sizeof(*sdl))
		socksize = sizeof(*sdl);
	ifasize = sizeof(*ifa) + 2 * socksize;

在函数的下一部分,if_attach分配结构并将结果连接起来,如图3-36所示:

在这里插入图片描述

  • 图3-36中,在ifaddr结构与两个soackaddr_dl结构间由一个空隙来说明它们分配在一个连续的内存中但没有定义在一个C结构中

图3-36所示的组织在出现在结构in_ifaddr中;这个结构的通用ifaddr部分中的指针指向这个结构的设备专用部分中的专用化sockaddr结构,在本例中是结构sockaddr_dl。下面代码就是这些结构的初始化

函数if_attach:分配并初始化链路层地址

(1)完成sockaddr_dl结构的初始化

  • 如下,如果有足够的内存可用,bzero把新结构清零,并且sdl指向紧接着ifnet结构的第一个sockaddr_dl。如果没有可用内存,代码被忽略
    • sdl_len 被设置为结构sockaddr_dl 的长度
    • sdl_family 被设置为AF_LINK,
    • 用if_name和unitname组成1的文本名称存放在sdl_data中,而它的长度存放在sdl_nlen
    • 接口索引被复制到sdl_index中
    • 接口类型被复制到sdl_type中
    • 分配的结构被插入到ifnet_addrs中,并通过ifa_ifp 和if_addrlist连接到结构ifnet
    • 最后,结构sockaddr_dl 用ifa_addr 连接到ifnet结构
if (ifa = (struct ifaddr *)malloc(ifasize, M_IFADDR, M_WAITOK)) {
		bzero((caddr_t)ifa, ifasize);
		sdl = (struct sockaddr_dl *)(ifa + 1);
		sdl->sdl_len = socksize;
		sdl->sdl_family = AF_LINK;
		bcopy(ifp->if_name, sdl->sdl_data, namelen);
		bcopy(unitname, namelen + (caddr_t)sdl->sdl_data, unitlen);
		sdl->sdl_nlen = (namelen += unitlen);
		sdl->sdl_index = ifp->if_index;
		sdl->sdl_type = ifp->if_type;
		ifnet_addrs[if_index - 1] = ifa;
		ifa->ifa_ifp = ifp;
		ifa->ifa_next = ifp->if_addrlist;
		ifa->ifa_rtrequest = link_rtrequest;
		ifp->if_addrlist = ifa;
		ifa->ifa_addr = (struct sockaddr *)sdl;

(2)初始化sockaddr_dl的掩码:(第二个sockaddr_dl结构是一个比特掩码,用来选择出现在第一个结构中的文本名称)

  • ifa_netmask 从结构ifaddr执行掩码结构。
  • while循环与名称对应的那些字节的每个比特都置为 1
// 初始化sockaddr_dl的掩码
sdl = (struct sockaddr_dl *)(socksize + (caddr_t)sdl);
		ifa->ifa_netmask = (struct sockaddr *)sdl;
		sdl->sdl_len = masklen;
		while (namelen != 0)
			sdl->sdl_data[--namelen] = 0xff;

下图显示的是第一个接口被if_attach连接后的结构
在这里插入图片描述
函数if_attach:以太网初始化:开始不调用ether_ifattach是因为它要把以太网硬件地址复制到if_attach分配的sockaddr_dl中

	if (ifp->if_output == ether_output)
		ether_ifattach(ifp);

接下来我们分析ether_ifattach函数,代码如下,这个函数的功能是执行对所有以太网设备通用的ifnet结构的初始化

  • 对于一个以太网设备,if_type为IFT_ETHER,硬件地址为6字节长,整个以太网首部有14字节,以太网MTU为1500(ETHERMTU)
  • for循环定位接口的链路层地址,然后初始化sockaddr_dl 中的以太网硬件地址信息。
  • 在系统初始化时,以太网地址被复制到结构arpcom中,现在被复制到链路层地址中
//4.4BSD-Lite\usr\src\sys\net\if_ethersubr.c
void
ether_ifattach(ifp)
	register struct ifnet *ifp;
{
	register struct ifaddr *ifa;
	register struct sockaddr_dl *sdl;

	ifp->if_type = IFT_ETHER;
	ifp->if_addrlen = 6;
	ifp->if_hdrlen = 14;
	ifp->if_mtu = ETHERMTU;
	for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next)
		if ((sdl = (struct sockaddr_dl *)ifa->ifa_addr) &&
		    sdl->sdl_family == AF_LINK) {
			sdl->sdl_type = IFT_ETHER;
			sdl->sdl_alen = ifp->if_addrlen;
			bcopy((caddr_t)((struct arpcom *)ifp)->ac_enaddr,
			      LLADDR(sdl), ifp->if_addrlen);
			break;
		}
}

ifinit函数

接口结构被初始化并链接在一起后,main调用ifinit,所下所示:

  • for循环遍历接口列表,并把没有被接口的attach函数设置的每个接口的输出队列的最大长度设置为10(ifqmaxlen)

输出队列的大小关键要考虑的是发送最大长度数据报的分组的个数。比如以太网,如果一个进程调用sendto发送65507字节的数据,它别分片为45个数据报片,并且每个数据报片被放进接口的输出队列。如果队列非常小,由于队列没有空间,进程可能不能发送大的数据报

  • if_slowtimo启动接口的监视计时器。当一个接口时钟到期,内核会调用这个接口的把关定时器函数。一个接口可以提前重设时钟要阻止把关定时器的调用,或者,如果不需要把关定时器函数,则可以把if_timer设置为0
//4.4BSD-Lite\usr\src\sys\net\if.c
void ifinit()
{
	register struct ifnet *ifp;

	for (ifp = ifnet; ifp; ifp = ifp->if_next)
		if (ifp->if_snd.ifq_maxlen == 0)
			ifp->if_snd.ifq_maxlen = ifqmaxlen;  // set default length
	if_slowtimo(0);
}

对于if_slowtime:

  • if_slowtime忽略if_timer为0的接口;
  • 如果if_timer不等于0,if_slowtimo把if_timer减1,并在这个时钟到达0时调用这个接口关联的if_watchdog函数。
  • 在调用if_slowtimo时,分组处理进程被splimp阻塞。
  • 返回前,if_slowtimo调用timeout,来以hz/IFNET_SLOWHZ时钟频率调度对它自己的调用。
  • hz是1s内时钟滴答数。它在系统初始化时设置,并保持不变。因为IFNET_SLOWHZ被定义为1,因此内核每hz调用一次if_slowtimo,即每秒一次
  • 函数timeout调度的函数被内核的函数callout回调
void if_slowtimo(void *arg)
{
	register struct ifnet *ifp;
	int s = splimp();

	for (ifp = ifnet; ifp; ifp = ifp->if_next) {
		if (ifp->if_timer == 0 || --ifp->if_timer)
			continue;
		if (ifp->if_watchdog)
			(*ifp->if_watchdog)(ifp->if_unit);
	}
	splx(s);
	timeout(if_slowtimo, (void *)0, hz / IFNET_SLOWHZ);
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值