原文链接:https://mp.weixin.qq.com/s/IC7PD4n0X3DRnVV2LDA61A
本文要点
-
引言
-
接口层相关数据结构
-
ifnet结构
-
ifaddr结构
-
sockaddr结构
-
-
ifnet与ifaddr的专用化
-
网络初始化
-
以太网初始化
-
SLIP初始化
-
环回初始化
-
-
接口函数
-
if_attach函数
-
ifinit函数
-
引言
本文讨论Net/3在协议栈底部的接口层,它包括(广义)在本地网上发送和接收分组的硬件与软件。
我们使用术语设备驱动程序来表示与硬件及网络接口通信的软件,网络接口是指在一个特定网络上硬件与设备驱动器之间的接口。
Net/3接口层(狭义)是指在网络协议层和设备驱动器之间提供一个与硬件无关的编程接口。这个接口层为所有的设备提供以下支持:
-
一套精心定义的接口函数;
-
一套标准的统计与控制标志;
-
一个与设备无关的存储协议地址的方法;
-
一个标准的输出分组的排队方法。
这里不要求接口层提供可靠的分组传输,仅要求提供最大努力的服务。更高协议层弥补这各种靠性缺陷。本文将介绍所有网络接口的通用数据结构,同时为了说明想着数据结构和算法,我们参考Net/3中三种特定的网络接口:
-
一个AMD 7990 LANANCE以太网接口:一个能广播局域网的例子。
-
一个串行线IP(SLIP)接口:一个在异步串行线上的点对点网络的例子。
-
一个环回接口:一个逻辑网络把所有输出分组作为输入返回。
接口层相关数据结构
1. ifnet结构
结构ifnet中包含所有接口的通用信息。在系统初始化期间,分别为每个网络设备分配一个独立的ifnet结构。每个ifnet结构有一个列表,它包含这个设备的一个或者多个协议地址。图1说明了一个接口和它的协议地址之间的关系。
图1 ifnet结构有一个ifaddr结构的列表
图1中的接口显示了3个存放在ifaddr结构中协议地址。虽然一些网络接口,例如 SLIP,仅支持一个协议;而其它接口,如以太网,支持多个协议并需要多个地址 。例如,一个系统可能使用一个以太网接口同时用于Internet和OSI两个协议。一个类型字段标识每个以太网帧的内容,并且因为Internet和OSI协议使用不同的编址方式,以太网接口必须有一个Internet地址和一个OSI地址。所有地址用一个链表链接起来(图1),并且每个结构包含一个回指指针指向相关的ifnet结构。
另外,一个网络接口支持同一协议的多个地址。例如,在Net/3中可能为一个以太网分配两个Internet地址。
ifnet结构比较大,我们分为五个部分来说明:
-
实现信息
-
硬件信息
-
接口统计
-
函数指针
-
输出列表
图2所示的是ifnet结构中的实现信息。
图2 ifnet结构:实现信息
80~82 if_next把所有的接口的ifnet结构链接成一个链表。函数if_attach在系统初始化期间构造这个链表。if_addrlist指向这个接口的ifaddr结构列表(图12)。每个ifaddr结构存储一个要在该接口进行通信的协议地址信息。
a. 通用接口信息
83~86 if_name是一个短字符串,用于标识接口类型,而if_unit标识多个类型相同的实例。例如,一个系统有两个SLIP接口,每个都有一个if_name("sl")和一个if_unit,对于第一个接口if_unit为0,对于第二个接口if_unit为1。if_index在内核中唯一地标识这个接口,这在sysctl系统调用和路由域中要用到;因为有时接口并不被一个协议地址唯一标识,例如几个SLIP连接可以有同样的IP地址。
if_flags表明接口的操作状态和属性。一个进程能检查所有的标志,但不能改变在图3中“内核专用”列作了记号的标志。
图3 if_flags值
b. 接口时钟
87 if_timer以秒为单位记录时间,直到内核为此接口调用函数if_watchdog为止。这个函数用于设备驱动程序定时收集接口统计,或者用于复位运行不正确的硬件。
c. BSD分组过滤器
88~89 if_pcount和if_bpf支持BSD分组过滤器(BPF)。通过BPF,一个进程能接收由此接口传输或者接收的分组的备份。
ifnet结构的下一下部分显示在图4中,它用来描述接口的硬件特性。
图4 ifnet结构:接口(硬件)特性
d. 接口特性
90~92 if_type指明接口支持的硬件地址类型。图5列出了net/if_types.h中几种公共的if_type类型。
图5 if_type:数据链路类型
93~94 if_addrlen是数据链路地址的长度,而if_hdrlen是由硬件附加给任何分组的首部长度。例如,以太网有一个长度为6字节的地址和一个长度为14字节的首部。
95 if_mtc是接口传输单元的最大值:接口在一次输出操作中能传输的最大数据单元的字节数,对于以太网来说为1500。
96~97 if_metric通常是0;其他更大的值不利于路由通过此接口。if_baudrate指定接口的传输速率,只有SLIP接口才设置它。
e. 接口统计
图6 结构ifnet:接口统计
98~111 大多数统计是不言自明的。当分组传输被共享媒体上的其它传输中断时,if_collisions加1。if_noproto统计由于协议不被系统或接口支持而不能处理的分组数,例如仅支持IP的系统接收到一个OSI分组,if_noproto加1。
f. 改变时间戳
112~113 if_lastchange记录任何统计改变的最近时间。
结构ifnet的下一个部分如图7所示,它包含指向标准接口层函数的指针,它们把设备专用的细节从网络层分离出来。每个网络接口实现这些适于特定设备的函数。
图7 结构ifnet:接口过程
g. 接口函数
114~129 在系统初始化时,每个设备驱动程序初始化它自己的ifnet结构,包括7个函数指针。图8说明了这些通用函数。
图8 结构ifnet:函数指针
内核通过ifnet结构中的这些指针直接调用它们。例如,如果ifp指向一个ifnet结构,
( *ifp->if_start ) ( ifp )
调用接口的设备驱动程序的if_start函数。
结构ifnet中最后一个成员是接口的输出队列,如图9所示。
图9 结构ifnet:输出队列
130~137 if_snd是接口输出分组队列,每个接口都有它自己的ifnet结构,所以也有它自己的输出队列。ifq_head指向队列的第一个分组(下一个要输出的分组),ifq_tail指向队列最后一个分组,if_len是当前队列中分组的数目,而ifq_maxlen是队列中允许的缓存最大个数。默认设置为50(全局整数ifqmaxlen,在编译期间根据IFQ_MAXLEN初始化而来)。队列通过一个mbuf链表来实现。ifq_drops统计因为队列满而丢弃的分组数。图10列出了访问队列的宏和函数。
图10 fiqueue例程
前5个例程定义在net/if.h中的宏,最后一个if_qflush是定义在net/if.c中的一个函数。这些宏经常出现在下面这样的程序语句中:
s = splimp();
if (IF_QFYLL(inq)){
IF_DROP(inq);
m_free(m);
}else
IF_ENQUEUE(inq, m);
splx(s);
这段代码试图把一个分组加到队列中,如果队列满,IF_DROP把ifq_drops加1,并且丢弃分组。可靠协议如TCP会重传丢弃的分组,使用不可靠协议的应用程序必须自己检测和处理重传。
访问队列被 splimp和splx括起来,阻止网络中断,并且防止在不确定状态时网络中断服务例程访问此队列。
2. ifaddr结构
下面介绍的结构是接口地址ifaddr结构,如图11所示,每个接口维护一个ifaddr结构的链表,因为一些链路,如以太网,支持多于一个协议。一个单独的ifaddr结构描述每个分配给接口的地址,通常每个协议都有一个地址;支持多地址的另一个原因是很多协议,包括TCP/IP,支持为单个物理接口指派多个地址。
图11 结构ifaddr
217~219 结构ifaddr通过ifa_next把分配给一个接口的所有地址链接起来,同时包括一个指回接口ifnet结构的指针ifa_ifp。图12显示了结构ifnet与ifaddr之间的关系。
图12 结构ifnet和ifaddr
220 ifa_addr指向接口的一个协议地址,而ifa_netmask指向一个位掩码,它用于选择ifa_addr中的网络部分。地址中表示网络部分的比特在掩码中被设置为1,地址中表示主机的部分被设置为0。两个地址都存放在sockaddr结构中。图34显示了一个地址及其掩码结构。对于IP地址,掩码选择IP地址中的网络和子网部分。
221~223 ifa_dstaddr(或它的别名ifa_broadaddr)指向一个点对点链路上另一端的接口协议地址或一个广播网中分配给接口的广播地址(如以太网)。接口的ifnet结构中互斥的两个标志IFF_BROADCAST和IFF_POINTOPOINT指示接口类型。
224~228 ifa_rtrequest、ifa_flags和ifa_metrict支持接口的路由查找。
ifa_refcnt统计对结构ifaddr的引用。宏IFAFREE仅在引用计数降到0时才释放这个结构,例如,当地址被命令SIOCDIFADDR ioctl删除时。结构ifaddr使用引用计数是因为接口和路由数据结构共享这些结构。
3. sockaddr结构
一个接口的编址信息不仅仅包括一个主机地址。Net/3在通用的sockaddr结构中维护主机地址、广播地址和网络掩码。
图13显示了这个结构的当前定义以及早期BSD版本的定义osockaddr。
图14说明了这些结构的组织。
图13 结构sockaddr和osockaddr定义
图14 结构sockaddr和osockaddr组织(省略前缀sa_)
a. sockaddr结构
120~124 每个协议有它自己的地址格式。Net/3在一个sockaddr结构中处理通用的地址。sa_len指示地址的长度(OSI和Unix域协议有不同长度的地址),sa_family指示地址的类型。图15列出地址簇常量。
图15 sa_family常量
成员sa_len和sa_family允许协议无关代码操作来自多个协议的变长的sockaddr结构。剩下的sa_data包含一个协议相关格式的地址。sa_data定义为一个14字节的数组,但当sockaddr结构覆盖更大的内存空间时,sa_data可能会扩展到253字节。sa_len仅有一个字节,而整个地址包括sa_len和sa_family必须不超过256字节。
每个协议定义一个专用的sockadd结构,该结构复制成成员sa_len和sa_family,但按那个协议的要求来定义成员sa_data。存储在sa_data中的地址是一个传输地址;它包含足够的信息来标识同一主机上的多个通信端点。例如Internet地址结构sockaddr_in,它包含一个IP地址和一个端口号。
b. osockaddr结构
271~274 结构osockaddr是4.3BSD Reno版本以前的sockaddr定义。由于在这个定义中一个地址的长度不是显示地可用,所以不能用来写处理变长地址的协议无关代码。
ifnet与ifaddr的专用化
结构ifnet和ifaddr包含适用于所有网络接口和协议地址的通用信息。为了容纳它设备和协议专用信息,每个设备定义以及每个协议地址都分配了一个专用化版本的ifnet和ifaddr结构。这些专用化结构总是包含一个ifnet或者ifaddr结构作为它们的第一个成员,这样无须考虑其它专用信息就能访问这些公共信息。
多数设备驱动程序通过分配一个专用化的ifnet结构的数组来处理同一类型的多个接口,但其他设备(环回设备)仅处理一个接口。图16所示的是我们的例子接口的专用化ifnet结构组织。
图16 专用化ifnet结构的组织
每个设备的结构以一个ifnet开始,接下来全是设备相关的数据。环回接口只声名了一个ifnet结构,因为它不要求作何设备相关数据。在图16中,我们显示的以太网和SLIP驱动程序的结构softc带有数组下标0,因为两个设备都支持多个接口。
结构arpcom(图22)对于所有以太网设备是通用的,并且包含地址解析协议(ARP)和以太网多播信息。结构le_softc(图21)包含专用于LANCE以太网设备驱动器的其他信息。
每个协议把每个接口的地址信息存储在一个专用化的ifaddr列表中。以太网协议使用一个in_ifaddr结构(后续介绍),而OSI协议使用一个iso_ifaddr结构。另外,当接口被初始化时,内核为每个接口分配了一个链路层地址,它在内核中标识这个接口。
内核通过分配一个ifaddr结构和两个sockaddr_dl结构(一个是链路层地址本身,另一个是地址掩码)来构造一个链路层地址。结构sockaddr_dl可被OSI、ARP和路由算法访问。图17显示了一个带有一个链路层地址、一个Internet地址和一个OSI地址的以太网接口。
图17 一个包含链路层、Internet和OSI地址的接口列表
网络初始化
前面介绍的所有数据结构在内核初始化时分配和互相链接起来。有些设备,例如SLIP和环回接口,完全用软件来实现。这些伪设备用存储在全局pdevinit数组中的一个pdevinit结构来表示(图18)。在内核配置期间构造这个数组,例如:
图18 结构pdevinit
120~123 对于SLIP和环回接口,在结构pdevinit中,pdev_attach分别被设置为slattach和loopattach。当调用attach这个函数时,pdev_count作为唯一传递参数,它指定创建的设备个数。只有一个环回设备被创建,但是如果管理员适当配置SLIP项可能有多个SLIP设置被创建。
网络初始化函数从main函数开始,如图19所示。
图19 main函数:网络初始化
70~96 cpu_startup查找并初始化所有连接到系统的硬件设备,包括任何网络接口。
97~174 在内核初始化硬件设备后,它调用包含在pdevinit数组中的每个pdev_attach函数。
175~234 ifinit和domaininit完成网络接口和协议的初始化,并且在scheduler开始内核进程的调度。ifinit和domaininit在原著第7章有讨论。
1. 以太网初始化
作为cpu_startup的一部分,内核查找任何连接的网络设备。这个进程的细节超出本文范围。一旦一个设备被识别,一个设备专用的初始化函数就被调用,图20显示的是3个例子接口的初始化函数。
图20 网络接口初始化函数
每个设备驱动程序为一个网络接口初始化一个专用化的ifnet结构,并调用if_attach把这个结构插入到接口链表中。显示在图21中的结构le_softc是我们的例子以太网驱动程序的专用化的ifnet结构(图16)。
图21 结构le_softc
a. le_softc结构
69~95 在if_le.c中声明了一个le_softc结构的数组。每个结构的第一个成员是sc_ac,一个arpcom结构,它对于所有以太网接口都是通用的,接下来是设备专用成员。宏sc_if和sc_addr简化了对结构ifnet以及存储在arpcom(sc_ac)中的以太网地址的访问,如图22所示。
图22 结构arpcom
b. arpcom结构
95~101 结构arpcom的第一个成员ac_if是一个ifnet结构,如图16所示。ac_enaddr是以太网硬件地址,它是在cpu_startup期间内核检测设备时由LANCE设备驱动程序从硬件上复制的。对于我们的例子驱动程序,这发生在函数leattach中(图23)。ac_ipaddr是上一个分配给此设备的IP地址。ac_multiaddrs是一个用结构ether_multi表示的以太网多播地址的列表。ac_multicnt统计这个列表的项数。
图23所示的是LANCE以太网驱动程序的初始化代码。
图23 函数leattach
106~115 内核在系统中每发现一个LANCE卡都调用一次leattach。
le指向此卡的专用化ifnet结构(图16),ifp指向这个结构的第一个成员sc_if,一个通用的ifnet结构。图23并不包括设备专用初始化代码,本文不予讨论。
c. 从设备复制硬件地址
126~137 对于LANCE设备,由厂商指派的以太网地址在这个循环中每次半个字节(4bit)从设备复制到sc_addr中。
d. 初始化ifnet结构
150~157 leattach从hp_device结构中把设备单元号复制到if_unit中来标识同类型的多个接口。该设备的if_name是"le";if_mtu为1500字节(ETHERMTU),以太网的最大传输单元;if_init、if_reset、if_ioctl、if_output和if_start都指向控制网络接口的通用函数的设备专用实现。
158 以太网设备都支持IFF_BROADCAST。由于LANCE设备不接收它自己发送的数据,因此被设置为IFF_SIMPLEX。支持多播的设备和硬件还要设置IFF_MULTICASH。
159~162 bpfattach登记有BPF的接口,函数if_attach把初始化了的ifnet结构插入到接口的链表中。
2. SLIP初始化
依赖标准异步串行设备的SLIP接口在调用cpu_startup时初始化。当main直接通过SLIP的pdevinit结构中的pdev_attach调用slattach时,SLIP伪设备被初始化。
每个SLIP接口由图24中的一个sl_softc结构来描述。
图24 结构sl_softc
43~54 与所有接口一样,sl_softc有一个ifnet结构并且后面跟着设备专用信息。
除了在ifnet结构中的输出队列外,SLIP设备还维护另一个队列sc_fastq,它用于要求延服务的分组——典型的交互应用产生。
sc_ttyp指向关联的终端设备。指针sc_buf和sc_ep分别指向一个接收SLIP分组的缓存的第一个字节和最后一个字节。sc_mp指向下一个接收字节地址,并且在另外一个字节到达时向前移动。
SLIP定义的4个标志显示在图25中。
图25 SLIP的if_flags和sc_flags值
SLIP在ifnet结构中定义了3个接口标志给设备驱动程序,另一个标志定义在结构sl_softc中。
sc_escape用于串行线的IP封装机制,TCP首部压缩信息保留在sc_comp中。
指针sc_bpf指向SLIP设备的BPF信息。
结构sl_softc由slattach初始化,如图26所示。
图26 函数slattach
135~152 不像leattach一次仅初始化一个接口,内核只调用一次slattch,并且slattach初始化所有的SLIP接口。硬件设备在内核执行cpu_startup被发现时初始化,而伪设备都是在main为这个设备调用pdev_attach函数时初始化的。一个SLIP设备的if_mtc为296字节(SLMTU)。这包括20字节的IP首部、标准的20字节TCP首部和256字节的用户数据。
一个SLIP网络由位于一个串行通信线两端的两个接口组成。slattach在if_flags中设置IFF_POINTOPOINT、SC_AUTOCOMP和IFF_MULTICAST。
SLIP接口限制它的输出分组队列的长度为50,并且它自己的接口队列sc_fashq的长度为32。
if_attach需要一个指向一个ifnet结构的指针,因此slattach将sc_if的地址传递给if_attach。
153~155 对于每个SLIP设备,slattach调用bpfattach来登记有BPF的接口。
3. 环回初始化
最后显示环回接口的初始化。环回接口把输出分组放回到相应的输入队列中。接口没有相关硬件设备。环回伪设备在main通过环回接口的pdevinit结构中的pdev_attah指针直接调用loopattach时初始化,如图27所示。
图27 环回接口初始化
41~56 环回if_mtu被设置为1536字节(LOMTU)。if_flags设置IFF_LOOPBACK和IFF_MULTICAST。因为环回地址没有链路首部和硬件地址,所以if_hdrlen和if_addrlen均为0。if_attach完成ifnet结构的初始化并且bpfattach登记有BPF的环回接口。
接口函数
1. if_attach函数
前面三个接口初始化函数都调用if_attach来完成接口的ifnet结构的初始化,并且这个结构插入到先前配置的接口列表中。在if_attach中,内核也为每个接口初始化并分配一个链路层地址。图28说明了由if_attach构造的数据结构。
图28 ifnet列表
在图28中,if_attach被调用了三次,以一个le_softc结构为参数从leattach调用,以一个sl_softc结构为参数从slattach调用,以一个通用ifnet结构为参数从loopattach调用。每次调用时,它向ifnet列表中添加一个新的ifnet结构,为这个接口创建创建一个链路层ifaddr结构(包括两个sockaddr_sl结构,图29),并初始化ifnet_addr数组中的一项。
图29 结构sockaddr_sl
初始化后,接口仅配置链路层地址。例如,IP地址直到后面讨论的ifconfi程序才配置。
链路层地址包含接口的一个逻辑地址和一个硬件地址(例如le0的一个48bit的以太网地址)。在ARP和OSI协议中要用到这个硬件地址,而一个sockaddr_dl中的逻辑地址包含一个名称和这个接口在内核的索引数值,它支持用于在接口索引和关联ifaddr结构间相互转换的表查找。
结构sockaddr_dl显示在图29中。
55~57 回忆图14,sdl_len指明了整个地址长度,而sdl_family指明了地址族类,在此例中为AF_LINK。
58 sdl_index在内核中标识接口。图28中的以太网接口会有一个为1的索引,SLIP接口的索引为2,而环回接口的索引为3。全局整数变量if_index包含的是内核最近分配的索引值。
60 sdl_type根据这个数据链路层地址的ifnet结构的成员if_type进行初始化。
61~68 除了一个数字索引,每个接口有一个由结构ifnet的成员if_name和if_unit组成的文本名称。例如第一个SLIP接口叫"sl0",而第二个SLIP接口叫"sl1"。文本名称存储在数组sdl_data的前面,并且sdl_nlen为这个名称的字节长度。
数据链路地址也存储在这个结构中。宏LLADDR将一个指向sockaddr_dl结构的指针转换成一个指向这个文本名称的第一个字节的指针。sdl_alen是硬件地址的长度。对于以太网设备,48bit的硬件地址位于结构sockaddr_dl的文本名称的前面。图34是一个初始化了的sockaddr_dl结构。
Net/3不使用sdl_slen。
if_attach更新两个全局变量。第一个是if_index,它存放系统中最后一个接口的索引值,第二个是ifnet_addr,它指向一个ifaddr指针的数组。这个数组的每项都指向一个接口的链路层地址。这个数组提供系统对每个接口的链路层地址的快速访问。
函数if_attach较长,下面分5个部分讨论这个函数。
图30 函数if_attach:分配接口索引
59~74 if_attach有一个参数:ifp,一个指向ifnet结构的指针,由网络设备驱动程序初始化。Net/3在一个链表中维护所有这些ifnet结构,全局指针ifnet指向这个链表的首部。while循环查的链表的尾部,并将链表尾部的空指针的地址存储在p中。在循环后,新ifnet结构被接到这个ifnet链表的尾部,if_index加1,并将新的索引值赋值为ifp->if_index。
a. 必要时调整ifnet_addrs数组的大小
75~85 第一次调用if_attach时,数组ifnet_addrs不存在,因此要分配16项的空间。当数组满时,一个两倍大的新数组被分配,老数组中的项被复制到新的数组中。
图30中的函数malloc和free不是同名的标准C库函数。如果malloc的第三个参数为M_WAITOK,且函数需要等待释放的可用内存,则阻塞调用进程。如果第三个参数为M_DONTWAIT,则当内存不可用时,函数不阻塞并返回一个空指针。
函数if_attach的下一部分如图31所示,它为接口准备一个文本名称并计算链路层地址长度。
图31 if_attach函数:计算链路层地址大小
b. 创建链路层名称并计算链路层地址的长度
86~99 if_attach用if_unit和if_name组装接口名称。函数sprint_d将if_unit的数值转换成一个串并存储到workbuf中。masklen是指sockaddr_sl数组中sdl_data前面的信息所占用的字节数加上这个接口的文本名称的大小(namelen+unitlen)。函数对socksize进行上舍入,socksize是masklen加上硬件地址长度,如果它小于一个sockaddr_dl结构的长度,就使用标准的sockaddr_dl结构。ifasize是一个ifaddr的大小加上两倍的socksize,因此,它能容纳结构sockaddr_dl。
函数的下一个部分中,if_attach分配结构并将结构连接起来,如图32所示。
图32 在if_attach中分配的链路层地址和掩码
c. 地址
100~116 如果有足够的内存可用,bzero把新结构清零,并且sdl指向紧接的ifnet结构的第一个sockaddr_dl。若没有可用内存,代码忽略。
图33 函数if_attach:分配并初始化链路层地址
sdl_len被设置为结构sockaddr_dl的长度,并且sdl_family被设置为AF_LINK。用if_name和unitname组成的文本名称存放在sdl_data中,而它的长度存储在sdl_nlen中。接口的索引被复制到sdl_index中,而接口的类型被复制到sdl_type中。分配的结构被被插入到数组ifnet_addr中,并通过ifa_ifp和ifa_addrlist链接到结构构ifnet。最后,结构sockaddr_dl用ifa_addr连接到ifnet结构。
d. 掩码
117~123 第二个sockaddr_dl结构是一个比特掩码,用来选择出现在第一个结构中的文本名称。ifa_netmask从结构ifaddr指向掩码结构(在这里是选择接口文本名称而不是网络掩码)。while循环把与名称对应的那些字节的每个比特都设置为1。
图34所示的是以太网接口鸽子的两个初始化了的sockaddr_dl结构。它的if_name为"le",if_unit为0,if_index为1。
图34 初始化了的sockaddl_dl结构
图35所示的是第一个接口被if_attach连接后的结构。
图35 第一次调用if_attach后if_net和sockaddr_dl结构
在if_attach的最后,以太网设置的函数ether_ifattach被调用,如图36所示。
图36 函数if_attach:以太网初始化
124~127 开始不调用ether_ifattach(例如:从leattach),是因为它要把以太网的硬件地址复制到if_attach分配的sockaddr_dl中。
e. ether_ifattach函数
图37 函数ether_ifattach
函数ether_inattach执行对所有以太网设备通用的ifnet结构的初始化。
338~357 对于一个以太网设备,if_type为IFT_ETHER,硬件地址为6个字节,首部长度为14字节,MTU为1500。
for循环定位接口的链路层地址,然后初始化结构sockaddr_dl中的以太网硬件地址信息。在系统初始化时,以太网地址被复制到结构arpcom中,现在被复制到链路层地址中。
2. ifinit函数
接口结构被初始化并链接到一起后,main(图19)调用ifinit,如图所示。
图38 函数ifinit
43~51 for循环遍历接口列表,并把没有被接口的attach函数设置的每个接口输出队列的最大长度设置为50。
if_slowtimo启动接口的监视计时器。当一个接口时钟到期,内核会调用这个接口的把关定时器函数,则可以把if_timer设置为0。图39所示的是if_slowtimo。
338~343 if_slowtimo函数有一个参数arg没有使用,但慢超时函数原型要求有这个参数。
图39 函数if_slowtimo
344~352 if_slowtimo忽略if_timer为0的接口;若if_timer不等于0,if_slowtimo把if_timer,并在这个时钟到达0时调用这个接口关联的if_watchdog函数。在调用if_slowtimo时,分组处理进程被slimp阻塞。返回前,if_slowtimo调用timeout,来以hz/IFNET_SLOWHZ时钟频率调度对它自己的调用。hz是1秒钟内的时钟滴答数(通常是100)。它在系统初始化时设置,并保持不变。因为IFNET_SLOWHZ被定义为1,因此内核每赫兹调用一次if_slowtimo,即每秒一次。
更多最新文章尽在公众号:大白爱爬山,欢迎关注!