Linux内核数据调用,Linux内核分析 - 网络[一]:收发数据包的调用

什么是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是加入的的补齐字节:

0818b9ca8b590ca3270a3433284dd417.png

举个例子,假设sizeof(net_device)大小为31B,private大小45B,则实际分配空间如图所示:

0818b9ca8b590ca3270a3433284dd417.png

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接收数据包。至此,从网卡驱动收发数据包的调用就是如此了。

最后,给个总结性的图:

0818b9ca8b590ca3270a3433284dd417.png

查看评论

5楼

windrivers 2013-08-07 20:43发表

[回复]

0818b9ca8b590ca3270a3433284dd417.png

你好,看了下newapi的代码,感觉相较于非NAPI,主要是省掉了硬中断提报到cpu队列的过程,其他没有什么变化,不知道文章里的NAPI的提升主要是综合中断和轮询是怎么来的?非NAPI也会用到中断和轮询啊

4楼

bobjoehill 2013-02-01 11:59发表

[回复]

0818b9ca8b590ca3270a3433284dd417.png

明明说的是32字节对齐啊

另外,NETDEV_ALIGN=32

yo-yo为啥说是4字节对齐?有什么特别的原因?

Re:

yo-yo 2013-02-26 09:41发表

[回复]

0818b9ca8b590ca3270a3433284dd417.png

回复bobjoehill:这一部分是我写错了,文章已修正,应该是32字节对齐,谢谢指正

3楼

ojccc2 2012-03-08 08:51发表

[回复]

0818b9ca8b590ca3270a3433284dd417.png

"可以看到,dev在分配时,即在它的后面分配了private的空间,需要注意的是,这两部分都是4字节对齐的,如下图所示,padding是加入的的补齐字节:"

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;

为什么是4字节对齐呢?,注释说是32字节对齐呀?NETDEV_ALIGN等于32

/* ensure 32-byte alignment of whole construct */

alloc_size += NETDEV_ALIGN - 1;

alloc_size加个31为啥就能保持对齐?

求教育。。。

Re:

yo-yo 2012-03-14 08:52发表

[回复]

0818b9ca8b590ca3270a3433284dd417.png

回复ojccc2:我是这样理解的。它要保证的是:net_device是4节字对齐的,private是4字节对齐的,起始地址即dev是四字节对齐的。if(sizeof_priv){}这段保证了net_device结构体是4字节对齐的

alloc_size+=NETDEV_ALIGN-1与后面的代码dev=PTR_ALIGN(p, NETDEV_ALIGN)结合起来看就知道了,其中p是分配的内存,但实际上dev指向的是内存中四字节对齐的地方,即dev!=p,最后添加31个字节就是为了保证dev起始地址是4字节对齐的,当然,private肯定也是四节字对齐的

dev->padded也指明了dev偏移了多少字节

2楼

yo-yo 2012-02-01 19:24发表

[回复]

0818b9ca8b590ca3270a3433284dd417.png

可能与所用的网卡有关,支持napi的网卡,使用的才是设备的poll函数;非napi的就是process_backlog

1楼

cbping 2012-01-30 14:37发表

[回复]

0818b9ca8b590ca3270a3433284dd417.png

work = n->poll(n, weight);

可是我在printk( n->poll),指向的是process_backlog(),kernel版本是2.6.35。

发表评论

用 户 名:

u012566181

评论内容:

0818b9ca8b590ca3270a3433284dd417.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值