一.以太网帧结构
以太网帧结构由48位的目的地址和源地址组成,紧接着是一个16位的类型字段(表示该帧所携带的数据类型,如IP分组是0x0800),最后是CRC循环校验,如图所示:
二.以太网的数据接收
1接收过程
step1: 产生中断并调用leintr
以太网接口会接收目的地址(单播地址或广播)为自己的帧,当收到一个完整的帧会产生中断,并且内核会调用leintr函数,该函数会根据帧是否出错来改变接口的统计信息,在linux下可用命令ifconfig -a查看各接口的错误统计,如下:
step2:调用leread将数据读入mbuf缓存
接下来调用leread函数将帧中的数据读入到一个mbuf中,大致代码如下:
struct ether_header {
u_char ether_dhost[6]; // 目的地址
u_char ether_shost[6]; // 源地址
u_short ether_type; // 数据类型
};
// 参数:
// - unit 第几个以太网接口
// - buf 指向收到的帧
// - len 帧长度
void leread(int unit, char* buf, int len)
{
// le_softc是以太网专用化的ifnet
struct le_softc *le = &le_softc[unit];
struct ether_header *et; // 以太网帧首部
struct mbuf *m;
le->sc_if.if_ipackets++; // 增加该接口收到的帧计数,可见上图中的RX packets:
et = (ether_header *)buf; // 重新解释接收到的帧首部为以太网帧头
// 接下来判断帧中的数据长度是否有误等等,若有错误则增加相应计数
// 接口是否带有BFPF,在其它章节说明
...
m = m_devget((char*)(et + 1), ...); // 将帧中的数据部分拷贝到一个mbuf中
ether_input(le->sc_if, et, m);// 调用ether_input,参数为:le_softc中的ifnet结构,帧首部,帧数据
}
step3:调用ether_input将数据放入相应上层协议队列
ether_input函数中检查帧中的数据类型,根据其类型将数据放入相应的协议输入队列中,并会产生一个软件中断,如:若是IP数据则放入IP输入队列ipintrp中,在软件中断处理过程中IP层不断从IP输入队列ipintrp中取出分组。若为ARP数据则放arpintrq中。注意若队列满则ether_input函数会丢弃该帧,并增加队列中的丟包计数(IP队列的丢包计数,即IP协议层的丢包计数),在linux下可以通过命令netstat -s 查看各个协议层的收发包情况。如下图所示:
2.接收过程图
三.以太网的数据发送
当一个网络层协议如IP协议,调用某接口的发送函数if_output(一个函数指针,会根据接口类型不同而指向不同的函数,对于以太网而言是ether_output),该函数做了以下处理:
1.首先检查接口是否打开,若为接口处于关闭则将errno设置为ENETDOWN。
2.寻找路由项,若l路由不能找到则将errno设为EHOSTUNREACH。
3.检查地址簇,若识别到则将errno设为EAFNOSUPPORT
4.最后将数据放到接口的发送队列中,若队列已满则丢弃该数据,增加接口丢包计数,并将errno设为ENOBUFS(当申请mbuf但空间不够时也会这样)
最后当接设备空闲时便调用letstart函数从接口发送队列中取出数据进行发送