链路层
链路层主要有3个目的:
1. 为IP模块发送和接收IP数据
2. 为ARP模块发送ARP请求和接收ARP应答
3. 为RARP发送RARP请求和接收RARP应答
TCP/IP协议支持多种不同的链路层协议,这取决于网络所使用的硬件。
以太网是当今TCP/IP采用的主要的局部网技术。它采用一种称作CSMA/CD的媒体接入方法,其意思是带冲突检测的载波侦听多路接入。速率为10Mb/s,地址为48bit。
A R P和R A R P协议对32 bit的I P地址和48 bit的硬件地址进行映射。
数据链路层主要工作:
接收:
将由网络设备驱动程序从设备硬件缓冲区复制到内核地址空间的网络数据帧挂到CPU的输入队列,并通知上层协议有网络数据帧到达,随后上层协议就可以从CPU输入队列中获取网络数据帧并处理了
发送:
上层协议实例要向外发送数据帧会由数据链路层放到设备输入队列,再由设备驱动程序的硬件发送函数hard_start_xmit将设备输入队列中的数据帧复制到设备硬件缓冲区中,实现对外发送
网络设备在收到数据帧后由驱动程序将数据拷贝到内核地址空间,在数据链路层中如何将数据帧放入CPU的输入队列呢?
内核网络子系统实现了两种机制
l netif_rx
网络设备将数据复制到socket buffer后,调用数据链路层方法。他通知内核接收到了网络数据帧,标记网络接收软件中断,执行接收数据的后续处理。这种机制每接收到一个数据帧,就会产生一个接收中断。
l NAPI
在一次中段处理中接收多个数据。在设备中断处理程序中调用的函数是netif_rx_schedule。
1.1 netif_rx函数分析
在此,我们首先从一个具体的网卡驱动来分析下,dm9000网卡驱动分析。
网卡发生中断,进入中断处理函数dm9000_interrupt,其中调用dm9000_rx处理接收的数据,在函数dm9000_rx中会申请struct sk_buff *skb结构,
skb = dev_alloc_skb()
申请数据内存后,根据函数eth_type_trans判断数据帧的格式,赋值给skb->protocol,然后调用函数netif_rx(skb)作进一步处理。
/* Pass to upper layer */
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);
此处举个例子对于skb->protocol 的值:ETH_P_802_3。
在函数eth_type_trans中会设置skb->pkt_type的值,例如:PACKET_BROADCAST。
不同的设备类型使用不同的功能:例如,以太设备使用eth_type_trans,而Token Ring interfaces则使用tr_type_trans。
接着是调用函数了,这函数将buffer传递给上层的网络。
该函数的主要功能是从网络设备驱动中接收一个数据包,然后将其发送到上层协议,等待上层协议的进一步处理(由软件中断NET_RX_SOFTIRQ来执行)。函数总是成功。数据包可能会由于阻塞而被丢弃掉。
函数返回值:
* NET_RX_SUCCESS (no congestion)
* NET_RX_DROP (packet was dropped)
01933: int netif_rx(struct sk_buff *skb) 01934: { 01935: struct softnet_data *queue; 01936: unsigned long flags; 01937: 01938: / * if netpoll wants it, pretend we never saw it */ 01939: if (netpoll_rx(skb)) 01940: return NET_RX_DROP;
01941: ///得到帧被接收的时间 01942: if (! skb- >tstamp.tv64) 01943: net_timestamp(skb); 01944: 01945: / * 01946: * The code is rearranged so that the path is the most 01947: * short when CPU is congested, but is still operating. 01948: */ 01949: local_irq_save(flags); ///取得当前cpu的softnet_data数据 01950: queue = &__get_cpu_var(softnet_data); 01951: ///将被当前cpu所接受的总帧的数目加一 01952: __get_cpu_var(netdev_rx_stat).total++; ///监测设备是否还有空间来存储帧,如果空间已满,表示网络阻塞严重,则返回一个错误,此后cpu将丢掉再来的帧。 01953: if (queue- >input_pkt_queue.qlen <= netdev_max_backlog) { 01954: if (queue- >input_pkt_queue.qlen) { 01955: enqueue: ///这个帧被加入到softnet_data的输入队列。并返回成功。 01956: __skb_queue_tail(&queue- >input_pkt_queue, skb); 01957: local_irq_restore(flags); 01958: return NET_RX_SUCCESS; 01959: } 01960: ///当队列是空的时候,表明这个队列并没有被软中断所schedule,因此我们需要将此队列加入到软中断的处理链表中。可以看到加入的正好是backlog,由于调用netif_rx的是非napi的驱动,因此backlog就是初始化时的process_backlog函数。 01961: napi_schedule(&queue->backlog); 01962: goto enqueue; 01963: } 01964: 01965: __get_cpu_var(netdev_rx_stat).dropped++; 01966: local_irq_restore(flags); 01967: 01968: kfree_skb(skb); 01969: return NET_RX_DROP; 01970: } ? end netif_rx ? |
数据帧在推送给上层协议的时候,是在网络子系统的接收软件中断处理完成的。软件中断是硬件中断处理程序的后半段,在网络数据帧的接收处理过程中,硬件中断服务程序只完成最紧要的任务,即数据从硬件缓冲区复制到内核地址空间,以防止数据丢失。而对数据帧的处理,就由网络子系统的软件中断处理程序在适当的时候完成。
网络子系统中,将CPU输入队列中的数据包传送给上层协议的处理函数是process_backlog,此函数的地址在网络子系统初始化时传给了管理CPU队列的数据结构struct softnet_data的backlog数据域的poll函数指针。
queue->backlog.poll = process_backlog;
此处有个注意的地方:
netif_rx(函数在接收到第一个网络数据帧时,会调用napi_schedule函数 。
网卡接收到数据包后dma到内核空间,然后调用netif_rx()将数据包挂接到softnet_data>input_pkt_queue中,如果backlog这个napi_struct没有被调度,则napi_schedule(&backlog).napi_schedule()会将backlog的poll_list挂接到softnet_data->poll_list上,同时出发软中断NET_RX_SOFTIRQ。NET_RX_SOFTIRQ软中断,调用相应的函数net_rx_action()。
不管是支持napi还是不支持napi的设备,都是通过struct napi_struct数据结构进行调度的了,对于不支持struct napi_struct的设备,在每CPU变量中定义了一个backlog变量,在进行调度的时候,会将其挂载到poll_list中。
napi_schedule函数分析
static inline void napi_schedule(struct napi_struct *n) { if (napi_schedule_prep(n)) __napi_schedule(n); } |
napi_schedule是对具体__napi_schedule函数的一个封装,而napi_schedule_prep函数则是进行一些必要的检查工作。
napi_schedule_prep是否禁止调用,是否已经在调用了,主要查看的是struct napi_struct的stata数据域中相关位状态。
检查完后再调用__napi_schedule.
void __napi_schedule(struct napi_struct *n) { unsigned long flags;
local_irq_save(flags); 将传入的struct napi_struct加入到每CPU变量softnet_data的poll_list队列中 list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list); __raise_softirq_irqoff(NET_RX_SOFTIRQ); local_irq_restore(flags); } |
在硬件中断中我们只是完成了最紧急的事情,将数据加入到每CPU变量softnet_data的输入队列中,只是进行数据指针的复制,速度很快。然后对于数据帧的具体处理将留到软中断中处理。