阅读《深入理解linux网络技术内幕》和Linux2.6.37.1源码记录
三个数据结构
1.net_device
2.softnet_data
3.sk_buff
softnet_data是一个cpu对应一个。
网卡接收数据的流程如下
网卡收到数据收首先会出发中断,导致执行中断处理函数dm9000_rx。
以太网驱动接收函数中的skb_reserve(skb, 2)
这是为了保证紧接Ethernet报头的ip报头可以从四节对齐的地址开始
因为Ethernet报头为14字节(目标mac+源mac+类型 = 6+6+2),所以需要额外填充
2字节。
值得一提的是,此中断处理函数在dm9000_open函数中注册,并非在driver_probe
函数中注册。中断处理函数的具体注册过程如下:
(net/core/dev.c)dev_open->__dev_open-> dm9000_open(ops->ndo_open)
最后在dm9000_open中通过request_irq注册网卡的中断处理函数dm9000_interrupt
在中断处理函数dm9000_interrupt中首先通过spin_lock_irqsave获取锁并将中断关闭,
然后判断中断的类型,是接收中断还是发送中断或者是其它,若是接收中断则调用
dm9000_rx函数进行帧接收。
linux2.6.37.1的dm9000驱动中 帧接收方式 采用的是中断期间处理多帧的策略(具体可见<网络技术内幕>)
在接收时,若网卡产生一次中断则会持续进行帧接收。多帧接收的具体实现是在dm9000_rx函数中实现的。
dm9000_rx函数的主体是一个do...while循环,若网卡一直有数据则会持续进行帧接收(中断期间处理多帧),
而此时的中断是关闭的(被spin_lock_irqsave关闭)。每次接受下来的帧则通过netif_rx->enqueue_to_backlog
函数将接收到的skb添加到softnet_data的input_pkt_queue队列中。
dm9000的数据接收方式为非napi方式,采用napi方式和非napi方式的重要区别在帧接收处理。
napi的接受过程如下:
网卡产生中断,调度napi_schedule执行
在napi_schedule中触发软中断
软中断处理函数轮训poll_list
调用napi_struct中的poll方法
在poll方法中执行帧接收等操作
而非napi的操作是方式是如dm9000所示:
网卡产生中断,通过dm9000rx、netif_rx在中断期间接受多帧
将接收到的帧存放到入口队列中,然后触发软中断
在软中断处理函数中轮询poll_list
调用的是非napi的系统默认poll方法process_backlog
在prcess_backlog中处理入口队列的帧
这两种方法的区别在于napi接收过程在中断下半部的poll中完成
非napi在中断处理函数中完成
内核中关于napi的问题
napi和非napi最大的区别在于 napi需要在网卡初始化的时候(网卡初始化分在probe和open两个函数中完成)
通过函数netif_napi_add初始化一个napi_struct数据中的poll方法,而非napi直接使用的是在net_dev_inti函数中
初始化的默认poll函数procee_backlog。最关键的区别在于这两种poll函数的工作方式。
封包类型设置
skb->protocol = eth_type_trans(skb, dev)
eth_type_trans函数其实需要完成两件事情,1.是设置包类型2.设置协议
设置包协议的是通过
skb->pkt_type = PACKET_BROADCAST;
skb->pkt_type = PACKET_MULTICAST;
skb->pkt_type = PACKET_OTHERHOST;
来实现的,具体需要根据mac_addr中ethernet的报头6+6+2)第一个6中的第一个byte的最高两位来普安段是
单播还是多播。如果未显式的设置skb->pkt_type则其实为0,表示PACKET_HOST,即MAC地址匹配。
封包类型设置完毕则进行协议设置。协议的类型可设置为:
1.ethernet帧
2.raw 802.3
3.802.3/LLC
4.802.3/SNAP
各协议之间的区别可参考 网络技术内幕 P289
主要区别是6+6+2中的2字节内数值表示的含义不同
2域中存放的的数值
>1536 表示这是一个ethernet帧
<1500的表示需要使用LLC报头的帧
802.3/LLC: DSAP SSAP CTL
802.3/SNAP:DSAP SSAP CTL SNAP(其中DSAP SSAP CTL固定)
若不是多播帧则将帧中的mac地址与本机mac做比较,判断是否是传给本机的。若是传给本机的
则解包提取出报文头中的上层使用的协议类型。
出处:http://www.blogjava.net/shiliqiang/articles/304505.html
2.6.24.4内核网络接收数据包分析
瀚海书香
在2.6.24.4中所有的网卡,不管是否支持napi,都是通过struct napi_struct结构进行。所有我们先说一下这个结构。
struct napi_struct{
struct list_head poll_list;
unsigned long state;
int weight;
int (*poll)(struct napi_struct *,int);
}
对应支持napi的网卡,自己填充这个结构体;而非napi网卡,则使用per cpu的softnet_data>backlog,这个结构的初始化在net_dev_init()中完成。
我们先说一下非napi机制的网卡:
网卡接收到数据包后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机制的网卡:
网卡初始化是会自己初始化一个自己的数据包接收队列,当有数据包到达时,将数据包dma到自己的数据包队列中,如果自己的napi没有调度,则napi_schedule(mynapi),这里的mynapi是网卡自己的napi_struct.napi_schedule()会将网卡自己的poll_list挂接到softnet_data->poll_list上,同时出发软中断NET_RX_SOFTIRQ。NET_RX_SOFTIRQ软中断,调用相应的函数net_rx_action()。
net_rx_action():
首先获取softnet_data->poll_list,通过遍历poll_list,获取每个poll_list对应的napi_struct结构(container_of实现),然后根据napi_struct的weight调用poll函数,如果是非napi网卡,这里的napi_struct是backlog,所以poll函数就是process_backlog;如果是napi的网卡,则会使自己的poll函数。
napi网卡的poll函数就是从自己数据包队列中dequeue出一个skb,然后调用netif_receive_skb().
非napi的process_backlog会获取softnet_data->input_pkt_queue,然后对队列input_pkt_queue进行dequeue操作,获得一个skb,之后调用netif_receive_skb(skb)。
netif_receive_skb():
对skb做一些准备工作,例如设置mac_len等,调用deliver_skb()给所有的注册ptype_all类型的协议处理handle,然后是网桥和VLAN的处理,之后会给注册的相应协议的ptype_base的handle。这里假设是ip协议,则会调用相应的ip协议handle的处理函数ip_rcv。
ip_rcv():
对skb做一些检查工作,如果skb->users!=1,则clone一个skb,之后会转入netfilter的NF_IP_PRE_ROUTING的hook点,调用所有在该点注册的hook函数。比如说如果开启了conntrack,则会在这里进行数据包重组。之后调用ip_rcv_finish().
ip_rcv_finish():
首先调用ip_route_input()决定数据包的路由,初始化skb->dst,调用dst_input(skb).
dst_input():
实际上是调用skb->dst->input(skb),对应input的初始化在route.c中。如果是发往本地的数据包dst->input=ip_local_deliver;如果是转发的数据包dst->input=ip_forward;
本地流程:
ip_local_deliver():
首先是对分片的数据包重组,会转入netfilter的NF_IP_LOCAL_IN的hook点,调用所有在该点注册的hook函数。之后会调用ip_local_deliver_finish(),之后就到第四层了。
转发流程:
ip_forward():
做一些源路由等方面的检查后,会转入netfilter的NF_IP_FORWARD的hook点,调用所有在该点注册的hook函数。之后会调用ip_forward_finish().
ip_forward_finish():
调用dst_output().
dst_output():
skb->dst->output(skb).一般output=ip_output.
ip_output():
设置skb的dev为发包的dev,同时设置skb->protocol,会转入netfilter的NF_IP_POST_ROUTING的hook点,调用所有在该点注册的hook函数。之后会调用ip_finish_output().
ip_finish_output():
检查一下数据包是否需要分片,如果需要分片,则进行ip_fragement(),之后调用ip_finish_output2().
ip_finish_output2():
根据neighbour,调用dst->neighbour->output.
到这为止,数据包会经过dev_queue_xmit放入dev的qdisc中。之后就是流控出队列。