什么是NAPI
NAPI是linux一套最新的处理网口数据的API,linux 2.5引入的,所以很多驱动并不支持这种操作方式。简单来说,NAPI是综合中断方式与轮询方式的技术。数据量很低与很高时,NAPI可以发挥中断方式与轮询方式的优点,性能较好。如果数据量不稳定,且说高不高说低不低,则NAPI会在两种方式切换上消耗不少时间,效率反而较低一些。
下面会用到netdev_priv()这个函数,这里先讲解下,每个网卡驱动都有自己的私有的数据,来维持网络的正常运行,而这部分私有数据放在网络设备数据后面(内存概念上),这个函数就是通过dev来取得这部分私有数据,注间这部分私有数据不在dev结构体中,而是紧接在dev内存空间后。
static inline void *netdev_priv(const struct net_device *dev)
return (char *)dev + ALIGN(sizeof(struct net_device), NETDEV_ALIGN);
弄清这个函数还得先清楚dev这个结构的分配
alloc_netdev() -> alloc_netdev_mq()
struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
void (*setup)(struct net_device *), unsigned int queue_count)
alloc_size = sizeof(struct net_device);
if (sizeof_priv) {
/* ensure 32-byte alignment of private area */
alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
alloc_size += sizeof_priv;
/* ensure 32-byte alignment of whole construct */
alloc_size += NETDEV_ALIGN - 1;
p = kzalloc(alloc_size, GFP_KERNEL);
if (!p) {
printk(KERN_ERR "alloc_netdev: Unable to allocate device./n");
return NULL;
可以看到,dev在分配时,即在它的后面分配了private的空间,需要注意的是,这两部分都是32字节对齐的,如下图所示,padding是加入的的补齐字节:
举个例子,假设sizeof(net_device)大小为31B,private大小45B,则实际分配空间如图所示:
b44_interrupt():当有数据包收发或发生错误时,会产生硬件中断,该函数被触发
struct b44 *bp = netdev_priv(dev);
取出网卡驱动的私有数据private,该部分数据位于dev数据后面
istat = br32(bp, B44_ISTAT);
imask = br32(bp, B44_IMASK);
读出当前中断状态和中断屏蔽字
if (istat) {
if (napi_schedule_prep(&bp->napi)) {
bp->istat = istat;
__b44_disable_ints(bp);
__napi_schedule(&bp->napi);
设置NAPI为SCHED状态,记录当前中断状态,关闭中断,执行调度
void __napi_schedule(struct napi_struct *n)
unsigned long flags;
local_irq_save(flags);
list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
local_irq_restore(flags);
__get_cpu_var():得到当前CPU的偏移量,与多CPU有关
将
napi
的
poll_list
加入到
softnet_data
队列尾部,然后引起软中断
NET_RX_SOFTIRQ
。
似乎还没有真正的收发函数出现,别急;
关于软中断的机制请参考资料
,在
net_dev_init()[dev.c]
中,注册了两个软中断处理函数,所以引起软中断后,最终调用了
net_rx_action()
。
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
下面来看下net_rx_action()函数实现:
static void net_rx_action(struct softirq_action *h)
struct list_head *list = &__get_cpu_var(softnet_data).poll_list; // [1]
n = list_first_entry(list, struct napi_struct, poll_list); // [2]
work = 0;
if (test_bit(NAPI_STATE_SCHED, &n->state)) {
work = n->poll(n, weight); // [3]
trace_napi_poll(n);
__get_cpu_var是不是很熟悉,在b44_interrupt()中才向它的poll_list中加入了一个napi_struct;代码[2]很简单了,从poll_list的头中取出一个napi_struct,然后执行代码[3],调用poll()函数;注意到这里在interrupt时,会向poll_list尾部加入一个napi_struct,并引起软中断,在软中断处理函数中,会从poll_list头部移除一个napi_struct,进行处理,理论上说,硬件中断加入的数据在其引起的软中断中被处理。
poll函数实际指向的是b44_poll(),这是显而易见的,但具体怎样调用的呢?在网卡驱动初始化函数b44_init_one()有这样一行代码:
netif_napi_add(dev, &bp->napi, b44_poll, 64);
而netif_napi_add()中初始化napi并将其加入dev的队列,
napi->poll = poll;
这行代码就是b44_poll赋给napi_poll,所以在
NET_RX_SOFTIRQ
软中断处理函数
net_rx_action()
中执行的
b44_poll()
。
怎么到这里都还没有收发数据包的函数呢!
b44_poll()
就是轮询中断向量,查找出引起本次操作的中断;
static int b44_poll(struct napi_struct *napi, int budget)
if (bp->istat & (ISTAT_TX | ISTAT_TO))
b44_tx(bp);
if (bp->istat & ISTAT_RX)
work_done += b44_rx(bp, budget);
if (bp->istat & ISTAT_ERRORS)
可以看到,查询了四种中断:
ISTAT_TX
、
ISTAT_TO
、
ISTAT_RX
、
ISTAT_ERRORS
#define
ISTAT_TO 0x00000080 /* General Purpose Timeout */
#define
ISTAT_RX 0x00010000 /* RX Interrupt */
#define
ISTAT_TX 0x01000000 /* TX Interrupt */
#define ISTAT_ERRORS (ISTAT_DSCE|ISTAT_DATAE|ISTAT_DPE|ISTAT_RDU|ISTAT_RFO|ISTAT_TFU)
如果是
TX
中断,则调用
b44_tx
发送数据包;如果是
RX
中断,则调用
b44_rx
接收数据包。至此,从网卡驱动收发数据包的调用就是如此了。
最后,给个总结性的图:
NAPI是
linux
一套最新的处理网口数据的API,
linux
2.5引入的,所以很多驱动并不支持这种操作方式。简单来说,NAPI是综合中断方式与轮询方式的技术。数据量很低与很高时,NAPI可以发挥中断方式与轮询方式的优点,性能较好。如果数据量不稳定,且说高不高说低不低,则NAPI会在两种方式切换上消耗不少时间,效率反而较低一些。
下面会用到netdev_priv()这个函数,这里先讲解下
在netif_receive_skb()函数中,可以看出处理的是像ARP、IP这些链路层以上的协议,那么,链路层报头是在哪里去掉的呢?答案是网卡驱动中,在
调用
netif_receive_skb()前,
skb->protocol = eth_type_trans(skb, bp->dev);
该函数对处理后skb>data跳过以太网报头,由mac_header指示以太网报头:
本系列文章总结
Linux
网络
栈,包括:
(1)
Linux
网络
协议栈总结
(2)非虚拟化
Linux
环境中的
网络
分段卸载技术 GSO/TSO/UFO/LRO/GRO
(3)QEMU/KVM + VxLAN 环境下的 Segmentation Offloading 技术(发送端)
(4)QEMU/KVM + VxLAN 环境下的 Segmentation Offloading 技术(接收...
本文收录在我的博客 http://www.sskywatcher.com/blog/archives/50Chapter 1. 如何设置参数
linux
内核
的
网络
参数可以有俩个不同的方法设置. 第一个方法是通过现在大多数
linux
发行版默认安装的 sysctl 工具. 另一个方法就是利用 /proc文件系统,这个方法应该是自从有了/proc文件系统以来所有的
linux
内核
都支持的. 使用sysct...
XFRM框架
IPsec基于XFRM(读作“transform”)框架实现,该框架源于USAGI项目,其目标是提供一种产品化的IPv6和IPsec协议栈。术语“transform”指的是在
内核
协议栈中根据一些IPsec规则转发入方向报文或者出方向报文。
Linux
内核
2.5引入了XFRM框架,XFRM是一种协议族独立的基本框架,这意味着对于IPv4和IPv6而言存在一个通用的
Linux
内核
分析
-
网络
[一]:
收发
数据包
的
调用
http://www.
linux
idc.com/
Linux
/2011-05/36063.htm
Linux
内核
分析
-
网络
[二]:网卡驱动接收报文 http://www.
linux
idc.com/
Linux
/2011-05/36064.htm
Linux
内核
分析
-
网络
[三]:从netif_receive_skb()说起 http://...
1. 前言
本文是“
Linux
内核
分析
”系列文章的第一篇,会以
内核
的核心功能为出发点,描述
Linux
内核
的整体架构,以及架构之下主要的软件子系统。之后,会介绍
Linux
内核
源文件的目录结构,并和各个软件子系统对应。
注:本文和其它的“
Linux
内核
分析
”文章都基于如下约定:
a)
内核
版本为
Linux
3.10.29(该版本是一个long term的版本,会被
Linux
社区持续维