本节介绍以太网接口相关内容
一、以太网接口的初始化
内核初始化函数cpu_startup查找连接的网络设备,当识别到网络后,设备专用的初始化函数被调用。
设备驱动程序为每个接口初始化一个专用的ifnet结构,并调用if_attach把这个结构插入到接口链表中。
le_softc结构存储了以太网接口的所有信息,其中的第一个成员arpcom包含了所有以太网接口通用的信息。其他部分为以太网接口的特性参数。
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 */
struct lereg0 *sc_r0; /* DIO registers */
struct lereg1 *sc_r1; /* LANCE registers */
struct lereg2 *sc_r2; /* dual-port RAM */
int sc_rmd; /* predicted next rmd to process */
int sc_tmd; /* next available tmd */
int sc_txcnt; /* # of transmit buffers in use */
/* stats */
int sc_runt;
int sc_jab;
int sc_merr;
int sc_babl;
int sc_cerr;
int sc_miss;
int sc_rown;
int sc_xown;
int sc_xown2;
int sc_uflo;
int sc_rxlen;
int sc_rxoff;
int sc_txoff;
int sc_busy;
short sc_iflags;
} le_softc[NLE];
再来分析一下arpcom结构的内容。
/*
* 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 */
};
ac_if 所有网络接口共有的信息
ac_enaddr 以太网的硬件地址,在cpu_startup时设备驱动程序从硬件上复制而来的
/*
* Read the ethernet address off the board, one nibble at a time.
*/
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++;
}
在函数leattch中通过以上代码复制硬件上的地址。
ac_ipaddr 上一次给该接口分配的ip地址
ac_multicastaddr 以太网多播地址的列表
ac_multicnt 地址列表的长度
/**********************************************************/
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);
/**************************************************************/
leattach函数同时也初始化了ifnet结构的其他参数。
接口的函数指针分别指向以太网设备的专用函数。
bpfattach 登记了bpf接口
ifattach 将接口插入到接口链表。
ifattach函数是如何将接口链到链表,下面进行分析。
该函数的主要完成为接口创建一个链路层地址ifaddr结构,并将接口插入接口链表。
ifaddr结构还包含了两个sockaddr_dl结构。第一个包含了一个表示逻辑地址,包含一个名称和这个接口在内核中的索引值,和该接口的物理地址。第二个为接口的地址掩码。
/*
* 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 */
};
此时结构中的sdl_family的值时AF_LINK,表示链路层地址。
sdl_type 与ifnet中的接口类型相同。
sdl_data的前sdl_nlen个字节存放逻辑地址,剩余部分存放物理地址
/*************************************************************/
/*
* create a Link Level name for this device
*/
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;
/*************************************************************/
创建接口的接口名,并计算sockaddr_dl的长度。
/***********************************************************/
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;
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;
/***********************************************************/
分配内存,并初始化各个值。最后设置链路层地址的mask,通过mask可以直接获取接口的逻辑地址。
函数的最后调用ether_ifattach函数对以太网ifnet结构通用的信息进行初始化。
并将物理地址从arpcom复制到链路层地址中。
以上就是整个以太网接口的初始化过程了,在第一次调用leattach后,系统会维护一个ifnet链表,连接所有的接口结构体,和一个ifnet_addrs地址指针数组,存放接口的链路层地址的指针。
接口结构被初始化后,内核初始化函数调用ifinit函数初始化各个接口的输出队列的最大长度。队列的大小关系到调用列程能发送的最大数据报。ifinit还调用了if_slowtimo函数启动接口监视定时,定时器的时长为if_timer的值,定时器到期后调用接口的if_watchdog函数。
本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/mythfish/archive/2008/10/22/3126071.aspx