网络数据包收发流程一从驱动到协议栈.docx
《网络数据包收发流程一从驱动到协议栈.docx》由会员分享,可在线阅读,更多相关《网络数据包收发流程一从驱动到协议栈.docx(28页珍藏版)》请在冰点文库上搜索。
网络数据包收发流程一从驱动到协议栈
网络数据包收发流程
(一):
从驱动到协议栈 2011-06-2711:
28:
20
分类:
LINUX
早就想整理网络数据包收发流程了,一直太懒没动笔。
今天下决心写了
一、硬件环境
intel82546:
PHY与MAC集成在一起的PCI网卡芯片,很强大
bcm5461:
PHY芯片,与之对应的MAC是TSEC
TSEC:
ThreeSpeedEthernetController,三速以太网控制器,PowerPc架构CPU里面的MAC模块
注意,TSEC内部有DMA子模块
话说现在的CPU越来越牛叉了,什么功能都往里面加,最常见的如MAC功能。
TSEC只是MAC功能模块的一种,其他架构的cpu也有和TSEC类似的MAC功能模块。
这些集成到CPU芯片上的功能模块有个学名,叫平台设备,即platformdevice。
二、网络收包原理
网络驱动收包大致有3种情况:
noNAPI:
mac每收到一个以太网包,都会产生一个接收中断给cpu,即完全靠中断方式来收包
缺点是当网络流量很大时,cpu大部分时间都耗在了处理mac的中断。
netpoll:
在网络和I/O子系统尚不能完整可用时,模拟了来自指定设备的中断,即轮询收包。
缺点是实时性差
NAPI:
采用中断+轮询的方式:
mac收到一个包来后会产生接收中断,但是马上关闭。
直到收够了netdev_max_backlog个包(默认300),或者收完mac上所有包后,才再打开接收中断
通过sysctl来修改 dev_max_backlog
或者通过proc修改/proc/sys/net/core/netdev_max_backlog
下面只写内核配置成使用NAPI的情况,只写TSEC驱动。
(非NAPI的情况和PCI网卡驱动以后再说)
内核版本linux2.6.24
三、NAPI相关数据结构
每个网络设备(MAC层)都有自己的net_device数据结构,这个结构上有napi_struct。
每当收到数据包时,网络设备驱动会把自己的napi_struct挂到CPU私有变量上。
这样在软中断时,net_rx_action会遍历cpu私有变量的poll_list,
执行上面所挂的napi_struct结构的poll钩子函数,将数据包从驱动传到网络协议栈。
四、内核启动时的准备工作
4.1初始化网络相关的全局数据结构,并挂载处理网络相关软中断的钩子函数
start_kernel()
-->rest_init()
-->do_basic_setup()
-->do_initcall
-->net_dev_init
__init net_dev_init()
{
//每个CPU都有一个CPU私有变量_get_cpu_var(softnet_data)
//_get_cpu_var(softnet_data).poll_list很重要,软中断中需要遍历它的
for_each_possible_cpu(i){
structsoftnet_data*queue;
queue=&per_cpu(softnet_data,i);
skb_queue_head_init(&queue->input_pkt_queue);
queue->completion_queue=NULL;
INIT_LIST_HEAD(&queue->poll_list);
queue->backlog.poll=process_backlog;
queue->backlog.weight=weight_p;
}
open_softirq(NET_TX_SOFTIRQ, net_tx_action,NULL); //在软中断上挂网络发送handler
open_softirq(NET_RX_SOFTIRQ, net_rx_action,NULL); //在软中断上挂网络接收handler
}
4.2加载网络设备的驱动
NOTE:
这里的网络设备是指MAC层的网络设备,即TSEC和PCI网卡(bcm5461是phy)
在网络设备驱动中创建net_device数据结构,并初始化其钩子函数open(),close()等
挂载TSEC的驱动的入口函数是 gfar_probe
//平台设备TSEC的数据结构
staticstructplatform_drivergfar_driver={
.probe= gfar_probe,
.remove=gfar_remove,
.driver={
.name="fsl-gianfar",
},
};
int gfar_probe(structplatform_device*pdev)
{
dev=alloc_etherdev(sizeof(*priv)); //创建net_device数据结构
dev->open= gfar_enet_open;
dev->hard_start_xmit= gfar_start_xmit;
dev->tx_timeout=gfar_timeout;
dev->watchdog_timeo=TX_TIMEOUT;
#ifdefCONFIG_GFAR_NAPI
netif_napi_add(dev,&priv->napi,gfar_poll,GFAR_DEV_WEIGHT); //软中断里会调用poll钩子函数
#endif
#ifdefCONFIG_NET_POLL_CONTROLLER
dev->poll_controller=gfar_netpoll;
#endif
dev->stop=gfar_close;
dev->change_mtu=gfar_change_mtu;
dev->mtu=1500;
dev->set_multicast_list=gfar_set_multi;
dev->set_mac_address=gfar_set_mac_address;
dev->ethtool_ops=&gfar_ethtool_ops;
}
五、启用网络设备
5.1用户调用ifconfig等程序,然后通过ioctl系统调用进入内核
socket的ioctl()系统调用
-->sock_ioctl()
-->dev_ioctl() //判断SIOCSIFFLAGS
-->__dev_get_by_name(net,ifr->ifr_name) //根据名字选net_device
-->dev_change_flags() //判断IFF_UP
-->dev_open(net_device) //调用open钩子函数
对于TSEC来说,挂的钩子函数是 gfar_enet_open(net_device)
5.2在网络设备的open钩子函数里,分配接收bd,挂中断ISR(包括rx、tx、err),对于TSEC来说
gfar_enet_open
-->给RxTxBd分配一致性DMA内存
-->把RxBd的“EA地址”赋给数据结构,物理地址赋给TSEC寄存器
-->把TxBd的“EA地址”赋给数据结构,物理地址赋给TSEC寄存器
-->给tx_skbuff指针数组分配内存,并初始化为NULL
-->给rx_skbuff指针数组分配内存,并初始化为NULL
-->初始化TxBd
-->初始化RxBd,提前分配存储以太网包的skb,这里使用的是一次性dma映射
(注意:
#defineDEFAULT_RX_BUFFER_SIZE 1536保证了skb能存一个以太网包)
rxbdp=priv->rx_bd_base;
for(i=0;irx_ring_size;i++){
structsk_buff*skb=NULL;
rxbdp->status=0;
//这里真正分配skb,并且初始化rxbpd->bufPtr,rxbdpd->length
skb=gfar_new_skb(dev,rxbdp);
priv->rx_skbuff[i]=skb;
rxbdp++;
}
rxbdp--;
rxbdp->status|=RXBD_WRAP; //给最后一个bd设置标记WRAP标记
-->注册TSEC相关的中断handler:
错误,接收,发送
request_irq(priv->interruptError, gfar_error,0,"enet_error",dev)
request_irq(priv->interruptTransmit, gfar_transmit,0,"enet_tx",dev)//包发送完
request_irq(priv->interruptReceive, gfar_receive,0,"enet_rx",dev) //包接收完
-->gfar_start(net_device)
//使能Rx、Tx
//开启TSEC的DMA寄存器
//Mask掉我们不关心的中断event
最终,TSEC相关的Bd等数据结构应该是下面这个样子的
六、中断里接收以太网包
TSEC的RX已经使能了,网络数据包进入内存的流程为:
网线-->Rj45网口-->MDI差分线
-->bcm5461(PHY芯片进行数模转换)-->MII总线
-->TSEC的DMAEngine会自动检查下一个可用的Rxbd
-->把网络数据包DMA到Rxbd所指向的内存,即skb->data
接收到一个完整的以太网数据包后,TSEC会根据eventmask触发一个Rx外部中断。
cpu保存现场,根据中断向量,开始执行外部中断处理函数do_IRQ()
do_IRQ伪代码
{
上半部处理硬中断
查看中断源寄存器,得知是网络外设产生了外部中断
执行网络设备的rx中断handler(设备不同,函数不同,但流程类似,TSEC是gfar_receive)
1.mask掉rxevent,再来数据包就不会产生rx中断
2.给napi_struct.state加上NAPI_STATE_SCHED状态
3.挂网络设备自己的napi_struct结构到cpu私有变量_get_cpu_var(softnet_data).poll_list
4.触发网络接收软中断
下半部处理软中断
依次执行所有软中断handler,包括timer,tasklet等等
执行网络接收的软中断handler net_rx_action
1.遍历cpu私有变量_get_cpu_var(softnet_data).poll_list
2.取出poll_list上面挂的napi_struct结构,执行钩子函数napi_struct.poll()
(设备不同,钩子函数不同,流程类似,TSEC是gfar_poll)
3.若poll钩子函数处理完所有包,则打开rxeventmask,再来数据包的话会产生rx中断
4.调用napi_complete(napi_struct*n)
把napi_struct结构从_get_cpu_var(softnet_data).poll_list上移走
同时去掉napi_struct.state的NAPI_STATE_SCHED状态
}
6.1TSEC的接收中断处理函数
gfar_receive
{
#ifdefCONFIG_GFAR_NAPI
//test_and_set当前net_device的napi_struct.state为NAPI_STATE_SCHED
//在软中断里调用net_rx_action会检查状态napi_struct.state
if(netif_rx_schedule_prep(dev,&priv->napi)){
tempval=gfar_read(&priv->regs->imask);
tempval&=IMASK_RX_DISABLED; //mask掉rx,不再产生rx中断
gfar_write(&priv->regs->imask,tempval);
//将当前net_device的napi_struct.poll_list挂到
//CPU私有变量__get_cpu_var(softnet_data).poll_list上,并触发软中断
//所以,在软中断中调用net_rx_action的时候,就会执行当前net_device的
//napi_struct.poll()钩子函数,即gfar_poll()
__netif_rx_schedule(dev,&priv->napi);
}
#else
gfar_clean_rx_ring(dev,priv->rx_ring_size);
#endif
}
6.2 网络接收软中断net_rx_action
net_rx_action()
{
structlist_head*list=&__get_cpu_var(softnet_data).poll_list;
//通过napi_struct.poll_list,将N多个napi_struct链接到一条链上
//通过CPU私有变量,我们找到了链头,然后开始遍历这个链
intbudget=netdev_budget; //这个值就是dev_max_backlog,通过sysctl来修改
while(!
list_empty(list)){
structnapi_struct*n;
intwork,weight;
local_irq_enable();
//从链上取一个napi_struct结构(接收中断处理函数里加到链表上的,如gfar_receive)
n=list_entry(list->next,structnapi_struct,poll_list);
weight=n->weight;
work=0;
if(test_bit(NAPI_STATE_SCHED,&n->state)) //检查状态标记,此标记在接收中断里加上的
work=n->poll(n,weight); //使用NAPI的话,使用的是网络设备自己的napi_struct.poll
//对于TSEC是,是gfar_poll
WARN_ON_ONCE(work>weight);
budget-=work;
local_irq_disable();
if(unlikely(work==weight)){
if(unlikely(napi_disable_pending(n)))
__napi_complete(n); //操作napi_struct,把去掉NAPI_STATE_SCHED状态,从链表中删去
else
list_move_tail(&n->poll_list,list);
}
netpoll_poll_unlock(have);
}
out:
local_irq_enable();
}
staticint gfar_poll(structnapi_struct*napi,intbudget)
{
structgfar_private*priv=container_of(napi,structgfar_private,napi);
structnet_device*dev=priv->dev; //TSEC对应的网络设备
inthowmany;
//根据dev的rxbd,获取skb并送入协议栈,返回处理的skb的个数,即以太网包的个数
howmany= gfar_clean_rx_ring(dev,budget);
//下面这个判断比较有讲究的
//收到的包的个数小于budget,代表我们在一个软中断里就全处理完了,所以打开 rx硬中断
//要是收到的包的个数大于budget,表示一个软中断里处理不完所有包,那就不打开 rx硬中断,
// 此次软中断的下一轮循环里再接着处理,直到包处理完(即howmany if(howmany netif_rx_complete(dev,napi);
gfar_write(&priv->regs->rstat,RSTAT_CLEAR_RHALT);
//打开rx硬中断,rx硬中断是在gfar_receive()中被关闭的
gfar_write(&priv->regs->imask,IMASK_DEFAULT);
}
returnhowmany;
}
gfar_clean_rx_ring(dev,budget)
{
bdp=priv->cur_rx;
while(!
((bdp->status&RXBD_EMPTY)||(--rx_work_limit<0))){
rmb();
skb=priv->rx_skbuff[priv->skb_currx]; //从rx_skbuff[]中获取skb
howmany++;
dev->stats.rx_packets++;
pkt_len=bdp->length-4; //从length中去掉以太网包的FCS长度
gfar_process_frame(dev,skb,pkt_len);
dev->stats.rx_bytes+=pkt_len;
dev->last_rx=jiffies;
bdp->status&=~RXBD_STATS; //清rxbd的状态
skb=gfar_new_skb(dev,bdp); //Addanotherskbforthefuture
priv->rx_skbuff[priv->skb_currx]=skb;
if(bdp->status&RXBD_WRAP) //更新指向bd的指针
bdp=priv->rx_bd_base; //bd有WARP标记,说明是最后一个bd了,需要“绕回来”
else
bdp++;
priv->skb_currx=(priv->skb_currx+1)&RX_RING_MOD_MASK(priv->rx_ring_size);
}
priv->cur_rx=bdp;/*Updatethecurrentrxbdpointertobethenextone*/
returnhowmany;
}
gfar_process_frame()
-->skb->protocol=eth_type_trans(skb,dev);//确定网络层包类型,IP、ARP、VLAN等等
-->RECEIVE(skb) //调用netif_receive_skb(skb)进入协议栈
#ifdefCONFIG_GFAR_NAPI
#defineRECEIVE(x)netif_receive_skb(x)
#else
#defineRECEIVE(x)netif_rx(x)
#endif
------------------------------------华丽的分割线---------------------------------------