网络子系统大杂烩

阅读《深入理解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中。之后就是流控出队列。

你可能感兴趣的:(网络子系统大杂烩)