为管理网络设备(一个网络设备可以理解为一块网卡,一台计算机可以装有多块网卡。以下为便于理解,称网络设备为网卡),内核为每块网卡分配了一个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)构造链路层地址。分配1个ifaddr结构体和2个sockaddr_dl结构体。这3个结构体分配在一块连续的内存上,即分配一块大小为(1个ifaddr结构的大小+2个sockaddr_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_timer为0的接口。若if_timer不为0,则将if_timer减1,如果减1后为0(说明定时时间已到),则调用接口相关联的监视器函数if_watchdog。然后if_slowtimo函数将它自己设置成每隔1秒执行一次。
6.在内核的main函数中。调用domaininit函数完成内核所支持的协议族的初始化。domaininit函数的执行流程如下:
(1)将内核声明的4个全局的domain结构体(isodomain、inetdomain、routedomain、unixdomain)链成一个链表,令全局变量domains指向链表的第一个元素。
(2)遍历全局的domain链表,对每个域,调用它的初始化函数dom_init;对域中的每个协议(即protosw结构),调用它的初始化函数pr_init。
(3)调用timeout函数启动慢定时函数pfslowtimo(每隔500ms执行一次)和快定时函数pffasttimo(每隔200ms执行一次)。pfslowtimo(或pffasttimo)函数执行时会遍历全局的domain链表,对每个域遍历其协议数组,调用每个协议(protosw结构)的慢定时函数pr_slowtimo(或快定时函数pr_fasttimo)。
http://tailengineer.blog.51cto.com/7914238/1304145
今天的文章ifnet_attach_densenet网络结构分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:http://bianchenghao.cn/67154.html