ifnet结构详解

为管理网络设备(一个网络设备可以理解为一块网卡,一台计算机可以装有多块网卡。以下为便于理解,称网络设备为网卡),内核为每块网卡分配了一个ifnet结构。内核用if_next把所有网卡的ifnet结构连成了一个链表,由函数if_attach在系统初始化期间构造该链表。

每块网卡可配置多个协议地址,每个协议地址用一个ifaddr结构来描述。内核把一块网卡支持的多个协议地址ifaddr连成了一个链表,并由ifnet结构中的if_addrlist指向本网卡的ifaddr结构链表。

structifnet{

/*************实现信息*********************************/

structifnet*if_next;//把所有接口的ifnet结构链接成一个链表

structifaddr*if_addrlist;//指向这个接口的ifaddr结构链表

char*if_name;//字符串,接口名称(类型),如”le”

shortif_unit;//标识多个相同类型的实例

u_shortif_index;//在内核中唯一地标识这个接口

shortif_flags;//接口的操作状态和属性,譬如接口正在工作和用于广播

shortif_timer;//以秒为单位记录时间,直到内核为此接口调用函数if_watchdog为止

intif_pcount;//混杂方式监听者的数目

caddr_tif_bpf;//分组过滤器结构

structif_data{

/*************硬件信息**********/

u_charifi_type;//指明接口支持的硬件地址类型,如以太网,令牌环

u_charifi_addrlen;//数据链路层地址(硬件地址)的长度

u_charifi_hdrlen;//由硬件附加给任何分组的首部的长度,如MAC首部长度(14字节)

u_longifi_mtu;//接口在一次输出操作中能传输的最大数据单元的字节数,如以太网是1500

u_longifi_metric;//路由度量(或代价,metric),通常为0

u_longifi_baudrate;//指定接口的传输速率,只用于SLIP接口

/***********接口统计信息**********/

u_longifi_ipackets;//packetsreceivedoninterface

u_longifi_ierrors;//inputerrorsoninterface

u_longifi_opackets;//packetssentoninterface

u_longifi_oerrors;//outputerrorsoninterface

u_longifi_collisions;//collisionsoncsmainterfaces

u_longifi_ibytes;//totalnumberofoctetsreceived

u_longifi_obytes;//totalnumberofoctetssent

u_longifi_imcasts;//packetsreceivedviamulticast

u_longifi_omcasts;//packetssentviamulticast

u_longifi_iqdrops;//droppedoninput,thisinterface

u_longifi_noproto;//destinedforunsupportedprotocol

structtimevalifi_lastchange;//lastupdated

}if_data;

/**函数指针,在系统初始化时,每个设备驱动程序初始化它自己的ifnet结构**/

int(*if_init)/*初始化接口*/

(int);

int(*if_output)/*对输出分组进行排队*/

(structifnet*,structmbuf*,structsockaddr*,structrtentry*);

int(*if_start)/*启动分组的传输*/

(structifnet*);

int(*if_done)/*传输完成后的清除*/

(structifnet*);

int(*if_ioctl)/*IO控制命令*/

(structifnet*,int,caddr_t);

int(*if_reset)/*复位接口设备*/

(int);

int(*if_watchdog)/*周期性监控接口*/

(int);

/******输出队列********/

structifqueue{

structmbuf*ifq_head;//指向队列的第一个分组

structmbuf*ifq_tail;//指向队列最后一个分组

intifq_len;//当前队列中分组的数目

intifq_maxlen;//队列中允许的缓存最大个数

intifq_drops;//因为队列满而丢弃的分组数

}if_snd;

};//structifnet

#defineif_mtuif_data.ifi_mtu

#defineif_typeif_data.ifi_type

#defineif_addrlenif_data.ifi_addrlen

#defineif_hdrlenif_data.ifi_hdrlen

#defineif_metricif_data.ifi_metric

#defineif_baudrateif_data.ifi_baudrate

#defineif_ipacketsif_data.ifi_ipackets

#defineif_ierrorsif_data.ifi_ierrors

#defineif_opacketsif_data.ifi_opackets

#defineif_oerrorsif_data.ifi_oerrors

#defineif_collisionsif_data.ifi_collisions

#defineif_ibytesif_data.ifi_ibytes

#defineif_obytesif_data.ifi_obytes

#defineif_imcastsif_data.ifi_imcasts

#defineif_omcastsif_data.ifi_omcasts

#defineif_iqdropsif_data.ifi_iqdrops

#defineif_noprotoif_data.ifi_noproto

#defineif_lastchangeif_data.ifi_lastchange

内核使用ifaddr结构来描述网络接口的一个协议地址。相关结构定义如下:

/********通用地址结构,如主机地址,广播地址和子网掩码**************/

structsockaddr{

u_charsa_len;//sockaddr的长度

u_charsa_family;//地址族,如AF_INET

charsa_data[14];//具体地址数组

};

/******链路层地址结构********/

structsockaddr_dl{

u_charsdl_len;//sockaddr的长度

u_charsal_family;//地址族,如AF_LINK

u_shortsdl_index;//在内核中唯一地标识这个接口

u_charsdl_type;//接口类型,如IFT_ETHER

u_charsdl_nlen;//接口名称长度

u_charsdl_alen;//链路层地址长度

u_charsdl_slen;//未用

u_charsdl_data[12];//保存网卡名称和链路层地址

};

//LLADDR获取指向sdl_data中保存的链路层地址的首地址

#defineLLADDR(s)((caddr_t)((s)->sdl_data+(s)->sdl_nlen))

structifaddr{

structifaddr*ifa_next;//接口的下一个地址

structifnet*ifa_ifp;//指回接口的ifnet结构的指针

structsockaddr*ifa_addr;//接口地址

structsockaddr*ifa_dstaddr//指向点对点链路另一端的协议地址

#defineifa_broadaddrifa_dstaddr//或指向广播地址

structsockaddr*ifa_netmask;//指向网络掩码

void(*ifa_rtrequest)();//支持接口的路由查找

u_shortifa_flags;//支持接口的路由查找

shortifa_refcnt;//引用计数

intifa_metric;//支持接口的路由查找

};

我们知道,内核为每个网络接口(网卡)分配一个ifnet结构,系统中所有网络接口的ifnet结构链成一个链表。每个网络接口可以配置若干个协议地址,每个地址用一个ifaddr结构来描述,每个接口的所有ifaddr结构链成一个链表。为管理系统中的所有网络设备,内核定义了如下的2个全局变量:

structifnet*ifnet;//指向系统中所有网络接口的ifnet链表的表头

structifaddr**ifnet_addrs//指向链路层接口地址的指针数组。

注意,ifnet_addrs为数组,数组中的成员为(structifaddr*)类型。数组中的每个成员分别指向一块网卡的链路层地址结构ifaddr。

对于以太网设备(其他类型的网络设备也是如此),内核定义了一个全局数组le_softc来管理,数组中的每个成员代表一块网卡,也就是说每块网卡都在数组le_softc中有一个位置,内核用le_softc数组中的一个成员来管理一块网卡。该数组定义如下:

structle_softc{

structarpcomsc_ac;

#definesc_ifsc_ac.ac_if//接口的ifnet结构

#definesc_addrsc_ac.ac_enaddr//以太网硬件地址

............................

.........硬件相关成员.........

............................

}le_softc[NLE];

stuctarpcom{

structifnetac_if;//代表接口的ifnet结构

u_charac_enaddr[6];//以太网硬件地址

structin_addrac_ipaddr;//IP地址

structether_multi*ac_multiaddrs;//指向以太网的多播地址列表

intac_multicnt;//多播地址列表的项数

};

内核启动时,会识别出系统安装的所有网卡,对于每个网卡,内核会将le_soft[i]i表示某块网卡,比如第一块网卡,i就是0;第二块网卡,i就是1,下同)中成员的sc_if(是一个ifnet结构)链入到全局变量ifnet所指向的链表中。同时内核会给每块网卡分配一个链路层地址,该地址用一个ifaddr结构和两个sockaddr_d1结构来描述(其中一个sockaddr_dl结构保存了网卡的名称和以太网硬件地址,另一个则保存了网卡的名称掩码)。内核将ifaddr结构中的ifa_addr成员和ifa_netmask成员分别指向这两个sockaddr_dl结构。随后内核将该代表链路层地址的ifaddr结构链入到网卡的ifnet结构的if_addrlist成员链表中,并将全局指针数组ifnet_addrs中的相应成员指向它。

具体来说,内核启动时对于网络初始化的过程如下:

1.在内核启动的main函数中,内核调用cpu_startup()函数。在该函数中会内核查找、识别系统所连接的网络设备。一旦识别一个网络设备,它就为该设备调用一次leattach函数。注意,内核为每个网卡调用一次leattach函数。

2.leattach函数中:

1)内核从网卡硬件上读取以太网硬件地址,并保存到数组le_softc中相应的成员的sc_addr成员中。

2)然后初始化数组le_softc中相应的ifnet结构(ifp指向le_softc[i]中的ifnet结构):

ifp->if_unit=hd->hp_unit;//以太网类型的设备编号,比如0

ifp->if_name=“le”;//网卡名称

ifp->if_mtu=ETHERMTU;//MTU

ifp->if_init=leinit;//初始化函数

ifp->if_reset=lereset;//复位函数

ifp->if_ioctl=leioctl;//ioctl函数

ifp->if_output=ether_output;//输出函数

ifp->if_start=lestart;//启动传输函数

ifp->if_flags=IFF_BROADCAST|IFF_SIMPLEX|IFF_MULTICAST;

3)调用bpfattach函数登记有BPF的接口。

4)调用if_attach函数把初始化过的ifnet结构挂入到全局的ifnet链表中。

3.if_attach函数中:

(1)ifnet结构挂入到全局的ifnet链表的末尾;

(2)初始化ifnet的中成员if_index为全局唯一索引:

ifp->if_index=++if_index;//if_index为全局变量初值为0

(3)如果尚未为全局指针数组ifnet_addrs分配空间,则为它分配8个空间;

(4)构造链路层地址。分配1ifaddr结构体和2sockaddr_dl结构体。这3个结构体分配在一块连续的内存上,即分配一块大小为(1ifaddr结构的大小+2sockaddr_dl结构的大小)的内存。将网卡名称保存到第一个sockaddr_dl结构(注意,此时以太网硬件地址还没有保存到这里),并将它的成员sal_family设置成AF_LINK,表明是数据链路层地址;将名称掩码保存到第二个sockaddr_dl结构。然后初始化ifaddr结构,将它链入到所属的ifnet结构的if_addrlist链表中(实际上它是ifnet结构的if_addrlist链表的第一个元素)。同时将全局数组ifnet_addrs中下一个可用的位置指向该ifaddr结构。然后将ifaddr结构的成员ifa_addr指向第一个sockaddr_dl结构;成员ifa_netmask指向第二个sockaddr_dl结构。

(5)调用ether_ifattach函数完成ifnet结构中其他成员的初始化。

4.ether_ifattach函数中:

(1)初始化ifnet结构中的几个成员:

ifp->if_type=IFT_ETHER;//接口类型,以太网

ifp->if_addrlen=6;//硬件地址长度

ifp->if_hdrlen=14;//MAC帧首部长度

ifp->if_mtu=ETHERMTU;//MTU

(2)初始化上面3.(4)中的第一个sockaddr_dl结构的几个成员:

sdl->sdl_type=IFT_ETHER;//接口类型,以太网

sdl->sdl_alen=if_addrlen;//硬件地址长度

(3)le_softc[i]中的网卡硬件地址复制到上面3.(4)中的第一个sockaddr_dl结构中。

5.执行完步骤1~4,程序返回到内核的main函数中。至此,所有网络接口的ifnet结构都已经完成初始化,并且链成了一个链表,保存在全局变量ifnet中。接着调用ifinit函数:该函数遍历全局的ifnet结构链表,为每个ifnet结构设置输出队列的最大缓存个数;接着该函数调用if_slowtimo函数启动接口的监视计时器。if_slowtimo函数遍历全局的ifnet结构链表,对于每个接口,忽略if_timer0的接口。若if_timer不为0,则将if_timer1,如果减1后为0(说明定时时间已到),则调用接口相关联的监视器函数if_watchdog。然后if_slowtimo函数将它自己设置成每隔1秒执行一次。

6.在内核的main函数中。调用domaininit函数完成内核所支持的协议族的初始化。domaininit函数的执行流程如下:

(1)将内核声明的4个全局的domain结构体(isodomaininetdomainroutedomainunixdomain)链成一个链表,令全局变量domains指向链表的第一个元素。

(2)遍历全局的domain链表,对每个域,调用它的初始化函数dom_init;对域中的每个协议(即protosw结构),调用它的初始化函数pr_init

(3)调用timeout函数启动慢定时函数pfslowtimo(每隔500ms执行一次)和快定时函数pffasttimo(每隔200ms执行一次)。pfslowtimo(或pffasttimo)函数执行时会遍历全局的domain链表,对每个域遍历其协议数组,调用每个协议(protosw结构)的慢定时函数pr_slowtimo(或快定时函数pr_fasttimo)

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值